Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

XLS-85d Token Escrow #272

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
226 changes: 226 additions & 0 deletions XLS-0085d-token-escrow/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
# XLS-85d Token Escrow — Token-Enabled Escrows

```markdown
Title: Token-Enabled Escrows
Revision: 7 (2025-02-17)
Type: Draft
Author:
Denis Angell, XRPL-Labs [dangell7](https://github.com/dangell7)
Affiliation: XRPL-Labs
```

> This proposal, XLS85d, replaces [XLS34d](https://github.com/XRPLF/XRPL-Standards/discussions/88) and draws inspiration from https://github.com/XRPLF/XRPL-Standards/discussions/133

The proposed `TokenEscrow` amendment to the XRP Ledger (XRPL) protocol enhances the existing `Escrow` functionality by enabling support for both Trustline-based tokens (IOUs) and Multi-Purpose Tokens (MPTs). This amendment introduces changes to ledger objects, transactions, and transaction processing logic to allow escrows to use IOU tokens and MPTs, while respecting issuer controls and maintaining ledger integrity.

# 1. Implementation

This amendment extends the functionality of escrows to support both IOUs and MPTs, accounting for the specific behaviors and constraints associated with each token type.

## 1.1. Overview of Token Types

### 1.1.1. IOU Tokens

- **Trustlines**: IOUs rely on trustlines between accounts.
- **Issuer Controls**:
- **Require Authorization (`lsfRequireAuth`)**: Issuers may require accounts to be authorized to hold their tokens.
- **Freeze Conditions (`lsfGlobalFreeze`, `lsfDefaultRipple`)**: Issuers can freeze tokens, affecting their transferability.
- **Transfer Mechanics**: Transfers occur via adjustments to trustline balances.
- **Transfer Rates**: Issuers can set a `TransferRate` that affects transfers involving their tokens.

### 1.1.2. Multi-Purpose Tokens (MPTs)

- **No Trustlines**: MPTs do not utilize trustlines.
- **Issuer Controls**:
- **Transfer Flags (`tfMPTCanTransfer`)**: Tokens must have this flag enabled to be transferable and to participate in transactions like escrows.
- **Require Authorization (`tfMPTRequireAuth`)**: Issuers may require authorization for accounts to hold their tokens.
- **Lock Conditions (`lsfMPTokenLock`)**: Tokens can be locked by the issuer, affecting their transferability.
- **Transfer Mechanics**: Transfers occur by moving token balances directly between accounts.
- **Transfer Fees**: Issuers can set a `TransferFee` (analogous to `TransferRate` for IOUs) that affects transfers involving their tokens.

## 1.2. Escrow Transactions and Logic

### 1.2.1. `EscrowCreate`

The `EscrowCreate` transaction is modified as follows:

| Field | Required? | JSON Type | Internal Type | Description |
|-----------|-----------|------------------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------|
| `Amount` | Yes | Object or String | Amount | The amount to deduct from the sender's balance and and set aside in escrow. Once escrowed, this amount can either go to the Destination address (after any `Finish` times/conditions) or returned to the sender (after any cancellation times/conditions). Can represent [XRP, in drops](https://xrpl.org/docs/references/protocol/data-types/basic-data-types#specifying-currency-amounts), an [IOU](https://xrpl.org/docs/concepts/tokens/fungible-tokens#fungible-tokens) token, or an [MPT](https://xrpl.org/docs/concepts/tokens/fungible-tokens/multi-purpose-tokens). Must always be a positive value.|
| `CancelAfter` | False | Number | UInt32 | (Optional) The time, in seconds since the Ripple Epoch, when this escrow expires. This value is immutable; the funds can only be returned to the sender after this time. Required when creating an Escrow with IOU or MPT |

**Failure Conditions:**

- **Issuer is the Source:**
- If the source account is the issuer of the token, the transaction fails with `tecNO_PERMISSION`.

- **Issuer Does Not Allow Token Escrow or Transfer:**
- **IOU Tokens**: If the issuer's account does not have the `lsfAllowTokenEscrow` flag set, the transaction fails with `tecNO_PERMISSION`.
- **MPTs**:
- If the `MPTokenIssuance` of the token being escrowed lacks the `lsfMPTCanEscrow` flag, the transaction fails with `tecNO_PERMISSION`.
- If the `MPTokenIssuance` of the token being escrowed lacks the `lsfMPTCanTransfer` flag, the transaction fails with `tecNO_PERMISSION` unless the destination address of the Escrow is the issuer of the MPT.

- **Source Account Not Authorized to Hold Token:**
- If the issuer requires authorization and the source is not authorized, the transaction fails with `tecNO_AUTH`.

- **Source Account's Token Holding Issues:**
- **IOU Tokens**: If the source lacks a trustline with the issuer, the transaction fails with `tecUNFUNDED `.
- **MPTs**: If the source does not hold the MPT, the transaction fails with `tecOBJECT_NOT_FOUND`.

- **Source Account is Frozen or Token is Locked:**
- If the token is frozen (global/individual/deepfreeze) (IOU) or locked (MPT) for the source, the transaction fails with `tecFROZEN`.

- **Insufficient Spendable Balance:**
- If the source account lacks sufficient spendable balance, the transaction fails with `tecUNFUNDED`.

**State Changes:**

- **Adjustment from Source to Issuer:**
- **IOU Tokens**: The escrow `Amount` is deducted from the source's trustline balance.
- **MPTs**: The escrow `Amount` is deducted from the source's MPT balance. The `sfOutstandingBalance` of the MPT issuance remains unchanged. The `sfEscrowAmount` is increased on both the source's MPT and the MPT issuance.
- **Escrow Object Creation:**
- The `Escrow` ledger object includes:
- `CancelAfter`: When the Escrow Expires (Required on IOU/MPT)
- `Amount`: Tokens held in escrow.
- `TransferRate`: `TransferRate` (IOUs) or `TransferFee` (MPTs) at creation.
- `IssuerNode`: Reference to the issuer’s ledger node if applicable.

Choose a reason for hiding this comment

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

Is IssuerNode a pointer to the issuer's AccountRoot entry, or does it point to something else?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It points to the page where the escrow keylet was stored in the issuer directory

Choose a reason for hiding this comment

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

Ah ok, that was actually my next question -- it sounds like all Escrows involving an issuer have their keylets stored in the issuer's owner directory, is that correct? Going back to this thread, I think this means you could compute the total amount of IOUs issued by an issuer by iterating over the issuer's owner directory, summing up all RippleState balances and Escrowed amounts. Is my understanding correct there?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, if the issuer is the sender or the receiver there is a keylet stored in the issuers directory.

As for the issuer's escrowed amount I'm not certain of the exact implementation until I write it...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Collaborator

Choose a reason for hiding this comment

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

@nkramer44 Can this be resolved?


### 1.2.2. `EscrowFinish`

**Failure Conditions:**

- **Destination Not Authorized to Hold Token:**
- If authorization is required and the destination is not authorized, transaction fails with `tecNO_AUTH`.

- **Destination Lacks Trustline or MPT Holding:**
- **IOU Tokens**: If the destination lacks a trustline with the issuer, transaction fails with `tecNO_LINE`.
- **MPTs**: If the destination does not hold the MPT, transaction fails with `tecNO_ENTRY`.
- A new trustline or MPT holding may be created during `EscrowFinish` if authorization is not required.

- **Cannot Create Trustline or MPT Holding:**
- If unable to create due to lack of authorization or reserves, transaction fails with `tecNO_AUTH` or `tecINSUFFICIENT_RESERVE`.

- **Destination Account is Frozen or Token is Locked:**
- **IOU Tokens**:
- **Deep Freeze**: If the token is deep frozen, the transaction fails with `tecFROZEN`.
- **Global/Individual Freeze**: The transaction succeeds despite the token being globally or individually frozen.
- **MPTs**:
- **Lock Conditions (Equivalent to Deep Freeze)**: Transaction fails with `tecFROZEN`.

**State Changes:**

- **Auto create Trustline or MPToken:**
- **IOU Tokens**: If the IOU does not require authorization and the account submitting the transaction is the recipient, then a trustline will be created.
- **MPTs**: If the MPT does not require authorization and the account submitting the transaction is the recipient, then the MPT will be created.
- **Adjustment from Issuer to Destination:**
- **IOU Tokens**: The escrow `Amount` is added to the destination's trustline balance.
- **MPTs**:
- If the escrow sender is the issuer of the asset that was escrowed and the destination is not the issuer, then:
1. The `EscrowedAmount` on the `MPTokenIssuance` of the asset that was held in escrow is decreased by `Amount`.
2. The `Amount` on the destination's `MPToken` is increased by the escrow's `Amount`.
3. The `OutstandingAmount` on the `MPTokenIssuance` of the asset that was held in escrow is unchanged.
- If the escrow sender is not the issuer of the asset that was escrowed but the destination is the issuer of the asset, then:
1. The `EscrowedAmount` on the `MPTokenIssuance` of the asset that was held in escrow is decreased by `Amount`.
2. No `MPToken` objects are changed because MPT issuers may not hold MPTokens.
3. The `OutstandingAmount` on the `MPTokenIssuance` of the asset that was held in escrow is decreased by `Amount` (i.e., this escrow finish is a "redemption").
- If neither the escrow source nor destination is the issuer of the asset that was escrowed, then
1. The `EscrowedAmount` on the `MPTokenIssuance` of the asset that was held in escrow is decreased by the escrow `Amount`.
2. The `EscrowedAmount` on the source's `MPToken` is decreased by the escrow `Amount`.
3. The `Amount` on the destination's `MPToken` is increased by the escrow `Amount`.
4. The `OutstandingAmount` on the `MPTokenIssuance` of the asset that was held in escrow is unchanged.
- **Deletion of Escrow Object:**
- The `Escrow` object is deleted after successful settlement.

### 1.2.3. `EscrowCancel`

**Failure Conditions:**

- **Source Not Authorized to Hold Token:**
- If authorization is required and the source is not authorized, transaction fails with `tecNO_AUTH`.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there anything we can do to mitigate this condition? This feels like a stuck escrow (which may be unavoidable, but would be nice to avoid). In this case, the sender may not be able to rectify the situation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure, I think if the holder is not authorized and there is require auth then allowing them to hold the token violates the require auth.

Copy link
Collaborator

Choose a reason for hiding this comment

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

if the holder is not authorized and there is require auth then allowing them to hold the token violates the require auth.

Looking into this for MPTs at least, if an account has a non-zero balance of MPT, then it can't be unauthorized. The issuer would have to Clawback all MPT first, and then un-authorize.

I think we should strive for the same behavior here (i.e., if an issuer is trying to unauthorize an account that has MPT in escrow, then this transaction should fail). Primary reason is it's very inelegant to have functionality work differently across features.

To pull this off though (at least for MPT), we'd need to perhaps track the total amount of MPT in-escrow on the MPToken object of the sender (or a boolean maybe like hasEscrow?). Alternatively, the transaction check would have to iterate through an account's escrows, checking for any MPT that matches the issuer doing the un-auth.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You could store the escrowed balance on the MPT. So the MPTAmount is 0 but the sfEscrowAmount != 0.

Done here: XRPLF/rippled@3e029b6

Copy link
Collaborator

Choose a reason for hiding this comment

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

Just double-checking that there's a unit test for this change in XRPLF/rippled#5185? It's not immediately clear from XRPLF/rippled@3e029b6 and I'm still making my way through XRPLF/rippled#5185


- **Source Lacks Trustline or MPT Holding:**
- **IOU Tokens**: If the source lacks a trustline with the issuer, transaction fails with `tecNO_LINE`.
- **MPTs**: If the source does not hold the MPT, transaction fails with `tecNO_ENTRY`.
- A new trustline or MPT holding may be created during `EscrowCancel` if authorization is not required.

- **Cannot Create Trustline or MPT Holding:**
- If unable to create due to lack of authorization or reserves, transaction fails with `tecNO_AUTH` or `tecINSUFFICIENT_RESERVE`.

- **Source Account is Frozen or Token is Locked:**
- **IOU Tokens**:
- **Deep Freeze**: The transaction succeeds, allowing the escrow to be cancelled.
- **Global/Individual Freeze**: The transaction succeeds, allowing the escrow to be cancelled.
- **MPTs**:
- **Lock Conditions (Deep Freeze Equivalent)**: The transaction succeeds, allowing the escrow to be cancelled.

**State Changes:**

- **Auto create Trustline or MPToken:**
- **IOU Tokens**: If the IOU does not require authorization and the account submitting the transaction is the recipient, then a trustline will be created.
- **MPTs**: If the MPT does not require authorization and the account submitting the transaction is the recipient, then the MPT will be created.
- **Adjustment from Issuer to Source:**
- **IOU Tokens**: The escrow `Amount` is added to the source's trustline balance.
- **MPTs**: The escrow `Amount` is added to the source's MPT balance. The `sfOutstandingBalance` of the MPT issuance remains unchanged. The `sfEscrowAmount` is decreased on both the source's MPT and the MPT issuance.
- **Deletion of Escrow Object:**
- The `Escrow` object is deleted after successful cancellation.

## 1.3. Key Differences Between IOU and MPT Escrows

| Aspect | IOU Tokens | Multi-Purpose Tokens (MPTs) |
|-------------------------------|--------------------------------------------------------------------------|----------------------------------------------------------------------------|
| **Trustlines** | Required between accounts and issuer | Not used |
| **Issuer Flag for Escrow** | `lsfAllowTokenEscrow` (account flag) | `tfMPTCanEscrow` (token flag) |
| **Transfer Flags** | N/A | `tfMPTCanTransfer` must be enabled for escrow |
| **Require Auth** | Applicable (`lsfRequireAuth`); accounts must be authorized prior to holding tokens | Applicable (`tfMPTRequireAuth`); accounts must be authorized prior to holding tokens |
| **Destination Authorization** | Not required at creation; required at settlement; cannot be granted during `EscrowFinish` if authorization required | Not required at creation; required at settlement; cannot be granted during `EscrowFinish` if authorization required |
| **Freeze/Lock Conditions** | **Deep Freeze** prevents `EscrowFinish`, but allows `EscrowCancel`; Global/Individual Freeze allows both operations | **Lock Conditions (Deep Freeze Equivalent)** prevent `EscrowFinish`, but allow `EscrowCancel` |
| **Transfer Rates/Fees** | `TransferRate` stored at creation and applied during settlement | `TransferFee` stored at creation and applied during settlement |
| **Outstanding Amount** | Remains unchanged during escrow | Remains unchanged during escrow |
| **Account Deletion** | Escrows prevent account deletion | Escrows prevent account deletion |

## 1.4. Transfer Rates and Fees

### 1.4.1. IOU Tokens (`TransferRate`)

- **Locked Transfer Rate**: The `TransferRate` is captured at the time of `EscrowCreate` and stored in the `Escrow` object. It is used during `EscrowFinish`, even if the issuer changes it later.
- **Fee Calculation**: The escrowed amount is adjusted according to the `TransferRate` upon settlement, potentially reducing the final amount credited to the destination.

### 1.4.2. MPTs (`TransferFee`)

- **Locked Transfer Fee**: The `TransferFee` is captured at the time of `EscrowCreate` and stored in the `Escrow` object, similar to IOUs.
- **Fee Calculation**: The escrowed amount is adjusted according to the `TransferFee` upon settlement, potentially reducing the final amount credited to the destination.
- **Consistent Fee Application**: Both IOUs and MPTs use the transfer rate or fee stored at escrow creation, ensuring predictability for the destination.

## 1.5. Ledger Object Updates

### 1.5.1 `Escrow` Ledger Object

The `Escrow` ledger object is updated as follows:

| Field Name | JSON Type | Internal Type | Description |
|-----------------|------------------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `Amount` | Object or String | Amount | The amount to be delivered by the held payment. Can represent XRP, an IOU token, or an MPT. Must always be a positive value. |
| `TransferRate` | Number | UInt32 | The transfer rate or fee at which the funds are escrowed, stored at creation and used during settlement. Applicable to both IOUs and MPTs. |
| `IssuerNode` | Number | UInt64 | *(Optional)* The ledger index of the issuer's directory node associated with the `Escrow`. Used when the issuer is neither the source nor destination account.|

### 1.5.2 `MPToken` Ledger Object

The `MPToken` ledger object is updated as follows:

| Field Name | JSON Type | Internal Type | Description |
|-----------------|------------------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `sfEscrowAmount` | Object | Amount | *(Optional)* The total of all outstanding escrows for this issuance. |
Copy link
Collaborator

Choose a reason for hiding this comment

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

At some point, I'd like to see these changes incorporated into the XLS for MPTs.


### 1.5.3 `MPTokenIssuance` Ledger Object

The `MPTokenIssuance` ledger object is updated as follows:

| Field Name | JSON Type | Internal Type | Description |
|-----------------|------------------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `sfEscrowAmount` | Object | Amount | *(Optional)* The total of all outstanding escrows for this issuance. |

## 1.6. Future Considerations

1. Clawback: XLS-85d currently does not provide a direct “clawback” mechanism within an active Escrow. If your use case requires clawback, you can either finish or cancel the Escrow (as appropriate) and then perform a clawback of the funds outside of the Escrow context. In other words, once the token amount returns to the issuer or source account, the existing clawback features for IOUs or MPTs can be used on those returned funds.

2. Issuer as Source: XLS-85d currently does not allow the issuer to be the source of the Escrow. If your use case requires this functionality you should create a new account, send the MPT or IOU to that account and then Escrow the token.