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

Uniswap V4通过Universal Router代币兑换持续回滚问题求助

Uniswap V4 LINK转USDC交易回滚排查

我尝试通过Uniswap V4完成LINK到USDC的代币兑换,但交易始终回滚。已完成ERC20授权Permit2、Permit2授权Universal Router的操作,且此前使用相同授权逻辑在Uniswap V3进行兑换可成功。以下是我的Swift实现代码:

public func executeV4(
    fromToken: TokenWallet,
    toToken: TokenWallet,
    sender: EthereumAddress,
    routerAddress: String,
    amountIn: BigUInt,
    amountOutMin: BigUInt,
    fee: UInt32 = 2,
    tickSpacing: Int32 = 1,
    payerIsUser: Bool = true,
    rpcUrl: String,
    gasLimit: BigUInt
) async throws -> String {
    
    GKLogDebug("==============================================")
    GKLogDebug("[executeV4] START")
    GKLogDebug("[executeV4] fromToken: \(fromToken.contractAddress ?? "")")
    GKLogDebug("[executeV4] toToken: \(toToken.contractAddress ?? "")")
    GKLogDebug("[executeV4] router: \(routerAddress)")
    GKLogDebug("[executeV4] amountIn: \(amountIn)")
    GKLogDebug("[executeV4] amountOutMin: \(amountOutMin)")
    GKLogDebug("[executeV4] rpc: \(rpcUrl)")
    GKLogDebug("==============================================")

    guard let url = URL(string: rpcUrl) else {
        GKLogDebug("[executeV4] ERROR: Invalid RPC URL")
        throw APIError.statusMessage(message: "Error - RPC URL")
    }
    let web3 = try await Web3.new(url)
    GKLogDebug("[executeV4] RPC OK")

    // Commands
    let command = Data([0x10])
    GKLogDebug("[executeV4] command: \(command.toHexString())")

    // Actions
    let actions = Data([0x06, 0x0C, 0x0F])
    GKLogDebug("[executeV4] actions: \(actions.toHexString())")

    guard let fromTokenAddress = fromToken.contractAddress,
          let toTokenAddress = toToken.contractAddress,
          let fromTokenAddressEth = EthereumAddress(fromTokenAddress),
          let toTokenAddressEth = EthereumAddress(toTokenAddress) else {
        GKLogDebug("[executeV4] ERROR: Token address missing")
        throw APIError.statusMessage(message: "Missing token addresses")
    }
    GKLogDebug("[executeV4] Token addr OK")

    guard let hooksAddress = EthereumAddress("0x0000000000000000000000000000000000000000") else {
        throw APIError.statusMessage(message: "Missing hooks")
    }

    let token0 = fromTokenAddressEth.address.lowercased() < toTokenAddressEth.address.lowercased()
        ? fromTokenAddressEth
        : toTokenAddressEth

    let token1 = fromTokenAddressEth.address.lowercased() < toTokenAddressEth.address.lowercased()
        ? toTokenAddressEth
        : fromTokenAddressEth

    let poolKeyValues: [Any] = [
        token0,
        token1,
        fee,
        tickSpacing,
        hooksAddress
    ]

    GKLogDebug("[executeV4] poolKey: \(poolKeyValues)")
    let zeroForOne = (fromTokenAddressEth.address.lowercased() == token0.address.lowercased())

    let swapValues: [Any] = [
        poolKeyValues,
        zeroForOne,
        amountIn,
        amountOutMin,
        Data()
    ]
    GKLogDebug("[executeV4] swapValues prepared")

    guard let swapParams = encodeInput(
        values: swapValues,
        abiTypes: [
            .init(name: "poolKey", type: .tuple(types: [
                .address, .address, .uint(bits: 24), .int(bits: 24), .address
            ])),
            .init(name: "zeroForOne", type: .bool),
            .init(name: "amountIn", type: .uint(bits: 128)),
            .init(name: "amountOutMinimum", type: .uint(bits: 128)),
            .init(name: "hookData", type: .dynamicBytes)
        ]
    ) else {
        GKLogDebug("[executeV4] ERROR: Failed to encode swapParams")
        throw APIError.statusMessage(message: "Fail to encode swapParams")
    }

    GKLogDebug("[executeV4] swapParams hex: \(swapParams.toHexString())")

    // SETTLE_ALL
    let settleAllValues: [Any] = [fromTokenAddressEth, amountIn]
    guard let settleAllParams = encodeSimpleTypes(
        values: settleAllValues,
        types: [.address, .uint(bits: 256)]
    ) else {
        GKLogDebug("[executeV4] ERROR: Fail to encode settleAll")
        throw APIError.statusMessage(message: "Fail to encode settleAllParams")
    }
    GKLogDebug("[executeV4] settleAllParams hex: \(settleAllParams.toHexString())")

    // TAKE_ALL
    let takeAllValues: [Any] = [toTokenAddressEth, amountOutMin]
    guard let takeAllParams = encodeSimpleTypes(
        values: takeAllValues,
        types: [.address, .uint(bits: 256)]
    ) else {
        GKLogDebug("[executeV4] ERROR: Fail to encode takeAll")
        throw APIError.statusMessage(message: "Fail to encode takeAllParams")
    }
    GKLogDebug("[executeV4] takeAllParams hex: \(takeAllParams.toHexString())")

    // Build params[]
    let params: [Data] = [swapParams, settleAllParams, takeAllParams]
    GKLogDebug("[executeV4] params count: \(params.count)")

    // Encode V4 input
    let v4InputValues: [Any] = [actions, params]
    let v4InputTypes: [ABI.Element.InOut] = [
        .init(name: "actions", type: .dynamicBytes),
        .init(name: "params", type: .array(type: .dynamicBytes, length: 0))
    ]

    guard let encodedV4Input = encodeInput(values: v4InputValues, abiTypes: v4InputTypes) else {
        GKLogDebug("[executeV4] ERROR: Failed to encode V4 input")
        throw APIError.statusMessage(message: "Failed to encode V4 input tuple")
    }

    GKLogDebug("[executeV4] encodedV4Input hex: \(encodedV4Input.toHexString())")

    
    let inputs = [encodedV4Input]
    let deadline = BigUInt(Date().timeIntervalSince1970 + 600)
    GKLogDebug("[executeV4] deadline: \(deadline)")

    let executeParams: [Any] = [command, inputs, deadline]
    GKLogDebug("[executeV4] executeParams OK")

    guard let walletAddress = LocalStorageManager.shared
        .get(LocalStorage.userWallet)?
        .walletAddressETH else {
        GKLogDebug("[executeV4] ERROR: missing local wallet")
        throw APIError.statusMessage(message: "Missing wallet address")
    }
    
    GKLogDebug("[executeV4] encodedV4Input hex: \(encodedV4Input.toHexString())")
    GKLogDebug("[executeV4] executeParams array count: \(executeParams.count)")
    for (index, param) in executeParams.enumerated() {
        if let dataParam = param as? Data {
            GKLogDebug("[executeV4] executeParams[\(index)] hex: \(dataParam.toHexString())")
        } else {
            GKLogDebug("[executeV4] executeParams[\(index)]: \(param)")
        }
    }


    let parameter = SwapUniTransParams(
        tokenIn: fromToken,
        sender: walletAddress,
        receiver: walletAddress,
        rpcURL: rpcUrl,
        value: 0
    )

    GKLogDebug("[executeV4] Preparing TX...")

    let rawTxBase64 = try await SwapUniHelper.createTransaction(
        parameter: parameter,
        contractAddress: routerAddress,
        abiName: "universal_execute",
        method: "execute",
        parameters: executeParams,
        value: 0,
        gasLimitEVM: gasLimit
    )

    GKLogDebug("[executeV4] TX encoded (base64): \(rawTxBase64.prefix(60))...")
    GKLogDebug("[executeV4] DONE")
    GKLogDebug("==============================================")

    return rawTxBase64
}

排查方向

  • Pool Key有效性:当前设置的fee=2(对应0.002%费率)、tickSpacing=1,需确认链上是否存在该参数的池;同时检查fee的编码是否符合uint24要求,避免位数溢出。
  • 参数编码匹配:amountIn和amountOutMin使用uint128编码,需确认数值未超出uint128范围;验证SETTLE_ALL、TAKE_ALL的参数结构是否完全匹配Uniswap V4 Router的要求。
  • Command与Input结构:确认0x10命令对应的inputs数组结构是否正确,是否遗漏必要参数。
  • Gas限制:Uniswap V4交易Gas消耗高于V3,尝试调高gasLimit至300000以上测试。
  • 池存在性验证:调用PoolManager合约的getPool方法,传入构建的poolKey,确认池是否真实存在。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.11 22:14:53