添加流动性指南
概述
添加流动性是为JAMM DEX交易对提供代币储备的过程。流动性提供者(LP)通过存入两种代币来获得LP代币,这些LP代币代表他们在池子中的份额,并可以获得交易费用收益。
流动性基础概念
什么是流动性
流动性是指交易对中可用于交易的代币数量。更高的流动性意味着:
更小的价格影响
更低的滑点
更好的交易体验
LP代币
当您添加流动性时,会收到LP代币作为凭证:
LP代币数量代表您在池子中的份额
可以随时用LP代币赎回相应的代币对
LP代币本身也是ERC-20代币,可以转让
流动性挖矿收益
流动性提供者的收益来源:
交易费用: 每笔交易的费用按比例分配给LP
推荐人奖励: 如果有推荐人系统,可能获得额外奖励
价格升值: 如果代币价格上涨,LP价值也会增加
添加流动性的方式
1. 代币对流动性
最常见的添加流动性方式,需要提供两种代币:
async function addLiquidity(
tokenA,
tokenB,
fee,
amountADesired,
amountBDesired,
slippagePercent,
userAddress,
signer
) {
const router = new ethers.Contract(ROUTER_ADDRESS, routerABI, signer);
// 1. Calculate minimum amounts (slippage protection)
const slippageBps = Math.floor(slippagePercent * 100);
const amountAMin = amountADesired.mul(10000 - slippageBps).div(10000);
const amountBMin = amountBDesired.mul(10000 - slippageBps).div(10000);
// 2. Check and approve tokens
await ensureTokenApproval(tokenA, ROUTER_ADDRESS, amountADesired, signer);
await ensureTokenApproval(tokenB, ROUTER_ADDRESS, amountBDesired, signer);
// 3. Set deadline
const deadline = Math.floor(Date.now() / 1000) + 60 * 20; // 20 minutes
// 4. Add liquidity
const tx = await router.addLiquidity(
tokenA,
tokenB,
fee,
amountADesired,
amountBDesired,
amountAMin,
amountBMin,
userAddress,
deadline
);
console.log("Add liquidity transaction submitted:", tx.hash);
const receipt = await tx.wait();
// 5. Parse result
const result = await parseLiquidityResult(receipt);
return result;
}
// Helper function: Ensure token approval
async function ensureTokenApproval(tokenAddress, spenderAddress, amount, signer) {
const token = new ethers.Contract(tokenAddress, erc20ABI, signer);
const userAddress = await signer.getAddress();
const currentAllowance = await token.allowance(userAddress, spenderAddress);
if (currentAllowance.lt(amount)) {
console.log(`Approving ${tokenAddress}...`);
const approveTx = await token.approve(spenderAddress, amount);
await approveTx.wait();
console.log("Approval completed");
}
}
2. JU + 代币流动性
添加JU和ERC-20代币的流动性:
async function addLiquidityETH(
token,
fee,
amountTokenDesired,
juAmount,
slippagePercent,
userAddress,
signer
) {
const router = new ethers.Contract(ROUTER_ADDRESS, routerABI, signer);
// 1. Calculate minimum amounts
const slippageBps = Math.floor(slippagePercent * 100);
const amountTokenMin = amountTokenDesired.mul(10000 - slippageBps).div(10000);
const amountETHMin = juAmount.mul(10000 - slippageBps).div(10000);
// 2. Approve token
await ensureTokenApproval(token, ROUTER_ADDRESS, amountTokenDesired, signer);
// 3. Check JU balance
const juBalance = await signer.getBalance();
if (juBalance.lt(juAmount)) {
throw new Error(`Insufficient JU balance. Required: ${ethers.utils.formatEther(juAmount)}, Have: ${ethers.utils.formatEther(juBalance)}`);
}
const deadline = Math.floor(Date.now() / 1000) + 60 * 20;
// 4. Add liquidity
const tx = await router.addLiquidityETH(
token,
fee,
amountTokenDesired,
amountTokenMin,
amountETHMin,
userAddress,
deadline,
{ value: juAmount } // Send JU
);
console.log("Add JU liquidity transaction submitted:", tx.hash);
return tx;
}
// Usage example
await addLiquidityETH(
TOKEN_ADDRESS,
100, // 1% fee rate
ethers.utils.parseEther("1000"), // 1000 tokens
ethers.utils.parseEther("1"), // 1 JU
1, // 1% slippage
userAddress,
signer
);
首次添加流动性
创建新的交易对
如果交易对不存在,添加流动性时会自动创建:
async function createPairAndAddLiquidity(
tokenA,
tokenB,
fee,
amountA,
amountB,
userAddress,
signer
) {
const factory = new ethers.Contract(FACTORY_ADDRESS, factoryABI, signer);
const router = new ethers.Contract(ROUTER_ADDRESS, routerABI, signer);
// 1. Check if pair exists
let pairAddress = await factory.getPair(tokenA, tokenB, fee);
if (pairAddress === ethers.constants.AddressZero) {
console.log("Pair does not exist, will be created automatically");
// Router will automatically create pair when adding liquidity
} else {
console.log("Pair already exists:", pairAddress);
}
// 2. Add liquidity (will automatically create pair if it doesn't exist)
const result = await addLiquidity(
tokenA,
tokenB,
fee,
amountA,
amountB,
1, // 1% slippage
userAddress,
signer
);
// 3. Get newly created pair address
pairAddress = await factory.getPair(tokenA, tokenB, fee);
console.log("Pair address:", pairAddress);
return { ...result, pairAddress };
}
初始价格设定
首次添加流动性时,您设定的代币比例决定了初始价格:
function calculateInitialPrice(amountA, amountB, decimalsA = 18, decimalsB = 18) {
// Normalize to 18 decimals
const normalizedA = amountA.mul(ethers.BigNumber.from(10).pow(18 - decimalsA));
const normalizedB = amountB.mul(ethers.BigNumber.from(10).pow(18 - decimalsB));
// Calculate price: 1 TokenA = ? TokenB
const priceAtoB = normalizedB.mul(ethers.utils.parseEther("1")).div(normalizedA);
const priceBtoA = normalizedA.mul(ethers.utils.parseEther("1")).div(normalizedB);
return {
priceAtoB: ethers.utils.formatEther(priceAtoB),
priceBtoA: ethers.utils.formatEther(priceBtoA)
};
}
// Usage example
const prices = calculateInitialPrice(
ethers.utils.parseEther("1000"), // 1000 TokenA
ethers.utils.parseUnits("2000", 6) // 2000 USDC (6 decimals)
);
console.log("Initial price - 1 TokenA =", prices.priceAtoB, "USDC");
console.log("Initial price - 1 USDC =", prices.priceBtoA, "TokenA");
后续添加流动性
按现有比例添加
对于已有流动性的交易对,需要按现有比例添加:
async function addLiquidityToExistingPair(
tokenA,
tokenB,
fee,
amountADesired,
userAddress,
signer
) {
const router = new ethers.Contract(ROUTER_ADDRESS, routerABI, signer);
const factory = new ethers.Contract(FACTORY_ADDRESS, factoryABI, provider);
// 1. Get current reserves
const pairAddress = await factory.getPair(tokenA, tokenB, fee);
if (pairAddress === ethers.constants.AddressZero) {
throw new Error("Pair does not exist");
}
const pair = new ethers.Contract(pairAddress, pairABI, provider);
const reserves = await pair.getReserves();
// 2. Determine token order
const token0 = await pair.token0();
const [reserveA, reserveB] = tokenA.toLowerCase() === token0.toLowerCase()
? [reserves.reserve0, reserves.reserve1]
: [reserves.reserve1, reserves.reserve0];
// 3. Calculate optimal TokenB amount
const amountBOptimal = await router.quote(amountADesired, reserveA, reserveB);
console.log("Recommended amounts to add:");
console.log("- TokenA:", ethers.utils.formatEther(amountADesired));
console.log("- TokenB:", ethers.utils.formatEther(amountBOptimal));
// 4. Add liquidity
const result = await addLiquidity(
tokenA,
tokenB,
fee,
amountADesired,
amountBOptimal,
1, // 1% slippage
userAddress,
signer
);
return result;
}
单边添加优化
当您只有一种代币时,可以先交换一部分来获得另一种代币:
async function addLiquidityWithSingleToken(
tokenIn,
tokenOut,
fee,
amountIn,
userAddress,
signer
) {
const router = new ethers.Contract(ROUTER_ADDRESS, routerABI, signer);
// 1. Calculate amount to swap (usually half)
const swapAmount = amountIn.div(2);
const remainingAmount = amountIn.sub(swapAmount);
// 2. First perform swap
console.log("Swapping partial tokens...");
const swapTx = await router.swapExactTokensForTokens(
swapAmount,
0, // Accept any amount of output
[tokenIn, tokenOut],
[fee],
userAddress,
ethers.constants.AddressZero, // No referrer
Math.floor(Date.now() / 1000) + 60 * 20
);
await swapTx.wait();
// 3. Query balance after swap
const tokenOutContract = new ethers.Contract(tokenOut, erc20ABI, provider);
const tokenOutBalance = await tokenOutContract.balanceOf(userAddress);
// 4. Add liquidity
console.log("Adding liquidity...");
const result = await addLiquidity(
tokenIn,
tokenOut,
fee,
remainingAmount,
tokenOutBalance,
2, // 2% slippage
userAddress,
signer
);
return result;
}
高级功能
使用Permit添加流动性
避免预先授权,直接通过签名添加流动性:
async function addLiquidityWithPermit(
tokenA,
tokenB,
fee,
amountADesired,
amountBDesired,
userAddress,
signer
) {
// Note: This feature requires LP tokens to support Permit, used for removing liquidity
// Adding liquidity itself still requires pre-approval of input tokens
// For adding liquidity, still need standard approve process
await ensureTokenApproval(tokenA, ROUTER_ADDRESS, amountADesired, signer);
await ensureTokenApproval(tokenB, ROUTER_ADDRESS, amountBDesired, signer);
// Then add liquidity normally
return await addLiquidity(
tokenA,
tokenB,
fee,
amountADesired,
amountBDesired,
1,
userAddress,
signer
);
}
批量添加流动性
为多个交易对同时添加流动性:
async function batchAddLiquidity(liquidityParams, signer) {
const results = [];
for (const params of liquidityParams) {
try {
console.log(`Adding liquidity: ${params.tokenA} / ${params.tokenB}`);
const result = await addLiquidity(
params.tokenA,
params.tokenB,
params.fee,
params.amountA,
params.amountB,
params.slippage || 1,
params.userAddress,
signer
);
results.push({
success: true,
pair: `${params.tokenA}/${params.tokenB}`,
...result
});
// Wait to avoid nonce conflicts
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (error) {
results.push({
success: false,
pair: `${params.tokenA}/${params.tokenB}`,
error: error.message
});
}
}
return results;
}
// Usage example
const liquidityParams = [
{
tokenA: TOKEN_A_ADDRESS,
tokenB: TOKEN_B_ADDRESS,
fee: 100,
amountA: ethers.utils.parseEther("100"),
amountB: ethers.utils.parseEther("100"),
userAddress: userAddress
},
{
tokenA: TOKEN_C_ADDRESS,
tokenB: TOKEN_D_ADDRESS,
fee: 50,
amountA: ethers.utils.parseEther("50"),
amountB: ethers.utils.parseEther("50"),
userAddress: userAddress
}
];
const results = await batchAddLiquidity(liquidityParams, signer);
流动性计算
LP代币数量计算
function calculateLPTokens(amountA, amountB, reserveA, reserveB, totalSupply) {
if (totalSupply.eq(0)) {
// First liquidity addition: geometric mean - minimum liquidity
const liquidity = sqrt(amountA.mul(amountB)).sub(ethers.BigNumber.from(1000));
return liquidity;
} else {
// Subsequent additions: proportional calculation
const liquidityA = amountA.mul(totalSupply).div(reserveA);
const liquidityB = amountB.mul(totalSupply).div(reserveB);
return liquidityA.lt(liquidityB) ? liquidityA : liquidityB;
}
}
// Square root calculation (simplified version)
function sqrt(value) {
if (value.eq(0)) return ethers.BigNumber.from(0);
let z = value.add(1).div(2);
let y = value;
while (z.lt(y)) {
y = z;
z = value.div(z).add(z).div(2);
}
return y;
}
价值计算
async function calculateLiquidityValue(pairAddress, lpAmount, userAddress) {
const pair = new ethers.Contract(pairAddress, pairABI, provider);
// Get basic information
const [token0, token1, reserves, totalSupply] = await Promise.all([
pair.token0(),
pair.token1(),
pair.getReserves(),
pair.totalSupply()
]);
// Calculate user share
const share = lpAmount.mul(ethers.utils.parseEther("1")).div(totalSupply);
// Calculate redeemable token amounts
const amount0 = reserves.reserve0.mul(lpAmount).div(totalSupply);
const amount1 = reserves.reserve1.mul(lpAmount).div(totalSupply);
return {
token0: token0,
token1: token1,
amount0: amount0,
amount1: amount1,
share: ethers.utils.formatEther(share), // Percentage
totalSupply: totalSupply
};
}
监控和分析
流动性事件监听
async function monitorLiquidityEvents(userAddress) {
const factory = new ethers.Contract(FACTORY_ADDRESS, factoryABI, provider);
const router = new ethers.Contract(ROUTER_ADDRESS, routerABI, provider);
// Listen for new pair creation
factory.on("PairCreated", (token0, token1, fee, pair, length) => {
console.log("New pair created:", {
token0: token0.slice(0, 6) + '...',
token1: token1.slice(0, 6) + '...',
fee: fee,
pair: pair.slice(0, 6) + '...',
totalPairs: length.toString()
});
});
// Listen for liquidity additions (need to monitor Mint events from all pairs)
// Simplified here to monitor specific pair
const pairAddress = await factory.getPair(TOKEN_A_ADDRESS, TOKEN_B_ADDRESS, 100);
if (pairAddress !== ethers.constants.AddressZero) {
const pair = new ethers.Contract(pairAddress, pairABI, provider);
pair.on("Mint", (sender, amount0, amount1) => {
console.log("Liquidity added:", {
sender: sender.slice(0, 6) + '...',
amount0: ethers.utils.formatEther(amount0),
amount1: ethers.utils.formatEther(amount1)
});
});
}
}
收益分析
async function analyzeLiquidityReturns(pairAddress, userAddress, fromBlock) {
const pair = new ethers.Contract(pairAddress, pairABI, provider);
// Get user's liquidity events
const mintFilter = pair.filters.Mint(null, null, null);
const burnFilter = pair.filters.Burn(null, null, null, userAddress);
const [mintEvents, burnEvents] = await Promise.all([
pair.queryFilter(mintFilter, fromBlock),
pair.queryFilter(burnFilter, fromBlock)
]);
// Analyze added liquidity
let totalAdded0 = ethers.BigNumber.from(0);
let totalAdded1 = ethers.BigNumber.from(0);
for (const event of mintEvents) {
// Need to further filter user-related events
totalAdded0 = totalAdded0.add(event.args.amount0);
totalAdded1 = totalAdded1.add(event.args.amount1);
}
// Analyze removed liquidity
let totalRemoved0 = ethers.BigNumber.from(0);
let totalRemoved1 = ethers.BigNumber.from(0);
for (const event of burnEvents) {
totalRemoved0 = totalRemoved0.add(event.args.amount0);
totalRemoved1 = totalRemoved1.add(event.args.amount1);
}
// Calculate current liquidity value held
const currentLP = await pair.balanceOf(userAddress);
const currentValue = await calculateLiquidityValue(pairAddress, currentLP, userAddress);
return {
totalAdded: { amount0: totalAdded0, amount1: totalAdded1 },
totalRemoved: { amount0: totalRemoved0, amount1: totalRemoved1 },
currentHolding: currentValue,
events: { mint: mintEvents.length, burn: burnEvents.length }
};
}
最佳实践
1. 风险管理
const liquidityRiskChecks = {
// Check impermanent loss risk
checkImpermanentLoss: (tokenA, tokenB) => {
// Stablecoin pairs: low risk
const stablecoins = ['USDC', 'USDT', 'DAI'];
const isStablePair = stablecoins.includes(tokenA) && stablecoins.includes(tokenB);
if (isStablePair) {
return { risk: 'LOW', message: 'Stablecoin pair, low impermanent loss risk' };
}
// Correlated assets: medium risk
const correlatedPairs = [['ETH', 'WETH'], ['BTC', 'WBTC']];
const isCorrelated = correlatedPairs.some(pair =>
(pair.includes(tokenA) && pair.includes(tokenB))
);
if (isCorrelated) {
return { risk: 'MEDIUM', message: 'Correlated assets, medium impermanent loss risk' };
}
// Other cases: high risk
return { risk: 'HIGH', message: 'Uncorrelated assets, high impermanent loss risk' };
},
// Check liquidity depth
checkLiquidityDepth: async (pairAddress) => {
const pair = new ethers.Contract(pairAddress, pairABI, provider);
const reserves = await pair.getReserves();
const totalLiquidity = reserves.reserve0.add(reserves.reserve1);
if (totalLiquidity.lt(ethers.utils.parseEther("1000"))) {
return { risk: 'HIGH', message: 'Low liquidity, may face high slippage' };
} else if (totalLiquidity.lt(ethers.utils.parseEther("10000"))) {
return { risk: 'MEDIUM', message: 'Medium liquidity' };
} else {
return { risk: 'LOW', message: 'Sufficient liquidity' };
}
}
};
2. 最优添加策略
function calculateOptimalLiquidityAmount(
tokenABalance,
tokenBBalance,
reserveA,
reserveB,
targetRatio = 0.5 // Target to use 50% of balance
) {
// Calculate current price ratio
const priceRatio = reserveB.mul(ethers.utils.parseEther("1")).div(reserveA);
// Calculate maximum addable amounts
const maxAmountA = tokenABalance.mul(targetRatio * 10000).div(10000);
const maxAmountB = tokenBBalance.mul(targetRatio * 10000).div(10000);
// Calculate optimal amounts based on price ratio
const requiredB = maxAmountA.mul(priceRatio).div(ethers.utils.parseEther("1"));
const requiredA = maxAmountB.mul(ethers.utils.parseEther("1")).div(priceRatio);
if (requiredB.lte(maxAmountB)) {
return { amountA: maxAmountA, amountB: requiredB };
} else {
return { amountA: requiredA, amountB: maxAmountB };
}
}
3. Gas优化
async function optimizeGasForLiquidity(liquidityParams, gasPrice) {
// Batch approvals to save gas
const approvals = [];
for (const params of liquidityParams) {
approvals.push(
ensureTokenApproval(params.tokenA, ROUTER_ADDRESS, params.amountA, signer),
ensureTokenApproval(params.tokenB, ROUTER_ADDRESS, params.amountB, signer)
);
}
// Execute approvals in parallel
await Promise.all(approvals);
// Calculate optimal gas price
const optimalGasPrice = await calculateOptimalGasPrice(gasPrice);
// Sort liquidity additions by gas efficiency
const sortedParams = liquidityParams.sort((a, b) => {
// Prioritize large liquidity amounts (higher gas efficiency)
const valueA = a.amountA.add(a.amountB);
const valueB = b.amountA.add(b.amountB);
return valueB.gt(valueA) ? 1 : -1;
});
return { sortedParams, optimalGasPrice };
}
总结
添加流动性是参与JAMM DEX生态的重要方式,本指南涵盖了:
基础概念: 流动性、LP代币、收益来源
添加方式: 代币对流动性、JU流动性
首次添加: 创建交易对、设定初始价格
后续添加: 按比例添加、单边优化
高级功能: Permit使用、批量操作
计算工具: LP数量、价值计算
监控分析: 事件监听、收益分析
最佳实践: 风险管理、策略优化
通过合理添加流动性,用户可以获得稳定的被动收入并为DEX生态做出贡献。