Errors and retries
Handle common errors and retry safely without sending a payment twice.
Every error looks like this:
{
"ok": false,
"error": "TX_EXPIRED",
"message": "Transaction expired",
"hint": "Call POST /construct-tx to build a fresh transaction."
}Match on error. Show message or hint to your team when you log it.
There are two kinds of error:
- Request errors come back straight away. Nothing was broadcast or charged.
- Operation errors happen later, while the relay runs. The operation ends as
failedwith anerrorCode. See Track status.
Idempotency keys
Every execute call needs an idempotencyKey, a UUID you create. It stops a retry from relaying the same payment twice.
- The first request with a key creates the operation (
202). - Any later request with the same key returns that same operation (
200). Nothing new is created.
import { randomUUID } from 'node:crypto'
const idempotencyKey = randomUUID()
await savePayment({ orderId, idempotencyKey }) // save it before you call the APIRules:
- One new UUID per relay. Reuse it only to retry the same relay.
- A key that already failed still returns the failed operation. To try again, use a new key and usually a freshly signed transaction.
- Keys are shared across your organization. A key used by a teammate returns
409 IDEMPOTENCY_KEY_CONFLICT.
When to retry
Retry with the same key:
- Timeouts and connection errors
- Any
5xxresponse 429: waitRetry-Afterseconds first422 INSUFFICIENT_BALANCE: top up first
Do not retry as is. Fix the request first:
400transaction errors such asTX_EXPIREDorTX_NOT_SIGNED: build, sign again, and use a new key.422 Validation failed: readdetails.fieldErrorsand fix the body.
export async function executeWithRetry(body: object, attempts = 5) {
for (let attempt = 1; attempt <= attempts; attempt++) {
try {
const res = await fetch('https://apiv2.tronergy.io/api/chain_relayers/portal/execute', {
method: 'POST',
headers: { 'content-type': 'application/json', 'x-api-key': process.env.TRONERGY_API_KEY! },
body: JSON.stringify(body), // same idempotencyKey every time
})
if (res.status === 429) {
const seconds = Number(res.headers.get('retry-after') ?? '1')
await new Promise((resolve) => setTimeout(resolve, seconds * 1000))
continue
}
if (res.status >= 500) throw new Error(`HTTP ${res.status}`)
return await res.json()
} catch (error) {
if (attempt === attempts) throw error
await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 250))
}
}
}Common errors
Before you resend a failed relay
Check txHash on the failed operation and look it up on Tronscan. If the user's transaction landed, do not send it again.
See errors for the full list of codes.