> 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/module07/appendices/handler-examples.md).

# Appendix: Handler Examples

## Reference Guide to Production RPC Handlers

[← Back to Building and Integrating Custom RPC Handlers](/core-dev-bootcamp/module07.md)

***

## Introduction

This appendix walks through three actual RPC handlers from the rippled codebase. These handlers are battle-tested, production-ready implementations that demonstrate best practices and patterns you should follow in your own custom handlers.

Each example includes:

* Complete code walkthrough
* Key implementation patterns
* How it integrates with the broader system
* Performance and security considerations

***

## 1. AccountInfo Handler

### Overview

The `AccountInfo` handler queries account state and returns detailed information about an account on the ledger. It's one of the most commonly used RPC commands and demonstrates fundamental patterns.

**Source**: `src/xrpld/rpc/handlers/AccountInfo.cpp`

### Complete Implementation

```cpp
Json::Value doAccountInfo(RPC::JsonContext& context)
{
    // STEP 1: Validate request has required parameters
    if (!context.params.isMember(jss::account)) {
        return rpcError(rpcINVALID_PARAMS);
    }

    // STEP 2: Parse and validate the account identifier
    std::string const strAccountID = context.params[jss::account].asString();

    auto const account = parseBase58<AccountID>(strAccountID);
    if (!account) {
        return rpcError(rpcACT_MALFORMED, "Account is malformed");
    }

    // STEP 3: Get the ledger to query against
    std::shared_ptr<ReadView const> ledger;
    auto const jvResult = RPC::lookupLedger(ledger, context);

    if (!ledger) {
        return jvResult;  // Return error from ledger lookup
    }

    // STEP 4: Query the account state from the ledger
    auto const sleAccount = ledger->read(keylet::account(*account));

    if (!sleAccount) {
        return rpcError(rpcACT_NOT_FOUND, "Account not found");
    }

    // STEP 5: Build the result object
    Json::Value result;
    result[jss::account_data] = sleAccount->getJson(0);
    result[jss::account_flags] = sleAccount->getFieldU32(sfFlags);

    // STEP 6: Include ledger information context
    if (ledger) {
        result[jss::ledger_current_index] =
            ledger->info().seq;
        result[jss::validated] =
            context.ledgerMaster.getValidLedgerIndex() >= ledger->info().seq;
    }

    return result;
}
```

### Key Patterns Demonstrated

**1. Parameter Validation**

```cpp
if (!context.params.isMember(jss::account)) {
    return rpcError(rpcINVALID_PARAMS);
}
```

* Always check for required parameters before accessing
* Use predefined JSON string constants (jss::\*) for consistency
* Return early with error if validation fails

**2. Account Parsing**

```cpp
auto const account = parseBase58<AccountID>(strAccountID);
if (!account) {
    return rpcError(rpcACT_MALFORMED, "Account is malformed");
}
```

* Use template function `parseBase58<>()` for Base58Check decoding
* Check optional return value before using
* Return specific error with descriptive message

**3. Ledger Lookup**

```cpp
std::shared_ptr<ReadView const> ledger;
auto const jvResult = RPC::lookupLedger(ledger, context);
if (!ledger) {
    return jvResult;  // Forward the error response
}
```

* Use `RPC::lookupLedger()` to get appropriate ledger
* Helper function returns error if ledger unavailable
* Forward error response to client

**4. Data Query**

```cpp
auto const sleAccount = ledger->read(keylet::account(*account));
if (!sleAccount) {
    return rpcError(rpcACT_NOT_FOUND, "Account not found");
}
```

* Use keylet functions to locate ledger objects
* Check for null before dereferencing
* Return appropriate "not found" error

**5. Response Construction**

```cpp
Json::Value result;
result[jss::account_data] = sleAccount->getJson(0);
result[jss::ledger_current_index] = ledger->info().seq;
result[jss::validated] = ...;
return result;
```

* Build response as Json::Value object
* Include both requested data and contextual information
* Always return ledger context for validation

### Error Handling

| Error Condition           | Error Code         | Message                   |
| ------------------------- | ------------------ | ------------------------- |
| Missing account parameter | rpcINVALID\_PARAMS | "Missing 'account' field" |
| Malformed account address | rpcACT\_MALFORMED  | "Account is malformed"    |
| No ledger available       | rpcNO\_CURRENT     | "No current ledger"       |
| Account doesn't exist     | rpcACT\_NOT\_FOUND | "Account not found"       |

***

## 2. Submit Handler

### Overview

The `Submit` handler processes transaction submission. It demonstrates more complex patterns including transaction validation, fee calculation, and async processing.

**Source**: `src/xrpld/rpc/handlers/Submit.cpp`

### Implementation Highlights

```cpp
Json::Value doSubmit(RPC::JsonContext& context)
{
    // STEP 1: Require IDENTIFIED or ADMIN role
    if (!context.role.isIdentified()) {
        return rpcError(rpcFORBIDDEN);
    }

    // STEP 2: Check for required tx_json parameter
    if (!context.params.isMember(jss::tx_json)) {
        return rpcError(rpcINVALID_PARAMS, "Missing tx_json");
    }

    // STEP 3: Parse the transaction JSON
    Json::Value const& tx_json = context.params[jss::tx_json];

    auto const txn = Transaction::makeTransaction(tx_json);
    if (!txn) {
        return rpcError(rpcINVALID_PARAMS, "Invalid transaction");
    }

    // STEP 4: Get the current ledger for fee calculation
    auto const ledger = context.ledgerMaster.getClosedLedger();
    if (!ledger) {
        return rpcError(rpcNO_CLOSED, "No closed ledger");
    }

    // STEP 5: Calculate base fee and check minimum
    XRPAmount const base_fee = ledger->baseFeeDrops();
    XRPAmount const tx_fee = txn->getFee();

    if (tx_fee < base_fee) {
        return rpcError(rpcINVALID_PARAMS,
            "Fee too low: " + to_string(tx_fee) +
            " < " + to_string(base_fee));
    }

    // STEP 6: Submit to mempool
    TER const result = context.app.getJobQueue()
        .postTransaction(txn);

    if (result != tesSUCCESS) {
        return rpcError(rpcTXN_FAILED,
            transResultString(result));
    }

    // STEP 7: Build result with transaction details
    Json::Value jvResult;
    jvResult[jss::engine_result] = transResultString(result);
    jvResult[jss::engine_result_code] = result;
    jvResult[jss::tx_blob] = txn->getSigningHash().toString();
    jvResult[jss::tx_json] = txn->getJson(0);

    return jvResult;
}
```

### Key Patterns Demonstrated

**1. Permission Checking**

```cpp
if (!context.role.isIdentified()) {
    return rpcError(rpcFORBIDDEN);
}
```

* Check role permissions early, before processing
* Deny access immediately for unauthorized clients
* Refer to [Authentication and Authorization](https://github.com/XRPL-Commons/xrpl-trainings/blob/main/core-dev-bootcamp/module07/authentication-authorization.md) module

**2. Complex Validation**

```cpp
auto const txn = Transaction::makeTransaction(tx_json);
if (!txn) {
    return rpcError(rpcINVALID_PARAMS, "Invalid transaction");
}
```

* Use specialized parsing functions when available
* Provide specific error messages for validation failures
* Validate all inputs before state-changing operations

**3. Context-Dependent Logic**

```cpp
XRPAmount const base_fee = ledger->baseFeeDrops();
XRPAmount const tx_fee = txn->getFee();

if (tx_fee < base_fee) {
    return rpcError(rpcINVALID_PARAMS, "Fee too low");
}
```

* Factor validation against current ledger state
* Provide helpful error messages with actual values
* Calculate derived values only when needed

**4. Async Operation Handling**

```cpp
TER const result = context.app.getJobQueue()
    .postTransaction(txn);

if (result != tesSUCCESS) {
    return rpcError(rpcTXN_FAILED, transResultString(result));
}
```

* Use job queue for async operations
* Map internal result codes to RPC errors
* Return human-readable error explanations

### Error Scenarios

| Scenario                | Code               | Response                      |
| ----------------------- | ------------------ | ----------------------------- |
| Unauthorized client     | rpcFORBIDDEN       | 403 Forbidden                 |
| Missing tx\_json        | rpcINVALID\_PARAMS | 400 Bad Request               |
| Unparseable transaction | rpcINVALID\_PARAMS | 400 Bad Request               |
| Fee below minimum       | rpcINVALID\_PARAMS | 400 Bad Request with fee info |
| Submission failed       | rpcTXN\_FAILED     | 500 with result code          |

***

## 3. ServerInfo Handler

### Overview

The `ServerInfo` handler returns comprehensive information about the running node. It demonstrates accessing application state and formatting complex hierarchical data.

**Source**: `src/xrpld/rpc/handlers/ServerInfo.cpp`

### Implementation Highlights

```cpp
Json::Value doServerInfo(RPC::JsonContext& context)
{
    // STEP 1: Create the result object
    Json::Value jvResult;

    // STEP 2: Add server information
    jvResult[jss::info][jss::server_state] =
        to_string(context.app.getOPs().getNetworkState());

    // STEP 3: Add ledger information
    auto const ledger =
        context.ledgerMaster.getValidatedLedger();

    if (ledger) {
        jvResult[jss::info][jss::validated_ledger]
            [jss::sequence] = ledger->info().seq;
        jvResult[jss::info][jss::validated_ledger]
            [jss::hash] = to_string(ledger->info().hash);
        jvResult[jss::info][jss::validated_ledger]
            [jss::close_time] = ledger->info().closeTime;
    }

    // STEP 4: Add network information
    const auto& validations =
        context.validators.getValidations();
    jvResult[jss::info][jss::validated_ledgers] =
        context.ledgerMaster.getCompleteLedgers();
    jvResult[jss::info][jss::peers] =
        static_cast<Json::UInt>(context.peers.size());

    // STEP 5: Add configuration details (with permission check)
    if (context.role.isAdmin()) {
        jvResult[jss::config] = context.app.getConfig()
            .getAdminServerJSON();
    }

    // STEP 6: Add resource usage
    jvResult[jss::load_factor] =
        context.app.getOPs().getLoadFactor();

    return jvResult;
}
```

### Key Patterns Demonstrated

**1. Hierarchical JSON Construction**

```cpp
jvResult[jss::info][jss::server_state] = to_string(...);
jvResult[jss::info][jss::validated_ledger][jss::sequence] = ...;
```

* Build nested JSON structures by chaining subscript operators
* Use jss:: constants for all key names
* JSON library auto-creates intermediate objects

**2. Optional Data Handling**

```cpp
auto const ledger = context.ledgerMaster.getValidatedLedger();

if (ledger) {
    jvResult[jss::info][jss::validated_ledger]
        [jss::sequence] = ledger->info().seq;
}
```

* Check for optional data before including
* Leave out fields if data isn't available
* Client code should handle missing optional fields

**3. Role-Based Response Modification**

```cpp
if (context.role.isAdmin()) {
    jvResult[jss::config] = context.app.getConfig()
        .getAdminServerJSON();
}
```

* Tailor response content based on caller's role
* Include sensitive information only for authorized clients
* Refer to [Authentication and Authorization](https://github.com/XRPL-Commons/xrpl-trainings/blob/main/core-dev-bootcamp/module07/authentication-authorization.md)

**4. Accessing Application State**

```cpp
context.app.getOPs().getNetworkState()
context.ledgerMaster.getValidatedLedger()
context.validators.getValidations()
context.app.getConfig()
```

* Use context object to access application components
* Through context.app, access the main Application singleton
* Cache results when making multiple queries

### No Error Handling?

Notably, ServerInfo returns early without extensive error checking because:

1. Server state is always available
2. Missing data is handled by omitting fields
3. No user input to validate
4. No permission that would cause rejection

This demonstrates that **not all handlers need error-heavy validation**. Only validate what the client provides.

***

## Common Implementation Patterns

### Pattern 1: Parameter Extraction and Validation

```cpp
// Safe extraction with validation
if (!context.params.isMember(jss::account)) {
    return rpcError(rpcINVALID_PARAMS);
}

std::string const strAccount =
    context.params[jss::account].asString();

auto const account = parseBase58<AccountID>(strAccount);
if (!account) {
    return rpcError(rpcACT_MALFORMED);
}
```

### Pattern 2: Ledger Access

```cpp
// Get appropriate ledger
std::shared_ptr<ReadView const> ledger;
auto const result = RPC::lookupLedger(ledger, context);

if (!ledger) {
    return result;  // Propagate error
}

// Use ledger safely
auto const sle = ledger->read(keylet);
if (!sle) {
    return rpcError(rpcACT_NOT_FOUND);
}
```

### Pattern 3: Permission Checking

```cpp
// Check permission early
if (!context.role.isAdmin()) {
    return rpcError(rpcNO_PERMISSION);
}

// Now proceed with privileged operations
```

### Pattern 4: Complex Result Building

```cpp
Json::Value result;

// Add required fields
result[jss::field1] = value1;

// Add optional fields conditionally
if (condition) {
    result[jss::optional_field] = optionalValue;
}

// Always include ledger context
result[jss::ledger_current_index] = ledger->info().seq;
result[jss::validated] = isValidated;

return result;
```

***

## Performance Considerations

### 1. Minimize Ledger Queries

```cpp
// BAD: Multiple reads from ledger
auto sle1 = ledger->read(keylet1);
auto sle2 = ledger->read(keylet2);

// GOOD: Batch when possible, cache results
```

### 2. Avoid Synchronous Blocking

```cpp
// BAD: Synchronous wait in handler
context.app.getJobQueue().waitFor(job);

// GOOD: Post async, return immediately
context.app.getJobQueue().post(job);
```

### 3. Check Permissions Early

```cpp
// Check before expensive operations
if (!context.role.isIdentified()) {
    return rpcError(rpcNO_PERMISSION);  // Fast fail
}

// Now do expensive work
```

***

## Testing These Handlers

See [Testing RPC Handlers](/core-dev-bootcamp/module07/testing-rpc-handlers.md) for comprehensive testing strategies.

Quick test template:

```cpp
TEST(AccountInfoTest, ValidAccount) {
    RPC::JsonContext context = setupTestContext();
    context.params[jss::account] = "rN7n7otQDd6FczFgLdlqtyMVrn...";

    auto result = doAccountInfo(context);

    ASSERT_TRUE(result.isMember(jss::account_data));
    ASSERT_EQ(result[jss::validated].asBool(), true);
}
```

***

## Navigation

[← Back to Appendices](/core-dev-bootcamp/module07/appendices.md) | [Helper Functions →](/core-dev-bootcamp/module07/appendices/helper-functions.md)

***

**Related Module Sections**:

* [Implementing Custom Handlers](/core-dev-bootcamp/module07/implementing-custom-handlers.md)
* [Error Handling and Validation](https://github.com/XRPL-Commons/xrpl-trainings/blob/main/core-dev-bootcamp/module07/error-handling-validation.md)
* [Testing RPC Handlers](/core-dev-bootcamp/module07/testing-rpc-handlers.md)
