> ## Documentation Index
> Fetch the complete documentation index at: https://docs.houdiniswap.com/llms.txt
> Use this file to discover all available pages before exploring further.

# DEX Swap Integration

> Complete guide to integrating on-chain DEX swaps with wallet signatures and approvals

## Overview

DEX (Decentralized Exchange) swaps execute on-chain through smart contracts. Unlike CEX swaps that use deposit addresses, DEX swaps require users to connect their wallet, sign transactions, and broadcast them directly to the blockchain.

<Info>
  **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.
</Info>

### Supported Networks

DEX swaps are currently supported on the following networks:

* **EVM** (Ethereum, BSC, Polygon, etc.)
* **Solana**
* **SUI**
* **TRON**
* **TON**
* **Stellar**

<Warning>
  **Stellar Trustline Requirement**: For swaps involving Stellar assets, the integrator is responsible for ensuring that the destination account has the required [trustlines](https://developers.stellar.org/docs/learn/fundamentals/stellar-data-structures/accounts#trustlines) established before initiating the swap. Houdini does not handle trustline creation automatically. If the trustline is missing, the swap will fail.
</Warning>

## Key Characteristics

| Feature       | DEX                                    | CEX (Private/Standard)        |
| ------------- | -------------------------------------- | ----------------------------- |
| **Execution** | On-chain via smart contracts           | Off-chain via exchanges       |
| **Wallet**    | Required (MetaMask, etc.)              | Not required                  |
| **Custody**   | User keeps custody                     | User sends to deposit address |
| **Speed**     | Varies by network (seconds to minutes) | 3-45 minutes                  |
| **Approvals** | May require token approvals            | Not required                  |

## How It Works

DEX swaps follow this flow:

```mermaid theme={null}
sequenceDiagram
    participant User
    participant YourApp
    participant Wallet
    participant DEX
    participant Blockchain

    User->>YourApp: Request swap
    YourApp->>API: Get quote
    API-->>YourApp: Return route
    YourApp->>API: Check approvals
    API-->>YourApp: Return approval/signature requirements
    User->>Wallet: Send approval transactions (if needed)
    Wallet->>Blockchain: Submit approval
    User->>Wallet: Sign signatures (if needed)
    Wallet->>DEX: Execute swap
    DEX->>Blockchain: Process on-chain
    Blockchain-->>YourApp: Transaction confirmed
```

## Technical Flow Diagram

This diagram shows the complete integration flow with conditional logic:

```mermaid theme={null}
flowchart TD
    Start([Start Integration]) --> Step1[Step 1: Get Supported Assets<br/>/dexTokens, /networks]
    Step1 --> Step2[Step 2: Get DEX Quote<br/>/dexQuote]
    Step2 --> Step3[Step 3: Check Approvals & Signatures<br/>/dexApprove]

    Step3 --> CheckArrays{What's needed?}

    CheckArrays -->|Has approvals| Step4[Step 4: Send Approval Transactions<br/>Broadcast via wallet]
    CheckArrays -->|Has signatures only| Step5[Step 5: Process Signatures<br/>User signs with wallet]
    CheckArrays -->|Has both| Step4
    CheckArrays -->|Neither| Step7[Step 7: Execute Swap<br/>/dexExchange]

    Step4 --> CheckSig{Has signatures?}
    CheckSig -->|Yes| Step5
    CheckSig -->|No| Step6[Step 6: Check Allowance<br/>/dexHasEnoughAllowance]

    Step5 --> CheckSigType{Signature type?}
    CheckSigType -->|SINGLE| CollectSig[Collect signature]
    CheckSigType -->|CHAINED| ChainLoop[Loop: Sign → /chainSignatures<br/>Until isComplete = true]

    CollectSig --> CheckApproval{Sent approvals?}
    ChainLoop --> CheckApproval

    CheckApproval -->|Yes| Step6
    CheckApproval -->|No| Step7

    Step6 --> PollAllowance{hasEnoughAllowance?}
    PollAllowance -->|No| Wait[Wait 30 seconds]
    Wait --> PollAllowance
    PollAllowance -->|Yes| Step7

    Step7 --> CheckOffChain{offChain?}

    CheckOffChain -->|false| BroadcastTx[Broadcast transaction<br/>via wallet]
    CheckOffChain -->|true| ConfirmOffChain[Call /dexConfirmTx<br/>with txHash: undefined]

    BroadcastTx --> ConfirmTx[Call /dexConfirmTx<br/>with txHash]
    ConfirmTx --> Step8[Step 8: Track Status<br/>/status]
    ConfirmOffChain --> Step8

    Step8 --> PollStatus{Status?}
    PollStatus -->|0-2| WaitStatus[Wait 30 seconds]
    WaitStatus --> Step8
    PollStatus -->|4 COMPLETED| Success([✅ Swap Complete])
    PollStatus -->|6 FAILED| Failed([❌ Swap Failed])

    style Step1 fill:#e1f5ff,color:#000
    style Step2 fill:#e1f5ff,color:#000
    style Step3 fill:#fff4e1,color:#000
    style Step4 fill:#ffe1e1,color:#000
    style Step5 fill:#ffe1e1,color:#000
    style Step6 fill:#ffe1e1,color:#000
    style Step7 fill:#e1ffe1,color:#000
    style Step8 fill:#f0e1ff,color:#000
    style Success fill:#90EE90,color:#000
    style Failed fill:#FFB6C1,color:#000

```

## Integration Steps

### Step 1: Get Supported Assets

Discover which tokens and networks are available for DEX swaps. Learn more about [DEX tokens](/developer-hub/core-concepts/tokens-networks#dex-tokens) and [network identifiers](/developer-hub/core-concepts/tokens-networks#network-identifiers).

<Warning>
  **Performance Note**: The `/tokens` endpoint can take several seconds to minute to respond due to the large token list.

  * Save tokens in your backend database
  * Load tokens on server startup or via scheduled job
  * Serve token list from your database to frontend
  * Refresh cache periodically (e.g., every 24 hours)
</Warning>

<CodeGroup>
  ```javascript JavaScript theme={null}
  const tokens = await fetchFromHoudini('/dexTokens');

  // Get supported networks
  const networks = await fetchFromHoudini('/networks');
  ```

  ```bash cURL theme={null}
  # Get tokens
  curl -X GET "https://api-partner.houdiniswap.com/dexTokens" \
    -H "Authorization: your_api_key:your_api_secret"

  # Get networks
  curl -X GET "https://api-partner.houdiniswap.com/networks" \
    -H "Authorization: your_api_key:your_api_secret"
  ```
</CodeGroup>

### Step 2: Get DEX Quote

Request a quote for the DEX swap using token IDs from [`/dexTokens`](/developer-hub/core-concepts/tokens-networks#fetch-dex-tokens):

<CodeGroup>
  ```javascript JavaScript theme={null}
  const quotes = await fetchFromHoudini('/dexQuote', {
    tokenIdFrom: '6689b73ec90e45f3b3e51566',  // ETH token _id from /dexTokens
    tokenIdTo: '6689b73ec90e45f3b3e51553',    // USDT token _id from /dexTokens
    amount: 1,
  });

  // Select the best quote (first in array)
  const selectedQuote = quotes[0];
  ```

  ```bash cURL theme={null}
  curl -X GET "https://api-partner.houdiniswap.com/dexQuote?tokenIdFrom=6689b73ec90e45f3b3e51558&tokenIdTo=6689b73ec90e45f3b3e51553&amount=1&address=0x742d35Cc6634C0532925a3b844Bc454e4438f44e" \
    -H "Authorization: your_api_key:your_api_secret"
  ```
</CodeGroup>

**Quote Parameters:**

* `tokenIdFrom`: Source token `_id` from [`/dexTokens`](/developer-hub/core-concepts/tokens-networks#fetch-dex-tokens) endpoint
* `tokenIdTo`: Destination token `_id` from [`/dexTokens`](/developer-hub/core-concepts/tokens-networks#fetch-dex-tokens) endpoint
* `amount`: Amount to swap (float: `1` = 1 token)
* `slippage` (optional): Slippage percentage (e.g., `0.5` for 0.5%)
* `fromAddress` (optional): Specific source address for the swap
* `toAddress` (optional): Specific destination address for receiving tokens

<Tip>
  Remember to use the `_id` field from DEX tokens, not the `id` field. Learn more about [token identifiers](/developer-hub/core-concepts/tokens-networks#token-identifiers).
</Tip>

#### Quote Response

The response is an **array of quote options** from different DEX aggregators, sorted by best rate (highest output first). Each quote object contains:

| Field                | Type    | Description                                                                                                                  |
| -------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `swap`               | string  | DEX identifier code used in subsequent API calls                                                                             |
| `swapName`           | string  | Human-readable DEX name (e.g., "CowSwap", "Uniswap")                                                                         |
| `quoteId`            | string  | Unique quote ID - pass this to `/dexExchange`                                                                                |
| `amountOut`          | number  | Expected output amount (human-readable)                                                                                      |
| `amountOutUsd`       | number  | USD value of output amount                                                                                                   |
| `netAmountOut`       | number  | Net output after fees                                                                                                        |
| `feeUsd`             | number  | Fee amount in USD                                                                                                            |
| `duration`           | number  | Estimated swap duration in minutes                                                                                           |
| `type`               | string  | Always `"dex"` for DEX swaps                                                                                                 |
| `logoUrl`            | string  | DEX logo image URL for UI display                                                                                            |
| `raw`                | object  | Full route data - **pass this as `route` to subsequent endpoints**                                                           |
| `supportsSignatures` | boolean | Whether this DEX supports gasless signatures                                                                                 |
| `markupSupported`    | boolean | Whether partner markup is supported                                                                                          |
| `rewardsAvailable`   | boolean | Whether rewards are available for this route                                                                                 |
| `filtered`           | boolean | If `true` and sender/receiver addresses differ on same-chain swaps, this route is not supported and should be disabled in UI |

<Warning>
  **Filtering Routes**: When `filtered: true`, the route does not support different sender and receiver addresses for same-chain swaps. You must either:

  * Filter out these routes from the UI when `addressFrom !== addressTo`
  * Disable the route option and show a message explaining it's not available for this configuration
</Warning>

### Step 3: Check Approvals and Signatures

Before executing a swap, check what's needed from the user:

<CodeGroup>
  ```javascript JavaScript theme={null}
  const approvalCheck = await fetch(`${API_BASE_URL}/dexApprove`, {
    method: 'POST',
    headers: {
      'Authorization': `${API_KEY}:${API_SECRET}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      tokenIdFrom: '6689b73ec90e45f3b3e51566',
      tokenIdTo: '6689b73ec90e45f3b3e51553',
      amount: 1,
      addressFrom: '0x45CF73349a4895fabA18c0f51f06D79f0794898D', // user's address
      swap: selectedQuote.swap, // From step 2
      route: selectedQuote.raw  // From step 2
    })
  });

  const { approvals, signatures } = await approvalCheck.json();
  ```

  ```bash cURL theme={null}
  curl -X POST "https://api-partner.houdiniswap.com/dexApprove" \
    -H "Authorization: your_api_key:your_api_secret" \
    -H "Content-Type: application/json" \
    -d '{
      "tokenIdFrom": "6689b73ec90e45f3b3e51566",
      "tokenIdTo": "6689b73ec90e45f3b3e51553",
      "amount": 1,
      "swap": "cs",
      "addressFrom": "0x45CF73349a4895fabA18c0f51f06D79f0794898D",
      "route": {...}
    }'
  ```
</CodeGroup>

**Response Contains:**

* `approvals`: Array of on-chain approval transactions (may be empty)
* `signatures`: Array of signatures needed (may be empty)

<Note>
  **Both Arrays Can Exist**: You may receive both approvals AND signatures. Handle approvals first, then signatures.
</Note>

#### Understanding Approvals

If the `approvals` array is not empty, the user must approve the DEX to spend their tokens. See more in Step 4.

#### Understanding Signatures

The `signatures` array can contain two types:

**1. SINGLE Type** - Simple one-time signature:
**Action:** User signs once, add to results, done.

**2. CHAINED Type** - Multi-step signature (currently only for Cowswap):
**Action:** User signs, call `/chainSignatures`, repeat until `isComplete: true`.

See more in Step 5.

### Step 4: Send Approval Transactions (if needed)

If the `approvals` array is not empty, broadcast approval transactions:

<CodeGroup>
  ```javascript JavaScript theme={null}
  // Send each approval transaction
  if (approvals && approvals.length > 0) {
    for (const approval of approvals) {
      // Send approval transaction through user's wallet
      const approvalTx = await walletProvider.sendTransaction({
        to: approval.to,      // Token contract address
        data: approval.data   // Encoded approve() call
      });

      // Wait for transaction to be mined
      await approvalTx.wait();
      console.log('Approval confirmed:', approvalTx.hash);
    }
  }
  ```

  ```bash Example theme={null}
  # User broadcasts approval transaction via wallet
  # Example: Approving USDC to be spent by Uniswap router
  to: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"  # USDC token
  data: "0x095ea7b3..."  # approve(spender, amount) encoded
  ```
</CodeGroup>

<Warning>
  **Skip This Step If**: No approvals were required (empty `approvals` array in Step 3).
</Warning>

### Step 5: Process Signatures (if needed)

Handle signature requests from Step 3:

<CodeGroup>
  ```javascript JavaScript theme={null}
  // Helper function to process all signatures
  async function processSignatures(signatures) {
    if (!signatures || signatures.length === 0) return [];

    const results = [];

    for (const sig of signatures) {
      // Prompt user to sign
      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 CHAINED and not complete, get next signature
      if (sig.type === 'CHAINED' && !sig.isComplete) {
        const nextSigResponse = await fetch(`${API_BASE_URL}/chainSignatures`, {
          method: 'POST',
          headers: {
            'Authorization': `${API_KEY}:${API_SECRET}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            tokenIdFrom,
            tokenIdTo,
            addressFrom: userWalletAddress,
            route: quote.route,
            swap: quote.swap,
            previousSignature: signatureObject,
            signatureKey: sig.key,
            signatureStep: sig.step
          })
        });

        const { chainSignatures } = await nextSigResponse.json();

        // Recursively process next signature
        if (chainSignatures?.length > 0) {
          const chainResults = await processSignatures(chainSignatures);
          if (chainResults.length > 0) {
            results.push(chainResults[chainResults.length - 1]); // Only add final
          }
        }
      } else {
        // SINGLE type or final CHAINED signature
        results.push(signatureObject);
      }
    }

    return results;
  }

  // Usage
  const collectedSignatures = await processSignatures(signatures);
  ```
</CodeGroup>

**Key Points:**

* **SINGLE**: User signs once, done
* **CHAINED**: User signs → API call → User signs again → Repeat until complete
* Only keep the **final** signature from CHAINED sequences

<Warning>
  **Skip This Step If**: No signatures were required (empty `signatures` array in Step 3).
</Warning>

### Step 6: Check Allowance (if approvals were sent)

If you sent approval transactions in Step 4, verify they are confirmed on-chain:

<CodeGroup>
  ```javascript JavaScript theme={null}
  // Poll until approval is confirmed on-chain
  if (approvals && approvals.length > 0) {
    let hasAllowance = false;
    while (!hasAllowance) {
      const allowanceCheck = await fetchFromHoudini('/dexHasEnoughAllowance', {
        tokenIdFrom: '6689b73ec90e45f3b3e51566',
        tokenIdTo: '6689b73ec90e45f3b3e51553',
        amount: 1,
        addressFrom: "0x45CF73349a4895fabA18c0f51f06D79f0794898D",
        swap: quote.swap,
        route: quote.raw
      });

      hasAllowance = allowanceCheck.hasEnoughAllowance;

      if (!hasAllowance) {
        await sleep(30000); // Wait 30 seconds before checking again
      }
    }
    console.log('Allowance confirmed!');
  }
  ```

  ```bash cURL theme={null}
  curl -X GET "https://api-partner.houdiniswap.com/dexHasEnoughAllowance?tokenIdFrom=6689b73ec90e45f3b3e51566&tokenIdTo=6689b73ec90e45f3b3e51553&amount=1&addressFrom=0x45CF73349a4895fabA18c0f51f06D79f0794898D&swap=cs&route=..." \
    -H "Authorization: your_api_key:your_api_secret"
  ```
</CodeGroup>

<Warning>
  **Skip This Step If**: No approvals were sent in Step 4.
</Warning>

### Step 7: Execute the Swap

Now execute the swap with any collected signatures:

<CodeGroup>
  ```javascript JavaScript theme={null}
  const swapResponse = await fetch(`${API_BASE_URL}/dexExchange`, {
    method: 'POST',
    headers: {
      'Authorization': `${API_KEY}:${API_SECRET}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      tokenIdFrom: '6689b73ec90e45f3b3e51566',
      tokenIdTo: '6689b73ec90e45f3b3e51553',
      amount: 26.2244,
      addressFrom: userWalletAddress,  // User's wallet address
      addressTo: destination address,    // Destination address to receive fund
      route: quote.raw,                // Route object from quote response
      swap: quote.swap,                // DEX identifier (e.g., "zx", "cs", "un")
      quoteId: quote.quoteId,          // Quote ID from quote response
      signatures: collectedSignatures, // From Step 5 (can be empty array)
      destinationTag: '',              // For chains that require memo/tag
      deviceInfo: 'web',               // Device type: 'web', 'ios', 'android'
      isMobile: false,                 // Boolean indicating mobile device
      walletInfo: 'MetaMask',          // Wallet name being used (e.g., "MetaMask", "Rabby Wallet")
      slippage: null                   // Custom slippage percentage (null = use default) 0.5 = 0.5%
    })
  });

  const { order } = await swapResponse.json();
  console.log('Swap created:', order.houdiniId);
  console.log('Off-chain?', order.metadata.offChain);
  ```

  ```bash cURL theme={null}
  curl -X POST "https://api-partner.houdiniswap.com/dexExchange" \
    -H "Authorization: your_api_key:your_api_secret" \
    -H "Content-Type: application/json" \
    -d '{
      "tokenIdFrom": "6689b73ec90e45f3b3e51566",
      "tokenIdTo": "6689b73ec90e45f3b3e51553",
      "amount": 26.2244,
      "addressFrom": "0xb7dE6b6eEBF7401aFea5a49D6405C9048fEf2d40",
      "addressTo": "0xb7dE6b6eEBF7401aFea5a49D6405C9048fEf2d40",
      "route": {...},
      "swap": "zx",
      "quoteId": "69155e0bdb5ab0cbe27e2709",
      "signatures": [],
      "destinationTag": "",
      "deviceInfo": "web",
      "isMobile": false,
      "walletInfo": "MetaMask",
      "slippage": null
    }'
  ```
</CodeGroup>

**Request Parameters:**

* `tokenIdFrom`: Source token ID
* `tokenIdTo`: Destination token ID
* `amount`: Amount to swap (float)
* `addressFrom`: User's wallet address (source)
* `addressTo`: Destination address for receiving tokens
* `route`: Complete route object from quote response (`quote.raw`)
* `swap`: DEX identifier from quote (e.g., "zx" for 0x, "cs" for Cowswap)
* `quoteId`: Quote ID from quote response
* `signatures`: Array of signature objects from Step 5 (empty if none required)
* `destinationTag`: Memo/tag for chains that require it (empty string if not needed)
* `deviceInfo`: Device type - "web", "ios", "android", or custom identifier
* `isMobile`: Boolean indicating if request is from mobile device
* `walletInfo`: Name of wallet being used (e.g., "MetaMask", "Rabby Wallet")
* `slippage`: Custom slippage tolerance (null to use default from quote)

**Response Contains:**

* `order.houdiniId`: Unique swap identifier for tracking
* `order.metadata.offChain`: Boolean indicating if user transaction is needed
* `order.metadata.to`: DEX router address (if offChain: false)
* `order.metadata.data`: Encoded swap call (if offChain: false)
* `order.metadata.value`: ETH value for native swaps (if offChain: false)

#### Broadcast Transaction and Confirm

After receiving the swap order, you need to:

1. Have the user broadcast the transaction (if `offChain: false`)
2. Call `/dexConfirmTx` to notify Houdini of the transaction hash

<Tabs>
  <Tab title="On-Chain Swap (offChain: false)">
    User must broadcast the transaction, then confirm with Houdini:

    ```javascript theme={null}
    if (!order.metadata.offChain) {
      // Step 1: User sends transaction via wallet
      const tx = await walletProvider.sendTransaction({
        to: order.metadata.to,
        data: order.metadata.data,
        value: order.metadata.value || '0'
      });

      const receipt = await tx.wait();
      const txHash = receipt.hash;

      console.log('Transaction broadcast:', txHash);

      // Step 2: Confirm with Houdini API
      await fetch(`${API_BASE_URL}/dexConfirmTx`, {
        method: 'POST',
        headers: {
          'Authorization': `${API_KEY}:${API_SECRET}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          houdiniId: order.houdiniId,
          txHash: txHash
        })
      });

      console.log('Transaction confirmed with Houdini');
    }
    ```
  </Tab>

  <Tab title="Off-Chain Swap (offChain: true)">
    Backend handles execution (e.g., Cowswap), but you still need to confirm:

    ```javascript theme={null}
    if (order.metadata.offChain) {
      // No user transaction needed - Houdini backend will execute the swap
      // But still need to call dexConfirmTx to start processing

      await fetch(`${API_BASE_URL}/dexConfirmTx`, {
        method: 'POST',
        headers: {
          'Authorization': `${API_KEY}:${API_SECRET}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          houdiniId: order.houdiniId,
          txHash: undefined  // No transaction hash for off-chain swaps
        })
      });

      console.log('Off-chain swap confirmed with Houdini');
    }
    ```
  </Tab>
</Tabs>

<Warning>
  **Critical Step**: You MUST call `/dexConfirmTx` after creating the swap order:

  * **On-chain swaps**: Pass the transaction hash after user broadcasts
  * **Off-chain swaps**: Pass `txHash: undefined` to start backend processing

  Without this call, the swap will not be processed and status tracking will not work.
</Warning>

### Step 8: Track Swap Status

Monitor the swap progress:

<CodeGroup>
  ```javascript JavaScript theme={null}
  const status = await fetchFromHoudini('/status', {
    id: order.houdiniId
  });

  console.log('Status:', status.status);
  console.log('Transaction hash:', status.txHash);
  ```

  ```bash cURL theme={null}
  curl -X GET "https://api-partner.houdiniswap.com/status?id=xyz123" \
    -H "Authorization: your_api_key:your_api_secret"
  ```
</CodeGroup>

**Status Codes:**

* `0` = WAITING - Awaiting transaction
* `1` = CONFIRMING - Transaction submitted, waiting for confirmations
* `2` = EXCHANGING - Processing swap
* `4` = COMPLETED - Swap complete ✅
* `6` = FAILED - Swap failed ❌

<Tip>
  **Polling**: Check status every 30 seconds for on-chain swaps. They typically complete in seconds to a few minutes depending on network congestion.
</Tip>

## Complete Example

For a complete, runnable Node.js example:

<Card title="DEX Swap Example" icon="github" href="https://github.com/HoudiniSwap/houdini-api-examples/blob/main/examples/dex-swap-example.ts">
  Complete DEX swap script with approval handling and wallet simulation
</Card>

## Next Steps

<CardGroup cols={2}>
  <Card title="Private Swaps" icon="lock" href="/developer-hub/swap-flows/private-swap">
    Learn about CEX-based private swaps
  </Card>

  <Card title="Standard Swaps" icon="gauge" href="/developer-hub/swap-flows/standard-swap">
    Integrate fast single-hop CEX swaps
  </Card>

  <Card title="Order Lifecycle" icon="rotate" href="/developer-hub/core-concepts/order-lifecycle">
    Understand swap status progression
  </Card>

  <Card title="Error Handling" icon="triangle-exclamation" href="/developer-hub/troubleshooting/errors">
    Handle errors and edge cases
  </Card>
</CardGroup>
