Skip to content

Sponsored fees reserves - #1335

Open
cybele-ripple wants to merge 12 commits into
mainfrom
sponsored-fees-reserves
Open

Sponsored fees reserves#1335
cybele-ripple wants to merge 12 commits into
mainfrom
sponsored-fees-reserves

Conversation

@cybele-ripple

@cybele-ripple cybele-ripple commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

High Level Overview of Change

Adds a "Sponsored fees & reserves" section to the account detail page
XLS spec - https://github.com/XRPLF/XRPL-Standards/tree/2733d3ad6f4814957fdbac635c057240064ec388/XLS-0068-sponsored-fees-and-reserves

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
  • Translation Updates
  • Release

Codebase Modernization

  • Updated files to React Hooks
  • Updated files to TypeScript

Preview

Note: this preview includes mock data which is not included in this PR.

Sponsored Fees & Reserves from the Accounts page

Minimized

sponFeesReservesMin

Expanded

sponFeesReservesMax

SponsorshipSet Transaction Types

Created

sponsorshipSetCreated

Deleted

sponSetDeleted

SponsorshipTransfer Types

Create

sponTransferCreate

Reassign

sponTransferReassign

End

sponTransferEnd

Standard Sponsor

Simple View

paymentSimple

Detail View

paymentDetailed

Raw View

paymentRaw

MPT Token Issuance

Create

mptTokenIssuanceCreate

Test Plan

Included tests in src/rippled/lib/test/rippled.test.ts

Surfaces XLS-68 sponsorship data on the account detail page: whether
another account is sponsoring this account's base reserve
(AccountRoot.Sponsor) and/or its transaction fees (Sponsorship ledger
object), matching the Figma spec for this feature.
Build table rows from an array instead of duplicated JSX, use the
existing AccountState type instead of any, and move the
show-if-sponsored guard out to the call site to match how
SignersCard/nftMinter/paychannels are conditionally rendered elsewhere
on the account page.
# Conflicts:
#	src/containers/Accounts/index.tsx
@kuan121
kuan121 self-requested a review August 2, 2026 14:20
Comment thread public/locales/en-US/translations.json
Comment thread src/containers/Accounts/SponsoredFeesReserves/index.tsx
An account can have more than one active Sponsorship object; getAccountSponsorship() previously used .find() and silently dropped all but the first, so the account page only ever showed one fee sponsor.
The Sponsored fees & reserves table truncated sponsor addresses via
shortenAccount, but full addresses are more useful for verifying who
is sponsoring an account.
…Sponsor field display

Adds transaction detail rendering (Description/Simple/TableDetail) for the
SponsorshipSet and SponsorshipTransfer transaction types, following the
existing per-transaction-type component pattern, plus generic support for
the transaction-common Sponsor/SponsorFlags/SponsorSignature fields so any
co-sponsored transaction shows who is sponsoring its fee and/or reserve.
Match sibling account-page sections: capitalize the title, make it
collapsible (collapsed by default) like Account Properties/Assets Held,
drop the redundant Status column, and show "No Sponsors" instead of
hiding the section entirely when an account has none. Also drop the
sponsor-scope text from the transaction Simple tab's Sponsor row to
keep the sidebar compact.
Comment thread src/containers/shared/transactionUtils.ts
Comment thread src/containers/shared/components/Transaction/SponsorshipSet/types.ts Outdated
Comment thread src/rippled/lib/rippled.ts Outdated
"account_page_signers": "Signers",
"account_page_signer_weight": "Weight",
"account_page_details": "Details",
"account_page_current_sequence": "Current Sequence",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  1. Missing transaction display names. Every other type has transaction_type_name_. Without transaction_type_name_SponsorshipSet / _SponsorshipTransfer, TxLabel falls back to defaultValue: type and renders the raw camelCase SponsorshipSet instead of Sponsorship Set — in the ledger view, transaction tables, and search results.
  2. Missing keys for other language files

Comment thread src/containers/shared/transactionUtils.ts
Comment thread src/rippled/accountState.ts Outdated
Comment thread src/rippled/lib/rippled.ts Outdated
account,
ledger_index: ledgerIndex,
type: 'sponsorship',
limit: 400,

@kuan121 kuan121 Aug 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The limit of rippled’s RPC methods does not mean “search all objects for this account and return at most 400 sponsorship objects.” Instead, it means “examine at most 400 account objects and return any sponsorship objects found within those 400.”

For example, if an account has 450 objects and its sponsorship objects happen to be the 405th and 420th objects, a request with limit: 400 could return zero sponsorship objects.

I think we’ll need to loop through the paginated results, either until we’ve examined all account objects or until we reach a predefined cap, such as 4,000 objects.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

getAccountSponsorship now pages through account_objects and examines up to 4000 objects. Added a test covering the multi-page case

const rows: { scopeKey: ScopeKey; sponsor: string }[] = [
...(account.sponsorship ?? []).map(({ owner }) => ({
scopeKey: 'account_page_sponsored_scope_transaction_fees' as ScopeKey,
sponsor: owner,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we check the values of FeeAmount and RemainingOwnerCount before claiming that the sponsor is sponsoring both fees and reserves? For example, if FeeAmount is 0 or RemainingOwnerCount is 0, do we still want to say that the sponsor is sponsoring both?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added a Number(feeAmount) > 0 filter before rendering a Transaction Fees row

Comment thread public/locales/en-US/translations.json Outdated
"sponsorship_operation_create": "Create",
"sponsorship_operation_reassign": "Reassign",
"sponsorship_operation_end": "End",
"sponsorship_set_description": "<Sponsor/> sponsors transaction fees for <Sponsee/>",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Only transaction fees?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

There is now a branch to account for whether feeAmountDelta/remainingOwnerCountDelta are present here

Comment thread src/containers/shared/components/Transaction/SponsorshipSet/parser.ts Outdated
@kuan121

kuan121 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

/ai-review set-config --profile=dge

@ripple-code-reviewer

Copy link
Copy Markdown

Repository ripple/explorer is not adopted yet. Use --adopt first.

@kuan121

kuan121 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

/ai-review --adopt

@ripple-code-reviewer

Copy link
Copy Markdown

Repository ripple/explorer has been adopted for auto-reviews by @kuan121.

@kuan121

kuan121 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

/ai-review set-config --profile=dge

@ripple-code-reviewer

Copy link
Copy Markdown

Updated ripple/explorer configuration: review-profile → dge.

@kuan121

kuan121 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

/ai-review

@ripple-code-reviewer ripple-code-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Solid, well-structured feature addition (sponsorship UI, transaction types, rippled API changes) with comprehensive test coverage. Found one likely bug in the new getAccountSponsorship error handling where new Error(message, code) won't actually attach a .code property to the error (the built-in Error constructor only accepts an options object as its second argument, not a status code), which contradicts what the accompanying test expects. Also found one hardcoded aria-label string that bypasses the translation function, per this repo's established i18n convention.

Cross-referenced against the pinned spec commit
(XRPLF/XRPL-Standards@2733d3a, branch 68-updates) and found three
inconsistencies:

- SponsorshipTransfer's tfSponsorshipEnd/Create/Reassign flags reverted
  back to the upper-16-bit values (0x10000/0x20000/0x40000) per the
  latest spec revision.
- SponsorshipSet's FeeAmount/ReserveCount fields are now
  FeeAmountDelta/RemainingOwnerCountDelta and represent deltas applied
  to the existing Sponsorship object, not absolute values — relabeled
  as "Fee Amount Change"/"Reserve Count Change" with explicit sign.
- The Sponsorship ledger entry's own field is RemainingOwnerCount, not
  ReserveCount — fixes a pre-existing bug in the account page's fee
  sponsorship display from before this set of changes.

@ripple-code-reviewer ripple-code-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Field naming collision in SponsorshipTransfer causing duplicate sponsor rendering—see inline.


export interface SponsorshipTransfer extends TransactionCommonFields {
ObjectID?: string
Sponsor?: string

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Field name collision: Sponsor conflicts with generic tx-level field. Rename to NewSponsor (like SponsorshipSet's CounterpartySponsor); update parser, Simple, TableDetail, Description:

Suggested change
Sponsor?: string
NewSponsor?: string

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Great call but incorrect suggested fix. Should gate the generic sponsor UI on a real co-sponsorship signal (data.tx.SponsorSignature / data.tx.SponsorFlags), or exclude the SponsorshipSet/SponsorshipTransfer types from it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed based on your comment (@pdp2121). The generic sponsor UI is now excluded only when SponsorshipTransfer's operation is create/reassign. This is now reflected in Transaction/SponsorshipTransfer/parser.ts, Transactions/SimpleTab.tsx, and Transactions/DetailTab/index.tsx

@@ -0,0 +1,9 @@
import { TransactionCommonFields } from '../types'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

These types files are no longer needed. Please use xrpl.js latest for accuracy/consistency

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

- Add transaction_type_name_SponsorshipSet/SponsorshipTransfer to
  en-US (translated) and all other locale files (null placeholders),
  fixing TxLabel's fallback to raw camelCase type names.
- Add missing Payment.tfSponsorCreatedAccount flag to TX_FLAGS.
- Fix SponsorshipTransfer's Description defaulting to
  sponsorship_transfer_end_self for any unrecognized operation,
  not just a genuine end-on-self case.
- Show an explicit +/- sign on SponsorshipSet's fee amount delta,
  matching the reserve count delta's existing sign handling.
- Rename accountState/rippled's reserveCount field to
  remainingOwnerCount to match the actual XRPL field name.
- Fix getAccountSponsorship to page through account_objects via
  marker instead of only examining the first 400 objects, which
  could silently miss a sponsee's Sponsorship entries entirely.
- Filter SponsoredFeesReserves' fee sponsor rows by an actual
  positive FeeAmount instead of assuming any sponsorship entry
  implies active fee sponsorship.
- Vary SponsorshipSet's description text based on whether the
  transaction is adjusting the fee budget, the reserve budget,
  both, or neither, instead of always saying "sponsors transaction
  fees."
- Remove clearRequireSignForFee/clearRequireSignForReserve from
  SponsorshipSet's parser output, which were computed but never
  rendered anywhere.

@ripple-code-reviewer ripple-code-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The sponsored fees/reserves feature is well tested and mostly clean, but the flag-bit constants in SponsorshipSet/parser.ts and SponsorshipTransfer/parser.ts duplicate the same hex values already defined in transactionUtils.ts's TX_FLAGS map (a copy-paste-prone pattern this repo has been flagged for before). There's also a real correctness gap in getAccountSponsorship: paging silently stops after examining 4000 account_objects, which can make an account with many owned objects incorrectly show "No Sponsors" even when a Sponsorship object exists further down the list. A minor i18n gap: the new CollapsibleSection's ariaLabel is a hardcoded English string instead of going through t().

…or UI

SponsorshipTransfer's Sponsor field is the same field as the generic
transaction-common Sponsor field, but on create/reassign it means "new
sponsor of the target object," not "who's co-sponsoring this outer
transaction." Exclude the generic Sponsor row/section in that case.
On tfSponsorshipEnd, the spec requires Sponsor to be omitted for the
transfer's own purpose, so if it's present there it can only be
genuine outer co-sponsorship and is still shown.

Exports getOperation from SponsorshipTransfer/parser.ts for reuse in
SimpleTab/DetailTab, and adds test coverage for all three cases.

@ripple-code-reviewer ripple-code-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Overall this is a clean, well-tested addition of Sponsored Fees & Reserves support. The main things worth a look: bit-flag constants for SponsorshipSet/SponsorshipTransfer are re-declared in each parser file instead of reusing/sharing the single source of truth already defined in transactionUtils.ts's TX_FLAGS map (same values, three separate places), which risks silent drift if the flag values ever change. There's also a hardcoded safety cap (4000 objects) in getAccountSponsorship that can silently miss a sponsorship for accounts owning a very large number of objects, with no logging when the cap is hit. Minor: OPERATION_LABEL_KEYS is duplicated verbatim between SponsorshipTransfer/Simple.tsx and TableDetail.tsx.

xrpl@5.1.0 now ships real SponsorshipSet/SponsorshipTransfer types
matching XLS-68, so the hand-rolled types.ts files in each of those
transaction folders are removed in favor of importing directly from
'xrpl', matching the convention used by every other transaction type
in this codebase (e.g. EscrowCreate).

Explorer only ever uses xrpl.js for type-only imports (never Wallet
or Client), so the 5.0.0 breaking changes around signing defaults and
connection error handling don't apply here.

Flags is now typed as `number | GlobalFlagsInterface` on BaseTransaction,
so both parsers narrow it defensively (`typeof tx.Flags === 'number'`)
before doing bitwise flag checks, matching the existing pattern in
PaymentChannelClaim's parser.

@ripple-code-reviewer ripple-code-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pagination safety cap defeated by filtering mismatch - see inline.

// A Sponsorship object is linked into both the sponsor's and sponsee's
// owner directories, so only keep the ones where this account is sponsored.
found.push(
...resp.account_objects.filter((d: any) => d.Sponsee === account),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Safety cap broken: totalExamined counts filtered objects, not examined. Use:

Suggested change
...resp.account_objects.filter((d: any) => d.Sponsee === account),
totalExamined += SPONSORSHIP_PAGE_SIZE
Suggested change
...resp.account_objects.filter((d: any) => d.Sponsee === account),
const totalExamined = examined + SPONSORSHIP_PAGE_SIZE

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.

3 participants