支付高级支付

支出权限

Solana 的 Token Programs 支持委托功能——允许你授权其他账户在指定额度内,从你的 token account 转移 token。这使得自动化支付、托管服务和第三方支付处理等场景成为可能,同时无需放弃对资金的控制权。

委托的工作原理

当你批准一个代理人(delegate)时,就是授权某个特定账户可以代表你转移 token:

  • 所有权不变:你仍然拥有这些 token,并可随时转移或撤销权限
  • 支出额度受限:代理人只能在批准的额度内转移 token
  • 每个账户仅限一个代理:每个 token account 只能有一个活跃代理
  • 新授权覆盖旧授权:批准新代理会自动撤销之前的代理权限

委托是非托管的。代理人只能在额度范围内支出 token,无法访问或转移超出批准额度的资金。账户所有者可随时撤销权限。

商业应用场景

应用场景委托如何发挥作用
支付处理商商户授权处理商结算交易
自动化工资发放财务部门批准工资服务商发放薪资
托管服务买方将权限委托给托管代理人以实现有条件释放
交易平台用户授权交易所代表其执行交易
卡片发行用户授权发卡机构将消费记入其 token account

授权代理人

授权其他账户从你的账户中支出代币:

import { getApproveCheckedInstruction } from "@solana-program/token";
// Approve delegate to spend up to 1,000 USDC (6 decimals)
const approveInstruction = getApproveCheckedInstruction({
source: tokenAccountAddress, // Your token account
mint: usdcMintAddress, // USDC mint
delegate: delegateAddress, // Account receiving permission
owner: ownerKeypair, // You (must sign)
amount: 1_000_000_000n, // 1,000 USDC in base units
decimals: 6
});

参数:

  • source:授予权限的 token account
  • delegate:将获得支出权限的账户
  • owner:token account 当前所有者(必须签署交易)
  • amount:代理可转移的最大代币数量
  • decimals:用于校验的代币小数位数(防止小数错误)

演示

Approve Delegate
// Generate keypairs for sender and delegate
const sender = (await generateKeypair()).signer;
const delegate = (await generateKeypair()).signer;
console.log("Sender Address:", sender.address);
console.log("Delegate Address:", delegate.address);
// Demo Setup: Create client, mint account, token account, and fund with initial tokens
const { client, mint, senderAta } = await demoSetup(sender);
console.log("\nMint Address:", mint.address);
console.log("Sender ATA:", senderAta);
// =============================================================================
// Approve Delegate
// =============================================================================
// Create instruction to approve delegate
const approveInstruction = getApproveCheckedInstruction({
source: senderAta,
mint: mint.address,
delegate: delegate.address,
owner: sender,
amount: 1_000_000n, // 1.0 tokens with 6 decimals
decimals: 6
});
// Send approve transaction
const signature = await client.transaction.prepareAndSend({
authority: sender,
instructions: [approveInstruction]
});
console.log("\n=== Approve Delegate ===");
console.log("Transaction Signature:", signature);
// Fetch token account data to show delegate is set
const tokenData = await fetchToken(client.runtime.rpc, senderAta);
console.log("\nSender Token Account Data:", tokenData.data);
// =============================================================================
// Demo Setup Helper Function
// =============================================================================
Console
Click to execute the code.

撤销代理

移除当前代理的所有支出权限:

import { getRevokeInstruction } from "@solana-program/token";
const revokeInstruction = getRevokeInstruction({
source: tokenAccountAddress, // Your token account
owner: ownerKeypair // You (must sign)
});

撤销会移除所有代理权限——无法部分撤销。如果你需要降低额度,请用更低的金额重新授权同一个代理。

演示

Revoke Delegate
// Generate keypairs for sender and delegate
const sender = (await generateKeypair()).signer;
const delegate = (await generateKeypair()).signer;
console.log("Sender Address:", sender.address);
console.log("Delegate Address:", delegate.address);
// Demo Setup: Create client, mint account, token account, and fund with initial tokens
const { client, mint, senderAta } = await demoSetup(sender);
console.log("\nMint Address:", mint.address);
console.log("Sender ATA:", senderAta);
// =============================================================================
// Transaction 1: Approve Delegate
// =============================================================================
// Create instruction to approve delegate
const approveInstruction = getApproveCheckedInstruction({
source: senderAta,
mint: mint.address,
delegate: delegate.address,
owner: sender,
amount: 1_000_000n, // 1.0 tokens with 6 decimals
decimals: 6
});
// Send approve transaction
const approveSignature = await client.transaction.prepareAndSend({
authority: sender,
instructions: [approveInstruction]
});
console.log("\n=== Transaction 1: Approve Delegate ===");
console.log("Transaction Signature:", approveSignature);
// Fetch token account data to show delegate is set
const tokenDataAfterApprove = await fetchToken(client.runtime.rpc, senderAta);
console.log("\nSender Token Account Data:", tokenDataAfterApprove.data);
// =============================================================================
// Transaction 2: Revoke Delegate
// =============================================================================
// Create instruction to revoke delegate
const revokeInstruction = getRevokeInstruction({
source: senderAta,
owner: sender
});
// Send revoke transaction
const revokeSignature = await client.transaction.prepareAndSend({
authority: sender,
instructions: [revokeInstruction]
});
console.log("\n=== Transaction 2: Revoke Delegate ===");
console.log("Transaction Signature:", revokeSignature);
// Fetch token account data to show delegate is revoked
const tokenDataAfterRevoke = await fetchToken(client.runtime.rpc, senderAta);
console.log("\nSender Token Account Data:", tokenDataAfterRevoke.data);
// =============================================================================
// Demo Setup Helper Function
// =============================================================================
Console
Click to execute the code.

作为代理进行转账

作为代理操作时,使用标准转账,但需用代理的 keypair 签名,而不是所有者:

Transfer as Delegate
import { getTransferCheckedInstruction } from "@solana-program/token";
const transferInstruction = getTransferCheckedInstruction({
source: ownerTokenAccount, // The account you have permission to spend from
mint: usdcMintAddress,
destination: recipientTokenAccount,
authority: delegateKeypair, // You (the delegate) sign, not the owner
amount: 100_000_000n, // 100 USDC
decimals: 6
});

转账成功的条件:

  • 源账户余额充足
  • 代理签署了交易

每次转账都会减少剩余额度。当额度为零时,代理将无法再转移代币。

演示

Transfer as Delegate
// Generate keypairs for sender, delegate, and recipient
const sender = (await generateKeypair()).signer;
const delegate = (await generateKeypair()).signer;
const recipient = (await generateKeypair()).signer;
console.log("Sender Address:", sender.address);
console.log("Delegate Address:", delegate.address);
console.log("Recipient Address:", recipient.address);
// Demo Setup: Create client, mint account, token accounts, and fund with initial tokens
const { client, mint, senderAta, recipientAta } = await demoSetup(
sender,
delegate,
recipient
);
console.log("\nMint Address:", mint.address);
console.log("Sender ATA:", senderAta);
console.log("Recipient ATA:", recipientAta);
// =============================================================================
// Transaction 1: Approve Delegate
// =============================================================================
// Create instruction to approve delegate
const approveInstruction = getApproveCheckedInstruction({
source: senderAta,
mint: mint.address,
delegate: delegate.address,
owner: sender,
amount: 1_000_000n, // 1.0 tokens with 6 decimals
decimals: 6
});
// Send approve transaction
const approveSignature = await client.transaction.prepareAndSend({
authority: sender,
instructions: [approveInstruction]
});
console.log("\n=== Transaction 1: Approve Delegate ===");
console.log("Delegate Address:", delegate.address);
console.log("Transaction Signature:", approveSignature);
// =============================================================================
// Fetch Token Account Data to Demonstrate Delegate is Set
// =============================================================================
const tokenAccountData = await fetchToken(client.runtime.rpc, senderAta);
console.log("\nSender Token Account Data:", tokenAccountData.data);
// =============================================================================
// Transaction 2: Transfer Using Delegate
// =============================================================================
// Create instruction to transfer tokens using delegate
// Note: delegate is the authority here, not the owner
const transferInstruction = getTransferCheckedInstruction({
source: senderAta,
mint: mint.address,
destination: recipientAta,
authority: delegate, // Delegate signs this transaction
amount: 500_000n, // 0.5 tokens with 6 decimals
decimals: 6
});
// Send transfer transaction
// Delegate pays for the transaction and authorizes the transfer (sender not needed)
const transferSignature = await client.transaction.prepareAndSend({
authority: delegate, // Delegate pays fee and signs
instructions: [transferInstruction]
});
// =============================================================================
// Fetch Final Token Account Balances
// =============================================================================
const finalSenderToken = await fetchToken(client.runtime.rpc, senderAta);
const finalRecipientToken = await fetchToken(client.runtime.rpc, recipientAta);
console.log("\n=== Transaction 2: Transfer Using Delegate ===");
console.log("Transaction Signature:", transferSignature);
console.log("\nSender Token Account Data:", finalSenderToken.data);
console.log("\nRecipient Token Account Data:", finalRecipientToken.data);
// =============================================================================
// Demo Setup Helper Function
// =============================================================================
Console
Click to execute the code.

检查代理状态

查询 token account 以查看其当前代理及剩余额度:

import { fetchToken } from "@solana-program/token";
const tokenAccount = await fetchToken(rpc, tokenAccountAddress);
if (tokenAccount.data.delegate) {
console.log("Delegate:", tokenAccount.data.delegate);
console.log("Remaining allowance:", tokenAccount.data.delegatedAmount);
} else {
console.log("No delegate set");
}

演示

Check Delegation Status
// Demo Setup: Create client, mint, two token accounts (one with delegate, one without)
const { client, ataWithDelegate, ataWithoutDelegate } = await demoSetup();
// =============================================================================
// Fetch Token Accounts
// =============================================================================
// Fetch token account with delegate
const tokenWithDelegate = await fetchToken(client.runtime.rpc, ataWithDelegate);
console.log("Token Account with Delegate:", tokenWithDelegate);
// Fetch token account without delegate
const tokenWithoutDelegate = await fetchToken(
client.runtime.rpc,
ataWithoutDelegate
);
console.log("\nToken Account without Delegate:", tokenWithoutDelegate);
// =============================================================================
// Demo Setup Helper Function
// =============================================================================
Console
Click to execute the code.

安全注意事项

账户所有者须知:

  • 仅批准可信的代理
  • 设置所需的最低支出限额
  • 不再需要时及时撤销代理权限
  • 监控账户,防止异常转账

服务提供方(代理)须知:

  • 向用户明确告知所请求的支出限额
  • 对代理账户进行妥善的密钥管理
  • 跟踪额度消耗情况,额度用尽前及时申请重新授权

代理与托管的区别

方面代理授权完全托管
Token 所有权用户保留用户转移给托管方
支出控制限于批准额度可支配全部已转移资金
撤销权限所有者可即时撤销需托管方配合
风险敞口限于批准额度全部余额
信任要求有限

代理授权提供了一种折中方式——既能实现自动化支付,又能将风险敞口限制在批准额度内。

相关资源

资源说明
Approve Delegate如何授权其他账户支配你的 token account。
Revoke Delegate如何移除现有代理并撤销其支出权限。
Transfer Token如何在 token account 之间转账。

Is this page helpful?

Table of Contents

Edit Page

管理者

©️ 2026 Solana 基金会版权所有
取得联系