Skip to main content

Overview

Multi-swap lets partners create multiple independent swap orders in a single API call, grouped under a shared multiId. Each order is individually priced and executed, but they can all be tracked together via a single status endpoint.
Best For: Platforms distributing payouts to multiple recipients, batch airdrop tools, or any use case requiring several simultaneous swaps from the same source token.
Multi-swap supports CEX and anonymous routing only. DEX orders are not supported. Each order in the batch is created using the standard from/to/amount/addressTo fields — no quoteId is needed.

Key Features

Single Request, Multiple Orders

Create up to many orders in one request, all linked by a shared multiId

CEX & Anonymous Routing

Supports standard (CEX) and private (anonymous) routing per order

Per-Order Status

Track all orders in the group with a single GET /exchanges/multi/{multiId} call

How It Works

1

Get Tokens

Fetch CEX-supported tokens and note each token’s id.
2

Create Multi-Swap

Call POST /exchanges/multi with an array of orders. Each order specifies token IDs, amount, destination address, and optional routing flags.
3

Monitor Status

Poll GET /exchanges/multi/{multiId} to track all orders in the group together.
4

Generate Batch Transaction (Solana only)

For Solana source tokens, call GET /exchanges/multi/{multiId}/tx?sender={senderAddress} to get pre-built batched transactions. Each transaction covers up to 10 deposit addresses — if your batch has more than 10 orders, the response returns multiple transactions to submit separately.

Integration Guide

Step 1: Get Tokens

Use token IDs (not symbols) when building your orders. Fetch and cache the token list from /tokens:
Cache the token list in your backend database. Never call /tokens on every user request — load on server startup or via a scheduled job and refresh every 24 hours.
async function fetchAllCexTokens() {
  let page = 1;
  let allTokens = [];

  while (true) {
    const response = await fetch(
      `https://api-partner.houdiniswap.com/v2/tokens?hasCex=true&pageSize=20&page=${page}`,
      {
        headers: { 'Authorization': `${API_KEY}:${API_SECRET}` }
      }
    );

    const { tokens, totalPages } = await response.json();
    allTokens = allTokens.concat(tokens);

    if (page >= totalPages) break;
    page++;
  }

  return allTokens; // save to your DB
}

Step 2: Create Multi-Swap

Call POST /exchanges/multi with an orders array. Each order is independent — different token pairs, amounts, and destination addresses are all supported in the same batch. Set anonymous: true on any order to enable private routing for that order.
const response = await fetch('https://api-partner.houdiniswap.com/v2/exchanges/multi', {
  method: 'POST',
  headers: {
    'Authorization': `${API_KEY}:${API_SECRET}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    orders: [
      {
        from: '6689b73ec90e45f3b3e51577',       // SOL token id
        to:   '6689b73ec90e45f3b3e51566',       // ETH token id
        amount: 10,
        addressTo: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e'
      },
      {
        from: '6689b73ec90e45f3b3e51577',       // SOL token id
        to:   '6689b73ec90e45f3b3e51558',       // USDC token id
        amount: 25,
        addressTo: '0xABcD1234abcd1234ABCD1234abcd1234ABCD1234',
        anonymous: true                          // enable private routing for this order
      },
      {
        from: '6689b73ec90e45f3b3e51577',       // SOL token id
        to:   '6689b73ec90e45f3b3e51577',       // SOL token id
        amount: 5,
        addressTo: 'RecipientSolanaAddressHere1111111111111111',
        anonymous: true,
        useXmr: true                            // force XMR as anonymous bridge
      }
    ]
  })
});

const result = await response.json();
console.log('Multi ID:', result.multiId);       // shared group identifier

result.orders.forEach((item, i) => {
  if (item.error) {
    console.error(`Order ${i} failed:`, item.error.message, item.error.code);
  } else {
    console.log(`Order ${i} ID:`, item.order.houdiniId);
    console.log(`  Deposit to:`, item.order.depositAddress);
    console.log(`  Send:`, item.order.inAmount, item.order.inSymbol);
    console.log(`  Expires:`, item.order.expires);
  }
});

Create Multi-Swap Response

{
  "multiId": "multi_a1b2c3d4e5f6",
  "orders": [
    {
      "order": {
        "houdiniId": "iBQMRX3xvXrFMGQi71ogo9",
        "multiId": "multi_a1b2c3d4e5f6",
        "created": "2025-12-25T06:13:46.673Z",
        "expires": "2025-12-25T06:43:46.673Z",
        "depositAddress": "SolDepositAddress111111111111111111111111111",
        "receiverAddress": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
        "anonymous": false,
        "statusLabel": "NEW",
        "inAmount": 10,
        "inSymbol": "SOL",
        "outAmount": 0.0412,
        "outSymbol": "ETH",
        "eta": 15
      }
    },
    {
      "order": {
        "houdiniId": "kJRNSY4ywYsGNHRmO82ph0",
        "multiId": "multi_a1b2c3d4e5f6",
        "created": "2025-12-25T06:13:46.673Z",
        "expires": "2025-12-25T06:43:46.673Z",
        "depositAddress": "SolDepositAddress222222222222222222222222222",
        "receiverAddress": "0xABcD1234abcd1234ABCD1234abcd1234ABCD1234",
        "anonymous": true,
        "statusLabel": "NEW",
        "inAmount": 25,
        "inSymbol": "SOL",
        "outAmount": 2431.75,
        "outSymbol": "USDC",
        "eta": 35
      }
    },
    {
      "error": {
        "message": "Token pair not available",
        "code": "PAIR_UNAVAILABLE"
      }
    }
  ]
}
Key Response Fields:
  • multiId: Shared identifier for the batch — use this to poll status for all orders
  • orders[].order: Full order object for successfully created orders (see Order Lifecycle)
  • orders[].error: Per-order error if creation failed — the rest of the batch is unaffected
  • depositAddress: Each order has its own unique deposit address
  • statusLabel: Starts as NEW — orders are initialized asynchronously
Orders are created asynchronously. Some orders may return with statusLabel: "INITIALIZING" immediately after creation. Poll the multi status endpoint to confirm all orders reach NEW before proceeding.

Order Fields Reference

FieldDescription
fromToken ID of input token (24-char MongoDB ObjectId)
toToken ID of output token
amountInput swap amount
addressToDestination wallet address for output funds
anonymoustrue to enable private multi-hop routing
destinationTagMemo/tag for chains that require it (e.g. XRP, XLM) — max 64 chars
useXmrtrue to force XMR as the anonymous bridge layer
walletInfoAdditional wallet metadata — max 256 chars

Step 3: Monitor Multi-Swap Status

Poll GET /exchanges/multi/{multiId} to retrieve status for all orders in the group at once:
async function pollMultiStatus(multiId) {
  const response = await fetch(
    `https://api-partner.houdiniswap.com/v2/exchanges/multi/${multiId}`,
    {
      headers: { 'Authorization': `${API_KEY}:${API_SECRET}` }
    }
  );

  const { orders } = await response.json();

  orders.forEach(order => {
    console.log(`${order.houdiniId}: ${order.statusLabel}`);
    // statusLabel values:
    // INITIALIZING  — order being set up
    // NEW / WAITING — awaiting deposit
    // CONFIRMING    — deposit detected, awaiting confirmations
    // EXCHANGING    — CEX is processing
    // ANONYMIZING   — routing through privacy layer (anonymous orders only)
    // FINISHED      — complete, funds sent
    // EXPIRED       — deposit not received in time
    // FAILED        — swap failed
    // REFUNDED      — funds returned to sender
  });

  const allDone = orders.every(o =>
    ['FINISHED', 'FAILED', 'EXPIRED', 'REFUNDED'].includes(o.statusLabel)
  );

  return { orders, allDone };
}

// Poll every 30 seconds until all orders are terminal
const interval = setInterval(async () => {
  const { orders, allDone } = await pollMultiStatus('multi_a1b2c3d4e5f6');
  if (allDone) clearInterval(interval);
}, 30_000);

Multi-Status Response

{
  "multiId": "multi_a1b2c3d4e5f6",
  "orders": [
    {
      "houdiniId": "iBQMRX3xvXrFMGQi71ogo9",
      "multiId": "multi_a1b2c3d4e5f6",
      "statusLabel": "WAITING",
      "displayStatus": "WAITING_FOR_DEPOSIT",
      "inAmount": 10,
      "inSymbol": "SOL",
      "outAmount": 0.0412,
      "outSymbol": "ETH",
      "depositAddress": "SolDepositAddress111111111111111111111111111",
      "receiverAddress": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
      "eta": 15,
      "created": "2025-12-25T06:13:46.673Z",
      "modified": "2025-12-25T06:13:50.001Z"
    },
    {
      "houdiniId": "kJRNSY4ywYsGNHRmO82ph0",
      "multiId": "multi_a1b2c3d4e5f6",
      "statusLabel": "EXCHANGING",
      "displayStatus": "DEPOSIT_DETECTED",
      "inAmount": 25,
      "inSymbol": "SOL",
      "outAmount": 2431.75,
      "outSymbol": "USDC",
      "depositAddress": "SolDepositAddress222222222222222222222222222",
      "receiverAddress": "0xABcD1234abcd1234ABCD1234abcd1234ABCD1234",
      "eta": 20,
      "created": "2025-12-25T06:13:46.673Z",
      "modified": "2025-12-25T06:20:12.443Z"
    }
  ]
}
Poll every 30 seconds. Track each order’s statusLabel independently — orders in the same batch progress at different rates. For a full list of statuses and transitions, see the Order Lifecycle guide.

Step 4: Generate Solana Batch Transaction

For Solana source tokens, you can fund all deposit addresses with pre-built transactions instead of sending separate transfers. Call GET /exchanges/multi/{multiId}/tx?sender={senderAddress}:
const senderAddress = 'YourSolanaWalletAddressHere111111111111111111';

const response = await fetch(
  `https://api-partner.houdiniswap.com/v2/exchanges/multi/${multiId}/tx?sender=${senderAddress}`,
  {
    headers: { 'Authorization': `${API_KEY}:${API_SECRET}` }
  }
);

const { chain, transactions } = await response.json();

console.log('Chain:', chain); // "solana"
console.log('Transactions to submit:', transactions.length);

for (const batch of transactions) {
  console.log('Covers order IDs:', batch.houdiniIds); // up to 10 per batch

  // batch.txData.data is a base64-encoded serialized Solana transaction
  const txBytes = Buffer.from(batch.txData.data, 'base64');

  const { Transaction, Connection, clusterApiUrl } = require('@solana/web3.js');
  const connection = new Connection(clusterApiUrl('mainnet-beta'));
  const tx = Transaction.from(txBytes);

  // Sign with your keypair or wallet adapter, then submit
  const signature = await connection.sendRawTransaction(tx.serialize());
  console.log('Submitted batch tx:', signature);
}

Batch Transaction Response

{
  "multiId": "multi_a1b2c3d4e5f6",
  "chain": "solana",
  "transactions": [
    {
      "houdiniIds": [
        "iBQMRX3xvXrFMGQi71ogo9",
        "kJRNSY4ywYsGNHRmO82ph0"
      ],
      "txData": {
        "data": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDa2V5..."
      }
    }
  ]
}
Key Fields:
  • chain: Always "solana" — only Solana is supported for batch transactions
  • transactions: Array of TxBatch objects — each covers up to 10 orders. Batches with more than 10 orders return multiple items; submit each transaction separately
  • houdiniIds: The order IDs funded by this specific transaction
  • txData.data: Base64-encoded serialized Solana transaction — deserialize, sign, and submit
Batch transactions are only supported when all orders in the multi-swap share the same Solana source token.

Best Practices

The multi-swap endpoint returns a partial success response — some orders may fail while others succeed. Always check each item in orders[] for an error field before proceeding, and handle failed orders independently without canceling the batch.
Orders returned with statusLabel: "INITIALIZING" are still being set up. Poll GET /exchanges/multi/{multiId} until all orders reach NEW or WAITING before sending deposits.
Each order has its own expires timestamp (typically 30 minutes from creation). For Solana batch transactions, sign and submit all transactions promptly — if any order expires before confirmation, that order will not be funded.
If your multi-swap has more than 10 Solana orders, the transactions array will contain multiple items. Submit each transaction independently — they are not dependent on each other and can be submitted in parallel.
Never expose API keys in frontend code. Validate all addressTo values before submission. Store multiId and each houdiniId for audit trails and support lookups.

Common Issues

Cause: Individual orders can fail validation (unsupported pair, amount out of range, invalid address) while the rest of the batch succeeds.Solution: Check each orders[].error in the response. Re-submit failed orders individually with corrected parameters. The valid orders in the batch are unaffected.
Cause: Batch transactions require all orders to share the same Solana source token.Solution: Use separate individual deposits for non-Solana source tokens.
Cause: The 30-minute deposit window elapsed before funds were sent.Solution: Re-create the expired orders with a new multi-swap request. Deposit promptly after creation.
Cause: Order setup is asynchronous and may take a few seconds.Solution: Poll GET /exchanges/multi/{multiId} every 5–10 seconds until all orders leave the INITIALIZING state before proceeding.
Cause: Private routing passes through an additional ANONYMIZING stage.Solution: This is expected. Monitor statusLabel per order — anonymous orders typically complete in 15–45 minutes.

Next Steps

Standard Swap

Single-order CEX swaps

Private Swap

Multi-hop anonymous swaps

Order Lifecycle

Understand all order statuses

Error Handling

Handle errors and edge cases