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 ​
- Overview
- Quick Start
- Data Access with Resolver
- Pool Discovery & State Reading
- Router-based Integration
- Callback-based Integration (Advanced)
- Operating DEX Modules
- Token Settlement with settle()
- Best Practices
- 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:
- DEX Type 1 (D1): DEX v1 Smart Collateral (Coming Soon)
- DEX Type 2 (D2): DEX v1 Smart Debt (Coming Soon)
- DEX Type 3 (D3): Smart Collateral Range Orders
- 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
FluidDexV2Routercontract 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:
// 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 ​
| Network | Contract | Address |
|---|---|---|
| Ethereum | FluidDexV2 | TBD |
| Ethereum | FluidDexV2Resolver | TBD |
| Ethereum | FluidDexV2Router | TBD |
| Polygon | FluidDexV2 | 0x7822B3944B1a68B231a6e7F55B57967F28BB369e |
| Polygon | FluidDexV2Resolver | 0x1E45589D501AcED82013c2838552122f943B33Ac |
| Polygon | FluidDexV2Router | 0x713fD04a47Db41AB5684AEC2A2063d5278A53616 |
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 ​
// 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 ​
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 ​
// 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:
// 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:
// 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:
// 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:
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 ​
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 ​
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
0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeEto 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 ​
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:
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:
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
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:
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 → CompleteStep by Step:
- You initiate: Call
dexV2.startOperation(encodedData)with your operation data - DEX calls back: DEX immediately calls
startOperationCallback(encodedData)on your contract - You execute: Inside the callback, perform swaps using
operate()and handle tokens usingsettle() - DEX verifies: DEX checks that all token transfers balance out correctly
- 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 ​
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:
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:
// 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 ​
// 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 operationsToken Settlement with settle() ​
The settle() function handles all token transfers. Understanding its parameters is crucial:
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)
// 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)
// 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 ​
- Implement proper slippage protection
- Validate callback caller authenticity
- Use router based integration for simplicity
- Use callback based integration for gas efficiency
- Batch multiple operations in startOperationCallback and settle at once at the end
Resources ​
Documentation ​
Contract Addresses ​
| Network | Contract | Address |
|---|---|---|
| Ethereum | FluidDexV2 | TBD |
| Ethereum | FluidDexV2Resolver | TBD |
| Ethereum | FluidDexV2Router | TBD |
| Polygon | FluidDexV2 | 0x7822B3944B1a68B231a6e7F55B57967F28BB369e |
| Polygon | FluidDexV2Resolver | 0x1E45589D501AcED82013c2838552122f943B33Ac |
| Polygon | FluidDexV2Router | 0x713fD04a47Db41AB5684AEC2A2063d5278A53616 |

