# Snippets

## Reusable Code Patterns

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

***

## Complete Handler Template

Use this as a starting point:

```cpp
//------------------------------------------------------------------------------
/*
    This file is part of rippled: https://github.com/ripple/rippled
    Copyright (c) 2024 Ripple Labs Inc.
*/
//==============================================================================

#include <xrpld/app/main/Application.h>
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/jss.h>

namespace ripple {

Json::Value doGetAccountBalance(RPC::JsonContext& context)
{
    Json::Value result;

    // TODO: Implement your handler here

    return result;
}

} // namespace ripple
```

***

## Snippet 1: Parameter Validation

```cpp
// Validate required 'account' parameter
if (!context.params.isMember(jss::account))
{
    return rpcError(rpcINVALID_PARAMS, "Missing 'account' field");
}

if (!context.params[jss::account].isString())
{
    return rpcError(rpcINVALID_PARAMS, "'account' must be a string");
}

// Parse account address
std::string const accountStr = context.params[jss::account].asString();

auto const account = parseBase58<AccountID>(accountStr);

if (!account)
{
    return rpcError(rpcACT_MALFORMED, "Invalid account address");
}
```

***

## Snippet 2: Ledger Lookup

```cpp
// Get the ledger to query
std::shared_ptr<ReadView const> ledger;
auto const ledgerResult = RPC::lookupLedger(ledger, context);

if (!ledger)
{
    // lookupLedger returns an error response if ledger not found
    return ledgerResult;
}
```

***

## Snippet 3: Read Account Object

```cpp
// Read account from ledger
auto const sleAccount = ledger->read(keylet::account(*account));

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

***

## Snippet 4: Extract Account Fields

```cpp
// Get account balance
STAmount balance = sleAccount->getFieldAmount(sfBalance);

// Get owner count (number of owned ledger objects)
std::uint32_t ownerCount = sleAccount->getFieldU32(sfOwnerCount);

// Get sequence number (optional - for additional info)
std::uint32_t sequence = sleAccount->getFieldU32(sfSequence);
```

***

## Snippet 5: Calculate Reserves

```cpp
// Get fee information from the ledger
auto const& fees = ledger->fees();

// Calculate base reserve (required for account existence)
XRPAmount baseReserve = fees.accountReserve(0);

// Calculate total reserve (base + owner reserves)
XRPAmount totalReserve = fees.accountReserve(ownerCount);

// Calculate owner reserve specifically
XRPAmount ownerReserve = totalReserve - baseReserve;

// Calculate available balance (what can actually be spent)
XRPAmount available = balance.xrp() - totalReserve;
```

***

## Snippet 6: Build Response

```cpp
// Build the response object
result[jss::account] = to_string(*account);
result[jss::ledger_index] = ledger->info().seq;
result[jss::validated] = ledger->isImmutable();

// Add balance information
result["balance"] = to_string(balance);
result["available_balance"] = to_string(available);

// Add reserve information
result["base_reserve"] = to_string(baseReserve);
result["owner_reserve"] = to_string(ownerReserve);
result["total_reserve"] = to_string(totalReserve);

// Add account metadata
result["owner_count"] = ownerCount;
```

***

## Snippet 7: Complete Minimal Handler

Here's a complete minimal implementation:

```cpp
Json::Value doGetAccountBalance(RPC::JsonContext& context)
{
    // 1. Validate account parameter
    if (!context.params.isMember(jss::account))
        return rpcError(rpcINVALID_PARAMS, "Missing 'account' field");

    auto const account = parseBase58<AccountID>(
        context.params[jss::account].asString()
    );

    if (!account)
        return rpcError(rpcACT_MALFORMED, "Invalid account address");

    // 2. Get ledger
    std::shared_ptr<ReadView const> ledger;
    auto const result = RPC::lookupLedger(ledger, context);
    if (!ledger)
        return result;

    // 3. Read account
    auto const sleAccount = ledger->read(keylet::account(*account));
    if (!sleAccount)
        return rpcError(rpcACT_NOT_FOUND, "Account not found");

    // 4. Extract data
    STAmount balance = sleAccount->getFieldAmount(sfBalance);
    std::uint32_t ownerCount = sleAccount->getFieldU32(sfOwnerCount);

    // 5. Calculate reserves
    auto const& fees = ledger->fees();
    XRPAmount baseReserve = fees.accountReserve(0);
    XRPAmount totalReserve = fees.accountReserve(ownerCount);
    XRPAmount available = balance.xrp() - totalReserve;

    // 6. Build response
    Json::Value response;
    response[jss::account] = to_string(*account);
    response[jss::ledger_index] = ledger->info().seq;
    response[jss::validated] = ledger->isImmutable();
    response["balance"] = to_string(balance);
    response["available_balance"] = to_string(available);
    response["base_reserve"] = to_string(baseReserve);
    response["owner_reserve"] = to_string(totalReserve - baseReserve);
    response["total_reserve"] = to_string(totalReserve);
    response["owner_count"] = ownerCount;

    return response;
}
```

***

## Snippet 8: Handler Registration

Add to `src/xrpld/rpc/handlers/Handlers.cpp`:

```cpp
{
    "get_account_balance",
    {
        &doGetAccountBalance,
        Role::USER,
        RPC::NEEDS_CURRENT_LEDGER
    }
},
```

***

## Snippet 9: Test Case Template

```cpp
#include <xrpl/protocol/jss.h>
#include <test/jtx.h>

namespace ripple {
namespace test {

class GetAccountBalance_test : public beast::unit_test::suite
{
public:
    void testBasicFunctionality()
    {
        using namespace jtx;

        Env env(*this);

        // Create test account with 1000 XRP
        Account alice("alice");
        env.fund(XRP(1000), alice);
        env.close();

        // Build RPC request parameters
        Json::Value params;
        params[jss::account] = alice.human();
        params[jss::ledger_index] = "current";

        // Create context (simplified for example)
        // In real tests, you'd use proper context construction

        // Call the handler
        // Json::Value result = doGetAccountBalance(context);

        // Verify response
        // BEAST_EXPECT(result.isMember("balance"));
        // BEAST_EXPECT(result["balance"].asString() == "1000000000");
    }

    void run() override
    {
        testBasicFunctionality();
    }
};

BEAST_DEFINE_TESTSUITE(GetAccountBalance, rpc, ripple);

} // namespace test
} // namespace ripple
```

***

## Snippet 10: Error Handling Pattern

```cpp
// Try-catch for unexpected errors
try
{
    // Your handler logic here
}
catch (std::exception const& ex)
{
    return rpcError(rpcINTERNAL, ex.what());
}
```

***

## Snippet 11: Optional Parameter Handling

```cpp
// Handle optional 'include_lines' parameter
bool includeLines = false;

if (context.params.isMember("include_lines"))
{
    if (!context.params["include_lines"].isBool())
    {
        return rpcError(rpcINVALID_PARAMS, "'include_lines' must be boolean");
    }

    includeLines = context.params["include_lines"].asBool();
}

if (includeLines)
{
    // Add trust line information to response
    Json::Value lines(Json::arrayValue);

    // ... iterate trust lines ...

    result["lines"] = lines;
}
```

***

## Snippet 12: Trust Line Iteration (Bonus)

For the optional enhancement:

```cpp
// Get account's trust lines
auto const sleRippleState = ledger->read(
    keylet::line(*account, issuerID, currency)
);

if (sleRippleState)
{
    STAmount balance = sleRippleState->getFieldAmount(sfBalance);

    Json::Value line;
    line["currency"] = to_string(currency);
    line["balance"] = to_string(balance);

    lines.append(line);
}
```

***

## Snippet 13: Pagination Support (Bonus)

```cpp
// Handle pagination parameters
unsigned int limit = 50;  // Default
std::optional<std::string> marker;

if (context.params.isMember("limit"))
{
    if (!context.params["limit"].isUInt())
        return rpcError(rpcINVALID_PARAMS, "'limit' must be unsigned int");

    limit = context.params["limit"].asUInt();

    if (limit == 0 || limit > 400)
        return rpcError(rpcINVALID_PARAMS, "'limit' must be 1-400");
}

if (context.params.isMember("marker"))
{
    if (!context.params["marker"].isString())
        return rpcError(rpcINVALID_PARAMS, "'marker' must be string");

    marker = context.params["marker"].asString();
}
```

***

## Snippet 14: Logging for Debugging

```cpp
// Add logging statements
JLOG(context.j.debug())
    << "GetAccountBalance: Processing account " << to_string(*account);

JLOG(context.j.trace())
    << "GetAccountBalance: Balance = " << to_string(balance)
    << ", Owner Count = " << ownerCount;

// For errors
JLOG(context.j.warn())
    << "GetAccountBalance: Account not found: " << accountStr;
```

***

## Snippet 15: Validation Helper Function

Create a helper to reduce repetition:

```cpp
namespace {

// Helper function for parameter validation
std::optional<std::string> validateStringParam(
    Json::Value const& params,
    std::string const& fieldName,
    bool required = true)
{
    if (!params.isMember(fieldName))
    {
        if (required)
            return "Missing '" + fieldName + "' field";
        return std::nullopt;
    }

    if (!params[fieldName].isString())
    {
        return "'" + fieldName + "' must be a string";
    }

    return std::nullopt;
}

} // anonymous namespace

// Usage in handler:
if (auto error = validateStringParam(context.params, jss::account))
{
    return rpcError(rpcINVALID_PARAMS, *error);
}
```

***

## How to Use These Snippets

1. **Start with the template** (Snippet 0)
2. **Add validation** (Snippet 1)
3. **Get the ledger** (Snippet 2)
4. **Read account data** (Snippets 3-4)
5. **Calculate reserves** (Snippet 5)
6. **Build response** (Snippet 6)
7. **Register handler** (Snippet 8)
8. **Write tests** (Snippet 9)

**Or use the complete minimal handler** (Snippet 7) and build from there!

***

## Important Notes

⚠️ **These snippets are educational examples**

* Always validate your own code
* Add proper error handling
* Follow Rippled's style guide
* Write comprehensive tests

✅ **Best Practices**

* Read existing handlers for reference
* Use helper functions from RPCHelpers.h
* Keep code readable and maintainable
* Document complex logic

***

[← Back to Workshop](/core-dev-bootcamp/module08/homeworks.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/homeworks/snippets.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.
