> ## 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, signatures, and broadcast before accepting a request. Both REST and WebSocket submission methods can fail immediately. A REST `BROADCAST` response only means that the provider accepted the transaction for broadcast: REST does not deliver later confirmation, replacement, or revert information. REST clients must independently query chain receipts and reverts. A WebSocket client can receive the same immediate response and, when it subscribes with `transaction.subscribe`, receive a later terminal outcome.

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 handed the candidate to its provider. This is not execution or confirmation. |
| `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 was accepted and is waiting for an 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 |
| `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 | 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 | Outcome retention window expired | 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 unavailable | Bounded backoff | 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 subscribing, the same WebSocket can deliver `transaction.outcome` with `CONFIRMED`, `REVERTED`, `REPLACED`, or `UNKNOWN`. A reverted custom error includes its pinned selector/name; an undecodable revert keeps `selector`/`data` and uses message `unknown`.

## WebSocket transaction outcomes

Subscribe after an accepted submission with:

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

The immediate response is `SUBSCRIBED`, or an outcome replay if the transaction is already terminal. Later delivery is:

```json theme={null}
{"type":"transaction.outcome","id":"sub-1","requestId":"0192...","status":"CONFIRMED | REVERTED | REPLACED | UNKNOWN","txHash":"0x...","replacementTxHash":"0x...","blockNumber":"123","blockHash":"0x...","observedAt":"2026-01-01T00:00:00Z","revert":{"source":"trace | historical_call | post_block_call | unknown","data":"0x...","selector":"0x12345678","name":"<decoded error name>","message":"<bounded decoded message or unknown>"}
```

`CONFIRMED` means included successfully with finalized terminal evidence; `REVERTED` means included but the EVM 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`.

### Connection closure and recovery

The current testnet deployment allows 64 pending outcome subscriptions on one client WebSocket. If the connection is full, Relay may close it while registering another outcome **after** writing that transaction's `BROADCAST` response. This does not roll back the broadcast. Nor does it mean Relay ran out of sponsor nonces: the signer has a separate unconfirmed-transaction window. The 64 value is configuration, not a permanent API guarantee.

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. Treat a submission whose broadcast response was lost as uncertain: reconcile it before signing or submitting a replacement logical action. 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.

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.