如何在执行Solidity合约函数时收取交易费并合并两次签名为单笔交易
解决方案
你只需要调整合约逻辑,将费用收取逻辑内嵌到add方法中,即可实现用户单笔签名完成费用支付+函数调用的需求,修改方案如下:
1. 修改Solidity合约代码
首先在合约中内置收款账户和费用扣除逻辑,把add方法改为可接收ETH的payable类型,调用时自动完成费用划转:
//SPDX-License-Identifier: Unlicense pragma solidity ^0.8.0; import "hardhat/console.sol"; contract Calculate { uint private c = 123; // 收款账户地址,部署时传入即可 address public immutable fundAcct; // 1% 费率 uint256 public constant FEE_PERCENT = 1; constructor(address _fundAcct) { fundAcct = _fundAcct; getC(); } function getC() public view returns (uint) { return c; } // 新增payable修饰符,支持调用时附带ETH function add(uint _a, uint _b) public payable { // 计算应付手续费 uint256 fee = msg.value * FEE_PERCENT / 100; // 手续费转账到指定账户,用call方式兼容性更好 (bool success, ) = fundAcct.call{value: fee}(""); require(success, "Fee transfer failed"); // 可选:如果用户多转了ETH,剩余部分退回给调用者 if(msg.value > fee) { (bool refundSuccess, ) = msg.sender.call{value: msg.value - fee}(""); require(refundSuccess, "Refund failed"); } c = _a + _b; } }
如果你不需要按比例收费,而是固定金额手续费,也可以直接校验
msg.value == 固定手续费金额即可,不需要动态计算费率。
2. 修改React端Ethers.js代码
移除单独的sendETH调用逻辑,直接在调用合约add方法时通过overrides参数附带要支付的ETH即可:
async function add() { await requestAccount(); const provider = new ethers.providers.Web3Provider(window.ethereum); const signer = provider.getSigner(); const contract = new ethers.Contract( calculateAddress, Calculate.abi, signer ); // 调用add方法时通过第三个参数传入overrides,指定本次交易附带的ETH金额 const tx = await contract.add(a, b, { value: ethers.utils.parseEther(fee.toString()) }); await tx.wait(); getC(); }
额外说明
- 这种方案全程只需要用户签署一次交易,既避免了两次签名的繁琐,也杜绝了用户付了费但没调用合约、或者调用了合约没付费的不一致问题
- 合约内置的转账逻辑会在
add执行前完成,手续费扣不成功的话整个交易会直接回滚,不会出现异常情况
内容的提问来源于stack exchange,提问作者Trevor Behnke
相关产品推荐
相关产品推荐

