Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a1dd179
Upcoming changes to Single Asset Vault (DGE-7974)
cybele-ripple Sep 3, 2026
d44e8f9
Address review of Single Asset Vault changes (DGE-7974)
cybele-ripple Sep 3, 2026
0504c81
Add missing @Override annotation to tfInnerBatchTxn() in Vault flags …
cybele-ripple Sep 4, 2026
53d66bd
Address PR review feedback for Vault flags (DGE-7974)
cybele-ripple Sep 17, 2026
5f48277
Add VaultKind, SubscriptionDate, RedemptionDate to support closed-end…
cybele-ripple Sep 17, 2026
fe777cf
Enable LendingProtocolV1_1 amendment for local rippled test container…
cybele-ripple Sep 17, 2026
96be695
Merge remote-tracking branch 'origin/main' into single-asset-vault
cybele-ripple Sep 22, 2026
09691be
Add remaining LendingProtocolV1_1 protocol surface
cybele-ripple Sep 24, 2026
e37030e
Attribute the vault deposit-block and donate flags to LendingProtocol…
cybele-ripple Sep 24, 2026
61b6380
Merge remote-tracking branch 'origin/main' into single-asset-vault
cybele-ripple Sep 28, 2026
aae84d3
Fix LendingProtocolIT tecNO_PERMISSION failures on LoanSet/LoanBrokerSet
cybele-ripple Sep 28, 2026
2336840
Widen LendingProtocolIT's Vault SubscriptionDate buffer for real netw…
cybele-ripple Sep 28, 2026
e1e1285
Fix scanForResult timeout mismatch after widening SubscriptionDate bu…
cybele-ripple Sep 29, 2026
e933650
Widen awaitLoanPaymentLate's timeout for real-network ledger close pa…
cybele-ripple Sep 29, 2026
73b0bf3
Omit VaultKind.OPEN_ENDED from serialized Vault JSON
cybele-ripple Oct 2, 2026
96efb58
Address remaining PR 832 review feedback: hashCode, unread locals
cybele-ripple Oct 14, 2026
cff3ebd
Merge remote-tracking branch 'origin/main' into single-asset-vault
cybele-ripple Oct 14, 2026
efd6fdb
Merge remote-tracking branch 'origin/main' into single-asset-vault
cybele-ripple Oct 15, 2026
41c8cb4
Remove LendingProtocolV1_2-gated Vault flags from this LP1.1 branch
cybele-ripple Oct 16, 2026
3b4caf3
Restore VaultFlagsTest.java instead of deleting it
cybele-ripple Oct 16, 2026
2cfdbd0
Address PR review feedback for single-asset-vault
cybele-ripple Oct 19, 2026
994e7ff
Add IT coverage for VaultWithdraw CredentialIDs and VaultDelete MemoData
cybele-ripple Oct 20, 2026
aabded6
fix: remove unread local variable in LendingProtocolIT
cybele-ripple Oct 21, 2026
e4fb977
Merge remote-tracking branch 'origin/main' into single-asset-vault
cybele-ripple Oct 21, 2026
1b0fd62
fix: enforce investment-period bounds in VaultCreate.check()
cybele-ripple Oct 21, 2026
16c1398
Merge remote-tracking branch 'origin/main' into single-asset-vault
cybele-ripple Oct 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package org.xrpl.xrpl4j.model.ledger;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.annotations.Beta;
import com.google.common.primitives.UnsignedInteger;
import com.google.common.primitives.UnsignedLong;
import org.immutables.value.Value;
import org.xrpl.xrpl4j.model.flags.VaultFlags;
import org.xrpl.xrpl4j.model.transactions.Address;
Expand All @@ -13,6 +15,7 @@
import org.xrpl.xrpl4j.model.transactions.Hash256;
import org.xrpl.xrpl4j.model.transactions.MpTokenIssuanceId;
import org.xrpl.xrpl4j.model.transactions.VaultData;
import org.xrpl.xrpl4j.model.transactions.VaultKind;
import org.xrpl.xrpl4j.model.transactions.WithdrawalPolicy;

import java.util.Optional;
Expand Down Expand Up @@ -195,6 +198,51 @@ default AssetScale scale() {
return AssetScale.of(UnsignedInteger.ZERO);
}

/**
* The ledger-entry version of this vault, which selects the accounting rules rippled applies to it. Vaults
* created before the {@code LendingProtocolV1_1} amendment activated carry no {@code LEVersion} field and are
* treated as version {@code 0} (legacy accounting); vaults created afterwards carry version {@code 1}
* (LoanBroker cash-basis accounting).
*
* @return An {@link UnsignedInteger}, defaulting to {@link UnsignedInteger#ZERO}.
*/
@JsonProperty("LEVersion")
@Value.Default
default UnsignedInteger leVersion() {
return UnsignedInteger.ZERO;
}

/**
* Distinguishes a closed-ended vault from the default open-ended kind. Only a closed-ended vault may have a
* {@code LoanBroker} attached to it once the {@code LendingProtocolV1_1} amendment is enabled.
*
* @return A {@link VaultKind}, defaulting to {@link VaultKind#OPEN_ENDED}.
*/
@JsonProperty("VaultKind")
@Value.Default
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = VaultKind.OpenEndedFilter.class)
default VaultKind vaultKind() {
return VaultKind.OPEN_ENDED;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

/**
* The time, in seconds since the Ripple Epoch, after which this closed-ended vault's Subscription phase ends
* and its Investment phase begins. Only present when {@link #vaultKind()} is {@link VaultKind#CLOSED_ENDED}.
*
* @return An {@link Optional} of type {@link UnsignedLong} representing the subscription date.
*/
@JsonProperty("SubscriptionDate")
Optional<UnsignedLong> subscriptionDate();

/**
* The time, in seconds since the Ripple Epoch, after which this closed-ended vault's Investment phase ends and
* its Redemption phase begins. Only present when {@link #vaultKind()} is {@link VaultKind#CLOSED_ENDED}.
*
* @return An {@link Optional} of type {@link UnsignedLong} representing the redemption date.
*/
@JsonProperty("RedemptionDate")
Optional<UnsignedLong> redemptionDate();
Comment thread
kuan121 marked this conversation as resolved.

/**
* The share MPTokenIssuance information for this vault. Only present in {@code vault_info} RPC responses.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import org.xrpl.xrpl4j.model.AddressConstants;
import org.xrpl.xrpl4j.model.flags.TransactionFlags;

import java.util.HashSet;
import java.util.List;
import java.util.Optional;

/**
Expand Down Expand Up @@ -76,6 +78,15 @@ default TransactionFlags flags() {
@JsonProperty("DestinationTag")
Optional<UnsignedInteger> destinationTag();

/**
* Set of Credentials to authorize a deposit made by this transaction. Each member of the array must be the ledger
* entry ID of a Credential entry in the ledger. Requires the {@code LendingProtocolV1_1} amendment.
*
* @return A list of type {@link Hash256}.
*/
@JsonProperty("CredentialIDs")
List<Hash256> credentialIds();

/**
* Validates LoanBrokerCoverWithdraw data verification preconditions.
*/
Expand All @@ -95,5 +106,16 @@ default void check() {
!dest.equals(AddressConstants.ACCOUNT_ZERO),
"Destination must not be the zero account."
));

if (!credentialIds().isEmpty()) {
Preconditions.checkArgument(
credentialIds().size() <= 8,
"CredentialIDs should have less than or equal to 8 items."
);
Preconditions.checkArgument(
new HashSet<>(credentialIds()).size() == credentialIds().size(),
"CredentialIDs should have unique values."
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.annotations.Beta;
import com.google.common.primitives.UnsignedLong;
import org.immutables.value.Value;
import org.xrpl.xrpl4j.model.flags.VaultCreateFlags;
import org.xrpl.xrpl4j.model.ledger.Issue;
Expand Down Expand Up @@ -99,6 +100,48 @@ default VaultCreateFlags flags() {
@JsonProperty("Scale")
Optional<AssetScale> scale();

/**
* Distinguishes a closed-ended vault from the default open-ended kind. Requires the {@code LendingProtocolV1_1}
* amendment to be present on the transaction at all; only a closed-ended vault may later have a
* {@code LoanBroker} attached to it.
*
* @return An optionally-present {@link VaultKind}.
*/
@JsonProperty("VaultKind")
Optional<VaultKind> vaultKind();

/**
* The time, in seconds since the Ripple Epoch, after which a closed-ended vault's Subscription phase ends and
* its Investment phase begins. Required, and only allowed, when {@link #vaultKind()} is
* {@link VaultKind#CLOSED_ENDED}.
*
* @return An {@link Optional} of type {@link UnsignedLong} representing the subscription date.
*/
@JsonProperty("SubscriptionDate")
Optional<UnsignedLong> subscriptionDate();

/**
* The time, in seconds since the Ripple Epoch, after which a closed-ended vault's Investment phase ends and its
* Redemption phase begins. Required, and only allowed, when {@link #vaultKind()} is
* {@link VaultKind#CLOSED_ENDED}.
*
* @return An {@link Optional} of type {@link UnsignedLong} representing the redemption date.
*/
@JsonProperty("RedemptionDate")
Optional<UnsignedLong> redemptionDate();

/**
* The minimum gap, in seconds, allowed between {@link #subscriptionDate()} and {@link #redemptionDate()} for a
* closed-ended vault, matching rippled's {@code kMinInvestmentPeriod}.
*/
UnsignedLong MIN_INVESTMENT_PERIOD_SECONDS = UnsignedLong.valueOf(180);

/**
* The maximum gap, in seconds, allowed between {@link #subscriptionDate()} and {@link #redemptionDate()} for a
* closed-ended vault, matching rippled's {@code kMaxInvestmentPeriod} (30 x 365.2425-day years).
*/
UnsignedLong MAX_INVESTMENT_PERIOD_SECONDS = UnsignedLong.valueOf(946708560L);

/**
* Validate VaultCreate preconditions.
*/
Expand All @@ -124,6 +167,34 @@ default void check() {
throw new IllegalArgumentException("Scale is only allowed for IOU assets.");
}
));

Comment thread
cybele-ripple marked this conversation as resolved.
boolean isClosedEnded = vaultKind().map(VaultKind.CLOSED_ENDED::equals).orElse(false);
if (!isClosedEnded) {
if (subscriptionDate().isPresent() || redemptionDate().isPresent()) {
throw new IllegalArgumentException(
"SubscriptionDate and RedemptionDate are only allowed when VaultKind is CLOSED_ENDED."
);
}
} else {
if (!subscriptionDate().isPresent() || !redemptionDate().isPresent()) {
throw new IllegalArgumentException(
"SubscriptionDate and RedemptionDate are both required when VaultKind is CLOSED_ENDED."
);
}
UnsignedLong subscription = subscriptionDate().get();
UnsignedLong redemption = redemptionDate().get();
if (redemption.compareTo(subscription) <= 0) {
throw new IllegalArgumentException("RedemptionDate must be strictly after SubscriptionDate.");
}
UnsignedLong gap = redemption.minus(subscription);
if (gap.compareTo(MIN_INVESTMENT_PERIOD_SECONDS) < 0 || gap.compareTo(MAX_INVESTMENT_PERIOD_SECONDS) >= 0) {
throw new IllegalArgumentException(
"The gap between SubscriptionDate and RedemptionDate must be at least " +
MIN_INVESTMENT_PERIOD_SECONDS + " seconds and less than " + MAX_INVESTMENT_PERIOD_SECONDS +
" seconds."
);
}
Comment thread
kuan121 marked this conversation as resolved.
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import org.immutables.value.Value;
import org.xrpl.xrpl4j.model.flags.TransactionFlags;

import java.util.Optional;

/**
* Delete a single asset vault. The vault must have no remaining assets or outstanding shares.
*
Expand Down Expand Up @@ -47,4 +49,16 @@ default TransactionFlags flags() {
@JsonProperty("VaultID")
Hash256 vaultId();

/**
* Arbitrary metadata recording why the vault is being deleted, limited to 256 bytes, in hex format. This field
* requires the {@code LendingProtocolV1_1} amendment.
*
* <p>Despite the name, this is a top-level {@code MemoData} field (rippled's {@code sfMemoData}) and is unrelated to
* the standard {@link Transaction#memos()} field.</p>
*
* @return An optionally-present {@link VaultData}.
*/
@JsonProperty("MemoData")
Optional<VaultData> memoData();
Comment thread
cybele-ripple marked this conversation as resolved.
Comment thread
cybele-ripple marked this conversation as resolved.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package org.xrpl.xrpl4j.model.transactions;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import com.google.common.annotations.Beta;

/**
* Distinguishes a closed-ended {@code Vault} from the default open-ended kind. Only a closed-ended vault may
* have a {@code LoanBroker} attached to it once the {@code LendingProtocolV1_1} amendment is enabled.
*/
@Beta
public enum VaultKind {

/**
* The default vault kind. Deposits and withdrawals are allowed at any time, and the vault has no defined
* lifecycle. An open-ended vault cannot have a {@code LoanBroker} attached once {@code LendingProtocolV1_1}
* is enabled.
*/
OPEN_ENDED(0),

/**
* A vault with a defined {@link VaultCreate#subscriptionDate()} and {@link VaultCreate#redemptionDate()},
* required in order to attach a {@code LoanBroker} once {@code LendingProtocolV1_1} is enabled.
*/
CLOSED_ENDED(1);

private final int value;

VaultKind(int value) {
this.value = value;
}

/**
* Get the integer value of this {@link VaultKind}.
*
* @return The integer value.
*/
@JsonValue
public int getValue() {
return value;
}

/**
* Construct a {@link VaultKind} from an integer value.
*
* @param value The integer value.
*
* @return The {@link VaultKind} for the given value.
*/
@JsonCreator
public static VaultKind forValue(int value) {
for (VaultKind kind : VaultKind.values()) {
if (kind.value == value) {
return kind;
}
}
throw new IllegalArgumentException("No matching VaultKind for value " + value);
}

/**
* Used with {@link com.fasterxml.jackson.annotation.JsonInclude.Include#CUSTOM} to omit {@link #OPEN_ENDED} from
* serialized JSON, matching how rippled omits {@code VaultKind} for open-ended vaults.
*/
public static class OpenEndedFilter {
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed

@Override
public boolean equals(Object obj) {
return obj == VaultKind.OPEN_ENDED;
}

@Override
public int hashCode() {
return VaultKind.OPEN_ENDED.hashCode();
}
Comment thread
kuan121 marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions;
import com.google.common.primitives.UnsignedInteger;
import org.immutables.value.Value;
import org.xrpl.xrpl4j.model.flags.TransactionFlags;

import java.util.HashSet;
import java.util.List;
import java.util.Optional;

/**
Expand Down Expand Up @@ -75,4 +78,31 @@ default TransactionFlags flags() {
@JsonProperty("DestinationTag")
Optional<UnsignedInteger> destinationTag();

/**
* Set of Credentials to authorize a deposit made by this transaction. Each member of the array must be the ledger
* entry ID of a Credential entry in the ledger. Requires the {@code LendingProtocolV1_1} amendment.
*
* @return A list of type {@link Hash256}.
*/
@JsonProperty("CredentialIDs")
List<Hash256> credentialIds();

/**
* Validate {@link VaultWithdraw#credentialIds} has less than or equal to 8 credentials, and that those
* credentials are unique.
*/
@Value.Check
default void validateCredentialIds() {
if (!credentialIds().isEmpty()) {
Preconditions.checkArgument(
credentialIds().size() <= 8,
"CredentialIDs should have less than or equal to 8 items."
);
Preconditions.checkArgument(
new HashSet<>(credentialIds()).size() == credentialIds().size(),
"CredentialIDs should have unique values."
);
}
}

}
Loading
Loading