深入解析 Token Extensions 的 Transfer Fee:为 Solana 生态自定义可编程手续费

·

Transfer Fee 是 Solana Token Extensions 最常用也最直观的一项功能:每次链上转账都会自动扣除一笔手续费,无需额外合约逻辑,收取的仍是同一 Token——简单、高效、安全。本指南将以 Devnet 实战为例,带你从 0 到 1 创建带 Transfer Fee 并发手续费的 SPL Token,并演示如何 提取、归属、收割这些链上手续费。无论你正在为 GameFi、RWA,还是 DAO 打造经济模型,这篇都可以作为核心代币设计备忘录。

👉 想立刻动手测试?点此直达完整脚本>


目录

  1. 起步:准备环境
  2. 安装依赖
  3. 初始化带 Transfer Fee 的 Mint
  4. 发送交易:创建账户与铸造
  5. 转账并查看手续费
  6. 提取手续费到任意地址
  7. 收割手续费到 Mint 再统一支配
  8. 常见问答
  9. [总结与下一步]

起步

  1. 打开 Solana Playground 模板
  2. 自动生成 Playground 钱包(首次使用会提示)→ 复制钱包地址
  3. 在终端执行 solana airdrop 5 获取 Devnet SOL
  4. 启动初始脚本观察余额输出

安装依赖

删掉默认模板,贴上:

import {
  Connection, Keypair, SystemProgram, Transaction,
  clusterApiUrl, sendAndConfirmTransaction,
} from '@solana/web3.js'
import {
  ExtensionType, TOKEN_2022_PROGRAM_ID,
  createAccount, createInitializeMintInstruction,
  createInitializeTransferFeeConfigInstruction,
  getMintLen, mintTo, transferCheckedWithFee,
  withdrawWithheldTokensFromAccounts,
  harvestWithheldTokensToMint,
  withdrawWithheldTokensFromMint,
} from '@solana/spl-token'

const connection = new Connection(clusterApiUrl('devnet'), 'confirmed')
const payer = pg.wallet.keypair
let transactionSignature: string

初始化带 Transfer Fee 的 Mint

配置参数

参数含义示例值
decimals小数位2
feeBasisPoints万分比(100 = 1%)100(1%)
maxFee单笔最高手续费100(即 1 Token)
const mintKeypair = Keypair.generate()
const mint = mintKeypair.publicKey
const decimals = 2
const feeBasisPoints = 100          // 1%
const maxFee = BigInt(100)          // 1.00 Token

const transferFeeConfigAuthority = pg.wallet.keypair
const withdrawWithheldAuthority = pg.wallet.keypair

计算所需空间

const mintLen = getMintLen([ExtensionType.TransferFeeConfig])
const lamports  = await connection.getMinimumBalanceForRentExemption(mintLen)

发送交易:创建账户与铸造

组合三步指令

const ixCreate  = SystemProgram.createAccount({
  fromPubkey: payer.publicKey,
  newAccountPubkey: mint,
  space: mintLen,
  lamports,
  programId: TOKEN_2022_PROGRAM_ID,
})

const ixFee = createInitializeTransferFeeConfigInstruction(
  mint,                      // mint
  transferFeeConfigAuthority.publicKey, // fee更新者
  withdrawWithheldAuthority.publicKey,  // 费资金提取者
  feeBasisPoints,
  maxFee,
  TOKEN_2022_PROGRAM_ID,
)

const ixMint = createInitializeMintInstruction(
  mint, decimals,
  payer.publicKey, null,
  TOKEN_2022_PROGRAM_ID,
)

const tx = new Transaction().add(ixCreate, ixFee, ixMint)
transactionSignature = await sendAndConfirmTransaction(connection, tx, [payer, mintKeypair])
console.log('创建 Mint:🔍 https://solana.fm/tx/', transactionSignature + '?cluster=devnet-solana')

创建演示用的 Token Accounts

const sourceTokenAccount = await createAccount(connection, payer, mint, payer.publicKey, undefined, undefined, TOKEN_2022_PROGRAM_ID)

const randomKeypair = Keypair.generate()
const destTokenAccount = await createAccount(connection, payer, mint, randomKeypair.publicKey, undefined, undefined, TOKEN_2022_PROGRAM_ID)

// 铸造 2000.00 Tokens 至 sourceTokenAccount
transactionSignature = await mintTo(connection, payer, mint, sourceTokenAccount, payer.publicKey, 2000_00, undefined, undefined, TOKEN_2022_PROGRAM_ID)

转账并查看手续费

总转账额:1000 Tokens
计算手续费:

const transferAmount = BigInt(1000_00)
const fee = (transferAmount * BigInt(feeBasisPoints)) / BigInt(10_000) // 10 Tokens
const feeCharged = fee > maxFee ? maxFee : fee

发起转账:

transactionSignature = await transferCheckedWithFee(
  connection, payer, sourceTokenAccount, mint, destTokenAccount,
  payer.publicKey, transferAmount, decimals, feeCharged,
  undefined, undefined, TOKEN_2022_PROGRAM_ID
)
console.log('转账含手续费:🔍 https://solana.fm/tx/', transactionSignature + '?cluster=devnet-solana')

转账后:


提取手续费到任意地址

// Step1 拉取所有关联账户
const allAccounts = await connection.getProgramAccounts(TOKEN_2022_PROGRAM_ID, {
  commitment: 'confirmed',
  filters: [{ memcmp: { offset: 0, bytes: mint.toString() } }]
})

// Step2 过滤含手续费的
const accountsToWithdrawFrom = allAccounts
  .map((a) => ({ pubkey: a.pubkey, account: unpackAccount(a.pubkey, a.account, TOKEN_2022_PROGRAM_ID) }))
  .filter((u) => {
    const feeData = getTransferFeeAmount(u.account)
    return feeData ? feeData.withheldAmount > 0 : false
  })
  .map((u) => u.pubkey)

// Step3 提取到 destTokenAccount
transactionSignature = await withdrawWithheldTokensFromAccounts(
  connection, payer, mint, destTokenAccount, withdrawWithheldAuthority,
  undefined, accountsToWithdrawFrom, undefined, TOKEN_2022_PROGRAM_ID
)

收割手续费到 Mint 再统一支配

代币账户中含有 任意Token时不能直接关闭。若需关闭账户需先 harvest 质押的手续费到 Mint。

// 再转一次,制造手续费余额
await transferCheckedWithFee(/*同上*/)

// 糖果式调用,任何人都可收割
const harvestTxSig = await harvestWithheldTokensToMint(
  connection, payer, mint, [destTokenAccount], undefined, TOKEN_2022_PROGRAM_ID
)
console.log('收割 Fee 到 mint:🔍 https://solana.fm/tx/', harvestTxSig + '?cluster=devnet-solana')

// 统一提取到指定账户
await withdrawWithheldTokensFromMint(
  connection, payer, mint, destTokenAccount,
  withdrawWithheldAuthority, undefined, undefined, TOKEN_2022_PROGRAM_ID
)

常见问答

Q1:Transfer Fee 会不会成为整条链的性能瓶颈?
A:不会。手续费被锁定在每个接收账户,无需写同一个“手续费池”。消除了全局锁,依旧享受 Solana 并行化的速度与吞吐量。

Q2:我可以后期调整手续费率吗?
A:只要 transferFeeConfigAuthority 签名即可重新配置:feeBasisPoints、maxFee,全链即时生效。

Q3:能否收取别的 Token 作为手续费?
A:Transfer Fee 仅收取同一 Token。想收取 USDC 或 SOL 请改用 Transfer Hook extension

Q4:所有账户都能强制收割别人的手续费?
A:是的。harvestWithheldTokensToMint 是公开指令,任何用户都能帮指定账户清理 withheld 余额,维持生态整洁。

Q5:接收方能拿到手续费返利吗?
A:不能。手续费进入 withheld 区域,只能在 Withdraw Authority 签名后提取。接收方不拥有这些余额的任何操作权限。


总结与下一步

Transfer Fee extension 用 原生指令级 取代了额外合约的复杂逻辑,让手续费收集在任何转账场景下都变得顺其自然。
如果你是产品经理:可将手续费用于 DAO 资金库、游戏治理代币销毁、NFT 双币赋能 等场景。如果你是开发者:下一章可以继续了解 Transfer Hook 实现灵活的多 Token 手续费模式。

👉 立即查阅更多 Solana Token Extensions 实战示例,助力链上产品创新>