Skip to content

Commit 4ba4f31

Browse files
authored
Support Token Escrows (#641)
* Add support for token escrows. * Update EscrowIT for IOU and MPT * Add migration guide.
1 parent 847b2fa commit 4ba4f31

File tree

20 files changed

+1791
-474
lines changed

20 files changed

+1791
-474
lines changed

V6_MIGRATION.md

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
# Version 5 to Version 6 Migration Guide
2+
3+
This guide outlines the breaking changes between v5.x.x and v6.0.0 and provides an upgrade path for applications using
4+
xrpl4j.
5+
6+
## Overview
7+
8+
Version 6.0.0 introduces support for
9+
the [XLS-0085 Token Escrow](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0085-token-escrow) amendment, which
10+
extends the existing Escrow functionality to support Trustline-based tokens (IOUs) and Multi-Purpose Tokens (MPTs), in
11+
addition to XRP.
12+
13+
## Breaking Changes
14+
15+
### Escrow Transaction Models
16+
17+
#### EscrowCreate.amount()
18+
19+
The `amount()` field in `EscrowCreate` has changed from `XrpCurrencyAmount` to `CurrencyAmount` to support XRP, IOU, and
20+
MPT tokens.
21+
22+
**Before (v5.x.x):**
23+
24+
```java
25+
26+
@JsonProperty("Amount")
27+
XrpCurrencyAmount amount();
28+
```
29+
30+
**After (v6.0.0):**
31+
32+
```java
33+
34+
@JsonProperty("Amount")
35+
CurrencyAmount amount();
36+
```
37+
38+
**Migration:**
39+
40+
Due to polymorphism, existing code using `XrpCurrencyAmount` will continue to work without changes:
41+
42+
```java
43+
// This still works in v6.0.0
44+
EscrowCreate escrowCreate = EscrowCreate.builder()
45+
.account(sourceAddress)
46+
.destination(destinationAddress)
47+
.amount(XrpCurrencyAmount.ofDrops(1000000))
48+
.fee(XrpCurrencyAmount.ofDrops(12))
49+
.sequence(UnsignedInteger.ONE)
50+
.build();
51+
```
52+
53+
You can now also create escrows with IOU or MPT tokens:
54+
55+
```java
56+
// New in v6.0.0: IOU escrow
57+
EscrowCreate iouEscrow = EscrowCreate.builder()
58+
.account(sourceAddress)
59+
.destination(destinationAddress)
60+
.amount(IssuedCurrencyAmount.builder()
61+
.currency("USD")
62+
.issuer(issuerAddress)
63+
.value("100")
64+
.build())
65+
.fee(XrpCurrencyAmount.ofDrops(12))
66+
.sequence(UnsignedInteger.ONE)
67+
.build();
68+
69+
// New in v6.0.0: MPT escrow
70+
EscrowCreate mptEscrow = EscrowCreate.builder()
71+
.account(sourceAddress)
72+
.destination(destinationAddress)
73+
.amount(MptCurrencyAmount.builder()
74+
.mptIssuanceId(mptIssuanceId)
75+
.value(UnsignedLong.valueOf(1000))
76+
.build())
77+
.fee(XrpCurrencyAmount.ofDrops(12))
78+
.sequence(UnsignedInteger.ONE)
79+
.build();
80+
```
81+
82+
### Escrow Ledger Objects
83+
84+
#### EscrowObject.amount()
85+
86+
The `amount()` field in `EscrowObject` has changed from `XrpCurrencyAmount` to `CurrencyAmount`.
87+
88+
**Before (v5.x.x):**
89+
90+
```java
91+
92+
@JsonProperty("Amount")
93+
XrpCurrencyAmount amount();
94+
```
95+
96+
**After (v6.0.0):**
97+
98+
```java
99+
100+
@JsonProperty("Amount")
101+
CurrencyAmount amount();
102+
```
103+
104+
**Migration:**
105+
106+
Code that expects `XrpCurrencyAmount` will need to handle the `CurrencyAmount` type. Use the `handle()` or `map()`
107+
methods to work with different currency types:
108+
109+
```java
110+
// Before (v5.x.x)
111+
XrpCurrencyAmount amount = escrowObject.amount();
112+
UnsignedLong drops = amount.toDrops();
113+
114+
// After (v6.0.0)
115+
CurrencyAmount amount = escrowObject.amount();
116+
amount.handle(
117+
// Handle XRP
118+
xrpAmount ->{
119+
UnsignedLong drops = xrpAmount.toDrops();
120+
},
121+
// Handle IOU
122+
issuedCurrencyAmount ->{
123+
String value = issuedCurrencyAmount.value();
124+
},
125+
// Handle MPT
126+
mptAmount ->{
127+
UnsignedLong value = mptAmount.value();
128+
}
129+
);
130+
```
131+
132+
#### New EscrowObject Fields
133+
134+
`EscrowObject` and `MetaEscrowObject` now include two new optional fields:
135+
136+
- **`transferRate()`**: Stores the transfer rate (for IOUs) or transfer fee (for MPTs) at escrow creation time. This
137+
rate is locked and used during settlement even if the issuer changes the rate later.
138+
- **`issuerNode()`**: A reference to the issuer's directory node when the issuer is neither the source nor destination.
139+
140+
```java
141+
Optional<TransferRate> transferRate();
142+
143+
Optional<String> issuerNode();
144+
```
145+
146+
These fields are automatically populated by the XRPL when creating token escrows and do not require any action from
147+
developers.
148+
149+
## New Features
150+
151+
### AccountSet Flag for IOU Escrows
152+
153+
A new `AccountSetFlag` has been added to allow IOU issuers to enable their tokens to be held in escrows.
154+
155+
**New Flag:**
156+
157+
```java
158+
/**
159+
* Allow trust line tokens (IOUs) issued by this account to be held in escrow (requires the TokenEscrow amendment)
160+
* and can only be enabled by the issuer account.
161+
*/
162+
ALLOW_TRUSTLINE_LOCKING(17)
163+
```
164+
165+
**Usage:**
166+
167+
IOU issuers must set this flag before their tokens can be used in escrows:
168+
169+
```java
170+
AccountSet accountSet = AccountSet.builder()
171+
.account(issuerAddress)
172+
.fee(XrpCurrencyAmount.ofDrops(12))
173+
.sequence(accountInfo.accountData().sequence())
174+
.setFlag(AccountSetFlag.ALLOW_TRUSTLINE_LOCKING)
175+
.signingPublicKey(issuerKeyPair.publicKey())
176+
.build();
177+
```
178+
179+
A corresponding `AccountRootFlags` entry has also been added:
180+
181+
```java
182+
public static final AccountRootFlags ALLOW_TRUSTLINE_LOCKING =
183+
new AccountRootFlags(0x40000000);
184+
185+
public boolean lsfAllowTrustLineLocking() {
186+
return this.isSet(AccountRootFlags.ALLOW_TRUSTLINE_LOCKING);
187+
}
188+
```
189+
190+
### MPT Locked Amount Tracking
191+
192+
`MpTokenObject` and `MpTokenIssuanceObject` now include an optional `lockedAmount()` field to track amounts locked in
193+
escrows:
194+
195+
```java
196+
/**
197+
* The amount of this MPToken that is locked in escrows.
198+
*/
199+
@JsonProperty("LockedAmount")
200+
Optional<MpTokenNumericAmount> lockedAmount();
201+
```
202+
203+
This field is automatically updated by the XRPL when MPT escrows are created, finished, or cancelled.
204+
205+
## Backward Compatibility
206+
207+
All existing XRP escrow functionality remains fully backward compatible:
208+
209+
- Existing code using `EscrowCreate`, `EscrowFinish`, and `EscrowCancel` with XRP will continue to work without
210+
modification
211+
- All existing unit and integration tests should continue to pass
212+
- JSON serialization and deserialization remain compatible
213+
214+
## Additional Resources
215+
216+
- **XLS-0085 Specification**: https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0085-token-escrow

xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/flags/AccountRootFlags.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,15 @@ public class AccountRootFlags extends Flags {
107107
@Beta
108108
public static final AccountRootFlags ALLOW_TRUSTLINE_CLAWBACK = new AccountRootFlags(0x80000000L);
109109

110+
/**
111+
* Constant {@link AccountRootFlags} for the {@code lsfAllowTrustLineLocking} account flag.
112+
*
113+
* <p>This constant will be marked {@link Beta} until the TokenEscrow amendment is enabled on mainnet. Its API is
114+
* subject to change.</p>
115+
*/
116+
@Beta
117+
public static final AccountRootFlags ALLOW_TRUSTLINE_LOCKING = new AccountRootFlags(0x40000000);
118+
110119
/**
111120
* Required-args Constructor.
112121
*
@@ -259,4 +268,17 @@ public boolean lsfDisallowIncomingTrustline() {
259268
public boolean lsfAllowTrustLineClawback() {
260269
return this.isSet(AccountRootFlags.ALLOW_TRUSTLINE_CLAWBACK);
261270
}
271+
272+
/**
273+
* Allows trust line tokens (IOUs) issued by this account to be held in escrow.
274+
*
275+
* <p>This constant will be marked {@link Beta} until the TokenEscrow amendment is enabled on mainnet. Its API is
276+
* subject to change.</p>
277+
*
278+
* @return {@code true} if {@code lsfAllowTrustLineLocking} is set, otherwise {@code false}.
279+
*/
280+
@Beta
281+
public boolean lsfAllowTrustLineLocking() {
282+
return this.isSet(AccountRootFlags.ALLOW_TRUSTLINE_LOCKING);
283+
}
262284
}

xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/EscrowObject.java

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
* Licensed under the Apache License, Version 2.0 (the "License");
1010
* you may not use this file except in compliance with the License.
1111
* You may obtain a copy of the License at
12-
*
12+
*
1313
* http://www.apache.org/licenses/LICENSE-2.0
14-
*
14+
*
1515
* Unless required by applicable law or agreed to in writing, software
1616
* distributed under the License is distributed on an "AS IS" BASIS,
1717
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -29,21 +29,26 @@
2929
import org.immutables.value.Value;
3030
import org.xrpl.xrpl4j.model.flags.Flags;
3131
import org.xrpl.xrpl4j.model.transactions.Address;
32+
import org.xrpl.xrpl4j.model.transactions.CurrencyAmount;
3233
import org.xrpl.xrpl4j.model.transactions.EscrowCancel;
3334
import org.xrpl.xrpl4j.model.transactions.EscrowCreate;
3435
import org.xrpl.xrpl4j.model.transactions.EscrowFinish;
3536
import org.xrpl.xrpl4j.model.transactions.Hash256;
36-
import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount;
3737

3838
import java.util.Optional;
3939

4040
/**
41-
* Represents a held payment of XRP waiting to be executed or canceled. An {@link EscrowCreate} transaction creates an
42-
* {@link EscrowObject} in the ledger. A successful {@link EscrowFinish} or {@link EscrowCancel} transaction deletes the
43-
* object. If the {@link EscrowObject} has a crypto-condition, the payment can only succeed if an {@link EscrowFinish}
41+
* Represents a held payment of XRP, IOU tokens, or MPT tokens waiting to be executed or canceled. An
42+
* {@link EscrowCreate} transaction creates an {@link EscrowObject} in the ledger. A successful {@link EscrowFinish} or
43+
* {@link EscrowCancel} transaction deletes the object.
44+
*
45+
* <p>If the {@link EscrowObject} has a crypto-condition, the payment can only succeed if an {@link EscrowFinish}
4446
* transaction provides the corresponding fulfillment that satisfies the condition (the only supported crypto-condition
4547
* type is PREIMAGE-SHA-256). If the {@link EscrowObject} has a {@link EscrowObject#finishAfter()} time, the held
4648
* payment can only execute after that time.
49+
*
50+
* <p>With the TokenEscrow amendment, escrows can hold IOU tokens (trustline-based) or MPT tokens (Multi-Purpose
51+
* Tokens) in addition to XRP. The transfer rate or transfer fee is locked at escrow creation time.
4752
*/
4853
@Value.Immutable
4954
@JsonSerialize(as = ImmutableEscrowObject.class)
@@ -71,36 +76,72 @@ default LedgerEntryType ledgerEntryType() {
7176
}
7277

7378
/**
74-
* The {@link Address} of the owner (sender) of this held payment. This is the account that provided the XRP, and gets
75-
* it back if the held payment is canceled.
79+
* The {@link Address} of the owner (sender) of this held payment. This is the account that provided the tokens, and
80+
* gets them back if the held payment is canceled.
7681
*
7782
* @return The {@link Address} of the owner of this escrow.
7883
*/
7984
@JsonProperty("Account")
8085
Address account();
8186

8287
/**
83-
* The destination {@link Address} where the XRP is paid if the held payment is successful.
88+
* The destination {@link Address} where the tokens are paid if the held payment is successful.
8489
*
8590
* @return The {@link Address} of the destination of this escrow.
8691
*/
8792
@JsonProperty("Destination")
8893
Address destination();
8994

9095
/**
91-
* The amount of XRP, in drops, to be delivered by the held payment.
96+
* The number of tokens to be delivered by the held payment.
9297
*
93-
* @return A {@link XrpCurrencyAmount} denoting the amount.
98+
* <p>Can be one of:
99+
* <ul>
100+
* <li>{@link org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount} - XRP in drops</li>
101+
* <li>{@link org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount} - IOU tokens</li>
102+
* <li>{@link org.xrpl.xrpl4j.model.transactions.MptCurrencyAmount} - MPT tokens</li>
103+
* </ul>
104+
*
105+
* @return A {@link CurrencyAmount} denoting the amount.
94106
*/
95107
@JsonProperty("Amount")
96-
XrpCurrencyAmount amount();
108+
CurrencyAmount amount();
109+
110+
/**
111+
* The transfer rate or transfer fee locked at escrow creation time. This field is present only for IOU or MPT escrows
112+
* (not XRP escrows).
113+
*
114+
* <p>For IOU tokens, this represents the transfer rate (in billionths of a unit) from the issuer's
115+
* {@code TransferRate} setting at the time of escrow creation. For MPT tokens, this represents the transfer fee (in
116+
* ten-thousandths of a basis point) from the MPT issuance's {@code TransferFee} setting at the time of escrow
117+
* creation.
118+
*
119+
* <p>This value is used during {@link org.xrpl.xrpl4j.model.transactions.EscrowFinish} to apply the correct fee,
120+
* even if the issuer changes their transfer rate/fee after the escrow was created.
121+
*
122+
* @return An {@link Optional} of type {@link UnsignedInteger} representing the locked transfer rate or fee.
123+
*/
124+
@JsonProperty("TransferRate")
125+
Optional<UnsignedInteger> transferRate();
126+
127+
/**
128+
* A hint indicating which page of the issuer's owner directory links to this object, in case the directory consists
129+
* of multiple pages. This field is present only when the issuer is neither the source nor the destination of the
130+
* escrow (i.e., for IOU or MPT escrows where a third-party issuer is involved).
131+
*
132+
* @return An {@link Optional} of type {@link String} containing the issuer node hint.
133+
*/
134+
@JsonProperty("IssuerNode")
135+
Optional<String> issuerNode();
136+
97137

98138
/**
99139
* A PREIMAGE-SHA-256 crypto-condition in DER hexadecimal encoding. If present, the
100-
* {@link org.xrpl.xrpl4j.model.transactions.EscrowFinish} transaction
101-
* must contain a fulfillment that satisfies this condition.
140+
* {@link org.xrpl.xrpl4j.model.transactions.EscrowFinish} transaction must contain a fulfillment that satisfies this
141+
* condition.
102142
*
103143
* @return An {@link Optional} of type {@link Condition} containing the escrow condition.
144+
*
104145
* @see "https://tools.ietf.org/html/draft-thomas-crypto-conditions-04#section-8.1"
105146
*/
106147
@JsonProperty("Condition")
@@ -120,8 +161,8 @@ default LedgerEntryType ledgerEntryType() {
120161
/**
121162
* The time, in <a href="https://xrpl.org/basic-data-types.html#specifying-time">seconds since the Ripple Epoch</a>,
122163
* after which this held payment can be finished. Any {@link org.xrpl.xrpl4j.model.transactions.EscrowFinish}
123-
* transaction before this time fails.
124-
* (Specifically, this is compared with the close time of the previous validated ledger.)
164+
* transaction before this time fails. (Specifically, this is compared with the close time of the previous validated
165+
* ledger.)
125166
*
126167
* @return An {@link Optional} of type {@link UnsignedLong} representing the finish after time.
127168
*/

0 commit comments

Comments
 (0)