Skip to content

Fluid DEX V2: Liquidity Source Integration ​

Complete integration guide for adding Fluid DEX V2 as a liquidity source in DEX aggregators and routing protocols.

Table of Contents ​

  1. Overview
  2. Quick Start
  3. Data Access with Resolver
  4. Pool Discovery & State Reading
  5. Router-based Integration
  6. Callback-based Integration (Advanced)
  7. Operating DEX Modules
  8. Token Settlement with settle()
  9. Best Practices
  10. Resources

Overview ​

What is Fluid DEX v2? ​

Fluid DEX v2 is an advanced decentralized exchange built on top of the Fluid Liquidity Layer, featuring:

  • Single Contract, Multiple DEX Types: One contract handles different types of AMMs (Automated Market Makers)
  • Smart Collateral & Debt: Revolutionary financial primitives for enhanced capital efficiency
  • Dynamic Fees: On-chain adaptive fee mechanisms and custom hooks
  • Flash Accounting: Ultra-efficient settlement for complex multi-step operations

Supported DEX Types ​

Currently, DEX v2 supports 4 major DEX types:

  1. DEX Type 1 (D1): DEX v1 Smart Collateral (Coming Soon)
  2. DEX Type 2 (D2): DEX v1 Smart Debt (Coming Soon)
  3. DEX Type 3 (D3): Smart Collateral Range Orders
  4. DEX Type 4 (D4): Smart Debt Range Orders

This guide focuses on D3 and D4 integration as they are currently available for use.

Architecture ​

  • Core Contract: FluidDexV2 - Singleton contract handling all DEX operations
  • Resolver Contract: FluidDexV2D3D4Resolver - Provides pool state reading, position analysis, and liquidity distribution queries
  • Router Contract: FluidDexV2Router - Simplified integration with easy-to-use functions
  • Callback Pattern: Gas-efficient but advanced integration requiring custom callback implementation

Quick Start ​

This guide covers both integration methods. Choose the router for simplicity or callbacks for maximum control and gas efficiency.

1. Integration Methods ​

Method 1: Router-based Integration

  • What it is: Use the FluidDexV2Router contract with simple function calls
  • Best for: Most integrators who want straightforward swap functionality
  • Pros: Easy integration, no callback implementation needed, supports both single and multi-hop swaps, native ETH support
  • Cons: Slightly higher gas costs due to additional contract layer

Method 2: Callback-based Integration (Advanced)

  • What it is: You implement callback functions that the DEX calls during operations
  • Best for: Advanced integrators who want maximum control and gas efficiency
  • Pros: Maximum gas efficiency, full control over token transfers, can batch multiple operations
  • Cons: Complex implementation, requires understanding callback mechanism and security

2. Data Access ​

Resolver Contract: FluidDexV2D3D4Resolver

  • Pool state reading and analysis
  • Position data queries
  • Liquidity distribution analysis
  • Used by both integration methods for data access

3. Core Data Structures ​

Essential structures for DEX v2 integration:

solidity
// Pool identification - every pool is uniquely identified by this structure
struct DexKey {
    address token0;        // Always the smaller token address (lexicographically)
    address token1;        // Always the larger token address (lexicographically)
    uint24 fee;           // Fee tier (e.g., 500 = 0.05%) or 0xFFFFFF for dynamic fees
    uint24 tickSpacing;   // Price granularity (1 = finest, higher = coarser)
    address controller;   // Optional controller contract (use address(0) if none)
}

// Parameters for exact input swaps (specify input amount, get variable output)
struct SwapInParams {
    DexKey dexKey;         // Pool to swap in
    bool swap0To1;         // Direction: true = token0 -> token1, false = token1 -> token0
    uint256 amountIn;      // Exact amount of input tokens
    uint256 amountOutMin;  // Minimum acceptable output amount (slippage protection)
    bytes controllerData;  // Optional data for controller contract
}

// Parameters for exact output swaps (specify output amount, pay variable input)
struct SwapOutParams {
    DexKey dexKey;         // Pool to swap in
    bool swap0To1;         // Direction: true = token0 -> token1, false = token1 -> token0
    uint256 amountOut;     // Exact amount of output tokens desired
    uint256 amountInMax;   // Maximum acceptable input amount (slippage protection)
    bytes controllerData;  // Optional data for controller contract
}

4. Contract Addresses ​

NetworkContractAddress
EthereumFluidDexV2TBD
EthereumFluidDexV2D3D4ResolverTBD
EthereumFluidDexV2RouterTBD
PolygonFluidDexV20x04e9FF525b6541Ca4a0Dca3326b79547f9057E15
PolygonFluidDexV2Resolver0x2Ba521a909BDBE56183e3cd5F27962466e674610
PolygonFluidDexV2Router0xe16aD4692B2505d21959f22Ff32D18d808D35759

Data Access with Resolver ​

Using the Resolver Contract ​

Use the FluidDexV2D3D4Resolver contract for all pool state reading, position analysis, and liquidity distribution queries.

Resolver Data Structures ​

solidity
// Pool state variables (packed and unpacked versions)
struct DexVariables {
    int256 currentTick;
    uint256 currentSqrtPriceX96;
    uint256 feeGrowthGlobal0X102;
    uint256 feeGrowthGlobal1X102;
}

struct DexVariables2 {
    uint256 protocolFee0To1;
    uint256 protocolFee1To0;
    uint256 protocolCutFee;
    uint256 token0Decimals;
    uint256 token1Decimals;
    uint256 activeLiquidity;
    bool fetchDynamicFeeFlag;
    bool inbuiltDynamicFeeFlag;
    uint256 lpFee;
    uint256 maxDecayTime;
    uint256 priceImpactToFeeDivisionFactor;
    uint256 minFee;
    uint256 maxFee;
    int256 netPriceImpact;
    uint256 lastUpdateTimestamp;
    uint256 decayTimeRemaining;
}

// Complete pool state (returned by resolver)
struct DexPoolState {
    uint256 dexVariablesPacked;
    uint256 dexVariables2Packed;
    DexVariables dexVariablesUnpacked;
    DexVariables2 dexVariables2Unpacked;
}

// Position-related structures
struct PositionData {
    uint256 liquidity;
    uint256 feeGrowthInside0X102;
    uint256 feeGrowthInside1X102;
}

struct PositionInfo {
    PositionData positionData;
    uint256 amount0;
    uint256 amount1;
    uint256 feeAccruedToken0;
    uint256 feeAccruedToken1;
}

// Liquidity distribution analysis
struct TickLiquidity {
    int24 tick;
    uint256 liquidity;
}

Key Methods ​

solidity
contract FluidDexV2D3D4Resolver {
    // Pool State Reading
    function getDexId(DexKey memory dexKey) public view returns (bytes32 dexId);
    function getDexPoolState(uint256 dexType, bytes32 dexId) public view returns (DexPoolState memory);
    function getDexPoolState(uint256 dexType, DexKey memory dexKey) public view returns (DexPoolState memory);
    
    // Position Information
    function getPositionData(
        uint256 dexType,
        DexKey memory dexKey,
        address user,
        int24 tickLower,
        int24 tickUpper,
        bytes32 positionSalt
    ) public view returns (PositionData memory);
    
    function getPositionInfo(
        uint256 dexType,
        DexKey memory dexKey,
        address user,
        int24 tickLower,
        int24 tickUpper,
        bytes32 positionSalt
    ) public view returns (PositionInfo memory);
    
    // Liquidity Analysis
    function getLiquidityAmounts(
        uint256 dexType,
        DexKey memory dexKey,
        int24 startTick,
        int24 endTick,
        uint256 startLiquidity
    ) public view returns (TickLiquidity[] memory);
}

Basic Usage Example ​

solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.29;

import "contracts/periphery/resolvers/dexV2/d3d4/main.sol";
import "contracts/protocols/dexV2/interfaces/iDexV2.sol";

contract FluidDexV2Integrator {
    FluidDexV2D3D4Resolver constant resolver = FluidDexV2D3D4Resolver(
        0x... // TBD - resolver address
    );
    
    IFluidDexV2 constant dexV2 = IFluidDexV2(
        0x... // TBD - main contract address
    );
    
    uint256 constant D3_DEX_TYPE = 3; // Smart Collateral Range Orders
    uint256 constant D4_DEX_TYPE = 4; // Smart Debt Range Orders
    
    function getPoolState(DexKey memory dexKey, uint256 dexType) external view returns (
        bytes32 dexId,
        int256 currentTick,
        uint256 currentPrice,
        uint256 activeLiquidity,
        uint256 fee
    ) {
        dexId = resolver.getDexId(dexKey);
        DexPoolState memory state = resolver.getDexPoolState(dexType, dexId);
        
        currentTick = state.dexVariablesUnpacked.currentTick;
        currentPrice = state.dexVariablesUnpacked.currentSqrtPriceX96;
        activeLiquidity = state.dexVariables2Unpacked.activeLiquidity;
        fee = state.dexVariables2Unpacked.lpFee;
    }
}

Pool Discovery & State Reading ​

Discovering Available Pools ​

DEX v2 doesn't have a single discovery method. Pools are created through governance and can be discovered by:

  1. Event Monitoring: Listen for pool initialization events
  2. Known Pool Lists: Maintain a registry of known pools
  3. On-chain Queries: Check if specific DexKeys exist by querying pool state
solidity
// Listen for this event to discover new pools
event LogInitialize(
    uint256 dexType,
    bytes32 dexId,
    address token0,
    address token1,
    uint24 fee,
    uint24 tickSpacing,
    address controller,
    uint256 sqrtPriceX96
);
solidity
function checkPoolExists(DexKey memory dexKey, uint256 dexType) external view returns (bool exists) {
    bytes32 dexId = resolver.getDexId(dexKey);
    DexPoolState memory state = resolver.getDexPoolState(dexType, dexId);
    
    // Pool exists if it has been initialized (non-zero sqrt price)
    exists = state.dexVariablesUnpacked.currentSqrtPriceX96 > 0;
}

Reading Pool State ​

solidity
function readPoolDetails(DexKey memory dexKey, uint256 dexType) external view returns (
    int256 currentTick,
    uint256 sqrtPriceX96,
    uint256 activeLiquidity,
    uint256 fee,
    uint256 token0Decimals,
    uint256 token1Decimals,
    bool isDynamicFee
) {
    DexPoolState memory state = resolver.getDexPoolState(dexType, dexKey);
    
    currentTick = state.dexVariablesUnpacked.currentTick;
    sqrtPriceX96 = state.dexVariablesUnpacked.currentSqrtPriceX96;
    activeLiquidity = state.dexVariables2Unpacked.activeLiquidity;
    fee = state.dexVariables2Unpacked.lpFee;
    token0Decimals = state.dexVariables2Unpacked.token0Decimals;
    token1Decimals = state.dexVariables2Unpacked.token1Decimals;
    isDynamicFee = state.dexVariables2Unpacked.inbuiltDynamicFeeFlag;
}

Analyzing Liquidity Distribution ​

solidity
function analyzeLiquidity(
    DexKey memory dexKey,
    uint256 dexType,
    int24 tick
) external view returns (TickLiquidity[] memory liquidityDistribution) {
    DexPoolState memory state = resolver.getDexPoolState(dexType, dexKey);
    int24 currentTick = state.dexVariablesUnpacked.currentTick;
    uint256 currentLiquidity = state.dexVariables2Unpacked.activeLiquidity;
    
    liquidityDistribution = resolver.getLiquidityAmounts(
        dexType,
        dexKey,
        currentTick,
        tick,
        currentLiquidity
    );
}

Router-based Integration ​

Overview ​

The FluidDexV2Router provides a simple, user-friendly interface for DEX v2 swaps. It handles all the complex callback logic internally, making integration straightforward while supporting both single-hop and multi-hop swaps across D3 and D4 pools.

Key Features ​

  • Simple API: Just call functions directly, no callback implementation needed
  • Native Token Support: Uses 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE to represent the native token
  • Multi-hop Routing: Support for complex swap paths across multiple pools
  • Mixed DEX Types: Can route through both D3 and D4 pools in a single transaction
  • Slippage Protection: Built-in minimum/maximum amount validation
  • Gas Refunds: Automatically refunds excess ETH sent with transactions

Router Interface ​

solidity
interface IFluidDexV2Router {
    // Single-hop exact input swap
    function swapIn(
        uint256 dexType_,                    // 3 for D3, 4 for D4
        SwapInParams calldata swapInParams_, // Swap parameters
        address to_                          // Token recipient
    ) external payable returns (uint256 amountOut_);
    
    // Single-hop exact output swap
    function swapOut(
        uint256 dexType_,                     // 3 for D3, 4 for D4
        SwapOutParams calldata swapOutParams_, // Swap parameters
        address to_                           // Token recipient
    ) external payable returns (uint256 amountIn_);
    
    // Multi-hop exact input swap
    function swapInMulti(
        address[] calldata path_,                 // Token path [tokenA, tokenB, tokenC]
        SwapInConfig[] calldata swapInConfigs_,   // Config for each hop
        uint256 amountIn_,                        // Input amount
        address to_                               // Token recipient
    ) external payable returns (uint256 amountOut_);
    
    // Multi-hop exact output swap
    function swapOutMulti(
        address[] calldata path_,                  // Token path [tokenA, tokenB, tokenC]
        SwapOutConfig[] calldata swapOutConfigs_,  // Config for each hop
        uint256 amountOut_,                        // Desired output amount
        address to_                                // Token recipient
    ) external payable returns (uint256 amountIn_);
}

// Multi-hop configuration structs
struct SwapInConfig {
    uint256 dexType;        // 3 for D3, 4 for D4
    DexKey dexKey;          // Pool identification
    uint256 amountOutMin;   // Minimum output for this hop
    bytes controllerData;   // Optional controller data
}

struct SwapOutConfig {
    uint256 dexType;        // 3 for D3, 4 for D4
    DexKey dexKey;          // Pool identification
    uint256 amountInMax;    // Maximum input for this hop
    bytes controllerData;   // Optional controller data
}

Router Usage Examples ​

1. Single-hop Exact Input Swap (swapIn) ​

Swap an exact amount of input tokens for a minimum amount of output tokens:

solidity
import "contracts/periphery/dexV2Router/main.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract MyDEXAggregator {
    FluidDexV2Router constant router = FluidDexV2Router(0x...); // Router address
    
    function swapUSDCForUSDT(
        uint256 amountIn,
        uint256 minAmountOut,
        address recipient
    ) external returns (uint256 amountOut) {
        // Define the pool
        DexKey memory dexKey = DexKey({
            token0: 0xA0b86a33E6441E4C7449e6C4c6E9E6C4c6E9E6C4, // USDC (smaller address)
            token1: 0xdAC17F958D2ee523a2206206994597C13D831ec7, // USDT (larger address)
            fee: 500,        // 0.05% fee
            tickSpacing: 10, // Tick spacing
            controller: address(0) // No controller
        });
        
        // Setup swap parameters
        SwapInParams memory swapParams = SwapInParams({
            dexKey: dexKey,
            swap0To1: true,      // USDC -> USDT (token0 -> token1)
            amountIn: amountIn,
            amountOutMin: minAmountOut,
            controllerData: ""
        });
        
        // Approve router to spend USDC
        IERC20(dexKey.token0).approve(address(router), amountIn);
        
        // Execute swap
        amountOut = router.swapIn(
            3,          // D3 DEX type
            swapParams,
            recipient
        );
    }
}

2. Single-hop Exact Output Swap (swapOut) ​

Swap a maximum amount of input tokens for an exact amount of output tokens:

solidity
function swapETHForExactUSDC(
    uint256 exactAmountOut,
    address recipient
) external payable returns (uint256 amountIn) {
    DexKey memory dexKey = DexKey({
        token0: 0xA0b86a33E6441E4C7449e6C4c6E9E6C4c6E9E6C4, // USDC
        token1: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, // ETH (native token)
        fee: 3000,       // 0.3% fee
        tickSpacing: 60,
        controller: address(0)
    });
    
    SwapOutParams memory swapParams = SwapOutParams({
        dexKey: dexKey,
        swap0To1: false,     // ETH -> USDC (token1 -> token0)
        amountOut: exactAmountOut,
        amountInMax: msg.value, // Maximum ETH to spend
        controllerData: ""
    });
    
    // Execute swap - ETH is sent via msg.value
    amountIn = router.swapOut{value: msg.value}(
        3,          // D3 DEX type
        swapParams,
        recipient
    );
    
    // Excess ETH is automatically refunded
}

3. Multi-hop Exact Input Swap (swapInMulti) ​

Swap through multiple pools: ETH → USDC → USDT

solidity
function swapETHToUSDTViaUSDC(
    uint256 amountIn,
    uint256 minAmountOut,
    address recipient
) external payable returns (uint256 amountOut) {
    // Define the swap path: ETH -> USDC -> USDT
    address[] memory path = new address[](3);
    path[0] = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; // ETH
    path[1] = 0xA0b86a33E6441E4C7449e6C4c6E9E6C4c6E9E6C4; // USDC
    path[2] = 0xdAC17F958D2ee523a2206206994597C13D831ec7; // USDT
    
    // Configure each hop
    SwapInConfig[] memory configs = new SwapInConfig[](2);
    
    // Hop 1: ETH -> USDC (D3 pool)
    configs[0] = SwapInConfig({
        dexType: 3,
        dexKey: DexKey({
            token0: path[1], // USDC (smaller address)
            token1: path[0], // ETH (larger address)
            fee: 3000,
            tickSpacing: 60,
            controller: address(0)
        }),
        amountOutMin: 0, // No intermediate slippage check
        controllerData: ""
    });
    
    // Hop 2: USDC -> USDT (D4 pool)
    configs[1] = SwapInConfig({
        dexType: 4,
        dexKey: DexKey({
            token0: path[1], // USDC (smaller address)
            token1: path[2], // USDT (larger address)
            fee: 100,
            tickSpacing: 1,
            controller: address(0)
        }),
        amountOutMin: minAmountOut, // Final slippage protection
        controllerData: ""
    });
    
    // Execute multi-hop swap
    amountOut = router.swapInMulti{value: msg.value}(
        path,
        configs,
        amountIn,
        recipient
    );
}

4. Multi-hop Exact Output Swap (swapOutMulti) ​

Get exact USDT output by swapping through ETH → USDC → USDT:

solidity
function getExactUSDTViaMultiHop(
    uint256 exactAmountOut,
    address recipient
) external payable returns (uint256 amountIn) {
    // Path: ETH -> USDC -> USDT (same as input example)
    address[] memory path = new address[](3);
    path[0] = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; // ETH
    path[1] = 0xA0b86a33E6441E4C7449e6C4c6E9E6C4c6E9E6C4; // USDC
    path[2] = 0xdAC17F958D2ee523a2206206994597C13D831ec7; // USDT
    
    SwapOutConfig[] memory configs = new SwapOutConfig[](2);
    
    // Hop 1: ETH -> USDC
    configs[0] = SwapOutConfig({
        dexType: 3,
        dexKey: DexKey({
            token0: path[1], // USDC
            token1: path[0], // ETH
            fee: 3000,
            tickSpacing: 60,
            controller: address(0)
        }),
        amountInMax: msg.value, // Max ETH to spend
        controllerData: ""
    });
    
    // Hop 2: USDC -> USDT
    configs[1] = SwapOutConfig({
        dexType: 4,
        dexKey: DexKey({
            token0: path[1], // USDC
            token1: path[2], // USDT
            fee: 100,
            tickSpacing: 1,
            controller: address(0)
        }),
        amountInMax: type(uint256).max, // No intermediate limit
        controllerData: ""
    });
    
    // Execute exact output multi-hop swap
    amountIn = router.swapOutMulti{value: msg.value}(
        path,
        configs,
        exactAmountOut,
        recipient
    );
}

Callback-based Integration (Advanced) ​

💡 New to DEX v2? Consider starting with the Router-based Integration above for a simpler approach. This section covers the advanced callback method for maximum control and gas efficiency.

The Callback Architecture ​

Why Callbacks? DEX v2 uses callbacks to give you maximum flexibility. You can perform multiple operations, complex routing, or custom logic all in one transaction.

The Flow:

Your Contract → startOperation() → DEX calls your callback → Your Logic → Settlement → Complete

Step by Step:

  1. You initiate: Call dexV2.startOperation(encodedData) with your operation data
  2. DEX calls back: DEX immediately calls startOperationCallback(encodedData) on your contract
  3. You execute: Inside the callback, perform swaps using operate() and handle tokens using settle()
  4. DEX verifies: DEX checks that all token transfers balance out correctly
  5. Transaction completes: If everything balances, the transaction succeeds

Key Benefits:

  • Atomic operations: Multiple swaps and operations in one transaction
  • Gas efficiency: No intermediate token transfers between operations
  • Flexibility: Implement any trading strategy or routing logic you want

Core Interfaces ​

solidity
interface IFluidDexV2 {
    // Initiates any operation - calls back to your contract
    function startOperation(bytes calldata data) external returns (bytes memory result);
    
    // Executes specific DEX operations (swap, deposit, etc.)
    function operate(
        uint256 dexType,        // 3 for D3, 4 for D4
        uint256 implementationId, // 1 for swap, 2 for user ops
        bytes memory data
    ) external returns (bytes memory returnData);
    
    // Handles token transfers and settlement
    function settle(
        address token,
        int256 supplyAmount,    // Positive = supply to DEX
        int256 borrowAmount,    // Positive = borrow from DEX  
        int256 storeAmount,     // Positive = store in DEX
        address to,             // Recipient address
        bool isCallback         // true = DEX calls dexCallback for transfer, false = approval based transfers
    ) external payable;
}

// Your contract must implement this
interface IDexV2Callbacks {
    function startOperationCallback(bytes calldata data) external returns (bytes memory);
    function dexCallback(address token, address to, uint256 amount) external;
}

// Swap function interfaces (for encoding with operate())
interface IDexV2SwapModule {
    function swapIn(SwapInParams calldata params) external returns (uint256 amountOut, uint256 protocolFee, uint256 lpFee);
    function swapOut(SwapOutParams calldata params) external returns (uint256 amountIn, uint256 protocolFee, uint256 lpFee);
}

Basic Swap Example ​

Here's a complete example showing how to implement a simple swap:

solidity
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract FluidDexV2Integrator is IDexV2Callbacks {
    using SafeERC20 for IERC20;
    
    IFluidDexV2 public immutable dexV2;
    
    constructor(address _dexV2) {
        dexV2 = IFluidDexV2(_dexV2);
    }
    
    // Public function users call to swap
    function swapTokens(
        DexKey memory dexKey,
        uint256 dexType,        // 3 for D3, 4 for D4
        bool swap0To1,
        uint256 amountIn,
        uint256 minAmountOut,
        address recipient
    ) external returns (uint256 amountOut) {
        // Encode parameters for callback
        bytes memory callbackData = abi.encode(
            dexKey,
            dexType,
            swap0To1,
            amountIn,
            minAmountOut,
            recipient
        );
        
        // Start the operation - DEX will call startOperationCallback
        bytes memory result = dexV2.startOperation(callbackData);
        
        // Decode the result
        (amountOut,,) = abi.decode(result, (uint256, uint256, uint256));
    }
    
    // DEX calls this function
    function startOperationCallback(bytes calldata data) external returns (bytes memory) {
        require(msg.sender == address(dexV2), "Unauthorized");
        
        // Decode parameters from callback data
        (
            DexKey memory dexKey,
            uint256 dexType,
            bool swap0To1,
            uint256 amountIn,
            uint256 minAmountOut,
            address recipient
        ) = abi.decode(data, (DexKey, uint256, bool, uint256, uint256, address));
        
        // 1. Execute the swap via operate()
        SwapInParams memory swapParams = SwapInParams({
            dexKey: dexKey,
            swap0To1: swap0To1,
            amountIn: amountIn,
            amountOutMin: minAmountOut,
            controllerData: ""
        });
        
        bytes memory swapData = abi.encodeWithSelector(
            bytes4(keccak256("swapIn(SwapInParams)")),
            swapParams
        );
        
        bytes memory swapResult = dexV2.operate(dexType, 1, swapData); // 1 = swap module
        
        // Decode swap result to get actual amounts
        (uint256 amountOut, uint256 protocolFee, uint256 lpFee) = abi.decode(swapResult, (uint256, uint256, uint256));
        
        // Verify we got at least the minimum amount
        require(amountOut >= minAmountOut, "Insufficient output amount");
        
        // 2. Handle token settlement
        if (swap0To1) {
            // Supply token0, receive token1
            dexV2.settle(dexKey.token0, int256(amountIn), 0, 0, address(this), true);
            dexV2.settle(dexKey.token1, 0, 0, int256(amountOut), recipient, false);
        } else {
            // Supply token1, receive token0  
            dexV2.settle(dexKey.token1, int256(amountIn), 0, 0, address(this), true);
            dexV2.settle(dexKey.token0, 0, 0, int256(amountOut), recipient, false);
        }
        
        return swapResult;
    }
    
    // DEX calls this when isCallback = true in settle()
    function dexCallback(address token, address to, uint256 amount) external {
        require(msg.sender == address(dexV2), "Unauthorized");
        
        // Transfer tokens from the original caller to DEX
        // Note: tx.origin is the user who initiated the transaction
        // Note: You can also use transient storage to store the caller address to know whom to transfer tokens from
        IERC20(token).safeTransferFrom(tx.origin, to, amount);
    }
}

Operating DEX Modules ​

Encoding Swap Operations ​

To perform swaps, you call operate() with properly encoded data:

solidity
// For D3 and D4 swaps
uint256 constant SWAP_MODULE_ID = 1;

// Example: Encoding a swapIn operation
SwapInParams memory params = SwapInParams({
    dexKey: dexKey,
    swap0To1: true,
    amountIn: 1000e6,  // 1000 USDC
    amountOutMin: 990e6, // Minimum 990 USDT
    controllerData: ""
});

bytes memory operateData = abi.encodeWithSelector(
    bytes4(keccak256("swapIn(SwapInParams)")),
    params
);

// Execute the swap
dexV2.operate(3, 1, operateData); // dexType=3 (D3), moduleId=1 (swap)

DEX Types and Module IDs ​

solidity
// DEX Types
uint256 constant D3_DEX_TYPE = 3;  // Smart Collateral Range Orders
uint256 constant D4_DEX_TYPE = 4;  // Smart Debt Range Orders

// Module IDs
uint256 constant SWAP_MODULE_ID = 1;      // For swapIn/swapOut
uint256 constant USER_MODULE_ID = 2;      // For deposit/withdraw/borrow/payback
uint256 constant CONTROLLER_MODULE_ID = 3; // For controller operations

Token Settlement with settle() ​

The settle() function handles all token transfers. Understanding its parameters is crucial:

solidity
function settle(
    address token,
    int256 supplyAmount,    // Positive = supply TO the DEX
    int256 borrowAmount,    // Positive = borrow FROM the DEX
    int256 storeAmount,     // Positive = store IN the DEX
    address to,             // Where tokens go
    bool isCallback         // true = DEX calls dexCallback for transfer
) external payable;

Settlement Patterns ​

1. Basic Token Input (User → DEX)

solidity
// User supplies 1000 USDC to DEX
dexV2.settle(
    USDC_ADDRESS,
    1000e6,        // supplyAmount: 1000 USDC to DEX
    0,             // borrowAmount: not borrowing
    0,             // storeAmount: not storing
    address(this), // to: callback will transfer from this contract
    true           // isCallback: DEX will call dexCallback
);

2. Using Stored Balances (Advanced)

solidity
// First, store tokens in DEX for later use (saves gas in multi-step operations)
dexV2.settle(
    USDC_ADDRESS,
    0,             // supplyAmount: not supplying to pool
    0,             // borrowAmount: not borrowing
    1000e6,        // storeAmount: store 1000 USDC in DEX
    address(this), // to: callback source
    true           // isCallback: DEX calls back for transfer
);

// Later, use stored tokens for swap (no external transfer needed)
dexV2.settle(
    USDC_ADDRESS,
    1000e6,        // supplyAmount: use stored tokens for swap
    0,             // borrowAmount: not borrowing
    -1000e6,       // storeAmount: negative = withdraw from storage
    address(0),    // to: not needed (internal transfer)
    false          // isCallback: not needed (internal transfer)
);

Best Practices ​

  1. Implement proper slippage protection
  2. Validate callback caller authenticity
  3. Use router based integration for simplicity
  4. Use callback based integration for gas efficiency
  5. Batch multiple operations in startOperationCallback and settle at once at the end

Resources ​

Documentation ​

Contract Addresses ​

NetworkContractAddress
EthereumFluidDexV2TBD
EthereumFluidDexV2D3D4ResolverTBD
EthereumFluidDexV2RouterTBD
PolygonFluidDexV20x04e9FF525b6541Ca4a0Dca3326b79547f9057E15
PolygonFluidDexV2Resolver0x2Ba521a909BDBE56183e3cd5F27962466e674610
PolygonFluidDexV2Router0xe16aD4692B2505d21959f22Ff32D18d808D35759