hedera-token-service

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Hedera Token Service (HTS) — JavaScript SDK

Hedera Token Service (HTS) — JavaScript SDK

HTS is Hedera's native token engine. It lets you create and manage fungible tokens and NFTs without writing smart contracts. Tokens created through HTS are first-class network entities with built-in compliance controls (KYC, freeze, wipe, pause) and custom fee schedules.
HTS是Hedera的原生代币引擎,无需编写智能合约即可创建和管理可替代代币与NFTs。通过HTS创建的代币是一等网络实体,具备内置合规控制(KYC、冻结、清零、暂停)和自定义费用方案。

Setup

环境搭建

All imports come from
@hiero-ledger/sdk
. Two setup patterns exist:
所有导入均来自
@hiero-ledger/sdk
,有两种搭建模式:

Client + setOperator (direct)

Client + setOperator(直接模式)

js
import { Client, AccountId, PrivateKey } from "@hiero-ledger/sdk";

const client = Client.forName(process.env.HEDERA_NETWORK)
    .setOperator(
        AccountId.fromString(process.env.OPERATOR_ID),
        PrivateKey.fromStringECDSA(process.env.OPERATOR_KEY),
    );
js
import { Client, AccountId, PrivateKey } from "@hiero-ledger/sdk";

const client = Client.forName(process.env.HEDERA_NETWORK)
    .setOperator(
        AccountId.fromString(process.env.OPERATOR_ID),
        PrivateKey.fromStringECDSA(process.env.OPERATOR_KEY),
    );

Wallet + LocalProvider (signer-based)

Wallet + LocalProvider(签名者模式)

js
import { Wallet, LocalProvider } from "@hiero-ledger/sdk";

const provider = new LocalProvider();
const wallet = new Wallet(process.env.OPERATOR_ID, process.env.OPERATOR_KEY, provider);
With the signer pattern, use
freezeWithSigner(wallet)
,
signWithSigner(wallet)
,
executeWithSigner(wallet)
, and
getReceiptWithSigner(wallet)
instead of
freezeWith(client)
/
execute(client)
.
js
import { Wallet, LocalProvider } from "@hiero-ledger/sdk";

const provider = new LocalProvider();
const wallet = new Wallet(process.env.OPERATOR_ID, process.env.OPERATOR_KEY, provider);
使用签名者模式时,需用
freezeWithSigner(wallet)
signWithSigner(wallet)
executeWithSigner(wallet)
getReceiptWithSigner(wallet)
替代
freezeWith(client)
/
execute(client)

Transaction Lifecycle

交易生命周期

Every transaction follows this flow: configure -> freeze -> sign -> execute -> receipt.
js
const tx = await new SomeTransaction()
    .setSomeField(value)
    .freezeWith(client);   // locks the transaction body

await tx.sign(privateKey); // add signatures (call multiple times for multi-sig)

const response = await tx.execute(client);
const receipt = await response.getReceipt(client);
When only the operator key is needed, you can skip explicit freeze/sign —
execute(client)
handles it:
js
const response = await new SomeTransaction()
    .setSomeField(value)
    .execute(client);
const receipt = await response.getReceipt(client);
每笔交易均遵循以下流程:配置 -> 冻结 -> 签名 -> 执行 -> 获取回执
js
const tx = await new SomeTransaction()
    .setSomeField(value)
    .freezeWith(client);   // 锁定交易主体

await tx.sign(privateKey); // 添加签名(多签时可多次调用)

const response = await tx.execute(client);
const receipt = await response.getReceipt(client);
若仅需操作员密钥,可跳过显式冻结/签名步骤——
execute(client)
会自动处理:
js
const response = await new SomeTransaction()
    .setSomeField(value)
    .execute(client);
const receipt = await response.getReceipt(client);

Creating Tokens

创建代币

Fungible Token

可替代代币

js
import {
    TokenCreateTransaction, TokenType, TokenSupplyType,
    PrivateKey, Hbar,
} from "@hiero-ledger/sdk";

const supplyKey = PrivateKey.generateECDSA();

const { tokenId } = await (
    await new TokenCreateTransaction()
        .setTokenName("My Token")
        .setTokenSymbol("MTK")
        .setDecimals(2)
        .setInitialSupply(10000)
        .setTreasuryAccountId(operatorId)
        .setAdminKey(operatorKey)
        .setSupplyKey(supplyKey)
        .execute(client)
).getReceipt(client);
js
import {
    TokenCreateTransaction, TokenType, TokenSupplyType,
    PrivateKey, Hbar,
} from "@hiero-ledger/sdk";

const supplyKey = PrivateKey.generateECDSA();

const { tokenId } = await (
    await new TokenCreateTransaction()
        .setTokenName("My Token")
        .setTokenSymbol("MTK")
        .setDecimals(2)
        .setInitialSupply(10000)
        .setTreasuryAccountId(operatorId)
        .setAdminKey(operatorKey)
        .setSupplyKey(supplyKey)
        .execute(client)
).getReceipt(client);

NFT (Non-Fungible Token)

NFT(非同质化代币)

NFTs require
TokenType.NonFungibleUnique
. They have no decimals and no initial supply — you mint individual serials after creation.
js
const { tokenId: nftId } = await (
    await new TokenCreateTransaction()
        .setTokenName("My NFT Collection")
        .setTokenSymbol("MNFT")
        .setTokenType(TokenType.NonFungibleUnique)
        .setSupplyType(TokenSupplyType.Finite)
        .setMaxSupply(1000)
        .setTreasuryAccountId(treasuryId)
        .setAdminKey(adminKey)
        .setSupplyKey(supplyKey)
        .execute(client)
).getReceipt(client);
NFT需设置
TokenType.NonFungibleUnique
,它们没有小数位无初始供应量——创建后需单独铸造每个序列号。
js
const { tokenId: nftId } = await (
    await new TokenCreateTransaction()
        .setTokenName("My NFT Collection")
        .setTokenSymbol("MNFT")
        .setTokenType(TokenType.NonFungibleUnique)
        .setSupplyType(TokenSupplyType.Finite)
        .setMaxSupply(1000)
        .setTreasuryAccountId(treasuryId)
        .setAdminKey(adminKey)
        .setSupplyKey(supplyKey)
        .execute(client)
).getReceipt(client);

Minting

铸造

js
import { TokenMintTransaction } from "@hiero-ledger/sdk";

// Fungible — specify amount
await new TokenMintTransaction()
    .setTokenId(tokenId)
    .setAmount(500)
    .execute(client);

// NFT — specify metadata per serial
const { serials } = await (
    await new TokenMintTransaction()
        .setTokenId(nftId)
        .addMetadata(Buffer.from("ipfs://QmFirst..."))
        .addMetadata(Buffer.from("ipfs://QmSecond..."))
        .execute(client)
).getReceipt(client);
// serials = [Long(1), Long(2)]
js
import { TokenMintTransaction } from "@hiero-ledger/sdk";

// 可替代代币——指定数量
await new TokenMintTransaction()
    .setTokenId(tokenId)
    .setAmount(500)
    .execute(client);

// NFT——为每个序列号指定元数据
const { serials } = await (
    await new TokenMintTransaction()
        .setTokenId(nftId)
        .addMetadata(Buffer.from("ipfs://QmFirst..."))
        .addMetadata(Buffer.from("ipfs://QmSecond..."))
        .execute(client)
).getReceipt(client);
// serials = [Long(1), Long(2)]

Token Association

代币关联

Hedera accounts must associate with a token before they can receive it (unless the account has automatic token associations enabled via
setMaxAutomaticTokenAssociations
).
js
import { TokenAssociateTransaction } from "@hiero-ledger/sdk";

await (
    await (
        await new TokenAssociateTransaction()
            .setAccountId(recipientId)
            .setTokenIds([tokenId])
            .freezeWith(client)
    ).sign(recipientKey)      // account owner must sign
).execute(client);
Alternatively, set
setMaxAutomaticTokenAssociations(-1)
on account creation to allow unlimited auto-association.
Hedera账户在接收代币前必须关联该代币(除非账户通过
setMaxAutomaticTokenAssociations
启用了自动代币关联)。
js
import { TokenAssociateTransaction } from "@hiero-ledger/sdk";

await (
    await (
        await new TokenAssociateTransaction()
            .setAccountId(recipientId)
            .setTokenIds([tokenId])
            .freezeWith(client)
    ).sign(recipientKey)      // 账户所有者必须签名
).execute(client);
或者,在创建账户时设置
setMaxAutomaticTokenAssociations(-1)
以允许无限自动关联。

Transfers

转移

Use
TransferTransaction
for both fungible tokens and NFTs. Amounts are signed: negative debits, positive credits. They must net to zero per token.
js
import { TransferTransaction } from "@hiero-ledger/sdk";

// Fungible transfer
await new TransferTransaction()
    .addTokenTransfer(tokenId, senderId, -100)
    .addTokenTransfer(tokenId, receiverId, 100)
    .execute(client);

// NFT transfer (tokenId, serial, sender, receiver)
await new TransferTransaction()
    .addNftTransfer(nftId, 1, senderId, receiverId)
    .execute(client);

// Mix Hbar + token transfers in one transaction
await new TransferTransaction()
    .addHbarTransfer(senderId, new Hbar(-5))
    .addHbarTransfer(receiverId, new Hbar(5))
    .addTokenTransfer(tokenId, senderId, -50)
    .addTokenTransfer(tokenId, receiverId, 50)
    .execute(client);
使用
TransferTransaction
处理可替代代币和NFT的转移。金额为带符号值:负数表示扣除,正数表示增加。每种代币的总金额必须为零。
js
import { TransferTransaction } from "@hiero-ledger/sdk";

// 可替代代币转移
await new TransferTransaction()
    .addTokenTransfer(tokenId, senderId, -100)
    .addTokenTransfer(tokenId, receiverId, 100)
    .execute(client);

// NFT转移(tokenId、序列号、发送方、接收方)
await new TransferTransaction()
    .addNftTransfer(nftId, 1, senderId, receiverId)
    .execute(client);

// 在一笔交易中混合Hbar与代币转移
await new TransferTransaction()
    .addHbarTransfer(senderId, new Hbar(-5))
    .addHbarTransfer(receiverId, new Hbar(5))
    .addTokenTransfer(tokenId, senderId, -50)
    .addTokenTransfer(tokenId, receiverId, 50)
    .execute(client);

Key Roles

密钥角色

Each key is optional. If omitted at creation, that capability is permanently disabled.
KeyPurpose
adminKey
Update/delete the token; rotate other keys
supplyKey
Mint and burn
freezeKey
Freeze/unfreeze accounts from transferring this token
kycKey
Grant/revoke KYC status on accounts
wipeKey
Wipe token balance from an account
pauseKey
Pause/unpause all token operations globally
feeScheduleKey
Update the custom fee schedule
metadataKey
Update token or NFT metadata
每个密钥都是可选的。若创建时未设置,对应的功能将永久禁用。
密钥用途
adminKey
更新/删除代币;轮换其他密钥
supplyKey
铸造和销毁代币
freezeKey
冻结/解冻账户的代币转移权限
kycKey
授予/撤销账户的KYC状态
wipeKey
清零账户的代币余额
pauseKey
全局暂停/恢复所有代币操作
feeScheduleKey
更新自定义费用方案
metadataKey
更新代币或NFT元数据

Compliance Operations

合规操作

js
// Grant KYC (required when token has kycKey)
await new TokenGrantKycTransaction()
    .setTokenId(tokenId).setAccountId(accountId).execute(client);

// Freeze an account
await new TokenFreezeTransaction()
    .setTokenId(tokenId).setAccountId(accountId).execute(client);

// Unfreeze
await new TokenUnfreezeTransaction()
    .setTokenId(tokenId).setAccountId(accountId).execute(client);

// Wipe tokens from an account
await new TokenWipeTransaction()
    .setTokenId(tokenId).setAccountId(accountId).setAmount(10).execute(client);

// Pause all transfers
await new TokenPauseTransaction().setTokenId(tokenId).execute(client);

// Unpause
await new TokenUnpauseTransaction().setTokenId(tokenId).execute(client);
js
// 授予KYC权限(当代币设置了kycKey时必需)
await new TokenGrantKycTransaction()
    .setTokenId(tokenId).setAccountId(accountId).execute(client);

// 冻结账户
await new TokenFreezeTransaction()
    .setTokenId(tokenId).setAccountId(accountId).execute(client);

// 解冻账户
await new TokenUnfreezeTransaction()
    .setTokenId(tokenId).setAccountId(accountId).execute(client);

// 清零账户代币
await new TokenWipeTransaction()
    .setTokenId(tokenId).setAccountId(accountId).setAmount(10).execute(client);

// 暂停所有转移操作
await new TokenPauseTransaction().setTokenId(tokenId).execute(client);

// 恢复转移操作
await new TokenUnpauseTransaction().setTokenId(tokenId).execute(client);

Burning & Deleting

销毁与删除

js
import { TokenBurnTransaction, TokenDeleteTransaction } from "@hiero-ledger/sdk";

// Burn fungible
await new TokenBurnTransaction()
    .setTokenId(tokenId).setAmount(100).execute(client);

// Burn NFT serials
await new TokenBurnTransaction()
    .setTokenId(nftId).setSerials([1, 2]).execute(client);

// Delete entire token (requires adminKey)
await new TokenDeleteTransaction()
    .setTokenId(tokenId).execute(client);
js
import { TokenBurnTransaction, TokenDeleteTransaction } from "@hiero-ledger/sdk";

// 销毁可替代代币
await new TokenBurnTransaction()
    .setTokenId(tokenId).setAmount(100).execute(client);

// 销毁NFT序列号
await new TokenBurnTransaction()
    .setTokenId(nftId).setSerials([1, 2]).execute(client);

// 删除整个代币(需要adminKey)
await new TokenDeleteTransaction()
    .setTokenId(tokenId).execute(client);

Querying Token Info

查询代币信息

js
import { TokenInfoQuery, TokenNftInfoQuery, NftId } from "@hiero-ledger/sdk";

const info = await new TokenInfoQuery().setTokenId(tokenId).execute(client);
console.log(info.name, info.symbol, info.totalSupply.toString());

const nftInfo = await new TokenNftInfoQuery()
    .setNftId(new NftId(nftId, 1))
    .execute(client);
console.log(nftInfo.accountId.toString()); // current owner
js
import { TokenInfoQuery, TokenNftInfoQuery, NftId } from "@hiero-ledger/sdk";

const info = await new TokenInfoQuery().setTokenId(tokenId).execute(client);
console.log(info.name, info.symbol, info.totalSupply.toString());

const nftInfo = await new TokenNftInfoQuery()
    .setNftId(new NftId(nftId, 1))
    .execute(client);
console.log(nftInfo.accountId.toString()); // 当前所有者

Airdrops

空投

Airdrops distribute tokens to multiple recipients. If a recipient can't auto-associate, the airdrop becomes pending and must be claimed.
js
import {
    TokenAirdropTransaction, TokenClaimAirdropTransaction,
    TokenCancelAirdropTransaction, TokenRejectTransaction, NftId,
} from "@hiero-ledger/sdk";

// Send airdrop
const record = await (
    await (
        await new TokenAirdropTransaction()
            .addTokenTransfer(tokenId, treasuryId, -300)
            .addTokenTransfer(tokenId, recipient1, 100)
            .addTokenTransfer(tokenId, recipient2, 100)
            .addTokenTransfer(tokenId, recipient3, 100)
            .addNftTransfer(nftId, 1, treasuryId, recipient1)
            .freezeWith(client)
            .sign(treasuryKey)
    ).execute(client)
).getRecord(client);

// Check pending airdrops
const { newPendingAirdrops } = record;

// Recipient claims a pending airdrop
await (
    await new TokenClaimAirdropTransaction()
        .addPendingAirdropId(newPendingAirdrops[0].airdropId)
        .freezeWith(client)
        .sign(recipientKey)
).execute(client);

// Sender cancels a pending airdrop
await new TokenCancelAirdropTransaction()
    .addPendingAirdropId(newPendingAirdrops[1].airdropId)
    .execute(client);

// Recipient rejects tokens they already received
await (
    await new TokenRejectTransaction()
        .setOwnerId(recipientId)
        .addTokenId(tokenId)           // reject fungible
        .addNftId(new NftId(nftId, 1)) // reject NFT
        .freezeWith(client)
        .sign(recipientKey)
).execute(client);
空投用于向多个接收方分发代币。若接收方无法自动关联,空投将变为待处理状态,需手动领取。
js
import {
    TokenAirdropTransaction, TokenClaimAirdropTransaction,
    TokenCancelAirdropTransaction, TokenRejectTransaction, NftId,
} from "@hiero-ledger/sdk";

// 发送空投
const record = await (
    await (
        await new TokenAirdropTransaction()
            .addTokenTransfer(tokenId, treasuryId, -300)
            .addTokenTransfer(tokenId, recipient1, 100)
            .addTokenTransfer(tokenId, recipient2, 100)
            .addTokenTransfer(tokenId, recipient3, 100)
            .addNftTransfer(nftId, 1, treasuryId, recipient1)
            .freezeWith(client)
            .sign(treasuryKey)
    ).execute(client)
).getRecord(client);

// 检查待处理空投
const { newPendingAirdrops } = record;

// 接收方领取待处理空投
await (
    await new TokenClaimAirdropTransaction()
        .addPendingAirdropId(newPendingAirdrops[0].airdropId)
        .freezeWith(client)
        .sign(recipientKey)
).execute(client);

// 发送方取消待处理空投
await new TokenCancelAirdropTransaction()
    .addPendingAirdropId(newPendingAirdrops[1].airdropId)
    .execute(client);

// 接收方拒绝已收到的代币
await (
    await new TokenRejectTransaction()
        .setOwnerId(recipientId)
        .addTokenId(tokenId)           // 拒绝可替代代币
        .addNftId(new NftId(nftId, 1)) // 拒绝NFT
        .freezeWith(client)
        .sign(recipientKey)
).execute(client);

Custom Fees

自定义费用

HTS supports three fee types. See
references/custom-fees.md
for full details.
js
import {
    CustomFixedFee, CustomFractionalFee, CustomRoyaltyFee, Hbar,
} from "@hiero-ledger/sdk";

const fixedFee = new CustomFixedFee()
    .setFeeCollectorAccountId(collectorId)
    .setHbarAmount(new Hbar(1));

const fractionalFee = new CustomFractionalFee()
    .setFeeCollectorAccountId(collectorId)
    .setNumerator(1).setDenominator(100)  // 1%
    .setMin(1).setMax(1000);

const royaltyFee = new CustomRoyaltyFee()
    .setFeeCollectorAccountId(collectorId)
    .setNumerator(5).setDenominator(100)  // 5%
    .setFallbackFee(
        new CustomFixedFee().setHbarAmount(new Hbar(2))
    );

await new TokenCreateTransaction()
    .setCustomFees([fixedFee, fractionalFee])
    // ... other fields
    .execute(client);
HTS支持三种费用类型,详情请参阅
references/custom-fees.md
js
import {
    CustomFixedFee, CustomFractionalFee, CustomRoyaltyFee, Hbar,
} from "@hiero-ledger/sdk";

const fixedFee = new CustomFixedFee()
    .setFeeCollectorAccountId(collectorId)
    .setHbarAmount(new Hbar(1));

const fractionalFee = new CustomFractionalFee()
    .setFeeCollectorAccountId(collectorId)
    .setNumerator(1).setDenominator(100)  // 1%
    .setMin(1).setMax(1000);

const royaltyFee = new CustomRoyaltyFee()
    .setFeeCollectorAccountId(collectorId)
    .setNumerator(5).setDenominator(100)  // 5%
    .setFallbackFee(
        new CustomFixedFee().setHbarAmount(new Hbar(2))
    );

await new TokenCreateTransaction()
    .setCustomFees([fixedFee, fractionalFee])
    // ... 其他字段
    .execute(client);

Common Gotchas

常见注意事项

  1. Association before transfer: Recipients must associate with the token or have auto-association slots. Without it, transfers fail with
    TOKEN_NOT_ASSOCIATED_TO_ACCOUNT
    .
  2. Signed amounts:
    addTokenTransfer
    uses signed amounts. Sender gets negative, receiver gets positive. They must net to zero.
  3. NFTs have no decimals or initial supply: Set
    TokenType.NonFungibleUnique
    and mint serials individually.
  4. Keys are permanent if no adminKey: Without an admin key, you cannot update or delete the token, and cannot change any other keys.
  5. KYC gate: If a token has a
    kycKey
    , accounts must receive KYC approval before they can transact with that token.
  6. Supply key for mint/burn: You need a supply key set at creation to later mint or burn.
  7. Treasury auto-association: The treasury account is automatically associated with the token.
  8. Freeze before multi-sig: When multiple parties need to sign, call
    freezeWith(client)
    first, then chain
    .sign(key)
    calls.
  1. 转移前需关联:接收方必须关联代币或拥有自动关联额度,否则转移会因
    TOKEN_NOT_ASSOCIATED_TO_ACCOUNT
    失败。
  2. 带符号金额
    addTokenTransfer
    使用带符号金额,发送方为负数,接收方为正数,且每种代币的总金额必须为零。
  3. NFT无小数位和初始供应量:需设置
    TokenType.NonFungibleUnique
    ,并单独铸造每个序列号。
  4. 无adminKey时密钥不可更改:若未设置adminKey,无法更新或删除代币,也无法修改其他密钥。
  5. KYC验证要求:若代币设置了
    kycKey
    ,账户必须获得KYC批准才能进行代币交易。
  6. 铸造/销毁需supplyKey:创建代币时需设置supplyKey,后续才能进行铸造或销毁操作。
  7. 国库账户自动关联:国库账户会自动与代币关联。
  8. 多签前需冻结:当需要多方签名时,先调用
    freezeWith(client)
    ,再链式调用
    .sign(key)

Reference Files

参考文件

  • references/api-reference.md
    — Complete list of all HTS transaction and query classes with their methods
  • references/custom-fees.md
    — Detailed guide to fixed, fractional, and royalty fees
  • references/api-reference.md
    — 所有HTS交易和查询类及其方法的完整列表
  • references/custom-fees.md
    — 固定费用、比例费用和版税费用的详细指南