如何获取Hedera上HTS Fungible Token的最新剩余授权额度?
HTS Fungible Token授权额度查询与合约转账失败问题
我开发了一个DApp,合约已获得代表用户花费HTS Fungible Token的授权额度,但合约一直触发SPENDER_DOES_NOT_HAVE_ALLOWANCE错误回滚。
我在代码中添加了判断逻辑:若当前授权额度小于所需金额,就请求用户发起新的授权交易:
if (amountGranted < tokenAmountToSpend) { // 执行授权交易 }
该逻辑看似可行,但调用镜像节点的/api/v1/account/{accountId}/allowances/tokens接口时,响应中的allowances.amount_granted仅显示初始授权额度,而非更新后的剩余额度。请问是否有其他接口可获取最新的剩余授权额度?
复现步骤
- 创建一个包含从调用者向合约自身转账功能的智能合约。
- 在Hedera上部署该智能合约。
- 创建新的Hedera账户并创建FT,或使用已有FT的账户。
- 将合约与计划转入的Hedera账户FT进行关联。
- 使用JS SDK向合约授予额度为3的Token授权。
- 通过
https://testnet.mirrornode.hedera.com/api/v1/accounts/${ownerAccountID}/allowances/tokens查询镜像节点授权信息,添加amountGranted < tokenAmountToSpend判断,若成立则调用grantAllowance()。 - 以Hedera账户为调用者,调用智能合约的
transfer函数,转账金额为2,调用成功。 - 再次以相同账户调用
transfer函数,转账金额为2,调用失败:合约回滚并提示SPENDER_DOES_NOT_HAVE_ALLOWANCE。
智能合约代码
// SPDX-License-Identifier: MIT pragma solidity >=0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract TransferCallerTokens { function transfer(address token, uint256 amount) public { IERC20(token).transferFrom(msg.sender, address(this), amount); } }
合约部署代码
async function deployContract() { const bytecode = fs.readFileSync( "TransferCallerTokens_sol_TransferCallerTokens.bin"); // 执行交易前切换客户端操作员,需使用合约管理员密钥签名 client.setOperator(adminAccountId, adminKey); // 构建交易 const contractCreate = new ContractCreateFlow() .setGas(100_000) .setBytecode(bytecode) .setAdminKey(adminKey) const contractCreateTxResponse = await contractCreate.execute(client); const contractCreateReceipt = await contractCreateTxResponse.getReceipt(client); const transferCallerTokensContractID = contractCreateReceipt.contractId; console.log(`The transferCallerTokensContractID: ${transferCallerTokensContractID}`); }
创建Fungible Token代码(FT归用户所有)
// 创建HTS Fungible Token async function createFungibleToken( client, treasuryAccountId, treasuryAccountPrivateKey, ) { // 生成供应密钥 const supplyKeyForFT = PrivateKey.generateED25519(); // 配置Token const createFTokenTxn = await new TokenCreateTransaction() .setTokenName('LoeweToken') .setTokenSymbol('LO') .setTokenType(TokenType.FungibleCommon) .setDecimals(1) .setInitialSupply(100) .setTreasuryAccountId(treasuryAccountId) .setSupplyKey(supplyKeyForFT) .setMaxTransactionFee(new Hbar(30)) .freezeWith(client); // 使用国库账户私钥签名交易 const createFTokenTxnSigned = await createFTokenTxn.sign(treasuryAccountPrivateKey); const createFTokenTxnResponse = await createFTokenTxnSigned.execute(client); }
授予授权额度代码
// 向智能合约授予授权额度 async function grantAllowance() { const tokenAmount = 2; const approveAllowanceTx = new AccountAllowanceApproveTransaction() .approveTokenAllowance( fungibleTokenId, ownerAccountId, spenderAccountId, tokenAmount ) .freezeWith(client); const approveAllowanceTxSign = await approveAllowanceTx .sign( PrivateKey.fromString(process.env.MY_PRIVATE_KEY) ); const approveAllowanceTxResponse = await approveAllowanceTxSign.execute(client); await approveAllowanceTxResponse.getReceipt(client); }
执行合约transfer函数代码
// 执行HTS Fungible Token转账 async function executeTransferTransaction() { const amount = 2; const allowanceInfo = await getAllowance(); const contractAllowanceInfo = allowanceInfo.allowances.find( (x) => (x.spender === '0.0.15079297' && x.token_id === '0.0.14073131') ); const contractAmountGranted = contractAllowanceInfo.amount_granted console.log(`Amount Granted for Contract is: ${contractAmountGranted}`) if (contractAmountGranted < amount) { await grantAllowance(); } const transferFromTx = new ContractExecuteTransaction() .setContractId(contractID) .setFunction( 'transfer', new ContractFunctionParameters() .addAddress(tokenIDSolidityAddress) .addUint256(amount), ) .setGas(3_000_000) .freezeWith(client); const transferFromTxResponse = await transferFromTx.execute(client); const receipt = await transferFromTxResponse.getReceipt(client); console.log(`Execute Transfer on TransferCallerTokens status ${receipt.status}`); }
环境信息
- 网络环境:Hedera Testnet
- Hedera JS SDK版本:2.29.0
问题解答
核心问题分析
你调用的镜像节点/api/v1/account/{accountId}/allowances/tokens接口返回的amount_granted是初始授权总额,而非剩余可使用额度,这是该接口的设计逻辑。剩余额度需要通过amount_granted减去amount_used计算得出,但该接口并未返回amount_used字段,导致你无法直接获取剩余可用额度。
解决方案
1. 使用Hedera SDK的AccountAllowanceQuery查询剩余额度
无需依赖镜像节点接口,直接通过Hedera JS SDK提供的AccountAllowanceQuery查询精准的剩余授权额度:
async function getRemainingAllowance() { const allowanceQuery = new AccountAllowanceQuery() .setOwnerAccountId(ownerAccountId) .setSpenderAccountId(spenderAccountId) .setTokenId(fungibleTokenId); const remainingAllowance = await allowanceQuery.execute(client); return remainingAllowance; }
该查询会直接返回当前合约可使用的剩余授权额度,可直接用于你的判断逻辑。
2. 调整授权逻辑,避免依赖镜像节点数据
将判断逻辑改为基于剩余额度而非初始授权总额:
// 替换原有的额度判断逻辑 const remainingAllowance = await getRemainingAllowance(); if (remainingAllowance < amount) { // 可以选择追加授权或重新授权 // 追加授权:使用AccountAllowanceApproveTransaction增加额度 // 重新授权:如果需要覆盖原有额度,注意先调用AccountAllowanceDeleteTransaction清除原有授权 await grantAllowance(); }
3. 注意授权操作的行为差异
AccountAllowanceApproveTransaction默认是追加授权而非覆盖,如果你需要重置授权额度,需要先调用AccountAllowanceDeleteTransaction清除原有授权,再重新授予新额度。- 合约执行
transferFrom后,剩余额度会实时更新,使用AccountAllowanceQuery能立即获取最新状态。
内容的提问来源于stack exchange,提问作者bguiz
相关产品推荐
相关产品推荐

