Skip to content

Xls 68 sponsored fees reserves updated#720

Open
cybele-ripple wants to merge 70 commits into
mainfrom
XLS-68-sponsored-fees-reservesupdated
Open

Xls 68 sponsored fees reserves updated#720
cybele-ripple wants to merge 70 commits into
mainfrom
XLS-68-sponsored-fees-reservesupdated

Conversation

@cybele-ripple

Copy link
Copy Markdown
Collaborator

This PR supports the new amendment for sponsored fees and reserves for the java library.

Changes here use XRPLF/rippled#5887

@codecov

codecov Bot commented Apr 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.19626% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.62%. Comparing base (6234b48) to head (631dfd4).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...org/xrpl/xrpl4j/crypto/signing/SignatureUtils.java 88.88% 2 Missing ⚠️
...l4j/model/transactions/SponsorshipValidations.java 89.47% 1 Missing and 1 partial ⚠️
...g/xrpl/xrpl4j/model/flags/SponsorshipSetFlags.java 96.96% 0 Missing and 1 partial ⚠️
...pl/xrpl4j/model/transactions/SponsorSignature.java 95.83% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main     #720      +/-   ##
============================================
+ Coverage     93.50%   93.62%   +0.11%     
- Complexity     2606     2699      +93     
============================================
  Files           486      497      +11     
  Lines          6578     6790     +212     
  Branches        565      585      +20     
============================================
+ Hits           6151     6357     +206     
- Misses          270      274       +4     
- Partials        157      159       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@cybele-ripple

Copy link
Copy Markdown
Collaborator Author

/ai-review

@xrplf-ai-reviewer xrplf-ai-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.

ℹ️ Note: This is a large diff (208,487 chars). Complex issues deep in the diff may receive less attention.

Missing cryptographic domain separation in sponsorSign — see inline comment.

Review by Claude Opus 4.6 · Prompt: V14


// Sponsor single-signing uses the same signable bytes as regular single-signing.
// The sponsor signs the complete transaction binary (including the sponsee's SigningPubKey).
final UnsignedByteArray signableTransactionBytes = this.signatureUtils.toSignableBytes(transaction);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

sponsorSign reuses regular signable bytes — no domain separation from the account-owner path. sponsorMultiSign correctly uses toSponsorMultiSignableBytes; the single-sign path should mirror this with a dedicated method and distinct prefix to prevent signatures being cross-applicable between roles.

Suggested change
final UnsignedByteArray signableTransactionBytes = this.signatureUtils.toSignableBytes(transaction);
final UnsignedByteArray signableTransactionBytes = this.signatureUtils.toSponsorSignableBytes(transaction);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I have changed toSignableBytes to toSponsorSignableBytes

*/
@Beta
@JsonProperty("Sponsor")
Optional<Address> sponsor();

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.

Even though other ledger objects are not listed here but some of them like MpTokenObject, MpTokenIssuanceObject etc do have reserve requirements. Should we add Sponsor field to such ledger objects as well?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think we should not add Sponsor to other ledger objects. There is some ambiguity in "Allowed Ledger Entry Types" ("Any other ledger entry type that contributes to an account's owner reserve, subject to implementation details") and I want to ensure that any added ledger object are truly supported

*/
@JsonProperty("Flags")
@Value.Default
default Flags flags() {

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.

Should we create SponsorshipFlags?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes. Just added the file xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/flags/SponsorshipFlags.java which creates these sponsorship flags

*
* @return An {@link Optional} {@link UnsignedInteger} containing the sponsor flags.
*/
@com.google.common.annotations.Beta

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.

nit: Should we use just the class name instead of fully qualified name? This applied to other places in this PR.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes. Imported and used just the class name here and here in this file

*/
@com.google.common.annotations.Beta
@JsonProperty("SponsorFlags")
Optional<UnsignedInteger> sponsorFlags();

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.

Should we create SponsorFlags class that extends TransactionFlags?

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.

Also, should we add the checks mentioned in the spec?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I agree we should create SponsorFlags to extend another class, but we should extend Flags instead of TransactionFlags since other flag types do this. I added SponsorFlags
I added validation checks for SponsorFields here

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.

Should the return value use the flag type, like this?

Optional<SponsorFlags> sponsorFlags();

*/
@JsonProperty("Flags")
@Value.Default
default TransactionFlags flags() {

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.

Should we create SponsorshipSetFlags?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Created xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/flags/SponsorshipSetFlags.java and associated tests xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/flags/SponsorshipSetFlagsTest.java

*
* @throws JsonProcessingException if JSON is not valid.
*/
public String encodeForMultiSigningWithSigningPubKey(

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.

Changes in this file can be reverted.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This change has been removed from the PR. There are now no changes in xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/

*
* @see "https://github.com/XRPLF/XRPL-Standards/blob/master/XLS-0068-sponsored-fees-and-reserves/README.md"
*/
@Disabled("SponsorshipTransfer requires featureSponsorship amendment which may not be enabled on test networks")

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.

Can we remove Disabled and instead use this image - legleux/xrpld:sponsor so that CI can pass for SponsorshipTransferIT? We can make a note to review the image back to develop once this rippled changed is merged and we have Docker image from develop branch.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added legleux/xrpld:sponsor to RippledContainer.java here

* Scenario: tfSponsorshipCreate - Creating a new sponsorship where sponsor uses multi-sig.
*/
@Test
public void testSponsorshipTransferSingleSponseeMultiSponsor() throws JsonRpcClientErrorException,

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.

Since we have not executed SignerListSet transaction to register signers for sponsor, I doubt this test would pass when run locally on standalone instance. Should we make sure to remove the Disabled and see if these tests run locally on standalone instance?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Removed the disabled line for these tests but they still failed with the legleux/xrpld:sponsor rippled image because the featureSponsorship amendment is not registered in Feature.cpp

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.

The feature name is Sponsor. Can you give it a try?

Reviewing once the integration tests passes either locally or on the CI pipeline will avoid back and forth.

@@ -0,0 +1,492 @@
package org.xrpl.xrpl4j.tests;

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.

  1. As per xrpl4j IT conventions we have one test file per amendment, we should merge these two IT files into one.
  2. It would be good to add lifecycle tests that tries to exercise as many fields and flags of various transactions and ledger objects introduced in this PR. For example:
    1. Tests for Fee sponsorship
      1. Alice sponsors Bob to pay for transaction fees
      2. Check ledger object that was modified/created and assert if Alice/Bob appears in it.
      3. Bob submits a Payment transaction.
      4. Assert that fees were paid by Alice and not Bob.
      5. Bob transfers ownership to Charlie.
      6. Assert that ledger objects involved are correctly reflected.
    2. Test for Ledger object sponsorship
      1. Similar idea as 2.1

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

  1. Changes to integration tests have been added to a single file, SponsorshipIT.java.

  2. I have added each of these test cases here.

* @return An {@link Optional} {@link List} of {@link Signer} objects.
*/
@JsonProperty("Signers")
Optional<List<SignerWrapper>> signers();

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.

The Singers needs to be sorted by AccountID in case of multi-signing. Batch and Lending Protocol have that sorting in-place which should be applied here as well.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added sorting in this file here

  if (!sortedSigners()) {
        return ImmutableSponsorSignature.builder()
          .from(this)
          .signers(signers().get().stream()
            .sorted(Comparator.comparing(wrapper -> new BigInteger(
              AddressCodec.getInstance().decodeAccountId(wrapper.signer().account()).hexValue(), 16
            )))
            .collect(Collectors.toList()))
          .sortedSigners(true)
          .build();
      }

@Value.Immutable
@JsonSerialize(as = ImmutableSponsorshipSet.class)
@JsonDeserialize(as = ImmutableSponsorshipSet.class)
public interface SponsorshipSet extends Transaction {

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.

Swap the RippledContainer image from legleux/xrpld:sponsor (pulled
from Docker Hub) to a locally-built xrpld:sponsor-local tag with
PullPolicy.defaultPolicy(), so integration tests run against the
locally-built rippled sponsor-branch image.
…sorshipIT

Move the charlieAddress declaration in testSponsorshipTransferReassign
from the top of the method to immediately before its first use in
Step 3, satisfying the checkstyle VariableDeclarationUsageDistance rule
(distance was 6, max allowed 3).
…sorshipIT

Move the charlieKeyPair declaration in testSponsorshipTransferReassign
from the top of the method to immediately before its first use in
Step 3 (alongside charlieAddress), satisfying the checkstyle
VariableDeclarationUsageDistance rule.
Restore the publicly-pullable legleux/xrpld:sponsor image and
PullPolicy.alwaysPull() so integration tests run on GitHub Actions,
which has no access to the locally-built xrpld:sponsor-local tag
introduced in 2773b22.
Per XLS-0068, the Sponsorship ledger object carries a SponseeNode
field (UInt64, nth=32). SponsorshipObject.java already serializes it
via @JsonProperty("SponseeNode"), but the field was absent from
definitions.json, so any binary-codec round-trip of a Sponsorship
object emitted by rippled would fail to decode.

Matches rippled source: sfields.macro line 155
  TYPED_SFIELD(sfSponseeNode, UINT64, 32)
Base automatically changed from rp/lending-protocol to main May 20, 2026 18:21
Merges main (Dynamic MPT XLS-94D, XLS-65 Vault, XLS-66 Lending Protocol,
Issue polymorphism refactor, Q2 dep upgrades, bug fixes) while preserving
XLS-68 sponsored fees changes (sponsorSign/sponsorMultiSign, SponsorshipObject,
SponsorshipSet/Transfer, SponsorSignature, SponsorshipValidations).

Resolved conflicts:
- Vault/Loan/LoanBroker objects: took main's AssetAmount→Amount refactor
- Transaction.java/TransactionType/LedgerObject: merged both sides' additions
- AbstractTransactionSigner: removed duplicate counterpartySign methods,
  fixed signingHelper→signatureHelper, moved overloads adjacent for checkstyle
- MetaMpTokenIssuanceObject: removed duplicate domainId() method
- Deleted orphaned AssetAmountDeserializer/Serializer and AssetAmountTest
- RippledContainer: kept legleux/xrpld:sponsor image for CI
*
* @see "https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0056-batch"
*/
public boolean tfInnerBatchTxn() {
*
* @return {@code true} if {@code tfFullyCanonicalSig} is set, otherwise {@code false}.
*/
public boolean tfFullyCanonicalSig() {
…y check

Replaced explicit (Consumer<CreateContainerCmd>) cast with an inferred
lambda so that the docker-java-api transitive dependency is no longer
directly referenced in source, fixing the dependency:analyze-only failure.
…devnet/testnet

- Add docker-java-api as explicit test dependency in integration-tests pom.xml
  to satisfy dependency:analyze-only (RippledContainer directly uses CreateContainerCmd
  bytecode regardless of the source-level cast)
- Annotate SponsorshipIT with @DisabledIf to skip on devnet/testnet/clio environments
  where the featureSponsorship amendment is not yet enabled, matching the pattern
  used by BatchTransactionIT, LendingProtocolIT, SingleAssetVaultIT, and XChainIT
@JsonProperty("SponsorSignature")
Optional<SponsorSignature> sponsorSignature();

// TODO: Add Granular Permission fields (SponsorFee, SponsorReserve) for sponsorship once #689 is merged.

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.

TODO nees to be addressed.

Objects.requireNonNull(transaction);

// Validate sponsorship fields per XLS-0068
SponsorshipValidations.validateSponsorFields(transaction);

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.

We shouldn't inject statically like this. If we have a dependency like SponsorshipValidations then we need to inject via the constructor. This will allow proper mocking for testing.

That said, I'm not convinced that this check should occur here in SignatureUtils. This function is merely serializing bytes. The correctness or non-correctness checks seem like they should be in the transaction itself, or possibly in the signing function itself (as opposed to here).

If we want to consider adding an @Check default function in Transaction.java that might be a good candidate. Transaction.java is not itself an immutable, but we do have some prior art here where we annotation transactionType() function with @Value.Default. The immutables codegen picks this up and applies it to all Transactions.

* @return An {@link UnsignedByteArray}.
*/
@Beta
public UnsignedByteArray toSponsorSignableBytes(final Transaction transaction) {

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.

This method doesn't seem any different from toSignableBytes(final Transaction transaction). Is there any reason we have it like this (i.e., with a different name)?

Objects.requireNonNull(privateKeyable);
Objects.requireNonNull(transaction);

final UnsignedByteArray signableTransactionBytes = this.signatureUtils.toSponsorSignableBytes(transaction);

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.

I might be missing something, but if privateKeyable is the Sponsor's private key, then couldn't this function simply call this.signatureUtils.toSignableBytes(transaction) instead?

If that answer is "yes", then do we need sponsorSign at all?

I see that we need a special function for sponsorMultiSign, so even if this is a redudant function, I kind of like the API part of it. In that case, could this just be implemented like this?

@Override
  public <T extends Transaction> Signature sponsorSign(final P privateKeyable, final T transaction) {
    Objects.requireNonNull(privateKeyable);
    Objects.requireNonNull(transaction);

    return this.sign(privateKeyable, transaction).signature();
  }

}

@Override
public <T extends Transaction> Signature sponsorSign(final P privateKeyable, final T transaction) {

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.

Should this return a SingleSignedTransaction<T> instead of a `Signature?

I'm inclined to say "no" because sign that returns a SingleSignedTransaction<T> appears to be an anomaly rather than a pattern, but this is still probably worth discussing.

* Its API is subject to change.</p>
*/
@Beta
protected static final PaymentFlags SPONSOR_CREATED_ACCOUNT = new PaymentFlags(0x00080000L);

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.

I don't see this change reflected in PaymentFlagsTests.

*/
@Beta
@JsonProperty("Sponsor")
Optional<Address> sponsor();

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.

Let's be sure to update unit tests for this. In particular, we'll want to make sure serialization works with and without this field being present.

*/
@Beta
@JsonProperty("Sponsor")
Optional<Address> sponsor();

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.

Let's be sure to update unit tests for this. In particular, we'll want to make sure serialization works with and without this field being present.

*/
@Beta
@JsonProperty("Sponsor")
Optional<Address> sponsor();

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.

Let's be sure to update unit tests for this (e.g., EscrowObjectJsonTests). In particular, we'll want to make sure serialization works with and without this field being present.

We should do this for any ledger object that gets a sponsor.

*/
@Beta
@JsonProperty("LowSponsor")
Optional<Address> lowSponsor();

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.

Let's be sure to update unit tests for this. In particular, we'll want to make sure serialization works with and without this field being present.

@Value.Immutable
@JsonSerialize(as = ImmutableSponsorSignature.class)
@JsonDeserialize(as = ImmutableSponsorSignature.class)
public interface SponsorSignature {

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.

I think this class needs to move to package org.xrpl.xrpl4j.crypto.signing;

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