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: FluidDexV2Resolver - Provides pool discovery, 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: FluidDexV2Resolver

  • 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
EthereumFluidDexV2ResolverTBD
EthereumFluidDexV2RouterTBD
PolygonFluidDexV20x7822B3944B1a68B231a6e7F55B57967F28BB369e
PolygonFluidDexV2Resolver0x1E45589D501AcED82013c2838552122f943B33Ac
PolygonFluidDexV2Router0x713fD04a47Db41AB5684AEC2A2063d5278A53616

Data Access with Resolver ​

Using the Resolver Contract ​

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

Resolver Data Structures ​

solidity
// Pool configuration with metadata
struct DexConfig {
    uint256 dexType;        // 3 for D3, 4 for D4
    DexKey dexKey;          // Pool identification
    bytes32 dexId;          // Computed pool ID
    bytes32 dexAssetId;     // Asset ID for money market integration
}

// Combined pool configuration and state
struct DexPoolInfo {
    DexConfig dexConfig;
    DexPoolState dexPoolState;
}

// 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 poolAccountingFlag;        // Per pool accounting flag
    bool fetchDynamicFeeFlag;
    uint256 feeVersion;            // 0 = static fee, 1 = dynamic fee
    uint256 lpFee;
    uint256 maxDecayTime;
    uint256 priceImpactToFeeDivisionFactor;
    uint256 minFee;
    uint256 maxFee;
    int256 netPriceImpact;
    uint256 lastUpdateTimestamp;
    uint256 decayTimeRemaining;
}

// Raw pool state data
struct DexPoolStateRaw {
    uint256 dexVariablesPacked;
    uint256 dexVariables2Packed;
    DexVariables dexVariablesUnpacked;
    DexVariables2 dexVariables2Unpacked;
}

// Complete pool state (returned by resolver)
struct DexPoolState {
    bytes32 dexId;
    uint256 dexPriceParsed;
    DexPoolStateRaw dexPoolStateRaw;
}

// 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 FluidDexV2Resolver {
    // Pool Discovery & Identification
    function getDexId(DexKey memory dexKey) public pure returns (bytes32 dexId);
    function getDexKey(uint256 dexType, bytes32 dexId) public view returns (DexKey memory dexKey);
    
    // Permissioned Pool Discovery (Money Market Listed Pools)
    function getD3PermissionedDexes() public view returns (DexKey[] memory dexKeys);
    function getD4PermissionedDexes() public view returns (DexKey[] memory dexKeys);
    function getAllPermissionedDexes() public view returns (DexConfig[] memory dexConfigs);
    function getAllPermissionedDexesPoolState() public view returns (DexPoolInfo[] memory dexPoolInfos);
    
    // Pool State Reading
    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;

contract FluidDexV2Integrator {
    FluidDexV2Resolver constant resolver = FluidDexV2Resolver(
        0x1E45589D501AcED82013c2838552122f943B33Ac // Polygon resolver address
    );
    
    IFluidDexV2 constant dexV2 = IFluidDexV2(
        0x7822B3944B1a68B231a6e7F55B57967F28BB369e // Polygon DEX v2 address
    );
    
    uint256 constant D3_DEX_TYPE = 3; // Smart Collateral Range Orders
    uint256 constant D4_DEX_TYPE = 4; // Smart Debt Range Orders
    
    // Get all available pools from Money Market
    function getAllAvailablePools() external view returns (DexPoolInfo[] memory) {
        return resolver.getAllPermissionedDexesPoolState();
    }
    
    // Get specific pool state
    function getPoolState(DexKey memory dexKey, uint256 dexType) external view returns (
        bytes32 dexId,
        int256 currentTick,
        uint256 currentPrice,
        uint256 activeLiquidity,
        uint256 fee
    ) {
        DexPoolState memory state = resolver.getDexPoolState(dexType, dexKey);
        
        dexId = state.dexId;
        currentTick = state.dexPoolStateRaw.dexVariablesUnpacked.currentTick;
        currentPrice = state.dexPoolStateRaw.dexVariablesUnpacked.currentSqrtPriceX96;
        activeLiquidity = state.dexPoolStateRaw.dexVariables2Unpacked.activeLiquidity;
        fee = state.dexPoolStateRaw.dexVariables2Unpacked.lpFee;
    }
    
    // Reverse lookup: get DexKey from dexId
    function getDexKeyFromId(uint256 dexType, bytes32 dexId) external view returns (DexKey memory) {
        return resolver.getDexKey(dexType, dexId);
    }
}

Pool Discovery & State Reading ​

Discovering Available Pools ​

DEX v2 pools can be discovered through multiple methods depending on the current system state:

1. Permissioned Pools (Currently Available) ​

Money Market Listed Pools: Currently, DEX v2 operates with pools that are permissioned and listed on the Fluid Money Market. These pools have been approved through governance and are integrated with the lending protocol for enhanced capital efficiency.

Key Functions for Pool Discovery:

solidity
// Get all D3 (Smart Collateral) pools from Money Market
DexKey[] memory d3Pools = resolver.getD3PermissionedDexes();

// Get all D4 (Smart Debt) pools from Money Market  
DexKey[] memory d4Pools = resolver.getD4PermissionedDexes();

// Get all pools with complete metadata and current state
DexPoolInfo[] memory allPools = resolver.getAllPermissionedDexesPoolState();

// Get pool configurations with dexType, dexKey, dexId, and dexAssetId
DexConfig[] memory allConfigs = resolver.getAllPermissionedDexes();

Important Notes:

  • These functions return only pools currently listed on the Money Market
  • Each pool has been approved through Fluid governance
  • Pools are integrated with lending protocols for Smart Collateral/Debt functionality
  • Use getAllPermissionedDexesPoolState() for the most comprehensive pool information in a single call

2. Future Permissionless Pools ​

Event Monitoring: When DEX v2 transitions to permissionless operation, pools that are not listed on the Money Market will need to be discovered via initialization events:

solidity
// Listen for this event to discover new permissionless pools
event LogInitialize(
    uint256 indexed dexType,
    bytes32 indexed dexId,
    DexKey dexKey,
    uint256 sqrtPriceX96
);

3. Pool Identification & Reverse Lookup ​

DexKey ↔ DexId Conversion: The resolver provides bidirectional conversion between DexKey and DexId:

solidity
// Convert DexKey to DexId (pure function - no storage read)
bytes32 dexId = resolver.getDexId(dexKey);

// Reverse lookup: Convert DexId back to DexKey (reads from storage)
DexKey memory dexKey = resolver.getDexKey(dexType, dexId);

Pool Existence Verification:

solidity
function checkPoolExists(DexKey memory dexKey, uint256 dexType) external view returns (bool exists) {
    DexPoolState memory state = resolver.getDexPoolState(dexType, dexKey);
    
    // Pool exists if it has been initialized (non-zero sqrt price)
    exists = state.dexPoolStateRaw.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.dexPoolStateRaw.dexVariablesUnpacked.currentTick;
    sqrtPriceX96 = state.dexPoolStateRaw.dexVariablesUnpacked.currentSqrtPriceX96;
    activeLiquidity = state.dexPoolStateRaw.dexVariables2Unpacked.activeLiquidity;
    fee = state.dexPoolStateRaw.dexVariables2Unpacked.lpFee;
    token0Decimals = state.dexPoolStateRaw.dexVariables2Unpacked.token0Decimals;
    token1Decimals = state.dexPoolStateRaw.dexVariables2Unpacked.token1Decimals;
    isDynamicFee = state.dexPoolStateRaw.dexVariables2Unpacked.feeVersion == 1;
}

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.dexPoolStateRaw.dexVariablesUnpacked.currentTick;
    uint256 currentLiquidity = state.dexPoolStateRaw.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/dexV2/router/main.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract MyDEXAggregator {
    FluidDexV2Router constant router = FluidDexV2Router(0x713fD04a47Db41AB5684AEC2A2063d5278A53616); // Polygon 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
EthereumFluidDexV2ResolverTBD
EthereumFluidDexV2RouterTBD
PolygonFluidDexV20x7822B3944B1a68B231a6e7F55B57967F28BB369e
PolygonFluidDexV2Resolver0x1E45589D501AcED82013c2838552122f943B33Ac
PolygonFluidDexV2Router0x713fD04a47Db41AB5684AEC2A2063d5278A53616