Skip to main content
Relay validates policy, availability, and signatures before scheduling an asynchronous broadcast. Both REST and WebSocket submission methods can fail immediately. A BROADCAST response means Relay accepted and scheduled the signed transaction; eth_sendRawTransaction may not have returned yet. It does not prove provider acceptance, inclusion, or execution. REST does not deliver later confirmation, replacement, or revert information, so REST clients must independently query chain receipts and reverts. A successful WebSocket submission is automatically tracked for terminal-outcome delivery on the submitting connection; it does not need a separate transaction.subscribe request. Branch on the stable code, HTTP status, and retryable field. Do not branch on message text. Messages are diagnostic and may change.

Immediate response states

Public error codes

Retry only when retryable is true, using bounded backoff and any supplied retryAfterMs. Never blindly create a new logical transaction after BROADCAST_RESULT_UNKNOWN. STATE_UNAVAILABLE is about the state snapshot needed to plan safely. SIGNER_QUEUE_FULL is capacity exhaustion; SIGNER_LANE_UNAVAILABLE means the wallet’s pinned sponsor lane is unsafe. BROADCAST_REJECTED is definitive provider rejection, while BROADCAST_RESULT_UNKNOWN is uncertainty. SIGNER_POLICY_DECLINED is a deterministic signer-policy decision; an unchanged retry will not help.

Client flow examples

Validation rejection (REST):
Successful REST submission:
The REST response ends here; query the chain yourself for receipt, replacement, or revert. WebSocket rejection and broadcast:
After a successful WebSocket submission, the same connection automatically receives transaction.outcome with the original client id, Relay requestId, txHash, and CONFIRMED, REVERTED, REPLACED, or UNKNOWN. No client outcome acknowledgement is required. An immediate rejection creates no outcome subscription. A reverted custom error includes its pinned selector/name; an undecodable revert keeps selector/data and uses message unknown.

WebSocket transaction outcomes

An accepted WebSocket submission automatically receives an outcome on its submitting connection, using the original id. Use transaction.subscribe primarily after reconnecting, when you retained both the server-assigned requestId and txHash:
If the transaction is still pending, Relay first responds with {"id":"sub-1","status":"SUBSCRIBED","requestId":"0192...","txHash":"0x...","retryable":false} and later sends its outcome. If a terminal outcome is still retained, Relay sends that outcome immediately instead of SUBSCRIBED. The outcome envelope is:
CONFIRMED means Relay checked finality and observed a successful outer transaction. The currently deployed implementation does not prove that the intended trading-wallet action executed inside it. Reconcile the expected order, trade, and balance events or resulting state before marking a logical action complete. REVERTED means the outer transaction reverted; REPLACED means the candidate was replaced (with replacementTxHash when known); UNKNOWN means no stronger terminal result is available. Revert sources are trace, historical_call, post_block_call (lower confidence), or unknown. The signer retains the latest 4,096 terminal outcomes per signer, shared across all users of that signer. This is a count limit, not a time-based retention guarantee: high traffic can evict an outcome quickly. Retention is in memory; do not assume replay survives a signer restart. Keep your own durable submission journal and use chain data when an outcome is no longer available.

Connection closure and recovery

The current configuration allows 64 pending outcome slots per client WebSocket. A tracked transaction occupies one until its terminal outcome is processed for delivery, not until a client acknowledgement; no acknowledgement is required. This is configurable outstanding tracking, not a lifetime limit of 64 submissions, the separate in-flight-request limit, or a sponsor-nonce limit. Immediate submission rejections take no outcome slot. If capacity is exhausted, Relay may close the socket while registering another outcome after writing its BROADCAST response. This does not undo the scheduled broadcast or prove the transaction failed. After a disconnect, preserve all unresolved requestId/txHash pairs and the last observed transaction.outcome. Reconnect with a fresh wallet-bound session; resubscribe to known transactions while their outcomes are retained, and use chain receipts or state to resolve those no longer available from Relay. Use bounded, asynchronous recovery work so receipt/log lookups do not halt the trading submission loop. A roughly 50-second pause during reconnect is not a Relay-prescribed backoff; investigate the client connector’s reconciliation and RPC queries. There is a harder lost-reply gap if the connection drops before the BROADCAST response supplies requestId and txHash: transaction.subscribe cannot look up that submission using only its WS id. The id provides connection-local correlation and duplicate handling, not durable cross-connection idempotency. There is currently no public Relay lookup by wallet plus nonce, client id, or clientOrderId; a dedicated client-order-ID lookup is planned but not live. Retrying the old id does not establish whether the earlier submission was scheduled or sent. For wallet.execute_batch and wallet.execute_replace_by_slot_packed, set a unique, nonzero, signed 32-byte payload.header.clientOrderId for each logical batch and persist it alongside the account, market, signed intent, and WS id. This is a batch-level correlation ID, not an order ID or an idempotency key; Relay does not enforce uniqueness, and one batch can yield multiple order events. If the reply is lost, page through Data Source GET /api/v1/users/{userId}/order-events for the traded user ID, following pagination.nextCursor. Filter returned rows by clientOrderId and confirm the account and market; matching rows can supply transactionHash and events. There is no documented server-side clientOrderId filter. The endpoint records resting-maker lifecycle events, not every taker-only IOC or other possible action. See the order-events reference. No matching event does not prove failure. The transaction may still be pending, indexing may lag, it may have reverted, or it may have executed without an event represented by that endpoint. A signed deadline expiring later does not rule out execution before expiry. Reconcile available chain receipts, wallet nonce, trading events, and account state before resubmitting the logical action. Revert decoding recognizes Solidity Error(string) (0x08c379a0) and Panic(uint256) (0x4e487b71), plus custom errors from the pinned compiled ABI catalog. Unknown selectors retain exact selector and data, with name omitted and message unknown. Decoded text is bounded and untrusted.

Outcome subscription errors

transaction.subscribe can return MALFORMED_REQUEST, INVALID_BINDING, TRANSACTION_OUTCOME_NOT_FOUND, TRANSACTION_OUTCOME_ALREADY_SUBSCRIBED, TRANSACTION_OUTCOME_EXPIRED, STATE_UNAVAILABLE, or INTERNAL_ERROR. These errors refer to lookup/subscription state, not a new broadcast attempt.

Generated custom-error appendix

The following entries are generated from the pinned AccountCore and KuruTradingWallet bindings. They are decoding names only; they are not Relay error codes. Regenerate this appendix whenever the pinned generated bindings or ABI catalog changes.