Case Study: CheckCreate Transactor

← Back to Transactors: Understanding the Lifecycle of a Transaction


Introduction

The CheckCreate transaction demonstrates all the essential patterns for implementing a transactor in rippled. Checks are deferred payment instructions—similar to paper checks—that allow a sender to authorize a payment that the recipient can later cash.

This case study provides a detailed, code-level walkthrough of the CheckCreate implementation, examining every validation step and state modification. By understanding this transaction, you'll have a template for implementing any new transaction type.


What CheckCreate Does

A CheckCreate transaction:

  1. Creates a new Check ledger object

  2. Links the Check to both sender's and recipient's owner directories

  3. Increments the sender's owner count

  4. Specifies a maximum amount that can be cashed

Transaction Fields:

Field
Required
Description

Account

Yes

The sender creating the check

Destination

Yes

The recipient who can cash the check

SendMax

Yes

Maximum amount that can be cashed

Expiration

No

When the check expires

DestinationTag

No

Tag for the destination

SourceTag

No

Tag for the source

InvoiceID

No

Arbitrary reference ID


Source Files

  • Header: src/xrpld/app/tx/detail/CreateCheck.h

  • Implementation: src/xrpld/app/tx/detail/CreateCheck.cpp


Phase 1: Preflight

Preflight performs stateless validation on the transaction content.

Check 1: Self-Send Prevention

A check to yourself is redundant—you can just keep the money:

Why temREDUNDANT? This is a permanent format error. The transaction can never be valid with these field values.

Check 2: SendMax Validation

The amount must be positive and well-formed:

isLegalNet() checks that:

  • The amount isn't negative

  • The amount doesn't overflow

  • The precision is valid

signum() <= 0 ensures the amount is positive (not zero).

Check 3: Currency Validation

The currency code must be valid:

badCurrency() returns a special invalid currency code. This catches malformed currency specifications.

Check 4: Expiration Validation

If an expiration is provided, it must not be zero:

Note: The ~ operator returns std::optional<T>, allowing us to check if the field is present.


Phase 2: Preclaim

Preclaim validates against the current ledger state.

Check 1: Destination Account Existence

Why tecNO_DST? The destination doesn't exist now, but it could be created before this transaction is applied. However, the transaction will fail if applied without the destination.

Check 2: DisallowIncoming Permission

Accounts can opt out of receiving certain objects:

This is amendment-gated (featureDisallowIncoming), so we only check if the amendment is enabled.

Check 3: Pseudo-Account Prevention

Pseudo-accounts (like AMM pools) cannot cash checks:

Check 4: Destination Tag Requirement

Some accounts require a destination tag:

Check 5: Freeze Status

For non-XRP amounts, check that the asset isn't frozen:

Global Freeze: The issuer has frozen all holdings of this currency.

Trust Line Freeze: The issuer has frozen the source's specific trust line.

The (issuerId > srcId) ? lsfHighFreeze : lsfLowFreeze pattern is due to how trust lines store flags—the "high" account's flags use different bits than the "low" account's flags.

Check 6: Expiration

Don't create a check that's already expired:

hasExpired() compares the expiration against the parent ledger's close time.


Phase 3: doApply

doApply modifies the ledger state.

Step 1: Verify Account Exists

This should never fail—if we got this far, the account exists. tefINTERNAL indicates a bug.

Step 2: Check Reserve

Calculate what the reserve will be with one more owned object. Use mPriorBalance (before fee) to allow dipping into reserve for fees.

Step 3: Create the Check SLE

The check's key is derived from the creator's account and the transaction sequence (or ticket number).

Step 4: Set Required Fields

Step 5: Set Optional Fields

The [~sfField] pattern returns std::optional, allowing conditional field setting.

Step 6: Insert into Ledger

This adds the new SLE to the view's modified set.

Step 7: Add to Destination Directory

The check is added to the destination's owner directory so they can find checks payable to them. The page number is stored in the Check for later removal.

Step 8: Add to Source Directory

The check is also added to the source's directory—they need to track checks they've created.

Step 9: Update Owner Count

Increment the source's owner count. This affects their reserve requirement.


Complete Transaction Flow Diagram


Ledger State Changes

Before CheckCreate:

After CheckCreate (fee = 12 drops):


Key Patterns Demonstrated

  1. Stateless validation in preflight: No ledger access

  2. Amendment checking: Using ctx.view.rules().enabled()

  3. Freeze checking: Both global and trust line freezes

  4. Reserve management: Check before creating objects

  5. Directory management: Add to both source and destination

  6. Owner count management: Increment when creating objects

  7. Optional field handling: Using [~sfField] pattern

  8. Appropriate error codes: tem* for format, tec* for state


Understanding CheckCreate helps with these related transactions:

  • CashCheck: Cashing a check (balance transfer + check deletion)

  • CancelCheck: Canceling a check (check deletion only)

  • EscrowCreate: Similar pattern for creating escrow objects

  • OfferCreate: Similar pattern for creating offer objects


Exercises

  1. Trace a failed CheckCreate: Walk through what happens when:

    • Destination doesn't exist

    • Trust line is frozen

    • Insufficient reserve

  2. Compare with CancelCheck: How does deletion differ from creation?

  3. Implement logging: Add detailed trace logs to follow execution


Codebase References

File
Description

src/xrpld/app/tx/detail/CreateCheck.cpp

CheckCreate implementation

src/xrpld/app/tx/detail/CreateCheck.h

CheckCreate class definition

src/xrpld/app/tx/detail/CashCheck.cpp

CashCheck for comparison

src/xrpld/app/tx/detail/CancelCheck.cpp

CancelCheck for comparison

include/xrpl/protocol/Indexes.h

Keylet definitions including keylet::check

Last updated