Fluid DEX Lite: Liquidity Source Integration ​
Complete integration guide for adding Fluid DEX Lite as a liquidity source in DEX aggregators and routing protocols.
Table of Contents ​
- Overview
- Quick Start
- Integration Methods
- Pool Discovery & State Reading
- Price Estimation
- Swap Execution
- Gas Optimization
- Legacy Integration
- Best Practices
- Resources
Overview ​
What is Fluid DEX Lite? ​
Fluid DEX Lite is an ultra-gas-optimized DEX specialized for correlated asset pairs, featuring:
- Ultra-Low Gas: ~10k gas per swap (most efficient on Ethereum)
- Singleton Architecture: Multiple pools under one contract for efficient multi-hop routing
- Easy Integration: Comprehensive resolver contract with clean APIs
- Multi-hop Support: Native support for complex routing paths
Architecture ​
- Core Contract:
FluidDexLite
- Handles all swaps and pool state - Resolver Contract:
FluidDexLiteResolver
- Provides discovery, state reading, and estimation APIs - Storage: Packed storage design for gas optimization
- Pools: Governance-controlled pool launches for correlated pairs
Quick Start ​
1. Integration Method ​
Standard: Resolver-based Integration
- Use
FluidDexLiteResolver
for discovery, state reading, and estimation - Clean APIs, no storage manipulation required
- Gas-free price estimation
- This is the recommended approach for all new integrations
Note: This guide also includes a legacy section for integrators who have already implemented direct contract integration.
2. Core Data Structures ​
struct DexKey {
address token0; // Lexicographically smaller token
address token1; // Lexicographically larger token
bytes32 salt; // Pool salt
}
struct DexState {
DexVariables dexVariables;
CenterPriceShift centerPriceShift;
RangeShift rangeShift;
ThresholdShift thresholdShift;
}
struct DexVariables {
uint256 fee;
uint256 revenueCut;
uint256 rebalancingStatus;
bool isCenterPriceShiftActive;
uint256 centerPrice;
address centerPriceAddress;
bool isRangePercentShiftActive;
uint256 upperRangePercent;
uint256 lowerRangePercent;
bool isThresholdPercentShiftActive;
uint256 upperShiftThresholdPercent;
uint256 lowerShiftThresholdPercent;
uint256 token0Decimals;
uint256 token1Decimals;
uint256 totalToken0AdjustedAmount;
uint256 totalToken1AdjustedAmount;
}
struct CenterPriceShift {
uint256 lastInteractionTimestamp;
uint256 rebalancingShiftingTime;
uint256 maxCenterPrice;
uint256 minCenterPrice;
uint256 shiftPercentage;
uint256 centerPriceShiftingTime;
uint256 startTimestamp;
}
struct RangeShift {
uint256 oldUpperRangePercent;
uint256 oldLowerRangePercent;
uint256 shiftingTime;
uint256 startTimestamp;
}
struct ThresholdShift {
uint256 oldUpperThresholdPercent;
uint256 oldLowerThresholdPercent;
uint256 shiftingTime;
uint256 startTimestamp;
}
struct Prices {
uint256 poolPrice;
uint256 centerPrice;
uint256 upperRangePrice;
uint256 lowerRangePrice;
uint256 upperThresholdPrice;
uint256 lowerThresholdPrice;
}
struct Reserves {
uint256 token0RealReserves;
uint256 token1RealReserves;
uint256 token0ImaginaryReserves;
uint256 token1ImaginaryReserves;
}
3. Contract Addresses ​
Network | Contract | Address |
---|---|---|
Ethereum Mainnet | FluidDexLite | 0xBbcb91440523216e2b87052A99F69c604A7b6e00 |
Ethereum Mainnet | FluidDexLiteResolver (v1.0.0) | 0x26b696D0dfDAB6c894Aa9a6575fCD07BB25BbD2C |
Integration Methods ​
Resolver-based Integration (Recommended) ​
Use the resolver contract for all discovery, state reading, and estimation operations.
Key Methods ​
contract FluidDexLiteResolver {
// Pool Discovery
function getAllDexes() public view returns (DexKey[] memory);
// State Reading
function getDexState(DexKey memory dexKey) public view returns (DexState memory);
function getPricesAndReserves(DexKey memory dexKey) public returns (Prices memory, Reserves memory);
function getDexEntireData(DexKey memory dexKey) public returns (DexEntireData memory);
function getAllDexesEntireData() public returns (DexEntireData[] memory);
// Price Estimation (Gas-free)
function estimateSwapSingle(DexKey calldata dexKey, bool swap0To1, int256 amountSpecified) public returns (uint256);
function estimateSwapHop(address[] calldata path, DexKey[] calldata dexKeys, int256 amountSpecified) public returns (uint256);
}
Basic Integration Example ​
// SPDX-License-Identifier: MIT
pragma solidity 0.8.29;
import "contracts/periphery/resolvers/dexLite/main.sol";
contract FluidDexLiteIntegrator {
FluidDexLiteResolver constant resolver = FluidDexLiteResolver(
0x26b696D0dfDAB6c894Aa9a6575fCD07BB25BbD2C
);
function getPoolData() external returns (
uint256 poolCount,
bytes8 firstDexId,
uint256 poolPrice,
uint256 token0Reserves,
uint256 token1Reserves
) {
// Discover all pools
DexKey[] memory dexes = resolver.getAllDexes();
poolCount = dexes.length;
if (poolCount > 0) {
// Get complete data for first pool
DexEntireData memory data = resolver.getDexEntireData(dexes[0]);
firstDexId = data.dexId;
poolPrice = data.prices.poolPrice;
token0Reserves = data.reserves.token0RealReserves;
token1Reserves = data.reserves.token1RealReserves;
}
}
function estimateSwap(
DexKey memory dexKey,
bool swap0To1,
uint256 amountIn
) external returns (uint256 amountOut) {
return resolver.estimateSwapSingle(dexKey, swap0To1, int256(amountIn));
}
}
Pool Discovery & State Reading ​
Discovering Available Pools ​
function discoverPools() external view returns (DexKey[] memory pools) {
pools = resolver.getAllDexes();
// Each pool is identified by DexKey with:
// - token0: lexicographically smaller token address
// - token1: lexicographically larger token address
// - salt: unique identifier (typically 0x00...)
}
Reading Pool State ​
function readPoolDetails(DexKey memory dexKey) external returns (
uint256 fee,
uint256 poolPrice,
uint256 token0Decimals,
uint256 token1Decimals,
uint256 realReserves0,
uint256 realReserves1
) {
// Get complete pool data
DexEntireData memory data = resolver.getDexEntireData(dexKey);
fee = data.dexState.dexVariables.fee;
poolPrice = data.prices.poolPrice;
token0Decimals = data.dexState.dexVariables.token0Decimals;
token1Decimals = data.dexState.dexVariables.token1Decimals;
realReserves0 = data.reserves.token0RealReserves;
realReserves1 = data.reserves.token1RealReserves;
}
Batch Reading All Pools ​
function readAllPoolsData() external returns (DexEntireData[] memory) {
// Get complete data for all pools in one call
return resolver.getAllDexesEntireData();
}
Price Estimation ​
Single Pool Estimation ​
function estimateSingleSwap(
DexKey memory dexKey,
bool swap0To1,
uint256 amountIn
) external returns (uint256 amountOut) {
// Gas-free estimation using resolver
amountOut = resolver.estimateSwapSingle(
dexKey,
swap0To1,
int256(amountIn) // Positive for exact input
);
}
function estimateExactOutput(
DexKey memory dexKey,
bool swap0To1,
uint256 amountOut
) external returns (uint256 amountIn) {
// Estimate required input for exact output
amountIn = resolver.estimateSwapSingle(
dexKey,
swap0To1,
-int256(amountOut) // Negative for exact output
);
}
Multi-hop Estimation ​
function estimateMultiHop(
address[] memory path,
DexKey[] memory dexKeys,
uint256 amountIn
) external returns (uint256 amountOut) {
// Gas-free multi-hop estimation
amountOut = resolver.estimateSwapHop(path, dexKeys, int256(amountIn));
}
Example: USDC → USDT → DAI ​
function estimateUSDCToDAI(uint256 usdcAmount) external returns (uint256 daiAmount) {
// Define path: USDC → USDT → DAI
address[] memory path = new address[](3);
path[0] = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; // USDC
path[1] = 0xdAC17F958D2ee523a2206206994597C13D831ec7; // USDT
path[2] = 0x6B175474E89094C44Da98b954EedeAC495271d0F; // DAI
// Define pools for each hop
DexKey[] memory dexKeys = new DexKey[](2);
dexKeys[0] = DexKey({
token0: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, // USDC-USDT
token1: 0xdAC17F958D2ee523a2206206994597C13D831ec7,
salt: 0x0000000000000000000000000000000000000000000000000000000000000000
});
dexKeys[1] = DexKey({
token0: 0x6B175474E89094C44Da98b954EedeAC495271d0F, // USDT-DAI
token1: 0xdAC17F958D2ee523a2206206994597C13D831ec7,
salt: 0x0000000000000000000000000000000000000000000000000000000000000000
});
daiAmount = resolver.estimateSwapHop(path, dexKeys, int256(usdcAmount));
}
Swap Execution ​
After estimation, execute swaps through the core FluidDexLite contract.
Single Pool Swap ​
interface IFluidDexLite {
function swapSingle(
DexKey calldata dexKey,
bool swap0To1,
int256 amountSpecified,
uint256 amountLimit,
address to,
bool isCallback,
bytes calldata callbackData,
bytes calldata extraData
) external payable returns (uint256 amountUnspecified);
}
function executeSwap(
DexKey memory dexKey,
bool swap0To1,
uint256 amountIn,
uint256 minAmountOut,
address recipient
) external {
IFluidDexLite dexLite = IFluidDexLite(0xBbcb91440523216e2b87052A99F69c604A7b6e00);
// Execute swap
uint256 amountOut = dexLite.swapSingle(
dexKey,
swap0To1,
int256(amountIn), // Exact input amount
minAmountOut, // Minimum output (slippage protection)
recipient, // Token recipient
false, // No callback
"", // No callback data
"" // No extra data
);
}
Multi-hop Swap ​
struct TransferParams {
address to;
bool isCallback;
bytes callbackData;
bytes extraData;
}
function executeMultiHop(
address[] memory path,
DexKey[] memory dexKeys,
uint256 amountIn,
uint256 minAmountOut,
address recipient
) external {
IFluidDexLite dexLite = IFluidDexLite(0xBbcb91440523216e2b87052A99F69c604A7b6e00);
// Set amount limits for each hop (0 = no limit except final)
uint256[] memory amountLimits = new uint256[](dexKeys.length);
amountLimits[amountLimits.length - 1] = minAmountOut; // Final output limit
TransferParams memory transferParams = TransferParams({
to: recipient,
isCallback: false,
callbackData: "",
extraData: ""
});
uint256 finalAmount = dexLite.swapHop(
path,
dexKeys,
int256(amountIn),
amountLimits,
transferParams
);
}
Gas Optimization ​
Callback Pattern ​
Implement callbacks to save ~5k gas per swap by optimizing token transfers.
interface IDexLiteCallback {
function dexCallback(address token, uint256 amount, bytes calldata data) external;
}
contract OptimizedIntegrator is IDexLiteCallback {
address constant FLUID_DEX_LITE = 0xBbcb91440523216e2b87052A99F69c604A7b6e00;
function executeOptimizedSwap(
DexKey memory dexKey,
bool swap0To1,
uint256 amountIn,
uint256 minAmountOut
) external {
IFluidDexLite(FLUID_DEX_LITE).swapSingle(
dexKey,
swap0To1,
int256(amountIn),
minAmountOut,
msg.sender,
true, // Enable callback
"",
""
);
}
function dexCallback(address token, uint256 amount, bytes calldata) external override {
require(msg.sender == FLUID_DEX_LITE, "Unauthorized");
// Transfer tokens to DEX contract
IERC20(token).transferFrom(tx.origin, FLUID_DEX_LITE, amount);
}
}
Multi-hop Optimization ​
Use swapHop
instead of multiple separate swaps to save gas on intermediate token transfers.
Legacy Integration ​
Direct Contract Integration ​
For existing integrations using direct contract interaction:
Storage-based Pool Discovery ​
// Read pool count
uint256 poolCount = uint256(fluidDexLite.readFromStorage(bytes32(1)));
// Read DexKey at index
function readDexKeyAtIndex(uint256 index) view returns (DexKey memory) {
bytes32 baseSlot = keccak256(abi.encode(1));
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);
}
Manual Estimation Pattern ​
bytes32 constant ESTIMATE_SWAP = keccak256("ESTIMATE_SWAP");
try fluidDexLite.swapSingle(
dexKey,
swap0To1,
int256(amountIn),
0,
address(0),
false,
"",
abi.encode(ESTIMATE_SWAP)
) {
revert("Should not succeed");
} catch (bytes memory reason) {
if (reason.length >= 36) {
bytes4 selector = bytes4(reason);
if (selector == bytes4(keccak256("EstimateSwap(uint256)"))) {
assembly {
amountOut := mload(add(reason, 36))
}
}
}
}
Best Practices ​
Gas Optimization ​
- Implement callback pattern for swap execution
- Batch multi-hop swaps using
swapHop
Error Handling ​
- Always use try-catch for estimation
- Implement proper slippage protection
- Handle edge cases (zero liquidity, large swaps)
Security ​
- Validate all DexKey parameters
- Use proper slippage calculations
- Implement deadline checks
- Verify callback caller authenticity
Resources ​
Documentation ​
Production Implementations ​
ParaSwap Integration (TypeScript)
- Repository: VeloraDEX/paraswap-dex-lib
- Features: Complete integration with pool discovery, pricing, and execution
KyberSwap Integration (Golang)
- Repository: KyberNetwork/kyberswap-dex-lib
- Features: High-performance integration with comprehensive testing
Contract Addresses ​
Network | Contract | Address |
---|---|---|
Ethereum Mainnet | FluidDexLite | 0xBbcb91440523216e2b87052A99F69c604A7b6e00 |
Ethereum Mainnet | FluidDexLiteResolver (v1.0.0) | 0x26b696D0dfDAB6c894Aa9a6575fCD07BB25BbD2C |