Абстракция комиссий

Каждая транзакция в Solana требует SOL для оплаты сетевых комиссий. Но пользователи, приходящие в ваше платёжное приложение, ожидают работать со стейблкоинами, а не управлять балансом второго токена. Абстракция комиссий устраняет это неудобство, позволяя другому участнику оплачивать комиссии.

В этом руководстве рассматриваются два уровня:

  1. Как работает спонсорство комиссий — базовый примитив Solana
  2. Абстракция комиссий в масштабе с Kora — готовый к использованию сервис абстракции комиссий

Как работает спонсорство комиссий

В транзакциях Solana есть назначенный плательщик комиссии — аккаунт, который оплачивает сетевую комиссию. По умолчанию это первый подписант. Но вы можете указать другой аккаунт в качестве плательщика комиссии, что позволяет третьей стороне ("спонсору") покрывать комиссии за отправителя.

И отправитель, и спонсор должны подписать транзакцию:

  • Отправитель подписывает для авторизации перевода своих токенов
  • Спонсор подписывает для подтверждения оплаты сетевой комиссии

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

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

Создайте аккаунт спонсора

Сгенерируйте отдельный keypair для спонсора, который будет оплачивать комиссии за транзакции. Спонсору нужен SOL для оплаты комиссий, но не обязательно иметь токены, которые переводятся.

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

Создайте инструкцию перевода токенов с отправителем в роли владельца. Отправитель владеет токенами и должен подписать перевод.

Sponsor Transaction Fee
const sponsor = await generateKeyPairSigner();
const transferInstruction = getTransferCheckedInstruction({
source: senderAta,
mint: mint.address,
destination: recipientAta,
authority: sender, // Sender signs for the transfer
amount: 250_000n, // adjusted for the mint's decimals
decimals: 6
});

Отправка с использованием спонсора как плательщика комиссии

Создайте клиент со спонсором в качестве плательщика комиссии через payer(sponsor), затем вызовите client.sendTransaction. Спонсор подписывает транзакцию для оплаты сетевой комиссии, а отправитель подписывает для авторизации перевода — sendTransaction автоматически собирает всех упомянутых подписантов.

Sponsor Transaction Fee
const sponsor = await generateKeyPairSigner();
const transferInstruction = getTransferCheckedInstruction({
source: senderAta,
mint: mint.address,
destination: recipientAta,
authority: sender,
amount: 250_000n,
decimals: 6
});
const client = createClient()
.use(payer(sponsor)) // Sponsor pays the transaction fees
.use(solanaLocalRpc());
const { context } = await client.sendTransaction([transferInstruction]);

Создайте аккаунт спонсора

Сгенерируйте отдельный keypair для спонсора, который будет оплачивать комиссии за транзакции. Спонсору нужен SOL для оплаты комиссий, но не обязательно иметь токены, которые переводятся.

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

Создайте инструкцию перевода токенов с отправителем в роли владельца. Отправитель владеет токенами и должен подписать перевод.

Отправка с использованием спонсора как плательщика комиссии

Создайте клиент со спонсором в качестве плательщика комиссии через payer(sponsor), затем вызовите client.sendTransaction. Спонсор подписывает транзакцию для оплаты сетевой комиссии, а отправитель подписывает для авторизации перевода — sendTransaction автоматически собирает всех упомянутых подписантов.

Sponsor Transaction Fee
const sponsor = await generateKeyPairSigner();

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

Demo
// Generate keypairs for sender, recipient, and sponsor (fee payer)
const sender = await generateKeyPairSigner();
const recipient = await generateKeyPairSigner();
const sponsor = await generateKeyPairSigner();
console.log("Sender Address:", sender.address);
console.log("Recipient Address:", recipient.address);
console.log("Sponsor Address (Fee Payer):", sponsor.address);
// Demo Setup: Create client, mint account, token accounts, and fund with initial tokens
const { client, mint } = await demoSetup(sender, recipient, sponsor);
console.log("\nMint Address:", mint.address);
// Derive the Associated Token Accounts addresses (ATAs) for sender and recipient
const [senderAta] = await findAssociatedTokenPda({
mint: mint.address,
owner: sender.address,
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
});
const [recipientAta] = await findAssociatedTokenPda({
mint: mint.address,
owner: recipient.address,
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
});
console.log("Sender Token Account:", senderAta.toString());
console.log("Recipient Token Account:", recipientAta.toString());
// =============================================================================
// Sponsored Token Payment Demo
// =============================================================================
// Create instruction to transfer tokens from sender to recipient
// Transferring 250,000 base units = 0.25 tokens (with 6 decimals)
const transferInstruction = getTransferCheckedInstruction({
source: senderAta,
mint: mint.address,
destination: recipientAta,
authority: sender, // Sender signs to authorize the transfer
amount: 250_000n, // 0.25 tokens
decimals: 6
});
// Send transaction with sponsor as fee payer using @solana/kit
// The sponsor (client.payer) pays fees; the sender signs for the transfer
const { context } = await client.sendTransaction([transferInstruction]);
const signature = context.signature;
console.log("\n=== Sponsored Token Payment Complete ===");
console.log("Transaction Signature:", signature.toString());
// Fetch final token account balances from the RPC
const senderBalance = await client.rpc.getTokenAccountBalance(senderAta).send();
const recipientBalance = await client.rpc
.getTokenAccountBalance(recipientAta)
.send();
console.log("\nSender Token Account Balance:", senderBalance.value.uiAmount);
console.log(
"Recipient Token Account Balance:",
recipientBalance.value.uiAmount
);
// Fetch transaction details
const transaction = await client.rpc
.getTransaction(signature, {
encoding: "jsonParsed",
maxSupportedTransactionVersion: 0
})
.send();
const feePayer = transaction?.transaction.message.accountKeys?.[0];
console.log("\nNote: The first account in accountKeys is always the fee payer");
console.log("Fee Payer Address:", feePayer);
// =============================================================================
// Demo Setup Helper Function
// =============================================================================
Console
Click to execute the code.

Когда вы создаёте токен аккаунт для конечного пользователя, он может закрыть его и вернуть SOL, использованный для rent. Рассмотрите возможность взимать плату с пользователей за создание аккаунта в стейблкоинах или заложите эту стоимость в экономику вашего продукта.

Абстракция комиссии в масштабе с Kora

Примитив fee payer мощный, но для построения полноценной production-системы без газа нужно больше: управление кошельками спонсоров, обработка конвертации токенов (чтобы пользователи могли "оплачивать" комиссии в USDC), лимитирование и контроль безопасности.

Kora решает эту проблему. Это JSON-RPC сервер, который предоставляет абстракцию комиссий, чтобы пользователям никогда не требовался SOL. Вы можете полностью спонсировать комиссии или принимать оплату комиссий в любом токене.

Разверните Kora одной командой:

cargo install kora-cli
kora --config path/to/kora.toml rpc start --signers-config path/to/signers.toml

Затем подключите клиент Kit к вашему серверу Kora и отправляйте транзакции с помощью того же API, что используется выше:

pnpm add @solana/kora
import { address } from "@solana/kit";
import { createKitKoraClient } from "@solana/kora";
// A Kora-backed Kit client: Kora is the fee payer, and users pay
// fees in the SPL token of your choice instead of SOL
const kora = await createKitKoraClient({
endpoint: "https://your-kora-instance",
rpcUrl: "https://api.mainnet-beta.solana.com",
feeToken: address("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"), // USDC
feePayerWallet: sender // Signs to authorize the token fee payment
});
// Same instruction, same call—Kora sponsors the network fee
const { context } = await kora.sendTransaction([transferInstruction]);
console.log("Signature:", context.signature);

Ресурсы по Kora

Is this page helpful?

Содержание

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