Tronergydocs

Errors and retries

Handle common errors and retry safely without sending a payment twice.

Every error looks like this:

Error response
{
  "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 failed with an errorCode. 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.
pay.ts
import { randomUUID } from 'node:crypto'

const idempotencyKey = randomUUID()
await savePayment({ orderId, idempotencyKey }) // save it before you call the API

Rules:

  • 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 5xx response
  • 429: wait Retry-After seconds first
  • 422 INSUFFICIENT_BALANCE: top up first

Do not retry as is. Fix the request first:

  • 400 transaction errors such as TX_EXPIRED or TX_NOT_SIGNED: build, sign again, and use a new key.
  • 422 Validation failed: read details.fieldErrors and fix the body.
retry.ts
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.