# 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)


---

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