Токен-программы Solana поддерживают делегирование — предоставление другому аккаунту разрешения переводить токены с вашего token account в пределах установленного лимита. Это позволяет реализовать такие сценарии, как автоматические платежи, эскроу-сервисы и обработку платежей третьими лицами без потери контроля над вашими средствами.
Как работает делегирование
Когда вы одобряете делегата, вы разрешаете определённому аккаунту переводить токены от вашего имени:
- Владелец сохраняет контроль: Вы по-прежнему владеете токенами и можете перевести их или отозвать разрешение в любой момент
- Ограничение по сумме: Делегат может переводить только сумму в пределах утверждённого лимита
- Один делегат на аккаунт: Каждый token account может иметь только одного активного делегата
- Новое разрешение заменяет старое: Одобрение нового делегата автоматически отзывает предыдущего
Делегирование не является кастодиальным. Делегат может расходовать токены только в пределах лимита, но не может получить доступ или вывести средства сверх утверждённой суммы. Владелец может отозвать разрешение в любой момент.
Бизнес-кейсы
| Сценарий использования | Как помогает делегирование |
|---|---|
| Платёжные процессоры | Мерчант предоставляет процессору разрешение на проведение расчётов |
| Автоматическая зарплата | Казначейство одобряет сервис для выплаты зарплаты |
| Эскроу-сервисы | Покупатель делегирует агенту эскроу условный перевод |
| Торговые платформы | Пользователь одобряет бирже выполнение сделок от его имени |
| Выпуск карт | Пользователь разрешает эмитенту карты списывать покупки с его 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 account, который предоставляет разрешениеdelegate: Аккаунт, который получит право тратить токеныowner: Текущий владелец token account (должен подписать транзакцию)amount: Максимальное количество токенов, которое делегат может перевестиdecimals: Десятичные знаки токена для проверки (предотвращает ошибки округления)
Демонстрация
// 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// =============================================================================
Отзыв делегата
Удалите все права на расходование у текущего делегата:
import { getRevokeInstruction } from "@solana-program/token";const revokeInstruction = getRevokeInstruction({source: tokenAccountAddress, // Your token accountowner: ownerKeypair // You (must sign)});
Отзыв удаляет все права делегата — частичного отзыва не существует. Если нужно уменьшить лимит, одобрите того же делегата с меньшей суммой.
Демонстрация
// 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// =============================================================================
Перевод от имени делегата
Действуя как делегат, используйте стандартный перевод, но подпишите его keypair делегата вместо владельца:
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});
Перевод будет успешным, если:
- На исходном аккаунте достаточно средств
- Делегат подписал транзакцию
Каждый перевод уменьшает оставшийся лимит. Когда лимит достигнет нуля, делегат больше не сможет переводить токены.
Демонстрация
// 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// =============================================================================
Проверка статуса делегирования
Запросите 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");}
Демонстрация
// 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// =============================================================================
Вопросы безопасности
Для владельцев аккаунтов:
- Одобряйте только доверенных делегатов
- Устанавливайте минимально необходимый лимит расходов
- Отзывайте делегирование, когда оно больше не требуется
- Следите за своими аккаунтами на предмет неожиданных переводов
Для сервис-провайдеров (делегатов):
- Четко сообщайте пользователям запрашиваемый лимит расходов
- Обеспечьте правильное управление ключами для вашего аккаунта делегата
- Отслеживайте расход лимита, чтобы запросить повторное одобрение до его исчерпания
Делегирование против кастодиального хранения
| Аспект | Делегирование | Полное кастодиальное хранение |
|---|---|---|
| Владение токенами | Пользователь сохраняет | Пользователь передает кастодиану |
| Контроль расходов | Ограничен одобренной суммой | Полный доступ к переведённым средствам |
| Отзыв | Мгновенно, владельцем | Требует участия кастодиана |
| Риск | Только в пределах лимита | На всю сумму баланса |
| Необходимое доверие | Минимальное | Высокое |
Делегирование — это компромисс: оно позволяет автоматизировать платежи, ограничивая риск только одобренной суммой.
Связанные ресурсы
| Ресурс | Описание |
|---|---|
| Approve Delegate | Как предоставить другому аккаунту разрешение тратить средства с вашего token account. |
| Revoke Delegate | Как удалить существующего делегата и отозвать его права на траты. |
| Transfer Token | Как переводить токены между token account. |
Is this page helpful?