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.
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
| Feature | DEX | CEX (Standard/Private) |
|---|
| Execution | On-chain via smart contracts | Off-chain via exchanges |
| Wallet | Required | Not required |
| Custody | User retains custody | User sends to deposit address |
| Approvals | May require token approvals | Not required |
| Speed | Seconds to minutes | 3–45 minutes |
How It Works
Get Tokens
Bulk fetch DEX-supported tokens and cache to your DB, or search by name/symbol. Note each token’s id.
Get Quotes
Call GET /quotes with token IDs and optional slippage. Select the quote with type: "dex".
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.
Handle Approvals (if needed)
Broadcast approval transactions via user’s wallet.
Handle Signatures (if needed)
Have user sign EIP-712 typed data. For chained signatures, call POST /dex/chainSignatures until isComplete: true.
Create Order
Call POST /exchanges with quoteId, addressTo, addressFrom, and any collected signatures.
Broadcast & Confirm
If not off-chain, have user broadcast the transaction. Then call POST /dex/confirmTx with the transaction hash.
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:
Bulk Fetch + Cache
Token Search
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
}
Search for specific tokens by name or symbol on demand.async function searchTokens(query) {
const params = new URLSearchParams({
term: query,
hasDex: 'true',
pageSize: '20',
page: '1'
});
const response = await fetch(
`https://api-partner.houdiniswap.com/v2/tokens?${params}`,
{
headers: { 'Authorization': `${API_KEY}:${API_SECRET}` }
}
);
const { tokens } = await response.json();
return tokens; // use token `id` in /quotes requests
}
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
})
});
}
Houdini backend handles execution — still call confirmTx to start processing:if (order.metadata?.offChain) {
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
})
});
}
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