Errors and retries
Errors come with an HTTP status and a JSON body:
{ "error": { "code": "invalid_authorization", "message": "invalid authorization: chain 1, expected 11155111" } }code is stable: branch on it. message is for people and may change.
| Status | code | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_json | The body is not JSON, has unknown fields, or is over 64 KiB. | Fix the request. |
| 400 | invalid_query | A query parameter of a list is not valid; message says which. | Fix the parameter. |
| 401 | unauthorized | No API key, an unknown one or a revoked one. | Check the key and the environment: a test key works only on the testnet API. |
| 403 | partner_disabled | Your account is disabled. | Contact support. |
| 404 | not_found | No such sweep or revocation for your account, or no webhook set. | Check the id. |
| 409 | hot_wallet_not_active | The hot wallet’s delegate is still being deployed, or its deployment failed. | Wait for hot_wallet.active; after failed, repeat POST /v1/hot-wallets. |
| 422 | unknown_chain | The chain is not supported in this environment. | Use a chain of the environment. |
| 422 | unknown_hot_wallet | The hot wallet is not registered on this chain. | Register it with POST /v1/hot-wallets. |
| 422 | unknown_token | The token is not listed on this chain. | Use a listed ticker, its address or native. |
| 422 | invalid_address | An address is not a non-zero 0x-address. | Fix the address. |
| 422 | invalid_amount | An amount is not a positive integer in the smallest units. | Send a string of digits, or leave amount out to sweep everything. |
| 422 | invalid_external_id | external_id is longer than 128 characters. | Use a shorter id. |
| 422 | invalid_authorization | The authorization is for another chain or delegate, is not signed by the deposit address, or its signature is invalid. | See below. |
| 422 | invalid_url | The webhook URL is not a public https URL. | Use a public https URL. |
| 500 | internal | Something failed on our side. | Retry later with the same external_id. |
A disabled account keeps what it already sent on chain: those transactions are seen through. Queued sweeps, revocations and deployments wait, and continue once the account is enabled again. Webhooks keep coming.
invalid_authorization
Section titled “invalid_authorization”message starts with | Cause |
|---|---|
invalid authorization: chain … | Signed for another chain, or for chain 0. |
invalid authorization: delegates to … | Signed for something other than this hot wallet’s delegate (for a revocation: other than the zero address). |
invalid authorization: signature … | A high s, or r or s out of range. |
signed by … | Another key signed: a wrong key, a wrong y_parity or a wrong digest. |
authorization is required … | A revocation came without its authorization. |
| anything else | A malformed field: address, y_parity, r or s. |
More in Signing authorizations.
Outcomes are not errors
Section titled “Outcomes are not errors”A request that is accepted can still end badly: a sweep rejected before sending or failed on chain, with a
reason, or a hot wallet whose delegate was not deployed, with an error. Those come in webhooks and in
GET /v1/sweeps/{id}. Like code, they are stable codes, such as not_upgraded or insufficient_gas_balance, and
never the node’s own messages: every one is in Statuses, confirmations and reorgs.
Retries
Section titled “Retries”- Retry network errors, timeouts and
5xx, with a growing pause, and with the sameexternal_idso that a sweep is never queued twice: see Idempotency. - Do not retry
4xxas they are: the same request gets the same answer.409 hot_wallet_not_activeis the exception: it passes once the delegate is deployed.
In the SDKs
Section titled “In the SDKs”API errors are *portuna.Error with StatusCode, Code and Message; portuna.ErrorCode(err) gives the
code, or "" for other errors such as a network failure.
_, _, err := client.CreateSweep(ctx, req)switch portuna.ErrorCode(err) {case "": if err != nil { // network error or timeout: retry with the same ExternalID }case "invalid_authorization": // sign againcase "hot_wallet_not_active": // wait for hot_wallet.activedefault: // fix the request}API errors are ApiError with status, code and message. Other errors, such as a network failure, are not.
import { ApiError } from '@portuna/sdk'
try { await client.createSweep(request)} catch (err) { if (err instanceof ApiError && err.code === 'invalid_authorization') { // sign again } else if (!(err instanceof ApiError) || err.status >= 500) { // retry with the same external_id } else { throw err }}