Indirect Swaps Integration Guide
This guide walks you through integrating Indirect Swaps.
Overview
The Indirect Swaps flow follows these steps:
- Create a trade - Get quote and transaction data for the swap
- Create an order - Register the swap for tracking and support
- Execute the transaction - Broadcast the transaction on-chain (Solana or EVM)
- Update the trade - Provide transaction ID for monitoring
- 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:
- Retrieved the asset information to identify assets and their networks
- Set up the proper authorization headers
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 swapfromAddress- User’s sending addresstoAddress- User’s receiving address
Optional parameters:
slippage- Slippage tolerance (default: 2%)swapMode-ExactIn(default) orExactOuttoAmount- Required ifswapMode=ExactOut
Response includes:
id- Trade ID for trackingtoAmount- Expected output amountnetworkFee- Fee informationexpiry- Trade expiry timestampsolanaTransactions- Transaction data (for Solana swaps) ORevmTransactions- 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:
- Decode the transaction data
- Sign it with the user’s wallet
- 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 progresscomplete- Swap successfulfailed- Swap failed (checkmessagefield for details)expired- Order expired before completionrefunded- Order was refundeddelayed- 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();