Skip to main content

Overview

DEX swaps execute on-chain through smart contracts. Unlike CEX swaps, DEX swaps require users to connect their wallet, sign transactions, and broadcast them to the blockchain. In API v2, DEX swaps use the same /quotes and /exchanges endpoints as CEX swaps — pass types=dex to get only DEX quotes, or filter the response by type: "dex". The quoteId is the single identifier passed through the entire flow — no route objects required.
Best For: Users who want true decentralized swaps, keep custody of their funds, and interact directly with on-chain liquidity sources like Uniswap, CowSwap, and 1inch.
Looking for the v1 DEX swap guide? See API v1 — DEX Swap.

Supported Networks

  • EVM (Ethereum, BSC, Polygon, etc.)
  • Solana
  • SUI
  • TRON
  • TON
  • Stellar
Stellar Trustline Requirement: Ensure the destination account has required trustlines before initiating the swap. Houdini does not create trustlines automatically.

Key Differences from CEX Swaps

FeatureDEXCEX (Standard/Private)
ExecutionOn-chain via smart contractsOff-chain via exchanges
WalletRequiredNot required
CustodyUser retains custodyUser sends to deposit address
ApprovalsMay require token approvalsNot required
SpeedSeconds to minutes3–45 minutes

How It Works

1

Get Tokens

Bulk fetch DEX-supported tokens and cache to your DB, or search by name/symbol. Note each token’s id.
2

Get Quotes

Call GET /quotes with token IDs and optional slippage. Select the quote with type: "dex".
3

Check Approvals (if requiresApproval: true)

If the quote has requiresApproval: true, call POST /dex/approve with quoteId and addressFrom. Returns on-chain approval transactions and/or signatures needed. Skip this step if requiresApproval: false.
4

Handle Approvals (if needed)

Broadcast approval transactions via user’s wallet.
5

Handle Signatures (if needed)

Have user sign EIP-712 typed data. For chained signatures, call POST /dex/chainSignatures until isComplete: true.
6

Create Order

Call POST /exchanges with quoteId, addressTo, addressFrom, and any collected signatures.
7

Broadcast & Confirm

If not off-chain, have user broadcast the transaction. Then call POST /dex/confirmTx with the transaction hash.
8

Monitor Status

Poll GET /orders/{houdiniId} until statusLabel is FINISHED.

Integration Guide

Step 1: Get Tokens

There are two approaches for getting tokens. Choose the one that fits your integration:
Fetch all DEX-supported tokens once and store them in your backend database.
Cache the token list in your backend database. Never call /tokens on every user request — refresh every 24 hours.
async function fetchAllDexTokens() {
  let page = 1;
  let allTokens = [];

  while (true) {
    const response = await fetch(
      `https://api-partner.houdiniswap.com/v2/tokens?hasDex=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: Get DEX Quote

Call GET /quotes with token IDs and optional slippage. Select the quote with type: "dex".
const params = new URLSearchParams({
  amount: '1',
  from: '6689b73ec90e45f3b3e51566',  // ETH token id from /tokens
  to:   '6689b73ec90e45f3b3e51553',  // USDT token id from /tokens
  types: 'dex',                      // only return DEX quotes
  slippage: '0.5',                   // 0.5% slippage (optional)
});

const response = await fetch(
  `https://api-partner.houdiniswap.com/v2/quotes?${params}`,
  {
    headers: {
      'Authorization': `${API_KEY}:${API_SECRET}`,
      'x-user-ip': userIp,
      'x-user-agent': userAgent,
      'x-user-timezone': userTimezone
    }
  }
);

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

// Select a DEX quote
const dexQuote = quotes.find(q => q.type === 'dex');
console.log('DEX provider:', dexQuote.swapName);      // e.g., "Bungee"
console.log('Amount out:', dexQuote.amountOut);
console.log('Quote ID:', dexQuote.quoteId);            // needed for next steps
console.log('Requires approval:', dexQuote.requiresApproval);

Quotes Response (DEX quote)

{
  "quotes": [
    {
      "quoteId": "69af9eb7f9c5affabcacccba",
      "type": "dex",
      "swap": "bg",
      "swapName": "Bungee",
      "logoUrl": "https://api.houdiniswap.com/assets/logos/bungee-6ohrmd.png",
      "amountOut": 0.007372135451768274,
      "amountOutUsd": 15.019251555,
      "netAmountOut": 0.007372135451768274,
      "duration": 1,
      "markupSupported": true,
      "apiMarkupValue": 10,
      "markupType": "bp",
      "restrictedCountries": [],
      "rewardsAvailable": false,
      "requiresApproval": true
    }
  ],
  "total": 3
}
Key Fields:
  • quoteId: Pass to /dex/approve and /exchanges
  • type: "dex" for on-chain swaps
  • requiresApproval: If true, call the approve/allowance flow (Step 3). If false, skip directly to Step 6.
  • markupSupported: Whether a fee markup can be applied to this route
  • apiMarkupValue / markupType: Markup amount and type ("bp" = basis points)
  • restrictedCountries: List of country codes where this route is unavailable

Step 3: Check Approvals and Signatures

Only run this step if requiresApproval: true on the selected quote. If requiresApproval is false, skip Steps 3–5 and go directly to Step 6: Create Order.
Call POST /dex/approve with just the quoteId and user’s wallet address:
const response = await fetch('https://api-partner.houdiniswap.com/v2/dex/approve', {
  method: 'POST',
  headers: {
    'Authorization': `${API_KEY}:${API_SECRET}`,
    'Content-Type': 'application/json',
    'x-user-ip': userIp,
    'x-user-agent': userAgent,
    'x-user-timezone': userTimezone
  },
  body: JSON.stringify({
    quoteId: '69155e0bdb5ab0cbe27e2709',           // from /quotes
    addressFrom: '0x45CF73349a4895fabA18c0f51f06D79f0794898D'  // user wallet
  })
});

const { approvals, signatures } = await response.json();
// approvals: on-chain approval transactions to broadcast (may be empty)
// signatures: EIP-712 typed data to sign (may be empty)
Response Fields:
  • approvals: Array of { data, to, from, fromChain } transactions to broadcast (may be empty)
  • signatures: Array of EIP-712 typed data objects to sign (may be empty)
You may receive both approvals and signatures. Handle approvals first, then signatures.

Signature Types

  • SINGLE: User signs once — collect the result and proceed
  • CHAINED (e.g., CowSwap): User signs → call /dex/chainSignatures → user signs again → repeat until isComplete: true

Step 4: Send Approval Transactions (if needed)

if (approvals && approvals.length > 0) {
  for (const approval of approvals) {
    const tx = await walletProvider.sendTransaction({
      to: approval.to,
      data: approval.data,
      from: approval.from
    });
    await tx.wait();
    console.log('Approval confirmed:', tx.hash);
  }
}
Skip this step if approvals is empty.

Step 5: Process Signatures (if needed)

async function processSignatures(signatures, quoteId, addressFrom) {
  if (!signatures || signatures.length === 0) return [];

  const results = [];

  for (const sig of signatures) {
    const signatureResult = await walletProvider.signTypedData({
      domain: sig.data.domain,
      types: sig.data.types,
      primaryType: sig.data.primaryType,
      message: sig.data.message
    });

    const signatureObject = {
      signature: signatureResult,
      key: sig.key,
      swapRequiredMetadata: sig.swapRequiredMetadata
    };

    if (sig.type === 'CHAINED' && !sig.isComplete) {
      // Get next signature in the chain
      const chainResponse = await fetch('https://api-partner.houdiniswap.com/v2/dex/chainSignatures', {
        method: 'POST',
        headers: {
          'Authorization': `${API_KEY}:${API_SECRET}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          quoteId,
          addressFrom,
          previousSignature: signatureObject,
          signatureKey: sig.key,
          signatureStep: sig.step
        })
      });

      const nextSignatures = await chainResponse.json();
      const chainResults = await processSignatures(nextSignatures, quoteId, addressFrom);
      if (chainResults.length > 0) {
        results.push(chainResults[chainResults.length - 1]); // only final
      }
    } else {
      results.push(signatureObject);
    }
  }

  return results;
}

const collectedSignatures = await processSignatures(signatures, dexQuote.quoteId, userAddress);
Key points:
  • SINGLE: Collect one signature and move on
  • CHAINED: Sign → /dex/chainSignatures → sign again → repeat until isComplete: true. Only the final result is passed to /exchanges
Skip this step if signatures is empty.

Step 6: Create Order

Call POST /exchanges with the quoteId, addresses, and any collected signatures:
const response = await fetch('https://api-partner.houdiniswap.com/v2/exchanges', {
  method: 'POST',
  headers: {
    'Authorization': `${API_KEY}:${API_SECRET}`,
    'Content-Type': 'application/json',
    'x-user-ip': userIp,
    'x-user-agent': userAgent,
    'x-user-timezone': userTimezone
  },
  body: JSON.stringify({
    quoteId: '69155e0bdb5ab0cbe27e2709',
    addressTo: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
    addressFrom: '0x45CF73349a4895fabA18c0f51f06D79f0794898D',
    signatures: collectedSignatures  // empty array if none required
  })
});

const order = await response.json();
console.log('Order ID:', order.houdiniId);
console.log('Off-chain?', order.metadata?.offChain);
Request Fields:
  • quoteId (required): From /quotes
  • addressTo (required): Destination wallet address
  • addressFrom (optional): Source wallet address — required for DEX swaps
  • signatures (optional): Collected from Step 5
  • destinationTag (optional): Memo for chains that require it
Order Response contains:
  • houdiniId: Use for status polling
  • metadata.offChain: true if Houdini backend broadcasts (e.g., CowSwap)
  • metadata.to: DEX router address (when offChain: false)
  • metadata.data: Encoded swap calldata (when offChain: false)
  • metadata.value: ETH value for native swaps (when offChain: false)

Step 7: Broadcast Transaction and Confirm

User broadcasts the transaction, then confirm with Houdini:
if (!order.metadata?.offChain) {
  const tx = await walletProvider.sendTransaction({
    to: order.metadata.to,
    data: order.metadata.data,
    value: order.metadata.value || '0'
  });

  const receipt = await tx.wait();

  await fetch('https://api-partner.houdiniswap.com/v2/dex/confirmTx', {
    method: 'POST',
    headers: {
      'Authorization': `${API_KEY}:${API_SECRET}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      id: order.houdiniId,
      txHash: receipt.hash
    })
  });
}
You must call /dex/confirmTx in both cases. For on-chain swaps, pass the txHash. For off-chain swaps, omit txHash. Without this call, the order will not be processed.

Step 8: Monitor Order Status

Poll GET /orders/{houdiniId} to track progress, or subscribe via the WebSocket API for real-time updates:
const response = await fetch(
  `https://api-partner.houdiniswap.com/v2/orders/${order.houdiniId}`,
  {
    headers: {
      'Authorization': `${API_KEY}:${API_SECRET}`,
      'x-user-ip': userIp,
      'x-user-agent': userAgent,
      'x-user-timezone': userTimezone
    }
  }
);

const status = await response.json();
console.log('Status:', status.statusLabel);
// WAITING   — awaiting transaction
// CONFIRMING — transaction submitted, waiting for confirmations
// EXCHANGING — swap processing
// FINISHED   — swap complete
// FAILED     — swap failed
Poll every 30 seconds. DEX swaps typically complete in seconds to a few minutes depending on network congestion. For real-time updates without polling, use the WebSocket API.

Example Repositories

See full working integrations on GitHub:

Next Steps