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 |
|---|---|
| Клиент API | new 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 делает то же из командной строки: см. Проверка делегата.