Files
2025-11-30 09:01:25 +08:00

962 lines
26 KiB
Markdown

# Durable Transaction Nonces
This guide covers Solana's durable transaction nonces, which enable transactions to remain valid indefinitely by replacing the time-limited recent blockhash mechanism. Essential for offline signing, multi-signature coordination, and scheduled transaction execution.
## Introduction
### The Expiration Problem
Solana transactions normally include a `recent_blockhash` field that serves two purposes:
1. **Double-spend prevention**: Ensures each transaction is unique and can only be processed once
2. **Transaction freshness**: Limits transaction validity to prevent spam and stale transactions
**The limitation:**
- Recent blockhashes expire after **150 blocks** (~60-90 seconds)
- Transactions with expired blockhashes are permanently rejected
- Cannot be re-validated, even with identical content
**Critical constraint:**
```
Transaction must be:
1. Signed with recent blockhash
2. Submitted to network
3. Processed by validator
4. Confirmed in block
All within ~60-90 seconds!
```
### Problems This Creates
**Hardware Wallet Users:**
- Fetch blockhash from network
- Transfer to air-gapped device
- User reviews and signs (can take minutes)
- Transfer back to online device
- Submit to network
- **Risk**: Blockhash expires during manual review
**Multi-Signature Wallets (DAOs, Squads, Realms):**
- Create transaction with blockhash
- Send to signer 1 for approval (hours/days)
- Send to signer 2 for approval (hours/days)
- Send to signer N for approval
- **Risk**: Blockhash expires while collecting signatures
**Scheduled Transactions:**
- Want to pre-sign transaction for future execution
- E.g., vesting unlock, scheduled payment, conditional trade
- **Risk**: Cannot pre-sign hours/days in advance
**Cross-Chain Bridges:**
- Wait for finality on source chain (minutes/hours)
- Sign transaction on destination chain
- **Risk**: Blockhash expires during cross-chain confirmation
### The Solution: Durable Nonces
Durable nonces replace `recent_blockhash` with a **stored on-chain value** that:
- ✅ Never expires (remains valid indefinitely)
- ✅ Changes with each use (prevents replay attacks)
- ✅ Enables offline signing without time pressure
- ✅ Supports multi-signature coordination
- ✅ Allows pre-signing transactions for future execution
**Key insight**: Instead of using the blockchain's recent history (blockhashes) to ensure uniqueness, durable nonces use a **dedicated account** that stores a nonce value and advances it after each transaction.
## How Durable Nonces Work
### Core Mechanism
1. **Nonce Account**: On-chain account (owned by System Program) storing a 32-byte nonce value
2. **Transaction Structure**: Use nonce value as `recent_blockhash` field
3. **Nonce Advancement**: First instruction MUST advance the nonce to prevent replay
4. **Authority Control**: Only nonce authority can advance nonce or authorize transactions
### Transaction Flow
```
Normal Transaction:
1. Fetch recent blockhash (expires in 90s)
2. Build transaction with blockhash
3. Sign transaction
4. Submit (must be within 90s)
5. Process and confirm
Durable Nonce Transaction:
1. Create nonce account (one-time setup)
2. Fetch current nonce value (no expiration!)
3. Build transaction with nonce as blockhash
4. Add advance_nonce instruction (MUST be first)
5. Sign transaction (no time pressure)
6. Submit anytime (minutes, hours, days later)
7. Process: advances nonce, executes instructions
```
### Double-Spend Prevention
**Without expiration, how does it prevent double-spending?**
The nonce value **changes** after each transaction:
```rust
// Transaction 1 with nonce value "ABC123..."
{
recent_blockhash: "ABC123...", // Current nonce value
instructions: [
advance_nonce_account(...), // Changes nonce to "XYZ789..."
transfer(...),
]
}
// If you try to submit Transaction 1 again:
// Runtime checks: Is "ABC123..." the current nonce?
// NO! It's now "XYZ789..."
// Transaction REJECTED (nonce mismatch)
```
**Critical**: The runtime **always** advances the nonce, even if the transaction fails after the advance instruction. This prevents replay attacks.
## Nonce Account Structure
### Account Layout
Nonce accounts are owned by the System Program and have this structure:
```rust
pub struct NonceState {
pub version: NonceVersion,
}
pub enum NonceVersion {
Legacy(Box<NonceData>),
Current(Box<NonceData>),
}
pub struct NonceData {
pub authority: Pubkey, // Who can authorize nonce operations
pub durable_nonce: Hash, // The actual nonce value (32 bytes)
pub fee_calculator: FeeCalculator, // Historic fee data
}
```
**Account requirements:**
- **Owner**: System Program (`11111111111111111111111111111111`)
- **Size**: 80 bytes
- **Rent exemption**: Required (minimum ~0.00144768 SOL)
- **Authority**: Can be any pubkey (keypair or PDA)
### Nonce Authority
The authority pubkey controls the nonce account:
- **Can**: Advance nonce, withdraw funds, authorize nonce transactions, change authority
- **Cannot**: Execute other instructions without nonce advancement (runtime enforces this)
**Authority options:**
- **Keypair**: Direct control (hot wallet, cold wallet)
- **PDA**: Program-controlled nonces (advanced use case)
- **Multisig**: Multiple signers required (DAO wallets)
## Creating Nonce Accounts
### Using Native Rust
```rust
use solana_sdk::{
instruction::Instruction,
pubkey::Pubkey,
signature::{Keypair, Signer},
system_instruction,
sysvar::rent::Rent,
transaction::Transaction,
};
use solana_client::rpc_client::RpcClient;
fn create_nonce_account(
rpc_client: &RpcClient,
payer: &Keypair,
nonce_account: &Keypair,
authority: &Pubkey,
) -> Result<(), Box<dyn std::error::Error>> {
// Calculate rent-exempt balance for nonce account
let rent = rpc_client.get_minimum_balance_for_rent_exemption(80)?;
// Create account instruction
let create_account_ix = system_instruction::create_account(
&payer.pubkey(),
&nonce_account.pubkey(),
rent, // Lamports (rent-exempt minimum)
80, // Space (nonce account size)
&solana_program::system_program::id(), // Owner (System Program)
);
// Initialize nonce instruction
let initialize_nonce_ix = system_instruction::initialize_nonce_account(
&nonce_account.pubkey(),
authority, // Nonce authority
);
// Build transaction
let recent_blockhash = rpc_client.get_latest_blockhash()?;
let transaction = Transaction::new_signed_with_payer(
&[create_account_ix, initialize_nonce_ix],
Some(&payer.pubkey()),
&[payer, nonce_account], // Both payer and nonce account must sign
recent_blockhash,
);
// Send transaction
let signature = rpc_client.send_and_confirm_transaction(&transaction)?;
println!("Created nonce account: {}", signature);
Ok(())
}
```
### Single-Step Creation
There's also a convenience function that combines both steps:
```rust
let instruction = system_instruction::create_nonce_account(
&payer.pubkey(),
&nonce_account.pubkey(),
authority,
rent_lamports,
);
// This creates a single instruction that:
// 1. Creates the account
// 2. Initializes it as a nonce account
```
### Using CLI
```bash
# Generate keypair for nonce account
solana-keygen new -o nonce-account.json
# Create nonce account
solana create-nonce-account nonce-account.json 0.0015
# Verify creation
solana nonce nonce-account.json
# Output: Current nonce value (32-byte hash)
```
## Querying Nonce Accounts
### Fetching Nonce Value
```rust
use solana_sdk::account::Account;
use solana_program::system_program;
fn get_nonce_value(
rpc_client: &RpcClient,
nonce_pubkey: &Pubkey,
) -> Result<Hash, Box<dyn std::error::Error>> {
// Fetch account data
let account = rpc_client.get_account(nonce_pubkey)?;
// Verify it's a nonce account
if account.owner != system_program::id() {
return Err("Account is not owned by System Program".into());
}
// Deserialize nonce data
let nonce_data = bincode::deserialize::<NonceState>(&account.data)?;
match nonce_data {
NonceState::Current(data) => Ok(data.durable_nonce),
NonceState::Legacy(data) => Ok(data.durable_nonce),
}
}
```
### Parsing Nonce Account
```rust
use solana_program::nonce::state::{Data, State};
fn parse_nonce_account(account_data: &[u8]) -> Result<Data, Box<dyn std::error::Error>> {
let state: State = bincode::deserialize(account_data)?;
match state {
State::Initialized(data) => Ok(data),
State::Uninitialized => Err("Nonce account not initialized".into()),
}
}
// Access nonce components
fn display_nonce_info(nonce_data: &Data) {
println!("Authority: {}", nonce_data.authority);
println!("Nonce value: {}", nonce_data.blockhash);
println!("Fee calculator: {:?}", nonce_data.fee_calculator);
}
```
## Building Transactions with Durable Nonces
### Transaction Structure
**Critical requirements:**
1. **First instruction** MUST be `advance_nonce_account`
2. Use nonce value as `recent_blockhash`
3. Sign with nonce authority (in addition to other required signers)
```rust
use solana_sdk::{
hash::Hash,
instruction::Instruction,
message::Message,
signature::{Keypair, Signer},
system_instruction,
transaction::Transaction,
};
fn build_nonce_transaction(
nonce_pubkey: &Pubkey,
nonce_authority: &Keypair,
nonce_value: Hash,
instructions: Vec<Instruction>,
payer: &Keypair,
) -> Transaction {
// 1. Create advance_nonce instruction (MUST BE FIRST)
let advance_nonce_ix = system_instruction::advance_nonce_account(
nonce_pubkey,
&nonce_authority.pubkey(),
);
// 2. Combine with your instructions
let mut all_instructions = vec![advance_nonce_ix];
all_instructions.extend(instructions);
// 3. Build message with nonce as blockhash
let message = Message::new_with_blockhash(
&all_instructions,
Some(&payer.pubkey()),
&nonce_value, // Use nonce value instead of recent blockhash!
);
// 4. Sign with both payer and nonce authority
let mut signers = vec![payer];
if nonce_authority.pubkey() != payer.pubkey() {
signers.push(nonce_authority);
}
Transaction::new(&signers, message, nonce_value)
}
```
### Complete Example: Transfer with Durable Nonce
```rust
fn transfer_with_nonce(
rpc_client: &RpcClient,
nonce_account: &Pubkey,
nonce_authority: &Keypair,
payer: &Keypair,
recipient: &Pubkey,
amount: u64,
) -> Result<(), Box<dyn std::error::Error>> {
// 1. Fetch current nonce value
let nonce_value = get_nonce_value(rpc_client, nonce_account)?;
// 2. Create transfer instruction
let transfer_ix = system_instruction::transfer(
&payer.pubkey(),
recipient,
amount,
);
// 3. Build transaction with nonce
let transaction = build_nonce_transaction(
nonce_account,
nonce_authority,
nonce_value,
vec![transfer_ix],
payer,
);
// 4. Can now submit immediately or store for later
// No expiration pressure!
let signature = rpc_client.send_and_confirm_transaction(&transaction)?;
println!("Transfer completed: {}", signature);
Ok(())
}
```
### Serializing for Offline Signing
```rust
use base58::ToBase58;
fn serialize_for_offline_signing(transaction: &Transaction) -> String {
// Serialize transaction to bytes
let serialized = bincode::serialize(transaction).unwrap();
// Encode as base58 for transport
serialized.to_base58()
}
fn deserialize_signed_transaction(base58_tx: &str) -> Transaction {
use base58::FromBase58;
let bytes = base58_tx.from_base58().unwrap();
bincode::deserialize(&bytes).unwrap()
}
```
## Managing Nonce Accounts
### Advancing Nonce
**Automatic advancement**: When you submit a transaction with a durable nonce, the runtime automatically advances the nonce as part of processing the `advance_nonce_account` instruction.
**Manual advancement** (without submitting transaction):
```rust
fn advance_nonce_manually(
rpc_client: &RpcClient,
nonce_account: &Pubkey,
nonce_authority: &Keypair,
payer: &Keypair,
) -> Result<(), Box<dyn std::error::Error>> {
let advance_ix = system_instruction::advance_nonce_account(
nonce_account,
&nonce_authority.pubkey(),
);
let recent_blockhash = rpc_client.get_latest_blockhash()?;
let transaction = Transaction::new_signed_with_payer(
&[advance_ix],
Some(&payer.pubkey()),
&[payer, nonce_authority],
recent_blockhash,
);
rpc_client.send_and_confirm_transaction(&transaction)?;
Ok(())
}
```
**When to manually advance:**
- Before reusing nonce for a new transaction
- To invalidate a previously signed transaction
- Regular rotation for security
### Withdrawing from Nonce Account
```rust
fn withdraw_from_nonce(
rpc_client: &RpcClient,
nonce_account: &Pubkey,
nonce_authority: &Keypair,
recipient: &Pubkey,
amount: u64,
payer: &Keypair,
) -> Result<(), Box<dyn std::error::Error>> {
let withdraw_ix = system_instruction::withdraw_nonce_account(
nonce_account,
&nonce_authority.pubkey(),
recipient,
amount,
);
let recent_blockhash = rpc_client.get_latest_blockhash()?;
let transaction = Transaction::new_signed_with_payer(
&[withdraw_ix],
Some(&payer.pubkey()),
&[payer, nonce_authority],
recent_blockhash,
);
rpc_client.send_and_confirm_transaction(&transaction)?;
Ok(())
}
```
**Important**: Must maintain rent-exempt minimum balance. Can only withdraw to zero if closing the account.
### Changing Nonce Authority
```rust
fn change_nonce_authority(
rpc_client: &RpcClient,
nonce_account: &Pubkey,
current_authority: &Keypair,
new_authority: &Pubkey,
payer: &Keypair,
) -> Result<(), Box<dyn std::error::Error>> {
let authorize_ix = system_instruction::authorize_nonce_account(
nonce_account,
&current_authority.pubkey(),
new_authority,
);
let recent_blockhash = rpc_client.get_latest_blockhash()?;
let transaction = Transaction::new_signed_with_payer(
&[authorize_ix],
Some(&payer.pubkey()),
&[payer, current_authority],
recent_blockhash,
);
rpc_client.send_and_confirm_transaction(&transaction)?;
Ok(())
}
```
**Use cases:**
- Transfer control to PDA for program-managed nonces
- Rotate keys for security
- Transfer to multisig for DAO control
## Offline Signing Workflows
### Hardware Wallet Flow
**Setup (online device):**
```rust
// 1. Create nonce account (one-time)
create_nonce_account(&rpc_client, &payer, &nonce_account, &hw_wallet_pubkey)?;
// 2. Fetch nonce value
let nonce_value = get_nonce_value(&rpc_client, &nonce_account.pubkey())?;
// 3. Build unsigned transaction
let unsigned_tx = build_nonce_transaction(
&nonce_account.pubkey(),
&hw_wallet_keypair, // Will be replaced with actual signature
nonce_value,
vec![transfer_ix],
&payer,
);
// 4. Serialize for hardware wallet
let serialized = serialize_for_offline_signing(&unsigned_tx);
// 5. Transfer to hardware wallet (USB, QR code, etc.)
```
**Signing (air-gapped hardware wallet):**
```rust
// 1. Receive serialized transaction
let tx = deserialize_signed_transaction(&serialized);
// 2. Display to user for review (no time pressure!)
// User reviews: recipient, amount, etc.
// 3. Sign with hardware wallet private key
// (Hardware wallet handles this internally)
// 4. Export signed transaction
let signed_serialized = serialize_for_offline_signing(&signed_tx);
// 5. Transfer back to online device
```
**Submission (online device):**
```rust
// 1. Receive signed transaction
let signed_tx = deserialize_signed_transaction(&signed_serialized);
// 2. Submit to network (can be hours/days after signing!)
let signature = rpc_client.send_and_confirm_transaction(&signed_tx)?;
```
### Multi-Signature Coordination
**DAO Proposal Execution Flow:**
```rust
// 1. Proposer creates transaction with nonce
let nonce_value = get_nonce_value(&rpc_client, &dao_nonce_account)?;
let proposal_tx = build_nonce_transaction(
&dao_nonce_account,
&dao_authority, // PDA controlled by governance
nonce_value,
vec![execute_proposal_ix],
&proposer,
);
// 2. Serialize and store in DAO state
let tx_data = bincode::serialize(&proposal_tx)?;
// Store tx_data in proposal account
// 3. Members vote over time (hours/days)
// Each vote increments approval count
// 4. When threshold reached, anyone can execute
let stored_tx: Transaction = bincode::deserialize(&proposal.tx_data)?;
// 5. Submit (nonce ensures it's still valid!)
rpc_client.send_and_confirm_transaction(&stored_tx)?;
```
### CLI Multi-Sig Example
**First co-signer (offline):**
```bash
solana transfer \
--from sender.json \
--sign-only \
--nonce nonce-account.json \
--nonce-authority nonce-authority.json \
--blockhash <NONCE_VALUE> \
--fee-payer co-sender.json \
receiver.json 0.1
# Output:
# Pubkey=Signature
# 5nZ8nY5...=4SBv7Xp...
```
**Second co-signer (online, hours/days later):**
```bash
solana transfer \
--from sender.json \
--nonce nonce-account.json \
--nonce-authority nonce-authority.json \
--blockhash <NONCE_VALUE> \
--fee-payer sender.json \
--signer 5nZ8nY5...=4SBv7Xp... \
receiver.json 0.1
```
## Security Considerations
### The Neodyme Vulnerability (2020)
**Historic issue**: Before Solana v1.3, there was a critical vulnerability in how durable nonce transactions were processed:
**The bug:**
1. Transaction with durable nonce starts processing
2. Runtime advances nonce (changes state)
3. Later instruction in transaction fails
4. Runtime rolls back ALL state changes
5. **BUG**: Nonce advancement was rolled back too!
6. Attacker could replay the transaction
**The exploit:**
```rust
// Malicious transaction:
{
instructions: [
advance_nonce(...), // Advances nonce
write_arbitrary_data(...), // Attacker's payload
fail_intentionally(...), // Forces transaction to fail
]
}
// After rollback:
// - Nonce reverted to original value
// - Arbitrary data write WAS NOT rolled back
// - Can replay transaction infinitely!
```
**Impact**: Could write arbitrary data to any account by replaying failed transactions.
**Fix** (Solana v1.3+): Nonce advancement is now **permanent** even on transaction failure. The runtime explicitly handles nonce accounts separately from normal rollback logic.
**Lesson**: This demonstrates why nonce advancement MUST happen regardless of transaction success/failure.
### Best Practices
**1. Never reuse nonce without advancing**
```rust
// BAD: Reusing nonce value
let nonce = get_nonce_value(&rpc, &nonce_account)?;
let tx1 = build_nonce_transaction(&nonce_account, &auth, nonce, vec![ix1], &payer);
let tx2 = build_nonce_transaction(&nonce_account, &auth, nonce, vec![ix2], &payer);
// If tx1 fails, tx2 might also fail with "nonce mismatch"
// GOOD: Advance between uses
let nonce1 = get_nonce_value(&rpc, &nonce_account)?;
let tx1 = build_nonce_transaction(&nonce_account, &auth, nonce1, vec![ix1], &payer);
rpc.send_and_confirm_transaction(&tx1)?;
// Fetch fresh nonce (it was advanced)
let nonce2 = get_nonce_value(&rpc, &nonce_account)?;
let tx2 = build_nonce_transaction(&nonce_account, &auth, nonce2, vec![ix2], &payer);
```
**2. Protect nonce authority**
```rust
// Use cold storage for nonce authority
// OR use PDA with program logic to restrict usage
let authority_pda = Pubkey::find_program_address(
&[b"nonce_authority", dao.key().as_ref()],
program_id,
);
```
**3. Maintain rent exemption**
```rust
// Check before withdrawal
let nonce_account = rpc.get_account(&nonce_pubkey)?;
let rent = rpc.get_minimum_balance_for_rent_exemption(80)?;
if nonce_account.lamports - withdraw_amount < rent {
return Err("Would violate rent exemption".into());
}
```
**4. Verify nonce advancement in transaction**
```rust
// In your program that uses nonce transactions:
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
// First account should be nonce account
let nonce_account = &accounts[0];
// Verify it's a valid nonce account
if nonce_account.owner != &system_program::id() {
return Err(ProgramError::InvalidAccountData);
}
// Verify advance_nonce was called
// (Runtime enforces this, but you can add checks)
Ok(())
}
```
**5. Monitor nonce account balance**
```rust
// Periodic check (e.g., daily job)
fn check_nonce_health(rpc: &RpcClient, nonce: &Pubkey) -> Result<(), String> {
let account = rpc.get_account(nonce)
.map_err(|_| "Nonce account not found")?;
let rent = rpc.get_minimum_balance_for_rent_exemption(80)
.map_err(|_| "Failed to fetch rent")?;
if account.lamports < rent {
return Err(format!(
"Nonce account below rent exemption: {} < {}",
account.lamports, rent
));
}
Ok(())
}
```
## Use Cases
### 1. Scheduled Payments (Vesting)
```rust
// Pre-sign monthly vesting releases
fn create_vesting_schedule(
rpc: &RpcClient,
nonce_account: &Pubkey,
nonce_authority: &Keypair,
recipient: &Pubkey,
amount_per_month: u64,
months: usize,
) -> Result<Vec<Transaction>, Box<dyn std::error::Error>> {
let mut transactions = Vec::new();
for month in 0..months {
// Fetch current nonce
let nonce = get_nonce_value(rpc, nonce_account)?;
// Create transfer
let transfer_ix = system_instruction::transfer(
&nonce_authority.pubkey(),
recipient,
amount_per_month,
);
// Build nonce transaction
let tx = build_nonce_transaction(
nonce_account,
nonce_authority,
nonce,
vec![transfer_ix],
nonce_authority,
);
transactions.push(tx);
// Advance nonce for next month's transaction
advance_nonce_manually(rpc, nonce_account, nonce_authority, nonce_authority)?;
}
Ok(transactions)
}
// Executor submits each month
fn execute_vesting_payment(
rpc: &RpcClient,
pre_signed_tx: &Transaction,
) -> Result<(), Box<dyn std::error::Error>> {
// No time pressure - can submit anytime!
rpc.send_and_confirm_transaction(pre_signed_tx)?;
Ok(())
}
```
### 2. Conditional Trades (Limit Orders)
```rust
// Pre-sign trade execution at specific price
fn create_limit_order(
nonce: &Pubkey,
authority: &Keypair,
swap_instruction: Instruction, // Execute when price reached
) -> Transaction {
let nonce_value = /* fetch nonce */;
build_nonce_transaction(
nonce,
authority,
nonce_value,
vec![swap_instruction],
authority,
)
}
// Bot monitors price and submits when condition met
fn execute_limit_order(rpc: &RpcClient, current_price: f64, limit_tx: &Transaction) {
if current_price >= target_price {
rpc.send_transaction(limit_tx).ok(); // Submit pre-signed transaction
}
}
```
### 3. Cross-Chain Bridges
```rust
// Sign Solana transaction while waiting for Ethereum finality
async fn bridge_from_ethereum_to_solana(
eth_tx_hash: H256,
solana_mint_ix: Instruction,
nonce_account: &Pubkey,
nonce_authority: &Keypair,
) -> Result<(), Box<dyn std::error::Error>> {
// 1. Pre-sign Solana mint transaction
let nonce = get_nonce_value(&solana_rpc, nonce_account)?;
let mint_tx = build_nonce_transaction(
nonce_account,
nonce_authority,
nonce,
vec![solana_mint_ix],
nonce_authority,
);
// 2. Wait for Ethereum finality (12+ minutes)
wait_for_ethereum_finality(eth_tx_hash).await?;
// 3. Submit Solana transaction (still valid!)
solana_rpc.send_and_confirm_transaction(&mint_tx)?;
Ok(())
}
```
### 4. DAO Governance Execution
Already covered in multi-sig example above - proposals can be voted on over days/weeks, then executed with pre-signed transaction.
## CLI Reference
**Create nonce account:**
```bash
solana create-nonce-account <KEYPAIR_PATH> <AMOUNT>
```
**Get current nonce:**
```bash
solana nonce <NONCE_ACCOUNT>
```
**Manually advance nonce:**
```bash
solana new-nonce <NONCE_ACCOUNT>
```
**Get nonce account info:**
```bash
solana nonce-account <NONCE_ACCOUNT>
```
**Withdraw from nonce:**
```bash
solana withdraw-from-nonce-account <NONCE_ACCOUNT> <DESTINATION> <AMOUNT>
```
**Change nonce authority:**
```bash
solana authorize-nonce-account <NONCE_ACCOUNT> <NEW_AUTHORITY>
```
**Sign transaction offline:**
```bash
solana <COMMAND> \
--sign-only \
--nonce <NONCE_ACCOUNT> \
--nonce-authority <AUTHORITY_KEYPAIR> \
--blockhash <NONCE_VALUE>
```
**Submit pre-signed transaction:**
```bash
solana <COMMAND> \
--nonce <NONCE_ACCOUNT> \
--nonce-authority <AUTHORITY_KEYPAIR> \
--blockhash <NONCE_VALUE> \
--signer <PUBKEY=SIGNATURE>
```
## Limitations and Considerations
**Transaction size:**
- Adding `advance_nonce_account` instruction adds ~40 bytes
- May push transaction over size limit if already near maximum
**Extra signature requirement:**
- Nonce authority must sign (if different from fee payer)
- Increases transaction complexity
**Rent cost:**
- Each nonce account requires ~0.0015 SOL rent-exempt minimum
- For many scheduled transactions, can become expensive
**Nonce advancement overhead:**
- Compute units to advance nonce (~few hundred CU)
- Minimal but worth considering for CU-constrained transactions
**Cannot mix recent blockhashes and nonces:**
- Transaction uses either recent blockhash OR durable nonce
- Cannot use both in the same transaction
## Resources
### Official Documentation
- [Introduction to Durable Nonces](https://solana.com/developers/guides/advanced/introduction-to-durable-nonces)
- [Durable Transaction Nonces Proposal](https://docs.anza.xyz/implemented-proposals/durable-tx-nonces)
- [CLI Nonce Examples](https://docs.anza.xyz/cli/examples/durable-nonce)
### Code Examples
- [Durable Nonces Repository](https://github.com/0xproflupin/solana-durable-nonces)
- [System Program Source](https://github.com/solana-labs/solana/blob/master/sdk/program/src/system_instruction.rs)
### Security Analysis
- [Neodyme: Nonce Upon a Time](https://neodyme.io/en/blog/nonce-upon-a-time/) - Historic vulnerability analysis
### Technical References
- [solana-sdk NonceState](https://docs.rs/solana-sdk/latest/solana_sdk/nonce/state/enum.State.html)
- [System Program Instructions](https://docs.rs/solana-sdk/latest/solana_sdk/system_instruction/)