XLS-0068: Sponsored Fees and Reserves#3199
Conversation
WalkthroughThis 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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 thetoEqualcheck on Line 37.The tests at Lines 6–14 (checking
SponsorFee= 65549 andSponsorReserve= 65550 individually) are fully covered by thetoEqual(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:SponsorSignaturevalidation 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 ofSigningPubKey/TxnSignatureorSignersto be present whenSponsorSignatureis provided, similar to how the base transaction rejects an emptySignersarray.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) === 0packages/xrpl/src/models/transactions/sponsorshipTransfer.ts (1)
29-52:Sponsor,SponsorFlags, andSponsorSignatureare already inherited fromBaseTransaction.These fields are redeclared in
SponsorshipTransferwith the same types asBaseTransaction. 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 (fromBaseTransaction) 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
Sponsorand thenassert.isUndefined(escrow.Sponsor). Consider consolidating into a single test to avoid redundancy.Also applies to: 318-335
337-362: TheSponsoredOwnerCount <= OwnerCountassertion 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
tfSponsorshipSetRequireSignForFeeandtfSponsorshipClearRequireSignForFeesimultaneously (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.
| "SponsorshipSet": 86, | ||
| "SponsorshipTransfer": 85, |
There was a problem hiding this comment.
🧩 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.SponsorshipSetandresult.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.
| export type SponsoredObject = Exclude< | ||
| LedgerEntry, | ||
| Amendments | FeeSettings | LedgerHashes | ||
| > |
There was a problem hiding this comment.
🧩 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 -nRepository: 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.
| // 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', | ||
| ) | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
High Level Overview of Change
Support for Sponsored Fees and Reserves. (Note: this is an open PR for V2)
XLS
rippled PR
Context of Change
Type of Change
Did you update HISTORY.md?
Test Plan
Unit tests. Integration tests TODO, will not pass yet