Conditional transaction submission

Robinhood's catalog includes eth_sendRawTransactionConditional. Call it at POST /rpc/robinhood with robinhood:rpc scope; it charges robinhood_send_tx. For the product view — who checks what, plan limits and a chart from each response to the next step — see Conditional Transactions API on Robinhood Chain.

Endpoint and plans: Robinhood Chain RPC endpoint.

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_sendRawTransactionConditional",
  "params": ["0xSIGNED_TRANSACTION", {"blockNumberMax": "0x18b95d0", "timestampMax": "0x6a9e99c7"}]
}

The bounds in this example come from one Robinhood Chain block in our measured eth_getBlockByNumber example: block 0x3626e6a (56,782,442) carries "l1BlockNumber": "0x18b95b7" (25,925,047) and "timestamp": "0x6a9e998b". The request allows 25 more parent-chain blocks (0x18b95b7 + 25 = 0x18b95d0) and 60 more seconds (0x6a9e998b + 60 = 0x6a9e99c7).

Which number each bound is compared with

Robinhood Chain runs on Arbitrum Nitro, and Nitro compares the options with these values:

OptionCompared withRead it from
blockNumberMin, blockNumberMaxThe parent-chain block number, inclusive — not the Robinhood Chain block number.l1BlockNumber of eth_getBlockByNumber("latest", false). Not eth_blockNumber.
timestampMin, timestampMaxThe Robinhood Chain block timestamp in Unix seconds, inclusive.timestamp of the same block.
knownAccountsEach account's storage root, or individual storage slot values. Pin narrowly: a whole storage root changes on every write to that account's storage, so a transaction pinned to it is refused far more often than one pinned to the specific slots you depend on.eth_getProof (storageHash) or eth_getStorageAt.

The block-number trap. eth_blockNumber returns the Robinhood Chain number, which is far above the parent-chain number (56,782,442 against 25,925,047 in the block above). A blockNumberMax computed from eth_blockNumber therefore never expires, and a blockNumberMin computed from it is never reached, so every submission is refused. Several Robinhood Chain blocks share one l1BlockNumber, so the block bound is coarse; when the deadline is about time, use timestampMax.

Validation by Triport

The options object accepts only blockNumberMin, blockNumberMax, timestampMin, timestampMax and knownAccounts. Triport validates key names and value shapes before fanout. An unknown key, malformed number, address, slot, or value returns the exact local error:

-32602 invalid conditional options: <field>: <reason>

For example blockNumberMax: must be a non-negative hex or decimal number, paysCoinbase: unknown field, or knownAccounts: 0x…: address must be 20-byte hex. Number bounds accept a hex quantity or a decimal number; send hex, the JSON-RPC convention.

The proxy does not evaluate conditions. A structurally valid request is passed to an eligible submission target, where the chain evaluates them. A local validation reject is free of network submission and gas cost. In a JSON-RPC batch, conditional requests are dispatched per item, so one invalid element gets its own -32602 without blocking other elements.

What comes back

ResponseWho decidedWhat to do
-32602 invalid conditional options: <field>: <reason>Triport, before any upstream call. Nothing was submitted and no gas was spent.A bug in your request builder: fix the named field and resend.
-32003 BlockNumberMin condition not met, BlockNumberMax condition not met, TimestampMin condition not met, TimestampMax condition not met, Storage root hash condition not met or Storage slot value condition not metThe chain: a condition did not hold when the node checked the transaction. Triport relays the code and message unchanged.The world moved on. Re-read state, rebuild and re-sign; resending the same bytes fails the same way.
-32003 conditions check failed for old state: followed by one of the messages aboveThe chain: Nitro's pre-check also checks the conditions against a slightly older block (by default one at least 2 seconds old, at most 4 blocks back).Same as above. A slot that changed in the last few blocks counts as changed.
-32000 upstream unavailable (HTTP 503)Triport: no submission target answered.Nothing was accepted. Resend the same signed transaction after a pause; the hash does not change.
"result": "0x…" (transaction hash)The first submission target to answer.Routed, not proof of inclusion: confirm with eth_getTransactionReceipt.

Nitro checks the conditions twice — when a node pre-checks the transaction and again when the sequencer orders it — and a failure at either point is returned to the caller as a -32003 error. A condition that does not hold is an error you can read, not a silent drop.

The practical rule: treat -32602 as "fix the code" and -32003 as "rebuild against fresh state" — the two need completely different responses. Triport's own rate limit is the HTTP 429 rate_limited response from the common error reference, not a JSON-RPC code, so a -32003 on this method is the chain's condition check. If a returned hash has no receipt yet, reconcile by hash; once the parent-chain l1BlockNumber has passed your blockNumberMax (or block time has passed your timestampMax), the transaction can no longer be included, so stop waiting and decide whether to rebuild.

It is not a private-orderflow facility: the sequencer decides inclusion. See the generated eth_sendRawTransactionConditional reference and the step-by-step guide.

Sources for the chain-side behaviour: Offchain Labs arbitrum_types/txoptions.go (option fields, the -32003 code and messages, the comparisons), Nitro execution/gethexec/tx_pre_checker.go (pre-check against l1BlockNumber, the old-state check and its defaults) and execution/gethexec/sequencer.go (the check when the sequencer orders the transaction).