手数料の抽象化

すべてのSolanaトランザクションは、ネットワーク手数料を支払うためにSOLを必要とします。しかし、決済アプリケーションを利用するユーザーは、ステーブルコインで取引することを期待しており、2つ目のトークン残高を管理することは望んでいません。手数料の抽象化は、他の誰かが手数料を支払うことで、この摩擦を取り除きます。

このガイドでは、2つのレベルをカバーします。

  1. 手数料スポンサーシップの仕組み — 基盤となるSolanaのプリミティブ
  2. Koraによる大規模な手数料の抽象化 — 本番環境対応の手数料抽象化サービス

手数料スポンサーシップの仕組み

Solanaトランザクションには、指定された手数料支払者、つまりネットワーク手数料を支払うアカウントがあります。デフォルトでは、これは最初の署名者です。しかし、別のアカウントを手数料支払者として指定することができ、第三者(「スポンサー」)が送信者に代わって手数料を負担することが可能になります。

送信者とスポンサーの両方がトランザクションに署名する必要があります。

  • 送信者は、トークンの転送を承認するために署名します
  • スポンサーは、ネットワーク手数料の支払いを承認するために署名します

コア決済の概念については、Solanaでの決済の仕組みを参照してください。

以下の手順は、コアフローを示しています。完全な実行可能なコードについては、デモを参照してください。

スポンサーアカウントの作成

トランザクション手数料を支払うスポンサー用に、別のkeypairを生成します。スポンサーは手数料のためにSOLが必要ですが、転送されるトークンを保有する必要はありません。

転送命令の作成

送信者を権限者として、トークン転送命令を作成します。送信者はトークンを所有しており、転送に署名する必要があります。

スポンサーを手数料支払者として送信

スポンサーを手数料支払者として設定したクライアントを payer(sponsor) で作成し、その後 client.sendTransaction を呼び出します。スポンサーはネットワーク手数料を支払うために署名し、送信者は送金を承認するために署名します。sendTransaction は参照されたすべての署名者を自動的に収集します。

スポンサーアカウントの作成

トランザクション手数料を支払うスポンサー用に、別の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.

エンドユーザーのためにトークンアカウントを作成する場合、ユーザーはそれを閉じて、rentに使用されたSOLを取り戻すことができます。ステーブルコインでアカウント作成料金をユーザーに請求するか、この費用を製品の経済性に組み込むことを検討してください。

Koraによる大規模な手数料抽象化

手数料支払者プリミティブは強力ですが、本番環境のガスレスシステムを構築するには、スポンサーウォレットの管理、トークン変換の処理(ユーザーが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?

目次

ページを編集
© 2026 Solana Foundation. 無断転載を禁じます。