支払い高度な支払い

遅延実行

すべてのSolanaトランザクションには、最近のブロックハッシュが含まれています。これは、トランザクションが「今」作成されたことを証明する最近のネットワーク状態への参照です。ネットワークは、約150ブロック(約60〜90秒)より古いブロックハッシュを持つトランザクションを拒否し、リプレイ攻撃や古い送信を防ぎます。これはリアルタイム決済には完璧に機能します。しかし、署名と送信の間にギャップが必要なワークフローでは機能しません。例えば:

シナリオ標準トランザクションが失敗する理由
財務運用東京のCFOが署名し、ニューヨークの経理担当者が承認—90秒では不十分
コンプライアンスワークフロートランザクションは実行前に法務/コンプライアンスレビューが必要
コールドストレージ署名エアギャップマシンは署名済みトランザクションの手動転送が必要
バッチ準備営業時間中に給与や支払いを準備し、夜間に実行
マルチシグ調整タイムゾーンをまたぐ複数の承認者
スケジュール支払い将来の日付に実行される支払いをスケジュール

従来の金融では、署名された小切手は90秒で期限切れになりません。特定のブロックチェーン操作も同様であるべきです。永続的ノンスは、最近のブロックハッシュを保存された永続的な値に置き換えることでこれを解決します。この値は使用時にのみ進み、送信準備が整うまでトランザクションを有効に保ちます。

仕組み

最近のブロックハッシュ(約150ブロック有効)の代わりに、nonceアカウントを使用します。これは、ブロックハッシュの代わりに使用できる一意の値を保存する特別なアカウントです。このnonceを使用する各トランザクションは、最初のinstructionとしてそれを「進める」必要があります。各nonce値は1つのトランザクションにのみ使用できます。

Durable Nonce
Standard Blockhash

nonceアカウントには、rent免除のために約0.0015 SOLが必要です。1つのnonceアカウント=一度に1つの保留中トランザクション。並列ワークフローの場合は、複数のnonceアカウントを作成してください。

nonceアカウントの作成

nonceアカウントの作成には、1つのトランザクション内で2つのinstructionsが必要です:

  1. System ProgramのgetCreateAccountInstructionを使用してアカウントを作成
  2. getInitializeNonceAccountInstructionを使用してnonceとして初期化

keypairの生成

nonceアカウントアドレスとして使用する新しいkeypairを生成し、必要なスペースとrentを計算します。

Create Nonce Account
const nonceKeypair = await generateKeyPairSigner();
const nonceSpace = BigInt(getNonceSize());
const nonceRent = await rpc
.getMinimumBalanceForRentExemption(nonceSpace)
.send();

アカウント作成instruction

rent免除に十分なlamportsを持つ、System Programが所有するアカウントを作成します。

Create Nonce Account
const nonceKeypair = await generateKeyPairSigner();
const nonceSpace = BigInt(getNonceSize());
const nonceRent = await rpc
.getMinimumBalanceForRentExemption(nonceSpace)
.send();
const createNonceAccountIx = getCreateAccountInstruction({
payer: sender,
newAccount: nonceKeypair,
lamports: nonceRent,
space: nonceSpace,
programAddress: SYSTEM_PROGRAM_ADDRESS
});

nonce初期化instruction

アカウントをnonceアカウントとして初期化し、それを進めることができる権限を設定します。

Create Nonce Account
const nonceKeypair = await generateKeyPairSigner();
const nonceSpace = BigInt(getNonceSize());
const nonceRent = await rpc
.getMinimumBalanceForRentExemption(nonceSpace)
.send();
const createNonceAccountIx = getCreateAccountInstruction({
payer: sender,
newAccount: nonceKeypair,
lamports: nonceRent,
space: nonceSpace,
programAddress: SYSTEM_PROGRAM_ADDRESS
});
const initNonceIx = getInitializeNonceAccountInstruction({
nonceAccount: nonceKeypair.address,
nonceAuthority: sender.address
});

トランザクションの構築

両方のinstructionsを含むトランザクションを構築します。

Create Nonce Account
const nonceKeypair = await generateKeyPairSigner();
const nonceSpace = BigInt(getNonceSize());
const nonceRent = await rpc
.getMinimumBalanceForRentExemption(nonceSpace)
.send();
const createNonceAccountIx = getCreateAccountInstruction({
payer: sender,
newAccount: nonceKeypair,
lamports: nonceRent,
space: nonceSpace,
programAddress: SYSTEM_PROGRAM_ADDRESS
});
const initNonceIx = getInitializeNonceAccountInstruction({
nonceAccount: nonceKeypair.address,
nonceAuthority: sender.address
});
const { value: blockhash } = await rpc.getLatestBlockhash().send();
const createNonceTx = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(sender, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),
(tx) =>
appendTransactionMessageInstructions(
[createNonceAccountIx, initNonceIx],
tx
)
);

署名と送信

nonceアカウントを作成して初期化するトランザクションに署名して送信します。

Create Nonce Account
const nonceKeypair = await generateKeyPairSigner();
const nonceSpace = BigInt(getNonceSize());
const nonceRent = await rpc
.getMinimumBalanceForRentExemption(nonceSpace)
.send();
const createNonceAccountIx = getCreateAccountInstruction({
payer: sender,
newAccount: nonceKeypair,
lamports: nonceRent,
space: nonceSpace,
programAddress: SYSTEM_PROGRAM_ADDRESS
});
const initNonceIx = getInitializeNonceAccountInstruction({
nonceAccount: nonceKeypair.address,
nonceAuthority: sender.address
});
const { value: blockhash } = await rpc.getLatestBlockhash().send();
const createNonceTx = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(sender, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),
(tx) =>
appendTransactionMessageInstructions(
[createNonceAccountIx, initNonceIx],
tx
)
);
const signedCreateNonceTx =
await signTransactionMessageWithSigners(createNonceTx);
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(
signedCreateNonceTx,
{ commitment: "confirmed" }
);

keypairの生成

nonceアカウントアドレスとして使用する新しいkeypairを生成し、必要なスペースとrentを計算します。

アカウント作成instruction

rent免除に十分なlamportsを持つ、System Programが所有するアカウントを作成します。

nonce初期化instruction

アカウントをnonceアカウントとして初期化し、それを進めることができる権限を設定します。

トランザクションの構築

両方のinstructionsを含むトランザクションを構築します。

署名と送信

nonceアカウントを作成して初期化するトランザクションに署名して送信します。

Create Nonce Account
const nonceKeypair = await generateKeyPairSigner();
const nonceSpace = BigInt(getNonceSize());
const nonceRent = await rpc
.getMinimumBalanceForRentExemption(nonceSpace)
.send();

遅延トランザクションの構築

最近のブロックハッシュの代わりに、nonceアカウントのblockhashをトランザクションの有効期間として使用します。

nonceの取得

nonceアカウントからデータを取得します。nonceアカウントのblockhashをトランザクションの有効期間として使用します。

Example Nonce Account Data
{
version: 1,
state: 1,
authority: 'HgjaL8artMtmntaQDVM2UBk3gppsYYERS4PkUhiaLZD1',
blockhash: '5U7seXqfgZx1uh5DFhdH1vyBhr7XGRrKxBAnJJTbbUa',
lamportsPerSignature: 5000n
}
Build Deferred Transaction
const { data: nonceData } = await fetchNonce(rpc, nonceKeypair.address);

転送instructionの作成

支払いのためのinstructionを作成します。この例ではトークン転送を示しています。

Build Deferred Transaction
const { data: nonceData } = await fetchNonce(rpc, nonceKeypair.address);
const transferInstruction = getTransferInstruction({
source: senderAta,
destination: recipientAta,
authority: sender.address,
amount: 250_000n
});

durable nonceを使用したトランザクションの構築

setTransactionMessageLifetimeUsingDurableNonceを使用します。これによりnonceがブロックハッシュとして設定され、advance nonce instructionが自動的に先頭に追加されます。

Build Deferred Transaction
const { data: nonceData } = await fetchNonce(rpc, nonceKeypair.address);
const transferInstruction = getTransferInstruction({
source: senderAta,
destination: recipientAta,
authority: sender.address,
amount: 250_000n
});
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(sender, tx),
(tx) =>
setTransactionMessageLifetimeUsingDurableNonce(
{
nonce: nonceData.blockhash as Nonce,
nonceAccountAddress: nonceKeypair.address,
nonceAuthorityAddress: nonceData.authority
},
tx
),
(tx) => appendTransactionMessageInstructions([transferInstruction], tx)
);

トランザクションへの署名

トランザクションに署名します。これで標準のブロックハッシュの代わりにdurable nonceが使用されます。

Build Deferred Transaction
const { data: nonceData } = await fetchNonce(rpc, nonceKeypair.address);
const transferInstruction = getTransferInstruction({
source: senderAta,
destination: recipientAta,
authority: sender.address,
amount: 250_000n
});
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(sender, tx),
(tx) =>
setTransactionMessageLifetimeUsingDurableNonce(
{
nonce: nonceData.blockhash as Nonce,
nonceAccountAddress: nonceKeypair.address,
nonceAuthorityAddress: nonceData.authority
},
tx
),
(tx) => appendTransactionMessageInstructions([transferInstruction], tx)
);
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);

nonceの取得

nonceアカウントからデータを取得します。nonceアカウントのblockhashをトランザクションの有効期間として使用します。

Example Nonce Account Data
{
version: 1,
state: 1,
authority: 'HgjaL8artMtmntaQDVM2UBk3gppsYYERS4PkUhiaLZD1',
blockhash: '5U7seXqfgZx1uh5DFhdH1vyBhr7XGRrKxBAnJJTbbUa',
lamportsPerSignature: 5000n
}

転送instructionの作成

支払いのためのinstructionを作成します。この例ではトークン転送を示しています。

durable nonceを使用したトランザクションの構築

setTransactionMessageLifetimeUsingDurableNonceを使用します。これによりnonceがブロックハッシュとして設定され、advance nonce instructionが自動的に先頭に追加されます。

トランザクションへの署名

トランザクションに署名します。これで標準のブロックハッシュの代わりにdurable nonceが使用されます。

Build Deferred Transaction
const { data: nonceData } = await fetchNonce(rpc, nonceKeypair.address);

トランザクションの保存または送信

署名後、トランザクションを保存用にエンコードします。準備ができたら、ネットワークに送信します。

保存用にエンコード

署名済みトランザクションをbase64にエンコードします。この値をデータベースに保存します。

トランザクションの送信

準備ができたら署名済みトランザクションを送信します。トランザクションはnonceが進められるまで有効です。

保存用にエンコード

署名済みトランザクションをbase64にエンコードします。この値をデータベースに保存します。

トランザクションの送信

準備ができたら署名済みトランザクションを送信します。トランザクションはnonceが進められるまで有効です。

Store and Execute
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
const base64EncodedTransaction =
getBase64EncodedWireTransaction(signedTransaction);
// Store base64EncodedTransaction in your database

デモ

Demo
// Generate keypairs for sender and recipient
const sender = await generateKeyPairSigner();
const recipient = await generateKeyPairSigner();
console.log("Sender Address:", sender.address);
console.log("Recipient Address:", recipient.address);
// Demo Setup: Create RPC connection, mint, and token accounts
const { rpc, rpcSubscriptions, mint } = await demoSetup(sender, recipient);
// =============================================================================
// Step 1: Create a Nonce Account
// =============================================================================
const nonceKeypair = await generateKeyPairSigner();
console.log("\nNonce Account Address:", nonceKeypair.address);
const nonceSpace = BigInt(getNonceSize());
const nonceRent = await rpc
.getMinimumBalanceForRentExemption(nonceSpace)
.send();
// Instruction to create new account for the nonce
const createNonceAccountIx = getCreateAccountInstruction({
payer: sender,
newAccount: nonceKeypair,
lamports: nonceRent,
space: nonceSpace,
programAddress: SYSTEM_PROGRAM_ADDRESS
});
// Instruction to initialize the nonce account
const initNonceIx = getInitializeNonceAccountInstruction({
nonceAccount: nonceKeypair.address,
nonceAuthority: sender.address
});
// Build and send nonce account creation transaction
const { value: blockhash } = await rpc.getLatestBlockhash().send();
const createNonceTx = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(sender, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),
(tx) =>
appendTransactionMessageInstructions(
[createNonceAccountIx, initNonceIx],
tx
)
);
const signedCreateNonceTx =
await signTransactionMessageWithSigners(createNonceTx);
assertIsTransactionWithBlockhashLifetime(signedCreateNonceTx);
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(
signedCreateNonceTx,
{ commitment: "confirmed" }
);
console.log("Nonce Account created.");
// =============================================================================
// Step 2: Token Payment with Durable Nonce
// =============================================================================
// Fetch current nonce value from the nonce account
const { data: nonceData } = await fetchNonce(rpc, nonceKeypair.address);
console.log("Nonce Account data:", nonceData);
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("\nMint Address:", mint.address);
console.log("Sender Token Account:", senderAta);
console.log("Recipient Token Account:", recipientAta);
const transferInstruction = getTransferInstruction({
source: senderAta,
destination: recipientAta,
authority: sender.address,
amount: 250_000n // 0.25 tokens
});
// Create transaction message using durable nonce lifetime
// setTransactionMessageLifetimeUsingDurableNonce automatically prepends
// the AdvanceNonceAccount instruction
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(sender, tx),
(tx) =>
setTransactionMessageLifetimeUsingDurableNonce(
{
nonce: nonceData.blockhash as string as Nonce,
nonceAccountAddress: nonceKeypair.address,
nonceAuthorityAddress: nonceData.authority
},
tx
),
(tx) => appendTransactionMessageInstructions([transferInstruction], tx)
);
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
assertIsTransactionWithDurableNonceLifetime(signedTransaction);
const transactionSignature = getSignatureFromTransaction(signedTransaction);
// Encode the transaction to base64, optionally save and send at a later time
const base64EncodedTransaction =
getBase64EncodedWireTransaction(signedTransaction);
console.log("\nBase64 Encoded Transaction:", base64EncodedTransaction);
// Send the encoded transaction, blockhash does not expire
await rpc
.sendTransaction(base64EncodedTransaction, {
encoding: "base64",
skipPreflight: true
})
.send();
console.log("\n=== Token Payment with Durable Nonce Complete ===");
console.log("Transaction Signature:", transactionSignature);
// =============================================================================
// Demo Setup Helper Function
// =============================================================================
Console
Click to execute the code.

保留中のトランザクションの無効化

各nonceアカウント blockhash は一度しか使用できません。保留中のトランザクションを無効化するか、nonceアカウントを再利用のために準備するには、手動で進めます。

import { getAdvanceNonceAccountInstruction } from "@solana-program/system";
// Submit this instruction (with a regular blockhash) to invalidate any pending transaction
getAdvanceNonceAccountInstruction({
nonceAccount: nonceAddress,
nonceAuthority
});

これにより新しいnonce値が生成され、古い値で署名されたトランザクションは永久に無効になります。

マルチパーティ承認ワークフロー

トランザクションをデシリアライズして追加の署名を加え、再度シリアライズして保存または送信します。

import {
getBase64Decoder,
getTransactionDecoder,
getBase64EncodedWireTransaction,
partiallySignTransaction
} from "@solana/kit";
// Deserialize the stored transaction
const txBytes = getBase64Decoder().decode(serializedString);
const partiallySignedTx = getTransactionDecoder().decode(txBytes);
// Each approver adds their signature
const fullySignedTx = await partiallySignTransaction(
[newSigner],
partiallySignedTx
);
// Serialize again for storage or submission
const serialized = getBase64EncodedWireTransaction(fullySignedTx);

トランザクションはシリアライズ、保存され、承認者間で受け渡すことができます。必要な署名がすべて収集されたら、ネットワークに送信します。

本番環境での考慮事項

ノンスアカウント管理:

  • 並列トランザクション準備のためのノンスアカウントプールを作成
  • どのノンスが「使用中」(署名済みトランザクションが保留中)かを追跡
  • トランザクションが送信または破棄された後のノンス再利用を実装

セキュリティ:

  • ノンス権限は、トランザクションを無効化できるかどうかを制御します。追加の制御と職務分離のために、ノンス権限をトランザクション署名者から分離することを検討してください
  • シリアル化されたトランザクションバイトを持つ誰でも、それをネットワークに送信できます

関連リソース

Is this page helpful?

管理運営

© 2026 Solana Foundation.
無断転載を禁じます。
つながろう