Relayer API endpoints
Error handling conventions: Relay-layer failures emit one of two stable strings on
error.message:'SUBMIT_TX_FAILED'or'RELAY_TIMEOUT', also exported asRELAY_ERROR_CODESfrom@sodax/sdk. Modules other than swap propagate these errors raw. The swap module wraps them intoSodaxError<SwapErrorCode>withcontext.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
URL: https://testnet-xcall-relay.nw.iconblockchain.xyz
Pass this URL as the relayerApiEndpoint override in your SodaxConfig when targeting 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
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 error convention).
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):
Chain key (ChainKeys.*)
Relay chain ID
AVALANCHE_MAINNET
6n
ARBITRUM_MAINNET
23n
BASE_MAINNET
30n
BSC_MAINNET
4n
INJECTIVE_MAINNET
19n
SONIC_MAINNET
146n
OPTIMISM_MAINNET
24n
POLYGON_MAINNET
5n
SOLANA_MAINNET
1n
SUI_MAINNET
21n
STELLAR_MAINNET
27n
ICON_MAINNET
1768124270n
HYPEREVM_MAINNET
26745n
LIGHTLINK_MAINNET
27756n
NEAR_MAINNET
15n
ETHEREUM_MAINNET
2n
BITCOIN_MAINNET
627463n
REDBELLY_MAINNET
726564n
KAIA_MAINNET
27489n
HEDERA_MAINNET
18501n
STACKS_MAINNET
60n
Relay API actions
All requests are JSON POST to the relay URL. The action field selects the operation.
submit — submit a transaction for relaying
submit — submit a transaction for relayingRelayExtraData ({ 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
get_transaction_packets — poll for relay packets by tx hashResponse (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
get_packet — fetch a single packet by connection serial numberResponse (GetPacketResponse):
Low-level functions (advanced usage)
These are exported from IntentRelayApiService for callers that need direct relay access (e.g. custom orchestration, bots):
submitTransaction
(payload, apiUrl) => Promise<Result<SubmitTxResponse>>
Submit a tx to the relay.
getTransactionPackets
(payload, apiUrl) => Promise<Result<GetTransactionPacketsResponse>>
Fetch packets for a tx hash.
getPacket
(payload, apiUrl) => Promise<Result<GetPacketResponse>>
Fetch a single packet by conn_sn.
waitUntilIntentExecuted
(payload) => Promise<Result<PacketData>>
Poll until a packet reaches 'executed' status or times out.
relayTxAndWaitPacket
(params: RelayAndWaitParams) => Promise<Result<PacketData>>
Submit + poll in one call. Handles getIntentRelayChainId conversion and split-tx chains automatically.
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; checkresult.error.cause.messagefor the relay's rejection reasonresult.error.message === 'HTTP_REQUEST_FAILED'— network-level failure; checkresult.error.causefor details
RelayAndWaitParams:
Example:
Last updated