# Fee and Sequence Handling

[← Back to Transactors: Understanding the Lifecycle of a Transaction](/core-dev-bootcamp/module03bis.md)

***

### Introduction

Transaction fees and sequence numbers are fundamental mechanisms that ensure the XRP Ledger operates securely and efficiently. Fees prevent spam and compensate the network for processing transactions, while sequence numbers prevent replay attacks and ensure transaction ordering.

Understanding how these mechanisms work is essential for implementing transactors correctly and for building applications that submit transactions reliably.

***

### Transaction Fees

Every transaction on the XRP Ledger requires a fee, paid in XRP. This fee is **destroyed** (burned), permanently removing it from circulation.

#### Base Fee

The base fee is the minimum fee for a standard transaction:

```cpp
static XRPAmount
Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
{
    // Get the reference fee from the ledger
    return view.fees().base;
}
```

The current reference base fee is 10 drops (0.00001 XRP).

#### Fee Calculation

Different transaction types may have different fee multipliers:

```cpp
// Some transactions cost more than the base fee
// For example, multi-signed transactions cost more per signature
XRPAmount baseFee = calculateBaseFee(view, tx);

// Account for additional signatures
if (tx.isFieldPresent(sfSigners))
{
    auto const& signers = tx.getFieldArray(sfSigners);
    baseFee = baseFee * (1 + signers.size());
}
```

#### Fee Escalation (Transaction Queue)

When the network is busy, the required fee increases:

```cpp
static XRPAmount
Transactor::minimumFee(
    Application& app,
    XRPAmount baseFee,
    Fees const& fees,
    ApplyFlags flags)
{
    // During high load, fees escalate
    if (flags & tapNO_ESCALATION)
        return baseFee;

    return app.getTxQ().minimumFee(baseFee);
}
```

#### Fee Checking in Preclaim

The base `Transactor` class checks fees during preclaim:

```cpp
static TER
Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
{
    auto const feePaid = ctx.tx[sfFee].xrp();

    // Fee must be non-negative
    if (feePaid < beast::zero)
        return temBAD_FEE;

    // Fee must be sufficient
    if (feePaid < baseFee)
        return telINSUF_FEE_P;

    // Account must have enough to pay fee
    auto const sle = ctx.view.read(keylet::account(ctx.tx[sfAccount]));
    auto const balance = (*sle)[sfBalance].xrp();

    if (balance < feePaid)
        return terINSUF_FEE_B;

    return tesSUCCESS;
}
```

#### Fee Payment

Fees are paid at the start of `doApply`:

```cpp
TER
Transactor::payFee()
{
    auto const feePaid = ctx_.tx[sfFee].xrp();

    // Get the account SLE
    auto const sle = view().peek(keylet::account(account_));

    // Deduct the fee
    auto const balance = sle->getFieldAmount(sfBalance);
    sle->setFieldAmount(sfBalance, balance - feePaid);

    // The fee is destroyed (no destination)
    return tesSUCCESS;
}
```

***

### Account Reserves

Accounts must maintain a minimum XRP balance called the **reserve**. The reserve has two components:

1. **Base Reserve**: Fixed amount every account must hold (currently 1 XRP)
2. **Owner Reserve**: Additional amount per owned object (currently 0.2 XRP per object)

#### Reserve Calculation

```cpp
STAmount accountReserve = view().fees().accountReserve(ownerCount);

// This is equivalent to:
// baseReserve + (ownerCount * ownerReserve)
// e.g., 1 XRP + (5 objects * 0.2 XRP) = 2 XRP
```

#### Checking Reserves Before Creating Objects

Before creating a new object that will increase the owner count, verify the account can afford it:

```cpp
TER CreateCheck::doApply()
{
    auto const sle = view().peek(keylet::account(account_));

    // Calculate reserve with one additional object
    STAmount const reserve{
        view().fees().accountReserve(
            sle->getFieldU32(sfOwnerCount) + 1)};

    // Use mPriorBalance (before fee deduction)
    if (mPriorBalance < reserve)
        return tecINSUFFICIENT_RESERVE;

    // Continue with object creation...
}
```

**Why `mPriorBalance`?**

Using the balance before fee deduction allows accounts to use reserve XRP to pay transaction fees. This is important for:

* Deleting objects when an account is low on funds
* Sending the last XRP out of an account

***

### Sequence Numbers

Each account has a sequence number that starts at 1 and increments with each transaction. This prevents:

* **Replay attacks**: A transaction can only be applied once
* **Transaction ordering issues**: Transactions are applied in sequence order

#### Sequence Number Checking

```cpp
static NotTEC
Transactor::checkSeqProxy(ReadView const& view, STTx const& tx, beast::Journal j)
{
    auto const account = tx[sfAccount];
    auto const sle = view.read(keylet::account(account));

    if (!sle)
        return terNO_ACCOUNT;

    auto const txSeq = tx.getSequence();
    auto const acctSeq = (*sle)[sfSequence];

    if (txSeq != acctSeq)
    {
        if (txSeq < acctSeq)
            return tefPAST_SEQ;  // Already used
        else
            return terPRE_SEQ;   // Too high, need earlier tx first
    }

    return tesSUCCESS;
}
```

#### Sequence Number Consumption

After a successful transaction (or `tec` failure), the sequence number is incremented:

```cpp
TER
Transactor::consumeSeqProxy(SLE::pointer const& sleAccount)
{
    auto const txSeq = ctx_.tx.getSequence();
    auto const acctSeq = (*sleAccount)[sfSequence];

    // Increment the account sequence
    (*sleAccount)[sfSequence] = acctSeq + 1;

    return tesSUCCESS;
}
```

***

### Tickets

Tickets provide an alternative to strict sequence ordering. A ticket is a pre-reserved sequence number that can be used later.

#### Creating Tickets

The `TicketCreate` transaction reserves a range of sequence numbers:

```cpp
// Reserve 10 tickets
{
    "TransactionType": "TicketCreate",
    "Account": "rAccount...",
    "TicketCount": 10
}
```

#### Using Tickets

Instead of `Sequence`, use `TicketSequence`:

```cpp
// Use ticket #5 instead of the account sequence
{
    "TransactionType": "Payment",
    "Account": "rAccount...",
    "TicketSequence": 5,
    // No "Sequence" field
}
```

#### Ticket Handling in Transactors

```cpp
// Get the sequence value (works for both regular sequence and tickets)
std::uint32_t const seq = ctx_.tx.getSeqValue();

// For creating objects, use this value as the identifier
Keylet const checkKeylet = keylet::check(account_, seq);
```

***

### LastLedgerSequence

Transactions can specify a maximum ledger sequence for inclusion:

```cpp
{
    "TransactionType": "Payment",
    "Account": "rAccount...",
    "Sequence": 42,
    "LastLedgerSequence": 75000000  // Expire after this ledger
}
```

If the transaction is not included by this ledger, it becomes invalid:

```cpp
static NotTEC
Transactor::checkPriorTxAndLastLedger(PreclaimContext const& ctx)
{
    // Check LastLedgerSequence if present
    if (auto const lastLedger = ctx.tx[~sfLastLedgerSequence])
    {
        if (ctx.view.seq() > *lastLedger)
            return tefMAX_LEDGER;  // Transaction expired
    }

    return tesSUCCESS;
}
```

**Best Practice:** Always set `LastLedgerSequence` to prevent transactions from being stuck indefinitely. A common value is `current_ledger + 4`.

***

### mPriorBalance and mSourceBalance

The `Transactor` base class tracks two balance values:

```cpp
XRPAmount mPriorBalance;   // Balance before transaction processing
XRPAmount mSourceBalance;  // Balance after fee deduction
```

These are set during the `reset()` method before `doApply()` runs:

```cpp
std::pair<TER, XRPAmount>
Transactor::reset(XRPAmount fee)
{
    auto const sle = view().peek(keylet::account(account_));

    // Store balance before fee
    mPriorBalance = (*sle)[sfBalance].xrp();

    // Deduct fee and store
    mSourceBalance = mPriorBalance - fee;

    // Update the ledger
    (*sle)[sfBalance] = mSourceBalance;

    return {tesSUCCESS, fee};
}
```

**Usage:**

* Use `mPriorBalance` for reserve checks (allows fee payment from reserves)
* Use `mSourceBalance` for balance-dependent operations (actual available funds)

***

### Fee-Related Result Codes

| Code                      | Meaning          | When Returned                      |
| ------------------------- | ---------------- | ---------------------------------- |
| `temBAD_FEE`              | Fee is malformed | Negative fee                       |
| `telINSUF_FEE_P`          | Fee too low      | Below minimum for network load     |
| `terINSUF_FEE_B`          | Can't afford fee | Balance < fee                      |
| `tecINSUFFICIENT_RESERVE` | Reserve not met  | Creating object without enough XRP |

***

### Sequence-Related Result Codes

| Code            | Meaning               | When Returned                            |
| --------------- | --------------------- | ---------------------------------------- |
| `tefPAST_SEQ`   | Sequence already used | Transaction replayed or sequence too low |
| `terPRE_SEQ`    | Sequence too high     | Earlier transaction not yet applied      |
| `tefMAX_LEDGER` | Transaction expired   | LastLedgerSequence exceeded              |
| `tefNO_TICKET`  | Ticket not found      | TicketSequence doesn't exist             |

***

### Best Practices

1. **Always set LastLedgerSequence**: Prevent stuck transactions
2. **Check reserves before creating objects**: Use `mPriorBalance`
3. **Handle sequence gaps**: Use tickets for out-of-order transactions
4. **Account for fee escalation**: During high load, fees increase
5. **Don't hardcode fees**: Query the current fee level
6. **Consider ticket usage**: For systems that need flexible ordering

***

### Codebase References

| File                                     | Description                              |
| ---------------------------------------- | ---------------------------------------- |
| `src/xrpld/app/tx/detail/Transactor.cpp` | Fee and sequence handling implementation |
| `src/xrpld/app/misc/TxQ.cpp`             | Transaction queue and fee escalation     |
| `include/xrpl/protocol/Fees.h`           | Fee structure definitions                |


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.xrpl-commons.org/core-dev-bootcamp/module03bis/fee-sequence-handling.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
