What is account sequence number on XRPL?
Last updated:
The account sequence number is a fundamental component of the XRP Ledger's transaction system, serving as a nonce mechanism that ensures transaction ordering, prevents replay attacks, and maintains account state integrity.
What is an Account Sequence?
Every XRPL account has a sequence number: a 32-bit unsigned integer that:
1. Starts at 1 when the account is created 2. Increments by 1 with each successful transaction 3. Must match exactly for a transaction to be valid 4. Cannot be reused once consumed 5. Prevents replay attacks by ensuring transaction uniqueness
Sequence Number Lifecycle
```javascript // New account creation const newAccount = { address: 'rNewAccount123...', Sequence: 1, // Always starts at 1 Balance: '20000000' // 20 XRP (minimum reserve) };
// First transaction const tx1 = { Account: 'rNewAccount123...', Sequence: 1, // Must be 1 TransactionType: 'Payment', // ... }; // After tx1 validates: Sequence becomes 2
// Second transaction const tx2 = { Account: 'rNewAccount123...', Sequence: 2, // Must be 2 TransactionType: 'TrustSet', // ... }; // After tx2 validates: Sequence becomes 3 ```
How Sequence Numbers Work
Basic Flow:
```javascript const xrpl = require('xrpl');
async function demonstrateSequence(client, wallet) { // Step 1: Query current sequence const accountInfo = await client.request({ command: 'account_info', account: wallet.address, ledger_index: 'validated' }); const currentSeq = accountInfo.result.account_data.Sequence; console.log('Current sequence:', currentSeq); // Step 2: Create transaction with current sequence const payment = { TransactionType: 'Payment', Account: wallet.address, Destination: 'rReceiver...', Amount: '1000000', Sequence: currentSeq, // MUST match account sequence Fee: '12' }; // Step 3: Sign transaction const signed = wallet.sign(payment); console.log('Signed with sequence:', currentSeq); // Step 4: Submit transaction const result = await client.submit(signed.tx_blob); if (result.result.engine_result === 'tesSUCCESS') { console.log('Transaction successful'); console.log('New sequence will be:', currentSeq + 1); } return result; } ```
Sequence Validation Rules
```javascript function validateTransactionSequence(tx, accountSequence) { if (tx.Sequence < accountSequence) { return { valid: false, code: 'tefPAST_SEQ', message: 'Sequence is in the past (already used)' }; } if (tx.Sequence > accountSequence) { return { valid: false, code: 'terPRE_SEQ', message: 'Sequence is in the future (transaction queued)' }; } if (tx.Sequence === accountSequence) { return { valid: true, message: 'Sequence matches, transaction can execute' }; } } ```
Error Codes Related to Sequence
1. tefPAST_SEQ: Sequence number is in the past - Transaction sequence < Account sequence - Transaction was already executed or is outdated - Cannot be retried (sequence already consumed)
2. terPRE_SEQ: Sequence number is too high - Transaction sequence > Account sequence - Transaction may be queued (if within queue limits) - Will execute when sequence reaches this number
3. telINSUF_FEE_P: Insufficient fee to queue transaction - Transaction sequence is in future - Fee too low to hold in queue - Increase fee or wait for sequence to advance
Transaction Queuing
XRPL allows transactions with future sequences to be queued:
```javascript async function submitWithFutureSequence(client, wallet) { const accountInfo = await client.request({ command: 'account_info', account: wallet.address }); const currentSeq = accountInfo.result.account_data.Sequence; // Submit transaction with sequence + 1 (future) const futureTx = { TransactionType: 'Payment', Account: wallet.address, Destination: 'rReceiver...', Amount: '1000000', Sequence: currentSeq + 1, // Future sequence Fee: '12' }; const signed = wallet.sign(futureTx); const result = await client.submit(signed.tx_blob); console.log('Result:', result.result.engine_result); // Likely: "terPRE_SEQ" - transaction queued // Transaction will execute when: // 1. Account sequence advances to currentSeq + 1 // 2. Either because currentSeq transaction completes // 3. Or currentSeq transaction fails/expires } ```
Auto-Filling Sequences
Most libraries automatically fetch and fill the sequence:
```javascript const xrpl = require('xrpl');
async function autoFillSequence(client, wallet) { // Create transaction without sequence const payment = { TransactionType: 'Payment', Account: wallet.address, Destination: 'rReceiver...', Amount: '1000000' }; // Auto-fill will fetch current sequence and add it const prepared = await client.autofill(payment); console.log('Auto-filled sequence:', prepared.Sequence); console.log('Auto-filled fee:', prepared.Fee); console.log('Auto-filled LastLedgerSequence:', prepared.LastLedgerSequence); // Now sign and submit const signed = wallet.sign(prepared); return await client.submit(signed.tx_blob); } ```
Sequence and Concurrency
Sequence numbers enforce strict ordering:
```javascript // Problem: Multiple transactions at once async function concurrentTransactions(client, wallet) { // Both fetch the same sequence const seq = await getCurrentSequence(client, wallet.address); const tx1 = { Sequence: seq, /* ... */ }; const tx2 = { Sequence: seq, /* ... */ }; // WRONG! Same sequence // Only ONE will succeed await Promise.all([ client.submit(wallet.sign(tx1).tx_blob), // Might succeed client.submit(wallet.sign(tx2).tx_blob) // Will fail (tefPAST_SEQ) ]); }
// Solution: Manage sequences manually async function sequentialTransactions(client, wallet) { let seq = await getCurrentSequence(client, wallet.address); const tx1 = { Sequence: seq, /* ... */ }; const tx2 = { Sequence: seq + 1, /* ... */ }; const tx3 = { Sequence: seq + 2, /* ... */ }; // Submit in order or use queue await client.submit(wallet.sign(tx1).tx_blob); await client.submit(wallet.sign(tx2).tx_blob); await client.submit(wallet.sign(tx3).tx_blob); } ```
Tickets: Alternative to Sequences
XRPL offers tickets for non-sequential transaction execution:
```javascript // Create tickets for flexible ordering const createTickets = { TransactionType: 'TicketCreate', Account: wallet.address, Sequence: 100, TicketCount: 10 // Creates 10 tickets };
// Tickets created: 100, 101, 102, ..., 109 // Account sequence becomes: 110
// Can now use any ticket in any order const tx1 = { Account: wallet.address, TicketSequence: 105, // Use ticket 105 Sequence: 0, // Set to 0 when using ticket TransactionType: 'Payment', // ... };
const tx2 = { Account: wallet.address, TicketSequence: 102, // Use ticket 102 (out of order!) Sequence: 0, TransactionType: 'TrustSet', // ... };
// Both can be submitted in any order // Each ticket can only be used once ```
Sequence in Multi-Signing
```javascript // Multi-signed transaction uses single sequence const multiSigTx = { TransactionType: 'Payment', Account: 'rMultiSigAccount...', Sequence: 50, // Account's current sequence // ... };
// Multiple signers sign the SAME transaction const sig1 = wallet1.sign(multiSigTx, true); const sig2 = wallet2.sign(multiSigTx, true); const sig3 = wallet3.sign(multiSigTx, true);
// Combine signatures const combined = xrpl.multisign([sig1.tx_blob, sig2.tx_blob, sig3.tx_blob]);
// Submit once, consumes one sequence await client.submit(combined); // Account sequence increments by 1 (not 3) ```
Monitoring Sequence State
```javascript const xrpl = require('xrpl');
async function monitorSequence(client, address) { // Get account info const accountInfo = await client.request({ command: 'account_info', account: address, ledger_index: 'validated' }); const data = accountInfo.result.account_data; return { currentSequence: data.Sequence, balance: data.Balance, previousTxnID: data.PreviousTxnID, previousTxnLgrSeq: data.PreviousTxnLgrSeq }; }
// Check if sequence advanced async function waitForSequenceAdvance(client, address, expectedSeq) { while (true) { const info = await monitorSequence(client, address); if (info.currentSequence >= expectedSeq) { return info.currentSequence; } await new Promise(resolve => setTimeout(resolve, 1000)); } } ```
Best Practices
1. Always fetch fresh sequence: Don't cache or assume sequence values 2. Handle sequence errors gracefully: Retry with updated sequence on tefPAST_SEQ 3. Use autofill for single transactions: Let libraries manage sequence 4. Manage manually for batch operations: Pre-assign sequences for multiple transactions 5. Consider tickets for complex workflows: When transactions may execute out of order 6. Monitor transaction validation: Confirm transaction succeeded before assuming sequence increment
Common Mistakes
```javascript // WRONG: Reusing sequence const tx1 = { Sequence: 10, /* ... */ }; const tx2 = { Sequence: 10, /* ... */ }; // ERROR!
// WRONG: Skipping sequence const tx1 = { Sequence: 10, /* ... */ }; const tx2 = { Sequence: 12, /* ... */ }; // Sequence 11 skipped!
// WRONG: Caching sequence const cachedSeq = await getSequence(); // Time passes... const tx = { Sequence: cachedSeq /* might be stale! */ };
// RIGHT: Fresh sequence per transaction for (let i = 0; i < 5; i++) { const seq = await getSequence(); const tx = { Sequence: seq, /* ... */ }; await submit(tx); } ```
The account sequence number is a simple yet powerful mechanism that provides transaction ordering, replay protection, and concurrency control in a decentralized environment, making it a cornerstone of XRPL's transaction model.