添加流动性指南

概述

添加流动性是为JAMM DEX交易对提供代币储备的过程。流动性提供者(LP)通过存入两种代币来获得LP代币,这些LP代币代表他们在池子中的份额,并可以获得交易费用收益。

流动性基础概念

什么是流动性

流动性是指交易对中可用于交易的代币数量。更高的流动性意味着:

  • 更小的价格影响

  • 更低的滑点

  • 更好的交易体验

LP代币

当您添加流动性时,会收到LP代币作为凭证:

  • LP代币数量代表您在池子中的份额

  • 可以随时用LP代币赎回相应的代币对

  • LP代币本身也是ERC-20代币,可以转让

流动性挖矿收益

流动性提供者的收益来源:

  1. 交易费用: 每笔交易的费用按比例分配给LP

  2. 推荐人奖励: 如果有推荐人系统,可能获得额外奖励

  3. 价格升值: 如果代币价格上涨,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生态的重要方式,本指南涵盖了:

  1. 基础概念: 流动性、LP代币、收益来源

  2. 添加方式: 代币对流动性、JU流动性

  3. 首次添加: 创建交易对、设定初始价格

  4. 后续添加: 按比例添加、单边优化

  5. 高级功能: Permit使用、批量操作

  6. 计算工具: LP数量、价值计算

  7. 监控分析: 事件监听、收益分析

  8. 最佳实践: 风险管理、策略优化

通过合理添加流动性,用户可以获得稳定的被动收入并为DEX生态做出贡献。