跳转到主要内容

V2 添加流动性示例

一个向 V2 恒定乘积池添加流动性的完整示例。

完整代码

import { ethers } from 'ethers';

// === 配置(主网)===
const RPC_URL = 'https://evmrpc.kasplex.org';
const V2_FACTORY = '0x4373b7Fcf5059A785843cD224129e01d243Aef71';
const V2_ROUTER = '0xC7ca845B8302346e1C7227f03bb9EFb35ecD51fe';

// === ABIs ===
const ERC20_ABI = [
  'function approve(address,uint256) returns (bool)',
  'function allowance(address,address) view returns (uint256)',
  'function balanceOf(address) view returns (uint256)',
  'function symbol() view returns (string)',
  'function decimals() view returns (uint8)',
];

const FACTORY_ABI = [
  'function getPair(address,address) view returns (address)',
];

const PAIR_ABI = [
  'function getReserves() view returns (uint112, uint112, uint32)',
  'function token0() view returns (address)',
  'function totalSupply() view returns (uint256)',
  'function balanceOf(address) view returns (uint256)',
];

const ROUTER_ABI = [
  'function addLiquidity(address,address,uint256,uint256,uint256,uint256,address,uint256) returns (uint256,uint256,uint256)',
  'function addLiquidityETH(address,uint256,uint256,uint256,address,uint256) payable returns (uint256,uint256,uint256)',
];

/**
 * 向 V2 池添加流动性。
 *
 * @param signer   - 已连接的 ethers Signer
 * @param tokenA   - 第一个代币地址
 * @param tokenB   - 第二个代币地址
 * @param amountA  - 期望的 tokenA 数量(原始单位)
 * @param slippage - 滑点容差(例如 1 表示 1%)
 */
async function addLiquidityV2(
  signer: ethers.Signer,
  tokenA: string,
  tokenB: string,
  amountA: bigint,
  slippage: number = 1
) {
  const provider = signer.provider!;
  const userAddress = await signer.getAddress();
  const deadline = Math.floor(Date.now() / 1000) + 1200;
  const slippageFactor = BigInt(Math.floor((100 - slippage) * 100)) ; // 例如 1% 时为 9900

  // --- 检查池是否存在并计算 amountB ---
  const factory = new ethers.Contract(V2_FACTORY, FACTORY_ABI, provider);
  const pairAddress = await factory.getPair(tokenA, tokenB);

  let amountB: bigint;

  if (pairAddress === ethers.ZeroAddress) {
    // 新池 — 用户设定初始价格
    console.log('池不存在。你正在设定初始价格。');
    console.log('请手动输入 amountB(此示例使用 1:1 比率):');
    amountB = amountA; // 修改为你期望的比率
  } else {
    // 已有池 — 按比例计算 amountB
    const pair = new ethers.Contract(pairAddress, PAIR_ABI, provider);
    const [reserve0, reserve1] = await pair.getReserves();
    const token0 = await pair.token0();

    const isAToken0 = tokenA.toLowerCase() === token0.toLowerCase();
    const reserveA = isAToken0 ? reserve0 : reserve1;
    const reserveB = isAToken0 ? reserve1 : reserve0;

    amountB = (amountA * reserveB) / reserveA;
    console.log(`池已存在。计算得出的 amountB:${amountB}`);
  }

  // --- 授权两个代币给 V2 Router ---
  console.log('正在授权代币...');
  const tokenAContract = new ethers.Contract(tokenA, ERC20_ABI, signer);
  const tokenBContract = new ethers.Contract(tokenB, ERC20_ABI, signer);

  const allowanceA = await tokenAContract.allowance(userAddress, V2_ROUTER);
  const allowanceB = await tokenBContract.allowance(userAddress, V2_ROUTER);

  if (allowanceA < amountA) {
    const tx = await tokenAContract.approve(V2_ROUTER, ethers.MaxUint256);
    await tx.wait();
  }
  if (allowanceB < amountB) {
    const tx = await tokenBContract.approve(V2_ROUTER, ethers.MaxUint256);
    await tx.wait();
  }

  // --- 添加流动性 ---
  console.log('正在添加流动性...');
  const router = new ethers.Contract(V2_ROUTER, ROUTER_ABI, signer);

  const tx = await router.addLiquidity(
    tokenA,
    tokenB,
    amountA,
    amountB,
    (amountA * slippageFactor) / 10000n,
    (amountB * slippageFactor) / 10000n,
    userAddress,
    deadline
  );

  const receipt = await tx.wait();
  console.log(`流动性已添加。交易哈希:${receipt!.hash}`);

  // --- 检查 LP 余额 ---
  const newPairAddress = pairAddress === ethers.ZeroAddress
    ? await factory.getPair(tokenA, tokenB)
    : pairAddress;

  const pair = new ethers.Contract(newPairAddress, PAIR_ABI, provider);
  const lpBalance = await pair.balanceOf(userAddress);
  console.log(`LP 代币余额:${lpBalance}`);
}

// === 使用示例 ===
async function main() {
  const provider = new ethers.JsonRpcProvider(RPC_URL);
  const signer = new ethers.Wallet('YOUR_PRIVATE_KEY', provider);

  const TOKEN_A = '0xB190a6A7fC2873f1Abf145279eD664348d5Ef630';
  const TOKEN_B = '0x3Ac3B30b7f18AEFD4590D7FE4d9C5944aaeB7220';

  await addLiquidityV2(
    signer,
    TOKEN_A,
    TOKEN_B,
    ethers.parseEther('10'), // 10 个代币
    1                         // 1% 滑点
  );
}

main().catch(console.error);

使用原生 KAS

要使用原生 KAS 添加流动性,请使用 addLiquidityETH
const tx = await router.addLiquidityETH(
  tokenAddress,           // ERC-20 代币(不是 WKAS)
  amountTokenDesired,
  amountTokenMin,
  amountKASMin,
  userAddress,
  deadline,
  { value: amountKASDesired }  // 将 KAS 作为 value 发送
);

移除流动性

const REMOVE_ABI = [
  'function removeLiquidity(address,address,uint256,uint256,uint256,address,uint256) returns (uint256,uint256)',
];

// 授权 LP 代币给 router
const lpToken = new ethers.Contract(pairAddress, ERC20_ABI, signer);
await (await lpToken.approve(V2_ROUTER, lpAmount)).wait();

// 移除
const router = new ethers.Contract(V2_ROUTER, REMOVE_ABI, signer);
const tx = await router.removeLiquidity(
  tokenA,
  tokenB,
  lpAmount,
  0, // amountAMin
  0, // amountBMin
  userAddress,
  deadline
);