Транзакция Solana — это контейнер, который содержит одну или несколько инструкций. Каждая инструкция — это операция: перевод токенов, создание аккаунта, вызов программы. Сеть выполняет все инструкции в транзакции последовательно и атомарно: либо каждая инструкция выполняется успешно, либо вся транзакция откатывается и считается неудачной.
Это означает, что вы можете объединить несколько переводов в одну транзакцию. Вместо того чтобы отправлять три отдельные транзакции для оплаты трем получателям, вы отправляете одну транзакцию с тремя инструкциями перевода. Это быстрее (одно подтверждение вместо трёх) и дешевле (одна базовая комиссия вместо трёх). Вот наглядный пример того, как платежи (на изображении они называются «дропами») объединяются в одну транзакцию, а для обработки большого количества платежей отправляется несколько транзакций.
Диаграмма пакетных платежей
Источник: QuickNode — Как отправлять массовые транзакции в Solana
Больше информации о транзакциях и инструкциях вы найдете в разделах Транзакции и Инструкции.
В этом пошаговом руководстве показано, как загрузить несколько инструкций перевода в одну транзакцию для пакетных платежей.
Объединение инструкций в одну транзакцию
Транзакция Solana может содержать несколько переводов разным получателям. Вы подписываете один раз, платите одну комиссию за транзакцию, и все переводы выполняются одновременно. Если какой-либо перевод не удастся, вся транзакция будет отклонена.
См. раздел Как работают платежи в Solana для ознакомления с основными понятиями.
Для объединения нескольких переводов необходимо создать каждую инструкцию отдельно, а затем собрать их в одну транзакцию.
Далее приведены основные этапы процесса. Полный рабочий код смотрите в разделе Демо.
Получение адресов token account
Сначала получите адреса Associated Token Account (ATA) для отправителя и каждого получателя. ATA — это детерминированные адреса, основанные на кошельке и mint.
const [senderAta] = await findAssociatedTokenPda({mint: mint.address,owner: sender.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const [recipient1Ata] = await findAssociatedTokenPda({mint: mint.address,owner: recipient1.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const [recipient2Ata] = await findAssociatedTokenPda({mint: mint.address,owner: recipient2.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});
Создание инструкций для перевода
Создайте отдельную инструкцию перевода для каждого получателя. В каждой инструкции указываются:
- адрес исходного token account
- адрес целевого token account
- authority (адрес владельца исходного token account)
- сумма в базовых единицах (с учётом знаков после запятой у mint)
const [senderAta] = await findAssociatedTokenPda({mint: mint.address,owner: sender.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const [recipient1Ata] = await findAssociatedTokenPda({mint: mint.address,owner: recipient1.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const [recipient2Ata] = await findAssociatedTokenPda({mint: mint.address,owner: recipient2.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const transfer1Instruction = getTransferInstruction({source: senderAta,destination: recipient1Ata,authority: sender.address,amount: 250_000n});const transfer2Instruction = getTransferInstruction({source: senderAta,destination: recipient2Ata,authority: sender.address,amount: 250_000n});
Отправка одной транзакцией
Добавьте все инструкции перевода в одну транзакцию. Это позволит выполнить все переводы атомарно: либо все переводы проходят успешно, либо вся транзакция откатывается.
Проверка балансов
После пакетного перевода проверьте балансы токенов для всех участников с помощью
splToken хелпера.
const [senderAta] = await findAssociatedTokenPda({mint: mint.address,owner: sender.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const [recipient1Ata] = await findAssociatedTokenPda({mint: mint.address,owner: recipient1.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const [recipient2Ata] = await findAssociatedTokenPda({mint: mint.address,owner: recipient2.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const transfer1Instruction = getTransferInstruction({source: senderAta,destination: recipient1Ata,authority: sender.address,amount: 250_000n});const transfer2Instruction = getTransferInstruction({source: senderAta,destination: recipient2Ata,authority: sender.address,amount: 250_000n});const signature = await client.transaction.prepareAndSend({authority: sender,instructions: [transfer1Instruction, transfer2Instruction],version: 0});const splToken = client.splToken({mint: mint.address,tokenProgram: "auto"});const senderBalance = await splToken.fetchBalance(sender.address);const recipient1Balance = await splToken.fetchBalance(recipient1.address);const recipient2Balance = await splToken.fetchBalance(recipient2.address);
Демонстрация
// Generate keypairs for sender and two recipientsconst sender = (await generateKeypair()).signer;const recipient1 = (await generateKeypair()).signer;const recipient2 = (await generateKeypair()).signer;console.log("Sender Address:", sender.address);console.log("Recipient 1 Address:", recipient1.address);console.log("Recipient 2 Address:", recipient2.address);// Demo Setup: Create client, mint account, token accounts, and fund with initial tokensconst { client, mint } = await demoSetup(sender, recipient1, recipient2);console.log("\nMint Address:", mint.address);// Derive the Associated Token Accounts addresses (ATAs) for sender and recipientsconst [senderAta] = await findAssociatedTokenPda({mint: mint.address,owner: sender.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const [recipient1Ata] = await findAssociatedTokenPda({mint: mint.address,owner: recipient1.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const [recipient2Ata] = await findAssociatedTokenPda({mint: mint.address,owner: recipient2.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});console.log("Sender Token Account:", senderAta.toString());console.log("Recipient 1 Token Account:", recipient1Ata.toString());console.log("Recipient 2 Token Account:", recipient2Ata.toString());// =============================================================================// Batch Token Payment Demo// =============================================================================// Create instructions to transfer tokens from sender to both recipients// Transferring 250,000 base units = 0.25 tokens (with 6 decimals) to eachconst transfer1Instruction = getTransferInstruction({source: senderAta,destination: recipient1Ata,authority: sender.address,amount: 250_000n // 0.25 tokens});const transfer2Instruction = getTransferInstruction({source: senderAta,destination: recipient2Ata,authority: sender.address,amount: 250_000n // 0.25 tokens});// Prepare and send both transfers in a single transaction using @solana/clientconst signature = await client.transaction.prepareAndSend({authority: sender,instructions: [transfer1Instruction, transfer2Instruction],version: 0});console.log("\n=== Batch Token Payment Complete ===");console.log("Transaction Signature:", signature.toString());// Fetch final token account balances using @solana/client SPL token helperconst splToken = client.splToken({mint: mint.address,tokenProgram: "auto"});const senderBalance = await splToken.fetchBalance(sender.address);const recipient1Balance = await splToken.fetchBalance(recipient1.address);const recipient2Balance = await splToken.fetchBalance(recipient2.address);console.log("\nSender Token Account Balance:", senderBalance);console.log("Recipient 1 Token Account Balance:", recipient1Balance);console.log("Recipient 2 Token Account Balance:", recipient2Balance);// =============================================================================// Demo Setup Helper Function// =============================================================================
Масштабирование с помощью планирования транзакций
Одна транзакция имеет ограничения по размеру — примерно 1232 байта. Для крупных пакетных операций (например, начисление зарплаты сотням сотрудников, массовые airdrop) вы превысите этот лимит и потребуется разбивать работу на несколько транзакций.
Хотя вы можете реализовать собственную логику распределения транзакций, пакет
@solana/instruction-plans
(часть Solana Kit) решает эту задачу на двух уровнях:
Планы инструкций определяют ваши операции и ограничения по их порядку выполнения:
- Последовательные — инструкции, которые должны выполняться по порядку
- Параллельные — инструкции, которые могут выполняться в любом порядке
- Неделимые — инструкции, которые должны оставаться вместе в одной транзакции
Планы транзакций формируются на основе планов инструкций. Планировщик интеллектуально группирует инструкции в транзакции оптимального размера с учетом ваших ограничений по порядку. Полученный план транзакций может быть:
- Выполнен — подписан и отправлен в сеть, при этом параллельные транзакции отправляются одновременно
- Симулирован — тестовый запуск в сети для проверки перед отправкой
- Сериализован — скомпилирован в base64 для внешних сервисов подписи или мультипользовательских сценариев
Такой двухуровневый подход позволяет вам мыслить в терминах операций ("перевести Алисе, затем перевести Бобу"), а библиотека берет на себя механику определения размера транзакций, их упаковки и параллельного выполнения.
Is this page helpful?