Error handling conventions: Relay-layer failures emit one of these stable strings onThe intent relay service bridges spoke-chain transactions to the SODAX hub (Sonic). All cross-chain operations — swaps, bridges, money market deposits/withdrawals, staking — submit a spoke-chain transaction hash to the relay, then poll until the hub confirms execution.error.message:'SUBMIT_TX_FAILED','RELAY_TIMEOUT', or'RELAY_POLLING_FAILED', also exported asRELAY_ERROR_CODESfrom@sodax/sdk. Onlydexpropagates these errors raw; every other feature module (moneyMarket, bridge, staking, migration, leverageYield, swap) routes them throughmapRelayFailureinto a typedSodaxErrorwithcontext.relayCode. The swap module wraps them intoSodaxError<SwapErrorCode>withcontext.relayCode(see SWAPS.md Error Handling).
Mainnet
URL:https://api.sodax.com/v1/relay
This is the default value of DEFAULT_RELAYER_API_ENDPOINT (exported from @sodax/sdk). It is set automatically in relayConfig.relayerApiEndpoint and picked up by ConfigService — no manual configuration is needed unless you are overriding the endpoint.
Testnet
There is no testnet relayer endpoint for integrators. SODAX is mainnet-only:ChainKeys contains
only *_MAINNET entries, and there are no testnet chain configs or RPC endpoints in the SDK. Build
and test against mainnet with small amounts — see Testing without a testnet.
SDK integration
IntentRelayApiService (packages/sdk/src/shared/services/intentRelay/IntentRelayApiService.ts) is an internal module. Callers never construct it directly. The relay is accessed through high-level service methods:
sodax.swaps.createIntent(...)— submits the spoke tx and waits for hub execution internallysodax.bridge.bridge(...)— similarly manages the full relay lifecyclesodax.moneyMarket.*,sodax.staking.*, and related methods do the same
Promise<Result<T>>. They route relay failures through mapRelayFailure, so the Result carries a typed SodaxError — discriminate on error.code, and read the raw relay string from context.relayCode. Do not match on error.message: the mapper replaces it with prose.
Raw
error.message matching applies only where nothing maps the failure: the sodax.dex.* relay legs, and the exported helpers relayTxAndWaitPacket, submitTransaction and waitUntilIntentExecuted when a consumer calls them directly. Those still carry the RELAY_ERROR_CODES strings on error.message.
Chain IDs used by the relay
The relay API identifies chains byIntentRelayChainId — a bigint value that is distinct from the ChainKeys.* string keys used everywhere else in the SDK.
Use getIntentRelayChainId(chainKey) (exported from @sodax/sdk) to convert a SpokeChainKey to its relay chain ID. The SDK does this conversion internally; callers only need it when constructing raw relay requests directly (advanced usage).
Full mapping (RelayChainIdMap in @sodax/sdk):
Relay API actions
All requests are JSONPOST to the relay URL. The action field selects the operation.
submit — submit a transaction for relaying
RelayExtraData ({ address: Hex; payload: Hex }) carries the hub destination address and the full call payload. Solana and Bitcoin use split transactions: the on-chain tx stores only a verification hash; the full call data is submitted off-chain here.
SubmitTxResponse):
get_transaction_packets — poll for relay packets by tx hash
GetTransactionPacketsResponse):
status === 'executed'. Use dst_tx_hash as the hub-chain transaction hash for subsequent solver interactions.
get_packet — fetch a single packet by connection serial number
GetPacketResponse):
Low-level functions (advanced usage)
These are exported fromIntentRelayApiService for callers that need direct relay access (e.g. custom orchestration, bots):
All functions return
Promise<Result<T>> — no throws across service boundaries. Check result.ok before using result.value. On failure, result.error is an Error instance:
result.error.message === 'RELAY_TIMEOUT'— packet did not arrive within the timeout (default: 120 000 ms); polling worked, the relay just didn’t deliver in timeresult.error.message === 'SUBMIT_TX_FAILED'— the relay rejected the submission (HTTP error, malformed response, orsuccess: false); checkresult.error.cause.messagefor the relay’s rejection reasonresult.error.message === 'RELAY_POLLING_FAILED'— polling itself never recovered (persistent network errors or exceptions during the wait window); the packet’s status is unknown. Checkresult.error.causefor details
RelayAndWaitParams: