Conditional transactions on Robinhood Chain

Attach inclusion conditions to a signed transaction with eth_sendRawTransactionConditional: the five supported options, a deadline taken from the parent-chain l1BlockNumber, knownAccounts state checks, and what -32602 and -32003 each mean.

Endpoint and plans: Robinhood Chain RPC endpoint.

eth_sendRawTransactionConditional submits a signed transaction together with conditions under which it may still be included. If a condition does not hold, the chain refuses the transaction with a -32003 error instead of including it — which is the point: it turns "I hope this is still valid" into something the chain checks.

Before using it, be clear about one boundary, because almost every mistake with this method comes from blurring it: Triport validates the shape of your conditions, but never evaluates them.

Method / Endpoint
eth_sendRawTransactionConditional at POST https://triport.io/rpc/robinhood
Authentication
x-token: $TRIPORT_API_KEY
Required scope
robinhood:rpc
Rate category
robinhood_send_tx — see Limits
Availability
Best-effort through one measured operator, without an availability SLA

New to the endpoint? Start with the Quickstart.

1. Who checks what

Two different systems look at your request, and they check different things:

  • Triport checks the option names and value shapes before fanout. An unknown key or a malformed number, address, slot or value is rejected locally with -32602, with no network submission and no gas cost.
  • The chain evaluates the conditions themselves. Robinhood Chain runs on Arbitrum Nitro, which checks them when a node pre-checks the transaction and again when the sequencer orders it; a failure at either point comes back as -32003.

So a -32602 from us means "your options are not well-formed", and a -32003 means "your options were fine, but a condition did not hold". Section 5 shows how to handle each.

2. The five options

params is positional: the signed raw transaction, then the options object. Only these keys are accepted:

OptionCompared with
blockNumberMin, blockNumberMaxThe parent-chain block number (l1BlockNumber in a Robinhood Chain block), inclusive — not eth_blockNumber.
timestampMin, timestampMaxThe Robinhood Chain block timestamp in Unix seconds, inclusive.
knownAccountsAddress → storage root, or address → storage-slot/value conditions.

Anything else is an error, not an ignored field. That is a deliberate design: a silently dropped condition would be worse than a rejection, because you would believe you had protection you did not have.

3. A deadline: blockNumberMax from l1BlockNumber

The simplest useful condition is "do not include this after block N". On Robinhood Chain, N is a parent-chain block number. Read it from the latest block with eth_getBlockByNumber:

{"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["latest",false]}

In our measured example, Robinhood Chain block 0x3626e6a (56,782,442) carries "l1BlockNumber": "0x18b95b7" (25,925,047) and "timestamp": "0x6a9e998b". Add your margin to l1BlockNumber — here 25 blocks, 0x18b95d0 — and encode it as a hex quantity:

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

Do not take the bound from eth_blockNumber. It returns the Robinhood Chain number, which is far above the parent-chain number (56,782,442 against 25,925,047 above): a blockNumberMax computed from it never expires, and a blockNumberMin computed from it is never reached, so every submission is refused.

Several Robinhood Chain blocks share one l1BlockNumber, so a block bound is coarse. When your deadline is genuinely about wall-clock time — an expiring quote, for example — use timestampMax instead: the timestamp above plus 60 seconds is "timestampMax": "0x6a9e99c7". A deadline that is too tight throws away transactions that would have been fine; one that is too loose is not a deadline at all.

4. knownAccounts: "only if the state is what I saw"

knownAccounts expresses the condition that matters most in practice: include this transaction only if the state it was built against has not changed. You can pin either an account's whole storage root, or specific storage slots and their expected values.

Read the values you intend to pin with eth_getStorageAt, or take the storage root (storageHash) from eth_getProof, and build the transaction against exactly those values. If they change before the chain checks the transaction, it is refused with -32003 rather than executing against state you never simulated.

Pin narrowly. A whole storage root changes on every write to that account's storage, so a transaction pinned to it will be refused far more often than one pinned to the two slots you actually depend on.

5. Two failures that look similar and are not

Local rejection — malformed options. Returned by us, before any upstream call:

{"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"invalid conditional options: blockNumberMax: must be a non-negative hex or decimal number"}}

The message names the exact field and reason. Nothing was submitted and no gas was spent, so this is a bug in your request builder — fix it and resend. There is a dedicated page for it at /errors/robinhood/invalid-conditional-options.

A condition did not hold. Decided by the chain, not by us, and returned to you as an error; Triport relays the code and message unchanged:

{"jsonrpc":"2.0","id":2,"error":{"code":-32003,"message":"BlockNumberMax condition not met"}}

The message names the condition: 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 met. Nitro's pre-check also re-checks the conditions against a slightly older block (by default one at least 2 seconds old, at most 4 blocks back); a failure there carries the prefix conditions check failed for old state:. Resending the same bytes fails the same way: re-read state, rebuild and re-sign.

The practical rule: treat -32602 as "fix the code" and -32003 as "the world moved on" — the two need completely different responses. Triport's own rate limit is an HTTP 429 rate_limited response, not a JSON-RPC code, so a -32003 on this method is the chain's condition check.

6. Batching

A JSON-RPC batch containing this method is dispatched per item. One invalid element receives its own -32602 and does not block the others, so a batch is not all-or-nothing here.

That also means a batch of N conditional submissions costs N against your rate budget, exactly like N separate calls; batching saves round-trips, not rate budget. See Limits.

7. What this is not

This is the section to read twice, because the method invites optimistic assumptions.

  • It is not private orderflow. Conditional submission is not a guaranteed private-orderflow facility, and this guide makes no such claim.
  • It is not a queue advantage. Robinhood sequencing is first-come, first-served, and a larger priority fee does not move a transaction forward — conditions do not change that either. See Fast submission.
  • It is not a guarantee of inclusion. The sequencer decides; the receipt is the only authoritative outcome.
  • It is not evaluated by us. Repeating it because it is the most common misreading: we validate shapes, the chain evaluates conditions.

If your design needs a hard guarantee that a transaction never executes against changed state, the conditional path reduces that risk but does not remove it. Keep the on-chain checks your contract would need anyway.

8. Tracking the outcome

Use the same discipline as ordinary submission, described in Build a wallet backend: persist the transaction hash before you send, submit once, and reconcile by hash rather than resending. A returned hash means the request was routed, not that the transaction was included.

Add one thing to that loop: a stop condition tied to your own bound. When the l1BlockNumber of the latest block passes your blockNumberMax (or its timestamp passes your timestampMax), the transaction can no longer be included; mark the attempt closed and decide deliberately whether to rebuild with fresh state — do not poll forever.

Frequently asked questions

Does Triport check whether my conditions hold? No. We validate the option names and shapes; the chain evaluates the conditions. A -32602 from us is always about the form of your request.

Which block number do blockNumberMin and blockNumberMax use? The parent-chain block number that each Robinhood Chain block carries as l1BlockNumber, not the number eth_blockNumber returns. Timestamps use the Robinhood Chain block timestamp.

Is gas spent on a local rejection? No. The request never reaches an upstream, so nothing is spent.

Can I use a field that is not in the list of five? No — it is rejected rather than ignored. That is intentional: silently dropping an unrecognised condition would leave you believing in protection you do not have.

Does one bad element ruin my batch? No. Conditional requests are dispatched per item, so the invalid element gets its own error and the rest proceed.

I got -32003 ... condition not met. What now? A condition did not hold when the chain checked the transaction. If it is a block or timestamp bound, check that you built it from l1BlockNumber and the block timestamp; if it is a storage condition, the state changed. Re-read state, rebuild and re-sign.

My transaction never landed and I got no error. Why? A failed condition comes back as -32003, so a hash without a receipt is a submission still to reconcile, not a silent condition failure. Reconcile by hash, and close the attempt once the parent-chain l1BlockNumber has passed your blockNumberMax or block time has passed your timestampMax.

Sources

The chain-side behaviour — which values the options are compared with, the -32003 code and its messages, the old-state re-check and its defaults — is from the Arbitrum Nitro source: Offchain Labs arbitrum_types/txoptions.go, Nitro execution/gethexec/tx_pre_checker.go and execution/gethexec/sequencer.go. The example block is our measured eth_getBlockByNumber example.

Terms on this page: Sequencer · JSON-RPC · Priority fee.