支付高级支付
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 accountmint: usdcMintAddress, // USDC mintdelegate: delegateAddress, // Account receiving permissionowner: ownerKeypair, // You (must sign)amount: 1_000_000_000n, // 1,000 USDC in base unitsdecimals: 6});
参数:
source:授予权限的 token accountdelegate:将获得支出权限的账户owner:token account 当前所有者(必须签署交易)amount:代理可转移的最大代币数量decimals:用于校验的代币小数位数(防止小数错误)
演示
Approve Delegate
// Generate keypairs for sender and delegateconst 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 tokensconst { client, mint, senderAta } = await demoSetup(sender);console.log("\nMint Address:", mint.address);console.log("Sender ATA:", senderAta);// =============================================================================// Approve Delegate// =============================================================================// Create instruction to approve delegateconst approveInstruction = getApproveCheckedInstruction({source: senderAta,mint: mint.address,delegate: delegate.address,owner: sender,amount: 1_000_000n, // 1.0 tokens with 6 decimalsdecimals: 6});// Send approve transactionconst 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 setconst 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 accountowner: ownerKeypair // You (must sign)});
撤销会移除所有代理权限——无法部分撤销。如果你需要降低额度,请用更低的金额重新授权同一个代理。
演示
Revoke Delegate
// Generate keypairs for sender and delegateconst 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 tokensconst { 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 delegateconst approveInstruction = getApproveCheckedInstruction({source: senderAta,mint: mint.address,delegate: delegate.address,owner: sender,amount: 1_000_000n, // 1.0 tokens with 6 decimalsdecimals: 6});// Send approve transactionconst 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 setconst tokenDataAfterApprove = await fetchToken(client.runtime.rpc, senderAta);console.log("\nSender Token Account Data:", tokenDataAfterApprove.data);// =============================================================================// Transaction 2: Revoke Delegate// =============================================================================// Create instruction to revoke delegateconst revokeInstruction = getRevokeInstruction({source: senderAta,owner: sender});// Send revoke transactionconst 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 revokedconst 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 frommint: usdcMintAddress,destination: recipientTokenAccount,authority: delegateKeypair, // You (the delegate) sign, not the owneramount: 100_000_000n, // 100 USDCdecimals: 6});
转账成功的条件:
- 源账户余额充足
- 代理签署了交易
每次转账都会减少剩余额度。当额度为零时,代理将无法再转移代币。
演示
Transfer as Delegate
// Generate keypairs for sender, delegate, and recipientconst 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 tokensconst { 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 delegateconst approveInstruction = getApproveCheckedInstruction({source: senderAta,mint: mint.address,delegate: delegate.address,owner: sender,amount: 1_000_000n, // 1.0 tokens with 6 decimalsdecimals: 6});// Send approve transactionconst 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 ownerconst transferInstruction = getTransferCheckedInstruction({source: senderAta,mint: mint.address,destination: recipientAta,authority: delegate, // Delegate signs this transactionamount: 500_000n, // 0.5 tokens with 6 decimalsdecimals: 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 signsinstructions: [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 delegateconst tokenWithDelegate = await fetchToken(client.runtime.rpc, ataWithDelegate);console.log("Token Account with Delegate:", tokenWithDelegate);// Fetch token account without delegateconst 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?