Skip to content

fix(ops): send net recipient amount as burn_amount in withdraw request#9

Merged
niconiconi merged 2 commits intomainfrom
fix/withdraw-amount-in-relayer-request
Mar 6, 2026
Merged

fix(ops): send net recipient amount as burn_amount in withdraw request#9
niconiconi merged 2 commits intomainfrom
fix/withdraw-amount-in-relayer-request

Conversation

@niconiconi
Copy link
Copy Markdown
Contributor

问题

Withdraw 始终失败,relayer 返回:「Transaction validation failed. The note may have been spent or is invalid.」

实际是合约 ZKP 验证失败(estimate_gas 报错),不是 nullifier 问题。

根因分析

合约 withdraw() 构造 ZKP 验证的 input[6]

uint256 amountWithFee = inp.amount + protocolFee + inp.relayerFee;
input[6] = amountWithFee;

电路的平衡约束:

input_utxo_amount = output_change + c.Amount

因此 c.Amount = burnAmount = net_recipient + relayerFee + protocolFee(总 UTXO 扣减量)。

ZKP 验证要求 circuit c.Amount == contract input[6],即:

net + protocolFee + relayerFee == inp.amount + contractProtocolFee + inp.relayerFee

只有当 inp.amount = net_recipient(净到账金额)时,两边才相等(因为双方用同一公式计算 protocolFee)。

Bug:原代码将 withdraw_amount 设为 burnAmount(= net + relayerFee + protocolFee,总扣减量),relayer 直接用它作为 inp.amount 传给合约,合约再次叠加手续费,导致 amountWithFee > c.Amount,ZKP 验证失败。

修复

将 proof context 中的 withdraw_amountburnAmount 改为 typedPlan.requestedAmount(用户请求的净到账金额),使合约能正确还原出与电路一致的 amountWithFee

验证

在 Sepolia testnet 上运行 automation(withdrawFeeBPS = 25),三个实例均在 cycle 8 成功 withdraw,链上 tx 已确认:

  • 0x0368d4afc36e59c4fb00730e79d3beb4aaf4fea9a78adc423d6fa6a9ac5574bc
  • 0x46b11ac7028767c936fe8ac2d4826ef54336f64d7e56902cd0f6bc396c0ece6b
  • 0x3f1172565024b3ff4468209d8fbbb262c4ddb5cf98ed4886cdfcf070c02744ed

修复前在 withdrawFeeBPS = 0 时可能碰巧通过(relayerFee 极小时误差也极小),设为 25 BPS 后问题必现。

🤖 Generated with Claude Code

The withdraw proof's circuit Amount (c.Amount) equals the total UTXO
deduction: net_recipient + relayerFee + protocolFee. The contract
computes amountWithFee = inp.amount + contractProtocolFee + inp.relayerFee
and uses it as ZKP public input[6]. For the proof to verify, inp.amount
must be the net recipient amount so that:

  amountWithFee = net + protocolFee + relayerFee = burnAmount = c.Amount

Previously, withdraw_amount (used as burn_amount / inp.amount in the
relayer request) was set to burnAmount (total UTXO deduction including
fees). The contract would then add fees on top again, making
amountWithFee > c.Amount and failing ZKP verification with
"Transaction validation failed. The note may have been spent or is invalid."

Fix: use typedPlan.requestedAmount (the net amount the user asked to
receive) as withdraw_amount in the proof context so the relayer sends
the correct inp.amount to the contract.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求解决了一个关键错误,该错误导致提款操作因智能合约内零知识证明(ZKP)计算不匹配而失败。修复方案确保在证明上下文中使用了正确的净收款人金额,从而使合约能够准确验证交易并成功处理提款。

Highlights

  • 修复提款失败问题: 解决了由于合约 ZKP 验证失败导致的提款操作持续失败的问题。
  • 根因分析: 发现问题是由于合约 withdraw() 方法在构造 ZKP 验证的 input[6] 时,将 burnAmount 作为 inp.amount 传入,导致合约再次叠加手续费,使得 amountWithFee 与电路的 c.Amount 不匹配。
  • 解决方案: 将 proof context 中的 withdraw_amountburnAmount 修改为 typedPlan.requestedAmount(用户请求的净到账金额),确保合约能够正确计算出与电路一致的 amountWithFee
  • 验证: 在 Sepolia 测试网上进行了验证,三个实例在 withdrawFeeBPS = 25 的情况下均成功提款,并提供了链上交易哈希。

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/ops/ops.ts
    • 修正了 withdraw_amount 的计算方式,将其从 burnAmount 改为 typedPlan.requestedAmount
Activity
  • 此拉取请求自创建以来没有人工活动。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

本次变更修复了提现(withdraw)流程中的一个关键 bug。之前传递给 relayer 的 withdraw_amount 包含了费用,导致合约在重复计算费用后 ZKP 验证失败。修复方案将此字段改为用户请求的净额(requestedAmount),从根本上解决了问题。代码变更符合逻辑,PR 描述清晰。我只提一个小建议,移除一处多余的类型转换,以提高代码的简洁性。

relayer: typedPlan.relayer,
recipient: input.recipient,
withdraw_amount: burnAmount,
withdraw_amount: BigInt(typedPlan.requestedAmount),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

这个修复是正确的,符合 PR 描述中的根因分析。

有一个小建议:根据 src/types.tsWithdrawPlan 的类型定义,typedPlan.requestedAmount 的类型已经是 bigint,所以这里的 BigInt() 包装是多余的,可以移除以使代码更简洁。

Suggested change
withdraw_amount: BigInt(typedPlan.requestedAmount),
withdraw_amount: typedPlan.requestedAmount,

…urn_amount

The test expected withdraw_amount = burnAmount (total UTXO deduction),
but the correct value is requestedAmount (net recipient amount).
Update assertions to reflect the fix in ops.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@niconiconi niconiconi merged commit 3d6206b into main Mar 6, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants