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

如何获取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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 06:02:10