> For the complete documentation index, see [llms.txt](https://docs.xrpl-commons.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.xrpl-commons.org/core-dev-bootcamp/module02/transaction-lifecycle-complete-transaction-journey.md).

# Transaction Lifecycle: Complete Transaction Journey

[← Back to Rippled II Overview](/core-dev-bootcamp/module02.md)

***

### Introduction

Understanding the complete lifecycle of a transaction—from the moment it's created to its final inclusion in a validated ledger—is crucial for developing applications on the XRP Ledger, debugging transaction issues, and optimizing transaction processing. Every transaction follows a well-defined path through multiple validation stages, consensus rounds, and finalization steps.

This deep dive traces the entire journey of a transaction, explaining each phase, the checks performed, the state transitions, and how to monitor and query transaction status at every step. Whether you're building a wallet, an exchange integration, or contributing to the core protocol, mastering the transaction lifecycle is essential.

{% embed url="<https://www.youtube.com/watch?v=F0fOY1FVrE0>" %}

***

### Transaction Lifecycle Overview

#### Complete Journey Diagram

```
┌─────────────────────────────────────────────────────────────┐
│                    1. Transaction Creation                   │
│              (Client creates and signs transaction)          │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ↓
┌─────────────────────────────────────────────────────────────┐
│                    2. Transaction Submission                 │
│           (Submit via RPC, WebSocket, or peer network)       │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ↓
┌─────────────────────────────────────────────────────────────┐
│                    3. Initial Validation                     │
│        (Signature check, format validation, preflight)       │
└──────────────────────────┬──────────────────────────────────┘
                           │
                  ┌────────┴────────┐
                  │                 │
                  ↓                 ↓
              ✓ Valid           ✗ Invalid
                  │                 │
                  │                 └──→ Rejected (temMALFORMED, etc.)
                  ↓
┌─────────────────────────────────────────────────────────────┐
│                  4. Preclaim Validation                      │
│       (Check ledger state, balance, account existence)       │
└──────────────────────────┬──────────────────────────────────┘
                           │
                  ┌────────┴────────┐
                  │                 │
                  ↓                 ↓
              ✓ Valid           ✗ Invalid
                  │                 │
                  │                 └──→ Rejected (tecUNFUNDED, etc.)
                  ↓
┌─────────────────────────────────────────────────────────────┐
│               5. Open Ledger Application                     │
│         (Tentatively apply to open ledger for preview)       │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ↓
┌─────────────────────────────────────────────────────────────┐
│                    6. Network Propagation                    │
│              (Broadcast to peers via tmTRANSACTION)          │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ↓
┌─────────────────────────────────────────────────────────────┐
│                    7. Consensus Round                        │
│        (Validators propose and agree on transaction set)     │
└──────────────────────────┬──────────────────────────────────┘
                           │
                  ┌────────┴────────┐
                  │                 │
                  ↓                 ↓
           Included in Set    Not Included
                  │                 │
                  │                 └──→ Deferred to next round
                  ↓
┌─────────────────────────────────────────────────────────────┐
│               8. Canonical Application (DoApply)             │
│          (Apply to ledger in canonical order, final)         │
└──────────────────────────┬──────────────────────────────────┘
                           │
                  ┌────────┴────────┐
                  │                 │
                  ↓                 ↓
            tesSUCCESS          tecFAILURE
                  │                 │
                  │                 └──→ Failed but fee charged
                  ↓
┌─────────────────────────────────────────────────────────────┐
│                    9. Ledger Closure                         │
│              (Ledger closed with transaction included)       │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ↓
┌─────────────────────────────────────────────────────────────┐
│                    10. Validation Phase                      │
│         (Validators sign and broadcast validations)          │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ↓
┌─────────────────────────────────────────────────────────────┐
│                    11. Fully Validated                       │
│     (Transaction immutable, part of permanent history)       │
└─────────────────────────────────────────────────────────────┘
```

#### Typical Timeline

**Fast Path** (ideal conditions):

```
T+0s:     Transaction submitted
T+0.1s:   Initial validation complete
T+0.2s:   Open ledger application
T+0.3s:   Network propagation
T+5s:     Consensus round completes
T+5.1s:   Canonical application
T+5.2s:   Ledger closed
T+7s:     Validations collected
T+7s:     Transaction fully validated

Total: ~7 seconds from submission to finality
```

**Slow Path** (transaction arrives late in open phase):

```
T+0s:     Transaction submitted
T+0.1s:   Validation complete
T+25s:    Consensus round starts (waiting for ledger close)
T+30s:    Consensus completes
T+30.1s:  Canonical application
T+30.2s:  Ledger closed
T+32s:    Transaction fully validated

Total: ~32 seconds (depends on when submitted)
```

***

### Phase 1: Transaction Creation

#### Transaction Structure

Before submission, a transaction must be properly constructed:

```json
{
  "TransactionType": "Payment",
  "Account": "rN7n7otQDd6FczFgLdlqtyMVrn3HMtthca",
  "Destination": "rLNaPoKeeBjZe2qs6x52yVPZpZ8td4dc6w",
  "Amount": "1000000",
  "Fee": "12",
  "Sequence": 42,
  "LastLedgerSequence": 75234567,
  "SigningPubKey": "03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB",
  "TxnSignature": "30450221008..."
}
```

#### Required Fields

**Universal Fields** (all transaction types):

* `TransactionType` - Type of transaction (Payment, OfferCreate, etc.)
* `Account` - Source account (sender)
* `Fee` - Transaction fee in drops (1 XRP = 1,000,000 drops)
* `Sequence` - Account sequence number (nonce)
* `SigningPubKey` - Public key used for signing
* `TxnSignature` - Cryptographic signature (or multi-signatures)

**Optional but Recommended**:

* `LastLedgerSequence` - Expiration ledger (transaction invalid after this)
* `SourceTag` / `DestinationTag` - Integer tags for routing/identification
* `Memos` - Arbitrary data attached to transaction

#### Transaction Signing

**Single Signature**:

```javascript
// Using xrpl.js
const xrpl = require('xrpl');

// Create transaction
const tx = {
  TransactionType: 'Payment',
  Account: wallet.address,
  Destination: 'rLNaPoKeeBjZe2qs6x52yVPZpZ8td4dc6w',
  Amount: '1000000',
  Fee: '12'
};

// Auto-fill (adds Sequence, LastLedgerSequence, etc.)
const prepared = await client.autofill(tx);

// Sign transaction
const signed = wallet.sign(prepared);

console.log(signed.tx_blob);  // Signed transaction blob
console.log(signed.hash);     // Transaction hash
```

**Multi-Signature**:

```javascript
// For accounts with SignerList
const tx = {
  TransactionType: 'Payment',
  Account: multiSigAccount,
  Destination: 'rLNaPoKeeBjZe2qs6x52yVPZpZ8td4dc6w',
  Amount: '1000000',
  Fee: '12',
  SigningPubKey: '',  // Empty for multi-sig
  Sequence: 42
};

// Each signer signs independently
const signer1Sig = wallet1.sign(tx, true);
const signer2Sig = wallet2.sign(tx, true);

// Combine signatures
const multisigned = xrpl.multisign([signer1Sig, signer2Sig]);
```

#### Transaction Hash

The transaction hash (ID) is calculated from the signed transaction:

```cpp
// Simplified hash calculation
uint256 calculateTransactionID(STTx const& tx)
{
    // Serialize the entire signed transaction
    Serializer s;
    tx.add(s);
    
    // Hash with SHA-512 and take first 256 bits
    return s.getSHA512Half();
}
```

**Important**: The hash is deterministic—the same signed transaction always produces the same hash.

***

### Phase 2: Transaction Submission

#### Submission Methods

**Method 1: RPC Submit**

Submit via JSON-RPC:

```bash
curl -X POST https://s1.ripple.com:51234/ \
  -H "Content-Type: application/json" \
  -d '{
    "method": "submit",
    "params": [{
      "tx_blob": "120000228000000024..."
    }]
  }'
```

Response:

```json
{
  "result": {
    "engine_result": "tesSUCCESS",
    "engine_result_code": 0,
    "engine_result_message": "The transaction was applied.",
    "tx_blob": "120000228000000024...",
    "tx_json": {
      "Account": "rN7n7otQDd6FczFgLdlqtyMVrn3HMtthca",
      "Amount": "1000000",
      "Destination": "rLNaPoKeeBjZe2qs6x52yVPZpZ8td4dc6w",
      "Fee": "12",
      "TransactionType": "Payment",
      "hash": "E08D6E9754025BA2534A78707605E0601F03ACE063687A0CA1BDDACFCD1698C7"
    }
  }
}
```

**Method 2: WebSocket Submit**

Real-time submission with streaming updates:

```javascript
const ws = new WebSocket('wss://s1.ripple.com');

ws.on('open', () => {
  ws.send(JSON.stringify({
    command: 'submit',
    tx_blob: '120000228000000024...'
  }));
});

ws.on('message', (data) => {
  const response = JSON.parse(data);
  console.log(response.result.engine_result);
});
```

**Method 3: Peer Network Submission**

Transactions submitted to one node propagate to all nodes:

```
Client → Node A → Overlay Network → All Nodes
```

Even if submitted to a non-validator, the transaction reaches validators through peer-to-peer propagation.

#### Submission Response

Immediate response indicates initial validation result:

**Success Codes**:

* `tesSUCCESS` - Transaction applied to open ledger
* `terQUEUED` - Transaction queued (network busy)

**Temporary Failure** (can retry):

* `terPRE_SEQ` - Sequence too high, earlier tx needed
* `tefPAST_SEQ` - Sequence too low (already used)

**Permanent Failure** (don't retry):

* `temMALFORMED` - Malformed transaction
* `temBAD_FEE` - Invalid fee
* `temBAD_SIGNATURE` - Invalid signature

***

### Phase 3: Initial Validation

#### Preflight Checks

Before accessing ledger state, static validation occurs:

```cpp
NotTEC Transactor::preflight(PreflightContext const& ctx)
{
    // 1. Verify signature
    if (!checkSignature(ctx.tx))
        return temBAD_SIGNATURE;
    
    // 2. Check transaction format
    if (!ctx.tx.isFieldPresent(sfAccount))
        return temMALFORMED;
    
    // 3. Validate fee
    auto const fee = ctx.tx.getFieldAmount(sfFee);
    if (fee < ctx.baseFee)
        return telINSUF_FEE_P;
    
    // 4. Check sequence
    if (ctx.tx.getSequence() == 0)
        return temBAD_SEQUENCE;
    
    // 5. Verify amounts are valid
    auto const amount = ctx.tx.getFieldAmount(sfAmount);
    if (amount <= zero)
        return temBAD_AMOUNT;
    
    return tesSUCCESS;
}
```

**Checks Performed**:

* ✓ Cryptographic signature valid
* ✓ Transaction format correct
* ✓ Required fields present
* ✓ Fee sufficient
* ✓ Amounts positive and properly formatted
* ✓ No contradictory fields

**Why Preflight Matters**: Catches obvious errors before expensive ledger state access.

***

### Phase 4: Preclaim Validation

#### Ledger State Checks

Read-only validation against current ledger state:

```cpp
TER Transactor::preclaim(PreclaimContext const& ctx)
{
    // 1. Source account must exist
    auto const sleAccount = ctx.view.read(
        keylet::account(ctx.tx[sfAccount]));
    
    if (!sleAccount)
        return terNO_ACCOUNT;
    
    // 2. Check sequence number
    auto const txSeq = ctx.tx.getSequence();
    auto const acctSeq = (*sleAccount)[sfSequence];
    
    if (txSeq != acctSeq)
        return tefPAST_SEQ;  // Already used or too high
    
    // 3. Verify sufficient balance
    auto const balance = (*sleAccount)[sfBalance];
    auto const fee = ctx.tx[sfFee];
    auto const amount = ctx.tx[sfAmount];
    
    if (balance < fee + amount)
        return tecUNFUNDED_PAYMENT;
    
    // 4. Check destination exists (for Payment)
    if (ctx.tx.getTransactionType() == ttPAYMENT)
    {
        auto const dest = ctx.tx[sfDestination];
        auto const sleDest = ctx.view.read(keylet::account(dest));
        
        if (!sleDest)
        {
            // Can create account if amount >= reserve
            if (amount < ctx.view.fees().accountReserve(0))
                return tecNO_DST_INSUF_XRP;
        }
    }
    
    return tesSUCCESS;
}
```

**Checks Performed**:

* ✓ Source account exists
* ✓ Sequence number correct
* ✓ Sufficient balance (including fee)
* ✓ Destination account requirements met
* ✓ Trust lines exist (for issued currencies)
* ✓ Account flags permit operation

***

### Phase 5: Open Ledger Application

#### Tentative Application

Transaction is tentatively applied to provide immediate feedback:

```cpp
std::pair<TER, bool> 
NetworkOPs::processTransaction(
    std::shared_ptr<Transaction> const& transaction)
{
    // Apply to open ledger
    auto result = app_.openLedger().modify(
        [&](OpenView& view, beast::Journal j)
        {
            return transaction->apply(app_, view, ApplyFlags::tapNONE);
        });
    
    if (result.second)  // Transaction applied successfully
    {
        JLOG(j_.trace()) 
            << "Transaction " << transaction->getID()
            << " applied to open ledger with result: "
            << transToken(result.first);
        
        // Relay to network
        app_.overlay().relay(transaction);
        
        return {result.first, true};
    }
    
    return {result.first, false};
}
```

**Open Ledger Characteristics**:

* **Not Final**: Open ledger is tentative, changes frequently
* **No Consensus**: Local view only, other nodes may differ
* **Immediate Feedback**: Clients get instant response
* **Can Change**: Transaction may be removed or re-ordered

**Why It Matters**:

* Users get immediate confirmation
* Wallets can show pending transactions
* Applications can provide real-time updates

***

### Phase 6: Network Propagation

#### Transaction Broadcasting

Once applied to open ledger, transaction broadcasts to peers:

```cpp
void Overlay::relay(std::shared_ptr<Transaction> const& tx)
{
    // Create protocol message
    protocol::TMTransaction msg;
    msg.set_rawtransaction(tx->getSerialized());
    msg.set_status(protocol::tsNEW);
    msg.set_receivetimestamp(
        std::chrono::system_clock::now().time_since_epoch().count());
    
    // Wrap in Message object
    auto m = std::make_shared<Message>(msg, protocol::mtTRANSACTION);
    
    // Broadcast to all peers (except source)
    for (auto& peer : getActivePeers())
    {
        if (peer.get() != source)
            peer->send(m);
    }
    
    JLOG(j_.trace()) 
        << "Relayed transaction " << tx->getID() 
        << " to " << getActivePeers().size() << " peers";
}
```

**Propagation Speed**:

* Local network: < 100ms
* Global network: 200-500ms
* All nodes receive transaction within 1 second

**Deduplication**:

* Nodes track recently seen transactions
* Duplicate transactions not re-processed
* Prevents network flooding

***

### Phase 7: Consensus Round

#### Transaction Set Building

As ledger close approaches, validators build transaction sets:

```cpp
std::set<TxID> buildInitialPosition(OpenView const& openLedger)
{
    std::set<TxID> position;
    
    // Include transactions from open ledger
    for (auto const& tx : openLedger.transactions())
    {
        // Only include transactions that:
        // 1. Are still valid
        // 2. Have sufficient fee
        // 3. Haven't expired (LastLedgerSequence)
        
        if (isValidForConsensus(tx))
            position.insert(tx.getTransactionID());
    }
    
    return position;
}
```

#### Consensus Process

Validators exchange proposals and converge:

**Round 1**: Initial proposals

```
Validator A proposes: {TX1, TX2, TX3, TX4}
Validator B proposes: {TX1, TX2, TX3, TX5}
Validator C proposes: {TX1, TX2, TX4, TX5}

Agreement:
TX1: 100% ✓
TX2: 100% ✓
TX3: 67%
TX4: 67%
TX5: 67%
```

**Round 2**: Converge on high-agreement transactions

```
All validators propose: {TX1, TX2}

Agreement:
TX1: 100% ✓ (included)
TX2: 100% ✓ (included)

TX3, TX4, TX5 deferred to next ledger
```

**Transaction Inclusion Criteria**:

* > 80% of UNL must agree to include
* Transaction must still be valid
* Must not have expired (LastLedgerSequence)

***

### Phase 8: Canonical Application

#### Deterministic Execution

After consensus, transactions are applied in canonical order:

```cpp
void applyTransactionsCanonically(
    Ledger& ledger,
    std::set<TxID> const& txSet)
{
    // 1. Retrieve transactions from set
    std::vector<std::shared_ptr<STTx>> transactions;
    for (auto const& id : txSet)
    {
        auto tx = fetchTransaction(id);
        transactions.push_back(tx);
    }
    
    // 2. Sort in canonical order
    std::sort(transactions.begin(), transactions.end(),
        [](auto const& a, auto const& b)
        {
            // Sort by account, then sequence
            if (a->getAccountID(sfAccount) != b->getAccountID(sfAccount))
                return a->getAccountID(sfAccount) < b->getAccountID(sfAccount);
            
            return a->getSequence() < b->getSequence();
        });
    
    // 3. Apply each transaction
    for (auto const& tx : transactions)
    {
        auto const result = applyTransaction(ledger, tx);
        
        // Record in metadata
        ledger.recordTransaction(tx, result);
        
        JLOG(j_.debug()) 
            << "Applied " << tx->getTransactionID()
            << " with result " << transToken(result);
    }
}
```

**DoApply Execution**:

```cpp
TER Payment::doApply()
{
    // 1. Debit source account
    auto const result = accountSend(
        view(),
        account_,
        ctx_.tx[sfDestination],
        ctx_.tx[sfAmount],
        j_);
    
    if (result != tesSUCCESS)
        return result;
    
    // 2. Update sequence number
    auto const sleAccount = view().peek(keylet::account(account_));
    (*sleAccount)[sfSequence] = ctx_.tx.getSequence() + 1;
    view().update(sleAccount);
    
    // 3. Record metadata
    ctx_.deliver(ctx_.tx[sfAmount]);
    
    return tesSUCCESS;
}
```

**Result Codes**:

* `tesSUCCESS` - Transaction succeeded
* `tecUNFUNDED` - Failed but fee charged
* `tecNO_TARGET` - Failed but fee charged

**Important**: Even failed transactions (`tec` codes) consume the fee and advance the sequence number.

***

### Phase 9: Ledger Closure

#### Closing the Ledger

After all transactions are applied:

```cpp
void closeLedger(
    std::shared_ptr<Ledger>& ledger,
    NetClock::time_point closeTime)
{
    // 1. Set close time
    ledger->setCloseTime(closeTime);
    
    // 2. Calculate state hash
    auto const stateHash = ledger->stateMap().getHash();
    ledger->setAccountHash(stateHash);
    
    // 3. Calculate transaction tree hash
    auto const txHash = ledger->txMap().getHash();
    ledger->setTxHash(txHash);
    
    // 4. Calculate ledger hash
    auto const ledgerHash = ledger->getHash();
    
    JLOG(j_.info()) 
        << "Closed ledger " << ledger->seq()
        << " hash: " << ledgerHash
        << " txns: " << ledger->txCount();
    
    // 5. Mark as closed
    ledger->setClosed();
}
```

**Ledger Hash Calculation**:

```cpp
uint256 Ledger::getHash() const
{
    Serializer s;
    s.add32(seq_);                    // Ledger sequence
    s.add64(closeTime_.count());      // Close time
    s.addBitString(parentHash_);      // Parent ledger hash
    s.addBitString(txHash_);          // Transaction tree hash
    s.addBitString(accountHash_);     // Account state hash
    s.add64(totalCoins_);             // Total XRP
    s.add64(closeTimeResolution_);    // Close time resolution
    s.add8(closeFlags_);              // Flags
    
    return s.getSHA512Half();
}
```

***

### Phase 10: Validation Phase

#### Creating Validations

Validators sign the closed ledger:

```cpp
std::shared_ptr<STValidation> createValidation(
    Ledger const& ledger,
    SecretKey const& secretKey)
{
    auto validation = std::make_shared<STValidation>(
        ledger.getHash(),
        ledger.seq(),
        NetClock::now(),
        publicKeyFromSecretKey(secretKey),
        calculateNodeID(publicKeyFromSecretKey(secretKey)),
        [&](STValidation& v)
        {
            v.setFlag(vfFullValidation);
            v.sign(secretKey);
        });
    
    JLOG(j_.info()) 
        << "Created validation for ledger " << ledger.seq()
        << " hash: " << ledger.getHash();
    
    return validation;
}
```

#### Broadcasting Validations

```cpp
void broadcastValidation(std::shared_ptr<STValidation> const& val)
{
    // Create protocol message
    protocol::TMValidation msg;
    Serializer s;
    val->add(s);
    msg.set_validation(s.data(), s.size());
    
    // Broadcast to all peers
    auto m = std::make_shared<Message>(msg, protocol::mtVALIDATION);
    overlay().broadcast(m);
    
    JLOG(j_.trace()) 
        << "Broadcast validation for ledger " << val->getLedgerSeq();
}
```

#### Collecting Validations

```cpp
bool hasValidationQuorum(
    LedgerHash const& hash,
    std::set<NodeID> const& validators)
{
    auto const& unl = getUNL();
    size_t validationCount = 0;
    
    for (auto const& validator : validators)
    {
        if (unl.contains(validator))
            validationCount++;
    }
    
    // Need >80% of UNL
    return validationCount >= (unl.size() * 4 / 5);
}
```

***

### Phase 11: Fully Validated

#### Finalization

When quorum is reached, ledger becomes fully validated:

```cpp
void markLedgerValidated(std::shared_ptr<Ledger> const& ledger)
{
    // 1. Mark as validated
    ledger->setValidated();
    
    // 2. Add to validated chain
    ledgerMaster_.addValidatedLedger(ledger);
    
    // 3. Update current validated ledger
    ledgerMaster_.setValidatedLedger(ledger);
    
    // 4. Publish to subscribers
    publishValidatedLedger(ledger);
    
    // 5. Start next open ledger
    openLedger_.accept(
        ledger,
        orderTx,
        consensusParms,
        {});  // Empty initial transaction set
    
    JLOG(j_.info()) 
        << "Ledger " << ledger->seq() 
        << " validated with " << validationCount(ledger)
        << " validations";
}
```

**Characteristics of Validated Ledger**:

* **Immutable**: Cannot be changed
* **Permanent**: Part of ledger history forever
* **Canonical**: All nodes have identical copy
* **Final**: Transactions cannot be reversed

***

### Transaction Status Querying

#### Methods to Check Transaction Status

**Method 1: tx RPC**

Query by transaction hash:

```bash
rippled tx E08D6E9754025BA2534A78707605E0601F03ACE063687A0CA1BDDACFCD1698C7
```

Response:

```json
{
  "result": {
    "Account": "rN7n7otQDd6FczFgLdlqtyMVrn3HMtthca",
    "Amount": "1000000",
    "Destination": "rLNaPoKeeBjZe2qs6x52yVPZpZ8td4dc6w",
    "Fee": "12",
    "Sequence": 42,
    "TransactionType": "Payment",
    "hash": "E08D6E9754025BA2534A78707605E0601F03ACE063687A0CA1BDDACFCD1698C7",
    "ledger_index": 75234567,
    "validated": true,
    "meta": {
      "TransactionIndex": 5,
      "TransactionResult": "tesSUCCESS",
      "AffectedNodes": [
        // Nodes modified by transaction
      ]
    }
  }
}
```

**Key Fields**:

* `validated`: true = in validated ledger, false = pending
* `meta.TransactionResult`: Final result code
* `ledger_index`: Which ledger contains transaction

**Method 2: account\_tx RPC**

Query all transactions for an account:

```bash
rippled account_tx rN7n7otQDd6FczFgLdlqtyMVrn3HMtthca
```

Lists transactions in reverse chronological order.

**Method 3: WebSocket Subscriptions**

Real-time transaction monitoring:

```javascript
ws.send(JSON.stringify({
  command: 'subscribe',
  accounts: ['rN7n7otQDd6FczFgLdlqtyMVrn3HMtthca']
}));

ws.on('message', (data) => {
  const msg = JSON.parse(data);
  if (msg.type === 'transaction') {
    console.log('Transaction:', msg.transaction);
    console.log('Status:', msg.validated ? 'Validated' : 'Pending');
  }
});
```

**Subscription Types**:

* `accounts` - Transactions affecting specific accounts
* `transactions` - All transactions network-wide
* `ledger` - Ledger close events

***

### Transaction Metadata

#### Metadata Structure

Metadata records the effects of a transaction:

```json
{
  "meta": {
    "TransactionIndex": 5,
    "TransactionResult": "tesSUCCESS",
    "AffectedNodes": [
      {
        "ModifiedNode": {
          "LedgerEntryType": "AccountRoot",
          "LedgerIndex": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8",
          "PreviousFields": {
            "Balance": "10000000",
            "Sequence": 42
          },
          "FinalFields": {
            "Balance": "8999988",
            "Sequence": 43,
            "Account": "rN7n7otQDd6FczFgLdlqtyMVrn3HMtthca"
          }
        }
      },
      {
        "ModifiedNode": {
          "LedgerEntryType": "AccountRoot",
          "LedgerIndex": "4F83A2CF7E70F77F79A307E6A472BFC2585B806A70833CCD1C26105BAE0D6E05",
          "PreviousFields": {
            "Balance": "5000000"
          },
          "FinalFields": {
            "Balance": "6000000",
            "Account": "rLNaPoKeeBjZe2qs6x52yVPZpZ8td4dc6w"
          }
        }
      }
    ],
    "delivered_amount": "1000000"
  }
}
```

**AffectedNodes Types**:

* `CreatedNode` - New ledger object created
* `ModifiedNode` - Existing object modified
* `DeletedNode` - Object deleted

**Key Metadata Fields**:

* `TransactionIndex` - Position in ledger
* `TransactionResult` - Final result code
* `delivered_amount` - Actual amount delivered (for partial payments)

***

### Transaction Expiration

#### LastLedgerSequence

Transactions can specify an expiration:

```json
{
  "TransactionType": "Payment",
  "Account": "rN7n7otQDd6FczFgLdlqtyMVrn3HMtthca",
  "Destination": "rLNaPoKeeBjZe2qs6x52yVPZpZ8td4dc6w",
  "Amount": "1000000",
  "LastLedgerSequence": 75234567
}
```

**Behavior**:

* If not included by ledger 75234567, transaction becomes invalid
* Prevents transactions from being stuck indefinitely
* Recommended: Set to current ledger + 4

**Checking Expiration**:

```cpp
bool isExpired(STTx const& tx, LedgerIndex currentLedger)
{
    if (!tx.isFieldPresent(sfLastLedgerSequence))
        return false;  // No expiration set
    
    return tx[sfLastLedgerSequence] < currentLedger;
}
```

***

### Hands-On Exercise

#### Exercise: Track a Transaction Through Its Complete Lifecycle

**Objective**: Submit a transaction and observe it at each phase of the lifecycle.

**Part 1: Prepare and Submit**

**Step 1**: Create and fund test accounts

```bash
# Using testnet
rippled account_info <your_address>
```

**Step 2**: Prepare transaction with monitoring

```javascript
const xrpl = require('xrpl');
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233');

await client.connect();

const wallet = xrpl.Wallet.fromSeed('sXXXXXXXXXXXXXXXX');

// Create transaction
const tx = {
  TransactionType: 'Payment',
  Account: wallet.address,
  Destination: 'rLNaPoKeeBjZe2qs6x52yVPZpZ8td4dc6w',
  Amount: '1000000',
  Fee: '12'
};

// Prepare with LastLedgerSequence
const prepared = await client.autofill(tx);
console.log('Prepared TX:', prepared);
console.log('LastLedgerSequence:', prepared.LastLedgerSequence);

// Sign
const signed = wallet.sign(prepared);
console.log('Transaction Hash:', signed.hash);
```

**Step 3**: Submit and record time

```javascript
const startTime = Date.now();

const result = await client.submit(signed.tx_blob);

console.log('Submission Time:', Date.now() - startTime, 'ms');
console.log('Initial Result:', result.result.engine_result);
console.log('Applied to Open Ledger:', result.result.engine_result === 'tesSUCCESS');
```

**Part 2: Monitor Progress**

**Step 4**: Subscribe to transaction

```javascript
await client.request({
  command: 'subscribe',
  accounts: [wallet.address]
});

client.on('transaction', (tx) => {
  console.log('Transaction Event:');
  console.log('  Hash:', tx.transaction.hash);
  console.log('  Validated:', tx.validated);
  console.log('  Ledger Index:', tx.ledger_index);
  console.log('  Time:', Date.now() - startTime, 'ms');
  
  if (tx.validated) {
    console.log('✓ Transaction fully validated!');
    console.log('  Result:', tx.meta.TransactionResult);
  }
});
```

**Step 5**: Poll for status

```javascript
async function checkStatus(hash) {
  try {
    const tx = await client.request({
      command: 'tx',
      transaction: hash
    });
    
    console.log('Status Check:');
    console.log('  Found:', true);
    console.log('  Validated:', tx.result.validated);
    console.log('  Ledger:', tx.result.ledger_index);
    console.log('  Result:', tx.result.meta.TransactionResult);
    
    return tx.result.validated;
  } catch (e) {
    console.log('Status Check: Not yet in validated ledger');
    return false;
  }
}

// Poll every second
const pollInterval = setInterval(async () => {
  const validated = await checkStatus(signed.hash);
  if (validated) {
    clearInterval(pollInterval);
    console.log('Total time to validation:', Date.now() - startTime, 'ms');
  }
}, 1000);
```

**Part 3: Analyze Results**

**Step 6**: Examine metadata

```javascript
const finalTx = await client.request({
  command: 'tx',
  transaction: signed.hash
});

console.log('\n=== Final Transaction Analysis ===');
console.log('Transaction Hash:', finalTx.result.hash);
console.log('Ledger Index:', finalTx.result.ledger_index);
console.log('Result Code:', finalTx.result.meta.TransactionResult);
console.log('Transaction Index:', finalTx.result.meta.TransactionIndex);

// Analyze affected nodes
console.log('\nAffected Nodes:');
for (const node of finalTx.result.meta.AffectedNodes) {
  if (node.ModifiedNode) {
    console.log('  Modified:', node.ModifiedNode.LedgerEntryType);
    console.log('    Account:', node.ModifiedNode.FinalFields.Account);
    if (node.ModifiedNode.PreviousFields.Balance) {
      console.log('    Balance Change:',
        node.ModifiedNode.FinalFields.Balance - 
        node.ModifiedNode.PreviousFields.Balance);
    }
  }
}

// Calculate total time
console.log('\nTiming:');
console.log('  Total time:', Date.now() - startTime, 'ms');
```

**Analysis Questions**

Answer these based on your observations:

1. **How long did each phase take?**
   * Submission to initial result: \_\_\_ ms
   * Initial result to validated: \_\_\_ ms
   * Total time: \_\_\_ ms
2. **What was the initial result?**
   * Did it apply to open ledger?
3. **Which ledger included the transaction?**
   * Ledger index?
   * How many ledgers closed between submission and inclusion?
4. **What was the metadata?**
   * Which nodes were affected?
   * What balances changed?
5. **Did the transaction expire?**
   * Was LastLedgerSequence set?
   * How close to expiration was it?

***

### Key Takeaways

#### Core Concepts

✅ **11-Phase Journey**: Transactions go through creation, submission, validation, consensus, application, and finalization

✅ **Multiple Validation Stages**: Preflight (static), Preclaim (state-based), DoApply (execution)

✅ **Open Ledger Preview**: Tentative application provides immediate feedback before consensus

✅ **Consensus Inclusion**: Validators must agree (>80%) to include transaction

✅ **Canonical Order**: Deterministic ordering ensures all nodes reach identical state

✅ **Immutable Finality**: Once validated, transactions cannot be reversed

✅ **Metadata Records Effects**: Complete record of all ledger modifications

#### Timing Expectations

✅ **Fast Path**: \~7 seconds submission to validation

✅ **Slow Path**: \~30 seconds if submitted late in open phase

✅ **Network Propagation**: <1 second to reach all nodes

✅ **Consensus Round**: 3-5 seconds

#### Development Skills

✅ **Transaction Construction**: Proper signing and field selection

✅ **Status Monitoring**: Using tx, account\_tx, and subscriptions

✅ **Error Handling**: Understanding result codes (tem/tef/ter/tec/tes)

✅ **Expiration Management**: Setting LastLedgerSequence appropriately

✅ **Metadata Analysis**: Understanding transaction effects

***

### Common Issues and Solutions

#### Issue 1: Transaction Stuck Pending

**Symptoms**: Transaction not validating after 30+ seconds

**Possible Causes**:

* Insufficient fee (transaction queued)
* Network congestion
* Sequence gap (earlier transaction missing)

**Solutions**:

```bash
# Check transaction status
rippled tx <hash>

# Check for sequence gaps
rippled account_info <account>

# Increase fee and resubmit if needed
```

#### Issue 2: tefPAST\_SEQ Error

**Symptoms**: Sequence number already used

**Cause**: Sequence out of sync or transaction already processed

**Solution**:

```javascript
// Always fetch current sequence
const accountInfo = await client.request({
  command: 'account_info',
  account: wallet.address
});

const currentSeq = accountInfo.result.account_data.Sequence;
```

#### Issue 3: Transaction Not Found

**Symptoms**: tx command returns "txnNotFound"

**Possible Causes**:

* Transaction not yet in validated ledger
* Transaction expired (LastLedgerSequence)
* Transaction rejected during validation

**Solution**:

```javascript
// Wait for validation or check expiration
const ledger = await client.request({command: 'ledger_current'});

if (ledger.result.ledger_current_index > tx.LastLedgerSequence) {
  console.log('Transaction expired');
}
```

#### Issue 4: tecUNFUNDED\_PAYMENT

**Symptoms**: Transaction failed with fee charged

**Cause**: Insufficient balance between submission and execution

**Prevention**:

```javascript
// Always check balance including reserves
const reserve = (2 + ownerCount) * baseReserve;
const available = balance - reserve;

if (amount + fee > available) {
  throw new Error('Insufficient funds');
}
```

***

### Additional Resources

#### Official Documentation

* **XRP Ledger Dev Portal**: [xrpl.org/docs](https://xrpl.org/docs)
* **Transaction Types**: [xrpl.org/transaction-types](https://xrpl.org/docs/references/protocol/transactions/types/)
* **Transaction Results**: [xrpl.org/transaction-results](https://xrpl.org/docs/references/protocol/transactions/transaction-results)

#### Codebase References

* `src/ripple/app/tx/impl/Transactor.cpp` - Transaction processing
* `src/ripple/app/misc/NetworkOPs.cpp` - Network operations and transaction handling
* `src/ripple/app/ledger/OpenLedger.cpp` - Open ledger management

#### Related Topics

* [Transactors](/core-dev-bootcamp/module02/transactors-transaction-processing-framework.md) - How transactions are validated and executed
* [Consensus Engine](/core-dev-bootcamp/module02/consensus-engine-xrp-ledger-consensus-protocol.md) - How transactions are included in consensus
* [Protocols](/core-dev-bootcamp/module02/protocols-communication-and-interoperability.md) - How transactions are propagated across the network

***
