Перейти к содержимому
PortunaPortunaPortunaДокументацияTestnet

TypeScript SDK

TypeScript SDK умеет то же, что и Go SDK. Это ESM для Node.js 20 и новее; единственная зависимость — viem, как peer-зависимость.

Окно терминала
(cd <sdk>/ts && npm ci) # dependencies and the build in dist/
npm install viem <sdk>/ts # in your project
ОбластьAPI
Клиент APInew Client(...) с методом на каждый запрос API; ошибки — ApiError
АвторизацииauthorizationDigest, authorizationFromSignature, parseRawSignature, normalizeS, verifyAuthorization
Снятие делегацииrevocationDigest, revocationFromSignature, verifyRevocation
Спискиsweeps, revocations, ledgerEntries, webhookDeliveries — по странице; allSweeps и другие — по всем страницам
ВебхукиverifyWebhook и parseWebhook на Web Crypto; verifyWebhookSync и parseWebhookSync на node:crypto — из @portuna/sdk/node
Проверка делегатаverifyDeployment, factoryAddress, batcherAddress, delegateAddress и команда verify-delegate
Типывсе запросы и ответы API с именами полей как в API: Sweep, HotWallet, Event…
import { Client } from '@portuna/sdk'
const client = new Client({ baseUrl: 'https://api-testnet.portuna.io', apiKey: process.env.PORTUNA_API_KEY! })
const balance = await client.balance({ signal: AbortSignal.timeout(10_000) })

Параметры: baseUrl, apiKey и, при желании, свой fetch. Каждый метод принимает { signal } — для отмены и таймаутов. Методы: balance, tariff, settings, updateSettings, hotWallets, createHotWallet, createSweep, sweep, sweeps, createRevocation, revocation, revocations, ledgerEntries, usage, webhook, setWebhook, deleteWebhook, webhookDeliveries, sendTestWebhook, health и openapi. createSweep и createRevocation возвращают { sweep, created } и { revocation, created }: created — false, если external_id уже использовался и вернулся существующий объект.

sweeps, revocations, ledgerEntries и webhookDeliveries принимают фильтр с параметрами запроса API — например, { status: 'failed', limit: 100 } — и возвращают одну страницу, { items, next_cursor }. Методы all — allSweeps и другие — проходят все страницы как асинхронный итератор:

for await (const sweep of client.allSweeps({ status: 'failed' })) {
console.log(`sweep ${sweep.id} failed: ${sweep.reason}`)
}

Любой ответ, кроме 2xx, бросает ApiError с полями status, code и message; code пуст, если ответ пришёл не в формате ошибок API — например, от прокси. Коды и что с ними делать — в гайде Ошибки и повторы.

authorizationDigest даёт хэш, который подписывает ваша кастоди. authorizationFromSignature принимает «сырую» подпись — DER или r‖s, в hex или байтах, с любым s, — нормализует её, подбирает y_parity и бросает исключение, если подписал не депозитный адрес. Сеть 0 не принимается.

export async function signAuthorization(
sign: Signer,
keyId: string,
deposit: Address,
chainId: number,
delegate: Address,
nonce: number,
): Promise<Authorization> {
const digest = authorizationDigest({ chainId, address: delegate, nonce }) // refuses chain 0
const signature = await sign(keyId, digest)
// Reads DER or r‖s, moves s to the lower half and finds y_parity. Throws unless the deposit address signed
// this very digest, so a wrong key never reaches the API.
return authorizationFromSignature({ chainId, address: delegate, nonce, deposit, signature })
}

Подробнее — в гайде Подпись авторизаций.

parseWebhookSync(secret, header, body) из @portuna/sdk/node проверяет заголовок Portuna-Signature по сырому телу — в пределах пяти минут — и возвращает типизированное Event, а иначе бросает WebhookSignatureError. Вне Node.js используйте асинхронный parseWebhook из @portuna/sdk.

function handle(event: Event): void {
switch (event.type) {
case 'sweep.swept':
case 'sweep.failed':
case 'sweep.rejected':
// Events may come out of order: the status in the data is the current one.
console.log(`sweep ${event.data.id} (${event.data.external_id}): ${event.data.status}`)
break
case 'balance.low':
console.log(`top up the gas wallet on chain ${event.data.chain_id}: ${event.data.balance} wei left`)
break
default:
// Other and future event types: accept them.
}
}
createServer((req, res) => {
const chunks: Buffer[] = []
req.on('data', (chunk: Buffer) => chunks.push(chunk))
req.on('end', () => {
// Verify the body exactly as it arrived, before parsing it.
const body = Buffer.concat(chunks)
let event: Event
try {
event = parseWebhookSync(secret, String(req.headers[signatureHeader.toLowerCase()] ?? ''), body)
} catch {
res.writeHead(401).end()
return
}
// Delivery is at least once: an event may come again, with the same id.
if (!handled.has(event.id)) {
handle(event)
handled.add(event.id)
}
res.writeHead(204).end()
})
}).listen(8080)

verifyDeployment принимает любой клиент с getChainId и getCode — например, PublicClient из viem:

const chain = createPublicClient({ transport: http(process.env.RPC_URL) })
const report = await verifyDeployment(chain, {
gasWallet: process.env.GAS_WALLET as Address,
hotWallet: process.env.HOT_WALLET as Address,
expectDelegate: process.env.DELEGATE as Address,
})
for (const c of report.checks) console.log(c.ok ? 'PASS' : 'FAIL', c.name, c.detail)
if (!report.ok) throw new Error('do not sign authorizations for this delegate')

Команда verify-delegate делает то же из командной строки: см. Проверка делегата.