You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Uniswap V3添加流动性创建新头寸频繁交易失败排查

Uniswap V3新建头寸高失败率问题排查

问题现象

  • 已完成Uniswap V3交易、新建头寸、平仓三类逻辑开发,其中交易、超价格范围头寸平仓功能运行稳定,代码可在yarn环境正常执行
  • 异常表现:新建头寸操作交易失败率极高,通常需要连续重试30-40次才能成功,暂未定位根因

现有实现代码

未开仓头寸定义逻辑

export class NewPosition extends UniPosition {
  static fromPosition(position: Position): NewPosition {
    return new NewPosition({
      pool: position.pool,
      liquidity: position.liquidity,
      tickLower: position.tickLower,
      tickUpper: position.tickUpper,
    });
  }

  static withRange(pool: Pool, rangePercentage: number, amount0: string): NewPosition {
    const newPriceRange = calculatePriceRange(pool.token0Price, rangePercentage);

    const tickSpacing = pool.tickSpacing;
    const rawLowerTick = priceToClosestTick(newPriceRange.lower);
    const rawUpperTick = priceToClosestTick(newPriceRange.upper);
    // 现有对齐逻辑
    const tickLower = rawLowerTick - (rawLowerTick % tickSpacing);
    const tickUpper = rawUpperTick + (tickSpacing - (rawUpperTick % tickSpacing));

    return NewPosition.fromPosition(
      NewPosition.fromAmount0({
        pool,
        tickLower,
        tickUpper,
        amount0,
        useFullPrecision: true,
      }),
    );
  }
}

头寸铸造逻辑

async mint(signer: Signer): Promise<BigNumber> {
  let useNative = Ether.onChain(await (await signer.getChainId())) as any;
  useNative = null;

  const params = NonfungiblePositionManager.addCallParameters(this, {
    slippageTolerance: new Percent(5, 100),
    deadline: ethers.constants.MaxUint256.toString(),    
    recipient: await signer.getAddress(),
    createPool: false,
    useNative: useNative,
  });
  const tx = await signer.sendTransaction({
    to: POSITIONS_ADDRESS, 
    from: await signer.getAddress(),
    data: params.calldata,
    value: params.value,
    maxFeePerGas: await getmaxFeePerGas(), 
    maxPriorityFeePerGas: getmaxPriorityFee(),  
    gasLimit: 600000,
  });
  const receipt = await tx.wait();
}

根因排查与修复方案

按出现概率从高到低排序:

  • Tick对齐逻辑存在负数计算bug
    现有自行实现的tick取模对齐逻辑,在价格对应tick为负数时会计算出不符合Uniswap V3要求的tick值:Uniswap V3要求头寸的上下tick必须是tickSpacing的整数倍,且tickLower < tickUpper,JS语言中取模运算对负数返回负余数,会导致算出的tick不满足整除要求、甚至出现tickLower >= tickUpper的情况,直接触发合约revert。重试多次成功本质是某次数值刚好落在正数tick区间,计算结果碰巧符合要求。
    修复方式:直接使用Uniswap SDK自带的nearestUsableTick方法完成tick对齐,不要自行实现取模逻辑。
  • 池子状态读取陈旧触发滑点保护
    现有逻辑如果复用缓存的Pool实例计算头寸参数,RPC节点返回的区块数据可能落后于链上最新状态,算出来的所需token数量和链上最新价格下的实际需求量偏差超过5%滑点容忍度时,会被合约滑点保护机制直接revert。另外代码中把deadline设为MaxUint256,会导致交易在mempool中排队时没有过期限制,排队期间价格波动就会失败。
    修复方式:每次发起mint交易前,强制拉取最新区块对应的池子slot0、流动性、tick数据重新生成Pool实例再计算头寸参数;deadline改为当前区块时间戳+120秒即可,不要用无限期过期值。
  • Gas参数硬编码导致交易失败
    代码中硬编码gasLimit为600000,不同链、不同头寸规模、不同价格区间下mint操作的实际gas消耗存在波动,当实际消耗超过60万时会触发out of gas错误;如果自定义的getmaxFeePerGas、getmaxPriorityFee方法返回的gas价格低于链上当前baseFee要求,交易会被节点直接丢弃,表现为失败。
    修复方式:发起交易前调用provider.estimateGas预估实际gas消耗,上浮20%作为gasLimit传入,不要硬编码固定值;EIP1559费用参数优先通过RPC的eth_feeHistory接口取最近几个区块的费用数据计算,不要用固定写死的返回值。
  • Token授权校验缺失
    现有代码未包含头寸对应两个ERC20代币对NonfungiblePositionManager合约的授权检查逻辑,如果当前地址授权额度低于本次mint需要划转的代币数量,会触发合约的余额/授权校验revert。
    修复方式:每次发起mint前先检查两个代币的allowance额度,额度不足时先发起approve交易,等授权确认上链后再发送mint交易。
  • 原生币传值逻辑错误
    代码中先获取了链上原生币标识又强制把useNative设为null,如果当前操作的交易对包含原生币(如WETH/USDC交易对直接存ETH的场景),会导致msg.value传值和合约要求的原生币数量不匹配,触发校验失败。
    修复方式:根据交易对实际包含的资产类型设置useNative参数,发送交易时确认value值和addCallParameters返回的value完全一致,不要多传或者少传。

内容的提问来源于stack exchange,提问作者Dajeir

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 19:06:43