Compare commits

...

2 Commits

Author SHA1 Message Date
JCW
78d8f89267 Updated the template
Signed-off-by: JCW <a1q123456@users.noreply.github.com>
2026-02-26 18:31:40 +00:00
JCW
34c49ff166 Add code generator
Signed-off-by: JCW <a1q123456@users.noreply.github.com>
2026-02-26 16:36:44 +00:00
16 changed files with 1995 additions and 2 deletions

4
.gitignore vendored
View File

@@ -80,3 +80,7 @@ DerivedData
# clangd cache
/.cache
# Auto-generated protocol wrapper classes (generated at CMake configure time)
/include/xrpl/protocol_autogen/transactions/
/include/xrpl/protocol_autogen/ledger_objects/

View File

@@ -74,15 +74,23 @@ add_module(xrpl protocol)
target_link_libraries(xrpl.libxrpl.protocol PUBLIC xrpl.libxrpl.crypto xrpl.libxrpl.json)
# Level 05
add_module(xrpl protocol_autogen)
target_link_libraries(xrpl.libxrpl.protocol_autogen PUBLIC xrpl.libxrpl.protocol)
# Set up code generation for protocol_autogen module
include(XrplProtocolAutogen)
setup_protocol_autogen()
# Level 06
add_module(xrpl core)
target_link_libraries(xrpl.libxrpl.core PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.json
xrpl.libxrpl.protocol)
# Level 06
# Level 07
add_module(xrpl resource)
target_link_libraries(xrpl.libxrpl.resource PUBLIC xrpl.libxrpl.protocol)
# Level 07
# Level 08
add_module(xrpl net)
target_link_libraries(xrpl.libxrpl.net PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.json
xrpl.libxrpl.protocol xrpl.libxrpl.resource)
@@ -140,6 +148,7 @@ target_link_modules(
net
nodestore
protocol
protocol_autogen
rdb
resource
server

View File

@@ -29,6 +29,7 @@ install(TARGETS common
xrpl.libxrpl.net
xrpl.libxrpl.nodestore
xrpl.libxrpl.protocol
xrpl.libxrpl.protocol_autogen
xrpl.libxrpl.resource
xrpl.libxrpl.server
xrpl.libxrpl.shamap

View File

@@ -0,0 +1,132 @@
#[===================================================================[
Protocol Autogen - Code generation for protocol wrapper classes
#]===================================================================]
# Function to set up code generation for protocol_autogen module
# This runs at configure time to generate C++ wrapper classes from macro files
function (setup_protocol_autogen)
# Directory paths
set(MACRO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include/xrpl/protocol/detail")
set(AUTOGEN_HEADER_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include/xrpl/protocol_autogen")
set(SCRIPTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/scripts")
# Input macro files
set(TRANSACTIONS_MACRO "${MACRO_DIR}/transactions.macro")
set(LEDGER_ENTRIES_MACRO "${MACRO_DIR}/ledger_entries.macro")
set(SFIELDS_MACRO "${MACRO_DIR}/sfields.macro")
# Python scripts
set(GENERATE_TX_SCRIPT "${SCRIPTS_DIR}/generate_tx_classes.py")
set(GENERATE_LEDGER_SCRIPT "${SCRIPTS_DIR}/generate_ledger_classes.py")
set(REQUIREMENTS_FILE "${SCRIPTS_DIR}/requirements.txt")
# Create output directories
file(MAKE_DIRECTORY "${AUTOGEN_HEADER_DIR}/transactions")
file(MAKE_DIRECTORY "${AUTOGEN_HEADER_DIR}/ledger_objects")
# Find Python3 - check if already found by Conan or find it ourselves
if (NOT Python3_EXECUTABLE)
find_package(Python3 COMPONENTS Interpreter QUIET)
endif ()
if (NOT Python3_EXECUTABLE)
# Try finding python3 executable directly
find_program(Python3_EXECUTABLE NAMES python3 python)
endif ()
if (NOT Python3_EXECUTABLE)
message(FATAL_ERROR "Python3 not found. Code generation cannot proceed.")
return()
endif ()
message(STATUS "Using Python3 for code generation: ${Python3_EXECUTABLE}")
# Set up Python virtual environment for code generation
set(VENV_DIR "${CMAKE_CURRENT_BINARY_DIR}/codegen_venv")
# Determine the Python executable path in the venv
if (WIN32)
set(VENV_PYTHON "${VENV_DIR}/Scripts/python.exe")
set(VENV_PIP "${VENV_DIR}/Scripts/pip.exe")
else ()
set(VENV_PYTHON "${VENV_DIR}/bin/python")
set(VENV_PIP "${VENV_DIR}/bin/pip")
endif ()
# Check if venv needs to be created or updated
set(VENV_NEEDS_UPDATE FALSE)
if (NOT EXISTS "${VENV_PYTHON}")
set(VENV_NEEDS_UPDATE TRUE)
message(STATUS "Creating Python virtual environment for code generation...")
elseif ("${REQUIREMENTS_FILE}" IS_NEWER_THAN "${VENV_DIR}/.requirements_installed")
set(VENV_NEEDS_UPDATE TRUE)
message(STATUS "Updating Python virtual environment (requirements changed)...")
endif ()
# Create/update virtual environment if needed
if (VENV_NEEDS_UPDATE)
message(STATUS "Setting up Python virtual environment at ${VENV_DIR}")
execute_process(COMMAND ${Python3_EXECUTABLE} -m venv "${VENV_DIR}"
RESULT_VARIABLE VENV_RESULT ERROR_VARIABLE VENV_ERROR)
if (NOT VENV_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to create virtual environment: ${VENV_ERROR}")
endif ()
message(STATUS "Installing Python dependencies...")
execute_process(COMMAND ${VENV_PIP} install --upgrade pip RESULT_VARIABLE PIP_UPGRADE_RESULT
OUTPUT_QUIET ERROR_VARIABLE PIP_UPGRADE_ERROR)
if (NOT PIP_UPGRADE_RESULT EQUAL 0)
message(WARNING "Failed to upgrade pip: ${PIP_UPGRADE_ERROR}")
endif ()
execute_process(COMMAND ${VENV_PIP} install -r "${REQUIREMENTS_FILE}"
RESULT_VARIABLE PIP_INSTALL_RESULT ERROR_VARIABLE PIP_INSTALL_ERROR)
if (NOT PIP_INSTALL_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to install Python dependencies: ${PIP_INSTALL_ERROR}")
endif ()
# Mark requirements as installed
file(TOUCH "${VENV_DIR}/.requirements_installed")
message(STATUS "Python virtual environment ready")
endif ()
# Generate transaction classes at configure time
message(STATUS "Generating transaction classes from transactions.macro...")
execute_process(COMMAND ${VENV_PYTHON} "${GENERATE_TX_SCRIPT}" "${TRANSACTIONS_MACRO}"
--header-dir "${AUTOGEN_HEADER_DIR}/transactions" --sfields-macro
"${SFIELDS_MACRO}"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
RESULT_VARIABLE TX_GEN_RESULT
OUTPUT_VARIABLE TX_GEN_OUTPUT
ERROR_VARIABLE TX_GEN_ERROR)
if (NOT TX_GEN_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to generate transaction classes:\n${TX_GEN_ERROR}")
else ()
message(STATUS "Transaction classes generated successfully")
endif ()
# Generate ledger entry classes at configure time
message(STATUS "Generating ledger entry classes from ledger_entries.macro...")
execute_process(COMMAND ${VENV_PYTHON} "${GENERATE_LEDGER_SCRIPT}" "${LEDGER_ENTRIES_MACRO}"
--header-dir "${AUTOGEN_HEADER_DIR}/ledger_objects" --sfields-macro
"${SFIELDS_MACRO}"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
RESULT_VARIABLE LEDGER_GEN_RESULT
OUTPUT_VARIABLE LEDGER_GEN_OUTPUT
ERROR_VARIABLE LEDGER_GEN_ERROR)
if (NOT LEDGER_GEN_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to generate ledger entry classes:\n${LEDGER_GEN_ERROR}")
else ()
message(STATUS "Ledger entry classes generated successfully")
endif ()
# Add the generated header directory to the module's include path
target_include_directories(
xrpl.libxrpl.protocol_autogen PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
# Install generated headers
install(DIRECTORY "${AUTOGEN_HEADER_DIR}/"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/xrpl/protocol_autogen" FILES_MATCHING
PATTERN "*.h")
endfunction ()

View File

@@ -0,0 +1,133 @@
#pragma once
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <optional>
#include <string>
namespace xrpl::ledger_entries {
/**
* @brief Base class for type-safe ledger entry wrappers.
*
* This class provides common functionality for all ledger entry types,
* including access to common fields (sfLedgerIndex, sfLedgerEntryType, sfFlags).
*
* This is an immutable wrapper around SLE (Serialized Ledger Entry).
* Use the corresponding Builder classes to construct new ledger entries.
*/
class LedgerEntryBase
{
public:
/**
* @brief Construct a ledger entry wrapper from an existing SLE object.
* @param sle The underlying serialized ledger entry to wrap
*/
explicit LedgerEntryBase(SLE const& sle) : sle_(sle)
{
}
/**
* @brief Get the ledger entry type.
* @return The type of this ledger entry
*/
[[nodiscard]]
LedgerEntryType
getType() const
{
return sle_.getType();
}
/**
* @brief Get the key (index) of this ledger entry.
*
* The key uniquely identifies this ledger entry in the ledger state.
* @return A constant reference to the 256-bit key
*/
[[nodiscard]]
uint256 const&
getKey() const
{
return sle_.key();
}
// Common field getters (from LedgerFormats.cpp commonFields)
/**
* @brief Get the ledger index (sfLedgerIndex).
*
* This field is OPTIONAL and represents the index of the ledger entry.
* @return The ledger index if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<uint256>
getLedgerIndex() const
{
if (sle_.isFieldPresent(sfLedgerIndex))
{
return sle_.at(sfLedgerIndex);
}
return std::nullopt;
}
/**
* @brief Check if the ledger entry has a ledger index.
* @return true if sfLedgerIndex is present, false otherwise
*/
[[nodiscard]]
bool
hasLedgerIndex() const
{
return sle_.isFieldPresent(sfLedgerIndex);
}
/**
* @brief Get the ledger entry type field (sfLedgerEntryType).
*
* This field is REQUIRED for all ledger entries and indicates the type
* of the ledger entry (e.g., AccountRoot, RippleState, Offer, etc.).
* @return The ledger entry type as a 16-bit unsigned integer
*/
[[nodiscard]]
uint16_t
getLedgerEntryType() const
{
return sle_.at(sfLedgerEntryType);
}
/**
* @brief Get the flags field (sfFlags).
*
* This field is REQUIRED for all ledger entries and contains
* type-specific flags that modify the behavior of the ledger entry.
* @return The flags value as a 32-bit unsigned integer
*/
[[nodiscard]]
std::uint32_t
getFlags() const
{
return sle_.at(sfFlags);
}
/**
* @brief Get the underlying SLE object.
*
* Provides direct access to the wrapped serialized ledger entry object
* for cases where the type-safe accessors are insufficient.
* @return A constant reference to the underlying SLE object
*/
[[nodiscard]]
SLE const&
getSle() const
{
return sle_;
}
protected:
/** @brief The underlying serialized ledger entry being wrapped. */
SLE const& sle_;
};
} // namespace xrpl::ledger_entries

View File

@@ -0,0 +1,75 @@
#pragma once
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STObject.h>
namespace xrpl::ledger_entries {
/**
* Base class for all ledger entry builders.
* Provides common field setters that are available for all ledger entry types.
*/
template <typename Derived>
class LedgerEntryBuilderBase
{
public:
/**
* Set the ledger index.
* @param value Ledger index
* @return Reference to the derived builder for method chaining.
*/
Derived&
setLedgerIndex(uint256 const& value)
{
object_[sfLedgerIndex] = value;
return static_cast<Derived&>(*this);
}
/**
* Set the flags.
* @param value Flags value
* @return Reference to the derived builder for method chaining.
*/
Derived&
setFlags(uint32_t value)
{
object_.setFieldU32(sfFlags, value);
return static_cast<Derived&>(*this);
}
/**
* @brief Factory method to create a new instance of the derived builder.
*
* Creates a default-constructed builder instance. It is recommended to use
* this factory method instead of directly constructing the derived type to
* avoid creating unnecessary temporary objects.
* @return A new instance of the derived builder type
*/
static Derived
create()
{
return Derived{};
}
/**
* @brief Factory method to create an instance of the derived builder from an existing SLE.
*
* Creates a builder instance initialized with data from an existing serialized
* ledger entry. It is recommended to use this factory method instead of directly
* constructing the derived type to avoid creating unnecessary temporary objects.
* @param sle The existing serialized ledger entry to initialize from
* @return A new instance of the derived builder type initialized with the SLE data
*/
static Derived
create(SLE const& sle)
{
return Derived{sle};
}
protected:
STObject object_{sfLedgerEntry};
};
} // namespace xrpl::ledger_entries

View File

@@ -0,0 +1,64 @@
<!-- cspell:words pyparsing -->
# Protocol Autogen
This directory contains auto-generated C++ wrapper classes for XRP Ledger protocol types.
## Generated Files
The files in this directory are automatically generated at **CMake configure time** from macro definition files:
- **Transaction classes** (in `transactions/`): Generated from `include/xrpl/protocol/detail/transactions.macro` by `scripts/generate_tx_classes.py`
- **Ledger entry classes** (in `ledger_objects/`): Generated from `include/xrpl/protocol/detail/ledger_entries.macro` by `scripts/generate_ledger_classes.py`
## Generation Process
The generation happens automatically when you **configure** the project (not during build). When you run CMake, the system:
1. Creates a Python virtual environment in the build directory (`codegen_venv`)
2. Installs Python dependencies from `scripts/requirements.txt` into the venv (only if needed)
3. Runs the Python generation scripts using the venv Python interpreter
4. Parses the macro files to extract type definitions
5. Generates type-safe C++ wrapper classes using Jinja2 templates
6. Places the generated headers in this directory
### When Regeneration Happens
The code is regenerated when:
- You run CMake configure for the first time
- The Python virtual environment doesn't exist
- `scripts/requirements.txt` has been modified
To force regeneration, delete the build directory and reconfigure.
### Python Dependencies
The code generation requires the following Python packages (automatically installed):
- `pcpp` - C preprocessor for Python
- `pyparsing` - Parser combinator library
- `Jinja2` - Template engine
These are isolated in a virtual environment and won't affect your system Python installation.
## Version Control
The generated `.h` files are **not checked into version control** - they are listed in `.gitignore`.
This means:
- Every developer needs Python 3 installed to configure the project
- CI/CD systems must run CMake configure to generate the files
- Generated files are always fresh and match the current macro definitions
## Modifying Generated Code
**Do not manually edit files in this directory.** Any changes will be overwritten the next time CMake configure runs.
To modify the generated classes:
- Edit the macro files in `include/xrpl/protocol/detail/`
- Edit the Jinja2 templates in `scripts/templates/`
- Edit the generation scripts in `scripts/`
- Update Python dependencies in `scripts/requirements.txt`
- Run CMake configure to regenerate

View File

@@ -0,0 +1,442 @@
#pragma once
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAccount.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TxFormats.h>
#include <optional>
#include <string>
namespace xrpl::transactions {
/**
* @brief Base class for all transaction wrapper types.
*
* Provides type-safe read-only accessors for common transaction fields.
* This is an immutable wrapper around STTx. Use the corresponding Builder classes
* to construct new transactions.
*/
class TransactionBase
{
public:
/**
* @brief Construct a transaction wrapper from an existing STTx object.
* @param tx The underlying transaction object to wrap
*/
explicit TransactionBase(STTx const& tx) : tx_(tx)
{
}
/**
* @brief Validate the transaction using passesLocalChecks.
* @param reason Output parameter for validation failure reason
* @return true if validation passes, false otherwise
*/
[[nodiscard]]
bool
validate(std::string& reason) const
{
return passesLocalChecks(tx_, reason);
}
/**
* @brief Get the transaction type.
* @return The type of this transaction
*/
[[nodiscard]]
xrpl::TxType
getTransactionType() const
{
return tx_.getTxnType();
}
/**
* @brief Get the account initiating the transaction (sfAccount).
*
* This field is REQUIRED for all transactions.
* @return The account ID of the transaction sender
*/
[[nodiscard]]
AccountID
getAccount() const
{
return tx_.at(sfAccount);
}
/**
* @brief Get the sequence number of the transaction (sfSequence).
*
* This field is REQUIRED for all transactions.
* @return The sequence number
*/
[[nodiscard]]
std::uint32_t
getSequence() const
{
return tx_.at(sfSequence);
}
/**
* @brief Get the transaction fee (sfFee).
*
* This field is REQUIRED for all transactions.
* @return The fee amount
*/
[[nodiscard]]
STAmount
getFee() const
{
return tx_.at(sfFee);
}
/**
* @brief Get the signing public key (sfSigningPubKey).
*
* This field is REQUIRED for all transactions.
* @return The public key used for signing as a blob
*/
[[nodiscard]]
Blob
getSigningPubKey() const
{
return tx_.getFieldVL(sfSigningPubKey);
}
/**
* @brief Get the transaction flags (sfFlags).
*
* This field is OPTIONAL.
* @return The flags value if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<uint32_t>
getFlags() const
{
if (tx_.isFieldPresent(sfFlags))
return tx_.at(sfFlags);
return std::nullopt;
}
/**
* @brief Check if the transaction has flags set.
* @return true if sfFlags is present, false otherwise
*/
[[nodiscard]]
bool
hasFlags() const
{
return tx_.isFieldPresent(sfFlags);
}
/**
* @brief Get the source tag (sfSourceTag).
*
* This field is OPTIONAL and used to identify the source of a payment.
* @return The source tag value if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<uint32_t>
getSourceTag() const
{
if (tx_.isFieldPresent(sfSourceTag))
return tx_.at(sfSourceTag);
return std::nullopt;
}
/**
* @brief Check if the transaction has a source tag.
* @return true if sfSourceTag is present, false otherwise
*/
[[nodiscard]]
bool
hasSourceTag() const
{
return tx_.isFieldPresent(sfSourceTag);
}
/**
* @brief Get the previous transaction ID (sfPreviousTxnID).
*
* This field is OPTIONAL and used for transaction chaining.
* @return The previous transaction ID if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<uint256>
getPreviousTxnID() const
{
if (tx_.isFieldPresent(sfPreviousTxnID))
return tx_.at(sfPreviousTxnID);
return std::nullopt;
}
/**
* @brief Check if the transaction has a previous transaction ID.
* @return true if sfPreviousTxnID is present, false otherwise
*/
[[nodiscard]]
bool
hasPreviousTxnID() const
{
return tx_.isFieldPresent(sfPreviousTxnID);
}
/**
* @brief Get the last ledger sequence (sfLastLedgerSequence).
*
* This field is OPTIONAL and specifies the latest ledger sequence
* in which this transaction can be included.
* @return The last ledger sequence if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<uint32_t>
getLastLedgerSequence() const
{
if (tx_.isFieldPresent(sfLastLedgerSequence))
return tx_.at(sfLastLedgerSequence);
return std::nullopt;
}
/**
* @brief Check if the transaction has a last ledger sequence.
* @return true if sfLastLedgerSequence is present, false otherwise
*/
[[nodiscard]]
bool
hasLastLedgerSequence() const
{
return tx_.isFieldPresent(sfLastLedgerSequence);
}
/**
* @brief Get the account transaction ID (sfAccountTxnID).
*
* This field is OPTIONAL and used to track transaction sequences.
* @return The account transaction ID if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<uint256>
getAccountTxnID() const
{
if (tx_.isFieldPresent(sfAccountTxnID))
return tx_.at(sfAccountTxnID);
return std::nullopt;
}
/**
* @brief Check if the transaction has an account transaction ID.
* @return true if sfAccountTxnID is present, false otherwise
*/
[[nodiscard]]
bool
hasAccountTxnID() const
{
return tx_.isFieldPresent(sfAccountTxnID);
}
/**
* @brief Get the operation limit (sfOperationLimit).
*
* This field is OPTIONAL and limits the number of operations in a transaction.
* @return The operation limit if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<uint32_t>
getOperationLimit() const
{
if (tx_.isFieldPresent(sfOperationLimit))
return tx_.at(sfOperationLimit);
return std::nullopt;
}
/**
* @brief Check if the transaction has an operation limit.
* @return true if sfOperationLimit is present, false otherwise
*/
[[nodiscard]]
bool
hasOperationLimit() const
{
return tx_.isFieldPresent(sfOperationLimit);
}
/**
* @brief Get the memos array (sfMemos).
*
* This field is OPTIONAL and contains arbitrary data attached to the transaction.
* @note This is an untyped field (STArray).
* @return A reference wrapper to the memos array if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<std::reference_wrapper<STArray const>>
getMemos() const
{
if (tx_.isFieldPresent(sfMemos))
return tx_.getFieldArray(sfMemos);
return std::nullopt;
}
/**
* @brief Check if the transaction has memos.
* @return true if sfMemos is present, false otherwise
*/
[[nodiscard]]
bool
hasMemos() const
{
return tx_.isFieldPresent(sfMemos);
}
/**
* @brief Get the ticket sequence (sfTicketSequence).
*
* This field is OPTIONAL and used when consuming a ticket instead of a sequence number.
* @return The ticket sequence if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<uint32_t>
getTicketSequence() const
{
if (tx_.isFieldPresent(sfTicketSequence))
return tx_.at(sfTicketSequence);
return std::nullopt;
}
/**
* @brief Check if the transaction has a ticket sequence.
* @return true if sfTicketSequence is present, false otherwise
*/
[[nodiscard]]
bool
hasTicketSequence() const
{
return tx_.isFieldPresent(sfTicketSequence);
}
/**
* @brief Get the transaction signature (sfTxnSignature).
*
* This field is OPTIONAL and contains the signature for single-signed transactions.
* @return The transaction signature as a blob if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<Blob>
getTxnSignature() const
{
if (tx_.isFieldPresent(sfTxnSignature))
return tx_.getFieldVL(sfTxnSignature);
return std::nullopt;
}
/**
* @brief Check if the transaction has a transaction signature.
* @return true if sfTxnSignature is present, false otherwise
*/
[[nodiscard]]
bool
hasTxnSignature() const
{
return tx_.isFieldPresent(sfTxnSignature);
}
/**
* @brief Get the signers array (sfSigners).
*
* This field is OPTIONAL and contains the list of signers for multi-signed transactions.
* @note This is an untyped field (STArray).
* @return A reference wrapper to the signers array if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<std::reference_wrapper<STArray const>>
getSigners() const
{
if (tx_.isFieldPresent(sfSigners))
return tx_.getFieldArray(sfSigners);
return std::nullopt;
}
/**
* @brief Check if the transaction has signers.
* @return true if sfSigners is present, false otherwise
*/
[[nodiscard]]
bool
hasSigners() const
{
return tx_.isFieldPresent(sfSigners);
}
/**
* @brief Get the network ID (sfNetworkID).
*
* This field is OPTIONAL and identifies the network this transaction is intended for.
* @return The network ID if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<uint32_t>
getNetworkID() const
{
if (tx_.isFieldPresent(sfNetworkID))
return tx_.at(sfNetworkID);
return std::nullopt;
}
/**
* @brief Check if the transaction has a network ID.
* @return true if sfNetworkID is present, false otherwise
*/
[[nodiscard]]
bool
hasNetworkID() const
{
return tx_.isFieldPresent(sfNetworkID);
}
/**
* @brief Get the delegate account (sfDelegate).
*
* This field is OPTIONAL and specifies a delegate account for the transaction.
* @return The delegate account ID if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<AccountID>
getDelegate() const
{
if (tx_.isFieldPresent(sfDelegate))
return tx_.at(sfDelegate);
return std::nullopt;
}
/**
* @brief Check if the transaction has a delegate account.
* @return true if sfDelegate is present, false otherwise
*/
[[nodiscard]]
bool
hasDelegate() const
{
return tx_.isFieldPresent(sfDelegate);
}
/**
* @brief Get the underlying STTx object.
*
* Provides direct access to the wrapped transaction object for cases
* where the type-safe accessors are insufficient.
* @return A constant reference to the underlying STTx object
*/
[[nodiscard]]
STTx const&
getSTTx() const
{
return tx_;
}
protected:
/** @brief The underlying transaction object being wrapped. */
STTx const& tx_;
};
} // namespace xrpl::transactions

View File

@@ -0,0 +1,128 @@
#pragma once
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/jss.h>
namespace xrpl::transactions {
/**
* Base class for all transaction builders.
* Provides common field setters that are available for all transaction types.
*/
template <typename Derived>
class TransactionBuilderBase
{
public:
/**
* Set the account that is sending the transaction.
* @param value Account address (typically as a string)
* @return Reference to the derived builder for method chaining.
*/
Derived&
setAccount(AccountID const& value)
{
set(object_, sfAccount, value);
return static_cast<Derived&>(*this);
}
/**
* Set the transaction fee.
* @param value Fee in drops (typically as a string or number)
* @return Reference to the derived builder for method chaining.
*/
Derived&
setFee(STAmount const& value)
{
set(object_, sfFee, value);
return static_cast<Derived&>(*this);
}
/**
* Set the sequence number.
* @param value Sequence number
* @return Reference to the derived builder for method chaining.
*/
Derived&
setSequence(std::uint32_t const& value)
{
set(object_, sfSequence, value);
return static_cast<Derived&>(*this);
}
/**
* Set the signing public key.
* @param value Public key (typically as a hex string)
* @return Reference to the derived builder for method chaining.
*/
Derived&
setSigningPubKey(Blob const& value)
{
set(object_, sfSigningPubKey, value);
return static_cast<Derived&>(*this);
}
/**
* Set transaction flags.
* @param value Flags value
* @return Reference to the derived builder for method chaining.
*/
Derived&
setFlags(std::uint32_t const& value)
{
set(object_, sfFlags, value);
return static_cast<Derived&>(*this);
}
/**
* Set the source tag.
* @param value Source tag
* @return Reference to the derived builder for method chaining.
*/
Derived&
setSourceTag(std::uint32_t const& value)
{
set(object_, sfSourceTag, value);
return static_cast<Derived&>(*this);
}
/**
* Set the last ledger sequence.
* @param value Last ledger sequence number
* @return Reference to the derived builder for method chaining.
*/
Derived&
setLastLedgerSequence(std::uint32_t const& value)
{
set(object_, sfLastLedgerSequence, value);
return static_cast<Derived&>(*this);
}
/**
* Set the account transaction ID.
* @param value Account transaction ID (typically as a hex string)
* @return Reference to the derived builder for method chaining.
*/
Derived&
setAccountTxnID(STUInt256 const& value)
{
set(object_, sfAccountTxnID, value);
return static_cast<Derived&>(*this);
}
/**
* @brief Factory method to create an instance of the derived builder.
*
* @return A new instance of the derived builder type
*/
static Derived
create()
{
return Derived{};
}
protected:
STObject object_{sfTransaction};
};
} // namespace xrpl::transactions

View File

@@ -0,0 +1,216 @@
#!/usr/bin/env python3
"""
Generate C++ wrapper classes for XRP Ledger entry types from ledger_entries.macro.
This script parses the ledger_entries.macro file and generates type-safe wrapper
classes for each ledger entry type, similar to the transaction wrapper classes.
Uses pcpp to preprocess the macro file and pyparsing to parse the DSL.
"""
# cspell:words sfields
import io
import argparse
from pathlib import Path
from jinja2 import Environment, FileSystemLoader
import pyparsing as pp
# Import common utilities
from macro_parser_common import CppCleaner, parse_sfields_macro, parse_field_list
def create_ledger_entry_parser():
"""Create a pyparsing parser for LEDGER_ENTRY macros.
This parser extracts the full LEDGER_ENTRY macro call and parses its arguments
using pyparsing's nesting-aware delimited list parsing.
"""
# Match the exact words
ledger_entry = pp.Keyword("LEDGER_ENTRY") | pp.Keyword("LEDGER_ENTRY_DUPLICATE")
# Define nested structures so pyparsing protects them
nested_braces = pp.original_text_for(pp.nested_expr("{", "}"))
nested_parens = pp.original_text_for(pp.nested_expr("(", ")"))
# Define standard text (anything that isn't a comma, parens, or braces)
plain_text = pp.Word(pp.printables + " \t\n", exclude_chars=",{}()")
# A single argument is any combination of the above
single_arg = pp.Combine(pp.OneOrMore(nested_braces | nested_parens | plain_text))
single_arg.set_parse_action(lambda t: t[0].strip())
# The arguments are a delimited list
args_list = pp.DelimitedList(single_arg)
# The full macro: LEDGER_ENTRY(args) or LEDGER_ENTRY_DUPLICATE(args)
macro_parser = (
ledger_entry + pp.Suppress("(") + pp.Group(args_list)("args") + pp.Suppress(")")
)
return macro_parser
def parse_ledger_entry_args(args_list):
"""Parse the arguments of a LEDGER_ENTRY macro call.
Args:
args_list: A list of parsed arguments from pyparsing, e.g.,
['ltACCOUNT_ROOT', '0x0061', 'AccountRoot', 'account', '({...})']
Returns:
A dict with parsed ledger entry information.
"""
if len(args_list) < 5:
raise ValueError(
f"Expected at least 5 parts in LEDGER_ENTRY, got {len(args_list)}: {args_list}"
)
tag = args_list[0]
value = args_list[1]
name = args_list[2]
rpc_name = args_list[3]
fields_str = args_list[4]
# Parse fields: ({field1, field2, ...})
fields = parse_field_list(fields_str)
return {
"tag": tag,
"value": value,
"name": name,
"rpc_name": rpc_name,
"fields": fields,
}
def parse_macro_file(file_path):
"""Parse the ledger_entries.macro file and return a list of ledger entry definitions.
Uses pcpp to preprocess the file and pyparsing to parse the LEDGER_ENTRY macros.
"""
with open(file_path, "r") as f:
c_code = f.read()
# Step 1: Clean the C++ code using pcpp
cleaner = CppCleaner("LEDGER_ENTRY_INCLUDE")
cleaner.parse(c_code)
out = io.StringIO()
cleaner.write(out)
clean_text = out.getvalue()
# Step 2: Parse the clean text using pyparsing
parser = create_ledger_entry_parser()
entries = []
for match, _, _ in parser.scan_string(clean_text):
# Extract the macro name and arguments
raw_args = match.args
# Parse the arguments
entry_data = parse_ledger_entry_args(raw_args)
entries.append(entry_data)
return entries
def generate_cpp_class(entry_info, header_dir, jinja_env, field_types):
"""Generate C++ header file for a ledger entry type."""
# Enrich field information with type data
for field in entry_info["fields"]:
field_name = field["name"]
if field_name in field_types:
field["typed"] = field_types[field_name]["typed"]
field["stiSuffix"] = field_types[field_name]["stiSuffix"]
field["typeData"] = field_types[field_name]["typeData"]
else:
# Unknown field - assume typed for safety
field["typed"] = True
field["stiSuffix"] = None
field["typeData"] = None
template = jinja_env.get_template("LedgerEntry.h.jinja2")
# Render the template
header_content = template.render(
name=entry_info["name"],
tag=entry_info["tag"],
value=entry_info["value"],
rpc_name=entry_info["rpc_name"],
fields=entry_info["fields"],
)
# Write header file
header_path = Path(header_dir) / f"{entry_info['name']}.h"
with open(header_path, "w") as f:
f.write(header_content)
print(f"Generated {header_path}")
def main():
parser = argparse.ArgumentParser(
description="Generate C++ ledger entry classes from ledger_entries.macro"
)
parser.add_argument("macro_path", help="Path to ledger_entries.macro")
parser.add_argument(
"--header-dir",
help="Output directory for header files",
default="include/xrpl/protocol/ledger_objects",
)
parser.add_argument(
"--sfields-macro",
help="Path to sfields.macro (default: auto-detect from macro_path)",
)
args = parser.parse_args()
# Auto-detect sfields.macro path if not provided
if args.sfields_macro:
sfields_path = Path(args.sfields_macro)
else:
# Assume sfields.macro is in the same directory as ledger_entries.macro
macro_path = Path(args.macro_path)
sfields_path = macro_path.parent / "sfields.macro"
# Parse sfields.macro to get field type information
print(f"Parsing {sfields_path}...")
field_types = parse_sfields_macro(sfields_path)
print(
f"Found {len(field_types)} field definitions ({sum(1 for f in field_types.values() if f['typed'])} typed, {sum(1 for f in field_types.values() if not f['typed'])} untyped)\n"
)
# Parse the file
entries = parse_macro_file(args.macro_path)
print(f"Found {len(entries)} ledger entries\n")
for entry in entries:
print(f"Ledger Entry: {entry['name']}")
print(f" Tag: {entry['tag']}")
print(f" Value: {entry['value']}")
print(f" RPC Name: {entry['rpc_name']}")
print(f" Fields: {len(entry['fields'])}")
for field in entry["fields"]:
mpt_info = f" ({field['mpt_support']})" if "mpt_support" in field else ""
print(f" - {field['name']}: {field['requirement']}{mpt_info}")
print()
# Set up Jinja2 environment
script_dir = Path(__file__).parent
template_dir = script_dir / "templates"
jinja_env = Environment(loader=FileSystemLoader(str(template_dir)))
# Generate C++ classes
header_dir = Path(args.header_dir)
header_dir.mkdir(parents=True, exist_ok=True)
for entry in entries:
generate_cpp_class(entry, header_dir, jinja_env, field_types)
print(f"\nGenerated {len(entries)} ledger entry classes")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,226 @@
#!/usr/bin/env python3
"""
Parse transactions.macro file to extract transaction information
and generate C++ classes for each transaction type.
Uses pcpp to preprocess the macro file and pyparsing to parse the DSL.
"""
# cspell:words sfields
import io
import argparse
from pathlib import Path
from jinja2 import Environment, FileSystemLoader
import pyparsing as pp
# Import common utilities
from macro_parser_common import CppCleaner, parse_sfields_macro, parse_field_list
def create_transaction_parser():
"""Create a pyparsing parser for TRANSACTION macros.
This parser extracts the full TRANSACTION macro call and parses its arguments
using pyparsing's nesting-aware delimited list parsing.
"""
# Define nested structures so pyparsing protects them
nested_braces = pp.original_text_for(pp.nested_expr("{", "}"))
nested_parens = pp.original_text_for(pp.nested_expr("(", ")"))
# Define standard text (anything that isn't a comma, parens, or braces)
plain_text = pp.Word(pp.printables + " \t\n", exclude_chars=",{}()")
# A single argument is any combination of the above
single_arg = pp.Combine(pp.OneOrMore(nested_braces | nested_parens | plain_text))
single_arg.set_parse_action(lambda t: t[0].strip())
# The arguments are a delimited list
args_list = pp.DelimitedList(single_arg)
# The full macro: TRANSACTION(args)
macro_parser = (
pp.Keyword("TRANSACTION")
+ pp.Suppress("(")
+ pp.Group(args_list)("args")
+ pp.Suppress(")")
)
return macro_parser
def parse_transaction_args(args_list):
"""Parse the arguments of a TRANSACTION macro call.
Args:
args_list: A list of parsed arguments from pyparsing, e.g.,
['ttPAYMENT', '0', 'Payment', 'Delegation::delegable',
'uint256{}', 'createAcct', '({...})']
Returns:
A dict with parsed transaction information.
"""
if len(args_list) < 7:
raise ValueError(
f"Expected at least 7 parts in TRANSACTION, got {len(args_list)}: {args_list}"
)
tag = args_list[0]
value = args_list[1]
name = args_list[2]
delegable = args_list[3]
amendments = args_list[4]
privileges = args_list[5]
fields_str = args_list[6]
# Parse fields: ({field1, field2, ...})
fields = parse_field_list(fields_str)
return {
"tag": tag,
"value": value,
"name": name,
"delegable": delegable,
"amendments": amendments,
"privileges": privileges,
"fields": fields,
}
def parse_macro_file(filepath):
"""Parse the transactions.macro file.
Uses pcpp to preprocess the file and pyparsing to parse the TRANSACTION macros.
"""
with open(filepath, "r") as f:
c_code = f.read()
# Step 1: Clean the C++ code using pcpp
cleaner = CppCleaner("TRANSACTION_INCLUDE")
cleaner.parse(c_code)
out = io.StringIO()
cleaner.write(out)
clean_text = out.getvalue()
# Step 2: Parse the clean text using pyparsing
parser = create_transaction_parser()
transactions = []
for match, _, _ in parser.scan_string(clean_text):
# Extract the macro name and arguments
raw_args = match.args
# Parse the arguments
tx_data = parse_transaction_args(raw_args)
transactions.append(tx_data)
return transactions
def generate_cpp_class(tx_info, header_dir, jinja_env, field_types):
"""Generate a header-only template class for a transaction using Jinja2 templates."""
class_name = tx_info["name"]
# Enrich field information with type data
for field in tx_info["fields"]:
field_name = field["name"]
if field_name in field_types:
field["typed"] = field_types[field_name]["typed"]
field["stiSuffix"] = field_types[field_name]["stiSuffix"]
field["typeData"] = field_types[field_name]["typeData"]
else:
# Unknown field - assume typed for safety
field["typed"] = True
field["stiSuffix"] = None
field["typeData"] = None
# Load template
header_template = jinja_env.get_template("Transaction.h.jinja2")
# Render header
header_content = header_template.render(tx_info)
header_path = header_dir / f"{class_name}.h"
with open(header_path, "w") as f:
f.write(header_content)
return header_path
# TransactionBase is a static file in the repository at:
# - include/xrpl/protocol/TransactionBase.h
# - src/libxrpl/protocol/TransactionBase.cpp
# It is NOT generated by this script.
def main():
parser = argparse.ArgumentParser(
description="Generate C++ transaction classes from transactions.macro"
)
parser.add_argument("macro_path", help="Path to transactions.macro")
parser.add_argument(
"--header-dir",
help="Output directory for header files",
default="include/xrpl/protocol/transactions",
)
parser.add_argument(
"--sfields-macro",
help="Path to sfields.macro (default: auto-detect from macro_path)",
)
args = parser.parse_args()
# Auto-detect sfields.macro path if not provided
if args.sfields_macro:
sfields_path = Path(args.sfields_macro)
else:
# Assume sfields.macro is in the same directory as transactions.macro
macro_path = Path(args.macro_path)
sfields_path = macro_path.parent / "sfields.macro"
# Parse sfields.macro to get field type information
print(f"Parsing {sfields_path}...")
field_types = parse_sfields_macro(sfields_path)
print(
f"Found {len(field_types)} field definitions ({sum(1 for f in field_types.values() if f['typed'])} typed, {sum(1 for f in field_types.values() if not f['typed'])} untyped)\n"
)
# Parse the file
transactions = parse_macro_file(args.macro_path)
print(f"Found {len(transactions)} transactions\n")
for tx in transactions:
print(f"Transaction: {tx['name']}")
print(f" Tag: {tx['tag']}")
print(f" Value: {tx['value']}")
print(f" Fields: {len(tx['fields'])}")
for field in tx["fields"]:
print(f" - {field['name']}: {field['requirement']}")
print()
# Set up output directory
header_dir = Path(args.header_dir)
header_dir.mkdir(parents=True, exist_ok=True)
print(f"\nGenerating header-only template classes...")
print(f" Headers: {header_dir}\n")
# Set up Jinja2 environment
script_dir = Path(__file__).parent
template_dir = script_dir / "templates"
jinja_env = Environment(loader=FileSystemLoader(template_dir))
generated_files = []
for tx_info in transactions:
header_path = generate_cpp_class(tx_info, header_dir, jinja_env, field_types)
generated_files.append(header_path)
print(f" Generated: {tx_info['name']}.h")
print(
f"\n✓ Successfully generated {len(transactions)} transaction classes ({len(generated_files)} header files)"
)
print(f" Headers: {header_dir.absolute()}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,193 @@
#!/usr/bin/env python3
"""
Common utilities for parsing XRP Ledger macro files.
This module provides shared functionality for parsing transactions.macro
and ledger_entries.macro files using pcpp and pyparsing.
"""
# cspell:words sfields
import re
import pyparsing as pp
from pcpp import Preprocessor
class CppCleaner(Preprocessor):
"""C preprocessor that removes C++ noise while preserving macro calls."""
def __init__(self, macro_include_name):
"""
Initialize the preprocessor.
Args:
macro_include_name: The name of the include flag to set to 0
(e.g., "TRANSACTION_INCLUDE" or "LEDGER_ENTRY_INCLUDE")
"""
super(CppCleaner, self).__init__()
# Define flags so #if blocks evaluate correctly
# We set the include flag to 0 so includes are skipped
self.define(f"{macro_include_name} 0")
# Suppress line directives
self.line_directive = None
def on_error(self, file, line, msg):
# Ignore #error directives
pass
def on_include_not_found(
self, is_malformed, is_system_include, curdir, includepath
):
# Ignore missing headers
pass
def parse_sfields_macro(sfields_path):
"""
Parse sfields.macro to determine which fields are typed vs untyped.
Returns a dict mapping field names to their type information:
{
'sfMemos': {'typed': False, 'stiSuffix': 'ARRAY', 'typeData': {...}},
'sfAmount': {'typed': True, 'stiSuffix': 'AMOUNT', 'typeData': {...}},
...
}
"""
# Mapping from STI suffix to C++ type for untyped fields
UNTYPED_TYPE_MAP = {
"ARRAY": {
"getter_method": "getFieldArray",
"setter_method": "setFieldArray",
"setter_use_brackets": False,
"setter_type": "STArray const&",
"return_type": "STArray const&",
"return_type_optional": "std::optional<std::reference_wrapper<STArray const>>",
},
"OBJECT": {
"getter_method": "getFieldObject",
"setter_method": "setFieldObject",
"setter_use_brackets": False,
"setter_type": "STObject const&",
"return_type": "STObject",
"return_type_optional": "std::optional<STObject>",
},
"PATHSET": {
"getter_method": "getFieldPathSet",
"setter_method": "setFieldPathSet",
"setter_use_brackets": False,
"setter_type": "STPathSet const&",
"return_type": "STPathSet const&",
"return_type_optional": "std::optional<std::reference_wrapper<STPathSet const>>",
},
}
field_info = {}
with open(sfields_path, "r") as f:
content = f.read()
# Parse TYPED_SFIELD entries
# Format: TYPED_SFIELD(sfName, stiSuffix, fieldValue, ...)
typed_pattern = r"TYPED_SFIELD\s*\(\s*(\w+)\s*,\s*(\w+)\s*,"
for match in re.finditer(typed_pattern, content):
field_name = match.group(1)
sti_suffix = match.group(2)
field_info[field_name] = {
"typed": True,
"stiSuffix": sti_suffix,
"typeData": {
"getter_method": "at",
"setter_method": "",
"setter_use_brackets": True,
"setter_type": f"SF_{sti_suffix}::type::value_type const&",
"return_type": f"SF_{sti_suffix}::type::value_type",
"return_type_optional": f"std::optional<SF_{sti_suffix}::type::value_type>",
},
}
# Parse UNTYPED_SFIELD entries
# Format: UNTYPED_SFIELD(sfName, stiSuffix, fieldValue, ...)
untyped_pattern = r"UNTYPED_SFIELD\s*\(\s*(\w+)\s*,\s*(\w+)\s*,"
for match in re.finditer(untyped_pattern, content):
field_name = match.group(1)
sti_suffix = match.group(2)
type_data = UNTYPED_TYPE_MAP.get(
sti_suffix, UNTYPED_TYPE_MAP.get("OBJECT")
) # Default to OBJECT
field_info[field_name] = {
"typed": False,
"stiSuffix": sti_suffix,
"typeData": type_data,
}
return field_info
def create_field_list_parser():
"""Create a pyparsing parser for field lists like '({...})'."""
# A field identifier (e.g., sfDestination, soeREQUIRED, soeMPTSupported)
field_identifier = pp.Word(pp.alphas + "_", pp.alphanums + "_")
# A single field definition: {sfName, soeREQUIRED, ...}
# Allow optional trailing comma inside the braces
field_def = (
pp.Suppress("{")
+ pp.Group(pp.DelimitedList(field_identifier) + pp.Optional(pp.Suppress(",")))(
"parts"
)
+ pp.Suppress("}")
)
# The field list: ({field1, field2, ...}) or ({}) for empty lists
# Allow optional trailing comma after the last field definition
field_list = (
pp.Suppress("(")
+ pp.Suppress("{")
+ pp.Group(
pp.Optional(pp.DelimitedList(field_def) + pp.Optional(pp.Suppress(",")))
)("fields")
+ pp.Suppress("}")
+ pp.Suppress(")")
)
return field_list
def parse_field_list(fields_str):
"""Parse a field list string like '({...})' using pyparsing.
Args:
fields_str: A string like '({
{sfDestination, soeREQUIRED},
{sfAmount, soeREQUIRED, soeMPTSupported}
})'
Returns:
A list of field dicts with 'name', 'requirement', 'flags', and 'supports_mpt'.
"""
parser = create_field_list_parser()
try:
result = parser.parse_string(fields_str, parse_all=True)
fields = []
for field_parts in result.fields:
if len(field_parts) < 2:
continue
field_name = field_parts[0]
requirement = field_parts[1]
flags = list(field_parts[2:]) if len(field_parts) > 2 else []
supports_mpt = "soeMPTSupported" in flags
fields.append(
{
"name": field_name,
"requirement": requirement,
"flags": flags,
"supports_mpt": supports_mpt,
}
)
return fields
except pp.ParseException as e:
raise ValueError(f"Failed to parse field list: {e}")

14
scripts/requirements.txt Normal file
View File

@@ -0,0 +1,14 @@
# Python dependencies for XRP Ledger code generation scripts
#
# These packages are required to run the code generation scripts that
# parse macro files and generate C++ wrapper classes.
# cspell:words pyparsing
# C preprocessor for Python - used to preprocess macro files
pcpp>=1.30
# Parser combinator library - used to parse the macro DSL
pyparsing>=3.0.0
# Template engine - used to generate C++ code from templates
Jinja2>=3.0.0

View File

@@ -0,0 +1,176 @@
#pragma once
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/LedgerEntryBase.h>
#include <xrpl/protocol_autogen/LedgerEntryBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::ledger_entries {
// Forward declaration
class {{ name }}Builder;
/**
* Ledger Entry: {{ name }}
* Type: {{ tag }} ({{ value }})
* RPC Name: {{ rpc_name }}
*
* Immutable wrapper around SLE providing type-safe field access.
* Use {{ name }}Builder to construct new ledger entries.
*/
class {{ name }} : public LedgerEntryBase
{
public:
static constexpr LedgerEntryType entryType = {{ tag }};
/**
* Construct a {{ name }} ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit {{ name }}(SLE const& sle)
: LedgerEntryBase(sle)
{
// Verify ledger entry type
if (sle.getType() != entryType)
{
throw std::runtime_error("Invalid ledger entry type for {{ name }}");
}
}
// Ledger entry-specific field getters
{%- for field in fields %}
{%- if field.typed %}
/**
* Get {{ field.name }} ({{ field.requirement }})
{%- if field.mpt_support %}
* MPT Support: {{ field.mpt_support }}
{%- endif %}
*/
{%- if field.requirement == 'soeREQUIRED' %}
[[nodiscard]]
{{ field.typeData.return_type }}
get{{ field.name[2:] }}() const
{
return this->sle_.{{ field.typeData.getter_method }}({{ field.name }});
}
{%- else %}
[[nodiscard]]
{{ field.typeData.return_type_optional }}
get{{ field.name[2:] }}() const
{
if (has{{ field.name[2:] }}())
return this->sle_.{{ field.typeData.getter_method }}({{ field.name }});
return std::nullopt;
}
[[nodiscard]]
bool
has{{ field.name[2:] }}() const
{
return this->sle_.isFieldPresent({{ field.name }});
}
{%- endif %}
{%- else %}
/**
* Get {{ field.name }} ({{ field.requirement }})
{%- if field.mpt_support %}
* MPT Support: {{ field.mpt_support }}
{%- endif %}
* Note: This is an untyped field ({{ field.cppType }}).
*/
{%- if field.requirement == 'soeREQUIRED' %}
[[nodiscard]]
{{ field.typeData.return_type }}
get{{ field.name[2:] }}() const
{
return this->sle_.{{ field.typeData.getter_method }}({{ field.name }});
}
{%- else %}
[[nodiscard]]
{{ field.typeData.return_type_optional }}
get{{ field.name[2:] }}() const
{
if (this->sle_.isFieldPresent({{ field.name }}))
return this->sle_.{{ field.typeData.getter_method }}({{ field.name }});
return std::nullopt;
}
[[nodiscard]]
bool
has{{ field.name[2:] }}() const
{
return this->sle_.isFieldPresent({{ field.name }});
}
{%- endif %}
{%- endif %}
{%- endfor %}
};
/**
* Builder for {{ name }} ledger entries.
* Provides a fluent interface for constructing ledger entries with method chaining.
* Uses Json::Value internally for flexible ledger entry construction.
* Inherits common field setters from LedgerEntryBuilderBase.
*/
class {{ name }}Builder : public LedgerEntryBuilderBase<{{ name }}Builder>
{
public:
{{ name }}Builder()
{
// Initialize with ledger entry type
object_[sfLedgerEntryType] = {{ tag }};
}
{{ name }}Builder(SLE const& sle)
{
if (object_[sfLedgerEntryType] != {{ tag }})
{
throw std::runtime_error("Invalid ledger entry type for {{ name }}");
}
object_ = sle;
}
// Ledger entry-specific field setters
{%- for field in fields %}
/**
* Set {{ field.name }} ({{ field.requirement }})
{%- if field.mpt_support %}
* MPT Support: {{ field.mpt_support }}
{%- endif %}
* @return Reference to this builder for method chaining.
*/
{{ name }}Builder&
set{{ field.name[2:] }}({{ field.typeData.setter_type }} value)
{
{%- if field.stiSuffix == 'ISSUE' %}
object_[{{ field.name }}] = STIssue({{ field.name }}, value);
{%- elif field.typeData.setter_use_brackets %}
object_[{{ field.name }}] = value;
{%- else %}
object_.{{ field.typeData.setter_method }}({{ field.name }}, value);
{%- endif %}
return *this;
}
{%- endfor %}
/**
* Build and return the completed {{ name }} wrapper.
* @return The constructed ledger entry wrapper.
* @throws std::runtime_error if the JSON cannot be parsed into a valid ledger entry.
*/
{{ name }}
build(uint256 const& index)
{
return {{ name }}{SLE(object_, index)};
}
};
} // namespace xrpl::ledger_entries

View File

@@ -0,0 +1,179 @@
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
// Forward declaration
class {{ name }}Builder;
/**
* Transaction: {{ name }}
* Type: {{ tag }} ({{ value }})
* Delegable: {{ delegable }}
* Amendment: {{ amendments }}
* Privileges: {{ privileges }}
*
* Immutable wrapper around STTx providing type-safe field access.
* Use {{ name }}Builder to construct new transactions.
*/
class {{ name }} : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = {{ tag }};
/**
* Construct a {{ name }} transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit {{ name }}(STTx const& tx)
: TransactionBase(tx)
{
// Verify transaction type
if (tx.getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for {{ name }}");
}
}
// Transaction-specific field getters
{%- for field in fields %}
{%- if field.typed %}
/**
* Get {{ field.name }} ({{ field.requirement }})
{%- if field.supports_mpt %}
* Note: This field supports MPT (Multi-Purpose Token) amounts.
{%- endif %}
*/
{%- if field.requirement == 'soeREQUIRED' %}
[[nodiscard]]
{{ field.typeData.return_type }}
get{{ field.name[2:] }}() const
{
return this->tx_.{{ field.typeData.getter_method }}({{ field.name }});
}
{%- else %}
[[nodiscard]]
{{ field.typeData.return_type_optional }}
get{{ field.name[2:] }}() const
{
if (has{{ field.name[2:] }}())
{
return this->tx_.{{ field.typeData.getter_method }}({{ field.name }});
}
return std::nullopt;
}
[[nodiscard]]
bool
has{{ field.name[2:] }}() const
{
return this->tx_.isFieldPresent({{ field.name }});
}
{%- endif %}
{%- else %}
/**
* Get {{ field.name }} ({{ field.requirement }})
{%- if field.supports_mpt %}
* Note: This field supports MPT (Multi-Purpose Token) amounts.
{%- endif %}
* Note: This is an untyped field
*/
{%- if field.requirement == 'soeREQUIRED' %}
[[nodiscard]]
{{ field.typeData.return_type }}
get{{ field.name[2:] }}() const
{
return this->tx_.{{ field.typeData.getter_method }}({{ field.name }});
}
{%- else %}
[[nodiscard]]
{{ field.typeData.return_type_optional }}
get{{ field.name[2:] }}() const
{
if (this->tx_.isFieldPresent({{ field.name }}))
return this->tx_.{{ field.typeData.getter_method }}({{ field.name }});
return std::nullopt;
}
[[nodiscard]]
bool
has{{ field.name[2:] }}() const
{
return this->tx_.isFieldPresent({{ field.name }});
}
{%- endif %}
{%- endif %}
{%- endfor %}
};
/**
* Builder for {{ name }} transactions.
* Provides a fluent interface for constructing transactions with method chaining.
* Uses Json::Value internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class {{ name }}Builder : public TransactionBuilderBase<{{ name }}Builder>
{
public:
{{ name }}Builder()
{
// Initialize with transaction type
object_[sfTransactionType] = {{ tag }};
}
{{ name }}Builder(STTx const& tx)
{
if (tx.getTxnType() != {{ tag }})
{
throw std::runtime_error("Invalid transaction type for {{ name }}Builder");
}
object_ = tx;
}
// Transaction-specific field setters
{%- for field in fields %}
/**
* Set {{ field.name }} ({{ field.requirement }})
{%- if field.supports_mpt %}
* Note: This field supports MPT (Multi-Purpose Token) amounts.
{%- endif %}
* @return Reference to this builder for method chaining.
*/
{{ name }}Builder&
set{{ field.name[2:] }}({{ field.typeData.setter_type }} value)
{
{%- if field.stiSuffix == 'ISSUE' %}
object_[{{ field.name }}] = STIssue({{ field.name }}, value);
{%- elif field.typeData.setter_use_brackets %}
object_[{{ field.name }}] = value;
{%- else %}
object_.{{ field.typeData.setter_method }}({{ field.name }}, value);
{%- endif %}
return *this;
}
{%- endfor %}
/**
* Build and return the completed {{ name }} wrapper.
* @return The constructed transaction wrapper.
* @throws std::runtime_error if the JSON cannot be parsed into a valid transaction.
*/
{{ name }}
build()
{
return {{ name }}(STTx(std::move(object_)));
}
};
} // namespace xrpl::transactions

View File

@@ -0,0 +1 @@
// This file is a placeholder to ensure the protocol_autogen module can be built.