Skip to content
Guides4 min read

How to Retry an Energy Rental API Request Without Paying Twice

Oliver BentleyTRON Research Editor

Short answer: in TRON, idempotency is guaranteed not by an HTTP header but by the txID of the signed transaction. If you've stored the signed payload and its txID, retrying the request with the same payload is safe — the network deduplicates the transaction and won't execute it twice. Double charges don't come from retrying; they come from rebuilding. Any change to the transaction body, down to reordering fields, yields a new txID — that is, a new, separately billed delegation operation.

Two levels of idempotency in energy rental

The network level. Energy rental is a DelegateResourceContract: the resource owner delegates energy to a recipient address, and the recipient uses that quota without staking anything themselves. The transaction parameters you're retrying are: resource (Bandwidth or Energy only), the amount (minimum 1 TRX, i.e. 1,000,000 sun), the recipient (not your own address and not a contract), and the optional lock_period. Important: lock_period is measured in blocks, not seconds — at a 3-second block interval, 86,400 blocks ≈ 3 days, while the current maximum is set by network parameter #78 and can be queried via wallet/getchainparameters. Never hardcode the period: if the parameter changes, so does the transaction body (delegateresource).

The provider's business API level. Here idempotency relies on your own order identifier: an external order_id, unique in your database and passed with every retry. This is a general pattern for reliable write operations, not documented behavior of any particular rental service: a repeat commit with the same key should return the already-recorded entry rather than create a new one. If the provider's API doesn't offer such a field, you can't retry provisioning requests automatically — only through manual reconciliation against the provisioning history.

Retries: what's safe and what isn't

OperationSafe to auto-retry
estimateenergy, triggerconstantcontract, cost calculationYes, read-only
Reading order status, gettransactionbyidYes
Broadcasting the same signed payloadYes, the txID doesn't change
Creating a new order without order_idNo
Rebuilding and re-signing the transactionOnly after verification (see below)

The rule is simple: reads can be retried freely; remote writes are never retried automatically until idempotency has been proven.

Why 200 OK, result: true, and "duplicate" prove nothing

result: true in a broadcast response only means the call completed without a reported error. It does not prove that the transaction stayed in the pending pool, propagated across the network, made it into a block, executed successfully, and was finalized. Finality is verified via /walletsolidity/gettransactionbyid and gettransactioninfobyid; success means the top-level result is not FAILED and receipt.result = SUCCESS.

The second trap: if an exception occurs during processing, the HTTP status may still be 200 while the body contains a single Error field. A retry layer that makes decisions based solely on HTTP codes will let such a response slip through.

The third is the least intuitive. DUP_TRANSACTION_ERROR usually means the same txID is already in the node's pending pool or in a block, but it does not prove inclusion in the chain: with node.rpc.trxCacheEnable turned on, the only match may have been the broadcast RPC cache. If you get this error, don't mark the order as successful — query the transaction and receipt by txID (broadcast errors).

When you may build a replacement transaction

By default, raw_data.expiration is the construction time plus 60 seconds (24 hours maximum), so "60 seconds" isn't a universal constant. And more importantly: your local clock passing expiration is not proof of non-inclusion.

  1. Wait until the timestamp of the latest solidified block on a reliable, synced node is ≥ the original transaction's expiration. A block is considered solidified once at least 19 active SRs have a latestBlockNum no lower than its number — typically about a minute.
  2. Query the original txID on the solidity endpoint. An empty response proves nothing on its own: the node may not have finalized the relevant block yet — the outcome remains unknown and polling must continue.
  3. Only once you've confirmed the txID is absent from the solidified canonical chain should you rebuild the transaction from a fresh TAPOS reference. The old txID is not reused.

Immediate rebuilding is acceptable in exactly one case: the transaction was never sent to any node and expired before the first broadcast attempt. If it was sent, or the outcome is unknown, you go with a safe re-broadcast.

Transport-level retries under load

On 429 and 403, retry with exponential backoff and jitter — otherwise several service instances will retry in lockstep. On 403, first check the API key and a possible temporary block. Honor the Retry-After header if present. Design polling around block production: a new block appears roughly every 3 seconds, and polling more often is pointless (rate limits).

Implementation checklist

  • Estimate energy via estimateenergy/triggerconstantcontract before broadcasting: fee_limit (sun) = energy_required × getEnergyFee plus a buffer.
  • Store the signed payload and txID before the first send — that's your idempotency key.
  • For the provider's API, generate order_id on your side and pass it with every retry.
  • Separate retryable and non-retryable operations; set the "success" status only on solidified confirmation.
  • Introduce an "unknown" status — without it, the system will start spawning replacements and paying twice.

Conclusion

Idempotency in TRON energy rental rests on two identifiers: the txID at the network level and your order_id at the API level. Every double charge begins the moment a service decides "it probably didn't go through" — which is why that decision must be based on the chain's solidified state, not on a timeout, an HTTP code, or the word "duplicate" in an error message.

Read next

Guides
How API limits and error codes work in TRON energy rental: TronGrid rate limits, broadcast errors, OUT_OF_ENERGY and fee_limit, retries without paying twice.
Oliver Bentley · 5 min read
Guides
How to combine webhooks and polling to reliably confirm energy delivery on TRON: finality levels, GetDelegatedResourceV2, idempotency and endpoint protection.
Oliver Bentley · 4 min read
Guides
How TRX perpetual futures work, how a cash-settled contract on Moscow Exchange crypto indices differs from buying the coin, and what it means for TRON in Russia.
Oliver Bentley · 3 min read