Skip to Content
Integration FlowsIndirect Swaps

Indirect Swaps Integration Guide

This guide walks you through integrating Indirect Swaps.

Overview

The Indirect Swaps flow follows these steps:

  1. Create a trade - Get quote and transaction data for the swap
  2. Create an order - Register the swap for tracking and support
  3. Execute the transaction - Broadcast the transaction on-chain (Solana or EVM)
  4. Update the trade - Provide transaction ID for monitoring
  5. Monitor order status - Track the swap progress via the order API

Pair Availability

⚠️

Important: Indirect Swaps support a huge variety of tokens (almost every token in the supported networks), but with specific network restrictions.

Supported Networks

Indirect Swaps support assets from the following networks:

  • Ethereum (ethereum)
  • Polygon (matic)
  • BNB Smart Chain (bsc)
  • Avalanche C-Chain (avalanchec)
  • Base (basemainnet)
  • Solana (solana)

Pair Requirements

  • Both assets must be on the same network - You cannot swap between different networks (e.g., Ethereum → Solana)
  • Almost infinite combinations - Any asset on a supported network can be swapped to any other asset on the same network

Example:

// Fetch all assets for solana and ethereum networks
const allAssets = await fetch('https://exchange.exodus.io/v3/assets?networks=solana,ethereum').then(
  (res) => res.json()
);
const solanaAssets = allAssets.filter((asset) => asset.network === 'solana');
const ethereumAssets = allAssets.filter((asset) => asset.network === 'ethereum');
 
// Valid: Both assets on Ethereum
const validTrade = await fetch('https://exchange.exodus.io/v3/trades', {
  method: 'POST',
  body: JSON.stringify({
    pairId: `${ethereumAssets[0].id}_${ethereumAssets[1].id}`,
    // ...
  }),
});
 
// Invalid: Cross-network swap (not supported)
const invalidTrade = await fetch('https://exchange.exodus.io/v3/trades', {
  method: 'POST',
  body: JSON.stringify({
    pairId: `${solanaAssets[0].id}_${ethereumAssets[1].id}`,
    // ...
  }),
});
💡

Tip: Use the /assets endpoint to find available assets and their network information.

Prerequisites

Before starting, make sure you have:

Step-by-Step Integration

Step 1: Create a Trade

Create a trade using POST /v3/trades. A trade combines a quote and transaction data in a single response, ready for on-chain execution.

Required parameters:

  • pairId - The pair (e.g., SOL_USDCSOL)
  • fromAmount - Amount to swap
  • fromAddress - User’s sending address
  • toAddress - User’s receiving address

Optional parameters:

  • slippage - Slippage tolerance (default: 2%)
  • swapMode - ExactIn (default) or ExactOut
  • toAmount - Required if swapMode=ExactOut

Response includes:

  • id - Trade ID for tracking
  • toAmount - Expected output amount
  • networkFee - Fee information
  • expiry - Trade expiry timestamp
  • solanaTransactions - Transaction data (for Solana swaps) OR
  • evmTransactions - Transaction data (for EVM-based swaps)

Step 2: Create Order from Trade

Create an order from the trade using POST /v3/trades/:id/order. This step is required for:

  • Order tracking and support
  • Order history management
  • Status monitoring via the orders API
  • Customer support and issue resolution

Step 3: Execute the Transaction

The trade response includes transaction data that you need to execute on-chain. The response will contain either solanaTransactions or evmTransactions depending on the blockchain network.

Important: Execute the transaction before it expires (check trade.expiry). After execution, save the transaction hash/signature for the next steps.

For Solana swaps:

The solanaTransactions array contains transaction objects with:

  • type - Transaction type (usually "swap")
  • data - Base64-encoded transaction data

You’ll need to:

  1. Decode the transaction data
  2. Sign it with the user’s wallet
  3. Submit it to the Solana network

Example (Solana):

import { Transaction } from '@solana/web3.js';
 
// Decode transaction from trade response
const transaction = Transaction.from(Buffer.from(trade.solanaTransactions[0].data, 'base64'));
 
// Sign with user's wallet
transaction.sign(userWallet);
 
// Send to network
const signature = await connection.sendRawTransaction(transaction.serialize());

For EVM-based swaps:

The evmTransactions array contains transaction objects that may include:

  • Revoke transaction (if needed) - To revoke previous approvals
  • Approval transaction (if needed) - To approve token spending
  • Swap transaction - The main swap transaction

Each EVM transaction object contains:

  • value - Amount of native currency to send (in wei, as hex string)
  • data - Transaction data (as hex string)
  • to - Recipient address (usually the DEX provider’s smart contract)
  • from - Sender address (user’s address)
  • gasPrice - Gas price (in wei, as hex string)
  • gasLimit - Gas limit (as number)

Example (EVM):

// Execute transactions in order (revoke → approval → swap)
for (const tx of trade.evmTransactions) {
  // Build transaction object
  const transaction = {
    to: tx.to,
    value: tx.value,
    data: tx.data,
    gasPrice: tx.gasPrice,
    gasLimit: tx.gasLimit,
  };
 
  // Sign with user's wallet (implementation depends on your wallet integration)
  const signedTx = await wallet.signTransaction(transaction);
 
  // Send to network
  const receipt = await provider.sendTransaction(signedTx);
  await receipt.wait(); // Wait for confirmation
}

Step 4: Update Trade

Update the trade status using PATCH /v3/trades/:id after executing the transaction. This step is required so our API can monitor the status of the order and provide proper tracking.

Update the trade with:

  • transactionId - The transaction hash/signature from the executed transaction
    • For Solana: Use the transaction signature
    • For EVM: Use the main swap transaction hash (the last transaction in the sequence)

Step 5: Monitor Order Status

Use the orderId to monitor the order status. Poll this endpoint periodically until the status is complete or failed.

Call GET /v3/orders/:orderId to check the order status:

Order statuses:

  • inProgress - Swap in progress
  • complete - Swap successful
  • failed - Swap failed (check message field for details)
  • expired - Order expired before completion
  • refunded - Order was refunded
  • delayed - Order is delayed

Example:

// Poll order status using the orderId from the order creation response
const checkOrderStatus = async (orderId) => {
  const order = await fetch(`https://exchange.exodus.io/v3/orders/${orderId}`, {
    headers: {
      'App-Name': 'acme-inc',
    },
  }).then((res) => res.json());
 
  if (order.status === 'complete') {
    console.log('Swap completed successfully!');
    return order;
  } else if (order.status === 'failed' || order.status === 'expired') {
    console.error('Swap failed:', order.message);
    return order;
  } else {
    // Still in progress (inProgress, delayed), check again later
    console.log(`Order status: ${order.status}`);
    setTimeout(() => checkOrderStatus(orderId), 5000);
  }
};
 
// Start monitoring
checkOrderStatus(order.orderId);

Swap Modes

ExactIn (Default)

User specifies the input amount, and the system calculates the output amount.

const trade = await fetch('https://exchange.exodus.io/v3/trades', {
  method: 'POST',
  headers: {
    'App-Name': 'acme-inc',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    pairId: 'SOL_USDCSOL',
    fromAmount: '1',
    fromAddress: 'user-sol-address',
    toAddress: 'user-usdc-address',
    swapMode: 'ExactIn', // optional, this is the default
  }),
}).then((res) => res.json());

ExactOut

User specifies the desired output amount, and the system calculates the required input amount.

const trade = await fetch('https://exchange.exodus.io/v3/trades', {
  method: 'POST',
  headers: {
    'App-Name': 'acme-inc',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    pairId: 'SOL_USDCSOL',
    fromAddress: 'user-sol-address',
    toAddress: 'user-usdc-address',
    swapMode: 'ExactOut',
    toAmount: '150', // Desired output amount
  }),
}).then((res) => res.json());

Complete Examples

Solana Example

import { Connection, Transaction } from '@solana/web3.js';
 
// 1. Create trade
const trade = await fetch('https://exchange.exodus.io/v3/trades', {
  method: 'POST',
  headers: {
    'App-Name': 'acme-inc',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    pairId: 'SOL_USDCSOL',
    fromAmount: '1',
    fromAddress: userSolAddress,
    toAddress: userUsdcAddress,
    slippage: 1, // 1% slippage tolerance
  }),
}).then((res) => res.json());
 
console.log(`Expected output: ${trade.toAmount.value} ${trade.toAmount.assetId}`);
console.log(`Fee: ${trade.fee.amount.value} ${trade.fee.amount.assetId}`);
 
// 2. Execute transaction on Solana
const connection = new Connection('https://api.mainnet-beta.solana.com');
 
// Decode the transaction from the trade response
const transaction = Transaction.from(Buffer.from(trade.solanaTransactions[0].data, 'base64'));
 
// Sign with user's wallet (implementation depends on your wallet integration)
transaction.sign(userWallet);
 
// Send transaction to the network
const signature = await connection.sendRawTransaction(transaction.serialize());
 
console.log(`Transaction sent: ${signature}`);
 
// Wait for confirmation before proceeding
await connection.confirmTransaction(signature, 'confirmed');
console.log(`Transaction confirmed: ${signature}`);
 
// 3. Create order from trade (required for tracking and support)
const order = await fetch(`https://exchange.exodus.io/v3/trades/${trade.id}/order`, {
  method: 'POST',
  headers: {
    'App-Name': 'acme-inc',
  },
}).then((res) => res.json());
 
console.log(`Order created: ${order.orderId}`);
 
// 4. Update trade with transaction ID (required for API monitoring)
await fetch(`https://exchange.exodus.io/v3/trades/${trade.id}`, {
  method: 'PATCH',
  headers: {
    'App-Name': 'acme-inc',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    transactionId: signature,
  }),
});
 
console.log(`Trade updated with transaction ID`);
 
// 5. Monitor order status using the orderId
// Continue checking the order status until it's completed or failed
const checkOrderStatus = async () => {
  const orderStatus = await fetch(`https://exchange.exodus.io/v3/orders/${order.orderId}`, {
    headers: {
      'App-Name': 'acme-inc',
    },
  }).then((res) => res.json());
 
  if (orderStatus.status === 'complete') {
    console.log('Swap completed successfully!');
    // The swap has been executed on-chain and funds are in the user's toAddress
  } else if (orderStatus.status === 'failed' || orderStatus.status === 'expired') {
    console.error('Swap failed:', orderStatus.message);
    // Check the message field for failure details
  } else {
    // Still in progress (inProgress, delayed), check again in 5 seconds
    console.log(`Order status: ${orderStatus.status}, checking again...`);
    setTimeout(checkOrderStatus, 5000);
  }
};
 
checkOrderStatus();

EVM Example

import { ethers } from 'ethers';
 
// 1. Create trade
const trade = await fetch('https://exchange.exodus.io/v3/trades', {
  method: 'POST',
  headers: {
    'App-Name': 'acme-inc',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    pairId: 'ETH_USDC',
    fromAmount: '0.1',
    fromAddress: userEthAddress,
    toAddress: userUsdcAddress,
    slippage: 1, // 1% slippage tolerance
  }),
}).then((res) => res.json());
 
console.log(`Expected output: ${trade.toAmount.value} ${trade.toAmount.assetId}`);
console.log(`Fee: ${trade.fee.amount.value} ${trade.fee.amount.assetId}`);
 
// 2. Execute transactions in order
// Note: For EVM swaps, there may be multiple transactions (revoke, approval, swap)
// You need to execute all of them, but typically only the final swap transaction hash is needed
let swapTransactionHash = null;
 
for (const tx of trade.evmTransactions) {
  const transaction = {
    to: tx.to,
    value: tx.value,
    data: tx.data,
    gasPrice: tx.gasPrice,
    gasLimit: tx.gasLimit,
  };
 
  // Sign and send transaction
  const signedTx = await wallet.signTransaction(transaction);
  const receipt = await provider.sendTransaction(signedTx);
 
  console.log(`Transaction sent: ${receipt.hash}`);
 
  // Wait for confirmation
  await receipt.wait();
  console.log(`Transaction confirmed: ${receipt.hash}`);
 
  // Store the last transaction hash (usually the swap transaction)
  swapTransactionHash = receipt.hash;
}
 
// 3. Create order from trade (required for tracking and support)
const order = await fetch(`https://exchange.exodus.io/v3/trades/${trade.id}/order`, {
  method: 'POST',
  headers: {
    'App-Name': 'acme-inc',
  },
}).then((res) => res.json());
 
console.log(`Order created: ${order.orderId}`);
 
// 4. Update trade with transaction ID (required for API monitoring)
// Use the main swap transaction hash
await fetch(`https://exchange.exodus.io/v3/trades/${trade.id}`, {
  method: 'PATCH',
  headers: {
    'App-Name': 'acme-inc',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    transactionId: swapTransactionHash,
  }),
});
 
console.log(`Trade updated with transaction ID`);
 
// 5. Monitor order status using the orderId
const checkOrderStatus = async () => {
  const orderStatus = await fetch(`https://exchange.exodus.io/v3/orders/${order.orderId}`, {
    headers: {
      'App-Name': 'acme-inc',
    },
  }).then((res) => res.json());
 
  if (orderStatus.status === 'complete') {
    console.log('Swap completed successfully!');
  } else if (orderStatus.status === 'failed' || orderStatus.status === 'expired') {
    console.error('Swap failed:', orderStatus.message);
  } else {
    // Still in progress (inProgress, delayed), check again later
    setTimeout(checkOrderStatus, 5000);
  }
};
 
checkOrderStatus();
Last updated on