Skip to main content
Error handling conventions: Relay-layer failures emit one of two stable strings on error.message: 'SUBMIT_TX_FAILED' or 'RELAY_TIMEOUT', also exported as RELAY_ERROR_CODES from @sodax/sdk. Modules other than swap propagate these errors raw. The swap module wraps them into SodaxError<SwapErrorCode> with context.relayCode (see SWAPS.md Error Handling).
The 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.

Mainnet

URL: https://xcall-relay.nw.iconblockchain.xyz 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 internally
  • sodax.bridge.bridge(...) — similarly manages the full relay lifecycle
  • sodax.moneyMarket.*, sodax.staking.*, and related methods do the same
All of these methods return Promise<Result<T>>. On relay failure the Result carries an error whose message is 'RELAY_TIMEOUT' or 'SUBMIT_TX_FAILED' (CODE form — see the error handling conventions note at the top of this page).

Chain IDs used by the relay

The relay API identifies chains by IntentRelayChainId — 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 JSON POST 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.
Response (SubmitTxResponse):

get_transaction_packets — poll for relay packets by tx hash

Response (GetTransactionPacketsResponse):
The packet is complete when 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

Response (GetPacketResponse):

Low-level functions (advanced usage)

These are exported from IntentRelayApiService 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)
  • result.error.message === 'SUBMIT_TX_FAILED' — the relay rejected the submission; check result.error.cause.message for the relay’s rejection reason
  • result.error.message === 'HTTP_REQUEST_FAILED' — network-level failure; check result.error.cause for details
RelayAndWaitParams:
Example: