Transactors: Transaction Processing Framework

← Back to Rippled II Overview


Introduction

Transactors are the heart of transaction processing in the XRP Ledger. Every transaction type—from simple XRP payments to complex escrow operations—is implemented as a Transactor, a C++ class that defines how that transaction is validated and executed. Understanding the transactor framework is essential for anyone who wants to contribute to the core protocol, implement custom transaction types, or debug transaction-related issues.

The transactor architecture ensures that all transactions undergo rigorous validation before modifying ledger state, maintaining the integrity and security that makes the XRP Ledger reliable for financial applications.


The Transactor Base Class

Architecture Overview

Every transaction type in Rippled inherits from the Transactor base class, which provides the fundamental framework for transaction processing. This inheritance model ensures consistent behavior across all transaction types while allowing each type to implement its specific business logic.

The base Transactor class is defined in src/ripple/app/tx/impl/Transactor.h and provides:

  • Common validation logic - Signature verification, fee checks, sequence number validation

  • Helper methods - Account balance queries, ledger state access, fee calculation

  • Virtual methods - Hooks for transaction-specific logic (preflight, preclaim, doApply)

  • Transaction context - Access to the ledger, transaction data, and application state

Base Class Structure

class Transactor
{
public:
    // Main entry point for transaction application
    static std::pair<TER, bool>
    apply(Application& app, OpenView& view, STTx const& tx, ApplyFlags flags);

    // Virtual methods for transaction-specific logic
    static NotTEC preflight(PreflightContext const& ctx);
    static TER preclaim(PreclaimContext const& ctx);
    virtual TER doApply() = 0;

protected:
    // Constructor - available to derived classes
    Transactor(ApplyContext& ctx);

    // Helper methods
    TER payFee();
    TER checkSeq();
    TER checkSign(PreclaimContext const& ctx);
    
    // Member variables
    ApplyContext& ctx_;
    beast::Journal j_;
    AccountID account_;
    XRPAmount mPriorBalance;
    XRPAmount mSourceBalance;
};

Key Concepts

ApplyContext: Provides access to the transaction being processed, the ledger view, and application services. This context object is passed through all stages of transaction processing.

Transaction Engine Result (TER): Every validation step returns a TER code indicating success (tesSUCCESS), temporary failure (ter codes), or permanent failure (tem or tef codes). These codes determine whether a transaction can be retried or should be permanently rejected.

Ledger Views: Transactors work with "views" of the ledger state, allowing tentative modifications that can be committed or rolled back. This ensures atomic transaction processing.


Three-Phase Validation Process

The transactor framework implements a rigorous three-phase validation process. Each phase has a specific purpose and access to different levels of information, creating a defense-in-depth approach to transaction validation.

Phase 1: Preflight

Purpose: Static validation that doesn't require ledger state

Access: Only the raw transaction data and protocol rules

When It Runs: Before any ledger state is accessed, can run in parallel

What It Checks:

  • Transaction format is valid

  • Required fields are present

  • Field values are within valid ranges

  • Amounts are positive and properly formatted

  • No malformed or contradictory data

Key Characteristic: Preflight checks are deterministic and stateless—they depend only on the transaction itself, not on current ledger state.

Preflight Example: Payment Transaction

NotTEC Payment::preflight(PreflightContext const& ctx)
{
    // Check if the Payment transaction type is enabled
    if (!ctx.rules.enabled(featurePayment))
        return temDISABLED;

    // Call base class preflight checks
    auto const ret = preflight1(ctx);
    if (!isTesSuccess(ret))
        return ret;

    // Verify destination account is specified
    if (!ctx.tx.isFieldPresent(sfDestination))
        return temDST_NEEDED;

    // Verify amount is specified and valid
    auto const amount = ctx.tx[sfAmount];
    if (!amount)
        return temBAD_AMOUNT;

    // Amount must be positive
    if (amount <= zero)
        return temBAD_AMOUNT;

    // Check for valid currency code if not XRP
    if (!isXRP(amount))
    {
        if (!amount.issue().currency)
            return temBAD_CURRENCY;
    }

    // Additional format validations...
    return preflight2(ctx);
}

Why Preflight Matters: By catching format errors early, preflight prevents wasting resources on obviously invalid transactions. It also provides fast feedback to clients about transaction formatting issues.

Phase 2: Preclaim

Purpose: Validation requiring read-only access to ledger state

Access: Current ledger state (read-only), transaction data, protocol rules

When It Runs: After preflight passes, but before any state modifications

What It Checks:

  • Source account exists and has sufficient balance

  • Destination account exists (or can be created)

  • Required authorizations are in place

  • Trust lines exist for non-XRP currencies

  • Account flags and settings permit the transaction

  • Sequence numbers are correct

Key Characteristic: Preclaim can read ledger state but cannot modify it. This allows for safe concurrent execution and caching of preclaim results.

Preclaim Example: Payment Transaction

TER Payment::preclaim(PreclaimContext const& ctx)
{
    // Get source and destination account IDs
    AccountID const src = ctx.tx[sfAccount];
    AccountID const dst = ctx.tx[sfDestination];

    // Source and destination cannot be the same
    if (src == dst)
        return temREDUNDANT;

    // Check if destination account exists
    auto const dstID = ctx.tx[sfDestination];
    auto const sleDst = ctx.view.read(keylet::account(dstID));

    // If destination doesn't exist, check if we can create it
    if (!sleDst)
    {
        auto const amount = ctx.tx[sfAmount];
        
        // Only XRP can create accounts
        if (!isXRP(amount))
            return tecNO_DST;

        // Amount must meet reserve requirement
        if (amount < ctx.view.fees().accountReserve(0))
            return tecNO_DST_INSUF_XRP;
    }
    else
    {
        // Destination exists - check if it requires dest tag
        auto const flags = sleDst->getFlags();
        
        if (flags & lsfRequireDestTag)
        {
            // Destination requires a tag but none provided
            if (!ctx.tx.isFieldPresent(sfDestinationTag))
                return tecDST_TAG_NEEDED;
        }

        // Check if destination has disallowed XRP
        if (flags & lsfDisallowXRP && isXRP(ctx.tx[sfAmount]))
            return tecNO_TARGET;
    }

    // Check source account balance
    auto const sleSrc = ctx.view.read(keylet::account(src));
    if (!sleSrc)
        return terNO_ACCOUNT;

    auto const balance = (*sleSrc)[sfBalance];
    auto const amount = ctx.tx[sfAmount];

    // Ensure sufficient balance (including fee)
    if (balance < amount + ctx.tx[sfFee])
        return tecUNFUNDED_PAYMENT;

    return tesSUCCESS;
}

Why Preclaim Matters: Preclaim catches state-dependent errors before attempting state modifications. This prevents partially-applied transactions and provides clear error messages about why a transaction cannot succeed.

Phase 3: DoApply

Purpose: Actual ledger state modification

Access: Full read/write access to ledger state

When It Runs: After both preflight and preclaim succeed

What It Does:

  • Debits source account

  • Credits destination account

  • Creates or modifies ledger objects

  • Applies transaction-specific business logic

  • Records transaction metadata

  • Consumes transaction fee

Key Characteristic: DoApply modifies ledger state. All changes are atomic—either the entire transaction succeeds and all changes are applied, or it fails and no changes are made.

DoApply Example: Payment Transaction

TER Payment::doApply()
{
    // Pay the transaction fee (happens for all transactions)
    auto const result = payFee();
    if (result != tesSUCCESS)
        return result;

    // Get amount to send
    auto const amount = ctx_.tx[sfAmount];
    auto const dst = ctx_.tx[sfDestination];

    // Perform the actual transfer
    auto const transferResult = accountSend(
        view(),           // Ledger view to modify
        account_,         // Source account
        dst,              // Destination account
        amount,           // Amount to transfer
        j_                // Journal for logging
    );

    if (transferResult != tesSUCCESS)
        return transferResult;

    // Handle partial payments and path finding if applicable
    if (ctx_.tx.isFlag(tfPartialPayment))
    {
        // Partial payment logic...
    }

    // Record transaction metadata
    ctx_.deliver(amount);

    return tesSUCCESS;
}

Why DoApply Matters: This is where the actual ledger state changes happen. DoApply ensures that only transactions that have passed all validation steps can modify the ledger, maintaining data integrity.


Transaction Types in Detail

The XRP Ledger supports numerous transaction types, each implemented as a specific transactor. Understanding the most common types helps you navigate the codebase and understand protocol capabilities.

Payment

File: src/ripple/app/tx/impl/Payment.cpp

Purpose: Transfer XRP or issued currencies between accounts

Key Features:

  • Direct XRP transfers

  • Issued currency transfers via trust lines

  • Path-based payments (automatic currency conversion)

  • Partial payments (deliver less than requested if full amount unavailable)

Common Fields:

  • Account - Source account

  • Destination - Recipient account

  • Amount - Amount to deliver

  • SendMax (optional) - Maximum amount to send

  • Paths (optional) - Payment paths for currency conversion

  • DestinationTag (optional) - Identifier for the recipient

Use Cases:

  • Simple XRP transfers

  • Issued currency payments

  • Cross-currency payments

  • Payment channel settlements

OfferCreate

File: src/ripple/app/tx/impl/CreateOffer.cpp

Purpose: Place an offer on the decentralized exchange (DEX)

Key Features:

  • Buy or sell any currency pair

  • Immediate-or-cancel orders

  • Fill-or-kill orders

  • Passive offers (don't consume existing offers)

  • Auto-bridging via XRP

Common Fields:

  • TakerPays - Asset the taker (matcher) pays

  • TakerGets - Asset the taker receives

  • Expiration (optional) - When offer expires

  • OfferSequence (optional) - Sequence of offer to replace

Use Cases:

  • Currency exchange

  • Market making

  • Arbitrage

  • Limit orders

OfferCancel

File: src/ripple/app/tx/impl/CancelOffer.cpp

Purpose: Remove an offer from the order book

Key Features:

  • Cancel by offer sequence number

  • Only offer owner can cancel

Common Fields:

  • OfferSequence - Sequence number of offer to cancel

TrustSet

File: src/ripple/app/tx/impl/SetTrust.cpp

Purpose: Create or modify a trust line for issued currencies

Key Features:

  • Set trust limit for a currency

  • Authorize/deauthorize trust lines

  • Configure trust line flags

Common Fields:

  • LimitAmount - Trust line limit and currency

  • QualityIn (optional) - Exchange rate for incoming transfers

  • QualityOut (optional) - Exchange rate for outgoing transfers

Use Cases:

  • Accept issued currencies

  • Set credit limits

  • Freeze trust lines

EscrowCreate

File: src/ripple/app/tx/impl/Escrow.cpp

Purpose: Lock XRP until conditions are met

Key Features:

  • Time-based release (CryptoConditions)

  • Conditional release (Interledger Protocol conditions)

  • Guaranteed delivery or return

Common Fields:

  • Destination - Who can claim the escrow

  • Amount - Amount of XRP to escrow

  • FinishAfter (optional) - Earliest finish time

  • CancelAfter (optional) - When escrow can be cancelled

  • Condition (optional) - Cryptographic condition for release

EscrowFinish

File: src/ripple/app/tx/impl/Escrow.cpp

Purpose: Complete an escrow and deliver XRP

Key Features:

  • Must meet time and/or condition requirements

  • Can be executed by anyone (typically destination)

Common Fields:

  • Owner - Account that created the escrow

  • OfferSequence - Sequence of EscrowCreate transaction

  • Fulfillment (optional) - Fulfillment of cryptographic condition

EscrowCancel

File: src/ripple/app/tx/impl/Escrow.cpp

Purpose: Return escrowed XRP to owner

Key Features:

  • Only after CancelAfter time passes

  • Can be executed by anyone

AccountSet

File: src/ripple/app/tx/impl/SetAccount.cpp

Purpose: Modify account settings and flags

Key Features:

  • Set account flags

  • Configure transfer rate

  • Set domain and message key

  • Configure email hash

Common Fields:

  • SetFlag / ClearFlag - Flags to modify

  • TransferRate (optional) - Fee for transferring issued currencies

  • Domain (optional) - Domain associated with account

  • MessageKey (optional) - Public key for encrypted messaging

Important Flags:

  • asfRequireDest - Require destination tag

  • asfRequireAuth - Require authorization for trust lines

  • asfDisallowXRP - Disallow XRP payments

  • asfDefaultRipple - Enable rippling by default

SignerListSet

File: src/ripple/app/tx/impl/SetSignerList.cpp

Purpose: Create or modify multi-signature configuration

Key Features:

  • Define list of authorized signers

  • Set signing quorum

  • Enable complex authorization schemes

Common Fields:

  • SignerQuorum - Required signature weight

  • SignerEntries - List of authorized signers with weights

PaymentChannelCreate

File: src/ripple/app/tx/impl/PayChan.cpp

Purpose: Open a unidirectional payment channel

Key Features:

  • Lock XRP for fast, off-ledger payments

  • Asynchronous payments with cryptographic claims

  • Efficient micropayments


Creating Custom Transactors

When implementing new features through amendments, you'll often need to create custom transactors. Here's the complete process:

Step 1: Define Transaction Format

Add your transaction type to src/ripple/protocol/TxFormats.cpp:

add(jss::MyCustomTx,
    ttMY_CUSTOM_TX,
    {
        // Required fields
        {sfAccount,         soeREQUIRED},
        {sfDestination,     soeREQUIRED},
        {sfCustomField,     soeREQUIRED},
        
        // Optional fields
        {sfOptionalField,   soeOPTIONAL},
    },
    commonFields);

Step 2: Create Transactor Class

Create src/ripple/app/tx/impl/MyCustomTx.h:

#ifndef RIPPLE_TX_MYCUSTOMTX_H_INCLUDED
#define RIPPLE_TX_MYCUSTOMTX_H_INCLUDED

#include <ripple/app/tx/impl/Transactor.h>

namespace ripple {

class MyCustomTx : public Transactor
{
public:
    static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};

    explicit MyCustomTx(ApplyContext& ctx) : Transactor(ctx) {}

    static NotTEC preflight(PreflightContext const& ctx);
    static TER preclaim(PreclaimContext const& ctx);
    TER doApply() override;
};

} // namespace ripple

#endif

Step 3: Implement Preflight

Create src/ripple/app/tx/impl/MyCustomTx.cpp:

#include <ripple/app/tx/impl/MyCustomTx.h>
#include <ripple/basics/Log.h>
#include <ripple/protocol/Feature.h>

namespace ripple {

NotTEC MyCustomTx::preflight(PreflightContext const& ctx)
{
    // Check if amendment is enabled
    if (!ctx.rules.enabled(featureMyCustomTx))
        return temDISABLED;

    // Perform base class preflight checks
    auto const ret = preflight1(ctx);
    if (!isTesSuccess(ret))
        return ret;

    // Validate custom field format
    if (!ctx.tx.isFieldPresent(sfCustomField))
        return temMALFORMED;

    auto const customValue = ctx.tx[sfCustomField];
    if (customValue < 0 || customValue > 1000000)
        return temBAD_AMOUNT;

    // Additional validation...

    return preflight2(ctx);
}

Step 4: Implement Preclaim

TER MyCustomTx::preclaim(PreclaimContext const& ctx)
{
    // Get account IDs
    AccountID const src = ctx.tx[sfAccount];
    AccountID const dst = ctx.tx[sfDestination];

    // Verify destination account exists
    auto const sleDst = ctx.view.read(keylet::account(dst));
    if (!sleDst)
        return tecNO_DST;

    // Check source account has sufficient balance
    auto const sleSrc = ctx.view.read(keylet::account(src));
    if (!sleSrc)
        return terNO_ACCOUNT;

    auto const balance = (*sleSrc)[sfBalance];
    auto const fee = ctx.tx[sfFee];
    
    if (balance < fee)
        return tecUNFUNDED;

    // Additional state-based validation...

    return tesSUCCESS;
}

Step 5: Implement DoApply

TER MyCustomTx::doApply()
{
    // Pay transaction fee
    auto const result = payFee();
    if (result != tesSUCCESS)
        return result;

    // Get transaction fields
    auto const dst = ctx_.tx[sfDestination];
    auto const customValue = ctx_.tx[sfCustomField];

    // Perform custom logic
    // Example: Create a new ledger object
    auto const sleNew = std::make_shared<SLE>(
        keylet::custom(account_, ctx_.tx.getSeqProxy().value()));
    
    sleNew->setAccountID(sfAccount, account_);
    sleNew->setAccountID(sfDestination, dst);
    sleNew->setFieldU32(sfCustomField, customValue);

    // Insert into ledger
    view().insert(sleNew);

    // Log the operation
    JLOG(j_.trace()) << "MyCustomTx applied successfully";

    return tesSUCCESS;
}

} // namespace ripple

Step 6: Register the Transactor

Add to src/ripple/app/tx/applySteps.cpp:

#include <ripple/app/tx/impl/MyCustomTx.h>

// In the invoke function, add:
case ttMY_CUSTOM_TX:
    return MyCustomTx::makeTxConsequences(ctx);

Step 7: Write Tests

Create src/test/app/MyCustomTx_test.cpp:

#include <ripple/protocol/Feature.h>
#include <ripple/protocol/jss.h>
#include <test/jtx.h>

namespace ripple {
namespace test {

class MyCustomTx_test : public beast::unit_test::suite
{
public:
    void testBasicOperation()
    {
        using namespace jtx;
        Env env(*this, supported_amendments() | featureMyCustomTx);

        // Create test accounts
        Account const alice{"alice"};
        Account const bob{"bob"};
        env.fund(XRP(10000), alice, bob);

        // Submit custom transaction
        Json::Value jv;
        jv[jss::Account] = alice.human();
        jv[jss::Destination] = bob.human();
        jv[jss::TransactionType] = jss::MyCustomTx;
        jv[jss::CustomField] = 12345;
        jv[jss::Fee] = "10";

        env(jv);
        env.close();

        // Verify results
        // Add assertions...
    }

    void run() override
    {
        testBasicOperation();
        // More tests...
    }
};

BEAST_DEFINE_TESTSUITE(MyCustomTx, app, ripple);

} // namespace test
} // namespace ripple

Transaction Lifecycle Within Framework

Understanding how a transaction flows through the transactor framework helps debug issues and optimize performance.

Complete Flow Diagram

Transaction Submission

    Preflight (Static Validation)

    ✓ Pass → Continue
    ✗ Fail → Reject (return tem code)

    Preclaim (State Validation)

    ✓ Pass → Continue
    ✗ Fail → Reject (return tec/ter code)

    Enter Consensus

    Reach Agreement

    DoApply (State Modification)

    ✓ Success → Commit changes
    ✗ Fail → Rollback (still consumes fee)

    Transaction Finalized in Ledger

Error Code Categories

tem (Malformed): Transaction is permanently invalid due to format issues

  • Example: temMALFORMED, temBAD_AMOUNT, temDISABLED

  • Action: Reject immediately, never retry

tef (Failure): Transaction failed during local checks

  • Example: tefFAILURE, tefPAST_SEQ

  • Action: Reject, may indicate client error

ter (Retry): Transaction failed but might succeed later

  • Example: terQUEUED, terPRE_SEQ

  • Action: Can be retried after conditions change

tec (Claimed Fee): Transaction failed but consumed fee

  • Example: tecUNFUNDED, tecNO_DST, tecNO_PERMISSION

  • Action: Failed permanently, fee charged

tes (Success): Transaction succeeded

  • Example: tesSUCCESS

  • Action: Changes committed to ledger


Hands-On Exercise

Exercise: Trace and Modify a Payment Transaction

Objective: Understand the payment transactor implementation through debugging and modification.

Part 1: Code Exploration

Step 1: Navigate to the Payment transactor

cd rippled/src/ripple/app/tx/impl/
open Payment.cpp  # or use your IDE

Step 2: Identify the three phases

Find and read:

  • Payment::preflight() - Lines implementing static checks

  • Payment::preclaim() - Lines checking ledger state

  • Payment::doApply() - Lines modifying state

Step 3: Trace a specific check

Follow how the Payment transactor checks if a destination requires a destination tag:

// In preclaim:
if (sleDst->getFlags() & lsfRequireDestTag)
{
    if (!ctx.tx.isFieldPresent(sfDestinationTag))
        return tecDST_TAG_NEEDED;
}

Questions:

  • Where is lsfRequireDestTag defined?

  • How is this flag set on an account?

  • What transaction type sets this flag?

Part 2: Debug a Payment

Step 1: Set up standalone mode with logging

rippled --conf=rippled.cfg --standalone

Enable transaction logging:

rippled log_level Transaction trace

Step 2: Create test accounts

# Create and fund two accounts
rippled account_info <address1>
rippled account_info <address2>

Step 3: Set destination tag requirement

# Set requireDestTag flag on destination account
rippled submit '{
  "TransactionType": "AccountSet",
  "Account": "<address2>",
  "SetFlag": 1,
  "Fee": "12"
}'

Step 4: Try payment without destination tag

# This should fail with tecDST_TAG_NEEDED
rippled submit '{
  "TransactionType": "Payment",
  "Account": "<address1>",
  "Destination": "<address2>",
  "Amount": "1000000",
  "Fee": "12"
}'

Step 5: Try payment with destination tag

# This should succeed
rippled submit '{
  "TransactionType": "Payment",
  "Account": "<address1>",
  "Destination": "<address2>",
  "Amount": "1000000",
  "DestinationTag": 12345,
  "Fee": "12"
}'

Part 3: Modify the Transactor (Advanced)

Step 1: Add custom logging

Edit Payment.cpp and add logging to doApply():

TER Payment::doApply()
{
    JLOG(j_.info()) << "Payment doApply started";
    JLOG(j_.info()) << "Source: " << account_;
    JLOG(j_.info()) << "Destination: " << ctx_.tx[sfDestination];
    JLOG(j_.info()) << "Amount: " << ctx_.tx[sfAmount];
    
    // ... existing code ...
}

Step 2: Recompile rippled

cd rippled/build
cmake --build . --target rippled

Step 3: Run with your modified code

./rippled --conf=rippled.cfg --standalone

Step 4: Submit a payment and observe your logs

Analysis Questions

Answer these based on your exploration:

  1. What happens in each validation phase?

    • List the checks performed in preflight

    • List the checks performed in preclaim

    • What state modifications occur in doApply?

  2. How are transaction fees handled?

    • Where is payFee() called?

    • What happens if an account can't pay the fee?

  3. How does the code handle XRP vs issued currencies?

    • Find the code that distinguishes between them

    • How do payment paths work for issued currencies?

  4. What's the role of the accountSend() helper?

    • Where is it implemented?

    • What does it do internally?


Key Takeaways

Core Concepts

Three-Phase Validation: Preflight (static), Preclaim (read state), DoApply (modify state) ensures robust transaction processing

Inheritance Architecture: All transaction types inherit from Transactor base class, ensuring consistent behavior

Error Code System: tem/tef/ter/tec/tes codes provide clear feedback about transaction status

Atomic Execution: Transactions either fully succeed or fully fail (except fee consumption)

State Views: Ledger modifications happen in views that can be committed or rolled back

Development Skills

Codebase Location: Transaction implementations in src/ripple/app/tx/impl/

Creating Custom Transactions: Follow the pattern of defining format, implementing phases, registering transactor

Debugging: Use standalone mode and logging to trace transaction execution

Testing: Write comprehensive unit tests for all transaction scenarios

Amendment Integration: New transaction types typically require amendments for activation


Common Patterns and Best Practices

Pattern 1: Check-Then-Act

Always validate before modifying state:

// Bad - might partially modify state before failing
auto const result1 = modifyState1();
auto const result2 = modifyState2();  // If this fails, state1 is modified
if (result2 != tesSUCCESS)
    return result2;

// Good - validate first, then modify
if (!canModifyState1())
    return tecFAILURE;
if (!canModifyState2())
    return tecFAILURE;

modifyState1();
modifyState2();

Pattern 2: Use Helper Functions

The Transactor base class provides many helpers:

// Check sequence number
auto const result = checkSeq();
if (result != tesSUCCESS)
    return result;

// Pay fee
auto const feeResult = payFee();
if (feeResult != tesSUCCESS)
    return feeResult;

// Check authorization
if (!hasAuthority())
    return tecNO_PERMISSION;

Pattern 3: Ledger Object Patterns

Creating, modifying, and deleting ledger objects:

// Read existing object
auto const sle = view().read(keylet::account(accountID));
if (!sle)
    return tecNO_TARGET;

// Modify object
auto sleMutable = view().peek(keylet::account(accountID));
(*sleMutable)[sfBalance] = newBalance;
view().update(sleMutable);

// Create new object
auto const sleNew = std::make_shared<SLE>(keylet);
sleNew->setFieldU32(sfFlags, 0);
view().insert(sleNew);

// Delete object
view().erase(sle);

Additional Resources

Official Documentation

Codebase References

  • src/ripple/app/tx/impl/ - All transactor implementations

  • src/ripple/app/tx/impl/Transactor.h - Base transactor class

  • src/ripple/protocol/TxFormats.cpp - Transaction format definitions

  • src/ripple/protocol/TER.h - Transaction result codes


Next Steps

Now that you understand how transactions are processed through transactors, explore how the Application layer orchestrates all system components.

➡️ Continue to: Application Layer - Central Orchestration

⬅️ Back to: Rippled II Overview


Get Started

Access the course: docs.xrpl-commons.org/core-dev-bootcamp

Got questions? Contact us here: Submit Feedback


© 2025 XRPL Commons - Core Dev Bootcamp

Last updated