Skip to content

Fluid DEX Lite: Liquidity Source Integration ​

Comprehensive guide for integrating Fluid DEX Lite as a liquidity source in DEX aggregators and routing protocols.

Integration Benefits ​

Adding Fluid DEX Lite as a liquidity source provides:

  1. Ultra-Low Gas Costs: Most gas-efficient DEX on Ethereum (~10k gas per swap)
  2. Correlated Pair Specialization: Optimized for highly liquid correlated pairs (USDC-USDT, ETH-wstETH)
  3. Singleton Architecture: Multiple pools under one contract for efficient multi-hop routing
  4. Easy Math Replication: Off-chain simulation via TypeScript, JavaScript, and Golang libraries
  5. Credit-Enhanced Liquidity: Access deeper liquidity through Fluid's credit system
  6. Multi-hop Support: Native support for complex routing paths through multiple pools

Architecture Overview ​

Fluid DEX Lite is an ultra-gas-optimized version of Fluid DEX v1, featuring:

  • Singleton contract hosting multiple correlated-asset pools
  • Packed storage for minimal gas consumption
  • Governance-controlled pool launches
  • Built-in multi-hop routing capabilities
  • Integrated callback system for gas optimization

Contract Address: 0xBbcb91440523216e2b87052A99F69c604A7b6e00 (Ethereum Mainnet)


Core Data Structures ​

DexKey Struct ​

Uniquely identifies a pool within the DEX Lite contract:

solidity
struct DexKey {
    address token0;    // First token address (lexicographically smaller)
    address token1;    // Second token address (lexicographically larger) 
    bytes32 salt;      // Unique identifier for pool variations
}

TransferParams Struct ​

Controls token transfers and callback behavior:

solidity
struct TransferParams {
    address to;            // Recipient address
    bool isCallback;       // Whether to use callback optimization
    bytes callbackData;    // Data passed to callback function
    bytes extraData;       // Additional data for special operations
}

Integration Methods ​

1. Single Pool Swaps (swapSingle) ​

Execute swaps through a single pool with maximum flexibility:

solidity
function swapSingle(
    DexKey calldata dexKey_,           // Pool identifier
    bool swap0To1_,                    // Direction: token0→token1 (true) or token1→token0 (false)
    int256 amountSpecified_,           // Swap amount (positive=exact input, negative=exact output)
    uint256 amountLimit_,              // Slippage protection (min output or max input)
    address to_,                       // Recipient address
    bool isCallback_,                  // Enable callback optimization
    bytes calldata callbackData_,      // Data for callback function
    bytes calldata extraData_          // Special operation data
) external payable returns (uint256 amountUnspecified_)

Key Parameters:

  • amountSpecified_:
    • Positive = Exact input swap (user provides exact input amount)
    • Negative = Exact output swap (user receives exact output amount)
  • amountLimit_:
    • For exact input: minimum output amount (slippage protection)
    • For exact output: maximum input amount (slippage protection)
  • swap0To1_: Swap direction based on token ordering in DexKey

Example Usage:

solidity
// Swap exactly 1000 USDC for USDT with 0.1% slippage tolerance
DexKey memory key = DexKey({
    token0: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, // USDC
    token1: 0xdAC17F958D2ee523a2206206994597C13D831ec7, // USDT
    salt: 0x0000000000000000000000000000000000000000000000000000000000000000
});

uint256 outputAmount = fluidDexLite.swapSingle(
    key,
    true,              // USDC → USDT
    1000 * 1e6,        // 1000 USDC input
    999 * 1e6,         // Min 999 USDT output (0.1% slippage)
    msg.sender,        // Recipient
    false,             // No callback
    "",                // No callback data
    ""                 // No extra data
);

2. Multi-Hop Swaps (swapHop) ​

Execute complex routing through multiple pools in a single transaction:

solidity
function swapHop(
    address[] calldata path_,              // Token path for the swap
    DexKey[] calldata dexKeys_,            // Pool identifiers for each hop
    int256 amountSpecified_,               // Swap amount (positive=exact input, negative=exact output)
    uint256[] calldata amountLimits_,      // Slippage protection for each hop
    TransferParams calldata transferParams_ // Transfer and callback configuration
) external payable returns (uint256 amountUnspecified_)

Key Requirements:

  • path_.length = dexKeys_.length + 1 (tokens = pools + 1)
  • amountLimits_.length = dexKeys_.length (one limit per hop)
  • Token order in path must match DexKey token order

Example Usage:

solidity
// Swap USDC → USDT → DAI through two pools
address[] memory path = new address[](3);
path[0] = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; // USDC
path[1] = 0xdAC17F958D2ee523a2206206994597C13D831ec7; // USDT  
path[2] = 0x6B175474E89094C44Da98b954EedeAC495271d0F; // DAI

DexKey[] memory dexKeys = new DexKey[](2);
dexKeys[0] = DexKey({
    token0: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, // USDC-USDT pool
    token1: 0xdAC17F958D2ee523a2206206994597C13D831ec7,
    salt: 0x0000000000000000000000000000000000000000000000000000000000000000
});
dexKeys[1] = DexKey({
    token0: 0x6B175474E89094C44Da98b954EedeAC495271d0F, // USDT-DAI pool
    token1: 0xdAC17F958D2ee523a2206206994597C13D831ec7,
    salt: 0x0000000000000000000000000000000000000000000000000000000000000000
});

uint256[] memory limits = new uint256[](2);
limits[0] = 999 * 1e6;   // Min USDT from first hop
limits[1] = 998 * 1e18;  // Min DAI from second hop

TransferParams memory transferParams = TransferParams({
    to: msg.sender,
    isCallback: false,
    callbackData: "",
    extraData: ""
});

uint256 finalAmount = fluidDexLite.swapHop(
    path,
    dexKeys,
    1000 * 1e6,    // 1000 USDC input
    limits,
    transferParams
);

Price Estimation ​

On-Chain Estimation ​

Use the ESTIMATE_SWAP pattern to get swap quotes without executing trades:

solidity
bytes32 constant ESTIMATE_SWAP = keccak256(bytes("ESTIMATE_SWAP"));

// For single pool swap estimation
try fluidDexLite.swapSingle(
    dexKey_,
    swap0To1_,
    amountSpecified_ > 0 ? 0 : type(uint256).max,
    0,              // No slippage limit for estimation
    address(0),     // No recipient needed
    false,          // No callback
    "",             // No callback data
    abi.encode(ESTIMATE_SWAP) 
) {
    // Should not reach here
    revert("Estimation Failed");
} catch (bytes memory reason) {
   // Check if this is the EstimateSwap error
    if (reason.length >= 36) {
        bytes4 errorSelector = bytes4(reason);
        // EstimateSwap error selector should match
        if (errorSelector == bytes4(keccak256("EstimateSwap(uint256)"))) {
            // Skip the 4-byte selector and decode the uint256 parameter
            assembly {
                amountUnspecified_ := mload(add(reason, 36))
            }
        } else {
            revert("Estimation Failed - Wrong Error");
        }
    } else {
        revert("Estimation Failed - Invalid Reason");
    }
}

// For multi pool swap estimation
try
    fluidDexLite.swapHop(
        path_,
        dexKeys_,
        amountSpecified_,
        amountLimits_,
        TransferParams(address(0), false, "", ESTIMATE_SWAP)
    )
{
    // Should not reach here
    revert("Estimation Failed");
} catch (bytes memory reason) {
    // Check if this is the EstimateSwap error
    if (reason.length >= 36) {
        bytes4 errorSelector = bytes4(reason);
        // EstimateSwap error selector should match
        if (errorSelector == bytes4(keccak256("EstimateSwap(uint256)"))) {
            // Skip the 4-byte selector and decode the uint256 parameter
            assembly {
                amountUnspecified_ := mload(add(reason, 36))
            }
        } else {
            revert("Estimation Failed - Wrong Error");
        }
    } else {
        revert("Estimation Failed - Invalid Reason");
    }
}

Off-Chain Calculation ​

Replicate swap math using the provided libraries for better performance:


Gas Optimization with Callbacks ​

Callback Interface ​

Implement the IDexLiteCallback interface to optimize gas usage:

solidity
interface IDexLiteCallback {
    function dexCallback(address token_, uint256 amount_, bytes calldata callbackData_) external;
}

Callback Implementation Pattern ​

The callback mechanism allows DEX Lite to request tokens after calculating exact swap amounts:

solidity
contract DexLiteIntegration is IDexLiteCallback {
    using SafeERC20 for IERC20;
    
    address private constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    address private constant FLUID_DEX_LITE = 0xBbcb91440523216e2b87052A99F69c604A7b6e00;
    
    function executeSwapWithCallback(
        DexKey memory dexKey,
        bool swap0To1,
        uint256 amountIn,
        uint256 minAmountOut,
        address user
    ) external {
        // Execute swap with callback enabled
        uint256 amountOut = FluidDexLite(FLUID_DEX_LITE).swapSingle(
            dexKey,
            swap0To1,
            int256(amountIn),
            minAmountOut,
            user,
            true, // Enable callback
            "",
            ""
        );
    }
    
    function dexCallback(address token_, uint256 amount_, bytes calldata data_) external override {
        require(msg.sender == FLUID_DEX_LITE, "Unauthorized callback");
        
        if (token_ == ETH_ADDRESS) {
            // For ETH swaps, send ETH to the DEX contract
            (bool success, ) = FLUID_DEX_LITE.call{value: amount_}("");
            require(success, "ETH transfer failed");
        } else {
            // For ERC20 swaps, transfer tokens from user to DEX contract
            IERC20(token_).safeTransfer(FLUID_DEX_LITE, amount_);
        }
    }
    
    receive() external payable {}
}

State Reading and Pool Discovery ​

Pool Discovery ​

Read all available pools from the contract's storage:

solidity
// Storage slot for _dexesList array length
uint256 constant DEXES_LIST_SLOT = 1;

// Get number of pools
uint256 poolCount = uint256(fluidDexLite.readFromStorage(bytes32(DEXES_LIST_SLOT)));


// Read DexKey for pool at index
function readDexKeyAtIndex(uint256 index) view returns (DexKey memory) {
    bytes32 baseSlot = keccak256(abi.encode(DEXES_LIST_SLOT));
    
    // Each DexKey takes 3 storage slots (token0, token1, salt)
    address token0 = address(uint160(uint256(fluidDexLite.readFromStorage(bytes32(uint256(baseSlot) + index * 3)))));
    address token1 = address(uint160(uint256(fluidDexLite.readFromStorage(bytes32(uint256(baseSlot) + index * 3 + 1)))));
    bytes32 salt = fluidDexLite.readFromStorage(bytes32(uint256(baseSlot) + index * 3 + 2));
    
    return DexKey(token0, token1, salt);
}

Pool State Reading ​

Access pool variables using the DexId mapping:

solidity
// Calculate DexId from DexKey
function calculateDexId(DexKey memory dexKey_) public pure returns (bytes8) {
    return bytes8(keccak256(abi.encode(dexKey_)));
}

// Calculate storage slot for pool variables
function calculatePoolStateSlot(bytes8 dexId_, uint256 baseSlot_) public pure returns (bytes32) {
    return keccak256(abi.encode(bytes32(dexId_), baseSlot_));
}

// Storage slot constants
uint256 constant DEX_VARIABLES_SLOT = 2;
uint256 constant CENTER_PRICE_SHIFT_SLOT = 3;
uint256 constant RANGE_SHIFT_SLOT = 4;
uint256 constant THRESHOLD_SHIFT_SLOT = 5;

// Read pool state
function readPoolState(DexKey memory dexKey_) public view returns (uint256 dexVariables_, uint256 centerPriceShift_, uint256 rangeShift_, uint256 thresholdShift_) {
    bytes8 dexId_ = calculateDexId_(dexKey_);
    
    dexVariables_ = uint256(fluidDexLite.readFromStorage(calculatePoolStateSlot(dexId_, DEX_VARIABLES_SLOT)));
    centerPriceShift_ = uint256(fluidDexLite.readFromStorage(calculatePoolStateSlot(dexId_, CENTER_PRICE_SHIFT_SLOT)));
    rangeShift_ = uint256(fluidDexLite.readFromStorage(calculatePoolStateSlot(dexId_, RANGE_SHIFT_SLOT)));
    thresholdShift_ = uint256(fluidDexLite.readFromStorage(calculatePoolStateSlot(dexId_, THRESHOLD_SHIFT_SLOT)));
}

Integration References ​

Production Implementations ​

  1. ParaSwap Integration (TypeScript)

    • Repository: VeloraDEX/paraswap-dex-lib PR #1055
    • Features: Complete DEX integration with pool discovery, price calculation, and swap execution
    • Language: TypeScript
    • Key Files:
      • fluid-dex-lite.ts - Main DEX adapter
      • fluid-dex-lite-math.ts - Off-chain swap calculations
      • fluid-dex-lite-pool.ts - Pool state management
  2. KyberSwap Integration (Golang)

    • Repository: KyberNetwork/kyberswap-dex-lib
    • Features: High-performance integration with comprehensive testing
    • Language: Golang
    • Key Files:
      • pool_simulator.go - Swap calculations and simulation
      • pool_tracker.go - Pool state tracking
      • pool_list_updater.go - Pool discovery and management

Best Practices ​

Gas Optimization ​

  1. Use Callbacks: Implement IDexLiteCallback for ~5k gas savings per swap
  2. Batch Operations: Use swapHop for multi-hop swaps instead of separate transactions
  3. Estimate Off-Chain: Use math libraries instead of on-chain estimation when possible

Deployment Addresses ​

NetworkContract Address
Ethereum Mainnet0xBbcb91440523216e2b87052A99F69c604A7b6e00

Support and Resources ​