> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kuru.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Relay errors and outcomes

> Stable error codes, retry guidance, and transaction outcomes for Relay clients.

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

| State | Meaning |
| - | - |
| `BROADCAST` | Relay accepted and scheduled the candidate for asynchronous RPC broadcast; provider acceptance is not yet proved. |
| `REJECTED` | Relay rejected the request or the provider definitively rejected the candidate. |
| `UNKNOWN` | Relay cannot determine whether the provider accepted the candidate (`BROADCAST_RESULT_UNKNOWN`). Reconcile the supplied candidate identity before deciding what to do. |
| `SUBSCRIBED` | A WebSocket `transaction.subscribe` request found a pending transaction and is waiting for its outcome. |

## 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`.

| Code | HTTP | Retryable | Meaning | Recommended client action | Transport / operation |
| - | -: | :-: | - | - | - |
| `MALFORMED_REQUEST` | 400 | No | Invalid JSON or request shape | Fix the request | REST/WS, all methods |
| `UNKNOWN_METHOD` | 400 | No | Method is not supported | Use a documented method | REST/WS |
| `UNSUPPORTED_CHAIN` | 400 | No | Chain is not configured | Select a supported chain | Submission/auth |
| `UNSUPPORTED_WALLET` | 400 | No | Wallet type is unsupported | Use a supported wallet | Submission/auth |
| `UNSUPPORTED_MARKET` | 400 | No | Market is unsupported | Use a supported market | Trading methods |
| `DEADLINE_EXPIRED` | 400 | No | Signed deadline has passed | Sign a fresh request | Submission |
| `DEADLINE_TOO_FAR` | 400 | No | Deadline exceeds the allowed horizon | Use a nearer deadline | Submission |
| `NONCE_OUTSIDE_WINDOW` | 400 | No | Nonce is outside the accepted window | Reconcile nonce and sign again | Submission |
| `NON_INCREASING_SESSION_NONCE` | 400 | No | WS wallet-intent session nonces must strictly increase | Reconcile and send a larger nonce | WebSocket submission |
| `INVALID_SIGNATURE` | 400 | No | Signature does not verify | Sign the exact canonical payload | Submission |
| `INVALID_BINDING` | 400 | No | Request binding does not match | Correct wallet/order/transaction binding | Submission; outcome subscription |
| `INVALID_TRIGGER_CONDITION` | 400 | No | Trigger condition is invalid | Correct trigger fields | Trigger methods |
| `INVALID_7702_AUTHORIZATION` | 400 | No | EIP-7702 authorization is malformed | Correct authorization fields | Authorization-enabled methods |
| `7702_CHAIN_MISMATCH` | 400 | No | Authorization chain differs from request chain | Sign for the requested chain | Authorization-enabled methods |
| `7702_DELEGATE_MISMATCH` | 400 | No | Delegate differs from the configured delegate | Use the configured delegate | Authorization-enabled methods |
| `7702_AUTHORITY_MISMATCH` | 400 | No | Authority differs from the wallet | Use the wallet as authority | Authorization-enabled methods |
| `7702_NONCE_MISMATCH` | 400 | No | Authorization nonce is stale or incorrect | Re-read and sign current nonce | Authorization-enabled methods |
| `7702_UNSUPPORTED_CODE_STATE` | 400 | No | Account code state cannot use EIP-7702 path | Use the supported account state | Authorization-enabled methods |
| `INVALID_ACCOUNT_AUTHORIZATION` | 400 | No | Account signer authorization is invalid | Correct signer authorization | `account_core.authorize_account_signer_by_sig` |
| `ACCOUNT_AUTH_NONCE_MISMATCH` | 400 | No | Account authorization nonce is stale | Re-read nonce and sign again | Account authorization |
| `ACCOUNT_PERMISSION_DECLINED` | 400 | No | Account permission request was declined | Change authorization request | Account authorization |
| `SIGNER_POLICY_DECLINED` | 400 | No | Signer rejected the policy | Changing transport or retrying unchanged will not help | Submission |
| `OPERATIONAL_SIMULATION_REVERTED` | 400 | No | An opt-in operational method explicitly reverted in bounded simulation; no transaction was signed or broadcast | Refresh state and correct the request | Operational methods |
| `AUTHENTICATION_FAILED` | 401 | No | JWT is missing, invalid, expired, or wallet-bound incorrectly | Obtain a new wallet-bound JWT | REST/WS protected access |
| `POLICY_DECLINED` | 403 | No | Relay policy rejected the request, including an operational buffered gas estimate above its method cap | Change the request or policy inputs | Submission |
| `WALLET_NOT_ALLOWLISTED` | 403 | No | Wallet is not allowlisted | Use an allowlisted wallet | REST/WS |
| `WALLET_NOT_DELEGATED` | 403 | No | Wallet lacks required delegation | Install or use delegation | Submission |
| `TRIGGER_REGISTRATION_DECLINED` | 403 | No | Trigger registration policy rejected | Change trigger or policy inputs | Trigger registration |
| `TRANSACTION_OUTCOME_NOT_FOUND` | 404 | No | Requested outcome identity is unknown | Verify `requestId`/`txHash` | WS `transaction.subscribe` |
| `METHOD_NOT_ALLOWED` | 405 | No | HTTP method is not accepted | Use the documented method | REST |
| `TRIGGER_REGISTRATION_CONFLICT` | 409 | No | Trigger conflicts with an existing registration | Reconcile existing trigger | Trigger registration |
| `TRANSACTION_OUTCOME_ALREADY_SUBSCRIBED` | 409 | No | This WS session already subscribed to the transaction | Reuse the existing subscription | WS `transaction.subscribe` |
| `TRANSACTION_OUTCOME_EXPIRED` | 410 | No | The terminal outcome was evicted from the bounded signer store | Query the chain directly | WS `transaction.subscribe` |
| `REQUEST_TOO_LARGE` | 413 | No | Request exceeds configured limits | Reduce request size | REST/WS |
| `UNSUPPORTED_MEDIA_TYPE` | 415 | No | Content type is unsupported | Send JSON with the documented type | REST |
| `RATE_LIMITED` | 429 | Yes | Rate limit exceeded | Honor `retryAfterMs` and back off | REST/WS |
| `BROADCAST_REJECTED` | 502 | No | Provider definitively rejected the candidate | Surface rejection; reconcile candidate before any new logical transaction | Submission |
| `AUTHENTICATION_UNAVAILABLE` | 503 | Yes | Authentication service unavailable | Bounded backoff | Auth/protected access |
| `WALLET_REGISTRY_UNAVAILABLE` | 503 | Yes | Wallet registry unavailable | Bounded backoff | REST/WS |
| `STATE_UNAVAILABLE` | 503 | Yes | Required exchange-state snapshot is missing, stale, incompatible, or temporarily unavailable | Bounded backoff, then reconcile state | Submission |
| `GAS_POLICY_UNAVAILABLE` | 503 | Yes | Gas policy or bounded operational estimation unavailable | Bounded backoff; do not treat a generic estimator failure as a contract revert | Submission |
| `ACCESS_LIST_POLICY_UNAVAILABLE` | 503 | Yes | Access-list policy unavailable | Bounded backoff | Submission |
| `SIGNER_QUEUE_FULL` | 503 | Yes | Signer admission capacity is exhausted | Back off; do not duplicate the logical request | Submission |
| `SIGNER_LANE_UNAVAILABLE` | 503 | Yes | Pinned sponsor lane is unsafe (readiness, nonce reconciliation, RPC health, or contract verification) | Back off until lane health returns | Submission |
| `TRIGGER_REGISTRATION_UNAVAILABLE` | 503 | Yes | Trigger registration service unavailable | Bounded backoff | Trigger registration |
| `ACCOUNT_AUTHORIZATION_UNAVAILABLE` | 503 | Yes | Account authorization service unavailable | Bounded backoff | Account authorization |
| `7702_PREFLIGHT_UNAVAILABLE` | 503 | Yes | EIP-7702 preflight unavailable | Bounded backoff | Authorization-enabled methods |
| `BROADCAST_RESULT_UNKNOWN` | 503 | No | Relay cannot determine provider acceptance | Reconcile candidate identity; do not blindly create a new logical transaction | Submission |
| `INTERNAL_DEADLINE_EXCEEDED` | 504 | Yes | Relay deadline elapsed before a result | Bounded backoff only when safe; reconcile first | REST/WS |
| `INTERNAL_ERROR` | 500 | No | Unexpected Relay failure | Surface incident; retry only after reconciliation | REST/WS |

`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):

```json theme={null}
{"code":"INVALID_SIGNATURE","message":"signature does not verify","retryable":false}
```

Successful REST submission:

```json theme={null}
{"status":"BROADCAST","requestId":"0192...","txHash":"0x..."}
```

The REST response ends here; query the chain yourself for receipt, replacement, or revert.

WebSocket rejection and broadcast:

```json theme={null}
{"id":"req-1","status":"REJECTED","code":"POLICY_DECLINED","retryable":false}
{"id":"req-2","status":"BROADCAST","requestId":"0192...","txHash":"0x..."}
```

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`:

```json theme={null}
{"id":"sub-1","method":"transaction.subscribe","payload":{"requestId":"0192...","txHash":"0x..."}}
```

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:

```json theme={null}
{"type":"transaction.outcome","id":"sub-1","requestId":"0192...","status":"CONFIRMED","txHash":"0x...","blockNumber":"123","blockHash":"0x...","observedAt":"2026-10-20T00:00:00Z"}
```

`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](/api/data-source/reference/order-events.md).

**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.

| Contract | Error | Signature | Selector |
| - | - | - | - |
| AccountCore | AccountAlreadyRegistered | `AccountAlreadyRegistered()` | `0xcff2d5ad` |
| AccountCore | AccountNotFound | `AccountNotFound()` | `0xfaba8ebe` |
| AccountCore | AlreadyInitialized | `AlreadyInitialized()` | `0x0dc149f0` |
| AccountCore | AssetNotEnabled | `AssetNotEnabled()` | `0xa3bd9445` |
| AccountCore | BuilderApprovalExpired | `BuilderApprovalExpired()` | `0x5e65c9f4` |
| AccountCore | BuilderApprovalNotFound | `BuilderApprovalNotFound()` | `0xd595f3a2` |
| AccountCore | BuilderFeeTooHigh | `BuilderFeeTooHigh()` | `0x89c89cba` |
| AccountCore | BuilderReferralNotFound | `BuilderReferralNotFound()` | `0xc0c83880` |
| AccountCore | BuilderReferralNotImproved | `BuilderReferralNotImproved()` | `0xc152b20d` |
| AccountCore | BuilderReferralTierMismatch | `BuilderReferralTierMismatch()` | `0x8c7badd7` |
| AccountCore | CannotAuthorizeOwner | `CannotAuthorizeOwner()` | `0x07b32335` |
| AccountCore | InsufficientBalance | `InsufficientBalance()` | `0xf4d678b8` |
| AccountCore | InvalidAuthority | `InvalidAuthority()` | `0xded4370e` |
| AccountCore | InvalidBuilder | `InvalidBuilder()` | `0x6d10926b` |
| AccountCore | InvalidCollateral | `InvalidCollateral()` | `0xd1ef4cea` |
| AccountCore | InvalidExpireTime | `InvalidExpireTime()` | `0x75e3453f` |
| AccountCore | InvalidInitialization | `InvalidInitialization()` | `0xf92ee8a9` |
| AccountCore | InvalidMarketConfig | `InvalidMarketConfig()` | `0xc9ae771e` |
| AccountCore | InvalidSignature | `InvalidSignature()` | `0x8baa579f` |
| AccountCore | InvalidSubaccount | `InvalidSubaccount()` | `0xf6391b43` |
| AccountCore | MarketFeeError | `MarketFeeError()` | `0xa9269545` |
| AccountCore | NativeAssetMismatch | `NativeAssetMismatch()` | `0xcd41a9e3` |
| AccountCore | NewOwnerIsZeroAddress | `NewOwnerIsZeroAddress()` | `0x7448fbae` |
| AccountCore | NoBuilderFees | `NoBuilderFees()` | `0xc7a51192` |
| AccountCore | NoHandoverRequest | `NoHandoverRequest()` | `0x6f5e8818` |
| AccountCore | NotInitializing | `NotInitializing()` | `0xd7e6bcf8` |
| AccountCore | OnlySpotRouter | `OnlySpotRouter()` | `0xdec150d3` |
| AccountCore | OnlyVerifiedSpotOrderBook | `OnlyVerifiedSpotOrderBook()` | `0x4266501b` |
| AccountCore | ProtocolPaused | `ProtocolPaused()` | `0x44279255` |
| AccountCore | Reentrancy | `Reentrancy()` | `0xab143c06` |
| AccountCore | SameRootRequired | `SameRootRequired()` | `0x28cddf05` |
| AccountCore | SpotFeesExceedProceeds | `SpotFeesExceedProceeds()` | `0xda77c0c4` |
| AccountCore | SpotTokenNotEnabled | `SpotTokenNotEnabled()` | `0xdea2abd6` |
| AccountCore | SubaccountLimitReached | `SubaccountLimitReached()` | `0xef4cdf7c` |
| AccountCore | Unauthorized | `Unauthorized()` | `0x82b42900` |
| AccountCore | UnauthorizedCallContext | `UnauthorizedCallContext()` | `0x9f03a026` |
| AccountCore | UpgradeFailed | `UpgradeFailed()` | `0x55299b49` |
| AccountCore | ZeroAddress | `ZeroAddress()` | `0xd92e233d` |
| AccountCore | ZeroAmount | `ZeroAmount()` | `0x1f2a2005` |
| KuruTradingWallet | DeadlineExpired | `DeadlineExpired()` | `0x1ab7da6b` |
| KuruTradingWallet | InvalidBuilder | `InvalidBuilder()` | `0x6d10926b` |
| KuruTradingWallet | InvalidExpireTime | `InvalidExpireTime()` | `0x75e3453f` |
| KuruTradingWallet | InvalidSignature | `InvalidSignature()` | `0x8baa579f` |
| KuruTradingWallet | NonceTooFarInFuture | `NonceTooFarInFuture()` | `0x8bce3a2b` |
| KuruTradingWallet | NonceTooLow | `NonceTooLow()` | `0xd24d82a4` |
| KuruTradingWallet | OrderIdBindingLengthMismatch | `OrderIdBindingLengthMismatch()` | `0xe64b5e50` |
| KuruTradingWallet | Reentrancy | `Reentrancy()` | `0xab143c06` |
| KuruTradingWallet | StaleAuthorizationEpoch | `StaleAuthorizationEpoch()` | `0x09c04c5a` |
| KuruTradingWallet | TriggerNonceUsed | `TriggerNonceUsed()` | `0xbf055185` |
| KuruTradingWallet | TriggerNotActive | `TriggerNotActive()` | `0xbac08cf0` |
| KuruTradingWallet | Unauthorized | `Unauthorized()` | `0x82b42900` |
| KuruTradingWallet | UnauthorizedKeeper | `UnauthorizedKeeper()` | `0x77b14347` |
| KuruTradingWallet | UnexpectedSlotOrderId | `UnexpectedSlotOrderId()` | `0x53e12325` |
| KuruTradingWallet | UnverifiedSpotMarket | `UnverifiedSpotMarket()` | `0xde616dbc` |
| KuruTradingWallet | ZeroAddress | `ZeroAddress()` | `0xd92e233d` |

Regenerate this appendix whenever the pinned generated bindings or ABI catalog changes.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.