Пакетные платежи

Транзакция Solana — это контейнер, который содержит одну или несколько инструкций. Каждая инструкция — это операция: перевод токенов, создание аккаунта, вызов программы. Сеть выполняет все инструкции в транзакции последовательно и атомарно: либо каждая инструкция выполняется успешно, либо вся транзакция откатывается и считается неудачной.

Это означает, что вы можете объединить несколько переводов в одну транзакцию. Вместо того чтобы отправлять три отдельные транзакции для оплаты трем получателям, вы отправляете одну транзакцию с тремя инструкциями перевода. Это быстрее (одно подтверждение вместо трёх) и дешевле (одна базовая комиссия вместо трёх). Вот наглядный пример того, как платежи (на изображении они называются «дропами») объединяются в одну транзакцию, а для обработки большого количества платежей отправляется несколько транзакций.

Диаграмма пакетных платежейДиаграмма пакетных платежей

Источник: QuickNode — Как отправлять массовые транзакции в Solana

Больше информации о транзакциях и инструкциях вы найдете в разделах Транзакции и Инструкции.

В этом пошаговом руководстве показано, как загрузить несколько инструкций перевода в одну транзакцию для пакетных платежей.

Объединение инструкций в одну транзакцию

Транзакция Solana может содержать несколько переводов разным получателям. Вы подписываете один раз, платите одну комиссию за транзакцию, и все переводы выполняются одновременно. Если какой-либо перевод не удастся, вся транзакция будет отклонена.

См. раздел Как работают платежи в Solana для ознакомления с основными понятиями.

Для объединения нескольких переводов необходимо создать каждую инструкцию отдельно, а затем собрать их в одну транзакцию.

Далее приведены основные этапы процесса. Полный рабочий код смотрите в разделе Демо.

Получение адресов token account

Сначала получите адреса Associated Token Account (ATA) для отправителя и каждого получателя. ATA — это детерминированные адреса, основанные на кошельке и mint.

Batch Payments
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)
Batch Payments
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
});

Отправка одной транзакцией

Добавьте все инструкции перевода в одну транзакцию. Это позволит выполнить все переводы атомарно: либо все переводы проходят успешно, либо вся транзакция откатывается.

Проверка балансов

После пакетного перевода проверьте балансы токенов для всех сторон, получив каждый token account через RPC-соединение клиента.

Batch Payments
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 result = await client.sendTransaction([
transfer1Instruction,
transfer2Instruction
]);
const senderBalance = await client.rpc.getTokenAccountBalance(senderAta).send();
const recipient1Balance = await client.rpc
.getTokenAccountBalance(recipient1Ata)
.send();
const recipient2Balance = await client.rpc
.getTokenAccountBalance(recipient2Ata)
.send();

Получение адресов token account

Сначала получите адреса Associated Token Account (ATA) для отправителя и каждого получателя. ATA — это детерминированные адреса, основанные на кошельке и mint.

Создание инструкций для перевода

Создайте отдельную инструкцию перевода для каждого получателя. В каждой инструкции указываются:

  • адрес исходного token account
  • адрес целевого token account
  • authority (адрес владельца исходного token account)
  • сумма в базовых единицах (с учётом знаков после запятой у mint)

Отправка одной транзакцией

Добавьте все инструкции перевода в одну транзакцию. Это позволит выполнить все переводы атомарно: либо все переводы проходят успешно, либо вся транзакция откатывается.

Проверка балансов

После пакетного перевода проверьте балансы токенов для всех сторон, получив каждый token account через RPC-соединение клиента.

Batch Payments
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
});

Демонстрация

Demo
// Generate keypairs for sender and two recipients
const sender = await generateKeyPairSigner();
const recipient1 = await generateKeyPairSigner();
const recipient2 = await generateKeyPairSigner();
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 tokens
const { client, mint } = await demoSetup(sender, recipient1, recipient2);
console.log("\nMint Address:", mint.address);
// Derive the Associated Token Accounts addresses (ATAs) for sender and recipients
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
});
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 each
const 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
});
// Send both transfers in a single transaction using @solana/kit
const result = await client.sendTransaction([
transfer1Instruction,
transfer2Instruction
]);
console.log("\n=== Batch Token Payment Complete ===");
console.log("Transaction Signature:", result.context.signature);
// Fetch final token account balances using the client's RPC connection
const senderBalance = await client.rpc.getTokenAccountBalance(senderAta).send();
const recipient1Balance = await client.rpc
.getTokenAccountBalance(recipient1Ata)
.send();
const recipient2Balance = await client.rpc
.getTokenAccountBalance(recipient2Ata)
.send();
console.log(
"\nSender Token Account Balance:",
senderBalance.value.uiAmountString
);
console.log(
"Recipient 1 Token Account Balance:",
recipient1Balance.value.uiAmountString
);
console.log(
"Recipient 2 Token Account Balance:",
recipient2Balance.value.uiAmountString
);
// =============================================================================
// Demo Setup Helper Function
// =============================================================================
Console
Click to execute the code.

Масштабирование с помощью планирования транзакций

Одна транзакция имеет ограничения по размеру — примерно 1232 байта. Для крупных пакетных операций (например, начисление зарплаты сотням сотрудников, массовые airdrop) вы превысите этот лимит и потребуется разбивать работу на несколько транзакций.

Хотя вы можете реализовать собственную логику распределения транзакций, пакет @solana/instruction-plans (часть Solana Kit) решает эту задачу на двух уровнях:

Планы инструкций определяют ваши операции и ограничения по их порядку выполнения:

  • Последовательные — инструкции, которые должны выполняться по порядку
  • Параллельные — инструкции, которые могут выполняться в любом порядке
  • Неделимые — инструкции, которые должны оставаться вместе в одной транзакции

Планы транзакций формируются на основе планов инструкций. Планировщик интеллектуально группирует инструкции в транзакции оптимального размера с учетом ваших ограничений по порядку. Полученный план транзакций может быть:

  • Выполнен — подписан и отправлен в сеть, при этом параллельные транзакции отправляются одновременно
  • Симулирован — тестовый запуск в сети для проверки перед отправкой
  • Сериализован — скомпилирован в base64 для внешних сервисов подписи или мультипользовательских сценариев

Такой двухуровневый подход позволяет вам мыслить в терминах операций ("перевести Алисе, затем перевести Бобу"), а библиотека берет на себя механику определения размера транзакций, их упаковки и параллельного выполнения.

Is this page helpful?

Содержание

Редактировать страницу