For the complete documentation index, see llms.txt. This page is also available as Markdown.

Make a Swap

Error handling conventions: The swap module returns SodaxError<SwapErrorCode> from swap, createIntent, postExecution, createLimitOrder, and createLimitOrderIntent. Discriminate on result.error.code (e.g. 'RELAY_TIMEOUT') — not result.error.message. See SWAPS.md for the full per-method code unions. The lower-level methods (getQuote, getStatus, submitIntent, getSolvedIntentPacket, cancelIntent, …) still return Result<T, SolverErrorResponse> or Result<T, Error | unknown>cancelIntent/cancelLimitOrder were not migrated to SodaxError, so don't switch (error.code) on those.

This guide provides a step-by-step walkthrough for executing a cross-chain swap using the Sodax SDK. It covers everything from initializing the SDK to handling errors during the swap process.

For detailed API reference, see SWAPS.md.

Example Source Code: A complete working example can be found in apps/node/src/swap.ts. This example demonstrates a full swap implementation from Arbitrum ETH to Polygon POL, including all error handling and status polling.

Prerequisites

Before you begin, ensure you have:

  • A wallet provider implementation (e.g., IEvmWalletProvider for EVM chains). You can use existing wallet provider implementations from the @sodax/wallet-sdk-core npm package, or use the local package @wallet-sdk-core if working within the Sodax monorepo.

  • The @sodax/sdk package installed

  • Sufficient token balance to cover the swap amount and fees

  • RPC URLs for the chains you're interacting with (we recommend having a dedicated node provider like Alchemy, Quicknode, etc.)

  • Private key or wallet (browser) connection for signing transactions. For React applications, you can use the @sodax/wallet-sdk-react npm package, or use the local package @wallet-sdk-react if working within the Sodax monorepo.

Step 1: Initialize Sodax Instance

First, create and initialize a Sodax instance. The Sodax constructor defaults to mainnet configuration, so no configuration is required for basic usage.

import { Sodax } from "@sodax/sdk";

// Create Sodax instance (defaults to mainnet configs)
const sodax = new Sodax();

// Initialize to fetch latest configuration from the backend API (optional, use version-based
// approach without initialize for more stability).
// Initialization fetches the latest configuration from the backend API, including supported
// tokens and chains. This ensures you have the most up-to-date token and chain information.
const initResult = await sodax.initialize();
if (!initResult.ok) {
  console.warn('Initialization failed, using packaged defaults:', initResult.error);
  // The SDK continues to work with built-in default config — this is non-fatal.
}

Note:

  • The new Sodax() constructor defaults to mainnet configuration automatically. No configuration is required for basic usage.

  • initialize() returns Promise<Result<void>>. If it fails the SDK falls back to the configuration packaged with the SDK version you installed.

  • If you skip initialize(), the SDK will use the configuration from the specific SDK version you're using. Initialization is recommended for production applications to ensure you have the latest supported tokens and chains.

Optional: Custom Configuration

If you need to use custom solver configuration or hub provider settings, you can pass them when creating the Sodax instance:

See CONFIGURE_SDK.md for the full SodaxConfig shape and canonical override patterns.

Step 2: Obtain a Wallet Provider

The SDK does not require you to construct a spoke provider object. Instead, you supply a wallet provider directly in each service call. The srcChainKey field in the call params tells the SDK which chain to route to, and the walletProvider field is type-narrowed to the correct interface for that chain.

Note: For Node.js environments, we suggest you provide RPC URLs when creating wallet providers (default public ones might not work). For browser environments, wallet providers are typically injected by wallet extensions.

For EVM chains (Arbitrum, Polygon, BSC, etc.):

Important: For the Sonic hub chain, pass an EVM wallet provider as well — Sonic is an EVM chain. The SDK distinguishes hub-vs-spoke behavior internally via srcChainKey.

Getting Supported Tokens

Before creating a swap, you may want to check which tokens are supported for swaps on each chain:

Before executing a swap, it is good practice to get a quote to show users the expected output amount. This helps set proper expectations and allows you to calculate slippage tolerance.

Example: See how quotes are obtained in the example file: apps/node/src/swap.ts.

Step 4: Check Token Allowance

Before creating a swap intent, check whether the Asset Manager contract already has permission to spend your tokens. If not, you will need to approve it first.

Example: See how allowance checking is implemented in the example file: apps/node/src/swap.ts.

Note on field names: CreateIntentParams uses srcChainKey and dstChainKey (not srcChain / dstChain). The on-chain Intent type has Intent.srcChain / Intent.dstChain as bigint relay chain IDs — these are internal identifiers and should not be confused with the user-facing chain key fields.

Step 5: Approve Tokens (If Needed)

If the allowance check returned false, approve the Asset Manager contract to spend your tokens. The approval amount matches the inputAmount in your intent parameters (fees are automatically deducted from this amount).

Example: See how token approval is handled in the example file: apps/node/src/swap.ts.

Important: Always wait for the approval transaction to be confirmed before proceeding with the swap.

Raw mode: approve also supports raw: true to return the unsigned transaction payload instead of broadcasting:

Step 6: Prepare Intent Parameters

Now that you have approval (if needed), prepare the complete intent parameters. Make sure to:

  • Use the quoted amount from Step 3 to set a reasonable minOutputAmount

  • Set an appropriate deadline (or use 0n for no deadline / limit-order behavior)

  • Ensure srcAddress matches your wallet address

  • Set dstAddress to where you want to receive the output tokens

Step 7: Execute the Swap

Now you're ready to execute the swap. The swap method orchestrates the complete lifecycle automatically:

  1. Creates the intent deposit transaction on the source chain

  2. Verifies the spoke transaction landed on-chain

  3. Submits the transaction to the relayer and waits for the relay packet to land on the hub (Sonic). This step is skipped when srcChainKey is the hub itself.

  4. Calls postExecution to notify the solver, triggering it to fill the intent

Example: See how the swap is executed in the example file: apps/node/src/swap.ts.

Step 8: Check Intent Status

After a successful swap submission, continuously monitor the intent status until it reaches a terminal state. Poll every 5 seconds until the swap is completed, failed, or not found.

Example: See the complete status polling implementation in the example file: apps/node/src/swap.ts.

Status Codes:

  • NOT_FOUND (-1): Intent not found in the solver system (may appear immediately after creation). After 3 consecutive NOT_FOUND responses, polling stops.

  • NOT_STARTED_YET (1): Intent is queued and waiting to be processed (continues polling)

  • STARTED_NOT_FINISHED (2): Intent is currently being processed (continues polling)

  • SOLVED (3): Swap completed successfully (includes fill_tx_hash when available) — Terminal state

  • FAILED (4): Swap failed to complete — Terminal state

Polling Behavior:

  • Polls every 5 seconds (configurable via intervalMs parameter)

  • Continues until a terminal state is reached (SOLVED, FAILED, or NOT_FOUND after 3 attempts)

  • Maximum polling duration: 5 minutes by default (60 attempts × 5 seconds, configurable via maxAttempts)

  • Handles temporary API errors gracefully by continuing to poll

Note: The fill_tx_hash field is only present when the status is SOLVED (3). This is the transaction hash of the fill transaction on the destination chain.

Step 9: Handle Errors

All swap methods return Result<T, SodaxError<SwapErrorCode>>. Discriminate on result.error.code (a closed reason-only union), never on error.message (human-readable, may change). The original lower-level failure is preserved on error.cause; structured metadata is on error.context.

See SWAPS.md for the full per-method code unions and context schema.

Note: The swap module exports narrow guards isSwapError, isSwapCreateIntentError, isPostExecutionError from @sodax/sdk. Use them in catch blocks for cross-bundle type safety; see SWAPS.md.

Complete Example

Here's a complete end-to-end example combining all the steps. For a production-ready implementation, see the example source code in apps/node/src/swap.ts:

Next Steps

Last updated