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:
- Ultra-Low Gas Costs: Most gas-efficient DEX on Ethereum (~10k gas per swap)
- Correlated Pair Specialization: Optimized for highly liquid correlated pairs (USDC-USDT, ETH-wstETH)
- Singleton Architecture: Multiple pools under one contract for efficient multi-hop routing
- Easy Math Replication: Off-chain simulation via TypeScript, JavaScript, and Golang libraries
- Credit-Enhanced Liquidity: Access deeper liquidity through Fluid's credit system
- 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:
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:
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:
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:
// 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:
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:
// 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:
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:
- TypeScript: ParaSwap Implementation
- Golang: KyberSwap Implementation
Gas Optimization with Callbacks ​
Callback Interface ​
Implement the IDexLiteCallback
interface to optimize gas usage:
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:
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:
// 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:
// 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 ​
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 adapterfluid-dex-lite-math.ts
- Off-chain swap calculationsfluid-dex-lite-pool.ts
- Pool state management
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 simulationpool_tracker.go
- Pool state trackingpool_list_updater.go
- Pool discovery and management
Best Practices ​
Gas Optimization ​
- Use Callbacks: Implement
IDexLiteCallback
for ~5k gas savings per swap - Batch Operations: Use
swapHop
for multi-hop swaps instead of separate transactions - Estimate Off-Chain: Use math libraries instead of on-chain estimation when possible
Deployment Addresses ​
Network | Contract Address |
---|---|
Ethereum Mainnet | 0xBbcb91440523216e2b87052A99F69c604A7b6e00 |
Support and Resources ​
- General Fluid Docs: https://docs.fluid.instadapp.io/
- DEX Blog Post: https://gov.fluid.io/t/introducing-fluid-dex-lite/1665
- Math Derivations: Excalidraw Diagrams