Skip to content

XLS-0068: Sponsored Fees and Reserves#3199

Open
achowdhry-ripple wants to merge 11 commits intomainfrom
sponsored-fees
Open

XLS-0068: Sponsored Fees and Reserves#3199
achowdhry-ripple wants to merge 11 commits intomainfrom
sponsored-fees

Conversation

@achowdhry-ripple
Copy link
Collaborator

@achowdhry-ripple achowdhry-ripple commented Feb 11, 2026

High Level Overview of Change

Support for Sponsored Fees and Reserves. (Note: this is an open PR for V2)

Context of Change

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor (non-breaking change that only restructures code)
  • Tests (You added tests for code that already exists, or your new feature included in this PR)
  • Documentation Updates
  • Release

Did you update HISTORY.md?

  • Yes
  • No, this change does not impact library users

Test Plan

Unit tests. Integration tests TODO, will not pass yet

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 11, 2026

Walkthrough

This pull request introduces support for Sponsored Fees and Reserves (XLS-68d) across the XRPL JavaScript library and binary codec. It adds new transaction types (SponsorshipSet, SponsorshipTransfer), a new ledger entry type (Sponsorship), sponsor-related fields to existing ledger entries, new granular permissions, and RPC method support for account sponsoring queries.

Changes

Cohort / File(s) Summary
Binary Codec Definitions
packages/ripple-binary-codec/HISTORY.md, packages/ripple-binary-codec/src/enums/definitions.json, packages/ripple-binary-codec/src/enums/xrpl-definitions-base.ts
Added 155+ field definitions for sponsorship support including SponsoredOwnerCount, SponsorFlags, SponseeNode, ObjectID, and new ledger/transaction types (Sponsorship, SponsorshipSet, SponsorshipTransfer). Added granular permissions SponsorFee and SponsorReserve.
Ledger Entry Models
packages/xrpl/src/models/ledger/AccountRoot.ts, packages/xrpl/src/models/ledger/BaseLedgerEntry.ts, packages/xrpl/src/models/ledger/RippleState.ts, packages/xrpl/src/models/ledger/LedgerEntry.ts, packages/xrpl/src/models/ledger/Sponsorship.ts
Extended AccountRoot with sponsor-related counts; added Sponsor field to BaseLedgerEntry and RippleState; introduced new Sponsorship ledger entry with flags, enums, and field definitions.
Transaction Models
packages/xrpl/src/models/transactions/common.ts, packages/xrpl/src/models/transactions/payment.ts, packages/xrpl/src/models/transactions/sponsorshipSet.ts, packages/xrpl/src/models/transactions/sponsorshipTransfer.ts, packages/xrpl/src/models/transactions/transaction.ts
Added sponsor-related fields (Sponsor, SponsorFlags, SponsorSignature) and validation to BaseTransaction. Introduced SponsorshipSet and SponsorshipTransfer transaction types with comprehensive validators. Added sponsor-created account flag to Payment.
RPC Methods
packages/xrpl/src/models/methods/accountObjects.ts, packages/xrpl/src/models/methods/accountSponsoring.ts
Added sponsored filter option to AccountObjects. Introduced new AccountSponsoring RPC method with request/response types and related type exclusions for sponsored objects.
Public API Exports and History
packages/xrpl/src/models/ledger/index.ts, packages/xrpl/src/models/transactions/index.ts, packages/xrpl/src/models/methods/index.ts, packages/xrpl/HISTORY.md
Updated exports to include new sponsorship types, interfaces, and enums across ledger, transaction, and method modules. Added XLS-68d feature note to release history.
Tests
packages/ripple-binary-codec/test/granularPermissions.test.ts, packages/xrpl/test/models/sponsorship.test.ts
Added comprehensive test suites validating granular permissions (14 permissions, value ranges, consecutiveness) and sponsorship features (AccountSponsoring, Sponsorship ledger entries, validation constraints).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • ckeshava
  • khancode
  • pdp2121

Poem

🐰 With sponsor flags and reserves in sight,
New ledger entries dance in the light,
SponsorshipSet and SponsorshipTransfer take flight,
Fees and reserves shared just right,
XLS-68d shines oh so bright! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main feature being added: support for XLS-0068 Sponsored Fees and Reserves, which aligns with the primary objective of this changeset.
Description check ✅ Passed The description includes most required sections from the template: High Level Overview with context and links, Type of Change marked as New feature, and HISTORY.md confirmation. However, the Test Plan section is incomplete with only a brief note about unit tests and TODO integration tests.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sponsored-fees

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@achowdhry-ripple achowdhry-ripple marked this pull request as ready for review February 11, 2026 17:32
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@packages/ripple-binary-codec/src/enums/definitions.json`:
- Around line 3876-3877: The TRANSACTION_TYPES mapping includes two invalid
entries "SponsorshipSet" and "SponsorshipTransfer" with codes 86 and 85; remove
these keys from the TRANSACTION_TYPES object in definitions.json to avoid
creating invalid serialization IDs, and search for any usages of the symbols
SponsorshipSet or SponsorshipTransfer elsewhere (e.g., any lookup code that
expects those enum keys) and update or remove those references so
deserialization/serialization only uses real rippled TxType names; ensure the
JSON remains valid after deletion and run unit/serialization tests to confirm no
remaining dependencies.

In `@packages/xrpl/src/models/methods/accountSponsoring.ts`:
- Around line 48-51: The SponsoredObject type currently excludes only
Amendments, FeeSettings, and LedgerHashes from the LedgerEntry union but needs
to also exclude DirectoryNode and NegativeUNL; update the SponsoredObject type
declaration (the Exclude<LedgerEntry, ...>) to include DirectoryNode and
NegativeUNL and update the JSDoc above (referencing SponsoredObject and
LedgerEntry) to list all four excluded types (DirectoryNode, Amendments,
FeeSettings, NegativeUNL) so the docs match the type.

In `@packages/xrpl/src/models/transactions/sponsorshipSet.ts`:
- Around line 150-171: The check for tfDeleteObject currently uses strict
equality (flags === tfDeleteObject) so it only fires when tfDeleteObject is the
sole flag; change it to a bitwise test (e.g., (flags &
SponsorshipSetFlags.tfDeleteObject) !== 0) so the delete-branch runs whenever
the tfDeleteObject bit is set; keep the existing validation that throws
ValidationError for tx.FeeAmount, tx.MaxFee, and tx.ReserveCount when that bit
is set.
🧹 Nitpick comments (8)
packages/ripple-binary-codec/test/granularPermissions.test.ts (2)

40-50: Duplicate test: "values greater than UINT16_MAX" appears in both describe blocks.

The test at Line 40 and Line 79 assert the same condition (all granular permission values > 65536). Consider removing one to avoid redundancy.

Also applies to: 79-88


6-14: Individual value tests are subsumed by the toEqual check on Line 37.

The tests at Lines 6–14 (checking SponsorFee = 65549 and SponsorReserve = 65550 individually) are fully covered by the toEqual(expectedPermissions) assertion at Line 37 which checks all 14 permissions including these two. Keeping them is fine for granular failure isolation, but they're not strictly necessary.

packages/xrpl/src/models/transactions/common.ts (2)

477-496: SponsorSignature validation allows an empty object — consider enforcing at least one signing method.

The current validation (lines 675–706) checks individual fields if present but accepts SponsorSignature: {} as valid. Consider requiring at least one of SigningPubKey/TxnSignature or Signers to be present when SponsorSignature is provided, similar to how the base transaction rejects an empty Signers array.

Proposed validation addition (inside the `if (common.SponsorSignature !== undefined)` block around line 706)
+    // Ensure at least one signing method is present
+    if (
+      sponsorSig.SigningPubKey === undefined &&
+      sponsorSig.TxnSignature === undefined &&
+      sponsorSig.Signers === undefined
+    ) {
+      throw new ValidationError(
+        'BaseTransaction: SponsorSignature must include signing fields',
+      )
+    }

722-734: Use bitwise operations for flag validation.

Using addition (tfSponsorFee + tfSponsorReserve) works here since the values are 1 and 2 with no overlap, but bitwise OR is more idiomatic for flag combinations and more robust if flags are ever extended.

Suggested change
-    const isValidFlag =
-      flags === SponsorFlags.tfSponsorFee ||
-      flags === SponsorFlags.tfSponsorReserve ||
-      flags === SponsorFlags.tfSponsorFee + SponsorFlags.tfSponsorReserve
+    const validMask =
+      SponsorFlags.tfSponsorFee | SponsorFlags.tfSponsorReserve
+    const isValidFlag = flags > 0 && (flags & ~validMask) === 0
packages/xrpl/src/models/transactions/sponsorshipTransfer.ts (1)

29-52: Sponsor, SponsorFlags, and SponsorSignature are already inherited from BaseTransaction.

These fields are redeclared in SponsorshipTransfer with the same types as BaseTransaction. The redeclaration isn't harmful and serves as documentation for the transaction-specific semantics, but it could cause confusion about whether these are the "co-signing" sponsor fields (from BaseTransaction) or the "transfer target" fields. A brief inline comment clarifying this dual nature would help future maintainers.

packages/xrpl/test/models/sponsorship.test.ts (2)

206-222: Duplicate test: "optional Sponsor" (line 206) and "unsponsored objects" (line 318) are nearly identical.

Both tests create an Escrow without a Sponsor and then assert.isUndefined(escrow.Sponsor). Consider consolidating into a single test to avoid redundancy.

Also applies to: 318-335


337-362: The SponsoredOwnerCount <= OwnerCount assertion only verifies hand-crafted test data.

Since the test constructs the object literal itself, this assertion (line 357-361) will always pass — it doesn't validate any library logic. It's fine as documentation of the spec constraint, but be aware it provides no runtime protection.

packages/xrpl/src/models/transactions/sponsorshipSet.ts (1)

19-44: Consider validating mutually exclusive flag pairs.

Setting both tfSponsorshipSetRequireSignForFee and tfSponsorshipClearRequireSignForFee simultaneously (or the reserve pair) is contradictory. While the server likely rejects this, adding a client-side check would fail fast with a clear message. This can be deferred if you prefer to keep validation minimal.

Comment on lines +3876 to +3877
"SponsorshipSet": 86,
"SponsorshipTransfer": 85,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, check if the file exists and show the relevant section
echo "=== Reading definitions.json for Sponsorship transaction types ==="
cat packages/ripple-binary-codec/src/enums/definitions.json | python3 -c "
import json, sys
try:
    data = json.load(sys.stdin)
    tx_types = data.get('TRANSACTION_TYPES', {})
    
    # Check for SponsorshipSet and SponsorshipTransfer
    print('Current values in definitions.json:')
    print(f'  SponsorshipSet = {tx_types.get(\"SponsorshipSet\", \"NOT FOUND\")}')
    print(f'  SponsorshipTransfer = {tx_types.get(\"SponsorshipTransfer\", \"NOT FOUND\")}')
    
    # Check for any duplicates
    print('\nChecking for duplicate type codes:')
    values_map = {}
    for k, v in tx_types.items():
        if v in values_map:
            print(f'  CONFLICT: {k} ({v}) and {values_map[v]} ({v}) share same code!')
        else:
            values_map[v] = k
    
    # Look for nearby type codes
    print('\nNearby transaction types (codes 80-90):')
    for k, v in sorted(tx_types.items(), key=lambda x: x[1]):
        if 80 <= v <= 90:
            print(f'  {k} = {v}')
except json.JSONDecodeError as e:
    print(f'JSON parsing error: {e}')
"

Repository: XRPLF/xrpl.js

Length of output: 400


🌐 Web query:

rippled transaction type codes SponsorshipSet SponsorshipTransfer

💡 Result:

In XRPL’s rippled (mainnet/mainline) there are no TransactionType entries named SponsorshipSet or SponsorshipTransfer, so there are no corresponding UInt16 “transaction type codes” for them in the canonical TxType enum / transaction-type identifiers list. The published TxType identifiers cover types like ttPAYMENT, ttACCOUNT_SET, ttTRUST_SET, etc., but not those sponsorship names. [1]

If you’re trying to get the numeric transaction type code (UInt16) for any TransactionType name on a given network/build, the authoritative way is to query the server’s definitions and read the TRANSACTION_TYPES map (string → UInt16). [2]

  • RPC method: server_definitions
  • Then check: result.TRANSACTION_TYPES.SponsorshipSet and result.TRANSACTION_TYPES.SponsorshipTransfer
  • If those keys are missing, that server/network does not support those transaction types. [2]

Related note: in proposed “sponsorship” designs for XRPL, sponsorship is described as behavior attached to an existing Payment via a flag (e.g., tfSponsor), not as new SponsorshipSet/SponsorshipTransfer transaction types. [3]

Sources
[1] rippled TxType (transaction type identifiers / enum list) https://thejohnfreeman.github.io/rippled/namespaceripple.html
[2] XRPL Binary Format docs (definitions file + TRANSACTION_TYPES + server_definitions) https://xrpl.org/docs/references/protocol/binary-format
[3] XRPL Standards discussion (sponsorship via ttPAYMENT + tfSponsor) XRPLF/XRPL-Standards#56


Remove SponsorshipSet and SponsorshipTransfer from TRANSACTION_TYPES—they do not exist in rippled.

These transaction types are not defined in rippled's TxType enum. Sponsorship in XRPL is implemented via a flag (tfSponsor) on Payment transactions, not as separate transaction types. Including codes 85 and 86 for non-existent types creates incorrect serialization mapping and will cause deserialization failures against actual rippled nodes.

🤖 Prompt for AI Agents
In `@packages/ripple-binary-codec/src/enums/definitions.json` around lines 3876 -
3877, The TRANSACTION_TYPES mapping includes two invalid entries
"SponsorshipSet" and "SponsorshipTransfer" with codes 86 and 85; remove these
keys from the TRANSACTION_TYPES object in definitions.json to avoid creating
invalid serialization IDs, and search for any usages of the symbols
SponsorshipSet or SponsorshipTransfer elsewhere (e.g., any lookup code that
expects those enum keys) and update or remove those references so
deserialization/serialization only uses real rippled TxType names; ensure the
JSON remains valid after deletion and run unit/serialization tests to confirm no
remaining dependencies.

Comment on lines +48 to +51
export type SponsoredObject = Exclude<
LedgerEntry,
Amendments | FeeSettings | LedgerHashes
>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find and check BaseLedgerEntry.ts for Sponsor field documentation
echo "=== BaseLedgerEntry.ts Sponsor field documentation ==="
find . -name "BaseLedgerEntry.ts" -type f | head -1 | xargs cat -n | grep -A 5 -B 5 "Sponsor"

echo ""
echo "=== LedgerEntry.ts content (full) ==="
find . -name "LedgerEntry.ts" -type f | head -1 | xargs wc -l

echo ""
echo "=== Check if DirectoryNode and NegativeUNL are in LedgerEntry union ==="
find . -name "LedgerEntry.ts" -type f | head -1 | xargs cat -n | grep -E "DirectoryNode|NegativeUNL"

echo ""
echo "=== accountSponsoring.ts current content ==="
find . -name "accountSponsoring.ts" -type f | head -1 | xargs cat -n

Repository: XRPLF/xrpl.js

Length of output: 4741


Add DirectoryNode and NegativeUNL to the SponsoredObject type exclusions.

Per the BaseLedgerEntry documentation, the Sponsor field "must not appear on DirectoryNode, Amendments, FeeSettings, or NegativeUNL objects." The current type excludes only Amendments, FeeSettings, and LedgerHashes, but both DirectoryNode and NegativeUNL are part of the LedgerEntry union and need to be excluded:

export type SponsoredObject = Exclude<
  LedgerEntry,
  Amendments | FeeSettings | LedgerHashes | DirectoryNode | NegativeUNL
>

Also update the JSDoc comment on lines 45-46 to reflect all four excluded types.

🤖 Prompt for AI Agents
In `@packages/xrpl/src/models/methods/accountSponsoring.ts` around lines 48 - 51,
The SponsoredObject type currently excludes only Amendments, FeeSettings, and
LedgerHashes from the LedgerEntry union but needs to also exclude DirectoryNode
and NegativeUNL; update the SponsoredObject type declaration (the
Exclude<LedgerEntry, ...>) to include DirectoryNode and NegativeUNL and update
the JSDoc above (referencing SponsoredObject and LedgerEntry) to list all four
excluded types (DirectoryNode, Amendments, FeeSettings, NegativeUNL) so the docs
match the type.

Comment on lines +150 to +171
// Validate flag combinations with tfDeleteObject
const flags = typeof tx.Flags === 'number' ? tx.Flags : 0
const tfDeleteObject = SponsorshipSetFlags.tfDeleteObject

if (flags === tfDeleteObject) {
// When deleting, cannot specify FeeAmount, MaxFee, or ReserveCount
if (tx.FeeAmount !== undefined) {
throw new ValidationError(
'SponsorshipSet: FeeAmount cannot be specified with tfDeleteObject',
)
}
if (tx.MaxFee !== undefined) {
throw new ValidationError(
'SponsorshipSet: MaxFee cannot be specified with tfDeleteObject',
)
}
if (tx.ReserveCount !== undefined) {
throw new ValidationError(
'SponsorshipSet: ReserveCount cannot be specified with tfDeleteObject',
)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: tfDeleteObject check uses strict equality instead of bitwise AND.

Line 154 checks flags === tfDeleteObject, which only matches when tfDeleteObject is the sole flag set. If a user combines tfDeleteObject with other flags (e.g., tfDeleteObject | tfSponsorshipSetRequireSignForFee), this guard is silently bypassed and the disallowed fields (FeeAmount, MaxFee, ReserveCount) won't be rejected.

Use a bitwise check instead:

🐛 Proposed fix
-  if (flags === tfDeleteObject) {
+  if ((flags & tfDeleteObject) !== 0) {
🤖 Prompt for AI Agents
In `@packages/xrpl/src/models/transactions/sponsorshipSet.ts` around lines 150 -
171, The check for tfDeleteObject currently uses strict equality (flags ===
tfDeleteObject) so it only fires when tfDeleteObject is the sole flag; change it
to a bitwise test (e.g., (flags & SponsorshipSetFlags.tfDeleteObject) !== 0) so
the delete-branch runs whenever the tfDeleteObject bit is set; keep the existing
validation that throws ValidationError for tx.FeeAmount, tx.MaxFee, and
tx.ReserveCount when that bit is set.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant