From 1ebddc4a5b63242db9fa9ab871a21a9aa45d2aca Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sun, 10 May 2026 08:07:04 +0200 Subject: [PATCH 01/19] Create README.md --- XLS-0093-token-paychan/README.md | 310 +++++++++++++++++++++++++++++++ 1 file changed, 310 insertions(+) create mode 100644 XLS-0093-token-paychan/README.md diff --git a/XLS-0093-token-paychan/README.md b/XLS-0093-token-paychan/README.md new file mode 100644 index 000000000..fa9c26f7f --- /dev/null +++ b/XLS-0093-token-paychan/README.md @@ -0,0 +1,310 @@ +
+xls: 93d
+title: Token-Enabled Payment Channels
+description: Extends Payment Channels to support IOU tokens and Multi-Purpose Tokens (MPTs).
+author: Denis Angell  (XRPL-Labs)
+category: Amendment
+status: Draft
+created: 2025-05-24
+updated: 2026-05-06
+
+ +# Token-Enabled Payment Channels + +## 1. Abstract + +This proposal introduces the `TokenPaychan` amendment to the XRP Ledger (XRPL), extending the existing `PaymentChannel` functionality to support both Trustline-based tokens (IOUs) and Multi-Purpose Tokens (MPTs) in addition to XRP. It defines updates to ledger objects, transactions, and transaction processing logic so that payment channels can hold and settle non-XRP assets while respecting issuer controls (authorization, freeze/lock, transfer rates/fees) and preserving ledger integrity. Transfer rates and fees are captured at channel creation and applied consistently at claim time, providing predictable settlement for participants. + +## 2. Motivation + +Payment channels on the XRPL today are limited to XRP. As issued assets (IOUs) and Multi-Purpose Tokens become increasingly used for payments, settlement, and tokenization use cases, the inability to use them in off-ledger/streamed-payment workflows blocks important applications such as token-denominated micropayments, streaming payments for stablecoins, and recurring token settlements between counterparties. Extending payment channels to support IOUs and MPTs closes this gap while keeping the existing issuer guarantees (authorization, freeze/lock, transfer fees) intact and predictable. + +## 3. Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. + +This amendment extends the functionality of payment channels to support both IOUs and MPTs, accounting for the specific behaviors and constraints associated with each token type. + +### 3.1. Overview of Token Types + +#### 3.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. + +#### 3.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 payment channels. + - **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. + +### 3.2. Transactions + +#### 3.2.1. `PaymentChannelCreate` + +The `PaymentChannelCreate` transaction is modified as follows: + +| Field | Required? | JSON Type | Internal Type | Description | +|----------|-----------|------------------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `Amount` | Yes | Object or String | Amount | The amount to fund the payment channel. 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. | + +##### 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 Payment Channels:** + - **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 used lacks the `lsfMPTCanEscrow` flag, the transaction fails with `tecNO_PERMISSION`. + - If the `MPTokenIssuance` of the token being used lacks the `lsfMPTCanTransfer` flag, the transaction fails with `tecNO_PERMISSION` unless the destination address of the Payment Channel 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 channel `Amount` is deducted from the source's trustline balance. + - **MPTs**: The channel `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. +- **Payment Channel Object Creation:** + - The `PaymentChannel` ledger object includes: + - `Amount`: Tokens held in the channel. + - `Balance`: Amount already paid out (starts at zero). + - `TransferRate`: `TransferRate` (IOUs) or `TransferFee` (MPTs) at creation. + - `IssuerNode`: Reference to the issuer's ledger node if applicable. + +#### 3.2.2. `PaymentChannelFund` + +The `PaymentChannelFund` transaction is modified to support token amounts. + +##### Failure Conditions + +- **Same conditions as `PaymentChannelCreate`** for validating the funding amount and token permissions. + +##### State Changes + +- **Adjustment from Source:** + - **IOU Tokens**: The funding `Amount` is deducted from the source's trustline balance. + - **MPTs**: The funding `Amount` is deducted from the source's MPT balance. The `sfEscrowAmount` is increased accordingly. +- **Payment Channel Object Update:** + - The channel's `Amount` field is increased by the funding amount. + +#### 3.2.3. `PaymentChannelClaim` + +##### Normal Claim (Balance Update) + +When claiming without closing the channel: + +###### Failure Conditions + +- **Destination Not Authorized to Hold Token:** + - If authorization is required and the destination is not authorized, the transaction fails with `tecNO_AUTH`. +- **Destination Lacks Trustline or MPT Holding:** + - **IOU Tokens**: If the destination lacks a trustline with the issuer, the transaction fails with `tecNO_LINE`. + - **MPTs**: If the destination does not hold the MPT, the transaction fails with `tecNO_ENTRY`. + - A new trustline or MPT holding MAY be created during `PaymentChannelClaim` if authorization is not required. +- **Cannot Create Trustline or MPT Holding:** + - If unable to create due to lack of authorization or reserves, the 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)**: The 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 to Destination:** + - **IOU Tokens**: The claimed amount is added to the destination's trustline balance. + - **MPTs**: Similar to escrow logic for MPT balance adjustments. +- **Channel Balance Update:** + - The channel's `Balance` field is updated to reflect the total amount claimed. + +##### Channel Closure + +When closing the channel (either explicitly with the close flag, or automatically when fully drained): + +###### Failure Conditions + +- **Source Not Authorized to Hold Token (for remaining funds):** + - If authorization is required and the source is not authorized, the transaction fails with `tecNO_AUTH`. +- **Source Lacks Trustline or MPT Holding (for remaining funds):** + - **IOU Tokens**: If the source lacks a trustline with the issuer, the transaction fails with `tecNO_LINE`. + - **MPTs**: If the source does not hold the MPT, the transaction fails with `tecNO_ENTRY`. + - A new trustline or MPT holding MAY be created during channel closure if authorization is not required. +- **Cannot Create Trustline or MPT Holding (for source):** + - If unable to create due to lack of authorization or reserves, the 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 channel to be closed. + - **Global/Individual Freeze**: The transaction succeeds, allowing the channel to be closed. + - **MPTs**: + - **Lock Conditions (Deep Freeze Equivalent)**: The transaction succeeds, allowing the channel to be closed. + +###### State Changes + +- **Auto-create Trustline or MPToken (for source):** + - **IOU Tokens**: If the IOU does not require authorization and the account submitting the transaction is the source, then a trustline will be created. + - **MPTs**: If the MPT does not require authorization and the account submitting the transaction is the source, then the MPT will be created. +- **Adjustment to Destination (final claim):** + - Any remaining claimable amount is transferred to the destination using the same logic as normal claims. +- **Adjustment to Source (remaining funds):** + - **IOU Tokens**: Any remaining channel funds are added to the source's trustline balance. + - **MPTs**: Any remaining channel funds are 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 Payment Channel Object:** + - The `PaymentChannel` object is deleted after successful closure. + +### 3.3. Ledger Entries + +#### 3.3.1. `PaymentChannel` Ledger Object + +The `PaymentChannel` ledger object is updated as follows: + +| Field Name | JSON Type | Internal Type | Description | +|----------------|------------------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `Amount` | Object or String | Amount | The total amount allocated to the payment channel. Can represent XRP, an IOU token, or an MPT. MUST always be a positive value. | +| `Balance` | Object or String | Amount | The amount already paid out from the channel. Same asset type as `Amount`. | +| `TransferRate` | Number | UInt32 | The transfer rate or fee at which the funds are held, stored at creation and used during claims. Applicable to both IOUs and MPTs. | +| `IssuerNode` | Number | UInt64 | *(Optional)* The ledger index of the issuer's directory node associated with the `PaymentChannel`. Used when the issuer is neither the source nor destination account. | + +#### 3.3.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 and payment channels for this token. | + +#### 3.3.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 and payment channels for this issuance. | + +### 3.4. Transfer Rates and Fees + +#### 3.4.1. IOU Tokens (`TransferRate`) + +- **Locked Transfer Rate**: The `TransferRate` is captured at the time of `PaymentChannelCreate` and stored in the `PaymentChannel` object. It is used during `PaymentChannelClaim`, even if the issuer changes it later. +- **Fee Calculation**: The claimed amount is adjusted according to the `TransferRate` upon settlement, potentially reducing the final amount credited to the destination. + +#### 3.4.2. MPTs (`TransferFee`) + +- **Locked Transfer Fee**: The `TransferFee` is captured at the time of `PaymentChannelCreate` and stored in the `PaymentChannel` object, similar to IOUs. +- **Fee Calculation**: The claimed 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 channel creation, ensuring predictability for the destination. + +### 3.5. Key Differences Between IOU and MPT Payment Channels + +| Aspect | IOU Tokens | Multi-Purpose Tokens (MPTs) | +|-------------------------------|-------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------| +| **Trustlines** | Required between accounts and issuer | Not used | +| **Issuer Flag for Channels** | `lsfAllowTokenEscrow` (account flag) | `tfMPTCanEscrow` (token flag) | +| **Transfer Flags** | N/A | `tfMPTCanTransfer` must be enabled for payment channels | +| **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 claim; cannot be granted during claim if authorization required | Not required at creation; required at claim; cannot be granted during claim if authorization required| +| **Freeze/Lock Conditions** | **Deep Freeze** prevents claims, but allows closure; Global/Individual Freeze allows both operations | **Lock Conditions (Deep Freeze Equivalent)** prevent claims, but allow closure | +| **Transfer Rates/Fees** | `TransferRate` stored at creation and applied during claims | `TransferFee` stored at creation and applied during claims | +| **Outstanding Amount** | Remains unchanged during channel operations | Remains unchanged during channel operations | +| **Account Deletion** | Payment channels prevent account deletion | Payment channels prevent account deletion | + +## 4. Rationale + +The amendment reuses the existing `PaymentChannel` mechanics so issuers, wallets, and tooling can extend support to non-XRP assets without learning a new construct. Key design decisions: + +- **Lock the transfer rate/fee at creation.** Capturing `TransferRate`/`TransferFee` in the `PaymentChannel` object ensures that the destination has a predictable settlement value over the lifetime of the channel, regardless of issuer-side changes. This mirrors how other XRPL constructs that pre-commit value (e.g. escrow) treat issuer-controlled fees. +- **Account-level vs. token-level opt-in.** IOUs use an account flag (`lsfAllowTokenEscrow`) because IOU issuance is account-scoped, while MPTs use a token flag (`tfMPTCanEscrow`) because each issuance has its own configuration. This keeps each control surface where issuers already manage it. +- **Disallow the issuer as source.** Letting the issuer be the source account would conflate issuance with channel funding and complicate `sfOutstandingBalance` accounting. Issuers who want to fund a channel can route through a separate holding account, which is also recommended for operational hygiene. See §3.6. +- **Preserve `sfOutstandingBalance` and track `sfEscrowAmount` separately.** Funds in a channel are still in circulation from the issuer's perspective; tracking them under `sfEscrowAmount` keeps issuance accounting accurate while making channel-locked balances explicit. +- **Reuse existing freeze/deep-freeze and lock semantics.** Aligning IOU and MPT behavior to the deep-freeze / lock equivalence (claims blocked, closure allowed) keeps the model consistent with how those controls operate elsewhere on the ledger. +- **Auto-creation rules mirror direct payments.** Allowing trustline/MPToken auto-creation on claim only when authorization is not required matches existing token settlement behavior and avoids surprising the recipient with an unsolicited holding when the issuer requires authorization. + +### 3.6. Future Considerations + +1. **Clawback**: This specification currently does not provide a direct "clawback" mechanism within an active Payment Channel. If a use case requires clawback, the channel can be closed and existing IOU/MPT clawback features used on the returned funds. +2. **Issuer as Source**: This specification currently does not allow the issuer to be the source of the Payment Channel. If a use case requires this functionality, a new account should be created, the MPT or IOU sent to that account, and then the payment channel created with that account as the source. + +## 5. Backwards Compatibility + +This amendment is backwards compatible with existing XRP-only payment channels: any `PaymentChannel`, `PaymentChannelCreate`, `PaymentChannelFund`, and `PaymentChannelClaim` whose `Amount` is XRP behaves exactly as before. The only behavioral changes occur for new transactions whose `Amount` references an IOU or MPT. + +The following are net-new requirements introduced by this amendment and only apply to token-denominated channels: + +- New ledger fields (`TransferRate` on `PaymentChannel`; `sfEscrowAmount` on `MPToken` and `MPTokenIssuance`) are optional and ignored by pre-amendment objects. +- New issuer opt-in flags (`lsfAllowTokenEscrow` for IOUs and `tfMPTCanEscrow` for MPTs) ensure issuers must explicitly enable token-denominated channels for their assets, preventing surprise behavior on existing issuances. +- Consumers of `PaymentChannel` ledger objects (clients, indexers, channel watchers) should be updated to handle non-XRP `Amount`/`Balance` representations and the optional `TransferRate` and `IssuerNode` fields. + +There are no changes to the wire format or semantics of existing XRP-denominated channels. + +## 6. Test Plan + +The implementation will include unit and integration tests covering, at minimum: + +- XRP, IOU, and MPT variants of `PaymentChannelCreate`, `PaymentChannelFund`, and `PaymentChannelClaim`, including normal claims and channel closure. +- Issuer opt-in enforcement (`lsfAllowTokenEscrow`, `tfMPTCanEscrow`) and `tfMPTCanTransfer` requirement. +- Authorization enforcement (`lsfRequireAuth`, `tfMPTRequireAuth`) at create, claim, and closure. +- Freeze and deep-freeze behavior for IOUs and lock conditions for MPTs, for both source and destination, on claim and closure paths. +- `TransferRate` / `TransferFee` capture at creation and consistent application at claim. +- `sfEscrowAmount` accounting on `MPToken` and `MPTokenIssuance` across funding, claims, and closure. +- Auto-creation of trustlines / `MPToken` objects only when authorization is not required. +- Failure-condition matrix covering each `tec*` listed above. +- Account deletion blocking when token-denominated channels exist. + +## 7. Reference Implementation + +To be linked when the corresponding rippled PR is opened. + +## 8. Security Considerations + +- **Issuer opt-in is mandatory.** Token-denominated channels require explicit issuer opt-in (`lsfAllowTokenEscrow` for IOUs, `tfMPTCanEscrow` for MPTs). Without this, no holder can lock issuer-controlled assets in a payment channel, preserving the issuer's existing controls. +- **Issuer cannot be source.** Disallowing the issuer as the source account prevents conflation of issuance with channel funding and avoids ambiguous `sfOutstandingBalance` accounting; issuers must route through a holding account. +- **Locked transfer rate/fee.** Capturing the `TransferRate`/`TransferFee` at creation prevents an issuer from changing settlement economics mid-channel. This protects the destination from unexpected fee increases but also means a fee reduction by the issuer will not propagate to existing channels. +- **Freeze/lock semantics.** Deep freeze (IOU) and lock (MPT) block new claims but permit closure, ensuring counterparties can always recover remaining funds without enabling continued transfers when the issuer has revoked transferability. +- **Authorization at claim time.** Destinations whose holdings require authorization MUST be authorized before they can claim; auto-creation of a trustline or `MPToken` is only permitted when authorization is not required, preventing unsolicited holdings. +- **Account deletion.** Open token-denominated payment channels prevent account deletion of the source, mirroring existing XRP-channel behavior and avoiding orphaned references. +- **`sfEscrowAmount` accounting.** All funds locked in token-denominated channels are tracked via `sfEscrowAmount` on the relevant `MPToken` and `MPTokenIssuance` objects, so channel-locked balances are explicit and auditable while `sfOutstandingBalance` continues to reflect circulating supply. +- **Replay and signature behavior unchanged.** The amendment does not change the authorization model for off-ledger claim signatures (`PaymentChannelClaim` signature validation), so existing replay-protection and channel-signing analyses continue to apply. +- **Client/indexer assumptions.** Tooling that previously assumed `PaymentChannel.Amount` is always XRP must be updated; failing to do so could lead to misreporting of balances or failed settlements but cannot, by itself, cause loss of funds on-ledger. + +# Appendix + +## Appendix A: FAQ + +### A.1: Why can't the issuer be the source of a token payment channel? + +Allowing the issuer to be the source would mix issuance with channel funding and complicate the `sfOutstandingBalance` accounting on the issuance. Issuers who want this behavior can use a separate holding account as the source. + +### A.2: What happens to the transfer rate or fee if the issuer changes it after the channel is created? + +The `TransferRate` (IOU) or `TransferFee` (MPT) is captured at `PaymentChannelCreate` and stored on the `PaymentChannel` object. Subsequent changes by the issuer do not affect existing channels. + +### A.3: Can a destination receive a token they don't already hold? + +Yes, but only when the issuer does not require authorization. In that case a new trustline or `MPToken` may be auto-created during claim or closure. If authorization is required, the destination must be authorized in advance. + +### A.4: How are frozen IOUs and locked MPTs treated? + +Deep-frozen IOUs and locked MPTs block new claims (the transaction fails with `tecFROZEN`) but allow channel closure so that the source can recover remaining funds. + +### A.5: Does this affect existing XRP payment channels? + +No. XRP-denominated payment channels are fully backwards compatible and behave exactly as before this amendment. \ No newline at end of file From 5a4a5e9b1e334dd797d3a99bb669725d57d285b2 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sun, 10 May 2026 08:17:48 +0200 Subject: [PATCH 02/19] Update README.md --- XLS-0093-token-paychan/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/XLS-0093-token-paychan/README.md b/XLS-0093-token-paychan/README.md index fa9c26f7f..34fcb217a 100644 --- a/XLS-0093-token-paychan/README.md +++ b/XLS-0093-token-paychan/README.md @@ -2,7 +2,8 @@ xls: 93d title: Token-Enabled Payment Channels description: Extends Payment Channels to support IOU tokens and Multi-Purpose Tokens (MPTs). -author: Denis Angell (XRPL-Labs) +author: Denis Angell (@dangell7) +discussion-from: https://github.com/XRPLF/XRPL-Standards/discussions/287 category: Amendment status: Draft created: 2025-05-24 From e0e670aee828b6eb650205b86f2eb101f5afec26 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sun, 10 May 2026 08:20:34 +0200 Subject: [PATCH 03/19] Update README.md --- XLS-0093-token-paychan/README.md | 54 ++++++++++++++++---------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/XLS-0093-token-paychan/README.md b/XLS-0093-token-paychan/README.md index 34fcb217a..43e67905a 100644 --- a/XLS-0093-token-paychan/README.md +++ b/XLS-0093-token-paychan/README.md @@ -3,7 +3,7 @@ xls: 93d title: Token-Enabled Payment Channels description: Extends Payment Channels to support IOU tokens and Multi-Purpose Tokens (MPTs). author: Denis Angell (@dangell7) -discussion-from: https://github.com/XRPLF/XRPL-Standards/discussions/287 +proposal-from: https://github.com/XRPLF/XRPL-Standards/discussions/287 category: Amendment status: Draft created: 2025-05-24 @@ -53,8 +53,8 @@ This amendment extends the functionality of payment channels to support both IOU The `PaymentChannelCreate` transaction is modified as follows: -| Field | Required? | JSON Type | Internal Type | Description | -|----------|-----------|------------------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Field | Required? | JSON Type | Internal Type | Description | +| -------- | --------- | ---------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Amount` | Yes | Object or String | Amount | The amount to fund the payment channel. 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. | ##### Failure Conditions @@ -178,28 +178,28 @@ When closing the channel (either explicitly with the close flag, or automaticall The `PaymentChannel` ledger object is updated as follows: -| Field Name | JSON Type | Internal Type | Description | -|----------------|------------------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `Amount` | Object or String | Amount | The total amount allocated to the payment channel. Can represent XRP, an IOU token, or an MPT. MUST always be a positive value. | -| `Balance` | Object or String | Amount | The amount already paid out from the channel. Same asset type as `Amount`. | -| `TransferRate` | Number | UInt32 | The transfer rate or fee at which the funds are held, stored at creation and used during claims. Applicable to both IOUs and MPTs. | -| `IssuerNode` | Number | UInt64 | *(Optional)* The ledger index of the issuer's directory node associated with the `PaymentChannel`. Used when the issuer is neither the source nor destination account. | +| Field Name | JSON Type | Internal Type | Description | +| -------------- | ---------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Amount` | Object or String | Amount | The total amount allocated to the payment channel. Can represent XRP, an IOU token, or an MPT. MUST always be a positive value. | +| `Balance` | Object or String | Amount | The amount already paid out from the channel. Same asset type as `Amount`. | +| `TransferRate` | Number | UInt32 | The transfer rate or fee at which the funds are held, stored at creation and used during claims. Applicable to both IOUs and MPTs. | +| `IssuerNode` | Number | UInt64 | _(Optional)_ The ledger index of the issuer's directory node associated with the `PaymentChannel`. Used when the issuer is neither the source nor destination account. | #### 3.3.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 and payment channels for this token. | +| Field Name | JSON Type | Internal Type | Description | +| ---------------- | --------- | ------------- | -------------------------------------------------------------------------------------- | +| `sfEscrowAmount` | Object | Amount | _(Optional)_ The total of all outstanding escrows and payment channels for this token. | #### 3.3.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 and payment channels for this issuance. | +| Field Name | JSON Type | Internal Type | Description | +| ---------------- | --------- | ------------- | ----------------------------------------------------------------------------------------- | +| `sfEscrowAmount` | Object | Amount | _(Optional)_ The total of all outstanding escrows and payment channels for this issuance. | ### 3.4. Transfer Rates and Fees @@ -216,17 +216,17 @@ The `MPTokenIssuance` ledger object is updated as follows: ### 3.5. Key Differences Between IOU and MPT Payment Channels -| Aspect | IOU Tokens | Multi-Purpose Tokens (MPTs) | -|-------------------------------|-------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------| -| **Trustlines** | Required between accounts and issuer | Not used | -| **Issuer Flag for Channels** | `lsfAllowTokenEscrow` (account flag) | `tfMPTCanEscrow` (token flag) | -| **Transfer Flags** | N/A | `tfMPTCanTransfer` must be enabled for payment channels | -| **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 claim; cannot be granted during claim if authorization required | Not required at creation; required at claim; cannot be granted during claim if authorization required| -| **Freeze/Lock Conditions** | **Deep Freeze** prevents claims, but allows closure; Global/Individual Freeze allows both operations | **Lock Conditions (Deep Freeze Equivalent)** prevent claims, but allow closure | -| **Transfer Rates/Fees** | `TransferRate` stored at creation and applied during claims | `TransferFee` stored at creation and applied during claims | -| **Outstanding Amount** | Remains unchanged during channel operations | Remains unchanged during channel operations | -| **Account Deletion** | Payment channels prevent account deletion | Payment channels prevent account deletion | +| Aspect | IOU Tokens | Multi-Purpose Tokens (MPTs) | +| ----------------------------- | ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | +| **Trustlines** | Required between accounts and issuer | Not used | +| **Issuer Flag for Channels** | `lsfAllowTokenEscrow` (account flag) | `tfMPTCanEscrow` (token flag) | +| **Transfer Flags** | N/A | `tfMPTCanTransfer` must be enabled for payment channels | +| **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 claim; cannot be granted during claim if authorization required | Not required at creation; required at claim; cannot be granted during claim if authorization required | +| **Freeze/Lock Conditions** | **Deep Freeze** prevents claims, but allows closure; Global/Individual Freeze allows both operations | **Lock Conditions (Deep Freeze Equivalent)** prevent claims, but allow closure | +| **Transfer Rates/Fees** | `TransferRate` stored at creation and applied during claims | `TransferFee` stored at creation and applied during claims | +| **Outstanding Amount** | Remains unchanged during channel operations | Remains unchanged during channel operations | +| **Account Deletion** | Payment channels prevent account deletion | Payment channels prevent account deletion | ## 4. Rationale @@ -308,4 +308,4 @@ Deep-frozen IOUs and locked MPTs block new claims (the transaction fails with `t ### A.5: Does this affect existing XRP payment channels? -No. XRP-denominated payment channels are fully backwards compatible and behave exactly as before this amendment. \ No newline at end of file +No. XRP-denominated payment channels are fully backwards compatible and behave exactly as before this amendment. From 0fcd1825384f831d94b562a0334e0d040e7d9f56 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Mon, 13 Jul 2026 11:43:53 -0400 Subject: [PATCH 04/19] docs: align XLS-93 with implementation and XLS-85 structure --- XLS-0093-token-paychan/README.md | 341 +++++++++++++------------------ 1 file changed, 144 insertions(+), 197 deletions(-) diff --git a/XLS-0093-token-paychan/README.md b/XLS-0093-token-paychan/README.md index 43e67905a..f2849cc24 100644 --- a/XLS-0093-token-paychan/README.md +++ b/XLS-0093-token-paychan/README.md @@ -1,157 +1,185 @@
-xls: 93d
-title: Token-Enabled Payment Channels
-description: Extends Payment Channels to support IOU tokens and Multi-Purpose Tokens (MPTs).
-author: Denis Angell (@dangell7)
-proposal-from: https://github.com/XRPLF/XRPL-Standards/discussions/287
-category: Amendment
-status: Draft
-created: 2025-05-24
-updated: 2026-05-06
+  xls: 93
+  title: Token-Enabled Payment Channels
+  description: Enhancement to existing Payment Channel functionality to support both Trustline-based tokens (IOUs) and Multi-Purpose Tokens (MPTs)
+  author: Denis Angell (@dangell7)
+  proposal-from: https://github.com/XRPLF/XRPL-Standards/discussions/287
+  status: Draft
+  category: Amendment
+  requires: [XLS-33](../XLS-0033-multi-purpose-tokens/README.md), [XLS-85](../XLS-0085-token-escrow/README.md)
+  created: 2025-05-24
 
-# Token-Enabled Payment Channels +> This proposal, XLS-93, extends payment channels to tokens in the same way [XLS-85](../XLS-0085-token-escrow/README.md) extends escrows, and reuses the issuer opt-in flags and locked-amount accounting introduced there. -## 1. Abstract +The proposed `TokenPaychan` amendment to the XRP Ledger (XRPL) protocol enhances the existing `PaymentChannel` 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 payment channels to use IOU tokens and MPTs, while respecting issuer controls and maintaining ledger integrity. -This proposal introduces the `TokenPaychan` amendment to the XRP Ledger (XRPL), extending the existing `PaymentChannel` functionality to support both Trustline-based tokens (IOUs) and Multi-Purpose Tokens (MPTs) in addition to XRP. It defines updates to ledger objects, transactions, and transaction processing logic so that payment channels can hold and settle non-XRP assets while respecting issuer controls (authorization, freeze/lock, transfer rates/fees) and preserving ledger integrity. Transfer rates and fees are captured at channel creation and applied consistently at claim time, providing predictable settlement for participants. +# 1. Implementation -## 2. Motivation +This amendment extends the functionality of payment channels to support both IOUs and MPTs, accounting for the specific behaviors and constraints associated with each token type. Token-denominated channels share their locking model — the `lsfAllowTrustLineLocking` / `lsfMPTCanEscrow` issuer opt-in flags and the `sfLockedAmount` accounting fields — with Token-Enabled Escrows (XLS-85). -Payment channels on the XRPL today are limited to XRP. As issued assets (IOUs) and Multi-Purpose Tokens become increasingly used for payments, settlement, and tokenization use cases, the inability to use them in off-ledger/streamed-payment workflows blocks important applications such as token-denominated micropayments, streaming payments for stablecoins, and recurring token settlements between counterparties. Extending payment channels to support IOUs and MPTs closes this gap while keeping the existing issuer guarantees (authorization, freeze/lock, transfer fees) intact and predictable. +## 1.1. Overview of Token Types -## 3. Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. - -This amendment extends the functionality of payment channels to support both IOUs and MPTs, accounting for the specific behaviors and constraints associated with each token type. - -### 3.1. Overview of Token Types - -#### 3.1.1. IOU Tokens +### 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. + - **Freeze Conditions (global, individual, and deep freeze)**: 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. -#### 3.1.2. Multi-Purpose Tokens (MPTs) +### 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 payment channels. - - **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 Flags (`lsfMPTCanTransfer`)**: Tokens must have this flag enabled to be transferable and to participate in transactions like payment channels. + - **Require Authorization (`lsfMPTRequireAuth`)**: Issuers may require authorization for accounts to hold their tokens. + - **Lock Conditions (`lsfMPTLocked`)**: 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. -### 3.2. Transactions +## 1.2. Payment Channel Transactions and Logic -#### 3.2.1. `PaymentChannelCreate` +### 1.2.1. `PaymentChannelCreate` The `PaymentChannelCreate` transaction is modified as follows: | Field | Required? | JSON Type | Internal Type | Description | | -------- | --------- | ---------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `Amount` | Yes | Object or String | Amount | The amount to fund the payment channel. 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. | +| `Amount` | Yes | Object or String | Amount | The amount to fund the payment channel. 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. | -##### Failure Conditions +**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 Payment Channels:** - - **IOU Tokens**: If the issuer's account does not have the `lsfAllowTokenEscrow` flag set, the transaction fails with `tecNO_PERMISSION`. + +- **Issuer Does Not Allow Token Locking or Transfer:** + - **IOU Tokens**: If the issuer's account does not have the `lsfAllowTrustLineLocking` flag set, the transaction fails with `tecNO_PERMISSION`. If the issuer's account does not exist, the transaction fails with `tecNO_ISSUER`. - **MPTs**: + - If the `MPTokenIssuance` of the token being used does not exist, the transaction fails with `tecOBJECT_NOT_FOUND`. - If the `MPTokenIssuance` of the token being used lacks the `lsfMPTCanEscrow` flag, the transaction fails with `tecNO_PERMISSION`. - - If the `MPTokenIssuance` of the token being used lacks the `lsfMPTCanTransfer` flag, the transaction fails with `tecNO_PERMISSION` unless the destination address of the Payment Channel 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`. + - If the `MPTokenIssuance` of the token being used lacks the `lsfMPTCanTransfer` flag, the transaction fails with `tecNO_AUTH` unless the destination address of the Payment Channel is the issuer of the MPT. + +- **Source or Destination Not Authorized to Hold Token:** + - If the issuer requires authorization and either the source or the destination 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`. + - **IOU Tokens**: If the source lacks a trustline with the issuer, the transaction fails with `tecNO_LINE`. - **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`. + +- **Source or Destination is Frozen or Token is Locked:** + - **IOU Tokens**: If the token is frozen (global/individual/deepfreeze) for the source or the destination, the transaction fails with `tecFROZEN`. + - **MPTs**: If the token is locked for the source or the destination, the transaction fails with `tecLOCKED`. + - **Insufficient Spendable Balance:** - - If the source account lacks sufficient spendable balance, the transaction fails with `tecUNFUNDED`. + - If the source account lacks sufficient spendable balance, the transaction fails with `tecINSUFFICIENT_FUNDS`. -##### State Changes +- **Precision Loss (IOU only):** + - If the channel `Amount` cannot be represented without loss of precision, the transaction fails with `tecPRECISION_LOSS`. + +**State Changes:** - **Adjustment from Source to Issuer:** - **IOU Tokens**: The channel `Amount` is deducted from the source's trustline balance. - - **MPTs**: The channel `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. + - **MPTs**: The channel `Amount` is deducted from the source's MPT balance. The `sfOutstandingAmount` of the MPT issuance remains unchanged. The `sfLockedAmount` is increased on both the source's MPT and the MPT issuance. - **Payment Channel Object Creation:** - The `PaymentChannel` ledger object includes: - `Amount`: Tokens held in the channel. - `Balance`: Amount already paid out (starts at zero). - - `TransferRate`: `TransferRate` (IOUs) or `TransferFee` (MPTs) at creation. - - `IssuerNode`: Reference to the issuer's ledger node if applicable. + - `TransferRate`: `TransferRate` (IOUs) or `TransferFee` (MPTs) at creation. Only stored when it differs from parity (no fee). + - `IssuerNode`: Reference to the issuer's ledger node. Only present for IOU channels where the issuer is neither the source nor the destination. -#### 3.2.2. `PaymentChannelFund` +### 1.2.2. `PaymentChannelFund` The `PaymentChannelFund` transaction is modified to support token amounts. -##### Failure Conditions +**Failure Conditions:** + +- **Asset Mismatch:** + - If the funding `Amount` is not the same asset as the channel's `Amount`, the transaction fails with `temBAD_AMOUNT`. + +- **Same conditions as `PaymentChannelCreate`** for validating the funding amount and token permissions (issuer opt-in, authorization, freeze/lock, transferability, spendable balance). -- **Same conditions as `PaymentChannelCreate`** for validating the funding amount and token permissions. +- **Precision Loss (IOU only):** + - If the funding `Amount` would be rounded away when added to the channel's `Amount`, the transaction fails with `tecPRECISION_LOSS`. -##### State Changes +**State Changes:** - **Adjustment from Source:** - **IOU Tokens**: The funding `Amount` is deducted from the source's trustline balance. - - **MPTs**: The funding `Amount` is deducted from the source's MPT balance. The `sfEscrowAmount` is increased accordingly. + - **MPTs**: The funding `Amount` is deducted from the source's MPT balance. The `sfLockedAmount` is increased accordingly. - **Payment Channel Object Update:** - - The channel's `Amount` field is increased by the funding amount. + - The channel's `Amount` field is increased by the funding amount. The stored `TransferRate` is not updated by funding. -#### 3.2.3. `PaymentChannelClaim` +### 1.2.3. `PaymentChannelClaim` -##### Normal Claim (Balance Update) +#### Normal Claim (Balance Update) When claiming without closing the channel: -###### Failure Conditions +**Failure Conditions:** + +- **Asset Mismatch:** + - If the claim's `Balance` or `Amount` is not the same asset as the channel's `Amount`, the transaction fails with `temBAD_AMOUNT`. - **Destination Not Authorized to Hold Token:** - If authorization is required and the destination is not authorized, the transaction fails with `tecNO_AUTH`. + - **Destination Lacks Trustline or MPT Holding:** - **IOU Tokens**: If the destination lacks a trustline with the issuer, the transaction fails with `tecNO_LINE`. - - **MPTs**: If the destination does not hold the MPT, the transaction fails with `tecNO_ENTRY`. - - A new trustline or MPT holding MAY be created during `PaymentChannelClaim` if authorization is not required. + - **MPTs**: If the destination does not hold the MPT, the transaction fails with `tecNO_PERMISSION`. + - A new trustline or MPT holding may be created during `PaymentChannelClaim` if the destination submits the transaction and authorization is not required. + - **Cannot Create Trustline or MPT Holding:** - - If unable to create due to lack of authorization or reserves, the transaction fails with `tecNO_AUTH` or `tecINSUFFICIENT_RESERVE`. + - If unable to create due to lack of reserves, the transaction fails with `tecNO_LINE_INSUF_RESERVE` (IOU) or `tecINSUFFICIENT_RESERVE` (MPT). + +- **Trustline Limit Exceeded (IOU only):** + - If the transaction is not submitted by the destination and the claimed amount would push the destination's trustline balance above its limit, the transaction fails with `tecLIMIT_EXCEEDED`. + - **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)**: The transaction fails with `tecFROZEN`. + - **Lock Conditions (Equivalent to Deep Freeze)**: The transaction fails with `tecLOCKED`. -###### State Changes +**State Changes:** -- **Auto-create Trustline or MPToken:** +- **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 to Destination:** - - **IOU Tokens**: The claimed amount is added to the destination's trustline balance. - - **MPTs**: Similar to escrow logic for MPT balance adjustments. +- **Adjustment from Issuer to Destination:** + - **IOU Tokens**: The claimed amount, less any transfer fee (see Section 1.4), is added to the destination's trustline balance. If the destination is the issuer, the claimed amount is simply redeemed. + - **MPTs**: + - If the destination is the issuer of the asset held in the channel, then: + 1. The `LockedAmount` on the `MPTokenIssuance` and the source's `MPToken` is decreased by the claimed amount. + 2. No destination `MPToken` object is changed because MPT issuers may not hold MPTokens. + 3. The `OutstandingAmount` on the `MPTokenIssuance` is decreased by the claimed amount (i.e., this claim is a "redemption"). + - If the destination is not the issuer of the asset held in the channel, then: + 1. The `LockedAmount` on the `MPTokenIssuance` and the source's `MPToken` is decreased by the claimed amount. + 2. The `Amount` on the destination's `MPToken` is increased by the claimed amount, less any transfer fee. + 3. The `OutstandingAmount` on the `MPTokenIssuance` is unchanged. - **Channel Balance Update:** - The channel's `Balance` field is updated to reflect the total amount claimed. -##### Channel Closure +#### Channel Closure -When closing the channel (either explicitly with the close flag, or automatically when fully drained): +When closing the channel (either explicitly with the close flag, or automatically when fully drained or expired): -###### Failure Conditions +**Failure Conditions:** -- **Source Not Authorized to Hold Token (for remaining funds):** +- **Source Not Authorized to Hold Token:** - If authorization is required and the source is not authorized, the transaction fails with `tecNO_AUTH`. -- **Source Lacks Trustline or MPT Holding (for remaining funds):** + +- **Source Lacks Trustline or MPT Holding:** - **IOU Tokens**: If the source lacks a trustline with the issuer, the transaction fails with `tecNO_LINE`. - - **MPTs**: If the source does not hold the MPT, the transaction fails with `tecNO_ENTRY`. - - A new trustline or MPT holding MAY be created during channel closure if authorization is not required. -- **Cannot Create Trustline or MPT Holding (for source):** - - If unable to create due to lack of authorization or reserves, the transaction fails with `tecNO_AUTH` or `tecINSUFFICIENT_RESERVE`. + - **MPTs**: If the source does not hold the MPT, the transaction fails with `tecNO_PERMISSION`. + - A new trustline or MPT holding may be created during channel closure if the source submits the transaction and authorization is not required. + +- **Cannot Create Trustline or MPT Holding:** + - If unable to create due to lack of reserves, the transaction fails with `tecNO_LINE_INSUF_RESERVE` (IOU) or `tecINSUFFICIENT_RESERVE` (MPT). + - **Source Account is Frozen or Token is Locked:** - **IOU Tokens**: - **Deep Freeze**: The transaction succeeds, allowing the channel to be closed. @@ -159,153 +187,72 @@ When closing the channel (either explicitly with the close flag, or automaticall - **MPTs**: - **Lock Conditions (Deep Freeze Equivalent)**: The transaction succeeds, allowing the channel to be closed. -###### State Changes +**State Changes:** -- **Auto-create Trustline or MPToken (for source):** +- **Auto create Trustline or MPToken:** - **IOU Tokens**: If the IOU does not require authorization and the account submitting the transaction is the source, then a trustline will be created. - **MPTs**: If the MPT does not require authorization and the account submitting the transaction is the source, then the MPT will be created. -- **Adjustment to Destination (final claim):** - - Any remaining claimable amount is transferred to the destination using the same logic as normal claims. -- **Adjustment to Source (remaining funds):** +- **Adjustment from Issuer to Source:** + - No transfer fee is applied when returning remaining funds to the source. - **IOU Tokens**: Any remaining channel funds are added to the source's trustline balance. - - **MPTs**: Any remaining channel funds are 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. + - **MPTs**: Any remaining channel funds are added to the source's MPT balance. The `sfOutstandingAmount` of the MPT issuance remains unchanged. The `sfLockedAmount` is decreased on both the source's MPT and the MPT issuance. - **Deletion of Payment Channel Object:** - The `PaymentChannel` object is deleted after successful closure. -### 3.3. Ledger Entries - -#### 3.3.1. `PaymentChannel` Ledger Object - -The `PaymentChannel` ledger object is updated as follows: +## 1.3. Key Differences Between IOU and MPT Payment Channels -| Field Name | JSON Type | Internal Type | Description | -| -------------- | ---------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Amount` | Object or String | Amount | The total amount allocated to the payment channel. Can represent XRP, an IOU token, or an MPT. MUST always be a positive value. | -| `Balance` | Object or String | Amount | The amount already paid out from the channel. Same asset type as `Amount`. | -| `TransferRate` | Number | UInt32 | The transfer rate or fee at which the funds are held, stored at creation and used during claims. Applicable to both IOUs and MPTs. | -| `IssuerNode` | Number | UInt64 | _(Optional)_ The ledger index of the issuer's directory node associated with the `PaymentChannel`. Used when the issuer is neither the source nor destination account. | +| Aspect | IOU Tokens | Multi-Purpose Tokens (MPTs) | +| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| **Trustlines** | Required between accounts and issuer | Not used | +| **Issuer Flag for Channels** | `lsfAllowTrustLineLocking` (account flag) | `lsfMPTCanEscrow` (issuance flag) | +| **Transfer Flags** | N/A | `lsfMPTCanTransfer` must be enabled for payment channels | +| **Require Auth** | Applicable (`lsfRequireAuth`); accounts must be authorized prior to holding tokens | Applicable (`lsfMPTRequireAuth`); accounts must be authorized prior to holding tokens | +| **Destination Authorization** | Required at creation and at claim; cannot be granted during claim if authorization required | Required at creation and at claim; cannot be granted during claim if authorization required | +| **Freeze/Lock Conditions** | Any freeze blocks create/fund; **Deep Freeze** prevents claims, but allows closure; Global/Individual Freeze allows claims and closure | Lock blocks create/fund; **Lock Conditions (Deep Freeze Equivalent)** prevent claims, but allow closure | +| **Transfer Rates/Fees** | `TransferRate` stored at creation and applied during claims | `TransferFee` stored at creation and applied during claims | +| **Outstanding Amount** | Remains unchanged during channel operations | Remains unchanged during channel operations | +| **Account Deletion** | Payment channels prevent account deletion | Payment channels prevent account deletion | -#### 3.3.2. `MPToken` Ledger Object +## 1.4. Transfer Rates and Fees -The `MPToken` ledger object is updated as follows: +### 1.4.1. IOU Tokens (`TransferRate`) -| Field Name | JSON Type | Internal Type | Description | -| ---------------- | --------- | ------------- | -------------------------------------------------------------------------------------- | -| `sfEscrowAmount` | Object | Amount | _(Optional)_ The total of all outstanding escrows and payment channels for this token. | +- **Rate Capped at Creation**: The `TransferRate` is captured at the time of `PaymentChannelCreate` and stored in the `PaymentChannel` object. At claim time, the lower of the stored rate and the issuer's current rate is applied: an increase by the issuer does not affect existing channels, while a decrease passes through to claims. +- **Fee Calculation**: The transfer fee is deducted from the claimed amount, reducing the final amount credited to the destination. No fee is applied when the issuer is the destination, or when remaining funds are returned to the source at closure. -#### 3.3.3. `MPTokenIssuance` Ledger Object +### 1.4.2. MPTs (`TransferFee`) -The `MPTokenIssuance` ledger object is updated as follows: +- **Fee Capped at Creation**: The `TransferFee` is captured at the time of `PaymentChannelCreate` and stored in the `PaymentChannel` object, similar to IOUs, with the same lower-of-stored-and-current rule. +- **Fee Calculation**: The transfer fee is deducted from the claimed amount, reducing the final amount credited to the destination. +- **Consistent Fee Application**: Both IOUs and MPTs use the same capped-rate rule, ensuring the destination's settlement value cannot be worsened by the issuer after channel creation. -| Field Name | JSON Type | Internal Type | Description | -| ---------------- | --------- | ------------- | ----------------------------------------------------------------------------------------- | -| `sfEscrowAmount` | Object | Amount | _(Optional)_ The total of all outstanding escrows and payment channels for this issuance. | +## 1.5. Ledger Object Updates -### 3.4. Transfer Rates and Fees +### 1.5.1 `PaymentChannel` Ledger Object -#### 3.4.1. IOU Tokens (`TransferRate`) - -- **Locked Transfer Rate**: The `TransferRate` is captured at the time of `PaymentChannelCreate` and stored in the `PaymentChannel` object. It is used during `PaymentChannelClaim`, even if the issuer changes it later. -- **Fee Calculation**: The claimed amount is adjusted according to the `TransferRate` upon settlement, potentially reducing the final amount credited to the destination. - -#### 3.4.2. MPTs (`TransferFee`) - -- **Locked Transfer Fee**: The `TransferFee` is captured at the time of `PaymentChannelCreate` and stored in the `PaymentChannel` object, similar to IOUs. -- **Fee Calculation**: The claimed 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 channel creation, ensuring predictability for the destination. - -### 3.5. Key Differences Between IOU and MPT Payment Channels - -| Aspect | IOU Tokens | Multi-Purpose Tokens (MPTs) | -| ----------------------------- | ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | -| **Trustlines** | Required between accounts and issuer | Not used | -| **Issuer Flag for Channels** | `lsfAllowTokenEscrow` (account flag) | `tfMPTCanEscrow` (token flag) | -| **Transfer Flags** | N/A | `tfMPTCanTransfer` must be enabled for payment channels | -| **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 claim; cannot be granted during claim if authorization required | Not required at creation; required at claim; cannot be granted during claim if authorization required | -| **Freeze/Lock Conditions** | **Deep Freeze** prevents claims, but allows closure; Global/Individual Freeze allows both operations | **Lock Conditions (Deep Freeze Equivalent)** prevent claims, but allow closure | -| **Transfer Rates/Fees** | `TransferRate` stored at creation and applied during claims | `TransferFee` stored at creation and applied during claims | -| **Outstanding Amount** | Remains unchanged during channel operations | Remains unchanged during channel operations | -| **Account Deletion** | Payment channels prevent account deletion | Payment channels prevent account deletion | - -## 4. Rationale - -The amendment reuses the existing `PaymentChannel` mechanics so issuers, wallets, and tooling can extend support to non-XRP assets without learning a new construct. Key design decisions: - -- **Lock the transfer rate/fee at creation.** Capturing `TransferRate`/`TransferFee` in the `PaymentChannel` object ensures that the destination has a predictable settlement value over the lifetime of the channel, regardless of issuer-side changes. This mirrors how other XRPL constructs that pre-commit value (e.g. escrow) treat issuer-controlled fees. -- **Account-level vs. token-level opt-in.** IOUs use an account flag (`lsfAllowTokenEscrow`) because IOU issuance is account-scoped, while MPTs use a token flag (`tfMPTCanEscrow`) because each issuance has its own configuration. This keeps each control surface where issuers already manage it. -- **Disallow the issuer as source.** Letting the issuer be the source account would conflate issuance with channel funding and complicate `sfOutstandingBalance` accounting. Issuers who want to fund a channel can route through a separate holding account, which is also recommended for operational hygiene. See §3.6. -- **Preserve `sfOutstandingBalance` and track `sfEscrowAmount` separately.** Funds in a channel are still in circulation from the issuer's perspective; tracking them under `sfEscrowAmount` keeps issuance accounting accurate while making channel-locked balances explicit. -- **Reuse existing freeze/deep-freeze and lock semantics.** Aligning IOU and MPT behavior to the deep-freeze / lock equivalence (claims blocked, closure allowed) keeps the model consistent with how those controls operate elsewhere on the ledger. -- **Auto-creation rules mirror direct payments.** Allowing trustline/MPToken auto-creation on claim only when authorization is not required matches existing token settlement behavior and avoids surprising the recipient with an unsolicited holding when the issuer requires authorization. - -### 3.6. Future Considerations - -1. **Clawback**: This specification currently does not provide a direct "clawback" mechanism within an active Payment Channel. If a use case requires clawback, the channel can be closed and existing IOU/MPT clawback features used on the returned funds. -2. **Issuer as Source**: This specification currently does not allow the issuer to be the source of the Payment Channel. If a use case requires this functionality, a new account should be created, the MPT or IOU sent to that account, and then the payment channel created with that account as the source. - -## 5. Backwards Compatibility - -This amendment is backwards compatible with existing XRP-only payment channels: any `PaymentChannel`, `PaymentChannelCreate`, `PaymentChannelFund`, and `PaymentChannelClaim` whose `Amount` is XRP behaves exactly as before. The only behavioral changes occur for new transactions whose `Amount` references an IOU or MPT. - -The following are net-new requirements introduced by this amendment and only apply to token-denominated channels: - -- New ledger fields (`TransferRate` on `PaymentChannel`; `sfEscrowAmount` on `MPToken` and `MPTokenIssuance`) are optional and ignored by pre-amendment objects. -- New issuer opt-in flags (`lsfAllowTokenEscrow` for IOUs and `tfMPTCanEscrow` for MPTs) ensure issuers must explicitly enable token-denominated channels for their assets, preventing surprise behavior on existing issuances. -- Consumers of `PaymentChannel` ledger objects (clients, indexers, channel watchers) should be updated to handle non-XRP `Amount`/`Balance` representations and the optional `TransferRate` and `IssuerNode` fields. - -There are no changes to the wire format or semantics of existing XRP-denominated channels. - -## 6. Test Plan - -The implementation will include unit and integration tests covering, at minimum: - -- XRP, IOU, and MPT variants of `PaymentChannelCreate`, `PaymentChannelFund`, and `PaymentChannelClaim`, including normal claims and channel closure. -- Issuer opt-in enforcement (`lsfAllowTokenEscrow`, `tfMPTCanEscrow`) and `tfMPTCanTransfer` requirement. -- Authorization enforcement (`lsfRequireAuth`, `tfMPTRequireAuth`) at create, claim, and closure. -- Freeze and deep-freeze behavior for IOUs and lock conditions for MPTs, for both source and destination, on claim and closure paths. -- `TransferRate` / `TransferFee` capture at creation and consistent application at claim. -- `sfEscrowAmount` accounting on `MPToken` and `MPTokenIssuance` across funding, claims, and closure. -- Auto-creation of trustlines / `MPToken` objects only when authorization is not required. -- Failure-condition matrix covering each `tec*` listed above. -- Account deletion blocking when token-denominated channels exist. - -## 7. Reference Implementation - -To be linked when the corresponding rippled PR is opened. - -## 8. Security Considerations - -- **Issuer opt-in is mandatory.** Token-denominated channels require explicit issuer opt-in (`lsfAllowTokenEscrow` for IOUs, `tfMPTCanEscrow` for MPTs). Without this, no holder can lock issuer-controlled assets in a payment channel, preserving the issuer's existing controls. -- **Issuer cannot be source.** Disallowing the issuer as the source account prevents conflation of issuance with channel funding and avoids ambiguous `sfOutstandingBalance` accounting; issuers must route through a holding account. -- **Locked transfer rate/fee.** Capturing the `TransferRate`/`TransferFee` at creation prevents an issuer from changing settlement economics mid-channel. This protects the destination from unexpected fee increases but also means a fee reduction by the issuer will not propagate to existing channels. -- **Freeze/lock semantics.** Deep freeze (IOU) and lock (MPT) block new claims but permit closure, ensuring counterparties can always recover remaining funds without enabling continued transfers when the issuer has revoked transferability. -- **Authorization at claim time.** Destinations whose holdings require authorization MUST be authorized before they can claim; auto-creation of a trustline or `MPToken` is only permitted when authorization is not required, preventing unsolicited holdings. -- **Account deletion.** Open token-denominated payment channels prevent account deletion of the source, mirroring existing XRP-channel behavior and avoiding orphaned references. -- **`sfEscrowAmount` accounting.** All funds locked in token-denominated channels are tracked via `sfEscrowAmount` on the relevant `MPToken` and `MPTokenIssuance` objects, so channel-locked balances are explicit and auditable while `sfOutstandingBalance` continues to reflect circulating supply. -- **Replay and signature behavior unchanged.** The amendment does not change the authorization model for off-ledger claim signatures (`PaymentChannelClaim` signature validation), so existing replay-protection and channel-signing analyses continue to apply. -- **Client/indexer assumptions.** Tooling that previously assumed `PaymentChannel.Amount` is always XRP must be updated; failing to do so could lead to misreporting of balances or failed settlements but cannot, by itself, cause loss of funds on-ledger. - -# Appendix - -## Appendix A: FAQ - -### A.1: Why can't the issuer be the source of a token payment channel? +The `PaymentChannel` ledger object is updated as follows: -Allowing the issuer to be the source would mix issuance with channel funding and complicate the `sfOutstandingBalance` accounting on the issuance. Issuers who want this behavior can use a separate holding account as the source. +| Field Name | JSON Type | Internal Type | Description | +| -------------- | ---------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Amount` | Object or String | Amount | The total amount allocated to the payment channel. Can represent XRP, an IOU token, or an MPT. Must always be a positive value. | +| `Balance` | Object or String | Amount | The amount already paid out from the channel. Same asset type as `Amount`. | +| `TransferRate` | Number | UInt32 | _(Optional)_ The transfer rate or fee at creation, used as an upper bound on the rate applied during claims. Only present when the rate at creation differs from parity. | +| `IssuerNode` | Number | UInt64 | _(Optional)_ The ledger index of the issuer's directory node associated with the `PaymentChannel`. Only present for IOU channels where the issuer is neither the source nor the destination. | -### A.2: What happens to the transfer rate or fee if the issuer changes it after the channel is created? +### 1.5.2 `MPToken` and `MPTokenIssuance` Ledger Objects -The `TransferRate` (IOU) or `TransferFee` (MPT) is captured at `PaymentChannelCreate` and stored on the `PaymentChannel` object. Subsequent changes by the issuer do not affect existing channels. +Token-denominated payment channels reuse the `sfLockedAmount` field introduced by [XLS-85](../XLS-0085-token-escrow/README.md) on both the `MPToken` and `MPTokenIssuance` ledger objects: -### A.3: Can a destination receive a token they don't already hold? +| Field Name | JSON Type | Internal Type | Description | +| ---------------- | --------- | ------------- | -------------------------------------------------------------------------------------------- | +| `sfLockedAmount` | String | UInt64 | _(Optional)_ The total of all outstanding escrows and payment channels for this issuance. | -Yes, but only when the issuer does not require authorization. In that case a new trustline or `MPToken` may be auto-created during claim or closure. If authorization is required, the destination must be authorized in advance. +### 1.5.3 `AccountRoot` Ledger Object -### A.4: How are frozen IOUs and locked MPTs treated? +No new flags are introduced. Token-denominated payment channels reuse the `lsfAllowTrustLineLocking` flag (`0x40000000`) introduced by [XLS-85](../XLS-0085-token-escrow/README.md): issuers who have enabled trust line locking for escrows have also enabled it for payment channels. See XLS-85 Section 1.6 for the corresponding `asfAllowTrustLineLocking` AccountSet flag. -Deep-frozen IOUs and locked MPTs block new claims (the transaction fails with `tecFROZEN`) but allow channel closure so that the source can recover remaining funds. +## 1.6. Future Considerations -### A.5: Does this affect existing XRP payment channels? +1. Clawback: XLS-93 currently does not provide a direct "clawback" mechanism within an active Payment Channel. If your use case requires clawback, you can close the channel and then perform a clawback of the funds outside of the payment channel context. In other words, once the token amount returns to the source account, the existing clawback features for IOUs or MPTs can be used on those returned funds. -No. XRP-denominated payment channels are fully backwards compatible and behave exactly as before this amendment. +2. Issuer as Source: XLS-93 currently does not allow the issuer to be the source of the Payment Channel. If your use case requires this functionality, you should create a new account, send the MPT or IOU to that account, and then create the payment channel with that account as the source. From 7aa681ff26990c9e32b64b0f8f877fc56c1db793 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Mon, 13 Jul 2026 19:26:30 -0400 Subject: [PATCH 05/19] docs: use tecWRONG_ASSET for XLS-93 asset mismatch --- XLS-0093-token-paychan/README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/XLS-0093-token-paychan/README.md b/XLS-0093-token-paychan/README.md index f2849cc24..d171e4865 100644 --- a/XLS-0093-token-paychan/README.md +++ b/XLS-0093-token-paychan/README.md @@ -97,7 +97,7 @@ The `PaymentChannelFund` transaction is modified to support token amounts. **Failure Conditions:** - **Asset Mismatch:** - - If the funding `Amount` is not the same asset as the channel's `Amount`, the transaction fails with `temBAD_AMOUNT`. + - If the funding `Amount` is not the same asset as the channel's `Amount`, the transaction fails with `tecWRONG_ASSET`. - **Same conditions as `PaymentChannelCreate`** for validating the funding amount and token permissions (issuer opt-in, authorization, freeze/lock, transferability, spendable balance). @@ -121,7 +121,7 @@ When claiming without closing the channel: **Failure Conditions:** - **Asset Mismatch:** - - If the claim's `Balance` or `Amount` is not the same asset as the channel's `Amount`, the transaction fails with `temBAD_AMOUNT`. + - If the claim's `Balance` or `Amount` is not the same asset as the channel's `Amount`, the transaction fails with `tecWRONG_ASSET`. - **Destination Not Authorized to Hold Token:** - If authorization is required and the destination is not authorized, the transaction fails with `tecNO_AUTH`. @@ -201,17 +201,17 @@ When closing the channel (either explicitly with the close flag, or automaticall ## 1.3. Key Differences Between IOU and MPT Payment Channels -| Aspect | IOU Tokens | Multi-Purpose Tokens (MPTs) | -| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -| **Trustlines** | Required between accounts and issuer | Not used | -| **Issuer Flag for Channels** | `lsfAllowTrustLineLocking` (account flag) | `lsfMPTCanEscrow` (issuance flag) | -| **Transfer Flags** | N/A | `lsfMPTCanTransfer` must be enabled for payment channels | -| **Require Auth** | Applicable (`lsfRequireAuth`); accounts must be authorized prior to holding tokens | Applicable (`lsfMPTRequireAuth`); accounts must be authorized prior to holding tokens | -| **Destination Authorization** | Required at creation and at claim; cannot be granted during claim if authorization required | Required at creation and at claim; cannot be granted during claim if authorization required | +| Aspect | IOU Tokens | Multi-Purpose Tokens (MPTs) | +| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | +| **Trustlines** | Required between accounts and issuer | Not used | +| **Issuer Flag for Channels** | `lsfAllowTrustLineLocking` (account flag) | `lsfMPTCanEscrow` (issuance flag) | +| **Transfer Flags** | N/A | `lsfMPTCanTransfer` must be enabled for payment channels | +| **Require Auth** | Applicable (`lsfRequireAuth`); accounts must be authorized prior to holding tokens | Applicable (`lsfMPTRequireAuth`); accounts must be authorized prior to holding tokens | +| **Destination Authorization** | Required at creation and at claim; cannot be granted during claim if authorization required | Required at creation and at claim; cannot be granted during claim if authorization required | | **Freeze/Lock Conditions** | Any freeze blocks create/fund; **Deep Freeze** prevents claims, but allows closure; Global/Individual Freeze allows claims and closure | Lock blocks create/fund; **Lock Conditions (Deep Freeze Equivalent)** prevent claims, but allow closure | -| **Transfer Rates/Fees** | `TransferRate` stored at creation and applied during claims | `TransferFee` stored at creation and applied during claims | -| **Outstanding Amount** | Remains unchanged during channel operations | Remains unchanged during channel operations | -| **Account Deletion** | Payment channels prevent account deletion | Payment channels prevent account deletion | +| **Transfer Rates/Fees** | `TransferRate` stored at creation and applied during claims | `TransferFee` stored at creation and applied during claims | +| **Outstanding Amount** | Remains unchanged during channel operations | Remains unchanged during channel operations | +| **Account Deletion** | Payment channels prevent account deletion | Payment channels prevent account deletion | ## 1.4. Transfer Rates and Fees @@ -233,7 +233,7 @@ When closing the channel (either explicitly with the close flag, or automaticall The `PaymentChannel` ledger object is updated as follows: | Field Name | JSON Type | Internal Type | Description | -| -------------- | ---------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| -------------- | ---------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Amount` | Object or String | Amount | The total amount allocated to the payment channel. Can represent XRP, an IOU token, or an MPT. Must always be a positive value. | | `Balance` | Object or String | Amount | The amount already paid out from the channel. Same asset type as `Amount`. | | `TransferRate` | Number | UInt32 | _(Optional)_ The transfer rate or fee at creation, used as an upper bound on the rate applied during claims. Only present when the rate at creation differs from parity. | @@ -243,8 +243,8 @@ The `PaymentChannel` ledger object is updated as follows: Token-denominated payment channels reuse the `sfLockedAmount` field introduced by [XLS-85](../XLS-0085-token-escrow/README.md) on both the `MPToken` and `MPTokenIssuance` ledger objects: -| Field Name | JSON Type | Internal Type | Description | -| ---------------- | --------- | ------------- | -------------------------------------------------------------------------------------------- | +| Field Name | JSON Type | Internal Type | Description | +| ---------------- | --------- | ------------- | ----------------------------------------------------------------------------------------- | | `sfLockedAmount` | String | UInt64 | _(Optional)_ The total of all outstanding escrows and payment channels for this issuance. | ### 1.5.3 `AccountRoot` Ledger Object From 8301f2eaf14b4c344b37d1a4b1b25b778eadcebc Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sun, 2 Aug 2026 14:06:30 -0400 Subject: [PATCH 06/19] docs: add template sections and document review decisions --- XLS-0093-token-paychan/README.md | 49 +++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/XLS-0093-token-paychan/README.md b/XLS-0093-token-paychan/README.md index d171e4865..802dc1291 100644 --- a/XLS-0093-token-paychan/README.md +++ b/XLS-0093-token-paychan/README.md @@ -12,6 +12,8 @@ > This proposal, XLS-93, extends payment channels to tokens in the same way [XLS-85](../XLS-0085-token-escrow/README.md) extends escrows, and reuses the issuer opt-in flags and locked-amount accounting introduced there. +## Abstract + The proposed `TokenPaychan` amendment to the XRP Ledger (XRPL) protocol enhances the existing `PaymentChannel` 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 payment channels to use IOU tokens and MPTs, while respecting issuer controls and maintaining ledger integrity. # 1. Implementation @@ -71,6 +73,8 @@ The `PaymentChannelCreate` transaction is modified as follows: - **Source or Destination is Frozen or Token is Locked:** - **IOU Tokens**: If the token is frozen (global/individual/deepfreeze) for the source or the destination, the transaction fails with `tecFROZEN`. - **MPTs**: If the token is locked for the source or the destination, the transaction fails with `tecLOCKED`. + - Note: this is deliberately stricter than base freeze semantics, under which an individual freeze only prevents the frozen holder from sending. Opening a lock is blocked by any freeze on either party, while paying out to the destination at claim time only requires that the destination is not deep frozen (see Normal Claim). This matches the lock creation rules of [XLS-85](../XLS-0085-token-escrow/README.md). + - The destination MAY be the issuer of the token; claims on such a channel redeem tokens back to the issuer. Because the freeze check above applies to the destination unconditionally, no channel can be created while the issuer has a global freeze in effect, including a channel whose destination is the issuer. This is intentionally stricter than direct payments, which always permit redemption to the issuer. - **Insufficient Spendable Balance:** - If the source account lacks sufficient spendable balance, the transaction fails with `tecINSUFFICIENT_FUNDS`. @@ -165,9 +169,11 @@ When claiming without closing the channel: #### Channel Closure -When closing the channel (either explicitly with the close flag, or automatically when fully drained or expired): +A channel closes when a claim carries the `tfClose` flag (immediately if the requester is the destination or the channel is fully drained; otherwise an expiration is scheduled per `SettleDelay`), or when any claim is processed against an already-expired channel. -**Failure Conditions:** +Closure returns the remaining channel funds (`Amount` minus `Balance`) to the source. **The failure conditions below apply only when this remainder is positive.** A fully drained channel has nothing to refund, so it closes without any source-side checks; the destination's ability to claim earned funds is never gated by the source's authorization, trustline, or freeze state. + +**Failure Conditions (positive remainder only):** - **Source Not Authorized to Hold Token:** - If authorization is required and the source is not authorized, the transaction fails with `tecNO_AUTH`. @@ -201,23 +207,24 @@ When closing the channel (either explicitly with the close flag, or automaticall ## 1.3. Key Differences Between IOU and MPT Payment Channels -| Aspect | IOU Tokens | Multi-Purpose Tokens (MPTs) | -| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -| **Trustlines** | Required between accounts and issuer | Not used | -| **Issuer Flag for Channels** | `lsfAllowTrustLineLocking` (account flag) | `lsfMPTCanEscrow` (issuance flag) | -| **Transfer Flags** | N/A | `lsfMPTCanTransfer` must be enabled for payment channels | -| **Require Auth** | Applicable (`lsfRequireAuth`); accounts must be authorized prior to holding tokens | Applicable (`lsfMPTRequireAuth`); accounts must be authorized prior to holding tokens | -| **Destination Authorization** | Required at creation and at claim; cannot be granted during claim if authorization required | Required at creation and at claim; cannot be granted during claim if authorization required | -| **Freeze/Lock Conditions** | Any freeze blocks create/fund; **Deep Freeze** prevents claims, but allows closure; Global/Individual Freeze allows claims and closure | Lock blocks create/fund; **Lock Conditions (Deep Freeze Equivalent)** prevent claims, but allow closure | -| **Transfer Rates/Fees** | `TransferRate` stored at creation and applied during claims | `TransferFee` stored at creation and applied during claims | -| **Outstanding Amount** | Remains unchanged during channel operations | Remains unchanged during channel operations | -| **Account Deletion** | Payment channels prevent account deletion | Payment channels prevent account deletion | +| Aspect | IOU Tokens | Multi-Purpose Tokens (MPTs) | +| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | +| **Trustlines** | Required between accounts and issuer | Not used | +| **Issuer Flag for Channels** | `lsfAllowTrustLineLocking` (account flag) | `lsfMPTCanEscrow` (issuance flag) | +| **Transfer Flags** | N/A | `lsfMPTCanTransfer` must be enabled for payment channels | +| **Require Auth** | Applicable (`lsfRequireAuth`); accounts must be authorized prior to holding tokens | Applicable (`lsfMPTRequireAuth`); accounts must be authorized prior to holding tokens | +| **Destination Authorization** | Required at creation and at claim; cannot be granted during claim if authorization required | Required at creation and at claim; cannot be granted during claim if authorization required | +| **Freeze/Lock Conditions** | Any freeze blocks create/fund; **Deep Freeze** prevents claims, but allows closure; Global/Individual Freeze allows claims and closure | Lock blocks create/fund; **Lock Conditions (Deep Freeze Equivalent)** prevent claims, but allow closure | +| **Transfer Rates/Fees** | `TransferRate` stored at creation and applied during claims | `TransferFee` stored at creation and applied during claims | +| **Outstanding Amount** | Remains unchanged during channel operations | Remains unchanged during channel operations | +| **Account Deletion** | Payment channels prevent account deletion | Payment channels prevent account deletion | +| **Holding Deletion** | Trustline deletion is NOT blocked by open channels (locked value lives in the channel object); closure refund then fails with `tecNO_LINE` until the line is re-established | `MPToken` deletion is blocked while `sfLockedAmount` is non-zero (`tecHAS_OBLIGATIONS`) | ## 1.4. Transfer Rates and Fees ### 1.4.1. IOU Tokens (`TransferRate`) -- **Rate Capped at Creation**: The `TransferRate` is captured at the time of `PaymentChannelCreate` and stored in the `PaymentChannel` object. At claim time, the lower of the stored rate and the issuer's current rate is applied: an increase by the issuer does not affect existing channels, while a decrease passes through to claims. +- **Rate Capped at Creation**: The `TransferRate` is captured at the time of `PaymentChannelCreate` and stored in the `PaymentChannel` object. At claim time, the lower of the stored rate and the issuer's current rate is applied: an increase by the issuer does not affect existing channels, while a decrease passes through to claims. This is identical to the behavior of the activated XLS-85 (Token Escrow) implementation, which uses the same shared unlock logic. - **Fee Calculation**: The transfer fee is deducted from the claimed amount, reducing the final amount credited to the destination. No fee is applied when the issuer is the destination, or when remaining funds are returned to the source at closure. ### 1.4.2. MPTs (`TransferFee`) @@ -256,3 +263,17 @@ No new flags are introduced. Token-denominated payment channels reuse the `lsfAl 1. Clawback: XLS-93 currently does not provide a direct "clawback" mechanism within an active Payment Channel. If your use case requires clawback, you can close the channel and then perform a clawback of the funds outside of the payment channel context. In other words, once the token amount returns to the source account, the existing clawback features for IOUs or MPTs can be used on those returned funds. 2. Issuer as Source: XLS-93 currently does not allow the issuer to be the source of the Payment Channel. If your use case requires this functionality, you should create a new account, send the MPT or IOU to that account, and then create the payment channel with that account as the source. + +3. Trustline Deletion While Locked: because the locked IOU value lives in the `PaymentChannel` object rather than on the trustline, an empty trustline can be deleted while channels remain open (see Section 3). Per-trustline lock accounting that would prevent this, for both escrows and payment channels, is deliberately left to a separate future amendment so that XLS-93 stays behaviorally aligned with the activated XLS-85. + +## 2. Rationale + +Payment channels are the last remaining XRP-only locking primitive; XLS-85 already extended escrows to IOUs and MPTs. Reusing the XLS-85 model wholesale, the same issuer opt-in flags (`lsfAllowTrustLineLocking`, `lsfMPTCanEscrow`), the same `sfLockedAmount` accounting, and the same shared lock/unlock logic in the implementation, means issuers make one opt-in decision that covers both primitives, and both primitives fail and succeed under identical token conditions. Every place where XLS-93 is stricter than base token semantics (any freeze blocks lock creation, no channel creation during global freeze even to the issuer) is inherited from the activated XLS-85 behavior rather than newly invented, keeping the two locking primitives coherent. + +## 3. Security Considerations + +- **Payee protection.** The destination's earned funds are never gated by source-side state. Normal claims check only destination-side conditions, and the source-side conditions in Channel Closure apply only to refunding a positive remainder; a fully drained channel closes without them. +- **Issuer trust surface.** An issuer that uses `RequireAuth` can deauthorize the source and thereby block the refund leg of closure (`tecNO_AUTH`) until re-authorized. The channel and its locked funds remain on ledger; no funds are lost. This is the same issuer trust surface that exists for XLS-85 escrow refunds and for clawback generally. +- **Transfer rate.** The claim rate is capped at the rate stored at creation (the lower of stored and current is applied), so an issuer cannot retroactively tax funds already locked by raising `TransferRate`/`TransferFee`. +- **Trustline deletion.** The source can delete an empty trustline while a channel is open. Closure refunds then fail with `tecNO_LINE` until the source re-establishes the line; the destination's claims are unaffected. `MPToken` deletion is blocked while locked (`tecHAS_OBLIGATIONS`). +- **Signature domain.** Claim signatures bind the specific channel ID and amount, unchanged from XRP payment channels; token support does not weaken replay protection. From 52987441937d142c7708f848f875ad025a9798af Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Mon, 10 Aug 2026 11:10:23 -0400 Subject: [PATCH 07/19] docs: add PaymentChannelClawback to XLS-93 --- XLS-0093-token-paychan/README.md | 68 ++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 7 deletions(-) diff --git a/XLS-0093-token-paychan/README.md b/XLS-0093-token-paychan/README.md index 802dc1291..bfc40c7ef 100644 --- a/XLS-0093-token-paychan/README.md +++ b/XLS-0093-token-paychan/README.md @@ -6,7 +6,7 @@ proposal-from: https://github.com/XRPLF/XRPL-Standards/discussions/287 status: Draft category: Amendment - requires: [XLS-33](../XLS-0033-multi-purpose-tokens/README.md), [XLS-85](../XLS-0085-token-escrow/README.md) + requires: [XLS-33](../XLS-0033-multi-purpose-tokens/README.md), [XLS-39](../XLS-0039-clawback/README.md), [XLS-85](../XLS-0085-token-escrow/README.md) created: 2025-05-24 @@ -14,11 +14,11 @@ ## Abstract -The proposed `TokenPaychan` amendment to the XRP Ledger (XRPL) protocol enhances the existing `PaymentChannel` 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 payment channels to use IOU tokens and MPTs, while respecting issuer controls and maintaining ledger integrity. +The proposed `TokenPaychan` amendment to the XRP Ledger (XRPL) protocol enhances the existing `PaymentChannel` 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 payment channels to use IOU tokens and MPTs, while respecting issuer controls and maintaining ledger integrity. It also adds one new transaction, `PaymentChannelClawback`, so that an issuer whose holders can already be clawed back retains that reach over value locked in a channel. # 1. Implementation -This amendment extends the functionality of payment channels to support both IOUs and MPTs, accounting for the specific behaviors and constraints associated with each token type. Token-denominated channels share their locking model — the `lsfAllowTrustLineLocking` / `lsfMPTCanEscrow` issuer opt-in flags and the `sfLockedAmount` accounting fields — with Token-Enabled Escrows (XLS-85). +This amendment extends the functionality of payment channels to support both IOUs and MPTs, accounting for the specific behaviors and constraints associated with each token type. Token-denominated channels share their locking model with Token-Enabled Escrows (XLS-85): the same `lsfAllowTrustLineLocking` and `lsfMPTCanEscrow` issuer opt-in flags, and the same `sfLockedAmount` accounting fields. ## 1.1. Overview of Token Types @@ -205,6 +205,56 @@ Closure returns the remaining channel funds (`Amount` minus `Balance`) to the so - **Deletion of Payment Channel Object:** - The `PaymentChannel` object is deleted after successful closure. +### 1.2.4. `PaymentChannelClawback` + +Locking a token into a channel moves it out of reach of the ordinary `Clawback` and `MPTokenIssuance` clawback transactions, which are both bounded by the holder's spendable balance and so cannot see locked value. `PaymentChannelClawback` gives the issuer that reach back. It requires the same opt-in the issuer already needed to claw back an ordinary holding, so it grants no new authority over a token; it removes a place the token could be kept out of reach. + +Only the unclaimed remainder of the channel (`Amount` minus `Balance`) can be clawed. The destination's earned `Balance` is never touched, so a clawback cannot reverse value the payee has already claimed. + +| Field | Required? | JSON Type | Internal Type | Description | +| --------- | --------- | ---------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Channel` | Yes | String | Hash256 | The ID of the `PaymentChannel` to claw from. | +| `Amount` | No | Object or String | Amount | The amount to claw back. Must be a positive, non-XRP amount of the channel's asset. If omitted, or if it is at least the unclaimed remainder, the entire remainder is clawed. | + +**Failure Conditions:** + +- **Malformed `Amount`:** + - If `Amount` is present and is XRP or is not positive, the transaction fails with `temBAD_AMOUNT`. An MPT amount above the maximum MPT value fails the same way. + - If `Amount` is present and names XRP as an IOU currency code, the transaction fails with `temBAD_CURRENCY`. + +- **Channel Does Not Exist:** + - If no `PaymentChannel` object matches `Channel`, the transaction fails with `tecNO_TARGET`. + +- **Channel Holds XRP:** + - XRP cannot be clawed back, so a channel denominated in XRP fails with `tecNO_PERMISSION`. + +- **Submitter Is Not the Issuer:** + - If the submitting account is not the issuer of the channel's asset, the transaction fails with `tecNO_PERMISSION`. + +- **Asset Mismatch:** + - If `Amount` is present and is not the same asset as the channel's `Amount`, the transaction fails with `tecWRONG_ASSET`. + +- **Issuer Does Not Allow Clawback:** + - **IOU Tokens**: If the issuer's account lacks the `lsfAllowTrustLineClawback` flag, or has the `lsfNoFreeze` flag set, the transaction fails with `tecNO_PERMISSION`. These are the same conditions that gate the [XLS-39](../XLS-0039-clawback/README.md) `Clawback` transaction. + - **MPTs**: If the `MPTokenIssuance` lacks the `lsfMPTCanClawback` flag, the transaction fails with `tecNO_PERMISSION`. If the `MPTokenIssuance` does not exist, the transaction fails with `tecOBJECT_NOT_FOUND`. + +**State Changes:** + +- **Adjustment to the Issuer:** + - No transfer fee is applied. A clawback is a redemption rather than a transfer between holders. + - **IOU Tokens**: No trustline is modified. The locked value was already removed from the source's trustline balance when the channel was created or funded, so retiring the channel's obligation is the whole of the clawback. + - **MPTs**: The clawed amount is deducted from the `sfLockedAmount` on both the source's `MPToken` and the `MPTokenIssuance`, and from the `sfOutstandingAmount` on the `MPTokenIssuance`. No `MPToken` is created for the issuer, since MPT issuers do not hold their own `MPToken`. + +- **Payment Channel Object Update:** + - For a partial clawback, the channel's `Amount` is reduced by the clawed amount and the channel remains open. + - For a full clawback, the channel's `Amount` is set equal to its `Balance`, leaving no remainder, and the channel is closed and deleted as described in Channel Closure. Because the remainder is zero, no refund is made to the source and no source-side authorization, holding, or freeze condition applies. + +**Effect on Outstanding Claims:** + +A clawback lowers the channel's `Amount`, which is the ceiling on what the destination can claim. Any authorization the source has already signed for a balance above the new `Amount` becomes unusable, and a claim presenting it fails with `tecUNFUNDED_PAYMENT`; the destination needs a fresh signature from the source for a balance at or below the new `Amount`. This is a consequence of the issuer's authority over the token rather than of the channel mechanics, and it mirrors what an ordinary clawback does to a holder's pending obligations. + +An expired channel can still be clawed. Expiry entitles the source to a refund but does not perform one until some account submits a transaction against the channel, so an issuer clawback and the source's refund race for the remainder. + ## 1.3. Key Differences Between IOU and MPT Payment Channels | Aspect | IOU Tokens | Multi-Purpose Tokens (MPTs) | @@ -216,6 +266,8 @@ Closure returns the remaining channel funds (`Amount` minus `Balance`) to the so | **Destination Authorization** | Required at creation and at claim; cannot be granted during claim if authorization required | Required at creation and at claim; cannot be granted during claim if authorization required | | **Freeze/Lock Conditions** | Any freeze blocks create/fund; **Deep Freeze** prevents claims, but allows closure; Global/Individual Freeze allows claims and closure | Lock blocks create/fund; **Lock Conditions (Deep Freeze Equivalent)** prevent claims, but allow closure | | **Transfer Rates/Fees** | `TransferRate` stored at creation and applied during claims | `TransferFee` stored at creation and applied during claims | +| **Clawback Opt-In** | `lsfAllowTrustLineClawback` (account flag), and `lsfNoFreeze` must not be set | `lsfMPTCanClawback` (issuance flag) | +| **Clawback Accounting** | Channel `Amount` is reduced; no trustline changes | `sfLockedAmount` and `sfOutstandingAmount` are reduced | | **Outstanding Amount** | Remains unchanged during channel operations | Remains unchanged during channel operations | | **Account Deletion** | Payment channels prevent account deletion | Payment channels prevent account deletion | | **Holding Deletion** | Trustline deletion is NOT blocked by open channels (locked value lives in the channel object); closure refund then fails with `tecNO_LINE` until the line is re-established | `MPToken` deletion is blocked while `sfLockedAmount` is non-zero (`tecHAS_OBLIGATIONS`) | @@ -260,16 +312,16 @@ No new flags are introduced. Token-denominated payment channels reuse the `lsfAl ## 1.6. Future Considerations -1. Clawback: XLS-93 currently does not provide a direct "clawback" mechanism within an active Payment Channel. If your use case requires clawback, you can close the channel and then perform a clawback of the funds outside of the payment channel context. In other words, once the token amount returns to the source account, the existing clawback features for IOUs or MPTs can be used on those returned funds. +1. Issuer as Source: XLS-93 currently does not allow the issuer to be the source of the Payment Channel. If your use case requires this functionality, you should create a new account, send the MPT or IOU to that account, and then create the payment channel with that account as the source. -2. Issuer as Source: XLS-93 currently does not allow the issuer to be the source of the Payment Channel. If your use case requires this functionality, you should create a new account, send the MPT or IOU to that account, and then create the payment channel with that account as the source. - -3. Trustline Deletion While Locked: because the locked IOU value lives in the `PaymentChannel` object rather than on the trustline, an empty trustline can be deleted while channels remain open (see Section 3). Per-trustline lock accounting that would prevent this, for both escrows and payment channels, is deliberately left to a separate future amendment so that XLS-93 stays behaviorally aligned with the activated XLS-85. +2. Trustline Deletion While Locked: because the locked IOU value lives in the `PaymentChannel` object rather than on the trustline, an empty trustline can be deleted while channels remain open (see Section 3). Per-trustline lock accounting that would prevent this, for both escrows and payment channels, is deliberately left to a separate future amendment so that XLS-93 stays behaviorally aligned with the activated XLS-85. ## 2. Rationale Payment channels are the last remaining XRP-only locking primitive; XLS-85 already extended escrows to IOUs and MPTs. Reusing the XLS-85 model wholesale, the same issuer opt-in flags (`lsfAllowTrustLineLocking`, `lsfMPTCanEscrow`), the same `sfLockedAmount` accounting, and the same shared lock/unlock logic in the implementation, means issuers make one opt-in decision that covers both primitives, and both primitives fail and succeed under identical token conditions. Every place where XLS-93 is stricter than base token semantics (any freeze blocks lock creation, no channel creation during global freeze even to the issuer) is inherited from the activated XLS-85 behavior rather than newly invented, keeping the two locking primitives coherent. +`PaymentChannelClawback` is included in the same amendment for the same reason. An issuer's clawback opt-in is a property of the token, so it should hold wherever that token sits. Deferring the transaction would have meant shipping a lock that quietly suspends an issuer control the token already carries, and the alternative of closing the channel first is not open to the issuer, which is not a party to the channel and cannot close it. + ## 3. Security Considerations - **Payee protection.** The destination's earned funds are never gated by source-side state. Normal claims check only destination-side conditions, and the source-side conditions in Channel Closure apply only to refunding a positive remainder; a fully drained channel closes without them. @@ -277,3 +329,5 @@ Payment channels are the last remaining XRP-only locking primitive; XLS-85 alrea - **Transfer rate.** The claim rate is capped at the rate stored at creation (the lower of stored and current is applied), so an issuer cannot retroactively tax funds already locked by raising `TransferRate`/`TransferFee`. - **Trustline deletion.** The source can delete an empty trustline while a channel is open. Closure refunds then fail with `tecNO_LINE` until the source re-establishes the line; the destination's claims are unaffected. `MPToken` deletion is blocked while locked (`tecHAS_OBLIGATIONS`). - **Signature domain.** Claim signatures bind the specific channel ID and amount, unchanged from XRP payment channels; token support does not weaken replay protection. +- **Clawback scope.** `PaymentChannelClawback` reaches only the unclaimed remainder and only for an issuer who already holds the clawback opt-in for that token. It cannot reverse a claim the destination has settled, and it cannot touch XRP or a token whose issuer never enabled clawback. +- **Clawback and pending authorizations.** An issuer clawback can invalidate a signed claim authorization the destination is holding, because the authorized balance may exceed the reduced channel `Amount`. A destination that wants to settle ahead of this should claim rather than accumulate authorizations, the same tradeoff that applies to holding an unsettled balance with any clawback-enabled issuer. From 73d7ae4c8df71aa8a6dd6c4c4efe73f11cb056e4 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Mon, 31 Aug 2026 12:12:02 -0400 Subject: [PATCH 08/19] docs: add no unclaimed remainder failure condition to XLS-93 --- XLS-0093-token-paychan/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/XLS-0093-token-paychan/README.md b/XLS-0093-token-paychan/README.md index bfc40c7ef..b79b40b47 100644 --- a/XLS-0093-token-paychan/README.md +++ b/XLS-0093-token-paychan/README.md @@ -238,6 +238,9 @@ Only the unclaimed remainder of the channel (`Amount` minus `Balance`) can be cl - **IOU Tokens**: If the issuer's account lacks the `lsfAllowTrustLineClawback` flag, or has the `lsfNoFreeze` flag set, the transaction fails with `tecNO_PERMISSION`. These are the same conditions that gate the [XLS-39](../XLS-0039-clawback/README.md) `Clawback` transaction. - **MPTs**: If the `MPTokenIssuance` lacks the `lsfMPTCanClawback` flag, the transaction fails with `tecNO_PERMISSION`. If the `MPTokenIssuance` does not exist, the transaction fails with `tecOBJECT_NOT_FOUND`. +- **No Unclaimed Remainder:** + - If the channel's `Balance` equals its `Amount` there is nothing to claw, and the transaction fails with `tecUNFUNDED_PAYMENT`. A claim that draws the channel down to its full `Amount` without `tfClose` leaves the channel open in exactly this state. + **State Changes:** - **Adjustment to the Issuer:** From 7ae9575e64c7ee4ccf80422797ec81bab390ffa5 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 30 Sep 2026 10:30:55 -0400 Subject: [PATCH 09/19] docs: XLS-93 review fixes --- XLS-0093-token-paychan/README.md | 76 +++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 21 deletions(-) diff --git a/XLS-0093-token-paychan/README.md b/XLS-0093-token-paychan/README.md index b79b40b47..8a1f8e5c3 100644 --- a/XLS-0093-token-paychan/README.md +++ b/XLS-0093-token-paychan/README.md @@ -65,6 +65,7 @@ The `PaymentChannelCreate` transaction is modified as follows: - **Source or Destination Not Authorized to Hold Token:** - If the issuer requires authorization and either the source or the destination is not authorized, the transaction fails with `tecNO_AUTH`. + - The issuer is always authorized for its own token, so a destination that is the issuer never fails this check. - **Source Account's Token Holding Issues:** - **IOU Tokens**: If the source lacks a trustline with the issuer, the transaction fails with `tecNO_LINE`. @@ -124,6 +125,8 @@ When claiming without closing the channel: **Failure Conditions:** +When the destination is the issuer of the channel's token, none of the authorization, holding, trustline limit or freeze conditions below apply: the issuer has no trustline to itself and holds no `MPToken`, and the claim redeems the tokens to the issuer (see State Changes). + - **Asset Mismatch:** - If the claim's `Balance` or `Amount` is not the same asset as the channel's `Amount`, the transaction fails with `tecWRONG_ASSET`. @@ -163,7 +166,7 @@ When claiming without closing the channel: - If the destination is not the issuer of the asset held in the channel, then: 1. The `LockedAmount` on the `MPTokenIssuance` and the source's `MPToken` is decreased by the claimed amount. 2. The `Amount` on the destination's `MPToken` is increased by the claimed amount, less any transfer fee. - 3. The `OutstandingAmount` on the `MPTokenIssuance` is unchanged. + 3. The `OutstandingAmount` on the `MPTokenIssuance` is decreased by the transfer fee, the claimed amount less the amount credited to the destination; the fee is credited to no holder, so it leaves the outstanding supply. - **Channel Balance Update:** - The channel's `Balance` field is updated to reflect the total amount claimed. @@ -205,16 +208,44 @@ Closure returns the remaining channel funds (`Amount` minus `Balance`) to the so - **Deletion of Payment Channel Object:** - The `PaymentChannel` object is deleted after successful closure. +#### Claim Authorization + +The `Signature` on a `PaymentChannelClaim` is over the following message, signed with the key whose `PublicKey` is stored in the channel. Fields are concatenated in order with no field headers or length prefixes; integers are big-endian. + +- **XRP channel (44 bytes):** + 1. The 4-byte prefix `0x434C4D00` (`HashPrefix::PaymentChannelClaim`). + 2. The 32-byte channel ID. + 3. The authorized amount in drops as an unsigned 64-bit integer. +- **IOU channel (84 bytes):** + 1. The 4-byte prefix `0x434C4D00`. + 2. The 32-byte channel ID. + 3. The authorized amount as the 64-bit value word the binary codec writes for a non-XRP `Amount`: a zero amount is `0x8000000000000000`; otherwise bit 63 is set, bit 62 is set for a positive amount, bits 54 to 61 hold the exponent plus 97, and bits 0 to 53 hold the mantissa. + 4. The 20-byte currency code. + 5. The 20-byte issuer `AccountID`. +- **MPT channel (88 bytes):** + 1. The 4-byte prefix `0x434C4D00`. + 2. The 32-byte channel ID. + 3. The authorized amount as an unsigned 64-bit integer. + 4. The 24-byte `MPTokenIssuanceID`. + 5. The 20-byte issuer `AccountID`. + +The IOU layout is the amount's `Amount` field serialization without its field header. The MPT layout is not: it has no leading type byte, and the issuer `AccountID` follows the `MPTokenIssuanceID`. + +The `channel_authorize` and `channel_verify` RPC methods take the authorized amount in `amount`. For an XRP channel it is a string of drops. For a token channel it is the same JSON object used for a transaction `Amount` (`currency`, `issuer` and `value` for an IOU; `mpt_issuance_id` and `value` for an MPT), and the server builds the message above from it. + ### 1.2.4. `PaymentChannelClawback` -Locking a token into a channel moves it out of reach of the ordinary `Clawback` and `MPTokenIssuance` clawback transactions, which are both bounded by the holder's spendable balance and so cannot see locked value. `PaymentChannelClawback` gives the issuer that reach back. It requires the same opt-in the issuer already needed to claw back an ordinary holding, so it grants no new authority over a token; it removes a place the token could be kept out of reach. +Locking a token into a channel moves it out of reach of the ordinary `Clawback` transaction (for an MPT, `Clawback` with the `MPTokenHolder` field defined in [XLS-33](../XLS-0033-multi-purpose-tokens/README.md)), which is bounded by the holder's spendable balance and so cannot see locked value. `PaymentChannelClawback` gives the issuer that reach back. It requires the same opt-in the issuer already needed to claw back an ordinary holding, so it grants no new authority over a token; it removes a place the token could be kept out of reach. Only the unclaimed remainder of the channel (`Amount` minus `Balance`) can be clawed. The destination's earned `Balance` is never touched, so a clawback cannot reverse value the payee has already claimed. -| Field | Required? | JSON Type | Internal Type | Description | -| --------- | --------- | ---------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Channel` | Yes | String | Hash256 | The ID of the `PaymentChannel` to claw from. | -| `Amount` | No | Object or String | Amount | The amount to claw back. Must be a positive, non-XRP amount of the channel's asset. If omitted, or if it is at least the unclaimed remainder, the entire remainder is clawed. | +| Field | Required? | JSON Type | Internal Type | Description | +| ----------------- | --------- | ---------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TransactionType` | Yes | String | UInt16 | The transaction type, `PaymentChannelClawback` (`ttPAYCHAN_CLAWBACK`, value `92`). | +| `Channel` | Yes | String | Hash256 | The ID of the `PaymentChannel` to claw from. | +| `Amount` | No | Object or String | Amount | The amount to claw back. Must be a positive, non-XRP amount of the channel's asset. If omitted, or if it is at least the unclaimed remainder, the entire remainder is clawed. | + +The transaction may be delegated under [XLS-75](../XLS-0075-permission-delegation/README.md). **Failure Conditions:** @@ -234,6 +265,9 @@ Only the unclaimed remainder of the channel (`Amount` minus `Balance`) can be cl - **Asset Mismatch:** - If `Amount` is present and is not the same asset as the channel's `Amount`, the transaction fails with `tecWRONG_ASSET`. +- **Precision Loss (IOU only):** + - If subtracting `Amount` from the channel's `Amount` would leave the channel's `Amount` unchanged, because the clawed amount is rounded away, the transaction fails with `tecPRECISION_LOSS`. + - **Issuer Does Not Allow Clawback:** - **IOU Tokens**: If the issuer's account lacks the `lsfAllowTrustLineClawback` flag, or has the `lsfNoFreeze` flag set, the transaction fails with `tecNO_PERMISSION`. These are the same conditions that gate the [XLS-39](../XLS-0039-clawback/README.md) `Clawback` transaction. - **MPTs**: If the `MPTokenIssuance` lacks the `lsfMPTCanClawback` flag, the transaction fails with `tecNO_PERMISSION`. If the `MPTokenIssuance` does not exist, the transaction fails with `tecOBJECT_NOT_FOUND`. @@ -260,20 +294,20 @@ An expired channel can still be clawed. Expiry entitles the source to a refund b ## 1.3. Key Differences Between IOU and MPT Payment Channels -| Aspect | IOU Tokens | Multi-Purpose Tokens (MPTs) | -| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -| **Trustlines** | Required between accounts and issuer | Not used | -| **Issuer Flag for Channels** | `lsfAllowTrustLineLocking` (account flag) | `lsfMPTCanEscrow` (issuance flag) | -| **Transfer Flags** | N/A | `lsfMPTCanTransfer` must be enabled for payment channels | -| **Require Auth** | Applicable (`lsfRequireAuth`); accounts must be authorized prior to holding tokens | Applicable (`lsfMPTRequireAuth`); accounts must be authorized prior to holding tokens | -| **Destination Authorization** | Required at creation and at claim; cannot be granted during claim if authorization required | Required at creation and at claim; cannot be granted during claim if authorization required | -| **Freeze/Lock Conditions** | Any freeze blocks create/fund; **Deep Freeze** prevents claims, but allows closure; Global/Individual Freeze allows claims and closure | Lock blocks create/fund; **Lock Conditions (Deep Freeze Equivalent)** prevent claims, but allow closure | -| **Transfer Rates/Fees** | `TransferRate` stored at creation and applied during claims | `TransferFee` stored at creation and applied during claims | -| **Clawback Opt-In** | `lsfAllowTrustLineClawback` (account flag), and `lsfNoFreeze` must not be set | `lsfMPTCanClawback` (issuance flag) | -| **Clawback Accounting** | Channel `Amount` is reduced; no trustline changes | `sfLockedAmount` and `sfOutstandingAmount` are reduced | -| **Outstanding Amount** | Remains unchanged during channel operations | Remains unchanged during channel operations | -| **Account Deletion** | Payment channels prevent account deletion | Payment channels prevent account deletion | -| **Holding Deletion** | Trustline deletion is NOT blocked by open channels (locked value lives in the channel object); closure refund then fails with `tecNO_LINE` until the line is re-established | `MPToken` deletion is blocked while `sfLockedAmount` is non-zero (`tecHAS_OBLIGATIONS`) | +| Aspect | IOU Tokens | Multi-Purpose Tokens (MPTs) | +| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Trustlines** | Required between accounts and issuer | Not used | +| **Issuer Flag for Channels** | `lsfAllowTrustLineLocking` (account flag) | `lsfMPTCanEscrow` (issuance flag) | +| **Transfer Flags** | N/A | `lsfMPTCanTransfer` must be enabled for payment channels | +| **Require Auth** | Applicable (`lsfRequireAuth`); accounts must be authorized prior to holding tokens | Applicable (`lsfMPTRequireAuth`); accounts must be authorized prior to holding tokens | +| **Destination Authorization** | Required at creation and at claim unless the destination is the issuer; cannot be granted during claim if authorization required | Required at creation and at claim unless the destination is the issuer; cannot be granted during claim if authorization required | +| **Freeze/Lock Conditions** | Any freeze blocks create/fund; **Deep Freeze** prevents claims, but allows closure; Global/Individual Freeze allows claims and closure | Lock blocks create/fund; **Lock Conditions (Deep Freeze Equivalent)** prevent claims, but allow closure | +| **Transfer Rates/Fees** | `TransferRate` stored at creation and applied during claims | `TransferFee` stored at creation and applied during claims | +| **Clawback Opt-In** | `lsfAllowTrustLineClawback` (account flag), and `lsfNoFreeze` must not be set | `lsfMPTCanClawback` (issuance flag) | +| **Clawback Accounting** | Channel `Amount` is reduced; no trustline changes | `sfLockedAmount` and `sfOutstandingAmount` are reduced | +| **Outstanding Amount** | N/A | Unchanged by create, fund and closure refund; decreased by the transfer fee on a claim, by the claimed amount on a claim to the issuer, and by the clawed amount on clawback | +| **Account Deletion** | Payment channels prevent account deletion | Payment channels prevent account deletion | +| **Holding Deletion** | Trustline deletion is NOT blocked by open channels (locked value lives in the channel object); closure refund then fails with `tecNO_LINE` until the line is re-established | `MPToken` deletion is blocked while `sfLockedAmount` is non-zero (`tecHAS_OBLIGATIONS`) | ## 1.4. Transfer Rates and Fees @@ -331,6 +365,6 @@ Payment channels are the last remaining XRP-only locking primitive; XLS-85 alrea - **Issuer trust surface.** An issuer that uses `RequireAuth` can deauthorize the source and thereby block the refund leg of closure (`tecNO_AUTH`) until re-authorized. The channel and its locked funds remain on ledger; no funds are lost. This is the same issuer trust surface that exists for XLS-85 escrow refunds and for clawback generally. - **Transfer rate.** The claim rate is capped at the rate stored at creation (the lower of stored and current is applied), so an issuer cannot retroactively tax funds already locked by raising `TransferRate`/`TransferFee`. - **Trustline deletion.** The source can delete an empty trustline while a channel is open. Closure refunds then fail with `tecNO_LINE` until the source re-establishes the line; the destination's claims are unaffected. `MPToken` deletion is blocked while locked (`tecHAS_OBLIGATIONS`). -- **Signature domain.** Claim signatures bind the specific channel ID and amount, unchanged from XRP payment channels; token support does not weaken replay protection. +- **Signature domain.** Claim signatures bind the specific channel ID and amount, unchanged from XRP payment channels; the token layouts in Claim Authorization also bind the currency and issuer or the `MPTokenIssuanceID`, so a signature for one asset cannot be presented against a channel holding another. - **Clawback scope.** `PaymentChannelClawback` reaches only the unclaimed remainder and only for an issuer who already holds the clawback opt-in for that token. It cannot reverse a claim the destination has settled, and it cannot touch XRP or a token whose issuer never enabled clawback. - **Clawback and pending authorizations.** An issuer clawback can invalidate a signed claim authorization the destination is holding, because the authorized balance may exceed the reduced channel `Amount`. A destination that wants to settle ahead of this should claim rather than accumulate authorizations, the same tradeoff that applies to holding an unsettled balance with any clawback-enabled issuer. From c1d5e5f3378ede0eebdfa70aa3383e709a97b081 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Mon, 5 Oct 2026 12:00:49 -0400 Subject: [PATCH 10/19] docs: XLS-93 review fixes round 2 --- XLS-0093-token-paychan/README.md | 70 +++++++++++++------------------- 1 file changed, 29 insertions(+), 41 deletions(-) diff --git a/XLS-0093-token-paychan/README.md b/XLS-0093-token-paychan/README.md index 8a1f8e5c3..b51192bdf 100644 --- a/XLS-0093-token-paychan/README.md +++ b/XLS-0093-token-paychan/README.md @@ -2,15 +2,17 @@ xls: 93 title: Token-Enabled Payment Channels description: Enhancement to existing Payment Channel functionality to support both Trustline-based tokens (IOUs) and Multi-Purpose Tokens (MPTs) + implementation: https://github.com/XRPLF/rippled/pull/7935; https://github.com/XRPLF/rippled/pull/7936 author: Denis Angell (@dangell7) proposal-from: https://github.com/XRPLF/XRPL-Standards/discussions/287 status: Draft category: Amendment - requires: [XLS-33](../XLS-0033-multi-purpose-tokens/README.md), [XLS-39](../XLS-0039-clawback/README.md), [XLS-85](../XLS-0085-token-escrow/README.md) + requires: XLS-33, XLS-39, XLS-85 created: 2025-05-24 + updated: 2026-10-05 -> This proposal, XLS-93, extends payment channels to tokens in the same way [XLS-85](../XLS-0085-token-escrow/README.md) extends escrows, and reuses the issuer opt-in flags and locked-amount accounting introduced there. +> This proposal, XLS-93, extends payment channels to tokens in the same way [XLS-85](../XLS-0085-token-escrow/README.md) extends escrows, and reuses the issuer opt-in flags and locked-amount accounting introduced there. It builds on [XLS-33](../XLS-0033-multi-purpose-tokens/README.md) for MPTs and on [XLS-39](../XLS-0039-clawback/README.md) for the clawback opt-in. ## Abstract @@ -80,9 +82,6 @@ The `PaymentChannelCreate` transaction is modified as follows: - **Insufficient Spendable Balance:** - If the source account lacks sufficient spendable balance, the transaction fails with `tecINSUFFICIENT_FUNDS`. -- **Precision Loss (IOU only):** - - If the channel `Amount` cannot be represented without loss of precision, the transaction fails with `tecPRECISION_LOSS`. - **State Changes:** - **Adjustment from Source to Issuer:** @@ -106,8 +105,8 @@ The `PaymentChannelFund` transaction is modified to support token amounts. - **Same conditions as `PaymentChannelCreate`** for validating the funding amount and token permissions (issuer opt-in, authorization, freeze/lock, transferability, spendable balance). -- **Precision Loss (IOU only):** - - If the funding `Amount` would be rounded away when added to the channel's `Amount`, the transaction fails with `tecPRECISION_LOSS`. +- **Inexact Sum:** + - The channel's new `Amount` must equal its old `Amount` plus the funding `Amount` exactly. An IOU sum is rounded to the mantissa width and is exact only if subtracting each operand from the sum gives back the other; an MPT sum is exact only if it does not overflow. Otherwise the transaction fails with `tecPRECISION_LOSS`. **State Changes:** @@ -134,9 +133,9 @@ When the destination is the issuer of the channel's token, none of the authoriza - If authorization is required and the destination is not authorized, the transaction fails with `tecNO_AUTH`. - **Destination Lacks Trustline or MPT Holding:** - - **IOU Tokens**: If the destination lacks a trustline with the issuer, the transaction fails with `tecNO_LINE`. - - **MPTs**: If the destination does not hold the MPT, the transaction fails with `tecNO_PERMISSION`. - - A new trustline or MPT holding may be created during `PaymentChannelClaim` if the destination submits the transaction and authorization is not required. + - The destination's trustline or `MPToken` is created during the claim only when the destination itself submits the transaction (and authorization is not required). No other submitter can create a holding for the destination, since holding a token requires the holder's consent. + - **IOU Tokens**: If the destination lacks a trustline with the issuer and did not submit the transaction, the transaction fails with `tecNO_LINE`. + - **MPTs**: If the destination does not hold the MPT and did not submit the transaction, the transaction fails with `tecNO_PERMISSION`. - **Cannot Create Trustline or MPT Holding:** - If unable to create due to lack of reserves, the transaction fails with `tecNO_LINE_INSUF_RESERVE` (IOU) or `tecINSUFFICIENT_RESERVE` (MPT). @@ -154,8 +153,8 @@ When the destination is the issuer of the channel's token, none of the authoriza **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. + - **IOU Tokens**: If the IOU does not require authorization and the account submitting the transaction is the destination, a trustline is created for it. + - **MPTs**: If the MPT does not require authorization and the account submitting the transaction is the destination, an `MPToken` is created for it. - **Adjustment from Issuer to Destination:** - **IOU Tokens**: The claimed amount, less any transfer fee (see Section 1.4), is added to the destination's trustline balance. If the destination is the issuer, the claimed amount is simply redeemed. - **MPTs**: @@ -182,9 +181,9 @@ Closure returns the remaining channel funds (`Amount` minus `Balance`) to the so - If authorization is required and the source is not authorized, the transaction fails with `tecNO_AUTH`. - **Source Lacks Trustline or MPT Holding:** - - **IOU Tokens**: If the source lacks a trustline with the issuer, the transaction fails with `tecNO_LINE`. - - **MPTs**: If the source does not hold the MPT, the transaction fails with `tecNO_PERMISSION`. - - A new trustline or MPT holding may be created during channel closure if the source submits the transaction and authorization is not required. + - The source's trustline or `MPToken` is created during closure only when the source itself submits the transaction (and authorization is not required), for the same reason as on a claim. + - **IOU Tokens**: If the source lacks a trustline with the issuer and did not submit the transaction, the transaction fails with `tecNO_LINE`. + - **MPTs**: If the source does not hold the MPT and did not submit the transaction, the transaction fails with `tecNO_PERMISSION`. - **Cannot Create Trustline or MPT Holding:** - If unable to create due to lack of reserves, the transaction fails with `tecNO_LINE_INSUF_RESERVE` (IOU) or `tecINSUFFICIENT_RESERVE` (MPT). @@ -199,8 +198,8 @@ Closure returns the remaining channel funds (`Amount` minus `Balance`) to the so **State Changes:** - **Auto create Trustline or MPToken:** - - **IOU Tokens**: If the IOU does not require authorization and the account submitting the transaction is the source, then a trustline will be created. - - **MPTs**: If the MPT does not require authorization and the account submitting the transaction is the source, then the MPT will be created. + - **IOU Tokens**: If the IOU does not require authorization and the account submitting the transaction is the source, a trustline is created for it. + - **MPTs**: If the MPT does not require authorization and the account submitting the transaction is the source, an `MPToken` is created for it. - **Adjustment from Issuer to Source:** - No transfer fee is applied when returning remaining funds to the source. - **IOU Tokens**: Any remaining channel funds are added to the source's trustline balance. @@ -212,24 +211,13 @@ Closure returns the remaining channel funds (`Amount` minus `Balance`) to the so The `Signature` on a `PaymentChannelClaim` is over the following message, signed with the key whose `PublicKey` is stored in the channel. Fields are concatenated in order with no field headers or length prefixes; integers are big-endian. -- **XRP channel (44 bytes):** - 1. The 4-byte prefix `0x434C4D00` (`HashPrefix::PaymentChannelClaim`). - 2. The 32-byte channel ID. - 3. The authorized amount in drops as an unsigned 64-bit integer. -- **IOU channel (84 bytes):** - 1. The 4-byte prefix `0x434C4D00`. - 2. The 32-byte channel ID. - 3. The authorized amount as the 64-bit value word the binary codec writes for a non-XRP `Amount`: a zero amount is `0x8000000000000000`; otherwise bit 63 is set, bit 62 is set for a positive amount, bits 54 to 61 hold the exponent plus 97, and bits 0 to 53 hold the mantissa. - 4. The 20-byte currency code. - 5. The 20-byte issuer `AccountID`. -- **MPT channel (88 bytes):** - 1. The 4-byte prefix `0x434C4D00`. - 2. The 32-byte channel ID. - 3. The authorized amount as an unsigned 64-bit integer. - 4. The 24-byte `MPTokenIssuanceID`. - 5. The 20-byte issuer `AccountID`. - -The IOU layout is the amount's `Amount` field serialization without its field header. The MPT layout is not: it has no leading type byte, and the issuer `AccountID` follows the `MPTokenIssuanceID`. +1. The 4-byte prefix `0x434C4D00` (`HashPrefix::PaymentChannelClaim`). +2. The 32-byte channel ID. +3. The authorized amount. For an XRP channel, the drops as an unsigned 64-bit integer, unchanged from today. For a token channel, the amount serialized as an `Amount` field value without its field header, the same bytes the binary codec writes for the transaction `Amount` field after its field ID: + - **IOU**: the 64-bit value word (a zero amount is `0x8000000000000000`; otherwise bit 63 is set, bit 62 is set for a positive amount, bits 54 to 61 hold the exponent plus 97, and bits 0 to 53 hold the mantissa), then the 20-byte currency code, then the 20-byte issuer `AccountID`. + - **MPT**: one type byte (`0x60` for a positive amount: the MPT bit `0x20` and the positive bit `0x40`), then the value as an unsigned 64-bit integer, then the 24-byte `MPTokenIssuanceID`. + +The message is 44 bytes for an XRP channel, 84 bytes for an IOU channel and 69 bytes for an MPT channel. The `MPTokenIssuanceID` already contains the issuer's `AccountID`, so nothing follows it. The `channel_authorize` and `channel_verify` RPC methods take the authorized amount in `amount`. For an XRP channel it is a string of drops. For a token channel it is the same JSON object used for a transaction `Amount` (`currency`, `issuer` and `value` for an IOU; `mpt_issuance_id` and `value` for an MPT), and the server builds the message above from it. @@ -245,7 +233,7 @@ Only the unclaimed remainder of the channel (`Amount` minus `Balance`) can be cl | `Channel` | Yes | String | Hash256 | The ID of the `PaymentChannel` to claw from. | | `Amount` | No | Object or String | Amount | The amount to claw back. Must be a positive, non-XRP amount of the channel's asset. If omitted, or if it is at least the unclaimed remainder, the entire remainder is clawed. | -The transaction may be delegated under [XLS-75](../XLS-0075-permission-delegation/README.md). +The transaction is not delegable under [XLS-75](../XLS-0075-permission-delegation/README.md): it is registered as `Delegation::NotDelegable`, so a delegate cannot submit it on the issuer's behalf, in line with XLS-75 Section 8 for new transaction types. **Failure Conditions:** @@ -265,18 +253,18 @@ The transaction may be delegated under [XLS-75](../XLS-0075-permission-delegatio - **Asset Mismatch:** - If `Amount` is present and is not the same asset as the channel's `Amount`, the transaction fails with `tecWRONG_ASSET`. -- **Precision Loss (IOU only):** - - If subtracting `Amount` from the channel's `Amount` would leave the channel's `Amount` unchanged, because the clawed amount is rounded away, the transaction fails with `tecPRECISION_LOSS`. +- **Inexact Difference (partial clawback only):** + - For a partial clawback the channel's new `Amount` must equal its old `Amount` minus the clawed `Amount` exactly. If the IOU subtraction rounds, so that the stored decrease differs from the amount clawed (including a decrease rounded away entirely), the transaction fails with `tecPRECISION_LOSS`. A full clawback is not subject to this check: it sets `Amount` equal to `Balance` directly and claws the exact remainder (see State Changes). - **Issuer Does Not Allow Clawback:** - **IOU Tokens**: If the issuer's account lacks the `lsfAllowTrustLineClawback` flag, or has the `lsfNoFreeze` flag set, the transaction fails with `tecNO_PERMISSION`. These are the same conditions that gate the [XLS-39](../XLS-0039-clawback/README.md) `Clawback` transaction. - **MPTs**: If the `MPTokenIssuance` lacks the `lsfMPTCanClawback` flag, the transaction fails with `tecNO_PERMISSION`. If the `MPTokenIssuance` does not exist, the transaction fails with `tecOBJECT_NOT_FOUND`. -- **No Unclaimed Remainder:** - - If the channel's `Balance` equals its `Amount` there is nothing to claw, and the transaction fails with `tecUNFUNDED_PAYMENT`. A claim that draws the channel down to its full `Amount` without `tfClose` leaves the channel open in exactly this state. - **State Changes:** +- **Nothing to Claw:** + - If the channel's `Balance` already equals its `Amount`, there is no remainder and the transaction succeeds without changing the channel. A claim that draws the channel down to its full `Amount` without `tfClose` leaves the channel open in exactly this state. + - **Adjustment to the Issuer:** - No transfer fee is applied. A clawback is a redemption rather than a transfer between holders. - **IOU Tokens**: No trustline is modified. The locked value was already removed from the source's trustline balance when the channel was created or funded, so retiring the channel's obligation is the whole of the clawback. From 059db88ad083621fcf0fd4beb7dc750026beddb9 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Mon, 5 Oct 2026 12:30:27 -0400 Subject: [PATCH 11/19] docs: restructure XLS-93 to the amendment template --- XLS-0093-token-paychan/README.md | 487 +++++++++++++++++++++++++------ 1 file changed, 398 insertions(+), 89 deletions(-) diff --git a/XLS-0093-token-paychan/README.md b/XLS-0093-token-paychan/README.md index b51192bdf..02c09403d 100644 --- a/XLS-0093-token-paychan/README.md +++ b/XLS-0093-token-paychan/README.md @@ -12,19 +12,25 @@ updated: 2026-10-05 +# Token-Enabled Payment Channels + > This proposal, XLS-93, extends payment channels to tokens in the same way [XLS-85](../XLS-0085-token-escrow/README.md) extends escrows, and reuses the issuer opt-in flags and locked-amount accounting introduced there. It builds on [XLS-33](../XLS-0033-multi-purpose-tokens/README.md) for MPTs and on [XLS-39](../XLS-0039-clawback/README.md) for the clawback opt-in. -## Abstract +## 1. Abstract The proposed `TokenPaychan` amendment to the XRP Ledger (XRPL) protocol enhances the existing `PaymentChannel` 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 payment channels to use IOU tokens and MPTs, while respecting issuer controls and maintaining ledger integrity. It also adds one new transaction, `PaymentChannelClawback`, so that an issuer whose holders can already be clawed back retains that reach over value locked in a channel. -# 1. Implementation +## 2. Motivation + +Payment channels accept XRP only. XLS-85 extended escrows to IOUs and MPTs, so an issuer who has opted in to token locking there still cannot have that token used in a channel, and off-ledger settlement of a token has no on-ledger lock to settle against. Locking a token also moves it out of reach of the ordinary `Clawback` transaction, which is bounded by the holder's spendable balance, so an issuer that relies on clawback would lose that control the moment a holder opened a channel. + +## 3. Specification This amendment extends the functionality of payment channels to support both IOUs and MPTs, accounting for the specific behaviors and constraints associated with each token type. Token-denominated channels share their locking model with Token-Enabled Escrows (XLS-85): the same `lsfAllowTrustLineLocking` and `lsfMPTCanEscrow` issuer opt-in flags, and the same `sfLockedAmount` accounting fields. -## 1.1. Overview of Token Types +## 3.1. Overview of Token Types -### 1.1.1. IOU Tokens +### 3.1.1. IOU Tokens - **Trustlines**: IOUs rely on trustlines between accounts. - **Issuer Controls**: @@ -33,7 +39,7 @@ This amendment extends the functionality of payment channels to support both IOU - **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) +### 3.1.2. Multi-Purpose Tokens (MPTs) - **No Trustlines**: MPTs do not utilize trustlines. - **Issuer Controls**: @@ -43,17 +49,106 @@ This amendment extends the functionality of payment channels to support both IOU - **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. Payment Channel Transactions and Logic +## 3.2. Ledger Entry: `PayChannel` + +The `PaymentChannel` ledger object is updated as follows: `Amount` and `Balance` may hold a token, and the optional `TransferRate` and `IssuerNode` fields are added. Unchanged fields are included below so that the example is self-contained. + +### 3.2.1. Fields + +| Field Name | Constant | Required | Internal Type | Default Value | Description | +| ------------------- | -------- | -------- | ------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `LedgerEntryType` | Yes | Yes | UINT16 | `0x0078` | Identifies this as a `PayChannel` object. | +| `Account` | Yes | Yes | ACCOUNT | N/A | The source address that owns this payment channel. | +| `Destination` | Yes | Yes | ACCOUNT | N/A | The destination address for this payment channel. While the channel is open, this address is the only one that can receive funds from the channel. | +| `Sequence` | Yes | No | UINT32 | N/A | The sequence number or ticket of the transaction that created the channel. | +| `Amount` | No | Yes | AMOUNT | N/A | The total amount allocated to the payment channel. Can represent XRP, an IOU token, or an MPT. Must always be a positive value. | +| `Balance` | No | Yes | AMOUNT | N/A | The amount already paid out from the channel. Same asset type as `Amount`. | +| `PublicKey` | Yes | Yes | BLOB | N/A | Public key of the key pair that can be used to sign claims against this channel. | +| `SettleDelay` | Yes | Yes | UINT32 | N/A | Number of seconds the source address must wait to close the channel if it still has funds in it. | +| `Expiration` | No | No | UINT32 | N/A | The mutable expiration time for this payment channel, in seconds since the Ripple Epoch. | +| `CancelAfter` | Yes | No | UINT32 | N/A | The immutable expiration time for this payment channel, in seconds since the Ripple Epoch. | +| `SourceTag` | Yes | No | UINT32 | N/A | An arbitrary tag to further specify the source for this payment channel. | +| `DestinationTag` | Yes | No | UINT32 | N/A | An arbitrary tag to further specify the destination for this payment channel. | +| `TransferRate` | Yes | No | UINT32 | N/A | The transfer rate or fee at creation, used as an upper bound on the rate applied during claims. Only present when the rate at creation differs from parity. | +| `OwnerNode` | No | Yes | UINT64 | N/A | A hint indicating which page of the source address's owner directory links to this entry. | +| `DestinationNode` | No | No | UINT64 | N/A | A hint indicating which page of the destination's owner directory links to this entry. | +| `IssuerNode` | No | No | UINT64 | N/A | The ledger index of the issuer's directory node associated with the `PaymentChannel`. Only present for IOU channels where the issuer is neither the source nor the destination. | +| `PreviousTxnID` | No | Yes | HASH256 | N/A | The identifying hash of the transaction that most recently modified this entry. | +| `PreviousTxnLgrSeq` | No | Yes | UINT32 | N/A | The index of the ledger that contains the transaction that most recently modified this entry. | + +### 3.2.2. Invariants + +- `'.Balance` and `'.Amount` are the same asset, and that asset is the asset of `.Amount`: the asset of a channel never changes after creation. +- `0 <= '.Balance <= '.Amount`. +- `.Balance <= '.Balance`: the paid-out balance only increases. +- `TransferRate` is present only if `Amount` is not XRP, and `'.TransferRate == .TransferRate`: it is set at creation and never updated. +- `IssuerNode` is present only if `Amount` is an IOU whose issuer is neither `Account` nor `Destination`. + +### 3.2.3. Example JSON + +```json +{ + "LedgerEntryType": "PayChannel", + "Flags": 0, + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Destination": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "Sequence": 12, + "Amount": { + "currency": "USD", + "issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "value": "1000" + }, + "Balance": { + "currency": "USD", + "issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "value": "250" + }, + "PublicKey": "032D2B4A9D4B0C1C7D5E0B6E5F9A8C7B6D5E4F3A2B1C0D9E8F7A6B5C4D3E2F1A0B", + "SettleDelay": 3600, + "TransferRate": 1005000000, + "OwnerNode": "0000000000000000", + "DestinationNode": "0000000000000000", + "IssuerNode": "0000000000000000", + "PreviousTxnID": "F0AB71E777B2DA54B86231E19B82554EF1F8211F92ECA473121C655BFC5329BF", + "PreviousTxnLgrSeq": 14661788 +} +``` + +## 3.3. Reused XLS-85 Fields and Flags + +### 3.3.1. `MPToken` and `MPTokenIssuance` Ledger Objects -### 1.2.1. `PaymentChannelCreate` +Token-denominated payment channels reuse the `sfLockedAmount` field introduced by [XLS-85](../XLS-0085-token-escrow/README.md) on both the `MPToken` and `MPTokenIssuance` ledger objects: + +| Field Name | JSON Type | Internal Type | Description | +| ---------------- | --------- | ------------- | ----------------------------------------------------------------------------------------- | +| `sfLockedAmount` | String | UInt64 | _(Optional)_ The total of all outstanding escrows and payment channels for this issuance. | + +### 3.3.2. `AccountRoot` Ledger Object + +No new flags are introduced. Token-denominated payment channels reuse the `lsfAllowTrustLineLocking` flag (`0x40000000`) introduced by [XLS-85](../XLS-0085-token-escrow/README.md): issuers who have enabled trust line locking for escrows have also enabled it for payment channels. See XLS-85 Section 1.6 for the corresponding `asfAllowTrustLineLocking` AccountSet flag. + +## 3.4. Transaction: `PaymentChannelCreate` The `PaymentChannelCreate` transaction is modified as follows: -| Field | Required? | JSON Type | Internal Type | Description | -| -------- | --------- | ---------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `Amount` | Yes | Object or String | Amount | The amount to fund the payment channel. 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. | +### 3.4.1. Fields + +| Field | Required? | JSON Type | Internal Type | Default Value | Description | +| -------- | --------- | ---------------- | ------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `Amount` | Yes | Object or String | Amount | N/A | The amount to fund the payment channel. 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. | -**Failure Conditions:** +### 3.4.2. Failure Conditions + +#### 3.4.2.1. Data Verification + +1. `Amount` is a token and the `TokenPaychan` amendment is not enabled. (`temBAD_AMOUNT`) +2. `Amount` is an IOU that is not positive. (`temBAD_AMOUNT`) +3. `Amount` is an IOU whose currency code is XRP. (`temBAD_CURRENCY`) +4. `Amount` is an MPT and the `MPTokensV1` amendment is not enabled. (`temDISABLED`) +5. `Amount` is an MPT that is not positive or exceeds the maximum MPT amount. (`temBAD_AMOUNT`) + +#### 3.4.2.2. Protocol-Level Failures - **Issuer is the Source:** - If the source account is the issuer of the token, the transaction fails with `tecNO_PERMISSION`. @@ -82,7 +177,7 @@ The `PaymentChannelCreate` transaction is modified as follows: - **Insufficient Spendable Balance:** - If the source account lacks sufficient spendable balance, the transaction fails with `tecINSUFFICIENT_FUNDS`. -**State Changes:** +### 3.4.3. State Changes - **Adjustment from Source to Issuer:** - **IOU Tokens**: The channel `Amount` is deducted from the source's trustline balance. @@ -94,11 +189,46 @@ The `PaymentChannelCreate` transaction is modified as follows: - `TransferRate`: `TransferRate` (IOUs) or `TransferFee` (MPTs) at creation. Only stored when it differs from parity (no fee). - `IssuerNode`: Reference to the issuer's ledger node. Only present for IOU channels where the issuer is neither the source nor the destination. -### 1.2.2. `PaymentChannelFund` +### 3.4.4. Example JSON + +```json +{ + "TransactionType": "PaymentChannelCreate", + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Destination": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "Amount": { + "currency": "USD", + "issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "value": "1000" + }, + "SettleDelay": 3600, + "PublicKey": "032D2B4A9D4B0C1C7D5E0B6E5F9A8C7B6D5E4F3A2B1C0D9E8F7A6B5C4D3E2F1A0B", + "Fee": "10", + "Sequence": 12 +} +``` + +## 3.5. Transaction: `PaymentChannelFund` The `PaymentChannelFund` transaction is modified to support token amounts. -**Failure Conditions:** +### 3.5.1. Fields + +| Field | Required? | JSON Type | Internal Type | Default Value | Description | +| -------- | --------- | ---------------- | ------------- | ------------- | -------------------------------------------------------------------------------------------------------- | +| `Amount` | Yes | Object or String | Amount | N/A | The amount to add to the channel. Must be the same asset as the channel's `Amount` and a positive value. | + +### 3.5.2. Failure Conditions + +#### 3.5.2.1. Data Verification + +1. `Amount` is a token and the `TokenPaychan` amendment is not enabled. (`temBAD_AMOUNT`) +2. `Amount` is an IOU that is not positive. (`temBAD_AMOUNT`) +3. `Amount` is an IOU whose currency code is XRP. (`temBAD_CURRENCY`) +4. `Amount` is an MPT and the `MPTokensV1` amendment is not enabled. (`temDISABLED`) +5. `Amount` is an MPT that is not positive or exceeds the maximum MPT amount. (`temBAD_AMOUNT`) + +#### 3.5.2.2. Protocol-Level Failures - **Asset Mismatch:** - If the funding `Amount` is not the same asset as the channel's `Amount`, the transaction fails with `tecWRONG_ASSET`. @@ -108,7 +238,7 @@ The `PaymentChannelFund` transaction is modified to support token amounts. - **Inexact Sum:** - The channel's new `Amount` must equal its old `Amount` plus the funding `Amount` exactly. An IOU sum is rounded to the mantissa width and is exact only if subtracting each operand from the sum gives back the other; an MPT sum is exact only if it does not overflow. Otherwise the transaction fails with `tecPRECISION_LOSS`. -**State Changes:** +### 3.5.3. State Changes - **Adjustment from Source:** - **IOU Tokens**: The funding `Amount` is deducted from the source's trustline balance. @@ -116,13 +246,58 @@ The `PaymentChannelFund` transaction is modified to support token amounts. - **Payment Channel Object Update:** - The channel's `Amount` field is increased by the funding amount. The stored `TransferRate` is not updated by funding. -### 1.2.3. `PaymentChannelClaim` +### 3.5.4. Example JSON + +```json +{ + "TransactionType": "PaymentChannelFund", + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Channel": "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198", + "Amount": { + "currency": "USD", + "issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "value": "500" + }, + "Fee": "10", + "Sequence": 13 +} +``` + +## 3.6. Transaction: `PaymentChannelClaim` + +### 3.6.1. Fields + +| Field | Required? | JSON Type | Internal Type | Default Value | Description | +| ----------- | --------- | ---------------- | ------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Balance` | No | Object or String | Amount | N/A | The total amount delivered by this channel after processing this claim. Same asset as the channel's `Amount`. | +| `Amount` | No | Object or String | Amount | N/A | The amount authorized by the `Signature`. Same asset as the channel's `Amount`; must be at least `Balance`. | +| `Signature` | No | String | Blob | N/A | The signature of the authorization message in Claim Authorization, made with the key whose `PublicKey` is stored in the channel. Required unless the source submits. | +| `PublicKey` | No | String | Blob | N/A | The public key used for `Signature`; must match the channel's `PublicKey`. Required when `Signature` is present. | -#### Normal Claim (Balance Update) +#### 3.6.1.1. Claim Authorization -When claiming without closing the channel: +The `Signature` on a `PaymentChannelClaim` is over the following message, signed with the key whose `PublicKey` is stored in the channel. Fields are concatenated in order with no field headers or length prefixes; integers are big-endian. + +1. The 4-byte prefix `0x434C4D00` (`HashPrefix::PaymentChannelClaim`). +2. The 32-byte channel ID. +3. The authorized amount. For an XRP channel, the drops as an unsigned 64-bit integer, unchanged from today. For a token channel, the amount serialized as an `Amount` field value without its field header, the same bytes the binary codec writes for the transaction `Amount` field after its field ID: + - **IOU**: the 64-bit value word (a zero amount is `0x8000000000000000`; otherwise bit 63 is set, bit 62 is set for a positive amount, bits 54 to 61 hold the exponent plus 97, and bits 0 to 53 hold the mantissa), then the 20-byte currency code, then the 20-byte issuer `AccountID`. + - **MPT**: one type byte (`0x60` for a positive amount: the MPT bit `0x20` and the positive bit `0x40`), then the value as an unsigned 64-bit integer, then the 24-byte `MPTokenIssuanceID`. + +The message is 44 bytes for an XRP channel, 84 bytes for an IOU channel and 69 bytes for an MPT channel. The `MPTokenIssuanceID` already contains the issuer's `AccountID`, so nothing follows it. + +### 3.6.2. Failure Conditions + +#### 3.6.2.1. Data Verification + +1. `Balance` or `Amount` is a token and the `TokenPaychan` amendment is not enabled. (`temBAD_AMOUNT`) +2. `Balance` or `Amount` is an MPT and the `MPTokensV1` amendment is not enabled. (`temDISABLED`) +3. `Balance` and `Amount` are both present and are not the same asset. (`temBAD_AMOUNT`) +4. `Signature` does not verify against the message in Claim Authorization for the authorized amount (`Amount`, or `Balance` when `Amount` is absent). (`temBAD_SIGNATURE`) + +#### 3.6.2.2. Protocol-Level Failures -**Failure Conditions:** +**Normal Claim (Balance Update)**, when claiming without closing the channel: When the destination is the issuer of the channel's token, none of the authorization, holding, trustline limit or freeze conditions below apply: the issuer has no trustline to itself and holds no `MPToken`, and the claim redeems the tokens to the issuer (see State Changes). @@ -150,26 +325,7 @@ When the destination is the issuer of the channel's token, none of the authoriza - **MPTs**: - **Lock Conditions (Equivalent to Deep Freeze)**: The transaction fails with `tecLOCKED`. -**State Changes:** - -- **Auto create Trustline or MPToken:** - - **IOU Tokens**: If the IOU does not require authorization and the account submitting the transaction is the destination, a trustline is created for it. - - **MPTs**: If the MPT does not require authorization and the account submitting the transaction is the destination, an `MPToken` is created for it. -- **Adjustment from Issuer to Destination:** - - **IOU Tokens**: The claimed amount, less any transfer fee (see Section 1.4), is added to the destination's trustline balance. If the destination is the issuer, the claimed amount is simply redeemed. - - **MPTs**: - - If the destination is the issuer of the asset held in the channel, then: - 1. The `LockedAmount` on the `MPTokenIssuance` and the source's `MPToken` is decreased by the claimed amount. - 2. No destination `MPToken` object is changed because MPT issuers may not hold MPTokens. - 3. The `OutstandingAmount` on the `MPTokenIssuance` is decreased by the claimed amount (i.e., this claim is a "redemption"). - - If the destination is not the issuer of the asset held in the channel, then: - 1. The `LockedAmount` on the `MPTokenIssuance` and the source's `MPToken` is decreased by the claimed amount. - 2. The `Amount` on the destination's `MPToken` is increased by the claimed amount, less any transfer fee. - 3. The `OutstandingAmount` on the `MPTokenIssuance` is decreased by the transfer fee, the claimed amount less the amount credited to the destination; the fee is credited to no holder, so it leaves the outstanding supply. -- **Channel Balance Update:** - - The channel's `Balance` field is updated to reflect the total amount claimed. - -#### Channel Closure +**Channel Closure** A channel closes when a claim carries the `tfClose` flag (immediately if the requester is the destination or the channel is fully drained; otherwise an expiration is scheduled per `SettleDelay`), or when any claim is processed against an already-expired channel. @@ -195,7 +351,28 @@ Closure returns the remaining channel funds (`Amount` minus `Balance`) to the so - **MPTs**: - **Lock Conditions (Deep Freeze Equivalent)**: The transaction succeeds, allowing the channel to be closed. -**State Changes:** +### 3.6.3. State Changes + +**Normal Claim (Balance Update)** + +- **Auto create Trustline or MPToken:** + - **IOU Tokens**: If the IOU does not require authorization and the account submitting the transaction is the destination, a trustline is created for it. + - **MPTs**: If the MPT does not require authorization and the account submitting the transaction is the destination, an `MPToken` is created for it. +- **Adjustment from Issuer to Destination:** + - **IOU Tokens**: The claimed amount, less any transfer fee (see Section 3.11), is added to the destination's trustline balance. If the destination is the issuer, the claimed amount is simply redeemed. + - **MPTs**: + - If the destination is the issuer of the asset held in the channel, then: + 1. The `LockedAmount` on the `MPTokenIssuance` and the source's `MPToken` is decreased by the claimed amount. + 2. No destination `MPToken` object is changed because MPT issuers may not hold MPTokens. + 3. The `OutstandingAmount` on the `MPTokenIssuance` is decreased by the claimed amount (i.e., this claim is a "redemption"). + - If the destination is not the issuer of the asset held in the channel, then: + 1. The `LockedAmount` on the `MPTokenIssuance` and the source's `MPToken` is decreased by the claimed amount. + 2. The `Amount` on the destination's `MPToken` is increased by the claimed amount, less any transfer fee. + 3. The `OutstandingAmount` on the `MPTokenIssuance` is decreased by the transfer fee, the claimed amount less the amount credited to the destination; the fee is credited to no holder, so it leaves the outstanding supply. +- **Channel Balance Update:** + - The channel's `Balance` field is updated to reflect the total amount claimed. + +**Channel Closure** - **Auto create Trustline or MPToken:** - **IOU Tokens**: If the IOU does not require authorization and the account submitting the transaction is the source, a trustline is created for it. @@ -207,40 +384,62 @@ Closure returns the remaining channel funds (`Amount` minus `Balance`) to the so - **Deletion of Payment Channel Object:** - The `PaymentChannel` object is deleted after successful closure. -#### Claim Authorization +### 3.6.4. Example JSON + +```json +{ + "TransactionType": "PaymentChannelClaim", + "Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "Channel": "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198", + "Balance": { + "currency": "USD", + "issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "value": "400" + }, + "Amount": { + "currency": "USD", + "issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "value": "400" + }, + "Signature": "30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B", + "PublicKey": "032D2B4A9D4B0C1C7D5E0B6E5F9A8C7B6D5E4F3A2B1C0D9E8F7A6B5C4D3E2F1A0B", + "Fee": "10", + "Sequence": 7 +} +``` + +## 3.7. Transaction: `PaymentChannelClawback` -The `Signature` on a `PaymentChannelClaim` is over the following message, signed with the key whose `PublicKey` is stored in the channel. Fields are concatenated in order with no field headers or length prefixes; integers are big-endian. +Locking a token into a channel moves it out of reach of the ordinary `Clawback` transaction (for an MPT, `Clawback` with the `MPTokenHolder` field defined in [XLS-33](../XLS-0033-multi-purpose-tokens/README.md)), which is bounded by the holder's spendable balance and so cannot see locked value. `PaymentChannelClawback` gives the issuer that reach back. It requires the same opt-in the issuer already needed to claw back an ordinary holding, so it grants no new authority over a token; it removes a place the token could be kept out of reach. -1. The 4-byte prefix `0x434C4D00` (`HashPrefix::PaymentChannelClaim`). -2. The 32-byte channel ID. -3. The authorized amount. For an XRP channel, the drops as an unsigned 64-bit integer, unchanged from today. For a token channel, the amount serialized as an `Amount` field value without its field header, the same bytes the binary codec writes for the transaction `Amount` field after its field ID: - - **IOU**: the 64-bit value word (a zero amount is `0x8000000000000000`; otherwise bit 63 is set, bit 62 is set for a positive amount, bits 54 to 61 hold the exponent plus 97, and bits 0 to 53 hold the mantissa), then the 20-byte currency code, then the 20-byte issuer `AccountID`. - - **MPT**: one type byte (`0x60` for a positive amount: the MPT bit `0x20` and the positive bit `0x40`), then the value as an unsigned 64-bit integer, then the 24-byte `MPTokenIssuanceID`. +Only the unclaimed remainder of the channel (`Amount` minus `Balance`) can be clawed. The destination's earned `Balance` is never touched, so a clawback cannot reverse value the payee has already claimed. -The message is 44 bytes for an XRP channel, 84 bytes for an IOU channel and 69 bytes for an MPT channel. The `MPTokenIssuanceID` already contains the issuer's `AccountID`, so nothing follows it. +### 3.7.1. Fields -The `channel_authorize` and `channel_verify` RPC methods take the authorized amount in `amount`. For an XRP channel it is a string of drops. For a token channel it is the same JSON object used for a transaction `Amount` (`currency`, `issuer` and `value` for an IOU; `mpt_issuance_id` and `value` for an MPT), and the server builds the message above from it. +| Field | Required? | JSON Type | Internal Type | Default Value | Description | +| ----------------- | --------- | ---------------- | ------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TransactionType` | Yes | String | UInt16 | N/A | The transaction type, `PaymentChannelClawback` (`ttPAYCHAN_CLAWBACK`, value `92`). | +| `Channel` | Yes | String | Hash256 | N/A | The ID of the `PaymentChannel` to claw from. | +| `Amount` | No | Object or String | Amount | N/A | The amount to claw back. Must be a positive, non-XRP amount of the channel's asset. If omitted, or if it is at least the unclaimed remainder, the entire remainder is clawed. | -### 1.2.4. `PaymentChannelClawback` +The transaction is not delegable under [XLS-75](../XLS-0075-permission-delegation/README.md): it is registered as `Delegation::NotDelegable`, so a delegate cannot submit it on the issuer's behalf, in line with XLS-75 Section 8 for new transaction types. -Locking a token into a channel moves it out of reach of the ordinary `Clawback` transaction (for an MPT, `Clawback` with the `MPTokenHolder` field defined in [XLS-33](../XLS-0033-multi-purpose-tokens/README.md)), which is bounded by the holder's spendable balance and so cannot see locked value. `PaymentChannelClawback` gives the issuer that reach back. It requires the same opt-in the issuer already needed to claw back an ordinary holding, so it grants no new authority over a token; it removes a place the token could be kept out of reach. +### 3.7.2. Transaction Fee -Only the unclaimed remainder of the channel (`Amount` minus `Balance`) can be clawed. The destination's earned `Balance` is never touched, so a clawback cannot reverse value the payee has already claimed. +**Fee Structure:** Standard -| Field | Required? | JSON Type | Internal Type | Description | -| ----------------- | --------- | ---------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `TransactionType` | Yes | String | UInt16 | The transaction type, `PaymentChannelClawback` (`ttPAYCHAN_CLAWBACK`, value `92`). | -| `Channel` | Yes | String | Hash256 | The ID of the `PaymentChannel` to claw from. | -| `Amount` | No | Object or String | Amount | The amount to claw back. Must be a positive, non-XRP amount of the channel's asset. If omitted, or if it is at least the unclaimed remainder, the entire remainder is clawed. | +This transaction uses the standard transaction fee (currently 10 drops, subject to Fee Voting changes). -The transaction is not delegable under [XLS-75](../XLS-0075-permission-delegation/README.md): it is registered as `Delegation::NotDelegable`, so a delegate cannot submit it on the issuer's behalf, in line with XLS-75 Section 8 for new transaction types. +### 3.7.3. Failure Conditions -**Failure Conditions:** +#### 3.7.3.1. Data Verification - **Malformed `Amount`:** - If `Amount` is present and is XRP or is not positive, the transaction fails with `temBAD_AMOUNT`. An MPT amount above the maximum MPT value fails the same way. - If `Amount` is present and names XRP as an IOU currency code, the transaction fails with `temBAD_CURRENCY`. +#### 3.7.3.2. Protocol-Level Failures + - **Channel Does Not Exist:** - If no `PaymentChannel` object matches `Channel`, the transaction fails with `tecNO_TARGET`. @@ -260,7 +459,7 @@ The transaction is not delegable under [XLS-75](../XLS-0075-permission-delegatio - **IOU Tokens**: If the issuer's account lacks the `lsfAllowTrustLineClawback` flag, or has the `lsfNoFreeze` flag set, the transaction fails with `tecNO_PERMISSION`. These are the same conditions that gate the [XLS-39](../XLS-0039-clawback/README.md) `Clawback` transaction. - **MPTs**: If the `MPTokenIssuance` lacks the `lsfMPTCanClawback` flag, the transaction fails with `tecNO_PERMISSION`. If the `MPTokenIssuance` does not exist, the transaction fails with `tecOBJECT_NOT_FOUND`. -**State Changes:** +### 3.7.4. State Changes - **Nothing to Claw:** - If the channel's `Balance` already equals its `Amount`, there is no remainder and the transaction succeeds without changing the channel. A claim that draws the channel down to its full `Amount` without `tfClose` leaves the channel open in exactly this state. @@ -280,7 +479,128 @@ A clawback lowers the channel's `Amount`, which is the ceiling on what the desti An expired channel can still be clawed. Expiry entitles the source to a refund but does not perform one until some account submits a transaction against the channel, so an issuer clawback and the source's refund race for the remainder. -## 1.3. Key Differences Between IOU and MPT Payment Channels +### 3.7.5. Example JSON + +```json +{ + "TransactionType": "PaymentChannelClawback", + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "Channel": "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198", + "Amount": { + "currency": "USD", + "issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "value": "100" + }, + "Fee": "10", + "Sequence": 31 +} +``` + +## 3.8. RPC: `channel_authorize` + +The `channel_authorize` and `channel_verify` RPC methods take the authorized amount in `amount`. For an XRP channel it is a string of drops. For a token channel it is the same JSON object used for a transaction `Amount` (`currency`, `issuer` and `value` for an IOU; `mpt_issuance_id` and `value` for an MPT), and the server builds the message in Claim Authorization from it. + +### 3.8.1. Request Fields + +| Field Name | Required? | JSON Type | Description | +| ------------ | --------- | ---------------- | --------------------------------------------------------------------------------------------------------------------- | +| `command` | Yes | string | Must be `"channel_authorize"` | +| `channel_id` | Yes | string | The 256-bit channel ID, in hexadecimal. | +| `amount` | Yes | string or object | The authorized amount: a string of drops for an XRP channel, or an `Amount` object for a token channel. | +| `secret` | No | string | The secret key of the channel's `PublicKey`. Exactly one of `secret`, `seed`, `seed_hex` or `passphrase` is required. | +| `key_type` | No | string | The signing algorithm of the key, `secp256k1` or `ed25519`. | + +### 3.8.2. Response Fields + +| Field Name | Always Present? | JSON Type | Description | +| ----------- | --------------- | --------- | ----------------------------------------------------------------- | +| `status` | Yes | string | `"success"` if the request succeeded | +| `signature` | Yes | string | The signature of the Claim Authorization message, in hexadecimal. | + +### 3.8.3. Failure Conditions + +1. Signing is not supported by this server. (`notSupported`) +2. `channel_id` or `amount` is missing, or no signing key field is present. (`invalidParams`) +3. `channel_id` is not a 256-bit hexadecimal string. (`channelMalformed`) +4. `amount` is neither a string of drops nor an `Amount` object for a token amount that is not negative. (`channelAmtMalformed`) + +### 3.8.4. Example Request + +```json +{ + "command": "channel_authorize", + "channel_id": "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198", + "amount": { + "currency": "USD", + "issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "value": "400" + }, + "seed": "snoPBrXtMeMyMHUVTgbuqAfg1SUTb", + "key_type": "secp256k1" +} +``` + +### 3.8.5. Example Response + +```json +{ + "status": "success", + "signature": "30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B" +} +``` + +## 3.9. RPC: `channel_verify` + +### 3.9.1. Request Fields + +| Field Name | Required? | JSON Type | Description | +| ------------ | --------- | ---------------- | ------------------------------------------------------------------- | +| `command` | Yes | string | Must be `"channel_verify"` | +| `channel_id` | Yes | string | The 256-bit channel ID, in hexadecimal. | +| `amount` | Yes | string or object | The authorized amount, in the same form as for `channel_authorize`. | +| `public_key` | Yes | string | The channel's `PublicKey`, in hexadecimal or base58. | +| `signature` | Yes | string | The signature to verify, in hexadecimal. | + +### 3.9.2. Response Fields + +| Field Name | Always Present? | JSON Type | Description | +| -------------------- | --------------- | --------- | ------------------------------------------------------------------------------------------------------ | +| `status` | Yes | string | `"success"` if the request succeeded | +| `signature_verified` | Yes | boolean | Whether `signature` is valid for the Claim Authorization message built from `channel_id` and `amount`. | + +### 3.9.3. Failure Conditions + +1. `public_key`, `channel_id`, `amount` or `signature` is missing, or `signature` is not a non-empty hexadecimal string. (`invalidParams`) +2. `public_key` is not a valid public key. (`publicMalformed`) +3. `channel_id` is not a 256-bit hexadecimal string. (`channelMalformed`) +4. `amount` is neither a string of drops nor an `Amount` object for a token amount that is not negative. (`channelAmtMalformed`) + +### 3.9.4. Example Request + +```json +{ + "command": "channel_verify", + "channel_id": "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198", + "amount": { + "currency": "USD", + "issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "value": "400" + }, + "public_key": "032D2B4A9D4B0C1C7D5E0B6E5F9A8C7B6D5E4F3A2B1C0D9E8F7A6B5C4D3E2F1A0B", + "signature": "30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B" +} +``` + +### 3.9.5. Example Response + +```json +{ + "status": "success", + "signature_verified": true +} +``` + +## 3.10. Key Differences Between IOU and MPT Payment Channels | Aspect | IOU Tokens | Multi-Purpose Tokens (MPTs) | | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -297,57 +617,46 @@ An expired channel can still be clawed. Expiry entitles the source to a refund b | **Account Deletion** | Payment channels prevent account deletion | Payment channels prevent account deletion | | **Holding Deletion** | Trustline deletion is NOT blocked by open channels (locked value lives in the channel object); closure refund then fails with `tecNO_LINE` until the line is re-established | `MPToken` deletion is blocked while `sfLockedAmount` is non-zero (`tecHAS_OBLIGATIONS`) | -## 1.4. Transfer Rates and Fees +## 3.11. Transfer Rates and Fees -### 1.4.1. IOU Tokens (`TransferRate`) +### 3.11.1. IOU Tokens (`TransferRate`) - **Rate Capped at Creation**: The `TransferRate` is captured at the time of `PaymentChannelCreate` and stored in the `PaymentChannel` object. At claim time, the lower of the stored rate and the issuer's current rate is applied: an increase by the issuer does not affect existing channels, while a decrease passes through to claims. This is identical to the behavior of the activated XLS-85 (Token Escrow) implementation, which uses the same shared unlock logic. - **Fee Calculation**: The transfer fee is deducted from the claimed amount, reducing the final amount credited to the destination. No fee is applied when the issuer is the destination, or when remaining funds are returned to the source at closure. -### 1.4.2. MPTs (`TransferFee`) +### 3.11.2. MPTs (`TransferFee`) - **Fee Capped at Creation**: The `TransferFee` is captured at the time of `PaymentChannelCreate` and stored in the `PaymentChannel` object, similar to IOUs, with the same lower-of-stored-and-current rule. - **Fee Calculation**: The transfer fee is deducted from the claimed amount, reducing the final amount credited to the destination. - **Consistent Fee Application**: Both IOUs and MPTs use the same capped-rate rule, ensuring the destination's settlement value cannot be worsened by the issuer after channel creation. -## 1.5. Ledger Object Updates - -### 1.5.1 `PaymentChannel` Ledger Object +## 3.12. Future Considerations -The `PaymentChannel` ledger object is updated as follows: - -| Field Name | JSON Type | Internal Type | Description | -| -------------- | ---------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Amount` | Object or String | Amount | The total amount allocated to the payment channel. Can represent XRP, an IOU token, or an MPT. Must always be a positive value. | -| `Balance` | Object or String | Amount | The amount already paid out from the channel. Same asset type as `Amount`. | -| `TransferRate` | Number | UInt32 | _(Optional)_ The transfer rate or fee at creation, used as an upper bound on the rate applied during claims. Only present when the rate at creation differs from parity. | -| `IssuerNode` | Number | UInt64 | _(Optional)_ The ledger index of the issuer's directory node associated with the `PaymentChannel`. Only present for IOU channels where the issuer is neither the source nor the destination. | +1. Issuer as Source: XLS-93 currently does not allow the issuer to be the source of the Payment Channel. If your use case requires this functionality, you should create a new account, send the MPT or IOU to that account, and then create the payment channel with that account as the source. -### 1.5.2 `MPToken` and `MPTokenIssuance` Ledger Objects +2. Trustline Deletion While Locked: because the locked IOU value lives in the `PaymentChannel` object rather than on the trustline, an empty trustline can be deleted while channels remain open (see Section 8). Per-trustline lock accounting that would prevent this, for both escrows and payment channels, is deliberately left to a separate future amendment so that XLS-93 stays behaviorally aligned with the activated XLS-85. -Token-denominated payment channels reuse the `sfLockedAmount` field introduced by [XLS-85](../XLS-0085-token-escrow/README.md) on both the `MPToken` and `MPTokenIssuance` ledger objects: +## 4. Rationale -| Field Name | JSON Type | Internal Type | Description | -| ---------------- | --------- | ------------- | ----------------------------------------------------------------------------------------- | -| `sfLockedAmount` | String | UInt64 | _(Optional)_ The total of all outstanding escrows and payment channels for this issuance. | +Payment channels are the last remaining XRP-only locking primitive; XLS-85 already extended escrows to IOUs and MPTs. Reusing the XLS-85 model wholesale, the same issuer opt-in flags (`lsfAllowTrustLineLocking`, `lsfMPTCanEscrow`), the same `sfLockedAmount` accounting, and the same shared lock/unlock logic in the implementation, means issuers make one opt-in decision that covers both primitives, and both primitives fail and succeed under identical token conditions. Every place where XLS-93 is stricter than base token semantics (any freeze blocks lock creation, no channel creation during global freeze even to the issuer) is inherited from the activated XLS-85 behavior rather than newly invented, keeping the two locking primitives coherent. -### 1.5.3 `AccountRoot` Ledger Object +`PaymentChannelClawback` is included in the same amendment for the same reason. An issuer's clawback opt-in is a property of the token, so it should hold wherever that token sits. Deferring the transaction would have meant shipping a lock that quietly suspends an issuer control the token already carries, and the alternative of closing the channel first is not open to the issuer, which is not a party to the channel and cannot close it. -No new flags are introduced. Token-denominated payment channels reuse the `lsfAllowTrustLineLocking` flag (`0x40000000`) introduced by [XLS-85](../XLS-0085-token-escrow/README.md): issuers who have enabled trust line locking for escrows have also enabled it for payment channels. See XLS-85 Section 1.6 for the corresponding `asfAllowTrustLineLocking` AccountSet flag. +## 5. Backwards Compatibility -## 1.6. Future Considerations +The change is amendment-gated. Before `TokenPaychan` is enabled, a token `Amount` on `PaymentChannelCreate` or `PaymentChannelFund`, and a token `Balance` or `Amount` on `PaymentChannelClaim`, are rejected with `temBAD_AMOUNT`, and `PaymentChannelClawback` is rejected with `temDISABLED`. -1. Issuer as Source: XLS-93 currently does not allow the issuer to be the source of the Payment Channel. If your use case requires this functionality, you should create a new account, send the MPT or IOU to that account, and then create the payment channel with that account as the source. +XRP channels are unchanged: the Claim Authorization message for an XRP channel keeps its 44-byte layout, existing `PayChannel` entries gain no fields (`TransferRate` and `IssuerNode` are only ever set on token channels), and `channel_authorize` and `channel_verify` continue to accept `amount` as a string of drops. -2. Trustline Deletion While Locked: because the locked IOU value lives in the `PaymentChannel` object rather than on the trustline, an empty trustline can be deleted while channels remain open (see Section 3). Per-trustline lock accounting that would prevent this, for both escrows and payment channels, is deliberately left to a separate future amendment so that XLS-93 stays behaviorally aligned with the activated XLS-85. +## 6. Test Plan -## 2. Rationale +The reference implementation adds the `PayChanToken` test suite (`src/test/app/PayChanToken_test.cpp`), which covers, for IOUs and MPTs separately: amendment enablement, the issuer opt-in flags, preflight, preclaim and apply of `PaymentChannelCreate`, `PaymentChannelFund` and `PaymentChannelClaim`, closure, auto-creation of the destination's holding, balances and metadata, the locked transfer rate, require-auth, freeze and lock, trustline limits, precision loss, the interaction with ordinary `Clawback`, `PaymentChannelClawback` for both asset types, the `channel_authorize` and `channel_verify` methods, and the byte layout of the Claim Authorization message. -Payment channels are the last remaining XRP-only locking primitive; XLS-85 already extended escrows to IOUs and MPTs. Reusing the XLS-85 model wholesale, the same issuer opt-in flags (`lsfAllowTrustLineLocking`, `lsfMPTCanEscrow`), the same `sfLockedAmount` accounting, and the same shared lock/unlock logic in the implementation, means issuers make one opt-in decision that covers both primitives, and both primitives fail and succeed under identical token conditions. Every place where XLS-93 is stricter than base token semantics (any freeze blocks lock creation, no channel creation during global freeze even to the issuer) is inherited from the activated XLS-85 behavior rather than newly invented, keeping the two locking primitives coherent. +## 7. Reference Implementation -`PaymentChannelClawback` is included in the same amendment for the same reason. An issuer's clawback opt-in is a property of the token, so it should hold wherever that token sits. Deferring the transaction would have meant shipping a lock that quietly suspends an issuer control the token already carries, and the alternative of closing the channel first is not open to the issuer, which is not a party to the channel and cannot close it. +[XRPLF/rippled#7935](https://github.com/XRPLF/rippled/pull/7935) (token-denominated channels) and [XRPLF/rippled#7936](https://github.com/XRPLF/rippled/pull/7936) (`PaymentChannelClawback`). -## 3. Security Considerations +## 8. Security Considerations - **Payee protection.** The destination's earned funds are never gated by source-side state. Normal claims check only destination-side conditions, and the source-side conditions in Channel Closure apply only to refunding a positive remainder; a fully drained channel closes without them. - **Issuer trust surface.** An issuer that uses `RequireAuth` can deauthorize the source and thereby block the refund leg of closure (`tecNO_AUTH`) until re-authorized. The channel and its locked funds remain on ledger; no funds are lost. This is the same issuer trust surface that exists for XLS-85 escrow refunds and for clawback generally. From 28fa060cdfa66fe941190b6c6bb0c3ddec91b883 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Mon, 5 Oct 2026 13:19:36 -0400 Subject: [PATCH 12/19] docs: PaymentChannelClawback transaction type is 94 --- XLS-0093-token-paychan/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XLS-0093-token-paychan/README.md b/XLS-0093-token-paychan/README.md index 02c09403d..d192a3673 100644 --- a/XLS-0093-token-paychan/README.md +++ b/XLS-0093-token-paychan/README.md @@ -418,7 +418,7 @@ Only the unclaimed remainder of the channel (`Amount` minus `Balance`) can be cl | Field | Required? | JSON Type | Internal Type | Default Value | Description | | ----------------- | --------- | ---------------- | ------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `TransactionType` | Yes | String | UInt16 | N/A | The transaction type, `PaymentChannelClawback` (`ttPAYCHAN_CLAWBACK`, value `92`). | +| `TransactionType` | Yes | String | UInt16 | N/A | The transaction type, `PaymentChannelClawback` (`ttPAYCHAN_CLAWBACK`, value `94`). | | `Channel` | Yes | String | Hash256 | N/A | The ID of the `PaymentChannel` to claw from. | | `Amount` | No | Object or String | Amount | N/A | The amount to claw back. Must be a positive, non-XRP amount of the channel's asset. If omitted, or if it is at least the unclaimed remainder, the entire remainder is clawed. | From 222a89782d4fa49ffd06178c6c4c342ce34607a3 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Mon, 5 Oct 2026 19:53:55 -0400 Subject: [PATCH 13/19] docs: XLS-93 PayChannel naming and MPT field fixes --- XLS-0093-token-paychan/README.md | 71 ++++++++++++++++---------------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/XLS-0093-token-paychan/README.md b/XLS-0093-token-paychan/README.md index d192a3673..1d1f484d6 100644 --- a/XLS-0093-token-paychan/README.md +++ b/XLS-0093-token-paychan/README.md @@ -18,7 +18,7 @@ ## 1. Abstract -The proposed `TokenPaychan` amendment to the XRP Ledger (XRPL) protocol enhances the existing `PaymentChannel` 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 payment channels to use IOU tokens and MPTs, while respecting issuer controls and maintaining ledger integrity. It also adds one new transaction, `PaymentChannelClawback`, so that an issuer whose holders can already be clawed back retains that reach over value locked in a channel. +The proposed `TokenPaychan` amendment to the XRP Ledger (XRPL) protocol enhances the existing `PayChannel` 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 payment channels to use IOU tokens and MPTs, while respecting issuer controls and maintaining ledger integrity. It also adds one new transaction, `PaymentChannelClawback`, so that an issuer whose holders can already be clawed back retains that reach over value locked in a channel. ## 2. Motivation @@ -43,7 +43,7 @@ This amendment extends the functionality of payment channels to support both IOU - **No Trustlines**: MPTs do not utilize trustlines. - **Issuer Controls**: - - **Transfer Flags (`lsfMPTCanTransfer`)**: Tokens must have this flag enabled to be transferable and to participate in transactions like payment channels. + - **Transfer Flags (`lsfMPTCanTransfer`)**: Tokens must have this flag enabled to be transferable and to participate in transactions like payment channels, unless the destination address of the Payment Channel is the issuer of the MPT. - **Require Authorization (`lsfMPTRequireAuth`)**: Issuers may require authorization for accounts to hold their tokens. - **Lock Conditions (`lsfMPTLocked`)**: Tokens can be locked by the issuer, affecting their transferability. - **Transfer Mechanics**: Transfers occur by moving token balances directly between accounts. @@ -51,30 +51,30 @@ This amendment extends the functionality of payment channels to support both IOU ## 3.2. Ledger Entry: `PayChannel` -The `PaymentChannel` ledger object is updated as follows: `Amount` and `Balance` may hold a token, and the optional `TransferRate` and `IssuerNode` fields are added. Unchanged fields are included below so that the example is self-contained. +The `PayChannel` ledger object is updated as follows: `Amount` and `Balance` may hold a token, and the optional `TransferRate` and `IssuerNode` fields are added. Unchanged fields are included below so that the example is self-contained. ### 3.2.1. Fields -| Field Name | Constant | Required | Internal Type | Default Value | Description | -| ------------------- | -------- | -------- | ------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `LedgerEntryType` | Yes | Yes | UINT16 | `0x0078` | Identifies this as a `PayChannel` object. | -| `Account` | Yes | Yes | ACCOUNT | N/A | The source address that owns this payment channel. | -| `Destination` | Yes | Yes | ACCOUNT | N/A | The destination address for this payment channel. While the channel is open, this address is the only one that can receive funds from the channel. | -| `Sequence` | Yes | No | UINT32 | N/A | The sequence number or ticket of the transaction that created the channel. | -| `Amount` | No | Yes | AMOUNT | N/A | The total amount allocated to the payment channel. Can represent XRP, an IOU token, or an MPT. Must always be a positive value. | -| `Balance` | No | Yes | AMOUNT | N/A | The amount already paid out from the channel. Same asset type as `Amount`. | -| `PublicKey` | Yes | Yes | BLOB | N/A | Public key of the key pair that can be used to sign claims against this channel. | -| `SettleDelay` | Yes | Yes | UINT32 | N/A | Number of seconds the source address must wait to close the channel if it still has funds in it. | -| `Expiration` | No | No | UINT32 | N/A | The mutable expiration time for this payment channel, in seconds since the Ripple Epoch. | -| `CancelAfter` | Yes | No | UINT32 | N/A | The immutable expiration time for this payment channel, in seconds since the Ripple Epoch. | -| `SourceTag` | Yes | No | UINT32 | N/A | An arbitrary tag to further specify the source for this payment channel. | -| `DestinationTag` | Yes | No | UINT32 | N/A | An arbitrary tag to further specify the destination for this payment channel. | -| `TransferRate` | Yes | No | UINT32 | N/A | The transfer rate or fee at creation, used as an upper bound on the rate applied during claims. Only present when the rate at creation differs from parity. | -| `OwnerNode` | No | Yes | UINT64 | N/A | A hint indicating which page of the source address's owner directory links to this entry. | -| `DestinationNode` | No | No | UINT64 | N/A | A hint indicating which page of the destination's owner directory links to this entry. | -| `IssuerNode` | No | No | UINT64 | N/A | The ledger index of the issuer's directory node associated with the `PaymentChannel`. Only present for IOU channels where the issuer is neither the source nor the destination. | -| `PreviousTxnID` | No | Yes | HASH256 | N/A | The identifying hash of the transaction that most recently modified this entry. | -| `PreviousTxnLgrSeq` | No | Yes | UINT32 | N/A | The index of the ledger that contains the transaction that most recently modified this entry. | +| Field Name | Constant | Required | Internal Type | Default Value | Description | +| ------------------- | -------- | -------- | ------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `LedgerEntryType` | Yes | Yes | UINT16 | `0x0078` | Identifies this as a `PayChannel` object. | +| `Account` | Yes | Yes | ACCOUNT | N/A | The source address that owns this payment channel. | +| `Destination` | Yes | Yes | ACCOUNT | N/A | The destination address for this payment channel. While the channel is open, this address is the only one that can receive funds from the channel. | +| `Sequence` | Yes | No | UINT32 | N/A | The sequence number or ticket of the transaction that created the channel. | +| `Amount` | No | Yes | AMOUNT | N/A | The total amount allocated to the payment channel. Can represent XRP, an IOU token, or an MPT. Must always be a positive value. | +| `Balance` | No | Yes | AMOUNT | N/A | The amount already paid out from the channel. Same asset type as `Amount`. | +| `PublicKey` | Yes | Yes | BLOB | N/A | Public key of the key pair that can be used to sign claims against this channel. | +| `SettleDelay` | Yes | Yes | UINT32 | N/A | Number of seconds the source address must wait to close the channel if it still has funds in it. | +| `Expiration` | No | No | UINT32 | N/A | The mutable expiration time for this payment channel, in seconds since the Ripple Epoch. | +| `CancelAfter` | Yes | No | UINT32 | N/A | The immutable expiration time for this payment channel, in seconds since the Ripple Epoch. | +| `SourceTag` | Yes | No | UINT32 | N/A | An arbitrary tag to further specify the source for this payment channel. | +| `DestinationTag` | Yes | No | UINT32 | N/A | An arbitrary tag to further specify the destination for this payment channel. | +| `TransferRate` | Yes | No | UINT32 | N/A | The transfer rate or fee at creation, used as an upper bound on the rate applied during claims. Only present when the rate at creation differs from parity. | +| `OwnerNode` | No | Yes | UINT64 | N/A | A hint indicating which page of the source address's owner directory links to this entry. | +| `DestinationNode` | No | No | UINT64 | N/A | A hint indicating which page of the destination's owner directory links to this entry. | +| `IssuerNode` | No | No | UINT64 | N/A | The ledger index of the issuer's directory node associated with the `PayChannel`. Only present for IOU channels where the issuer is neither the source nor the destination. | +| `PreviousTxnID` | No | Yes | HASH256 | N/A | The identifying hash of the transaction that most recently modified this entry. | +| `PreviousTxnLgrSeq` | No | Yes | UINT32 | N/A | The index of the ledger that contains the transaction that most recently modified this entry. | ### 3.2.2. Invariants @@ -120,9 +120,10 @@ The `PaymentChannel` ledger object is updated as follows: `Amount` and `Balance` Token-denominated payment channels reuse the `sfLockedAmount` field introduced by [XLS-85](../XLS-0085-token-escrow/README.md) on both the `MPToken` and `MPTokenIssuance` ledger objects: -| Field Name | JSON Type | Internal Type | Description | -| ---------------- | --------- | ------------- | ----------------------------------------------------------------------------------------- | -| `sfLockedAmount` | String | UInt64 | _(Optional)_ The total of all outstanding escrows and payment channels for this issuance. | +| Object | Field Name | JSON Type | Internal Type | Description | +| ----------------- | ---------------- | --------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MPToken` | `sfLockedAmount` | String | UInt64 | _(Optional)_ The total this holder has locked for this issuance: its outstanding escrows plus the unclaimed remainder of its payment channels. | +| `MPTokenIssuance` | `sfLockedAmount` | String | UInt64 | _(Optional)_ The total locked across all holders of this issuance: their outstanding escrows plus the unclaimed remainder of their payment channels. | ### 3.3.2. `AccountRoot` Ledger Object @@ -183,7 +184,7 @@ The `PaymentChannelCreate` transaction is modified as follows: - **IOU Tokens**: The channel `Amount` is deducted from the source's trustline balance. - **MPTs**: The channel `Amount` is deducted from the source's MPT balance. The `sfOutstandingAmount` of the MPT issuance remains unchanged. The `sfLockedAmount` is increased on both the source's MPT and the MPT issuance. - **Payment Channel Object Creation:** - - The `PaymentChannel` ledger object includes: + - The `PayChannel` ledger object includes: - `Amount`: Tokens held in the channel. - `Balance`: Amount already paid out (starts at zero). - `TransferRate`: `TransferRate` (IOUs) or `TransferFee` (MPTs) at creation. Only stored when it differs from parity (no fee). @@ -367,7 +368,7 @@ Closure returns the remaining channel funds (`Amount` minus `Balance`) to the so 3. The `OutstandingAmount` on the `MPTokenIssuance` is decreased by the claimed amount (i.e., this claim is a "redemption"). - If the destination is not the issuer of the asset held in the channel, then: 1. The `LockedAmount` on the `MPTokenIssuance` and the source's `MPToken` is decreased by the claimed amount. - 2. The `Amount` on the destination's `MPToken` is increased by the claimed amount, less any transfer fee. + 2. The `MPTAmount` on the destination's `MPToken` is increased by the claimed amount, less any transfer fee. 3. The `OutstandingAmount` on the `MPTokenIssuance` is decreased by the transfer fee, the claimed amount less the amount credited to the destination; the fee is credited to no holder, so it leaves the outstanding supply. - **Channel Balance Update:** - The channel's `Balance` field is updated to reflect the total amount claimed. @@ -382,7 +383,7 @@ Closure returns the remaining channel funds (`Amount` minus `Balance`) to the so - **IOU Tokens**: Any remaining channel funds are added to the source's trustline balance. - **MPTs**: Any remaining channel funds are added to the source's MPT balance. The `sfOutstandingAmount` of the MPT issuance remains unchanged. The `sfLockedAmount` is decreased on both the source's MPT and the MPT issuance. - **Deletion of Payment Channel Object:** - - The `PaymentChannel` object is deleted after successful closure. + - The `PayChannel` object is deleted after successful closure. ### 3.6.4. Example JSON @@ -419,7 +420,7 @@ Only the unclaimed remainder of the channel (`Amount` minus `Balance`) can be cl | Field | Required? | JSON Type | Internal Type | Default Value | Description | | ----------------- | --------- | ---------------- | ------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `TransactionType` | Yes | String | UInt16 | N/A | The transaction type, `PaymentChannelClawback` (`ttPAYCHAN_CLAWBACK`, value `94`). | -| `Channel` | Yes | String | Hash256 | N/A | The ID of the `PaymentChannel` to claw from. | +| `Channel` | Yes | String | Hash256 | N/A | The ID of the `PayChannel` to claw from. | | `Amount` | No | Object or String | Amount | N/A | The amount to claw back. Must be a positive, non-XRP amount of the channel's asset. If omitted, or if it is at least the unclaimed remainder, the entire remainder is clawed. | The transaction is not delegable under [XLS-75](../XLS-0075-permission-delegation/README.md): it is registered as `Delegation::NotDelegable`, so a delegate cannot submit it on the issuer's behalf, in line with XLS-75 Section 8 for new transaction types. @@ -441,7 +442,7 @@ This transaction uses the standard transaction fee (currently 10 drops, subject #### 3.7.3.2. Protocol-Level Failures - **Channel Does Not Exist:** - - If no `PaymentChannel` object matches `Channel`, the transaction fails with `tecNO_TARGET`. + - If no `PayChannel` object matches `Channel`, the transaction fails with `tecNO_TARGET`. - **Channel Holds XRP:** - XRP cannot be clawed back, so a channel denominated in XRP fails with `tecNO_PERMISSION`. @@ -606,7 +607,7 @@ The `channel_authorize` and `channel_verify` RPC methods take the authorized amo | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Trustlines** | Required between accounts and issuer | Not used | | **Issuer Flag for Channels** | `lsfAllowTrustLineLocking` (account flag) | `lsfMPTCanEscrow` (issuance flag) | -| **Transfer Flags** | N/A | `lsfMPTCanTransfer` must be enabled for payment channels | +| **Transfer Flags** | N/A | `lsfMPTCanTransfer` must be enabled for payment channels unless the destination is the issuer | | **Require Auth** | Applicable (`lsfRequireAuth`); accounts must be authorized prior to holding tokens | Applicable (`lsfMPTRequireAuth`); accounts must be authorized prior to holding tokens | | **Destination Authorization** | Required at creation and at claim unless the destination is the issuer; cannot be granted during claim if authorization required | Required at creation and at claim unless the destination is the issuer; cannot be granted during claim if authorization required | | **Freeze/Lock Conditions** | Any freeze blocks create/fund; **Deep Freeze** prevents claims, but allows closure; Global/Individual Freeze allows claims and closure | Lock blocks create/fund; **Lock Conditions (Deep Freeze Equivalent)** prevent claims, but allow closure | @@ -621,12 +622,12 @@ The `channel_authorize` and `channel_verify` RPC methods take the authorized amo ### 3.11.1. IOU Tokens (`TransferRate`) -- **Rate Capped at Creation**: The `TransferRate` is captured at the time of `PaymentChannelCreate` and stored in the `PaymentChannel` object. At claim time, the lower of the stored rate and the issuer's current rate is applied: an increase by the issuer does not affect existing channels, while a decrease passes through to claims. This is identical to the behavior of the activated XLS-85 (Token Escrow) implementation, which uses the same shared unlock logic. +- **Rate Capped at Creation**: The `TransferRate` is captured at the time of `PaymentChannelCreate` and stored in the `PayChannel` object. At claim time, the lower of the stored rate and the issuer's current rate is applied: an increase by the issuer does not affect existing channels, while a decrease passes through to claims. This is identical to the behavior of the activated XLS-85 (Token Escrow) implementation, which uses the same shared unlock logic. - **Fee Calculation**: The transfer fee is deducted from the claimed amount, reducing the final amount credited to the destination. No fee is applied when the issuer is the destination, or when remaining funds are returned to the source at closure. ### 3.11.2. MPTs (`TransferFee`) -- **Fee Capped at Creation**: The `TransferFee` is captured at the time of `PaymentChannelCreate` and stored in the `PaymentChannel` object, similar to IOUs, with the same lower-of-stored-and-current rule. +- **Fee Capped at Creation**: The `TransferFee` is captured at the time of `PaymentChannelCreate` and stored in the `PayChannel` object, similar to IOUs, with the same lower-of-stored-and-current rule. - **Fee Calculation**: The transfer fee is deducted from the claimed amount, reducing the final amount credited to the destination. - **Consistent Fee Application**: Both IOUs and MPTs use the same capped-rate rule, ensuring the destination's settlement value cannot be worsened by the issuer after channel creation. @@ -634,7 +635,7 @@ The `channel_authorize` and `channel_verify` RPC methods take the authorized amo 1. Issuer as Source: XLS-93 currently does not allow the issuer to be the source of the Payment Channel. If your use case requires this functionality, you should create a new account, send the MPT or IOU to that account, and then create the payment channel with that account as the source. -2. Trustline Deletion While Locked: because the locked IOU value lives in the `PaymentChannel` object rather than on the trustline, an empty trustline can be deleted while channels remain open (see Section 8). Per-trustline lock accounting that would prevent this, for both escrows and payment channels, is deliberately left to a separate future amendment so that XLS-93 stays behaviorally aligned with the activated XLS-85. +2. Trustline Deletion While Locked: because the locked IOU value lives in the `PayChannel` object rather than on the trustline, an empty trustline can be deleted while channels remain open (see Section 8). Per-trustline lock accounting that would prevent this, for both escrows and payment channels, is deliberately left to a separate future amendment so that XLS-93 stays behaviorally aligned with the activated XLS-85. ## 4. Rationale From 65c7ef06c525de75ca7f89a96dea0324056c1eb5 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 6 Oct 2026 12:21:56 -0400 Subject: [PATCH 14/19] docs: XLS-93 editor and review fixes --- XLS-0093-token-paychan/README.md | 688 ++++++++++++++++++------------- 1 file changed, 405 insertions(+), 283 deletions(-) diff --git a/XLS-0093-token-paychan/README.md b/XLS-0093-token-paychan/README.md index 1d1f484d6..2f3e3f37a 100644 --- a/XLS-0093-token-paychan/README.md +++ b/XLS-0093-token-paychan/README.md @@ -9,7 +9,7 @@ category: Amendment requires: XLS-33, XLS-39, XLS-85 created: 2025-05-24 - updated: 2026-10-05 + updated: 2026-10-06 # Token-Enabled Payment Channels @@ -26,7 +26,7 @@ Payment channels accept XRP only. XLS-85 extended escrows to IOUs and MPTs, so a ## 3. Specification -This amendment extends the functionality of payment channels to support both IOUs and MPTs, accounting for the specific behaviors and constraints associated with each token type. Token-denominated channels share their locking model with Token-Enabled Escrows (XLS-85): the same `lsfAllowTrustLineLocking` and `lsfMPTCanEscrow` issuer opt-in flags, and the same `sfLockedAmount` accounting fields. +This amendment extends the functionality of payment channels to support both IOUs and MPTs, accounting for the specific behaviors and constraints associated with each token type. Token-denominated channels share their locking model with Token-Enabled Escrows (XLS-85): the same `lsfAllowTrustLineLocking` and `lsfMPTCanEscrow` issuer opt-in flags, and the same `LockedAmount` accounting fields. ## 3.1. Overview of Token Types @@ -51,38 +51,29 @@ This amendment extends the functionality of payment channels to support both IOU ## 3.2. Ledger Entry: `PayChannel` -The `PayChannel` ledger object is updated as follows: `Amount` and `Balance` may hold a token, and the optional `TransferRate` and `IssuerNode` fields are added. Unchanged fields are included below so that the example is self-contained. +A [`PayChannel` ledger entry](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/paychannel) holds the funds of one payment channel. ### 3.2.1. Fields -| Field Name | Constant | Required | Internal Type | Default Value | Description | -| ------------------- | -------- | -------- | ------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `LedgerEntryType` | Yes | Yes | UINT16 | `0x0078` | Identifies this as a `PayChannel` object. | -| `Account` | Yes | Yes | ACCOUNT | N/A | The source address that owns this payment channel. | -| `Destination` | Yes | Yes | ACCOUNT | N/A | The destination address for this payment channel. While the channel is open, this address is the only one that can receive funds from the channel. | -| `Sequence` | Yes | No | UINT32 | N/A | The sequence number or ticket of the transaction that created the channel. | -| `Amount` | No | Yes | AMOUNT | N/A | The total amount allocated to the payment channel. Can represent XRP, an IOU token, or an MPT. Must always be a positive value. | -| `Balance` | No | Yes | AMOUNT | N/A | The amount already paid out from the channel. Same asset type as `Amount`. | -| `PublicKey` | Yes | Yes | BLOB | N/A | Public key of the key pair that can be used to sign claims against this channel. | -| `SettleDelay` | Yes | Yes | UINT32 | N/A | Number of seconds the source address must wait to close the channel if it still has funds in it. | -| `Expiration` | No | No | UINT32 | N/A | The mutable expiration time for this payment channel, in seconds since the Ripple Epoch. | -| `CancelAfter` | Yes | No | UINT32 | N/A | The immutable expiration time for this payment channel, in seconds since the Ripple Epoch. | -| `SourceTag` | Yes | No | UINT32 | N/A | An arbitrary tag to further specify the source for this payment channel. | -| `DestinationTag` | Yes | No | UINT32 | N/A | An arbitrary tag to further specify the destination for this payment channel. | -| `TransferRate` | Yes | No | UINT32 | N/A | The transfer rate or fee at creation, used as an upper bound on the rate applied during claims. Only present when the rate at creation differs from parity. | -| `OwnerNode` | No | Yes | UINT64 | N/A | A hint indicating which page of the source address's owner directory links to this entry. | -| `DestinationNode` | No | No | UINT64 | N/A | A hint indicating which page of the destination's owner directory links to this entry. | -| `IssuerNode` | No | No | UINT64 | N/A | The ledger index of the issuer's directory node associated with the `PayChannel`. Only present for IOU channels where the issuer is neither the source nor the destination. | -| `PreviousTxnID` | No | Yes | HASH256 | N/A | The identifying hash of the transaction that most recently modified this entry. | -| `PreviousTxnLgrSeq` | No | Yes | UINT32 | N/A | The index of the ledger that contains the transaction that most recently modified this entry. | +As a reference, [here](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/paychannel#paychannel-fields) are the fields that the `PayChannel` ledger entry currently has. + +This spec changes `Amount` and `Balance` and adds `TransferRate` and `IssuerNode`: + +| Field Name | Constant | Required | Internal Type | Default Value | Description | +| -------------- | -------- | -------- | ------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Amount` | No | Yes | AMOUNT | N/A | The total amount allocated to the payment channel. Can represent XRP, an IOU token, or an MPT. Must always be a positive value. | +| `Balance` | No | Yes | AMOUNT | N/A | The cumulative amount claimed from the channel, before transfer fees. Same asset as `Amount`. | +| `TransferRate` | Yes | No | UINT32 | N/A | The transfer rate or fee at creation, used as an upper bound on the rate applied during claims. Only present when the rate at creation differs from parity. | +| `IssuerNode` | No | No | UINT64 | N/A | The ledger index of the issuer's directory node associated with the `PayChannel`. Only present for IOU channels where the issuer is neither the source nor the destination. | ### 3.2.2. Invariants - `'.Balance` and `'.Amount` are the same asset, and that asset is the asset of `.Amount`: the asset of a channel never changes after creation. - `0 <= '.Balance <= '.Amount`. -- `.Balance <= '.Balance`: the paid-out balance only increases. +- `.Balance <= '.Balance` (the paid-out balance only increases). - `TransferRate` is present only if `Amount` is not XRP, and `'.TransferRate == .TransferRate`: it is set at creation and never updated. - `IssuerNode` is present only if `Amount` is an IOU whose issuer is neither `Account` nor `Destination`. +- If `Amount` is an IOU, the difference `'.Amount - '.Balance` is exact. An IOU difference `a - b` is exact only if adding `b` back to it gives `a` and subtracting it from `a` gives `b`. XRP and MPT differences are integers and always exact. ### 3.2.3. Example JSON @@ -114,34 +105,134 @@ The `PayChannel` ledger object is updated as follows: `Amount` and `Balance` may } ``` -## 3.3. Reused XLS-85 Fields and Flags +## 3.3. Ledger Entry: `MPToken` + +An [`MPToken` ledger entry](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/mptoken) records one account's holding of an MPT. + +### 3.3.1. Fields -### 3.3.1. `MPToken` and `MPTokenIssuance` Ledger Objects +As a reference, [here](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/mptoken#mptoken-fields) are the fields that the `MPToken` ledger entry currently has. -Token-denominated payment channels reuse the `sfLockedAmount` field introduced by [XLS-85](../XLS-0085-token-escrow/README.md) on both the `MPToken` and `MPTokenIssuance` ledger objects: +This spec adds no fields. It extends the meaning of the `LockedAmount` field introduced by [XLS-85](../XLS-0085-token-escrow/README.md) to cover payment channels: -| Object | Field Name | JSON Type | Internal Type | Description | -| ----------------- | ---------------- | --------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MPToken` | `sfLockedAmount` | String | UInt64 | _(Optional)_ The total this holder has locked for this issuance: its outstanding escrows plus the unclaimed remainder of its payment channels. | -| `MPTokenIssuance` | `sfLockedAmount` | String | UInt64 | _(Optional)_ The total locked across all holders of this issuance: their outstanding escrows plus the unclaimed remainder of their payment channels. | +| Field Name | Constant | Required | Internal Type | Default Value | Description | +| -------------- | -------- | -------- | ------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `LockedAmount` | No | No | UINT64 | `0` | The total this holder has locked for this issuance: its outstanding escrows plus the unclaimed remainder (`Amount` minus `Balance`) of its payment channels. | -### 3.3.2. `AccountRoot` Ledger Object +### 3.3.2. Invariants -No new flags are introduced. Token-denominated payment channels reuse the `lsfAllowTrustLineLocking` flag (`0x40000000`) introduced by [XLS-85](../XLS-0085-token-escrow/README.md): issuers who have enabled trust line locking for escrows have also enabled it for payment channels. See XLS-85 Section 1.6 for the corresponding `asfAllowTrustLineLocking` AccountSet flag. +Existing invariants remain; this spec adds none. Those that bound `LockedAmount` are: -## 3.4. Transaction: `PaymentChannelCreate` +- `0 <= '.MPTAmount` and `'.MPTAmount` does not exceed the maximum MPT amount. +- `0 <= '.LockedAmount` and `'.LockedAmount` does not exceed the maximum MPT amount. + +### 3.3.3. Example JSON + +```json +{ + "LedgerEntryType": "MPToken", + "Flags": 0, + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "MPTokenIssuanceID": "0000000CB5F762798A53D543A014CAF8B297CFF8F2F937E8", + "MPTAmount": "9000", + "LockedAmount": "1000", + "OwnerNode": "0000000000000000", + "PreviousTxnID": "CF5CFFCE70097D07B93A95E3462DDBC4C16033CF2462C1EBA5A29CEDF3E89DBC", + "PreviousTxnLgrSeq": 14661788 +} +``` -The `PaymentChannelCreate` transaction is modified as follows: +## 3.4. Ledger Entry: `MPTokenIssuance` + +An [`MPTokenIssuance` ledger entry](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/mptokenissuance) defines one MPT and its issuer controls. ### 3.4.1. Fields +As a reference, [here](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/mptokenissuance#mptokenissuance-fields) are the fields that the `MPTokenIssuance` ledger entry currently has. + +This spec adds no fields. It extends the meaning of the `LockedAmount` field introduced by [XLS-85](../XLS-0085-token-escrow/README.md) to cover payment channels: + +| Field Name | Constant | Required | Internal Type | Default Value | Description | +| -------------- | -------- | -------- | ------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------- | +| `LockedAmount` | No | No | UINT64 | `0` | The total locked across all holders of this issuance: their outstanding escrows plus the unclaimed remainder of channels. | + +### 3.4.2. Invariants + +Existing invariants remain; this spec adds none. Those that bound `LockedAmount` are: + +- `0 <= '.LockedAmount` and `'.LockedAmount` does not exceed the maximum MPT amount. +- `'.LockedAmount <= '.OutstandingAmount`. + +### 3.4.3. Example JSON + +```json +{ + "LedgerEntryType": "MPTokenIssuance", + "Flags": 104, + "Issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "Sequence": 12, + "OutstandingAmount": "100000", + "LockedAmount": "1000", + "OwnerNode": "0000000000000000", + "PreviousTxnID": "D4D5C79AC13DE6DD0CD1609FD34A40E0040C20D1B3A21C5F855D03320AB8174D", + "PreviousTxnLgrSeq": 14661788 +} +``` + +`Flags` is `lsfMPTCanEscrow | lsfMPTCanTransfer | lsfMPTCanClawback` (`0x08 | 0x20 | 0x40`). + +## 3.5. Ledger Entry: `AccountRoot` + +An [`AccountRoot` ledger entry](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/accountroot) describes a single account, its settings, and its XRP balance. + +### 3.5.1. Fields + +As a reference, [here](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/accountroot#accountroot-fields) are the fields that the `AccountRoot` ledger entry currently has. This spec adds and changes no fields. + +### 3.5.2. Flags + +This spec adds no flags. It extends the meaning of the `lsfAllowTrustLineLocking` flag introduced by [XLS-85](../XLS-0085-token-escrow/README.md) to cover payment channels: + +| Flag Name | Flag Value | Description | +| -------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `lsfAllowTrustLineLocking` | `0x40000000` | Holders of this issuer's IOUs may lock them in escrows and in payment channels. An issuer that enabled trust line locking for escrows has also enabled it here. | + +The issuer sets it with the `asfAllowTrustLineLocking` `AccountSet` flag, see XLS-85 Section 1.6. + +### 3.5.3. Invariants + +Existing invariants remain; this spec adds none. + +### 3.5.4. Example JSON + +```json +{ + "LedgerEntryType": "AccountRoot", + "Flags": 1073741824, + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "Balance": "100000000", + "OwnerCount": 0, + "Sequence": 30, + "PreviousTxnID": "F0AB71E777B2DA54B86231E19B82554EF1F8211F92ECA473121C655BFC5329BF", + "PreviousTxnLgrSeq": 14661788 +} +``` + +## 3.6. Transaction: `PaymentChannelCreate` + +The [`PaymentChannelCreate` transaction](https://xrpl.org/docs/references/protocol/transactions/types/paymentchannelcreate) is modified to accept a token `Amount`. + +### 3.6.1. Fields + +As a reference, [here](https://xrpl.org/docs/references/protocol/transactions/types/paymentchannelcreate#paymentchannelcreate-fields) are the fields that `PaymentChannelCreate` currently has. This spec changes `Amount`: + | Field | Required? | JSON Type | Internal Type | Default Value | Description | | -------- | --------- | ---------------- | ------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Amount` | Yes | Object or String | Amount | N/A | The amount to fund the payment channel. 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. | -### 3.4.2. Failure Conditions +### 3.6.2. Failure Conditions -#### 3.4.2.1. Data Verification +#### 3.6.2.1. Data Verification 1. `Amount` is a token and the `TokenPaychan` amendment is not enabled. (`temBAD_AMOUNT`) 2. `Amount` is an IOU that is not positive. (`temBAD_AMOUNT`) @@ -149,48 +240,51 @@ The `PaymentChannelCreate` transaction is modified as follows: 4. `Amount` is an MPT and the `MPTokensV1` amendment is not enabled. (`temDISABLED`) 5. `Amount` is an MPT that is not positive or exceeds the maximum MPT amount. (`temBAD_AMOUNT`) -#### 3.4.2.2. Protocol-Level Failures +#### 3.6.2.2. Protocol-Level Failures + +1. **Issuer is the Source:** + - If the source account is the issuer of the token, the transaction fails with `tecNO_PERMISSION`. -- **Issuer is the Source:** - - If the source account is the issuer of the token, the transaction fails with `tecNO_PERMISSION`. +2. **Issuer Does Not Allow Token Locking or Transfer:** + - **IOU Tokens**: If the issuer's account does not have the `lsfAllowTrustLineLocking` flag set, the transaction fails with `tecNO_PERMISSION`. If the issuer's account does not exist, the transaction fails with `tecNO_ISSUER`. + - **MPTs**: + - If the `MPTokenIssuance` of the token being used does not exist, the transaction fails with `tecOBJECT_NOT_FOUND`. + - If the `MPTokenIssuance` of the token being used lacks the `lsfMPTCanEscrow` flag, the transaction fails with `tecNO_PERMISSION`. + - If the `MPTokenIssuance` of the token being used lacks the `lsfMPTCanTransfer` flag, the transaction fails with `tecNO_AUTH` unless the destination address of the Payment Channel is the issuer of the MPT. -- **Issuer Does Not Allow Token Locking or Transfer:** - - **IOU Tokens**: If the issuer's account does not have the `lsfAllowTrustLineLocking` flag set, the transaction fails with `tecNO_PERMISSION`. If the issuer's account does not exist, the transaction fails with `tecNO_ISSUER`. - - **MPTs**: - - If the `MPTokenIssuance` of the token being used does not exist, the transaction fails with `tecOBJECT_NOT_FOUND`. - - If the `MPTokenIssuance` of the token being used lacks the `lsfMPTCanEscrow` flag, the transaction fails with `tecNO_PERMISSION`. - - If the `MPTokenIssuance` of the token being used lacks the `lsfMPTCanTransfer` flag, the transaction fails with `tecNO_AUTH` unless the destination address of the Payment Channel is the issuer of the MPT. +3. **Issuer is a Pseudo-Account (MPT only):** + - If `Amount` is an MPT whose issuer is a pseudo-account, such as a vault share, the transaction fails with `tecWRONG_ASSET`. This matches the rule `AMMCreate` applies to vault shares (see [Appendix A.4](#a4-can-vault-shares-be-locked-in-a-payment-channel)). -- **Source or Destination Not Authorized to Hold Token:** - - If the issuer requires authorization and either the source or the destination is not authorized, the transaction fails with `tecNO_AUTH`. - - The issuer is always authorized for its own token, so a destination that is the issuer never fails this check. +4. **Source or Destination Not Authorized to Hold Token:** + - If the issuer requires authorization and either the source or the destination is not authorized, the transaction fails with `tecNO_AUTH`. + - The issuer is always authorized for its own token, so a destination that is the issuer never fails this check. -- **Source Account's Token Holding Issues:** - - **IOU Tokens**: If the source lacks a trustline with the issuer, the transaction fails with `tecNO_LINE`. - - **MPTs**: If the source does not hold the MPT, the transaction fails with `tecOBJECT_NOT_FOUND`. +5. **Source Account's Token Holding Issues:** + - **IOU Tokens**: If the source lacks a trustline with the issuer, the transaction fails with `tecNO_LINE`. + - **MPTs**: If the source does not hold the MPT, the transaction fails with `tecOBJECT_NOT_FOUND`. -- **Source or Destination is Frozen or Token is Locked:** - - **IOU Tokens**: If the token is frozen (global/individual/deepfreeze) for the source or the destination, the transaction fails with `tecFROZEN`. - - **MPTs**: If the token is locked for the source or the destination, the transaction fails with `tecLOCKED`. - - Note: this is deliberately stricter than base freeze semantics, under which an individual freeze only prevents the frozen holder from sending. Opening a lock is blocked by any freeze on either party, while paying out to the destination at claim time only requires that the destination is not deep frozen (see Normal Claim). This matches the lock creation rules of [XLS-85](../XLS-0085-token-escrow/README.md). - - The destination MAY be the issuer of the token; claims on such a channel redeem tokens back to the issuer. Because the freeze check above applies to the destination unconditionally, no channel can be created while the issuer has a global freeze in effect, including a channel whose destination is the issuer. This is intentionally stricter than direct payments, which always permit redemption to the issuer. +6. **Source or Destination is Frozen or Token is Locked:** + - **IOU Tokens**: If the token is frozen (global/individual/deepfreeze) for the source or the destination, the transaction fails with `tecFROZEN`. + - **MPTs**: If the token is locked for the source or the destination, the transaction fails with `tecLOCKED`. + - Note: this is deliberately stricter than base freeze semantics, under which an individual freeze only prevents the frozen holder from sending. Opening a lock is blocked by any freeze on either party, while paying out to the destination at claim time only requires that the destination is not deep frozen (see [Claim with `Balance`](#38221-claim-with-balance)). This matches the lock creation rules of [XLS-85](../XLS-0085-token-escrow/README.md). + - The destination MAY be the issuer of the token; claims on such a channel redeem tokens back to the issuer. Because the freeze check above applies to the destination unconditionally, no channel can be created while the issuer has a global freeze in effect, including a channel whose destination is the issuer. This is intentionally stricter than direct payments, which always permit redemption to the issuer. -- **Insufficient Spendable Balance:** - - If the source account lacks sufficient spendable balance, the transaction fails with `tecINSUFFICIENT_FUNDS`. +7. **Insufficient Spendable Balance:** + - If the source account lacks sufficient spendable balance, the transaction fails with `tecINSUFFICIENT_FUNDS`. -### 3.4.3. State Changes +### 3.6.3. State Changes -- **Adjustment from Source to Issuer:** - - **IOU Tokens**: The channel `Amount` is deducted from the source's trustline balance. - - **MPTs**: The channel `Amount` is deducted from the source's MPT balance. The `sfOutstandingAmount` of the MPT issuance remains unchanged. The `sfLockedAmount` is increased on both the source's MPT and the MPT issuance. -- **Payment Channel Object Creation:** - - The `PayChannel` ledger object includes: - - `Amount`: Tokens held in the channel. - - `Balance`: Amount already paid out (starts at zero). - - `TransferRate`: `TransferRate` (IOUs) or `TransferFee` (MPTs) at creation. Only stored when it differs from parity (no fee). - - `IssuerNode`: Reference to the issuer's ledger node. Only present for IOU channels where the issuer is neither the source nor the destination. +1. **Adjustment from Source to Issuer:** + - **IOU Tokens**: The channel `Amount` is deducted from the source's trustline balance. + - **MPTs**: The channel `Amount` is deducted from the source's MPT balance. The `OutstandingAmount` of the MPT issuance remains unchanged. The `LockedAmount` is increased on both the source's `MPToken` and the `MPTokenIssuance`. +2. **Payment Channel Object Creation:** + - The `PayChannel` ledger object includes: + - `Amount`: Tokens held in the channel. + - `Balance`: Amount already claimed (starts at zero). + - `TransferRate`: `TransferRate` (IOUs) or `TransferFee` (MPTs) at creation. Only stored when it differs from parity (no fee). + - `IssuerNode`: Reference to the issuer's ledger node. Only present for IOU channels where the issuer is neither the source nor the destination. -### 3.4.4. Example JSON +### 3.6.4. Example JSON ```json { @@ -209,19 +303,21 @@ The `PaymentChannelCreate` transaction is modified as follows: } ``` -## 3.5. Transaction: `PaymentChannelFund` +## 3.7. Transaction: `PaymentChannelFund` -The `PaymentChannelFund` transaction is modified to support token amounts. +The [`PaymentChannelFund` transaction](https://xrpl.org/docs/references/protocol/transactions/types/paymentchannelfund) is modified to accept a token `Amount`. -### 3.5.1. Fields +### 3.7.1. Fields + +As a reference, [here](https://xrpl.org/docs/references/protocol/transactions/types/paymentchannelfund#paymentchannelfund-fields) are the fields that `PaymentChannelFund` currently has. This spec changes `Amount`: | Field | Required? | JSON Type | Internal Type | Default Value | Description | | -------- | --------- | ---------------- | ------------- | ------------- | -------------------------------------------------------------------------------------------------------- | | `Amount` | Yes | Object or String | Amount | N/A | The amount to add to the channel. Must be the same asset as the channel's `Amount` and a positive value. | -### 3.5.2. Failure Conditions +### 3.7.2. Failure Conditions -#### 3.5.2.1. Data Verification +#### 3.7.2.1. Data Verification 1. `Amount` is a token and the `TokenPaychan` amendment is not enabled. (`temBAD_AMOUNT`) 2. `Amount` is an IOU that is not positive. (`temBAD_AMOUNT`) @@ -229,25 +325,27 @@ The `PaymentChannelFund` transaction is modified to support token amounts. 4. `Amount` is an MPT and the `MPTokensV1` amendment is not enabled. (`temDISABLED`) 5. `Amount` is an MPT that is not positive or exceeds the maximum MPT amount. (`temBAD_AMOUNT`) -#### 3.5.2.2. Protocol-Level Failures +#### 3.7.2.2. Protocol-Level Failures -- **Asset Mismatch:** - - If the funding `Amount` is not the same asset as the channel's `Amount`, the transaction fails with `tecWRONG_ASSET`. +1. **Asset Mismatch:** + - If the funding `Amount` is not the same asset as the channel's `Amount`, the transaction fails with `tecWRONG_ASSET`. -- **Same conditions as `PaymentChannelCreate`** for validating the funding amount and token permissions (issuer opt-in, authorization, freeze/lock, transferability, spendable balance). +2. **Same conditions as `PaymentChannelCreate`** for validating the funding amount and token permissions (issuer opt-in, authorization, freeze/lock, transferability, spendable balance). -- **Inexact Sum:** - - The channel's new `Amount` must equal its old `Amount` plus the funding `Amount` exactly. An IOU sum is rounded to the mantissa width and is exact only if subtracting each operand from the sum gives back the other; an MPT sum is exact only if it does not overflow. Otherwise the transaction fails with `tecPRECISION_LOSS`. +3. **Inexact Sum or Remainder:** + - The channel's new `Amount` must equal its old `Amount` plus the funding `Amount` exactly. An IOU sum is rounded to the mantissa width and is exact only if subtracting each operand from the sum gives back the other; an MPT sum is exact only if it does not overflow. + - The remainder the channel holds after funding, the new `Amount` minus the channel's `Balance`, must also be exact, as defined in the `PayChannel` [Invariants](#322-invariants). + - Otherwise the transaction fails with `tecPRECISION_LOSS`. -### 3.5.3. State Changes +### 3.7.3. State Changes -- **Adjustment from Source:** - - **IOU Tokens**: The funding `Amount` is deducted from the source's trustline balance. - - **MPTs**: The funding `Amount` is deducted from the source's MPT balance. The `sfLockedAmount` is increased accordingly. -- **Payment Channel Object Update:** - - The channel's `Amount` field is increased by the funding amount. The stored `TransferRate` is not updated by funding. +1. **Adjustment from Source:** + - **IOU Tokens**: The funding `Amount` is deducted from the source's trustline balance. + - **MPTs**: The funding `Amount` is deducted from the source's MPT balance. The `LockedAmount` is increased accordingly. +2. **Payment Channel Object Update:** + - The channel's `Amount` field is increased by the funding amount. The stored `TransferRate` is not updated by funding. -### 3.5.4. Example JSON +### 3.7.4. Example JSON ```json { @@ -264,18 +362,21 @@ The `PaymentChannelFund` transaction is modified to support token amounts. } ``` -## 3.6. Transaction: `PaymentChannelClaim` +## 3.8. Transaction: `PaymentChannelClaim` -### 3.6.1. Fields +The [`PaymentChannelClaim` transaction](https://xrpl.org/docs/references/protocol/transactions/types/paymentchannelclaim) is modified to accept a token `Balance` and `Amount`. -| Field | Required? | JSON Type | Internal Type | Default Value | Description | -| ----------- | --------- | ---------------- | ------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Balance` | No | Object or String | Amount | N/A | The total amount delivered by this channel after processing this claim. Same asset as the channel's `Amount`. | -| `Amount` | No | Object or String | Amount | N/A | The amount authorized by the `Signature`. Same asset as the channel's `Amount`; must be at least `Balance`. | -| `Signature` | No | String | Blob | N/A | The signature of the authorization message in Claim Authorization, made with the key whose `PublicKey` is stored in the channel. Required unless the source submits. | -| `PublicKey` | No | String | Blob | N/A | The public key used for `Signature`; must match the channel's `PublicKey`. Required when `Signature` is present. | +### 3.8.1. Fields -#### 3.6.1.1. Claim Authorization +As a reference, [here](https://xrpl.org/docs/references/protocol/transactions/types/paymentchannelclaim#paymentchannelclaim-fields) are the fields that `PaymentChannelClaim` currently has. This spec changes `Balance`, `Amount` and the message `Signature` covers: + +| Field | Required? | JSON Type | Internal Type | Default Value | Description | +| ----------- | --------- | ---------------- | ------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Balance` | No | Object or String | Amount | N/A | The cumulative amount claimed from the channel after processing this claim, before transfer fees. Same asset as the channel's `Amount`. | +| `Amount` | No | Object or String | Amount | N/A | The amount authorized by the `Signature`. Same asset as the channel's `Amount`; must be at least `Balance`. | +| `Signature` | No | String | Blob | N/A | The signature of the message in [Signature](#3811-signature), made with the key whose `PublicKey` is stored in the channel. Required when the destination submits a claim with `Balance`. The source may omit it, and a claim without `Balance` needs none. | + +#### 3.8.1.1. Signature The `Signature` on a `PaymentChannelClaim` is over the following message, signed with the key whose `PublicKey` is stored in the channel. Fields are concatenated in order with no field headers or length prefixes; integers are big-endian. @@ -287,105 +388,109 @@ The `Signature` on a `PaymentChannelClaim` is over the following message, signed The message is 44 bytes for an XRP channel, 84 bytes for an IOU channel and 69 bytes for an MPT channel. The `MPTokenIssuanceID` already contains the issuer's `AccountID`, so nothing follows it. -### 3.6.2. Failure Conditions +### 3.8.2. Failure Conditions -#### 3.6.2.1. Data Verification +#### 3.8.2.1. Data Verification 1. `Balance` or `Amount` is a token and the `TokenPaychan` amendment is not enabled. (`temBAD_AMOUNT`) 2. `Balance` or `Amount` is an MPT and the `MPTokensV1` amendment is not enabled. (`temDISABLED`) 3. `Balance` and `Amount` are both present and are not the same asset. (`temBAD_AMOUNT`) -4. `Signature` does not verify against the message in Claim Authorization for the authorized amount (`Amount`, or `Balance` when `Amount` is absent). (`temBAD_SIGNATURE`) +4. `Signature` does not verify against the message in [Signature](#3811-signature) for the authorized amount (`Amount`, or `Balance` when `Amount` is absent). (`temBAD_SIGNATURE`) -#### 3.6.2.2. Protocol-Level Failures +#### 3.8.2.2. Protocol-Level Failures -**Normal Claim (Balance Update)**, when claiming without closing the channel: +##### 3.8.2.2.1. Claim with `Balance` -When the destination is the issuer of the channel's token, none of the authorization, holding, trustline limit or freeze conditions below apply: the issuer has no trustline to itself and holds no `MPToken`, and the claim redeems the tokens to the issuer (see State Changes). +These conditions apply to every claim that carries `Balance`, with or without `tfClose`. On a claim with `tfClose` they govern the payout to the destination; the conditions in [Channel Closure](#38222-channel-closure) govern only the refund of the remainder to the source. -- **Asset Mismatch:** - - If the claim's `Balance` or `Amount` is not the same asset as the channel's `Amount`, the transaction fails with `tecWRONG_ASSET`. +When the destination is the issuer of the channel's token, none of the authorization, holding, trustline limit or freeze conditions below apply: the issuer has no trustline to itself and holds no `MPToken`, and the claim redeems the tokens to the issuer (see [State Changes](#3831-claim-with-balance)). -- **Destination Not Authorized to Hold Token:** - - If authorization is required and the destination is not authorized, the transaction fails with `tecNO_AUTH`. +1. **Asset Mismatch:** + - If the claim's `Balance` or `Amount` is not the same asset as the channel's `Amount`, the transaction fails with `tecWRONG_ASSET`. -- **Destination Lacks Trustline or MPT Holding:** - - The destination's trustline or `MPToken` is created during the claim only when the destination itself submits the transaction (and authorization is not required). No other submitter can create a holding for the destination, since holding a token requires the holder's consent. - - **IOU Tokens**: If the destination lacks a trustline with the issuer and did not submit the transaction, the transaction fails with `tecNO_LINE`. - - **MPTs**: If the destination does not hold the MPT and did not submit the transaction, the transaction fails with `tecNO_PERMISSION`. +2. **Destination Not Authorized to Hold Token:** + - If authorization is required and the destination is not authorized, the transaction fails with `tecNO_AUTH`. -- **Cannot Create Trustline or MPT Holding:** - - If unable to create due to lack of reserves, the transaction fails with `tecNO_LINE_INSUF_RESERVE` (IOU) or `tecINSUFFICIENT_RESERVE` (MPT). +3. **Destination Lacks Trustline or MPT Holding:** + - The destination's trustline or `MPToken` is created during the claim only when the destination itself submits the transaction (and authorization is not required). No other submitter can create a holding for the destination, since holding a token requires the holder's consent. + - **IOU Tokens**: If the destination lacks a trustline with the issuer and did not submit the transaction, the transaction fails with `tecNO_LINE`. + - **MPTs**: If the destination does not hold the MPT and did not submit the transaction, the transaction fails with `tecNO_PERMISSION`. -- **Trustline Limit Exceeded (IOU only):** - - If the transaction is not submitted by the destination and the claimed amount would push the destination's trustline balance above its limit, the transaction fails with `tecLIMIT_EXCEEDED`. +4. **Cannot Create Trustline or MPT Holding:** + - If unable to create due to lack of reserves, the transaction fails with `tecNO_LINE_INSUF_RESERVE` (IOU) or `tecINSUFFICIENT_RESERVE` (MPT). -- **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)**: The transaction fails with `tecLOCKED`. +5. **Trustline Limit Exceeded (IOU only):** + - If the transaction is not submitted by the destination and the claimed amount would push the destination's trustline balance above its limit, the transaction fails with `tecLIMIT_EXCEEDED`. -**Channel Closure** +6. **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)**: The transaction fails with `tecLOCKED`. -A channel closes when a claim carries the `tfClose` flag (immediately if the requester is the destination or the channel is fully drained; otherwise an expiration is scheduled per `SettleDelay`), or when any claim is processed against an already-expired channel. +7. **Inexact Payout or Remainder (IOU only):** + - After the existing checks that `Balance` is above the channel's `Balance` and at most its `Amount` (`tecUNFUNDED_PAYMENT`), the amount this claim pays (`Balance` minus the channel's current `Balance`) and the remainder it leaves in the channel (the channel's `Amount` minus `Balance`) must both be exact, as defined in the `PayChannel` [Invariants](#322-invariants). Otherwise the transaction fails with `tecPRECISION_LOSS`. -Closure returns the remaining channel funds (`Amount` minus `Balance`) to the source. **The failure conditions below apply only when this remainder is positive.** A fully drained channel has nothing to refund, so it closes without any source-side checks; the destination's ability to claim earned funds is never gated by the source's authorization, trustline, or freeze state. +##### 3.8.2.2.2. Channel Closure -**Failure Conditions (positive remainder only):** - -- **Source Not Authorized to Hold Token:** - - If authorization is required and the source is not authorized, the transaction fails with `tecNO_AUTH`. - -- **Source Lacks Trustline or MPT Holding:** - - The source's trustline or `MPToken` is created during closure only when the source itself submits the transaction (and authorization is not required), for the same reason as on a claim. - - **IOU Tokens**: If the source lacks a trustline with the issuer and did not submit the transaction, the transaction fails with `tecNO_LINE`. - - **MPTs**: If the source does not hold the MPT and did not submit the transaction, the transaction fails with `tecNO_PERMISSION`. - -- **Cannot Create Trustline or MPT Holding:** - - If unable to create due to lack of reserves, the transaction fails with `tecNO_LINE_INSUF_RESERVE` (IOU) or `tecINSUFFICIENT_RESERVE` (MPT). - -- **Source Account is Frozen or Token is Locked:** - - **IOU Tokens**: - - **Deep Freeze**: The transaction succeeds, allowing the channel to be closed. - - **Global/Individual Freeze**: The transaction succeeds, allowing the channel to be closed. - - **MPTs**: - - **Lock Conditions (Deep Freeze Equivalent)**: The transaction succeeds, allowing the channel to be closed. - -### 3.6.3. State Changes +A channel closes when a claim carries the `tfClose` flag (immediately if the requester is the destination or the channel is fully drained; otherwise an expiration is scheduled per `SettleDelay`), or when any claim is processed against an already-expired channel. -**Normal Claim (Balance Update)** - -- **Auto create Trustline or MPToken:** - - **IOU Tokens**: If the IOU does not require authorization and the account submitting the transaction is the destination, a trustline is created for it. - - **MPTs**: If the MPT does not require authorization and the account submitting the transaction is the destination, an `MPToken` is created for it. -- **Adjustment from Issuer to Destination:** - - **IOU Tokens**: The claimed amount, less any transfer fee (see Section 3.11), is added to the destination's trustline balance. If the destination is the issuer, the claimed amount is simply redeemed. - - **MPTs**: - - If the destination is the issuer of the asset held in the channel, then: - 1. The `LockedAmount` on the `MPTokenIssuance` and the source's `MPToken` is decreased by the claimed amount. - 2. No destination `MPToken` object is changed because MPT issuers may not hold MPTokens. - 3. The `OutstandingAmount` on the `MPTokenIssuance` is decreased by the claimed amount (i.e., this claim is a "redemption"). - - If the destination is not the issuer of the asset held in the channel, then: - 1. The `LockedAmount` on the `MPTokenIssuance` and the source's `MPToken` is decreased by the claimed amount. - 2. The `MPTAmount` on the destination's `MPToken` is increased by the claimed amount, less any transfer fee. - 3. The `OutstandingAmount` on the `MPTokenIssuance` is decreased by the transfer fee, the claimed amount less the amount credited to the destination; the fee is credited to no holder, so it leaves the outstanding supply. -- **Channel Balance Update:** - - The channel's `Balance` field is updated to reflect the total amount claimed. - -**Channel Closure** - -- **Auto create Trustline or MPToken:** - - **IOU Tokens**: If the IOU does not require authorization and the account submitting the transaction is the source, a trustline is created for it. - - **MPTs**: If the MPT does not require authorization and the account submitting the transaction is the source, an `MPToken` is created for it. -- **Adjustment from Issuer to Source:** - - No transfer fee is applied when returning remaining funds to the source. - - **IOU Tokens**: Any remaining channel funds are added to the source's trustline balance. - - **MPTs**: Any remaining channel funds are added to the source's MPT balance. The `sfOutstandingAmount` of the MPT issuance remains unchanged. The `sfLockedAmount` is decreased on both the source's MPT and the MPT issuance. -- **Deletion of Payment Channel Object:** - - The `PayChannel` object is deleted after successful closure. +Closure returns the remaining channel funds (`Amount` minus `Balance`) to the source. **The failure conditions below apply only when this remainder is positive.** A fully drained channel has nothing to refund, so it closes without any source-side checks; the destination's ability to claim earned funds is never gated by the source's authorization, trustline, or freeze state. -### 3.6.4. Example JSON +1. **Source Not Authorized to Hold Token:** + - If authorization is required and the source is not authorized, the transaction fails with `tecNO_AUTH`. + +2. **Source Lacks Trustline or MPT Holding:** + - The source's trustline or `MPToken` is created during closure only when the source itself submits the transaction (and authorization is not required), for the same reason as on a claim. + - **IOU Tokens**: If the source lacks a trustline with the issuer and did not submit the transaction, the transaction fails with `tecNO_LINE`. + - **MPTs**: If the source does not hold the MPT and did not submit the transaction, the transaction fails with `tecNO_PERMISSION`. + +3. **Cannot Create Trustline or MPT Holding:** + - If unable to create due to lack of reserves, the transaction fails with `tecNO_LINE_INSUF_RESERVE` (IOU) or `tecINSUFFICIENT_RESERVE` (MPT). + +4. **Source Account is Frozen or Token is Locked:** + - **IOU Tokens**: + - **Deep Freeze**: The transaction succeeds, allowing the channel to be closed. + - **Global/Individual Freeze**: The transaction succeeds, allowing the channel to be closed. + - **MPTs**: + - **Lock Conditions (Deep Freeze Equivalent)**: The transaction succeeds, allowing the channel to be closed. + +### 3.8.3. State Changes + +#### 3.8.3.1. Claim with `Balance` + +1. **Auto create Trustline or MPToken:** + - **IOU Tokens**: If the IOU does not require authorization and the account submitting the transaction is the destination, a trustline is created for it. + - **MPTs**: If the MPT does not require authorization and the account submitting the transaction is the destination, an `MPToken` is created for it. +2. **Adjustment from Issuer to Destination:** + - **IOU Tokens**: The claimed amount, less any transfer fee (see [Transfer Rates and Fees](#313-transfer-rates-and-fees)), is added to the destination's trustline balance. If the destination is the issuer, the claimed amount is simply redeemed. + - **MPTs**: + - If the destination is the issuer of the asset held in the channel, then: + 1. The `LockedAmount` on the `MPTokenIssuance` and the source's `MPToken` is decreased by the claimed amount. + 2. No destination `MPToken` object is changed because MPT issuers may not hold MPTokens. + 3. The `OutstandingAmount` on the `MPTokenIssuance` is decreased by the claimed amount (i.e., this claim is a "redemption"). + - If the destination is not the issuer of the asset held in the channel, then: + 1. The `LockedAmount` on the `MPTokenIssuance` and the source's `MPToken` is decreased by the claimed amount. + 2. The `MPTAmount` on the destination's `MPToken` is increased by the claimed amount, less any transfer fee. + 3. The `OutstandingAmount` on the `MPTokenIssuance` is decreased by the transfer fee, the claimed amount less the amount credited to the destination; the fee is credited to no holder, so it leaves the outstanding supply. +3. **Channel Balance Update:** + - The channel's `Balance` field is set to the claim's `Balance`, the cumulative amount claimed before transfer fees. + +#### 3.8.3.2. Channel Closure + +1. **Auto create Trustline or MPToken:** + - **IOU Tokens**: If the IOU does not require authorization and the account submitting the transaction is the source, a trustline is created for it. + - **MPTs**: If the MPT does not require authorization and the account submitting the transaction is the source, an `MPToken` is created for it. +2. **Adjustment from Issuer to Source:** + - No transfer fee is applied when returning remaining funds to the source. + - The refund, `Amount` minus `Balance`, is exact by construction: every transaction that changes `Amount` or `Balance` keeps that difference exact (see the `PayChannel` [Invariants](#322-invariants)), so closure needs no precision check. + - **IOU Tokens**: Any remaining channel funds are added to the source's trustline balance. + - **MPTs**: Any remaining channel funds are added to the source's MPT balance. The `OutstandingAmount` of the MPT issuance remains unchanged. The `LockedAmount` is decreased on both the source's `MPToken` and the `MPTokenIssuance`. +3. **Deletion of Payment Channel Object:** + - The `PayChannel` object is deleted after successful closure. + +### 3.8.4. Example JSON ```json { @@ -409,13 +514,13 @@ Closure returns the remaining channel funds (`Amount` minus `Balance`) to the so } ``` -## 3.7. Transaction: `PaymentChannelClawback` +## 3.9. Transaction: `PaymentChannelClawback` Locking a token into a channel moves it out of reach of the ordinary `Clawback` transaction (for an MPT, `Clawback` with the `MPTokenHolder` field defined in [XLS-33](../XLS-0033-multi-purpose-tokens/README.md)), which is bounded by the holder's spendable balance and so cannot see locked value. `PaymentChannelClawback` gives the issuer that reach back. It requires the same opt-in the issuer already needed to claw back an ordinary holding, so it grants no new authority over a token; it removes a place the token could be kept out of reach. Only the unclaimed remainder of the channel (`Amount` minus `Balance`) can be clawed. The destination's earned `Balance` is never touched, so a clawback cannot reverse value the payee has already claimed. -### 3.7.1. Fields +### 3.9.1. Fields | Field | Required? | JSON Type | Internal Type | Default Value | Description | | ----------------- | --------- | ---------------- | ------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -425,62 +530,62 @@ Only the unclaimed remainder of the channel (`Amount` minus `Balance`) can be cl The transaction is not delegable under [XLS-75](../XLS-0075-permission-delegation/README.md): it is registered as `Delegation::NotDelegable`, so a delegate cannot submit it on the issuer's behalf, in line with XLS-75 Section 8 for new transaction types. -### 3.7.2. Transaction Fee +### 3.9.2. Transaction Fee **Fee Structure:** Standard This transaction uses the standard transaction fee (currently 10 drops, subject to Fee Voting changes). -### 3.7.3. Failure Conditions +### 3.9.3. Failure Conditions -#### 3.7.3.1. Data Verification +#### 3.9.3.1. Data Verification -- **Malformed `Amount`:** - - If `Amount` is present and is XRP or is not positive, the transaction fails with `temBAD_AMOUNT`. An MPT amount above the maximum MPT value fails the same way. - - If `Amount` is present and names XRP as an IOU currency code, the transaction fails with `temBAD_CURRENCY`. +1. **Malformed `Amount`:** + - If `Amount` is present and is XRP or is not positive, the transaction fails with `temBAD_AMOUNT`. An MPT amount above the maximum MPT value fails the same way. + - If `Amount` is present and names XRP as an IOU currency code, the transaction fails with `temBAD_CURRENCY`. -#### 3.7.3.2. Protocol-Level Failures +#### 3.9.3.2. Protocol-Level Failures -- **Channel Does Not Exist:** - - If no `PayChannel` object matches `Channel`, the transaction fails with `tecNO_TARGET`. +1. **Channel Does Not Exist:** + - If no `PayChannel` object matches `Channel`, the transaction fails with `tecNO_TARGET`. -- **Channel Holds XRP:** - - XRP cannot be clawed back, so a channel denominated in XRP fails with `tecNO_PERMISSION`. +2. **Channel Holds XRP:** + - XRP cannot be clawed back, so a channel denominated in XRP fails with `tecNO_PERMISSION`. -- **Submitter Is Not the Issuer:** - - If the submitting account is not the issuer of the channel's asset, the transaction fails with `tecNO_PERMISSION`. +3. **Submitter Is Not the Issuer:** + - If the submitting account is not the issuer of the channel's asset, the transaction fails with `tecNO_PERMISSION`. -- **Asset Mismatch:** - - If `Amount` is present and is not the same asset as the channel's `Amount`, the transaction fails with `tecWRONG_ASSET`. +4. **Asset Mismatch:** + - If `Amount` is present and is not the same asset as the channel's `Amount`, the transaction fails with `tecWRONG_ASSET`. -- **Inexact Difference (partial clawback only):** - - For a partial clawback the channel's new `Amount` must equal its old `Amount` minus the clawed `Amount` exactly. If the IOU subtraction rounds, so that the stored decrease differs from the amount clawed (including a decrease rounded away entirely), the transaction fails with `tecPRECISION_LOSS`. A full clawback is not subject to this check: it sets `Amount` equal to `Balance` directly and claws the exact remainder (see State Changes). +5. **Inexact Difference (partial clawback only):** + - For a partial clawback the channel's new `Amount` must equal its old `Amount` minus the clawed `Amount` exactly, and the remainder it leaves, the new `Amount` minus the channel's `Balance`, must also be exact, as defined in the `PayChannel` [Invariants](#322-invariants). If the first IOU subtraction rounds, the stored decrease differs from the amount clawed (including a decrease rounded away entirely); if the second rounds, the remainder is not exact. In either case the transaction fails with `tecPRECISION_LOSS`. A full clawback is not subject to this check: it sets `Amount` equal to `Balance` directly and claws the exact remainder (see State Changes). -- **Issuer Does Not Allow Clawback:** - - **IOU Tokens**: If the issuer's account lacks the `lsfAllowTrustLineClawback` flag, or has the `lsfNoFreeze` flag set, the transaction fails with `tecNO_PERMISSION`. These are the same conditions that gate the [XLS-39](../XLS-0039-clawback/README.md) `Clawback` transaction. - - **MPTs**: If the `MPTokenIssuance` lacks the `lsfMPTCanClawback` flag, the transaction fails with `tecNO_PERMISSION`. If the `MPTokenIssuance` does not exist, the transaction fails with `tecOBJECT_NOT_FOUND`. +6. **Issuer Does Not Allow Clawback:** + - **IOU Tokens**: If the issuer's account lacks the `lsfAllowTrustLineClawback` flag, or has the `lsfNoFreeze` flag set, the transaction fails with `tecNO_PERMISSION`. These are the same conditions that gate the [XLS-39](../XLS-0039-clawback/README.md) `Clawback` transaction. + - **MPTs**: If the `MPTokenIssuance` lacks the `lsfMPTCanClawback` flag, the transaction fails with `tecNO_PERMISSION`. If the `MPTokenIssuance` does not exist, the transaction fails with `tecOBJECT_NOT_FOUND`. -### 3.7.4. State Changes +### 3.9.4. State Changes -- **Nothing to Claw:** - - If the channel's `Balance` already equals its `Amount`, there is no remainder and the transaction succeeds without changing the channel. A claim that draws the channel down to its full `Amount` without `tfClose` leaves the channel open in exactly this state. +1. **Nothing to Claw:** + - If the channel's `Balance` already equals its `Amount`, there is no remainder and the transaction succeeds without changing the channel. A claim that draws the channel down to its full `Amount` without `tfClose` leaves the channel open in exactly this state. -- **Adjustment to the Issuer:** - - No transfer fee is applied. A clawback is a redemption rather than a transfer between holders. - - **IOU Tokens**: No trustline is modified. The locked value was already removed from the source's trustline balance when the channel was created or funded, so retiring the channel's obligation is the whole of the clawback. - - **MPTs**: The clawed amount is deducted from the `sfLockedAmount` on both the source's `MPToken` and the `MPTokenIssuance`, and from the `sfOutstandingAmount` on the `MPTokenIssuance`. No `MPToken` is created for the issuer, since MPT issuers do not hold their own `MPToken`. +2. **Adjustment to the Issuer:** + - No transfer fee is applied. A clawback is a redemption rather than a transfer between holders. + - **IOU Tokens**: No trustline is modified. The locked value was already removed from the source's trustline balance when the channel was created or funded, so retiring the channel's obligation is the whole of the clawback. + - **MPTs**: The clawed amount is deducted from the `LockedAmount` on both the source's `MPToken` and the `MPTokenIssuance`, and from the `OutstandingAmount` on the `MPTokenIssuance`. No `MPToken` is created for the issuer, since MPT issuers do not hold their own `MPToken`. -- **Payment Channel Object Update:** - - For a partial clawback, the channel's `Amount` is reduced by the clawed amount and the channel remains open. - - For a full clawback, the channel's `Amount` is set equal to its `Balance`, leaving no remainder, and the channel is closed and deleted as described in Channel Closure. Because the remainder is zero, no refund is made to the source and no source-side authorization, holding, or freeze condition applies. +3. **Payment Channel Object Update:** + - For a partial clawback, the channel's `Amount` is reduced by the clawed amount and the channel remains open. + - For a full clawback, the channel's `Amount` is set equal to its `Balance`, leaving no remainder, and the channel is closed and deleted as described in [Channel Closure](#3832-channel-closure). Because the remainder is zero, no refund is made to the source and no source-side authorization, holding, or freeze condition applies. -**Effect on Outstanding Claims:** +#### 3.9.4.1. Effect on Outstanding Claims -A clawback lowers the channel's `Amount`, which is the ceiling on what the destination can claim. Any authorization the source has already signed for a balance above the new `Amount` becomes unusable, and a claim presenting it fails with `tecUNFUNDED_PAYMENT`; the destination needs a fresh signature from the source for a balance at or below the new `Amount`. This is a consequence of the issuer's authority over the token rather than of the channel mechanics, and it mirrors what an ordinary clawback does to a holder's pending obligations. +A clawback lowers the channel's `Amount`, which is the ceiling on the `Balance` a claim can request. A claim whose requested `Balance` exceeds the new `Amount` fails with `tecUNFUNDED_PAYMENT`. The signed `Amount` only has to be at least the requested `Balance`, so an authorization the source signed for more than the new `Amount` still pays out any `Balance` up to the new `Amount` without a fresh signature; only the part of the authorization above the new `Amount` can no longer be claimed. This is a consequence of the issuer's authority over the token rather than of the channel mechanics, and it mirrors what an ordinary clawback does to a holder's pending obligations. An expired channel can still be clawed. Expiry entitles the source to a refund but does not perform one until some account submits a transaction against the channel, so an issuer clawback and the source's refund race for the remainder. -### 3.7.5. Example JSON +### 3.9.5. Example JSON ```json { @@ -497,35 +602,29 @@ An expired channel can still be clawed. Expiry entitles the source to a refund b } ``` -## 3.8. RPC: `channel_authorize` +## 3.10. RPC: `channel_authorize` + +The [`channel_authorize` method](https://xrpl.org/docs/references/http-websocket-apis/admin-api-methods/signing-methods/channel_authorize) is modified to accept a token `amount`. For an XRP channel `amount` is still a string of drops. For a token channel it is the same JSON object used for a transaction `Amount` (`currency`, `issuer` and `value` for an IOU; `mpt_issuance_id` and `value` for an MPT), and the server builds the message in [Signature](#3811-signature) from it. + +### 3.10.1. Request Fields -The `channel_authorize` and `channel_verify` RPC methods take the authorized amount in `amount`. For an XRP channel it is a string of drops. For a token channel it is the same JSON object used for a transaction `Amount` (`currency`, `issuer` and `value` for an IOU; `mpt_issuance_id` and `value` for an MPT), and the server builds the message in Claim Authorization from it. +As a reference, [here](https://xrpl.org/docs/references/http-websocket-apis/admin-api-methods/signing-methods/channel_authorize#request-format) are the request fields that `channel_authorize` currently has. This spec changes `amount`: -### 3.8.1. Request Fields +| Field Name | Required? | JSON Type | Description | +| ---------- | --------- | ---------------- | ------------------------------------------------------------------------------------------------------- | +| `amount` | Yes | string or object | The authorized amount: a string of drops for an XRP channel, or an `Amount` object for a token channel. | -| Field Name | Required? | JSON Type | Description | -| ------------ | --------- | ---------------- | --------------------------------------------------------------------------------------------------------------------- | -| `command` | Yes | string | Must be `"channel_authorize"` | -| `channel_id` | Yes | string | The 256-bit channel ID, in hexadecimal. | -| `amount` | Yes | string or object | The authorized amount: a string of drops for an XRP channel, or an `Amount` object for a token channel. | -| `secret` | No | string | The secret key of the channel's `PublicKey`. Exactly one of `secret`, `seed`, `seed_hex` or `passphrase` is required. | -| `key_type` | No | string | The signing algorithm of the key, `secp256k1` or `ed25519`. | +### 3.10.2. Response Fields -### 3.8.2. Response Fields +The response fields are [unchanged](https://xrpl.org/docs/references/http-websocket-apis/admin-api-methods/signing-methods/channel_authorize#response-format). For a token channel, `signature` is over the token message in [Signature](#3811-signature). -| Field Name | Always Present? | JSON Type | Description | -| ----------- | --------------- | --------- | ----------------------------------------------------------------- | -| `status` | Yes | string | `"success"` if the request succeeded | -| `signature` | Yes | string | The signature of the Claim Authorization message, in hexadecimal. | +### 3.10.3. Failure Conditions -### 3.8.3. Failure Conditions +The existing failure conditions are unchanged. This spec extends one: -1. Signing is not supported by this server. (`notSupported`) -2. `channel_id` or `amount` is missing, or no signing key field is present. (`invalidParams`) -3. `channel_id` is not a 256-bit hexadecimal string. (`channelMalformed`) -4. `amount` is neither a string of drops nor an `Amount` object for a token amount that is not negative. (`channelAmtMalformed`) +1. `amount` is neither a string of drops nor an `Amount` object for a token amount that is not negative. (`channelAmtMalformed`) -### 3.8.4. Example Request +### 3.10.4. Example Request ```json { @@ -541,7 +640,7 @@ The `channel_authorize` and `channel_verify` RPC methods take the authorized amo } ``` -### 3.8.5. Example Response +### 3.10.5. Example Response ```json { @@ -550,33 +649,29 @@ The `channel_authorize` and `channel_verify` RPC methods take the authorized amo } ``` -## 3.9. RPC: `channel_verify` +## 3.11. RPC: `channel_verify` -### 3.9.1. Request Fields +The [`channel_verify` method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods/payment-channel-methods/channel_verify) is modified to accept a token `amount`, in the same form as for `channel_authorize`. -| Field Name | Required? | JSON Type | Description | -| ------------ | --------- | ---------------- | ------------------------------------------------------------------- | -| `command` | Yes | string | Must be `"channel_verify"` | -| `channel_id` | Yes | string | The 256-bit channel ID, in hexadecimal. | -| `amount` | Yes | string or object | The authorized amount, in the same form as for `channel_authorize`. | -| `public_key` | Yes | string | The channel's `PublicKey`, in hexadecimal or base58. | -| `signature` | Yes | string | The signature to verify, in hexadecimal. | +### 3.11.1. Request Fields -### 3.9.2. Response Fields +As a reference, [here](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods/payment-channel-methods/channel_verify#request-format) are the request fields that `channel_verify` currently has. This spec changes `amount`: -| Field Name | Always Present? | JSON Type | Description | -| -------------------- | --------------- | --------- | ------------------------------------------------------------------------------------------------------ | -| `status` | Yes | string | `"success"` if the request succeeded | -| `signature_verified` | Yes | boolean | Whether `signature` is valid for the Claim Authorization message built from `channel_id` and `amount`. | +| Field Name | Required? | JSON Type | Description | +| ---------- | --------- | ---------------- | ------------------------------------------------------------------- | +| `amount` | Yes | string or object | The authorized amount, in the same form as for `channel_authorize`. | -### 3.9.3. Failure Conditions +### 3.11.2. Response Fields + +The response fields are [unchanged](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods/payment-channel-methods/channel_verify#response-format). For a token channel, `signature_verified` reports whether `signature` is valid for the token message in [Signature](#3811-signature) built from `channel_id` and `amount`. + +### 3.11.3. Failure Conditions -1. `public_key`, `channel_id`, `amount` or `signature` is missing, or `signature` is not a non-empty hexadecimal string. (`invalidParams`) -2. `public_key` is not a valid public key. (`publicMalformed`) -3. `channel_id` is not a 256-bit hexadecimal string. (`channelMalformed`) -4. `amount` is neither a string of drops nor an `Amount` object for a token amount that is not negative. (`channelAmtMalformed`) +The existing failure conditions are unchanged. This spec extends one: -### 3.9.4. Example Request +1. `amount` is neither a string of drops nor an `Amount` object for a token amount that is not negative. (`channelAmtMalformed`) + +### 3.11.4. Example Request ```json { @@ -592,7 +687,7 @@ The `channel_authorize` and `channel_verify` RPC methods take the authorized amo } ``` -### 3.9.5. Example Response +### 3.11.5. Example Response ```json { @@ -601,37 +696,39 @@ The `channel_authorize` and `channel_verify` RPC methods take the authorized amo } ``` -## 3.10. Key Differences Between IOU and MPT Payment Channels - -| Aspect | IOU Tokens | Multi-Purpose Tokens (MPTs) | -| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Trustlines** | Required between accounts and issuer | Not used | -| **Issuer Flag for Channels** | `lsfAllowTrustLineLocking` (account flag) | `lsfMPTCanEscrow` (issuance flag) | -| **Transfer Flags** | N/A | `lsfMPTCanTransfer` must be enabled for payment channels unless the destination is the issuer | -| **Require Auth** | Applicable (`lsfRequireAuth`); accounts must be authorized prior to holding tokens | Applicable (`lsfMPTRequireAuth`); accounts must be authorized prior to holding tokens | -| **Destination Authorization** | Required at creation and at claim unless the destination is the issuer; cannot be granted during claim if authorization required | Required at creation and at claim unless the destination is the issuer; cannot be granted during claim if authorization required | -| **Freeze/Lock Conditions** | Any freeze blocks create/fund; **Deep Freeze** prevents claims, but allows closure; Global/Individual Freeze allows claims and closure | Lock blocks create/fund; **Lock Conditions (Deep Freeze Equivalent)** prevent claims, but allow closure | -| **Transfer Rates/Fees** | `TransferRate` stored at creation and applied during claims | `TransferFee` stored at creation and applied during claims | -| **Clawback Opt-In** | `lsfAllowTrustLineClawback` (account flag), and `lsfNoFreeze` must not be set | `lsfMPTCanClawback` (issuance flag) | -| **Clawback Accounting** | Channel `Amount` is reduced; no trustline changes | `sfLockedAmount` and `sfOutstandingAmount` are reduced | -| **Outstanding Amount** | N/A | Unchanged by create, fund and closure refund; decreased by the transfer fee on a claim, by the claimed amount on a claim to the issuer, and by the clawed amount on clawback | -| **Account Deletion** | Payment channels prevent account deletion | Payment channels prevent account deletion | -| **Holding Deletion** | Trustline deletion is NOT blocked by open channels (locked value lives in the channel object); closure refund then fails with `tecNO_LINE` until the line is re-established | `MPToken` deletion is blocked while `sfLockedAmount` is non-zero (`tecHAS_OBLIGATIONS`) | - -## 3.11. Transfer Rates and Fees - -### 3.11.1. IOU Tokens (`TransferRate`) +## 3.12. Key Differences Between XRP, IOU and MPT Payment Channels + +| Aspect | XRP | IOU Tokens | Multi-Purpose Tokens (MPTs) | +| ----------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Trustlines** | Not used | Required between accounts and issuer | Not used | +| **Issuer Flag for Channels** | N/A | `lsfAllowTrustLineLocking` (account flag) | `lsfMPTCanEscrow` (issuance flag) | +| **Transfer Flags** | N/A | N/A | `lsfMPTCanTransfer` must be enabled for payment channels unless the destination is the issuer | +| **Require Auth** | N/A | Applicable (`lsfRequireAuth`); accounts must be authorized prior to holding tokens | Applicable (`lsfMPTRequireAuth`); accounts must be authorized prior to holding tokens | +| **Destination Authorization** | N/A | Required at creation and at claim unless the destination is the issuer; cannot be granted during claim if authorization required | Required at creation and at claim unless the destination is the issuer; cannot be granted during claim if authorization required | +| **Freeze/Lock Conditions** | N/A | Any freeze blocks create/fund; **Deep Freeze** prevents claims, but allows closure; Global/Individual Freeze allows claims and closure | Lock blocks create/fund; **Lock Conditions (Deep Freeze Equivalent)** prevent claims, but allow closure | +| **Transfer Rates/Fees** | N/A | `TransferRate` stored at creation and applied during claims | `TransferFee` stored at creation and applied during claims | +| **Precision** | Integer drops | `tecPRECISION_LOSS` when fund, claim or partial clawback would leave an inexact `Amount` or `Amount - Balance` | Integer amounts; a fund whose sum overflows fails with `tecPRECISION_LOSS` | +| **Clawback Opt-In** | Not clawable (`tecNO_PERMISSION`) | `lsfAllowTrustLineClawback` (account flag), and `lsfNoFreeze` must not be set | `lsfMPTCanClawback` (issuance flag) | +| **Clawback Accounting** | N/A | Channel `Amount` is reduced; no trustline changes | `LockedAmount` and `OutstandingAmount` are reduced | +| **Outstanding Amount** | N/A | N/A | Unchanged by create, fund and closure refund; decreased by the transfer fee on a claim, by the claimed amount on a claim to the issuer, and by the clawed amount on clawback | +| **Signature Message** | 44 bytes, the drops as an unsigned 64-bit integer | 84 bytes, the IOU `Amount` serialization | 69 bytes, the MPT `Amount` serialization | +| **Account Deletion** | Payment channels prevent account deletion | Payment channels prevent account deletion | Payment channels prevent account deletion | +| **Holding Deletion** | N/A | Trustline deletion is NOT blocked by open channels (locked value lives in the channel object); a close submitted by any account other than the source then fails with `tecNO_LINE`, while the source's own closing transaction re-creates the line (when authorization is not required) and receives the refund | `MPToken` deletion is blocked while `LockedAmount` is non-zero (`tecHAS_OBLIGATIONS`) | + +## 3.13. Transfer Rates and Fees + +### 3.13.1. IOU Tokens (`TransferRate`) - **Rate Capped at Creation**: The `TransferRate` is captured at the time of `PaymentChannelCreate` and stored in the `PayChannel` object. At claim time, the lower of the stored rate and the issuer's current rate is applied: an increase by the issuer does not affect existing channels, while a decrease passes through to claims. This is identical to the behavior of the activated XLS-85 (Token Escrow) implementation, which uses the same shared unlock logic. - **Fee Calculation**: The transfer fee is deducted from the claimed amount, reducing the final amount credited to the destination. No fee is applied when the issuer is the destination, or when remaining funds are returned to the source at closure. -### 3.11.2. MPTs (`TransferFee`) +### 3.13.2. MPTs (`TransferFee`) - **Fee Capped at Creation**: The `TransferFee` is captured at the time of `PaymentChannelCreate` and stored in the `PayChannel` object, similar to IOUs, with the same lower-of-stored-and-current rule. - **Fee Calculation**: The transfer fee is deducted from the claimed amount, reducing the final amount credited to the destination. - **Consistent Fee Application**: Both IOUs and MPTs use the same capped-rate rule, ensuring the destination's settlement value cannot be worsened by the issuer after channel creation. -## 3.12. Future Considerations +## 3.14. Future Considerations 1. Issuer as Source: XLS-93 currently does not allow the issuer to be the source of the Payment Channel. If your use case requires this functionality, you should create a new account, send the MPT or IOU to that account, and then create the payment channel with that account as the source. @@ -639,7 +736,7 @@ The `channel_authorize` and `channel_verify` RPC methods take the authorized amo ## 4. Rationale -Payment channels are the last remaining XRP-only locking primitive; XLS-85 already extended escrows to IOUs and MPTs. Reusing the XLS-85 model wholesale, the same issuer opt-in flags (`lsfAllowTrustLineLocking`, `lsfMPTCanEscrow`), the same `sfLockedAmount` accounting, and the same shared lock/unlock logic in the implementation, means issuers make one opt-in decision that covers both primitives, and both primitives fail and succeed under identical token conditions. Every place where XLS-93 is stricter than base token semantics (any freeze blocks lock creation, no channel creation during global freeze even to the issuer) is inherited from the activated XLS-85 behavior rather than newly invented, keeping the two locking primitives coherent. +Payment channels are the last remaining XRP-only locking primitive; XLS-85 already extended escrows to IOUs and MPTs. Reusing the XLS-85 model wholesale, the same issuer opt-in flags (`lsfAllowTrustLineLocking`, `lsfMPTCanEscrow`), the same `LockedAmount` accounting, and the same shared lock/unlock logic in the implementation, means issuers make one opt-in decision that covers both primitives, and both primitives fail and succeed under identical token conditions. Every place where XLS-93 is stricter than base token semantics (any freeze blocks lock creation, no channel creation during global freeze even to the issuer) is inherited from the activated XLS-85 behavior rather than newly invented, keeping the two locking primitives coherent. `PaymentChannelClawback` is included in the same amendment for the same reason. An issuer's clawback opt-in is a property of the token, so it should hold wherever that token sits. Deferring the transaction would have meant shipping a lock that quietly suspends an issuer control the token already carries, and the alternative of closing the channel first is not open to the issuer, which is not a party to the channel and cannot close it. @@ -647,11 +744,11 @@ Payment channels are the last remaining XRP-only locking primitive; XLS-85 alrea The change is amendment-gated. Before `TokenPaychan` is enabled, a token `Amount` on `PaymentChannelCreate` or `PaymentChannelFund`, and a token `Balance` or `Amount` on `PaymentChannelClaim`, are rejected with `temBAD_AMOUNT`, and `PaymentChannelClawback` is rejected with `temDISABLED`. -XRP channels are unchanged: the Claim Authorization message for an XRP channel keeps its 44-byte layout, existing `PayChannel` entries gain no fields (`TransferRate` and `IssuerNode` are only ever set on token channels), and `channel_authorize` and `channel_verify` continue to accept `amount` as a string of drops. +XRP channels are unchanged: the [Signature](#3811-signature) message for an XRP channel keeps its 44-byte layout, existing `PayChannel` entries gain no fields (`TransferRate` and `IssuerNode` are only ever set on token channels), and `channel_authorize` and `channel_verify` continue to accept `amount` as a string of drops. ## 6. Test Plan -The reference implementation adds the `PayChanToken` test suite (`src/test/app/PayChanToken_test.cpp`), which covers, for IOUs and MPTs separately: amendment enablement, the issuer opt-in flags, preflight, preclaim and apply of `PaymentChannelCreate`, `PaymentChannelFund` and `PaymentChannelClaim`, closure, auto-creation of the destination's holding, balances and metadata, the locked transfer rate, require-auth, freeze and lock, trustline limits, precision loss, the interaction with ordinary `Clawback`, `PaymentChannelClawback` for both asset types, the `channel_authorize` and `channel_verify` methods, and the byte layout of the Claim Authorization message. +The reference implementation adds the `PayChanToken` test suite (`src/test/app/PayChanToken_test.cpp`), which covers, for IOUs and MPTs separately: amendment enablement, the issuer opt-in flags, preflight, preclaim and apply of `PaymentChannelCreate`, `PaymentChannelFund` and `PaymentChannelClaim`, closure, auto-creation of the destination's holding, balances and metadata, the locked transfer rate, require-auth, freeze and lock, trustline limits, precision loss, the interaction with ordinary `Clawback`, `PaymentChannelClawback` for both asset types, the `channel_authorize` and `channel_verify` methods, and the byte layout of the [Signature](#3811-signature) message. ## 7. Reference Implementation @@ -659,10 +756,35 @@ The reference implementation adds the `PayChanToken` test suite (`src/test/app/P ## 8. Security Considerations -- **Payee protection.** The destination's earned funds are never gated by source-side state. Normal claims check only destination-side conditions, and the source-side conditions in Channel Closure apply only to refunding a positive remainder; a fully drained channel closes without them. +- **Payee protection.** The destination's earned funds are never gated by source-side state. Claims check only destination-side conditions, and the source-side conditions in [Channel Closure](#38222-channel-closure) apply only to refunding a positive remainder; a fully drained channel closes without them. - **Issuer trust surface.** An issuer that uses `RequireAuth` can deauthorize the source and thereby block the refund leg of closure (`tecNO_AUTH`) until re-authorized. The channel and its locked funds remain on ledger; no funds are lost. This is the same issuer trust surface that exists for XLS-85 escrow refunds and for clawback generally. - **Transfer rate.** The claim rate is capped at the rate stored at creation (the lower of stored and current is applied), so an issuer cannot retroactively tax funds already locked by raising `TransferRate`/`TransferFee`. -- **Trustline deletion.** The source can delete an empty trustline while a channel is open. Closure refunds then fail with `tecNO_LINE` until the source re-establishes the line; the destination's claims are unaffected. `MPToken` deletion is blocked while locked (`tecHAS_OBLIGATIONS`). -- **Signature domain.** Claim signatures bind the specific channel ID and amount, unchanged from XRP payment channels; the token layouts in Claim Authorization also bind the currency and issuer or the `MPTokenIssuanceID`, so a signature for one asset cannot be presented against a channel holding another. +- **Precision.** IOU amounts carry a 16-digit mantissa, so a sum or difference of values at very different scales can round. Fund, claim and partial clawback reject with `tecPRECISION_LOSS` unless the quantity each changes (the new `Amount` on fund and partial clawback, the paid difference on claim) and `Amount - Balance` are exact, so closure refunds exactly what was not claimed and no transaction creates or destroys value through rounding. +- **Trustline deletion.** The source can delete an empty trustline while a channel is open. A close submitted by any account other than the source then fails with `tecNO_LINE`; the source's own closing transaction re-creates the line (when authorization is not required) and receives the refund. The destination's claims are unaffected. `MPToken` deletion is blocked while locked (`tecHAS_OBLIGATIONS`). +- **Signature domain.** Claim signatures bind the specific channel ID and amount, unchanged from XRP payment channels; the token layouts in [Signature](#3811-signature) also bind the currency and issuer or the `MPTokenIssuanceID`, so a signature for one asset cannot be presented against a channel holding another. - **Clawback scope.** `PaymentChannelClawback` reaches only the unclaimed remainder and only for an issuer who already holds the clawback opt-in for that token. It cannot reverse a claim the destination has settled, and it cannot touch XRP or a token whose issuer never enabled clawback. -- **Clawback and pending authorizations.** An issuer clawback can invalidate a signed claim authorization the destination is holding, because the authorized balance may exceed the reduced channel `Amount`. A destination that wants to settle ahead of this should claim rather than accumulate authorizations, the same tradeoff that applies to holding an unsettled balance with any clawback-enabled issuer. +- **Clawback and pending authorizations.** An issuer clawback lowers the ceiling on the `Balance` a claim can request. A signed authorization for more than the reduced `Amount` still pays out any `Balance` up to that `Amount`; only the part above it can no longer be claimed. A destination that wants to settle ahead of this should claim rather than accumulate authorizations, the same tradeoff that applies to holding an unsettled balance with any clawback-enabled issuer. + +# Appendix + +## Appendix A: FAQ + +### A.1: Why does `PaymentChannelCreate` reject a frozen destination, when an individual freeze only stops the frozen holder from sending? + +Create runs the same lock-creation check as XLS-85 escrows, which rejects any freeze or lock on either party. This is stricter than base freeze semantics, and keeping it identical to escrow means one rule for both locking primitives. The destination is never stranded by it: a claim checks only deep freeze (IOU) or lock (MPT) on the destination, so a channel opened before an individual or global freeze still pays out. + +### A.2: Why does a claim apply the lower of the stored and the current transfer rate? + +The rate stored at creation is a cap, so an issuer cannot raise the fee on value already locked, and a later decrease passes through to the destination. The activated XLS-85 escrows apply the same rule through the same shared unlock logic. + +### A.3: Can the source delete its trust line while tokens are locked in a channel? + +Yes, for an IOU. The locked value lives in the `PayChannel` entry, not on the trust line, so an empty trust line can be deleted. A close submitted by any account other than the source then fails with `tecNO_LINE`; the source's own closing transaction re-creates the line (when authorization is not required) and receives the refund. The destination's claims are unaffected. An `MPToken` cannot be deleted while its `LockedAmount` is non-zero (`tecHAS_OBLIGATIONS`). Per-trust-line lock accounting is left to a future amendment that would cover escrows as well. + +### A.4: Can vault shares be locked in a payment channel? + +No. A vault share is an MPT issued by the vault's pseudo-account, which cannot claw back its own shares, so shares locked in a channel would sit outside the compliance controls of the vault's underlying asset. `PaymentChannelCreate` rejects an MPT whose issuer is a pseudo-account with `tecWRONG_ASSET`, the same rule `AMMCreate` applies. `PaymentChannelFund` accepts only the channel's own asset, so no channel can come to hold shares. + +### A.5: Can IOU precision create or destroy value when `Amount` or `Balance` change scale? + +No. An IOU amount is a normalized value with a 16-digit mantissa, so adding or subtracting values at very different scales can round: with `Amount` at 10^20 and `Balance` at 1, `Amount - Balance` rounds back to 10^20. Every transaction that changes `Amount` or `Balance` therefore rejects with `tecPRECISION_LOSS` unless the result is exact: fund requires the new `Amount` and the new `Amount - Balance` to be exact, claim requires the paid difference and the new `Amount - Balance` to be exact, and a partial clawback requires the new `Amount` and the new `Amount - Balance` to be exact. Closure refunds `Amount - Balance`, which these checks keep exact, so the refund equals what was not claimed. From 359239d58656ed6aa879efc37ff505d415d2515d Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 6 Oct 2026 21:11:23 -0400 Subject: [PATCH 15/19] docs: reconcile XLS-93 with the implementation --- XLS-0093-token-paychan/README.md | 140 ++++++++++++++++++++++--------- 1 file changed, 99 insertions(+), 41 deletions(-) diff --git a/XLS-0093-token-paychan/README.md b/XLS-0093-token-paychan/README.md index 2f3e3f37a..dc3713d55 100644 --- a/XLS-0093-token-paychan/README.md +++ b/XLS-0093-token-paychan/README.md @@ -63,16 +63,16 @@ This spec changes `Amount` and `Balance` and adds `TransferRate` and `IssuerNode | -------------- | -------- | -------- | ------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Amount` | No | Yes | AMOUNT | N/A | The total amount allocated to the payment channel. Can represent XRP, an IOU token, or an MPT. Must always be a positive value. | | `Balance` | No | Yes | AMOUNT | N/A | The cumulative amount claimed from the channel, before transfer fees. Same asset as `Amount`. | -| `TransferRate` | Yes | No | UINT32 | N/A | The transfer rate or fee at creation, used as an upper bound on the rate applied during claims. Only present when the rate at creation differs from parity. | +| `TransferRate` | Yes | No | UINT32 | N/A | The transfer rate or fee at creation, used as an upper bound on the rate applied during claims. Only present when the rate at creation differs from parity. Never updated. | | `IssuerNode` | No | No | UINT64 | N/A | The ledger index of the issuer's directory node associated with the `PayChannel`. Only present for IOU channels where the issuer is neither the source nor the destination. | ### 3.2.2. Invariants -- `'.Balance` and `'.Amount` are the same asset, and that asset is the asset of `.Amount`: the asset of a channel never changes after creation. +These invariants apply to every `PayChannel` whose `Amount` is not XRP, once `TokenPaychan` is enabled: + +- `'.Balance` and `'.Amount` are the same asset. - `0 <= '.Balance <= '.Amount`. - `.Balance <= '.Balance` (the paid-out balance only increases). -- `TransferRate` is present only if `Amount` is not XRP, and `'.TransferRate == .TransferRate`: it is set at creation and never updated. -- `IssuerNode` is present only if `Amount` is an IOU whose issuer is neither `Account` nor `Destination`. - If `Amount` is an IOU, the difference `'.Amount - '.Balance` is exact. An IOU difference `a - b` is exact only if adding `b` back to it gives `a` and subtracting it from `a` gives `b`. XRP and MPT differences are integers and always exact. ### 3.2.3. Example JSON @@ -121,7 +121,7 @@ This spec adds no fields. It extends the meaning of the `LockedAmount` field int ### 3.3.2. Invariants -Existing invariants remain; this spec adds none. Those that bound `LockedAmount` are: +This spec adds no invariant and relaxes one: once `TokenPaychan` is enabled, a `PaymentChannelClaim` may create at most one `MPToken` (the holding auto-created for the account that submits it, see [Claim with `Balance`](#3831-claim-with-balance) and [Channel Closure](#3832-channel-closure)), provided it creates or deletes no `MPTokenIssuance` and deletes no `MPToken`. The existing invariants that bound `LockedAmount` are: - `0 <= '.MPTAmount` and `'.MPTAmount` does not exceed the maximum MPT amount. - `0 <= '.LockedAmount` and `'.LockedAmount` does not exceed the maximum MPT amount. @@ -234,10 +234,10 @@ As a reference, [here](https://xrpl.org/docs/references/protocol/transactions/ty #### 3.6.2.1. Data Verification -1. `Amount` is a token and the `TokenPaychan` amendment is not enabled. (`temBAD_AMOUNT`) -2. `Amount` is an IOU that is not positive. (`temBAD_AMOUNT`) -3. `Amount` is an IOU whose currency code is XRP. (`temBAD_CURRENCY`) -4. `Amount` is an MPT and the `MPTokensV1` amendment is not enabled. (`temDISABLED`) +1. `Amount` is an MPT and the `MPTokensV1` amendment is not enabled. (`temDISABLED`) +2. `Amount` is a token and the `TokenPaychan` amendment is not enabled. (`temBAD_AMOUNT`) +3. `Amount` is an IOU that is not positive. (`temBAD_AMOUNT`) +4. `Amount` is an IOU whose currency code is XRP. (`temBAD_CURRENCY`) 5. `Amount` is an MPT that is not positive or exceeds the maximum MPT amount. (`temBAD_AMOUNT`) #### 3.6.2.2. Protocol-Level Failures @@ -256,7 +256,7 @@ As a reference, [here](https://xrpl.org/docs/references/protocol/transactions/ty - If `Amount` is an MPT whose issuer is a pseudo-account, such as a vault share, the transaction fails with `tecWRONG_ASSET`. This matches the rule `AMMCreate` applies to vault shares (see [Appendix A.4](#a4-can-vault-shares-be-locked-in-a-payment-channel)). 4. **Source or Destination Not Authorized to Hold Token:** - - If the issuer requires authorization and either the source or the destination is not authorized, the transaction fails with `tecNO_AUTH`. + - If the issuer requires authorization and either the source or the destination is not authorized, the transaction fails with `tecNO_AUTH`. For an IOU, a destination with no trustline to the issuer fails with `tecNO_LINE` when authorization is required. - The issuer is always authorized for its own token, so a destination that is the issuer never fails this check. 5. **Source Account's Token Holding Issues:** @@ -272,6 +272,9 @@ As a reference, [here](https://xrpl.org/docs/references/protocol/transactions/ty 7. **Insufficient Spendable Balance:** - If the source account lacks sufficient spendable balance, the transaction fails with `tecINSUFFICIENT_FUNDS`. +8. **Inexact Source Debit (IOU only):** + - Locking `Amount` debits the source's trustline, which must decrease by exactly `Amount`. If the source's spendable balance minus `Amount` is not exact, as defined in the `PayChannel` [Invariants](#322-invariants), or `Amount` cannot be added to that balance within the precision tolerance shared with XLS-85 escrows, the transaction fails with `tecPRECISION_LOSS`. XRP and MPT amounts are integers and are not subject to this check. + ### 3.6.3. State Changes 1. **Adjustment from Source to Issuer:** @@ -319,10 +322,10 @@ As a reference, [here](https://xrpl.org/docs/references/protocol/transactions/ty #### 3.7.2.1. Data Verification -1. `Amount` is a token and the `TokenPaychan` amendment is not enabled. (`temBAD_AMOUNT`) -2. `Amount` is an IOU that is not positive. (`temBAD_AMOUNT`) -3. `Amount` is an IOU whose currency code is XRP. (`temBAD_CURRENCY`) -4. `Amount` is an MPT and the `MPTokensV1` amendment is not enabled. (`temDISABLED`) +1. `Amount` is an MPT and the `MPTokensV1` amendment is not enabled. (`temDISABLED`) +2. `Amount` is a token and the `TokenPaychan` amendment is not enabled. (`temBAD_AMOUNT`) +3. `Amount` is an IOU that is not positive. (`temBAD_AMOUNT`) +4. `Amount` is an IOU whose currency code is XRP. (`temBAD_CURRENCY`) 5. `Amount` is an MPT that is not positive or exceeds the maximum MPT amount. (`temBAD_AMOUNT`) #### 3.7.2.2. Protocol-Level Failures @@ -330,9 +333,13 @@ As a reference, [here](https://xrpl.org/docs/references/protocol/transactions/ty 1. **Asset Mismatch:** - If the funding `Amount` is not the same asset as the channel's `Amount`, the transaction fails with `tecWRONG_ASSET`. -2. **Same conditions as `PaymentChannelCreate`** for validating the funding amount and token permissions (issuer opt-in, authorization, freeze/lock, transferability, spendable balance). +2. **Expired Channel:** + - If the channel's `Expiration` or `CancelAfter` has passed, the transaction closes the channel instead of funding it, whichever account submits it. The conditions in [Channel Closure](#38222-channel-closure) apply to the refund, and the conditions below do not apply. + +3. **Same conditions as `PaymentChannelCreate`** for validating the funding amount and token permissions (issuer opt-in, authorization, freeze/lock, transferability, spendable balance). -3. **Inexact Sum or Remainder:** +4. **Inexact Debit, Sum or Remainder:** + - For an IOU, the source's spendable balance minus the funding `Amount` must be exact, as on `PaymentChannelCreate`, so the source is debited exactly what the channel gains. - The channel's new `Amount` must equal its old `Amount` plus the funding `Amount` exactly. An IOU sum is rounded to the mantissa width and is exact only if subtracting each operand from the sum gives back the other; an MPT sum is exact only if it does not overflow. - The remainder the channel holds after funding, the new `Amount` minus the channel's `Balance`, must also be exact, as defined in the `PayChannel` [Invariants](#322-invariants). - Otherwise the transaction fails with `tecPRECISION_LOSS`. @@ -344,6 +351,8 @@ As a reference, [here](https://xrpl.org/docs/references/protocol/transactions/ty - **MPTs**: The funding `Amount` is deducted from the source's MPT balance. The `LockedAmount` is increased accordingly. 2. **Payment Channel Object Update:** - The channel's `Amount` field is increased by the funding amount. The stored `TransferRate` is not updated by funding. +3. **Expired Channel:** + - The channel is closed and deleted as in [Channel Closure](#3832-channel-closure), and no funds are added. ### 3.7.4. Example JSON @@ -401,7 +410,7 @@ The message is 44 bytes for an XRP channel, 84 bytes for an IOU channel and 69 b ##### 3.8.2.2.1. Claim with `Balance` -These conditions apply to every claim that carries `Balance`, with or without `tfClose`. On a claim with `tfClose` they govern the payout to the destination; the conditions in [Channel Closure](#38222-channel-closure) govern only the refund of the remainder to the source. +These conditions apply to every claim that carries `Balance`, with or without `tfClose`. On a claim with `tfClose` they govern the payout to the destination; the conditions in [Channel Closure](#38222-channel-closure) govern the refund of the remainder to the source, and a failure there fails the whole claim, payout included. When the destination is the issuer of the channel's token, none of the authorization, holding, trustline limit or freeze conditions below apply: the issuer has no trustline to itself and holds no `MPToken`, and the claim redeems the tokens to the issuer (see [State Changes](#3831-claim-with-balance)). @@ -409,7 +418,7 @@ When the destination is the issuer of the channel's token, none of the authoriza - If the claim's `Balance` or `Amount` is not the same asset as the channel's `Amount`, the transaction fails with `tecWRONG_ASSET`. 2. **Destination Not Authorized to Hold Token:** - - If authorization is required and the destination is not authorized, the transaction fails with `tecNO_AUTH`. + - If authorization is required and the destination is not authorized, the transaction fails with `tecNO_AUTH`. For an IOU, a destination with no trustline to the issuer fails with `tecNO_LINE` when authorization is required. 3. **Destination Lacks Trustline or MPT Holding:** - The destination's trustline or `MPToken` is created during the claim only when the destination itself submits the transaction (and authorization is not required). No other submitter can create a holding for the destination, since holding a token requires the holder's consent. @@ -434,12 +443,12 @@ When the destination is the issuer of the channel's token, none of the authoriza ##### 3.8.2.2.2. Channel Closure -A channel closes when a claim carries the `tfClose` flag (immediately if the requester is the destination or the channel is fully drained; otherwise an expiration is scheduled per `SettleDelay`), or when any claim is processed against an already-expired channel. +A channel closes when a `PaymentChannelClaim` carries the `tfClose` flag (immediately if the requester is the destination or the channel is fully drained; otherwise an expiration is scheduled per `SettleDelay`), when any `PaymentChannelClaim` or `PaymentChannelFund` is processed against an already-expired channel, whichever account submits it, or when a `PaymentChannelClawback` claws the full remainder. -Closure returns the remaining channel funds (`Amount` minus `Balance`) to the source. **The failure conditions below apply only when this remainder is positive.** A fully drained channel has nothing to refund, so it closes without any source-side checks; the destination's ability to claim earned funds is never gated by the source's authorization, trustline, or freeze state. +Closure returns the remaining channel funds (`Amount` minus `Balance`) to the source. **The failure conditions below apply only when this remainder is positive.** A fully drained channel has nothing to refund, so it closes without any source-side checks. A claim with `tfClose` that fails one of them fails as a whole, payout included. A claim without `tfClose` on a channel that has not expired never runs these checks, so the destination can collect its earned funds that way regardless of the source's authorization, trustline, or freeze state. 1. **Source Not Authorized to Hold Token:** - - If authorization is required and the source is not authorized, the transaction fails with `tecNO_AUTH`. + - If authorization is required and the source is not authorized, the transaction fails with `tecNO_AUTH`. For an IOU, a source with no trustline to the issuer fails with `tecNO_LINE` when authorization is required, whichever account submits the transaction. 2. **Source Lacks Trustline or MPT Holding:** - The source's trustline or `MPToken` is created during closure only when the source itself submits the transaction (and authorization is not required), for the same reason as on a claim. @@ -447,7 +456,7 @@ Closure returns the remaining channel funds (`Amount` minus `Balance`) to the so - **MPTs**: If the source does not hold the MPT and did not submit the transaction, the transaction fails with `tecNO_PERMISSION`. 3. **Cannot Create Trustline or MPT Holding:** - - If unable to create due to lack of reserves, the transaction fails with `tecNO_LINE_INSUF_RESERVE` (IOU) or `tecINSUFFICIENT_RESERVE` (MPT). + - If unable to create due to lack of reserves, the transaction fails with `tecNO_LINE_INSUF_RESERVE` (IOU) or `tecINSUFFICIENT_RESERVE` (MPT). The channel's own owner reserve is released before this check, so the source needs reserve for the new holding counted against its owner count without the channel. 4. **Source Account is Frozen or Token is Locked:** - **IOU Tokens**: @@ -464,7 +473,7 @@ Closure returns the remaining channel funds (`Amount` minus `Balance`) to the so - **IOU Tokens**: If the IOU does not require authorization and the account submitting the transaction is the destination, a trustline is created for it. - **MPTs**: If the MPT does not require authorization and the account submitting the transaction is the destination, an `MPToken` is created for it. 2. **Adjustment from Issuer to Destination:** - - **IOU Tokens**: The claimed amount, less any transfer fee (see [Transfer Rates and Fees](#313-transfer-rates-and-fees)), is added to the destination's trustline balance. If the destination is the issuer, the claimed amount is simply redeemed. + - **IOU Tokens**: The claimed amount, less any transfer fee (see [Transfer Rates and Fees](#314-transfer-rates-and-fees)), is added to the destination's trustline balance. If the destination is the issuer, the claimed amount is simply redeemed. - **MPTs**: - If the destination is the issuer of the asset held in the channel, then: 1. The `LockedAmount` on the `MPTokenIssuance` and the source's `MPToken` is decreased by the claimed amount. @@ -516,7 +525,7 @@ Closure returns the remaining channel funds (`Amount` minus `Balance`) to the so ## 3.9. Transaction: `PaymentChannelClawback` -Locking a token into a channel moves it out of reach of the ordinary `Clawback` transaction (for an MPT, `Clawback` with the `MPTokenHolder` field defined in [XLS-33](../XLS-0033-multi-purpose-tokens/README.md)), which is bounded by the holder's spendable balance and so cannot see locked value. `PaymentChannelClawback` gives the issuer that reach back. It requires the same opt-in the issuer already needed to claw back an ordinary holding, so it grants no new authority over a token; it removes a place the token could be kept out of reach. +Locking a token into a channel moves it out of reach of the ordinary `Clawback` transaction (for an MPT, `Clawback` with the `Holder` field, named `MPTokenHolder` in [XLS-33](../XLS-0033-multi-purpose-tokens/README.md)), which is bounded by the holder's spendable balance and so cannot see locked value. `PaymentChannelClawback` gives the issuer that reach back. It requires the same opt-in the issuer already needed to claw back an ordinary holding, so it grants no new authority over a token; it removes a place the token could be kept out of reach. Only the unclaimed remainder of the channel (`Amount` minus `Balance`) can be clawed. The destination's earned `Balance` is never touched, so a clawback cannot reverse value the payee has already claimed. @@ -524,11 +533,11 @@ Only the unclaimed remainder of the channel (`Amount` minus `Balance`) can be cl | Field | Required? | JSON Type | Internal Type | Default Value | Description | | ----------------- | --------- | ---------------- | ------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `TransactionType` | Yes | String | UInt16 | N/A | The transaction type, `PaymentChannelClawback` (`ttPAYCHAN_CLAWBACK`, value `94`). | +| `TransactionType` | Yes | String | UInt16 | N/A | The transaction type, `PaymentChannelClawback` (`ttPAYCHAN_CLAWBACK`, value `96`). | | `Channel` | Yes | String | Hash256 | N/A | The ID of the `PayChannel` to claw from. | | `Amount` | No | Object or String | Amount | N/A | The amount to claw back. Must be a positive, non-XRP amount of the channel's asset. If omitted, or if it is at least the unclaimed remainder, the entire remainder is clawed. | -The transaction is not delegable under [XLS-75](../XLS-0075-permission-delegation/README.md): it is registered as `Delegation::NotDelegable`, so a delegate cannot submit it on the issuer's behalf, in line with XLS-75 Section 8 for new transaction types. +The transaction is delegable under [XLS-75](../XLS-0075-permission-delegation/README.md), like `Clawback`. It is registered in `transactions.macro` with `Delegation::Delegable` and gated on `TokenPaychan`, so an issuer can use `DelegateSet` to grant a delegate the `PaymentChannelClawback` permission, and the delegate submits it with `Delegate` set to its own account. A delegate without that permission fails with `terNO_DELEGATE_PERMISSION`. A delegated clawback acts exactly as if the issuer had submitted it. ### 3.9.2. Transaction Fee @@ -622,7 +631,7 @@ The response fields are [unchanged](https://xrpl.org/docs/references/http-websoc The existing failure conditions are unchanged. This spec extends one: -1. `amount` is neither a string of drops nor an `Amount` object for a token amount that is not negative. (`channelAmtMalformed`) +1. `amount` is not a string of drops, and is not a transaction `Amount` JSON object for an IOU or MPT whose value is not negative. (`channelAmtMalformed`) ### 3.10.4. Example Request @@ -669,7 +678,7 @@ The response fields are [unchanged](https://xrpl.org/docs/references/http-websoc The existing failure conditions are unchanged. This spec extends one: -1. `amount` is neither a string of drops nor an `Amount` object for a token amount that is not negative. (`channelAmtMalformed`) +1. `amount` is not a string of drops, and is not a transaction `Amount` JSON object for an IOU or MPT whose value is not negative. (`channelAmtMalformed`) ### 3.11.4. Example Request @@ -696,7 +705,54 @@ The existing failure conditions are unchanged. This spec extends one: } ``` -## 3.12. Key Differences Between XRP, IOU and MPT Payment Channels +## 3.12. RPC: `gateway_balances` + +The [`gateway_balances` method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods/account-methods/gateway_balances) is modified to count IOU value locked in payment channels. The response's `locked` totals, per currency, now include the unclaimed remainder (`Amount` minus `Balance`) of every IOU `PayChannel` in the queried account's owner directory: channels the account opened, channels whose destination is that account, and channels whose `IssuerNode` points there. XRP and MPT channels are not counted. + +### 3.12.1. Request Fields + +The request fields are [unchanged](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods/account-methods/gateway_balances#request-format). + +### 3.12.2. Response Fields + +As a reference, [here](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods/account-methods/gateway_balances#response-format) are the response fields that `gateway_balances` currently has. This spec changes `locked`: + +| Field Name | Always Present? | JSON Type | Description | +| ---------- | --------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `locked` | No | Object | Total amounts locked, keyed by currency code. This spec adds the unclaimed remainder of each IOU payment channel in the account's owner directory to the existing escrow totals. | + +### 3.12.3. Failure Conditions + +The failure conditions are unchanged. + +### 3.12.4. Example Request + +```json +{ + "command": "gateway_balances", + "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "ledger_index": "validated" +} +``` + +### 3.12.5. Example Response + +For the channel opened by the `PaymentChannelCreate` [Example JSON](#364-example-json), before any claim, with the source having held 10000 USD before locking 1000: + +```json +{ + "status": "success", + "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "obligations": { + "USD": "9000" + }, + "locked": { + "USD": "1000" + } +} +``` + +## 3.13. Key Differences Between XRP, IOU and MPT Payment Channels | Aspect | XRP | IOU Tokens | Multi-Purpose Tokens (MPTs) | | ----------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -707,28 +763,28 @@ The existing failure conditions are unchanged. This spec extends one: | **Destination Authorization** | N/A | Required at creation and at claim unless the destination is the issuer; cannot be granted during claim if authorization required | Required at creation and at claim unless the destination is the issuer; cannot be granted during claim if authorization required | | **Freeze/Lock Conditions** | N/A | Any freeze blocks create/fund; **Deep Freeze** prevents claims, but allows closure; Global/Individual Freeze allows claims and closure | Lock blocks create/fund; **Lock Conditions (Deep Freeze Equivalent)** prevent claims, but allow closure | | **Transfer Rates/Fees** | N/A | `TransferRate` stored at creation and applied during claims | `TransferFee` stored at creation and applied during claims | -| **Precision** | Integer drops | `tecPRECISION_LOSS` when fund, claim or partial clawback would leave an inexact `Amount` or `Amount - Balance` | Integer amounts; a fund whose sum overflows fails with `tecPRECISION_LOSS` | +| **Precision** | Integer drops | `tecPRECISION_LOSS` when create or fund would debit the source's trustline inexactly, or when fund, claim or partial clawback would leave an inexact `Amount` or `Amount - Balance` | Integer amounts; a fund whose sum overflows fails with `tecPRECISION_LOSS` | | **Clawback Opt-In** | Not clawable (`tecNO_PERMISSION`) | `lsfAllowTrustLineClawback` (account flag), and `lsfNoFreeze` must not be set | `lsfMPTCanClawback` (issuance flag) | | **Clawback Accounting** | N/A | Channel `Amount` is reduced; no trustline changes | `LockedAmount` and `OutstandingAmount` are reduced | | **Outstanding Amount** | N/A | N/A | Unchanged by create, fund and closure refund; decreased by the transfer fee on a claim, by the claimed amount on a claim to the issuer, and by the clawed amount on clawback | | **Signature Message** | 44 bytes, the drops as an unsigned 64-bit integer | 84 bytes, the IOU `Amount` serialization | 69 bytes, the MPT `Amount` serialization | -| **Account Deletion** | Payment channels prevent account deletion | Payment channels prevent account deletion | Payment channels prevent account deletion | +| **Account Deletion** | Payment channels prevent account deletion | Payment channels prevent deletion of the source and destination accounts, and of the issuer's account while a channel is in its owner directory (`IssuerNode`) | Payment channels prevent account deletion | | **Holding Deletion** | N/A | Trustline deletion is NOT blocked by open channels (locked value lives in the channel object); a close submitted by any account other than the source then fails with `tecNO_LINE`, while the source's own closing transaction re-creates the line (when authorization is not required) and receives the refund | `MPToken` deletion is blocked while `LockedAmount` is non-zero (`tecHAS_OBLIGATIONS`) | -## 3.13. Transfer Rates and Fees +## 3.14. Transfer Rates and Fees -### 3.13.1. IOU Tokens (`TransferRate`) +### 3.14.1. IOU Tokens (`TransferRate`) - **Rate Capped at Creation**: The `TransferRate` is captured at the time of `PaymentChannelCreate` and stored in the `PayChannel` object. At claim time, the lower of the stored rate and the issuer's current rate is applied: an increase by the issuer does not affect existing channels, while a decrease passes through to claims. This is identical to the behavior of the activated XLS-85 (Token Escrow) implementation, which uses the same shared unlock logic. -- **Fee Calculation**: The transfer fee is deducted from the claimed amount, reducing the final amount credited to the destination. No fee is applied when the issuer is the destination, or when remaining funds are returned to the source at closure. +- **Fee Calculation**: The destination is credited the claimed amount divided by the applied rate (the lower of the stored and current rate, as a ratio to 1,000,000,000), rounded up to IOU precision; the fee is the claimed amount minus that credit. No fee is applied when the issuer is the destination, or when remaining funds are returned to the source at closure. -### 3.13.2. MPTs (`TransferFee`) +### 3.14.2. MPTs (`TransferFee`) - **Fee Capped at Creation**: The `TransferFee` is captured at the time of `PaymentChannelCreate` and stored in the `PayChannel` object, similar to IOUs, with the same lower-of-stored-and-current rule. - **Fee Calculation**: The transfer fee is deducted from the claimed amount, reducing the final amount credited to the destination. - **Consistent Fee Application**: Both IOUs and MPTs use the same capped-rate rule, ensuring the destination's settlement value cannot be worsened by the issuer after channel creation. -## 3.14. Future Considerations +## 3.15. Future Considerations 1. Issuer as Source: XLS-93 currently does not allow the issuer to be the source of the Payment Channel. If your use case requires this functionality, you should create a new account, send the MPT or IOU to that account, and then create the payment channel with that account as the source. @@ -742,13 +798,15 @@ Payment channels are the last remaining XRP-only locking primitive; XLS-85 alrea ## 5. Backwards Compatibility -The change is amendment-gated. Before `TokenPaychan` is enabled, a token `Amount` on `PaymentChannelCreate` or `PaymentChannelFund`, and a token `Balance` or `Amount` on `PaymentChannelClaim`, are rejected with `temBAD_AMOUNT`, and `PaymentChannelClawback` is rejected with `temDISABLED`. +The change is amendment-gated. Before `TokenPaychan` is enabled, a token `Amount` on `PaymentChannelCreate` or `PaymentChannelFund`, and a token `Balance` or `Amount` on `PaymentChannelClaim`, are rejected with `temBAD_AMOUNT`, and `PaymentChannelClawback` is rejected with `temDISABLED`. Before `TokenPaychan` is enabled, a `DelegateSet` that grants the `PaymentChannelClawback` permission is rejected with `temMALFORMED`. XRP channels are unchanged: the [Signature](#3811-signature) message for an XRP channel keeps its 44-byte layout, existing `PayChannel` entries gain no fields (`TransferRate` and `IssuerNode` are only ever set on token channels), and `channel_authorize` and `channel_verify` continue to accept `amount` as a string of drops. ## 6. Test Plan -The reference implementation adds the `PayChanToken` test suite (`src/test/app/PayChanToken_test.cpp`), which covers, for IOUs and MPTs separately: amendment enablement, the issuer opt-in flags, preflight, preclaim and apply of `PaymentChannelCreate`, `PaymentChannelFund` and `PaymentChannelClaim`, closure, auto-creation of the destination's holding, balances and metadata, the locked transfer rate, require-auth, freeze and lock, trustline limits, precision loss, the interaction with ordinary `Clawback`, `PaymentChannelClawback` for both asset types, the `channel_authorize` and `channel_verify` methods, and the byte layout of the [Signature](#3811-signature) message. +The reference implementation adds the `PayChanToken` test suite (`src/test/app/PayChanToken_test.cpp`), which covers, for IOUs and MPTs separately: amendment enablement, the issuer opt-in flags, preflight, preclaim and apply of `PaymentChannelCreate`, `PaymentChannelFund` and `PaymentChannelClaim`, closure, auto-creation of the destination's holding, balances and metadata, the locked transfer rate, require-auth, freeze and lock, trustline limits, precision loss, the interaction with ordinary `Clawback`, `PaymentChannelClawback` for both asset types, the `channel_authorize` and `channel_verify` methods, and the byte layout of the [Signature](#3811-signature) message. Within `PayChanToken` it also covers the exact source debit on create and fund, and a delegate submitting `PaymentChannelClawback` with and without the permission. + +It also adds `InvariantsPayChan` (`src/test/app/invariants/InvariantsPayChan_test.cpp`) for the `PayChannel` invariants, `STAmount` cases for exact IOU sums and differences, `Delegate` cases for delegating `PaymentChannelClawback` and its amendment gate, and `RPCCall` cases for a token `amount` on the command line. ## 7. Reference Implementation @@ -756,10 +814,10 @@ The reference implementation adds the `PayChanToken` test suite (`src/test/app/P ## 8. Security Considerations -- **Payee protection.** The destination's earned funds are never gated by source-side state. Claims check only destination-side conditions, and the source-side conditions in [Channel Closure](#38222-channel-closure) apply only to refunding a positive remainder; a fully drained channel closes without them. +- **Payee protection.** Claims check only destination-side conditions, and the source-side conditions in [Channel Closure](#38222-channel-closure) apply only to refunding a positive remainder; a fully drained channel closes without them. A claim with `tfClose` that fails one of those conditions fails as a whole, payout included, but a claim without `tfClose` on a channel that has not expired never runs them, so the destination can always collect its earned funds that way. - **Issuer trust surface.** An issuer that uses `RequireAuth` can deauthorize the source and thereby block the refund leg of closure (`tecNO_AUTH`) until re-authorized. The channel and its locked funds remain on ledger; no funds are lost. This is the same issuer trust surface that exists for XLS-85 escrow refunds and for clawback generally. - **Transfer rate.** The claim rate is capped at the rate stored at creation (the lower of stored and current is applied), so an issuer cannot retroactively tax funds already locked by raising `TransferRate`/`TransferFee`. -- **Precision.** IOU amounts carry a 16-digit mantissa, so a sum or difference of values at very different scales can round. Fund, claim and partial clawback reject with `tecPRECISION_LOSS` unless the quantity each changes (the new `Amount` on fund and partial clawback, the paid difference on claim) and `Amount - Balance` are exact, so closure refunds exactly what was not claimed and no transaction creates or destroys value through rounding. +- **Precision.** IOU amounts carry a 16-digit mantissa, so a sum or difference of values at very different scales can round. Create and fund reject with `tecPRECISION_LOSS` unless the source's spendable balance minus the locked amount is exact, so the trustline is debited by exactly what the channel records. Fund, claim and partial clawback reject with `tecPRECISION_LOSS` unless the quantity each changes (the new `Amount` on fund and partial clawback, the paid difference on claim) and `Amount - Balance` are exact, so closure refunds exactly what was not claimed and the channel's recorded `Amount` and `Balance` always equal the value locked and paid. - **Trustline deletion.** The source can delete an empty trustline while a channel is open. A close submitted by any account other than the source then fails with `tecNO_LINE`; the source's own closing transaction re-creates the line (when authorization is not required) and receives the refund. The destination's claims are unaffected. `MPToken` deletion is blocked while locked (`tecHAS_OBLIGATIONS`). - **Signature domain.** Claim signatures bind the specific channel ID and amount, unchanged from XRP payment channels; the token layouts in [Signature](#3811-signature) also bind the currency and issuer or the `MPTokenIssuanceID`, so a signature for one asset cannot be presented against a channel holding another. - **Clawback scope.** `PaymentChannelClawback` reaches only the unclaimed remainder and only for an issuer who already holds the clawback opt-in for that token. It cannot reverse a claim the destination has settled, and it cannot touch XRP or a token whose issuer never enabled clawback. @@ -787,4 +845,4 @@ No. A vault share is an MPT issued by the vault's pseudo-account, which cannot c ### A.5: Can IOU precision create or destroy value when `Amount` or `Balance` change scale? -No. An IOU amount is a normalized value with a 16-digit mantissa, so adding or subtracting values at very different scales can round: with `Amount` at 10^20 and `Balance` at 1, `Amount - Balance` rounds back to 10^20. Every transaction that changes `Amount` or `Balance` therefore rejects with `tecPRECISION_LOSS` unless the result is exact: fund requires the new `Amount` and the new `Amount - Balance` to be exact, claim requires the paid difference and the new `Amount - Balance` to be exact, and a partial clawback requires the new `Amount` and the new `Amount - Balance` to be exact. Closure refunds `Amount - Balance`, which these checks keep exact, so the refund equals what was not claimed. +Not in the channel's accounting. An IOU amount is a normalized value with a 16-digit mantissa, so adding or subtracting values at very different scales can round: with `Amount` at 10^20 and `Balance` at 1, `Amount - Balance` rounds back to 10^20. Create and fund therefore reject with `tecPRECISION_LOSS` unless the source's spendable balance minus the locked amount is exact, so the source is debited exactly what the channel records. Every transaction that changes `Amount` or `Balance` also rejects with `tecPRECISION_LOSS` unless the result is exact: fund requires the new `Amount` and the new `Amount - Balance` to be exact, claim requires the paid difference and the new `Amount - Balance` to be exact, and a partial clawback requires the new `Amount` and the new `Amount - Balance` to be exact. Closure refunds `Amount - Balance`, which these checks keep exact, so the refund equals what was not claimed. From c42823e60a22d86f1d0c266cb302355181506959 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 6 Oct 2026 22:35:24 -0400 Subject: [PATCH 16/19] docs: XLS-93 close refund, MPT fee rounding and fund rate --- XLS-0093-token-paychan/README.md | 34 +++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/XLS-0093-token-paychan/README.md b/XLS-0093-token-paychan/README.md index dc3713d55..adfe6f6ac 100644 --- a/XLS-0093-token-paychan/README.md +++ b/XLS-0093-token-paychan/README.md @@ -350,7 +350,7 @@ As a reference, [here](https://xrpl.org/docs/references/protocol/transactions/ty - **IOU Tokens**: The funding `Amount` is deducted from the source's trustline balance. - **MPTs**: The funding `Amount` is deducted from the source's MPT balance. The `LockedAmount` is increased accordingly. 2. **Payment Channel Object Update:** - - The channel's `Amount` field is increased by the funding amount. The stored `TransferRate` is not updated by funding. + - The channel's `Amount` field is increased by the funding amount. The stored `TransferRate` is not updated by funding, so the added tokens are claimed at no more than the rate captured at creation (see [Rationale](#4-rationale)). 3. **Expired Channel:** - The channel is closed and deleted as in [Channel Closure](#3832-channel-closure), and no funds are added. @@ -447,18 +447,23 @@ A channel closes when a `PaymentChannelClaim` carries the `tfClose` flag (immedi Closure returns the remaining channel funds (`Amount` minus `Balance`) to the source. **The failure conditions below apply only when this remainder is positive.** A fully drained channel has nothing to refund, so it closes without any source-side checks. A claim with `tfClose` that fails one of them fails as a whole, payout included. A claim without `tfClose` on a channel that has not expired never runs these checks, so the destination can collect its earned funds that way regardless of the source's authorization, trustline, or freeze state. +For an IOU channel the refund depends on which account submits the closing transaction. When the source submits it, the refund can re-create the source's trustline and does not check its limit. When any other account submits it (the destination's claim with `tfClose`, or any account's claim or fund against an expired channel), the refund creates no trustline for the source and checks its limit, so the close fails with `tecNO_LINE` when the source has no trustline and with `tecLIMIT_EXCEEDED` when the refund would push the source's trustline balance above its limit, and the channel stays open. This matches the activated [XLS-85](../XLS-0085-token-escrow/README.md) (Token Escrow) implementation of `EscrowCancel`, which runs the same unlock logic: an account other than the escrow owner cancelling the escrow gets the same `tecNO_LINE` and `tecLIMIT_EXCEEDED` results. + 1. **Source Not Authorized to Hold Token:** - If authorization is required and the source is not authorized, the transaction fails with `tecNO_AUTH`. For an IOU, a source with no trustline to the issuer fails with `tecNO_LINE` when authorization is required, whichever account submits the transaction. 2. **Source Lacks Trustline or MPT Holding:** - The source's trustline or `MPToken` is created during closure only when the source itself submits the transaction (and authorization is not required), for the same reason as on a claim. - **IOU Tokens**: If the source lacks a trustline with the issuer and did not submit the transaction, the transaction fails with `tecNO_LINE`. - - **MPTs**: If the source does not hold the MPT and did not submit the transaction, the transaction fails with `tecNO_PERMISSION`. + - **MPTs**: If the source does not hold the MPT and did not submit the transaction, the transaction fails with `tecNO_PERMISSION`. A positive remainder is part of the source's `LockedAmount`, and an `MPToken` with a non-zero `LockedAmount` cannot be deleted (`tecHAS_OBLIGATIONS`), so the source's `MPToken` exists whenever there is a remainder to refund. 3. **Cannot Create Trustline or MPT Holding:** - If unable to create due to lack of reserves, the transaction fails with `tecNO_LINE_INSUF_RESERVE` (IOU) or `tecINSUFFICIENT_RESERVE` (MPT). The channel's own owner reserve is released before this check, so the source needs reserve for the new holding counted against its owner count without the channel. -4. **Source Account is Frozen or Token is Locked:** +4. **Trustline Limit Exceeded (IOU only):** + - If the transaction is not submitted by the source and the refund would push the source's trustline balance above its limit, the transaction fails with `tecLIMIT_EXCEEDED`. The source's own closing transaction does not check the limit. + +5. **Source Account is Frozen or Token is Locked:** - **IOU Tokens**: - **Deep Freeze**: The transaction succeeds, allowing the channel to be closed. - **Global/Individual Freeze**: The transaction succeeds, allowing the channel to be closed. @@ -481,7 +486,7 @@ Closure returns the remaining channel funds (`Amount` minus `Balance`) to the so 3. The `OutstandingAmount` on the `MPTokenIssuance` is decreased by the claimed amount (i.e., this claim is a "redemption"). - If the destination is not the issuer of the asset held in the channel, then: 1. The `LockedAmount` on the `MPTokenIssuance` and the source's `MPToken` is decreased by the claimed amount. - 2. The `MPTAmount` on the destination's `MPToken` is increased by the claimed amount, less any transfer fee. + 2. The `MPTAmount` on the destination's `MPToken` is increased by the claimed amount, less any transfer fee (see [Transfer Rates and Fees](#314-transfer-rates-and-fees) for its rounding). 3. The `OutstandingAmount` on the `MPTokenIssuance` is decreased by the transfer fee, the claimed amount less the amount credited to the destination; the fee is credited to no holder, so it leaves the outstanding supply. 3. **Channel Balance Update:** - The channel's `Balance` field is set to the claim's `Balance`, the cumulative amount claimed before transfer fees. @@ -489,8 +494,8 @@ Closure returns the remaining channel funds (`Amount` minus `Balance`) to the so #### 3.8.3.2. Channel Closure 1. **Auto create Trustline or MPToken:** - - **IOU Tokens**: If the IOU does not require authorization and the account submitting the transaction is the source, a trustline is created for it. - - **MPTs**: If the MPT does not require authorization and the account submitting the transaction is the source, an `MPToken` is created for it. + - **IOU Tokens**: If the IOU does not require authorization, the account submitting the transaction is the source, and the source has no trustline with the issuer, a trustline is created for it. + - **MPTs**: If the MPT does not require authorization, the account submitting the transaction is the source, and the source has no `MPToken`, an `MPToken` is created for it. 2. **Adjustment from Issuer to Source:** - No transfer fee is applied when returning remaining funds to the source. - The refund, `Amount` minus `Balance`, is exact by construction: every transaction that changes `Amount` or `Balance` keeps that difference exact (see the `PayChannel` [Invariants](#322-invariants)), so closure needs no precision check. @@ -775,13 +780,16 @@ For the channel opened by the `PaymentChannelCreate` [Example JSON](#364-example ### 3.14.1. IOU Tokens (`TransferRate`) -- **Rate Capped at Creation**: The `TransferRate` is captured at the time of `PaymentChannelCreate` and stored in the `PayChannel` object. At claim time, the lower of the stored rate and the issuer's current rate is applied: an increase by the issuer does not affect existing channels, while a decrease passes through to claims. This is identical to the behavior of the activated XLS-85 (Token Escrow) implementation, which uses the same shared unlock logic. +- **Rate Capped at Creation**: The `TransferRate` is captured at the time of `PaymentChannelCreate` and stored in the `PayChannel` object. At claim time, the lower of the stored rate and the issuer's current rate is applied: an increase by the issuer does not affect existing channels, including tokens later added to them by `PaymentChannelFund`, which does not update the stored rate, while a decrease passes through to claims. This is identical to the behavior of the activated XLS-85 (Token Escrow) implementation, which uses the same shared unlock logic. - **Fee Calculation**: The destination is credited the claimed amount divided by the applied rate (the lower of the stored and current rate, as a ratio to 1,000,000,000), rounded up to IOU precision; the fee is the claimed amount minus that credit. No fee is applied when the issuer is the destination, or when remaining funds are returned to the source at closure. ### 3.14.2. MPTs (`TransferFee`) - **Fee Capped at Creation**: The `TransferFee` is captured at the time of `PaymentChannelCreate` and stored in the `PayChannel` object, similar to IOUs, with the same lower-of-stored-and-current rule. -- **Fee Calculation**: The transfer fee is deducted from the claimed amount, reducing the final amount credited to the destination. +- **Fee Calculation**: The transfer fee is deducted from the claimed amount, reducing the amount credited to the destination. The applied rate is the lower of the stored and current rate, as a ratio to 1,000,000,000; an MPT's rate is 1,000,000,000 plus 10,000 times its `TransferFee`. MPT amounts are integers, so the credit is rounded, and the rounding depends on the `fixCleanup3_4_0` amendment: + - With `fixCleanup3_4_0` enabled, the destination is credited `floor(claimed × 1,000,000,000 / rate)`. The credit is rounded down, so any fractional fee is charged against the claimed amount. + - Without `fixCleanup3_4_0`, the destination is credited the claimed amount divided by the applied rate, rounded up, the same rule as for IOUs. + - In both cases the fee is the claimed amount minus the credit. No fee is applied when the issuer is the destination, or when remaining funds are returned to the source at closure. - **Consistent Fee Application**: Both IOUs and MPTs use the same capped-rate rule, ensuring the destination's settlement value cannot be worsened by the issuer after channel creation. ## 3.15. Future Considerations @@ -796,6 +804,8 @@ Payment channels are the last remaining XRP-only locking primitive; XLS-85 alrea `PaymentChannelClawback` is included in the same amendment for the same reason. An issuer's clawback opt-in is a property of the token, so it should hold wherever that token sits. Deferring the transaction would have meant shipping a lock that quietly suspends an issuer control the token already carries, and the alternative of closing the channel first is not open to the issuer, which is not a party to the channel and cannot close it. +`PaymentChannelFund` does not re-capture the transfer rate. The channel stores one `TransferRate` for its whole `Amount`, captured at creation and never updated, so tokens funded into an existing channel are claimed at no more than that rate even if the issuer has raised its rate since. This is accepted. The alternative, re-capturing the rate on each fund, would apply the new rate to tokens already locked in the channel, which is the retroactive fee increase the creation cap exists to prevent. + ## 5. Backwards Compatibility The change is amendment-gated. Before `TokenPaychan` is enabled, a token `Amount` on `PaymentChannelCreate` or `PaymentChannelFund`, and a token `Balance` or `Amount` on `PaymentChannelClaim`, are rejected with `temBAD_AMOUNT`, and `PaymentChannelClawback` is rejected with `temDISABLED`. Before `TokenPaychan` is enabled, a `DelegateSet` that grants the `PaymentChannelClawback` permission is rejected with `temMALFORMED`. @@ -816,9 +826,9 @@ It also adds `InvariantsPayChan` (`src/test/app/invariants/InvariantsPayChan_tes - **Payee protection.** Claims check only destination-side conditions, and the source-side conditions in [Channel Closure](#38222-channel-closure) apply only to refunding a positive remainder; a fully drained channel closes without them. A claim with `tfClose` that fails one of those conditions fails as a whole, payout included, but a claim without `tfClose` on a channel that has not expired never runs them, so the destination can always collect its earned funds that way. - **Issuer trust surface.** An issuer that uses `RequireAuth` can deauthorize the source and thereby block the refund leg of closure (`tecNO_AUTH`) until re-authorized. The channel and its locked funds remain on ledger; no funds are lost. This is the same issuer trust surface that exists for XLS-85 escrow refunds and for clawback generally. -- **Transfer rate.** The claim rate is capped at the rate stored at creation (the lower of stored and current is applied), so an issuer cannot retroactively tax funds already locked by raising `TransferRate`/`TransferFee`. +- **Transfer rate.** The claim rate is capped at the rate stored at creation (the lower of stored and current is applied), so an issuer cannot retroactively tax funds already locked by raising `TransferRate`/`TransferFee`. Funding does not re-capture the rate, so tokens added to an existing channel by `PaymentChannelFund` are also claimed at no more than the rate captured at creation, even if the issuer has raised its rate since. This is accepted: re-capturing on fund would raise the fee on tokens already locked (see [Rationale](#4-rationale)). - **Precision.** IOU amounts carry a 16-digit mantissa, so a sum or difference of values at very different scales can round. Create and fund reject with `tecPRECISION_LOSS` unless the source's spendable balance minus the locked amount is exact, so the trustline is debited by exactly what the channel records. Fund, claim and partial clawback reject with `tecPRECISION_LOSS` unless the quantity each changes (the new `Amount` on fund and partial clawback, the paid difference on claim) and `Amount - Balance` are exact, so closure refunds exactly what was not claimed and the channel's recorded `Amount` and `Balance` always equal the value locked and paid. -- **Trustline deletion.** The source can delete an empty trustline while a channel is open. A close submitted by any account other than the source then fails with `tecNO_LINE`; the source's own closing transaction re-creates the line (when authorization is not required) and receives the refund. The destination's claims are unaffected. `MPToken` deletion is blocked while locked (`tecHAS_OBLIGATIONS`). +- **Trustline deletion and limit.** The source can delete an empty trustline while a channel is open. A close submitted by any account other than the source then fails with `tecNO_LINE`, and also fails with `tecLIMIT_EXCEEDED` when the refund would push the source's trustline balance above its limit; a claim with `tfClose` that hits either fails as a whole, payout included. The source's own closing transaction re-creates the line (when authorization is not required), skips the limit check and receives the refund. This matches the activated XLS-85 implementation of `EscrowCancel` for a canceller other than the escrow owner: the refund never creates a trustline for, or credits past the trustline limit of, a source that did not submit the transaction. The destination's claims without `tfClose` are unaffected. `MPToken` deletion is blocked while locked (`tecHAS_OBLIGATIONS`). - **Signature domain.** Claim signatures bind the specific channel ID and amount, unchanged from XRP payment channels; the token layouts in [Signature](#3811-signature) also bind the currency and issuer or the `MPTokenIssuanceID`, so a signature for one asset cannot be presented against a channel holding another. - **Clawback scope.** `PaymentChannelClawback` reaches only the unclaimed remainder and only for an issuer who already holds the clawback opt-in for that token. It cannot reverse a claim the destination has settled, and it cannot touch XRP or a token whose issuer never enabled clawback. - **Clawback and pending authorizations.** An issuer clawback lowers the ceiling on the `Balance` a claim can request. A signed authorization for more than the reduced `Amount` still pays out any `Balance` up to that `Amount`; only the part above it can no longer be claimed. A destination that wants to settle ahead of this should claim rather than accumulate authorizations, the same tradeoff that applies to holding an unsettled balance with any clawback-enabled issuer. @@ -833,11 +843,11 @@ Create runs the same lock-creation check as XLS-85 escrows, which rejects any fr ### A.2: Why does a claim apply the lower of the stored and the current transfer rate? -The rate stored at creation is a cap, so an issuer cannot raise the fee on value already locked, and a later decrease passes through to the destination. The activated XLS-85 escrows apply the same rule through the same shared unlock logic. +The rate stored at creation is a cap, so an issuer cannot raise the fee on value already locked, and a later decrease passes through to the destination. `PaymentChannelFund` does not re-capture the rate, so the cap also covers tokens added after creation; re-capturing it would raise the fee on tokens already locked. The activated XLS-85 escrows apply the same rule through the same shared unlock logic. ### A.3: Can the source delete its trust line while tokens are locked in a channel? -Yes, for an IOU. The locked value lives in the `PayChannel` entry, not on the trust line, so an empty trust line can be deleted. A close submitted by any account other than the source then fails with `tecNO_LINE`; the source's own closing transaction re-creates the line (when authorization is not required) and receives the refund. The destination's claims are unaffected. An `MPToken` cannot be deleted while its `LockedAmount` is non-zero (`tecHAS_OBLIGATIONS`). Per-trust-line lock accounting is left to a future amendment that would cover escrows as well. +Yes, for an IOU. The locked value lives in the `PayChannel` entry, not on the trust line, so an empty trust line can be deleted. A close submitted by any account other than the source then fails with `tecNO_LINE`; the source's own closing transaction re-creates the line (when authorization is not required) and receives the refund. A close by another account also fails with `tecLIMIT_EXCEEDED` when the refund would push the source's trustline balance above its limit, a check the source's own close skips. A claim with `tfClose` that hits either failure fails as a whole, payout included. This matches the activated XLS-85 implementation of `EscrowCancel` for a canceller other than the escrow owner. The destination's claims without `tfClose` are unaffected. An `MPToken` cannot be deleted while its `LockedAmount` is non-zero (`tecHAS_OBLIGATIONS`). Per-trust-line lock accounting is left to a future amendment that would cover escrows as well. ### A.4: Can vault shares be locked in a payment channel? From fd94952656c25d19f61afdb26c71c50b92063543 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 7 Oct 2026 11:08:12 -0400 Subject: [PATCH 17/19] docs: XLS-93 credit precision matches escrow --- XLS-0093-token-paychan/README.md | 48 +++++++++++++++++--------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/XLS-0093-token-paychan/README.md b/XLS-0093-token-paychan/README.md index adfe6f6ac..576f82689 100644 --- a/XLS-0093-token-paychan/README.md +++ b/XLS-0093-token-paychan/README.md @@ -9,7 +9,7 @@ category: Amendment requires: XLS-33, XLS-39, XLS-85 created: 2025-05-24 - updated: 2026-10-06 + updated: 2026-10-07 # Token-Enabled Payment Channels @@ -440,6 +440,7 @@ When the destination is the issuer of the channel's token, none of the authoriza 7. **Inexact Payout or Remainder (IOU only):** - After the existing checks that `Balance` is above the channel's `Balance` and at most its `Amount` (`tecUNFUNDED_PAYMENT`), the amount this claim pays (`Balance` minus the channel's current `Balance`) and the remainder it leaves in the channel (the channel's `Amount` minus `Balance`) must both be exact, as defined in the `PayChannel` [Invariants](#322-invariants). Otherwise the transaction fails with `tecPRECISION_LOSS`. + - These checks cover the channel's own amounts. The credit to the destination's trustline is not checked and follows IOU balance precision (see [Credit Precision](#3141-iou-tokens-transferrate)). ##### 3.8.2.2.2. Channel Closure @@ -478,7 +479,7 @@ For an IOU channel the refund depends on which account submits the closing trans - **IOU Tokens**: If the IOU does not require authorization and the account submitting the transaction is the destination, a trustline is created for it. - **MPTs**: If the MPT does not require authorization and the account submitting the transaction is the destination, an `MPToken` is created for it. 2. **Adjustment from Issuer to Destination:** - - **IOU Tokens**: The claimed amount, less any transfer fee (see [Transfer Rates and Fees](#314-transfer-rates-and-fees)), is added to the destination's trustline balance. If the destination is the issuer, the claimed amount is simply redeemed. + - **IOU Tokens**: The claimed amount, less any transfer fee (see [Transfer Rates and Fees](#314-transfer-rates-and-fees)), is added to the destination's trustline balance, at that balance's IOU precision (see [Credit Precision](#3141-iou-tokens-transferrate)). If the destination is the issuer, the claimed amount is simply redeemed. - **MPTs**: - If the destination is the issuer of the asset held in the channel, then: 1. The `LockedAmount` on the `MPTokenIssuance` and the source's `MPToken` is decreased by the claimed amount. @@ -498,8 +499,8 @@ For an IOU channel the refund depends on which account submits the closing trans - **MPTs**: If the MPT does not require authorization, the account submitting the transaction is the source, and the source has no `MPToken`, an `MPToken` is created for it. 2. **Adjustment from Issuer to Source:** - No transfer fee is applied when returning remaining funds to the source. - - The refund, `Amount` minus `Balance`, is exact by construction: every transaction that changes `Amount` or `Balance` keeps that difference exact (see the `PayChannel` [Invariants](#322-invariants)), so closure needs no precision check. - - **IOU Tokens**: Any remaining channel funds are added to the source's trustline balance. + - The refund, `Amount` minus `Balance`, is exact by construction: every transaction that changes `Amount` or `Balance` keeps that difference exact (see the `PayChannel` [Invariants](#322-invariants)). + - **IOU Tokens**: Any remaining channel funds are added to the source's trustline balance, at that balance's IOU precision. Closure does not check this credit, so a refund much smaller than the source's balance rounds, exactly as an XLS-85 `EscrowCancel` refund does (see [Credit Precision](#3141-iou-tokens-transferrate)). - **MPTs**: Any remaining channel funds are added to the source's MPT balance. The `OutstandingAmount` of the MPT issuance remains unchanged. The `LockedAmount` is decreased on both the source's `MPToken` and the `MPTokenIssuance`. 3. **Deletion of Payment Channel Object:** - The `PayChannel` object is deleted after successful closure. @@ -759,22 +760,22 @@ For the channel opened by the `PaymentChannelCreate` [Example JSON](#364-example ## 3.13. Key Differences Between XRP, IOU and MPT Payment Channels -| Aspect | XRP | IOU Tokens | Multi-Purpose Tokens (MPTs) | -| ----------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Trustlines** | Not used | Required between accounts and issuer | Not used | -| **Issuer Flag for Channels** | N/A | `lsfAllowTrustLineLocking` (account flag) | `lsfMPTCanEscrow` (issuance flag) | -| **Transfer Flags** | N/A | N/A | `lsfMPTCanTransfer` must be enabled for payment channels unless the destination is the issuer | -| **Require Auth** | N/A | Applicable (`lsfRequireAuth`); accounts must be authorized prior to holding tokens | Applicable (`lsfMPTRequireAuth`); accounts must be authorized prior to holding tokens | -| **Destination Authorization** | N/A | Required at creation and at claim unless the destination is the issuer; cannot be granted during claim if authorization required | Required at creation and at claim unless the destination is the issuer; cannot be granted during claim if authorization required | -| **Freeze/Lock Conditions** | N/A | Any freeze blocks create/fund; **Deep Freeze** prevents claims, but allows closure; Global/Individual Freeze allows claims and closure | Lock blocks create/fund; **Lock Conditions (Deep Freeze Equivalent)** prevent claims, but allow closure | -| **Transfer Rates/Fees** | N/A | `TransferRate` stored at creation and applied during claims | `TransferFee` stored at creation and applied during claims | -| **Precision** | Integer drops | `tecPRECISION_LOSS` when create or fund would debit the source's trustline inexactly, or when fund, claim or partial clawback would leave an inexact `Amount` or `Amount - Balance` | Integer amounts; a fund whose sum overflows fails with `tecPRECISION_LOSS` | -| **Clawback Opt-In** | Not clawable (`tecNO_PERMISSION`) | `lsfAllowTrustLineClawback` (account flag), and `lsfNoFreeze` must not be set | `lsfMPTCanClawback` (issuance flag) | -| **Clawback Accounting** | N/A | Channel `Amount` is reduced; no trustline changes | `LockedAmount` and `OutstandingAmount` are reduced | -| **Outstanding Amount** | N/A | N/A | Unchanged by create, fund and closure refund; decreased by the transfer fee on a claim, by the claimed amount on a claim to the issuer, and by the clawed amount on clawback | -| **Signature Message** | 44 bytes, the drops as an unsigned 64-bit integer | 84 bytes, the IOU `Amount` serialization | 69 bytes, the MPT `Amount` serialization | -| **Account Deletion** | Payment channels prevent account deletion | Payment channels prevent deletion of the source and destination accounts, and of the issuer's account while a channel is in its owner directory (`IssuerNode`) | Payment channels prevent account deletion | -| **Holding Deletion** | N/A | Trustline deletion is NOT blocked by open channels (locked value lives in the channel object); a close submitted by any account other than the source then fails with `tecNO_LINE`, while the source's own closing transaction re-creates the line (when authorization is not required) and receives the refund | `MPToken` deletion is blocked while `LockedAmount` is non-zero (`tecHAS_OBLIGATIONS`) | +| Aspect | XRP | IOU Tokens | Multi-Purpose Tokens (MPTs) | +| ----------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Trustlines** | Not used | Required between accounts and issuer | Not used | +| **Issuer Flag for Channels** | N/A | `lsfAllowTrustLineLocking` (account flag) | `lsfMPTCanEscrow` (issuance flag) | +| **Transfer Flags** | N/A | N/A | `lsfMPTCanTransfer` must be enabled for payment channels unless the destination is the issuer | +| **Require Auth** | N/A | Applicable (`lsfRequireAuth`); accounts must be authorized prior to holding tokens | Applicable (`lsfMPTRequireAuth`); accounts must be authorized prior to holding tokens | +| **Destination Authorization** | N/A | Required at creation and at claim unless the destination is the issuer; cannot be granted during claim if authorization required | Required at creation and at claim unless the destination is the issuer; cannot be granted during claim if authorization required | +| **Freeze/Lock Conditions** | N/A | Any freeze blocks create/fund; **Deep Freeze** prevents claims, but allows closure; Global/Individual Freeze allows claims and closure | Lock blocks create/fund; **Lock Conditions (Deep Freeze Equivalent)** prevent claims, but allow closure | +| **Transfer Rates/Fees** | N/A | `TransferRate` stored at creation and applied during claims | `TransferFee` stored at creation and applied during claims | +| **Precision** | Integer drops | `tecPRECISION_LOSS` when create or fund would debit the source's trustline inexactly, or when fund, claim or partial clawback would leave an inexact `Amount` or `Amount - Balance`; the credit to the receiver's trustline on claim and closure is not checked and follows that balance's 16-digit precision, as XLS-85 escrow credits do | Integer amounts; a fund whose sum overflows fails with `tecPRECISION_LOSS` | +| **Clawback Opt-In** | Not clawable (`tecNO_PERMISSION`) | `lsfAllowTrustLineClawback` (account flag), and `lsfNoFreeze` must not be set | `lsfMPTCanClawback` (issuance flag) | +| **Clawback Accounting** | N/A | Channel `Amount` is reduced; no trustline changes | `LockedAmount` and `OutstandingAmount` are reduced | +| **Outstanding Amount** | N/A | N/A | Unchanged by create, fund and closure refund; decreased by the transfer fee on a claim, by the claimed amount on a claim to the issuer, and by the clawed amount on clawback | +| **Signature Message** | 44 bytes, the drops as an unsigned 64-bit integer | 84 bytes, the IOU `Amount` serialization | 69 bytes, the MPT `Amount` serialization | +| **Account Deletion** | Payment channels prevent account deletion | Payment channels prevent deletion of the source and destination accounts, and of the issuer's account while a channel is in its owner directory (`IssuerNode`) | Payment channels prevent account deletion | +| **Holding Deletion** | N/A | Trustline deletion is NOT blocked by open channels (locked value lives in the channel object); a close submitted by any account other than the source then fails with `tecNO_LINE`, while the source's own closing transaction re-creates the line (when authorization is not required) and receives the refund | `MPToken` deletion is blocked while `LockedAmount` is non-zero (`tecHAS_OBLIGATIONS`) | ## 3.14. Transfer Rates and Fees @@ -782,6 +783,7 @@ For the channel opened by the `PaymentChannelCreate` [Example JSON](#364-example - **Rate Capped at Creation**: The `TransferRate` is captured at the time of `PaymentChannelCreate` and stored in the `PayChannel` object. At claim time, the lower of the stored rate and the issuer's current rate is applied: an increase by the issuer does not affect existing channels, including tokens later added to them by `PaymentChannelFund`, which does not update the stored rate, while a decrease passes through to claims. This is identical to the behavior of the activated XLS-85 (Token Escrow) implementation, which uses the same shared unlock logic. - **Fee Calculation**: The destination is credited the claimed amount divided by the applied rate (the lower of the stored and current rate, as a ratio to 1,000,000,000), rounded up to IOU precision; the fee is the claimed amount minus that credit. No fee is applied when the issuer is the destination, or when remaining funds are returned to the source at closure. +- **Credit Precision**: The credit to the receiver's trustline, the destination's on a claim and the source's on a closure refund, is added to that trustline's balance, which keeps IOU precision: the new balance is rounded to a 16-digit mantissa. No precision check applies to the credit, so when the credit is much smaller than the receiver's balance, the balance does not grow by exactly the credit: a refund of 1 to a source whose balance is 10^20 leaves the balance at 10^20. The channel's own `Amount` and `Balance` stay exact (see the `PayChannel` [Invariants](#322-invariants)); only the receiver's trustline balance rounds. This is exactly how XLS-85 escrow refunds and finishes behave in the activated implementation: `EscrowCancel`, `EscrowFinish`, `PaymentChannelClaim` and channel closure all credit through the same shared unlock logic (`escrowUnlockApplyHelper` in rippled's `src/libxrpl/ledger/helpers/EscrowHelpers.cpp`, which credits through `directSendNoFee` with no precision check). ### 3.14.2. MPTs (`TransferFee`) @@ -816,7 +818,7 @@ XRP channels are unchanged: the [Signature](#3811-signature) message for an XRP The reference implementation adds the `PayChanToken` test suite (`src/test/app/PayChanToken_test.cpp`), which covers, for IOUs and MPTs separately: amendment enablement, the issuer opt-in flags, preflight, preclaim and apply of `PaymentChannelCreate`, `PaymentChannelFund` and `PaymentChannelClaim`, closure, auto-creation of the destination's holding, balances and metadata, the locked transfer rate, require-auth, freeze and lock, trustline limits, precision loss, the interaction with ordinary `Clawback`, `PaymentChannelClawback` for both asset types, the `channel_authorize` and `channel_verify` methods, and the byte layout of the [Signature](#3811-signature) message. Within `PayChanToken` it also covers the exact source debit on create and fund, and a delegate submitting `PaymentChannelClawback` with and without the permission. -It also adds `InvariantsPayChan` (`src/test/app/invariants/InvariantsPayChan_test.cpp`) for the `PayChannel` invariants, `STAmount` cases for exact IOU sums and differences, `Delegate` cases for delegating `PaymentChannelClawback` and its amendment gate, and `RPCCall` cases for a token `amount` on the command line. +It also adds `InvariantsPayChan` (`src/test/app/invariants/InvariantsPayChan_test.cpp`) for the `PayChannel` invariants, `STAmount` cases for exact IOU sums and differences, `EscrowToken` cases showing that an IOU escrow refund or finish credit rounds at the receiver's balance precision as described in [Credit Precision](#3141-iou-tokens-transferrate), `Delegate` cases for delegating `PaymentChannelClawback` and its amendment gate, and `RPCCall` cases for a token `amount` on the command line. ## 7. Reference Implementation @@ -827,7 +829,7 @@ It also adds `InvariantsPayChan` (`src/test/app/invariants/InvariantsPayChan_tes - **Payee protection.** Claims check only destination-side conditions, and the source-side conditions in [Channel Closure](#38222-channel-closure) apply only to refunding a positive remainder; a fully drained channel closes without them. A claim with `tfClose` that fails one of those conditions fails as a whole, payout included, but a claim without `tfClose` on a channel that has not expired never runs them, so the destination can always collect its earned funds that way. - **Issuer trust surface.** An issuer that uses `RequireAuth` can deauthorize the source and thereby block the refund leg of closure (`tecNO_AUTH`) until re-authorized. The channel and its locked funds remain on ledger; no funds are lost. This is the same issuer trust surface that exists for XLS-85 escrow refunds and for clawback generally. - **Transfer rate.** The claim rate is capped at the rate stored at creation (the lower of stored and current is applied), so an issuer cannot retroactively tax funds already locked by raising `TransferRate`/`TransferFee`. Funding does not re-capture the rate, so tokens added to an existing channel by `PaymentChannelFund` are also claimed at no more than the rate captured at creation, even if the issuer has raised its rate since. This is accepted: re-capturing on fund would raise the fee on tokens already locked (see [Rationale](#4-rationale)). -- **Precision.** IOU amounts carry a 16-digit mantissa, so a sum or difference of values at very different scales can round. Create and fund reject with `tecPRECISION_LOSS` unless the source's spendable balance minus the locked amount is exact, so the trustline is debited by exactly what the channel records. Fund, claim and partial clawback reject with `tecPRECISION_LOSS` unless the quantity each changes (the new `Amount` on fund and partial clawback, the paid difference on claim) and `Amount - Balance` are exact, so closure refunds exactly what was not claimed and the channel's recorded `Amount` and `Balance` always equal the value locked and paid. +- **Precision.** IOU amounts carry a 16-digit mantissa, so a sum or difference of values at very different scales can round. Create and fund reject with `tecPRECISION_LOSS` unless the source's spendable balance minus the locked amount is exact, so the trustline is debited by exactly what the channel records. Fund, claim and partial clawback reject with `tecPRECISION_LOSS` unless the quantity each changes (the new `Amount` on fund and partial clawback, the paid difference on claim) and `Amount - Balance` are exact, so closure unlocks exactly what was not claimed and the channel's recorded `Amount` and `Balance` always equal the value locked and paid out of it. The credit of a payout or refund to the receiver's trustline is not checked: it follows IOU balance precision and rounds when it is much smaller than the receiver's balance, exactly as XLS-85 escrow refunds and finishes do through the same unlock logic (see [Credit Precision](#3141-iou-tokens-transferrate)). - **Trustline deletion and limit.** The source can delete an empty trustline while a channel is open. A close submitted by any account other than the source then fails with `tecNO_LINE`, and also fails with `tecLIMIT_EXCEEDED` when the refund would push the source's trustline balance above its limit; a claim with `tfClose` that hits either fails as a whole, payout included. The source's own closing transaction re-creates the line (when authorization is not required), skips the limit check and receives the refund. This matches the activated XLS-85 implementation of `EscrowCancel` for a canceller other than the escrow owner: the refund never creates a trustline for, or credits past the trustline limit of, a source that did not submit the transaction. The destination's claims without `tfClose` are unaffected. `MPToken` deletion is blocked while locked (`tecHAS_OBLIGATIONS`). - **Signature domain.** Claim signatures bind the specific channel ID and amount, unchanged from XRP payment channels; the token layouts in [Signature](#3811-signature) also bind the currency and issuer or the `MPTokenIssuanceID`, so a signature for one asset cannot be presented against a channel holding another. - **Clawback scope.** `PaymentChannelClawback` reaches only the unclaimed remainder and only for an issuer who already holds the clawback opt-in for that token. It cannot reverse a claim the destination has settled, and it cannot touch XRP or a token whose issuer never enabled clawback. @@ -855,4 +857,4 @@ No. A vault share is an MPT issued by the vault's pseudo-account, which cannot c ### A.5: Can IOU precision create or destroy value when `Amount` or `Balance` change scale? -Not in the channel's accounting. An IOU amount is a normalized value with a 16-digit mantissa, so adding or subtracting values at very different scales can round: with `Amount` at 10^20 and `Balance` at 1, `Amount - Balance` rounds back to 10^20. Create and fund therefore reject with `tecPRECISION_LOSS` unless the source's spendable balance minus the locked amount is exact, so the source is debited exactly what the channel records. Every transaction that changes `Amount` or `Balance` also rejects with `tecPRECISION_LOSS` unless the result is exact: fund requires the new `Amount` and the new `Amount - Balance` to be exact, claim requires the paid difference and the new `Amount - Balance` to be exact, and a partial clawback requires the new `Amount` and the new `Amount - Balance` to be exact. Closure refunds `Amount - Balance`, which these checks keep exact, so the refund equals what was not claimed. +Not in the channel's accounting. An IOU amount is a normalized value with a 16-digit mantissa, so adding or subtracting values at very different scales can round: with `Amount` at 10^20 and `Balance` at 1, `Amount - Balance` rounds back to 10^20. Create and fund therefore reject with `tecPRECISION_LOSS` unless the source's spendable balance minus the locked amount is exact, so the source is debited exactly what the channel records. Every transaction that changes `Amount` or `Balance` also rejects with `tecPRECISION_LOSS` unless the result is exact: fund requires the new `Amount` and the new `Amount - Balance` to be exact, claim requires the paid difference and the new `Amount - Balance` to be exact, and a partial clawback requires the new `Amount` and the new `Amount - Balance` to be exact. Closure unlocks `Amount - Balance`, which these checks keep exact, so the refund equals what was not claimed. The one step not checked is the credit of a payout or refund to the receiver's trustline: it follows that balance's 16-digit precision, so a refund of 1 to a source whose balance is 10^20 leaves the balance at 10^20, exactly as an XLS-85 escrow refund or finish does (see [Credit Precision](#3141-iou-tokens-transferrate)). From 09750b0031da5c88b112b784f6a40ca236c2d634 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 15 Oct 2026 20:53:52 -0400 Subject: [PATCH 18/19] docs: XLS-93 invariant that a channel's asset never changes --- XLS-0093-token-paychan/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/XLS-0093-token-paychan/README.md b/XLS-0093-token-paychan/README.md index 576f82689..3f44ed34a 100644 --- a/XLS-0093-token-paychan/README.md +++ b/XLS-0093-token-paychan/README.md @@ -75,6 +75,8 @@ These invariants apply to every `PayChannel` whose `Amount` is not XRP, once `To - `.Balance <= '.Balance` (the paid-out balance only increases). - If `Amount` is an IOU, the difference `'.Amount - '.Balance` is exact. An IOU difference `a - b` is exact only if adding `b` back to it gives `a` and subtracting it from `a` gives `b`. XRP and MPT differences are integers and always exact. +Once `TokenPaychan` is enabled, for every `PayChannel`, XRP channels included: `.Amount` and `'.Amount` are the same asset (currency and issuer, or `MPTokenIssuanceID`); a channel's asset never changes. + ### 3.2.3. Example JSON ```json From a7a858355a606aad58ff69f35237fe6059cb1d45 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Fri, 16 Oct 2026 12:12:17 -0400 Subject: [PATCH 19/19] docs: bring XLS-93 in line with the implementation --- XLS-0093-token-paychan/README.md | 212 +++++++++++++++++++++++-------- 1 file changed, 159 insertions(+), 53 deletions(-) diff --git a/XLS-0093-token-paychan/README.md b/XLS-0093-token-paychan/README.md index 3f44ed34a..6aef29dd2 100644 --- a/XLS-0093-token-paychan/README.md +++ b/XLS-0093-token-paychan/README.md @@ -9,7 +9,7 @@ category: Amendment requires: XLS-33, XLS-39, XLS-85 created: 2025-05-24 - updated: 2026-10-07 + updated: 2026-10-16 # Token-Enabled Payment Channels @@ -75,7 +75,12 @@ These invariants apply to every `PayChannel` whose `Amount` is not XRP, once `To - `.Balance <= '.Balance` (the paid-out balance only increases). - If `Amount` is an IOU, the difference `'.Amount - '.Balance` is exact. An IOU difference `a - b` is exact only if adding `b` back to it gives `a` and subtracting it from `a` gives `b`. XRP and MPT differences are integers and always exact. -Once `TokenPaychan` is enabled, for every `PayChannel`, XRP channels included: `.Amount` and `'.Amount` are the same asset (currency and issuer, or `MPTokenIssuanceID`); a channel's asset never changes. +Once `TokenPaychan` is enabled, for every `PayChannel`, XRP channels included: + +- `.Amount` and `'.Amount` are the same asset (currency and issuer, or `MPTokenIssuanceID`); a channel's asset never changes. +- If `'.Amount < .Amount`, the transaction is a `PaymentChannelClawback` and `'.Balance = .Balance`: only a clawback lowers a channel's `Amount`, and it leaves `Balance` unchanged. + +The existing invariant that no XRP is created counts a channel's `Amount - Balance` toward the XRP total only when `Amount` is XRP; a token channel holds no XRP. ### 3.2.3. Example JSON @@ -123,7 +128,7 @@ This spec adds no fields. It extends the meaning of the `LockedAmount` field int ### 3.3.2. Invariants -This spec adds no invariant and relaxes one: once `TokenPaychan` is enabled, a `PaymentChannelClaim` may create at most one `MPToken` (the holding auto-created for the account that submits it, see [Claim with `Balance`](#3831-claim-with-balance) and [Channel Closure](#3832-channel-closure)), provided it creates or deletes no `MPTokenIssuance` and deletes no `MPToken`. The existing invariants that bound `LockedAmount` are: +This spec adds no invariant and relaxes one: once `TokenPaychan` is enabled, a `PaymentChannelClaim` may create at most one `MPToken`, and only one held by the account that submits it (the holding auto-created for a destination that submits a claim, see [Claim with `Balance`](#3831-claim-with-balance)), provided it creates or deletes no `MPTokenIssuance` and deletes no `MPToken`. The existing invariants that bound `LockedAmount` are: - `0 <= '.MPTAmount` and `'.MPTAmount` does not exceed the maximum MPT amount. - `0 <= '.LockedAmount` and `'.LockedAmount` does not exceed the maximum MPT amount. @@ -238,45 +243,55 @@ As a reference, [here](https://xrpl.org/docs/references/protocol/transactions/ty 1. `Amount` is an MPT and the `MPTokensV1` amendment is not enabled. (`temDISABLED`) 2. `Amount` is a token and the `TokenPaychan` amendment is not enabled. (`temBAD_AMOUNT`) -3. `Amount` is an IOU that is not positive. (`temBAD_AMOUNT`) -4. `Amount` is an IOU whose currency code is XRP. (`temBAD_CURRENCY`) -5. `Amount` is an MPT that is not positive or exceeds the maximum MPT amount. (`temBAD_AMOUNT`) +3. `Amount` is a token and the `fixCleanup3_2_0` or `fixTokenEscrowV1` amendment is not enabled. (`temDISABLED`) +4. `Amount` is an IOU that is not positive. (`temBAD_AMOUNT`) +5. `Amount` is an IOU whose currency code is XRP. (`temBAD_CURRENCY`) +6. `Amount` is an MPT that is not positive or exceeds the maximum MPT amount. (`temBAD_AMOUNT`) #### 3.6.2.2. Protocol-Level Failures -1. **Issuer is the Source:** +For a token `Amount`, conditions 1 to 9 are checked in the order listed, after the existing checks on the destination account. + +1. **Issuer is a Pseudo-Account (MPT only):** + - If `Amount` is an MPT whose issuer is a pseudo-account, such as a vault share, the transaction fails with `tecWRONG_ASSET`. This matches the rule `AMMCreate` applies to vault shares (see [Appendix A.4](#a4-can-vault-shares-be-locked-in-a-payment-channel)). + +2. **Issuer is the Source:** - If the source account is the issuer of the token, the transaction fails with `tecNO_PERMISSION`. -2. **Issuer Does Not Allow Token Locking or Transfer:** - - **IOU Tokens**: If the issuer's account does not have the `lsfAllowTrustLineLocking` flag set, the transaction fails with `tecNO_PERMISSION`. If the issuer's account does not exist, the transaction fails with `tecNO_ISSUER`. +3. **Issuer Does Not Allow Token Locking:** + - **IOU Tokens**: If the issuer's account does not exist, the transaction fails with `tecNO_ISSUER`. If the issuer's account does not have the `lsfAllowTrustLineLocking` flag set, the transaction fails with `tecNO_PERMISSION`. A pseudo-account, such as the AMM account that issues LP tokens, never has this flag set, so an IOU it issues fails with `tecNO_PERMISSION`. - **MPTs**: - If the `MPTokenIssuance` of the token being used does not exist, the transaction fails with `tecOBJECT_NOT_FOUND`. - If the `MPTokenIssuance` of the token being used lacks the `lsfMPTCanEscrow` flag, the transaction fails with `tecNO_PERMISSION`. - - If the `MPTokenIssuance` of the token being used lacks the `lsfMPTCanTransfer` flag, the transaction fails with `tecNO_AUTH` unless the destination address of the Payment Channel is the issuer of the MPT. -3. **Issuer is a Pseudo-Account (MPT only):** - - If `Amount` is an MPT whose issuer is a pseudo-account, such as a vault share, the transaction fails with `tecWRONG_ASSET`. This matches the rule `AMMCreate` applies to vault shares (see [Appendix A.4](#a4-can-vault-shares-be-locked-in-a-payment-channel)). +4. **Source Account's Token Holding Issues:** + - **IOU Tokens**: If the source lacks a trustline with the issuer, the transaction fails with `tecNO_LINE`. If the source owes the issuer on that trustline, the transaction fails with `tecNO_PERMISSION`. + - **MPTs**: If the source does not hold the MPT, the transaction fails with `tecOBJECT_NOT_FOUND`. -4. **Source or Destination Not Authorized to Hold Token:** - - If the issuer requires authorization and either the source or the destination is not authorized, the transaction fails with `tecNO_AUTH`. For an IOU, a destination with no trustline to the issuer fails with `tecNO_LINE` when authorization is required. +5. **Source or Destination Not Authorized to Hold Token:** + - If the issuer requires authorization and either the source or the destination is not authorized, the transaction fails with `tecNO_AUTH`. The source is checked first. For an IOU, a destination with no trustline to the issuer fails with `tecNO_LINE` when authorization is required. + - For an MPT whose `MPTokenIssuance` has a `DomainID`, an account holding valid credentials that the permissioned domain accepts is authorized, whether or not it has an `MPToken`. - The issuer is always authorized for its own token, so a destination that is the issuer never fails this check. -5. **Source Account's Token Holding Issues:** - - **IOU Tokens**: If the source lacks a trustline with the issuer, the transaction fails with `tecNO_LINE`. - - **MPTs**: If the source does not hold the MPT, the transaction fails with `tecOBJECT_NOT_FOUND`. - 6. **Source or Destination is Frozen or Token is Locked:** - **IOU Tokens**: If the token is frozen (global/individual/deepfreeze) for the source or the destination, the transaction fails with `tecFROZEN`. - **MPTs**: If the token is locked for the source or the destination, the transaction fails with `tecLOCKED`. - Note: this is deliberately stricter than base freeze semantics, under which an individual freeze only prevents the frozen holder from sending. Opening a lock is blocked by any freeze on either party, while paying out to the destination at claim time only requires that the destination is not deep frozen (see [Claim with `Balance`](#38221-claim-with-balance)). This matches the lock creation rules of [XLS-85](../XLS-0085-token-escrow/README.md). - The destination MAY be the issuer of the token; claims on such a channel redeem tokens back to the issuer. Because the freeze check above applies to the destination unconditionally, no channel can be created while the issuer has a global freeze in effect, including a channel whose destination is the issuer. This is intentionally stricter than direct payments, which always permit redemption to the issuer. -7. **Insufficient Spendable Balance:** +7. **Token Not Transferable (MPT only):** + - If the `MPTokenIssuance` of the token being used lacks the `lsfMPTCanTransfer` flag, the transaction fails with `tecNO_AUTH` unless the destination address of the Payment Channel is the issuer of the MPT. + +8. **Insufficient Spendable Balance:** - If the source account lacks sufficient spendable balance, the transaction fails with `tecINSUFFICIENT_FUNDS`. -8. **Inexact Source Debit (IOU only):** +9. **Inexact Source Debit (IOU only):** - Locking `Amount` debits the source's trustline, which must decrease by exactly `Amount`. If the source's spendable balance minus `Amount` is not exact, as defined in the `PayChannel` [Invariants](#322-invariants), or `Amount` cannot be added to that balance within the precision tolerance shared with XLS-85 escrows, the transaction fails with `tecPRECISION_LOSS`. XRP and MPT amounts are integers and are not subject to this check. +10. **Insufficient XRP Reserve:** + - A token channel locks no XRP, but it is an object the source owns, so the source must meet the XRP reserve for its `OwnerCount` plus one, measured against its XRP balance before the transaction fee, as for an XRP channel. Otherwise the transaction fails with `tecINSUFFICIENT_RESERVE`. When the `Sponsor` amendment is enabled and a sponsor covers the channel's reserve, the sponsor's reserve is checked instead. + - For an XRP channel, the source's XRP balance before the transaction fee, minus the XRP locked, must also meet the source's reserve for its `OwnerCount` plus one, or for its `OwnerCount` alone when a sponsor covers the channel's reserve. Otherwise the transaction fails with `tecUNFUNDED`. A token channel, sponsored or not, has no such check. + ### 3.6.3. State Changes 1. **Adjustment from Source to Issuer:** @@ -288,6 +303,10 @@ As a reference, [here](https://xrpl.org/docs/references/protocol/transactions/ty - `Balance`: Amount already claimed (starts at zero). - `TransferRate`: `TransferRate` (IOUs) or `TransferFee` (MPTs) at creation. Only stored when it differs from parity (no fee). - `IssuerNode`: Reference to the issuer's ledger node. Only present for IOU channels where the issuer is neither the source nor the destination. +3. **Directory Placement and Owner Count:** + - The channel is added to the owner directories of the source and the destination, as for an XRP channel, and the source's `OwnerCount` increases by one. + - An IOU channel whose destination is not the issuer is also added to the issuer's owner directory, and the page is stored in `IssuerNode`. The issuer's `OwnerCount` does not change. + - An MPT channel is not added to the issuer's owner directory. ### 3.6.4. Example JSON @@ -326,9 +345,10 @@ As a reference, [here](https://xrpl.org/docs/references/protocol/transactions/ty 1. `Amount` is an MPT and the `MPTokensV1` amendment is not enabled. (`temDISABLED`) 2. `Amount` is a token and the `TokenPaychan` amendment is not enabled. (`temBAD_AMOUNT`) -3. `Amount` is an IOU that is not positive. (`temBAD_AMOUNT`) -4. `Amount` is an IOU whose currency code is XRP. (`temBAD_CURRENCY`) -5. `Amount` is an MPT that is not positive or exceeds the maximum MPT amount. (`temBAD_AMOUNT`) +3. `Amount` is a token and the `fixCleanup3_2_0` or `fixTokenEscrowV1` amendment is not enabled. (`temDISABLED`) +4. `Amount` is an IOU that is not positive. (`temBAD_AMOUNT`) +5. `Amount` is an IOU whose currency code is XRP. (`temBAD_CURRENCY`) +6. `Amount` is an MPT that is not positive or exceeds the maximum MPT amount. (`temBAD_AMOUNT`) #### 3.7.2.2. Protocol-Level Failures @@ -346,6 +366,9 @@ As a reference, [here](https://xrpl.org/docs/references/protocol/transactions/ty - The remainder the channel holds after funding, the new `Amount` minus the channel's `Balance`, must also be exact, as defined in the `PayChannel` [Invariants](#322-invariants). - Otherwise the transaction fails with `tecPRECISION_LOSS`. +5. **No XRP Reserve Check:** + - A token fund locks no XRP and adds no owned object, so it has no XRP reserve check. An XRP fund keeps its existing reserve checks. + ### 3.7.3. State Changes 1. **Adjustment from Source:** @@ -403,10 +426,12 @@ The message is 44 bytes for an XRP channel, 84 bytes for an IOU channel and 69 b #### 3.8.2.1. Data Verification -1. `Balance` or `Amount` is a token and the `TokenPaychan` amendment is not enabled. (`temBAD_AMOUNT`) -2. `Balance` or `Amount` is an MPT and the `MPTokensV1` amendment is not enabled. (`temDISABLED`) -3. `Balance` and `Amount` are both present and are not the same asset. (`temBAD_AMOUNT`) -4. `Signature` does not verify against the message in [Signature](#3811-signature) for the authorized amount (`Amount`, or `Balance` when `Amount` is absent). (`temBAD_SIGNATURE`) +1. `Balance` or `Amount` is not positive. (`temBAD_AMOUNT`) +2. `Balance` or `Amount` is a token and the `TokenPaychan` amendment is not enabled. (`temBAD_AMOUNT`) +3. `Balance` or `Amount` is a token and the `fixCleanup3_2_0` or `fixTokenEscrowV1` amendment is not enabled. (`temDISABLED`) +4. `Balance` or `Amount` is an MPT and the `MPTokensV1` amendment is not enabled. (`temDISABLED`) +5. `Balance` and `Amount` are both present and are not the same asset. (`temBAD_AMOUNT`) +6. `Signature` does not verify against the message in [Signature](#3811-signature) for the authorized amount (`Amount`, or `Balance` when `Amount` is absent). (`temBAD_SIGNATURE`) #### 3.8.2.2. Protocol-Level Failures @@ -416,8 +441,12 @@ These conditions apply to every claim that carries `Balance`, with or without `t When the destination is the issuer of the channel's token, none of the authorization, holding, trustline limit or freeze conditions below apply: the issuer has no trustline to itself and holds no `MPToken`, and the claim redeems the tokens to the issuer (see [State Changes](#3831-claim-with-balance)). +Neither a claim nor a closure re-checks the issuer's `lsfAllowTrustLineLocking`, `lsfMPTCanEscrow` or `lsfMPTCanTransfer` flag, so an issuer that clears one after the channel is created does not block its claims or its closure. + +On a channel whose `Expiration` or `CancelAfter` has passed, a claim closes the channel as in [Channel Closure](#38222-channel-closure) and pays nothing to the destination, whichever account submits it. Condition 1 below still applies, and on a claim with `Balance` so do conditions 2 and 6; the other conditions below do not. + 1. **Asset Mismatch:** - - If the claim's `Balance` or `Amount` is not the same asset as the channel's `Amount`, the transaction fails with `tecWRONG_ASSET`. + - If the claim's `Balance` or `Amount` is not the same asset as the channel's `Amount`, the transaction fails with `tecWRONG_ASSET`. `Amount` is checked whether or not the claim carries `Balance`. 2. **Destination Not Authorized to Hold Token:** - If authorization is required and the destination is not authorized, the transaction fails with `tecNO_AUTH`. For an IOU, a destination with no trustline to the issuer fails with `tecNO_LINE` when authorization is required. @@ -442,7 +471,14 @@ When the destination is the issuer of the channel's token, none of the authoriza 7. **Inexact Payout or Remainder (IOU only):** - After the existing checks that `Balance` is above the channel's `Balance` and at most its `Amount` (`tecUNFUNDED_PAYMENT`), the amount this claim pays (`Balance` minus the channel's current `Balance`) and the remainder it leaves in the channel (the channel's `Amount` minus `Balance`) must both be exact, as defined in the `PayChannel` [Invariants](#322-invariants). Otherwise the transaction fails with `tecPRECISION_LOSS`. - - These checks cover the channel's own amounts. The credit to the destination's trustline is not checked and follows IOU balance precision (see [Credit Precision](#3141-iou-tokens-transferrate)). + - These checks cover the channel's own amounts. The credit to the destination's trustline is not checked and follows IOU balance precision (see [Credit Precision](#3151-iou-tokens-transferrate)). + +8. **Missing Signature or Wrong Signing Key:** + - If the destination submits the claim without `Signature`, the transaction fails with `tecNO_PERMISSION`. + - If `Signature` is present and the transaction's `PublicKey` is not the channel's `PublicKey`, the transaction fails with `tecNO_PERMISSION`. + +9. **Deposit Authorization:** + - If the destination has `lsfDepositAuth` set and the source submits the claim, the source must be preauthorized by the destination, by account or through the credentials in `CredentialIDs`; otherwise the transaction fails with `tecNO_PERMISSION`. A claim the destination submits is not checked. This condition applies when the destination is the issuer as well, and is the existing rule for XRP claims. ##### 3.8.2.2.2. Channel Closure @@ -456,12 +492,12 @@ For an IOU channel the refund depends on which account submits the closing trans - If authorization is required and the source is not authorized, the transaction fails with `tecNO_AUTH`. For an IOU, a source with no trustline to the issuer fails with `tecNO_LINE` when authorization is required, whichever account submits the transaction. 2. **Source Lacks Trustline or MPT Holding:** - - The source's trustline or `MPToken` is created during closure only when the source itself submits the transaction (and authorization is not required), for the same reason as on a claim. + - The source's trustline is created during closure only when the source itself submits the transaction (and authorization is not required), for the same reason as on a claim. - **IOU Tokens**: If the source lacks a trustline with the issuer and did not submit the transaction, the transaction fails with `tecNO_LINE`. - - **MPTs**: If the source does not hold the MPT and did not submit the transaction, the transaction fails with `tecNO_PERMISSION`. A positive remainder is part of the source's `LockedAmount`, and an `MPToken` with a non-zero `LockedAmount` cannot be deleted (`tecHAS_OBLIGATIONS`), so the source's `MPToken` exists whenever there is a remainder to refund. + - **MPTs**: A positive remainder is part of the source's `LockedAmount`, and an `MPToken` with a non-zero `LockedAmount` cannot be deleted (`tecHAS_OBLIGATIONS`), so the source's `MPToken` exists whenever there is a remainder to refund, and closure never creates one. -3. **Cannot Create Trustline or MPT Holding:** - - If unable to create due to lack of reserves, the transaction fails with `tecNO_LINE_INSUF_RESERVE` (IOU) or `tecINSUFFICIENT_RESERVE` (MPT). The channel's own owner reserve is released before this check, so the source needs reserve for the new holding counted against its owner count without the channel. +3. **Cannot Create Trustline (IOU only):** + - If the source's trustline must be re-created and the source cannot meet the reserve for it, the transaction fails with `tecNO_LINE_INSUF_RESERVE`. The reserve is measured against the source's XRP balance before the transaction fee. The channel's own owner reserve is released before this check, so the source needs reserve for the new trustline counted against its owner count without the channel. 4. **Trustline Limit Exceeded (IOU only):** - If the transaction is not submitted by the source and the refund would push the source's trustline balance above its limit, the transaction fails with `tecLIMIT_EXCEEDED`. The source's own closing transaction does not check the limit. @@ -481,7 +517,7 @@ For an IOU channel the refund depends on which account submits the closing trans - **IOU Tokens**: If the IOU does not require authorization and the account submitting the transaction is the destination, a trustline is created for it. - **MPTs**: If the MPT does not require authorization and the account submitting the transaction is the destination, an `MPToken` is created for it. 2. **Adjustment from Issuer to Destination:** - - **IOU Tokens**: The claimed amount, less any transfer fee (see [Transfer Rates and Fees](#314-transfer-rates-and-fees)), is added to the destination's trustline balance, at that balance's IOU precision (see [Credit Precision](#3141-iou-tokens-transferrate)). If the destination is the issuer, the claimed amount is simply redeemed. + - **IOU Tokens**: The claimed amount, less any transfer fee (see [Transfer Rates and Fees](#315-transfer-rates-and-fees)), is added to the destination's trustline balance, at that balance's IOU precision (see [Credit Precision](#3151-iou-tokens-transferrate)). If the destination is the issuer, the claimed amount is simply redeemed. - **MPTs**: - If the destination is the issuer of the asset held in the channel, then: 1. The `LockedAmount` on the `MPTokenIssuance` and the source's `MPToken` is decreased by the claimed amount. @@ -489,20 +525,20 @@ For an IOU channel the refund depends on which account submits the closing trans 3. The `OutstandingAmount` on the `MPTokenIssuance` is decreased by the claimed amount (i.e., this claim is a "redemption"). - If the destination is not the issuer of the asset held in the channel, then: 1. The `LockedAmount` on the `MPTokenIssuance` and the source's `MPToken` is decreased by the claimed amount. - 2. The `MPTAmount` on the destination's `MPToken` is increased by the claimed amount, less any transfer fee (see [Transfer Rates and Fees](#314-transfer-rates-and-fees) for its rounding). + 2. The `MPTAmount` on the destination's `MPToken` is increased by the claimed amount, less any transfer fee (see [Transfer Rates and Fees](#315-transfer-rates-and-fees) for its rounding). 3. The `OutstandingAmount` on the `MPTokenIssuance` is decreased by the transfer fee, the claimed amount less the amount credited to the destination; the fee is credited to no holder, so it leaves the outstanding supply. 3. **Channel Balance Update:** - The channel's `Balance` field is set to the claim's `Balance`, the cumulative amount claimed before transfer fees. #### 3.8.3.2. Channel Closure -1. **Auto create Trustline or MPToken:** +1. **Auto create Trustline:** - **IOU Tokens**: If the IOU does not require authorization, the account submitting the transaction is the source, and the source has no trustline with the issuer, a trustline is created for it. - - **MPTs**: If the MPT does not require authorization, the account submitting the transaction is the source, and the source has no `MPToken`, an `MPToken` is created for it. + - **MPTs**: No `MPToken` is created; the source's `MPToken` exists whenever there is a remainder to refund (see [Channel Closure](#38222-channel-closure)). 2. **Adjustment from Issuer to Source:** - No transfer fee is applied when returning remaining funds to the source. - The refund, `Amount` minus `Balance`, is exact by construction: every transaction that changes `Amount` or `Balance` keeps that difference exact (see the `PayChannel` [Invariants](#322-invariants)). - - **IOU Tokens**: Any remaining channel funds are added to the source's trustline balance, at that balance's IOU precision. Closure does not check this credit, so a refund much smaller than the source's balance rounds, exactly as an XLS-85 `EscrowCancel` refund does (see [Credit Precision](#3141-iou-tokens-transferrate)). + - **IOU Tokens**: Any remaining channel funds are added to the source's trustline balance, at that balance's IOU precision. Closure does not check this credit, so a refund much smaller than the source's balance rounds, exactly as an XLS-85 `EscrowCancel` refund does (see [Credit Precision](#3151-iou-tokens-transferrate)). - **MPTs**: Any remaining channel funds are added to the source's MPT balance. The `OutstandingAmount` of the MPT issuance remains unchanged. The `LockedAmount` is decreased on both the source's `MPToken` and the `MPTokenIssuance`. 3. **Deletion of Payment Channel Object:** - The `PayChannel` object is deleted after successful closure. @@ -557,7 +593,11 @@ This transaction uses the standard transaction fee (currently 10 drops, subject #### 3.9.3.1. Data Verification -1. **Malformed `Amount`:** +1. **Amendments Not Enabled:** + - If the `fixCleanup3_2_0` or `fixTokenEscrowV1` amendment is not enabled, the transaction fails with `temDISABLED`, whether or not it carries `Amount`. + - If `Amount` is an MPT and the `MPTokensV1` amendment is not enabled, the transaction fails with `temDISABLED`. + +2. **Malformed `Amount`:** - If `Amount` is present and is XRP or is not positive, the transaction fails with `temBAD_AMOUNT`. An MPT amount above the maximum MPT value fails the same way. - If `Amount` is present and names XRP as an IOU currency code, the transaction fails with `temBAD_CURRENCY`. @@ -593,7 +633,7 @@ This transaction uses the standard transaction fee (currently 10 drops, subject - **MPTs**: The clawed amount is deducted from the `LockedAmount` on both the source's `MPToken` and the `MPTokenIssuance`, and from the `OutstandingAmount` on the `MPTokenIssuance`. No `MPToken` is created for the issuer, since MPT issuers do not hold their own `MPToken`. 3. **Payment Channel Object Update:** - - For a partial clawback, the channel's `Amount` is reduced by the clawed amount and the channel remains open. + - For a partial clawback, the channel's `Amount` is reduced by the clawed amount and the channel remains open. The channel's `Balance` is not changed. - For a full clawback, the channel's `Amount` is set equal to its `Balance`, leaving no remainder, and the channel is closed and deleted as described in [Channel Closure](#3832-channel-closure). Because the remainder is zero, no refund is made to the source and no source-side authorization, holding, or freeze condition applies. #### 3.9.4.1. Effect on Outstanding Claims @@ -631,6 +671,8 @@ As a reference, [here](https://xrpl.org/docs/references/http-websocket-apis/admi | ---------- | --------- | ---------------- | ------------------------------------------------------------------------------------------------------- | | `amount` | Yes | string or object | The authorized amount: a string of drops for an XRP channel, or an `Amount` object for a token channel. | +On the command line, the amount argument is either a string of drops or the `Amount` JSON object passed as a single argument: `channel_authorize [] |`. Any other amount argument returns `channelAmtMalformed`. + ### 3.10.2. Response Fields The response fields are [unchanged](https://xrpl.org/docs/references/http-websocket-apis/admin-api-methods/signing-methods/channel_authorize#response-format). For a token channel, `signature` is over the token message in [Signature](#3811-signature). @@ -678,6 +720,8 @@ As a reference, [here](https://xrpl.org/docs/references/http-websocket-apis/publ | ---------- | --------- | ---------------- | ------------------------------------------------------------------- | | `amount` | Yes | string or object | The authorized amount, in the same form as for `channel_authorize`. | +On the command line, the amount argument takes the same two forms as for `channel_authorize`: `channel_verify | `. + ### 3.11.2. Response Fields The response fields are [unchanged](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods/payment-channel-methods/channel_verify#response-format). For a token channel, `signature_verified` reports whether `signature` is valid for the token message in [Signature](#3811-signature) built from `channel_id` and `amount`. @@ -715,7 +759,7 @@ The existing failure conditions are unchanged. This spec extends one: ## 3.12. RPC: `gateway_balances` -The [`gateway_balances` method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods/account-methods/gateway_balances) is modified to count IOU value locked in payment channels. The response's `locked` totals, per currency, now include the unclaimed remainder (`Amount` minus `Balance`) of every IOU `PayChannel` in the queried account's owner directory: channels the account opened, channels whose destination is that account, and channels whose `IssuerNode` points there. XRP and MPT channels are not counted. +The [`gateway_balances` method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods/account-methods/gateway_balances) is modified to count IOU value locked in payment channels. The response's `locked` totals, per currency, now include the unclaimed remainder (`Amount` minus `Balance`) of every IOU `PayChannel` in the queried account's owner directory whose `Amount` is issued by that account: channels whose destination is that account, and channels whose `IssuerNode` points there. A channel whose unclaimed remainder is zero is not counted. XRP and MPT channels, and channels holding another issuer's IOU, are not counted. ### 3.12.1. Request Fields @@ -725,9 +769,9 @@ The request fields are [unchanged](https://xrpl.org/docs/references/http-websock As a reference, [here](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods/account-methods/gateway_balances#response-format) are the response fields that `gateway_balances` currently has. This spec changes `locked`: -| Field Name | Always Present? | JSON Type | Description | -| ---------- | --------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `locked` | No | Object | Total amounts locked, keyed by currency code. This spec adds the unclaimed remainder of each IOU payment channel in the account's owner directory to the existing escrow totals. | +| Field Name | Always Present? | JSON Type | Description | +| ---------- | --------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `locked` | No | Object | Total amounts locked, keyed by currency code. This spec adds the non-zero unclaimed remainder of each IOU payment channel of the account's own tokens in its owner directory to the existing escrow totals. | ### 3.12.3. Failure Conditions @@ -760,7 +804,69 @@ For the channel opened by the `PaymentChannelCreate` [Example JSON](#364-example } ``` -## 3.13. Key Differences Between XRP, IOU and MPT Payment Channels +## 3.13. RPC: `account_channels` + +The [`account_channels` method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods/account-methods/account_channels) is modified to return a token channel's `amount` and `balance` as `Amount` JSON objects. An XRP channel still returns them as strings of drops. + +### 3.13.1. Request Fields + +The request fields are [unchanged](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods/account-methods/account_channels#request-format). + +### 3.13.2. Response Fields + +As a reference, [here](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods/account-methods/account_channels#response-format) are the fields that each entry of the response's `channels` array currently has. This spec changes `amount` and `balance`: + +| Field Name | Always Present? | JSON Type | Description | +| ---------- | --------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `amount` | Yes | String or Object | The channel's `Amount`: a string of drops for an XRP channel, or an `Amount` JSON object for a token channel (`currency`, `issuer` and `value` for an IOU; `mpt_issuance_id` and `value` for an MPT). | +| `balance` | Yes | String or Object | The channel's `Balance`, in the same form as `amount`. | + +### 3.13.3. Failure Conditions + +The failure conditions are unchanged. + +### 3.13.4. Example Request + +```json +{ + "command": "account_channels", + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "ledger_index": "validated" +} +``` + +### 3.13.5. Example Response + +For the channel in the `PayChannel` [Example JSON](#323-example-json): + +```json +{ + "status": "success", + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "channels": [ + { + "channel_id": "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198", + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "destination_account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "amount": { + "currency": "USD", + "issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "value": "1000" + }, + "balance": { + "currency": "USD", + "issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "value": "250" + }, + "public_key": "aBQE3x8xM3iRSt3g2pajoxyUywLJ77TpSMA1gZY1c2XDh32vpFug", + "public_key_hex": "032D2B4A9D4B0C1C7D5E0B6E5F9A8C7B6D5E4F3A2B1C0D9E8F7A6B5C4D3E2F1A0B", + "settle_delay": 3600 + } + ] +} +``` + +## 3.14. Key Differences Between XRP, IOU and MPT Payment Channels | Aspect | XRP | IOU Tokens | Multi-Purpose Tokens (MPTs) | | ----------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -779,15 +885,15 @@ For the channel opened by the `PaymentChannelCreate` [Example JSON](#364-example | **Account Deletion** | Payment channels prevent account deletion | Payment channels prevent deletion of the source and destination accounts, and of the issuer's account while a channel is in its owner directory (`IssuerNode`) | Payment channels prevent account deletion | | **Holding Deletion** | N/A | Trustline deletion is NOT blocked by open channels (locked value lives in the channel object); a close submitted by any account other than the source then fails with `tecNO_LINE`, while the source's own closing transaction re-creates the line (when authorization is not required) and receives the refund | `MPToken` deletion is blocked while `LockedAmount` is non-zero (`tecHAS_OBLIGATIONS`) | -## 3.14. Transfer Rates and Fees +## 3.15. Transfer Rates and Fees -### 3.14.1. IOU Tokens (`TransferRate`) +### 3.15.1. IOU Tokens (`TransferRate`) - **Rate Capped at Creation**: The `TransferRate` is captured at the time of `PaymentChannelCreate` and stored in the `PayChannel` object. At claim time, the lower of the stored rate and the issuer's current rate is applied: an increase by the issuer does not affect existing channels, including tokens later added to them by `PaymentChannelFund`, which does not update the stored rate, while a decrease passes through to claims. This is identical to the behavior of the activated XLS-85 (Token Escrow) implementation, which uses the same shared unlock logic. - **Fee Calculation**: The destination is credited the claimed amount divided by the applied rate (the lower of the stored and current rate, as a ratio to 1,000,000,000), rounded up to IOU precision; the fee is the claimed amount minus that credit. No fee is applied when the issuer is the destination, or when remaining funds are returned to the source at closure. - **Credit Precision**: The credit to the receiver's trustline, the destination's on a claim and the source's on a closure refund, is added to that trustline's balance, which keeps IOU precision: the new balance is rounded to a 16-digit mantissa. No precision check applies to the credit, so when the credit is much smaller than the receiver's balance, the balance does not grow by exactly the credit: a refund of 1 to a source whose balance is 10^20 leaves the balance at 10^20. The channel's own `Amount` and `Balance` stay exact (see the `PayChannel` [Invariants](#322-invariants)); only the receiver's trustline balance rounds. This is exactly how XLS-85 escrow refunds and finishes behave in the activated implementation: `EscrowCancel`, `EscrowFinish`, `PaymentChannelClaim` and channel closure all credit through the same shared unlock logic (`escrowUnlockApplyHelper` in rippled's `src/libxrpl/ledger/helpers/EscrowHelpers.cpp`, which credits through `directSendNoFee` with no precision check). -### 3.14.2. MPTs (`TransferFee`) +### 3.15.2. MPTs (`TransferFee`) - **Fee Capped at Creation**: The `TransferFee` is captured at the time of `PaymentChannelCreate` and stored in the `PayChannel` object, similar to IOUs, with the same lower-of-stored-and-current rule. - **Fee Calculation**: The transfer fee is deducted from the claimed amount, reducing the amount credited to the destination. The applied rate is the lower of the stored and current rate, as a ratio to 1,000,000,000; an MPT's rate is 1,000,000,000 plus 10,000 times its `TransferFee`. MPT amounts are integers, so the credit is rounded, and the rounding depends on the `fixCleanup3_4_0` amendment: @@ -796,7 +902,7 @@ For the channel opened by the `PaymentChannelCreate` [Example JSON](#364-example - In both cases the fee is the claimed amount minus the credit. No fee is applied when the issuer is the destination, or when remaining funds are returned to the source at closure. - **Consistent Fee Application**: Both IOUs and MPTs use the same capped-rate rule, ensuring the destination's settlement value cannot be worsened by the issuer after channel creation. -## 3.15. Future Considerations +## 3.16. Future Considerations 1. Issuer as Source: XLS-93 currently does not allow the issuer to be the source of the Payment Channel. If your use case requires this functionality, you should create a new account, send the MPT or IOU to that account, and then create the payment channel with that account as the source. @@ -812,7 +918,7 @@ Payment channels are the last remaining XRP-only locking primitive; XLS-85 alrea ## 5. Backwards Compatibility -The change is amendment-gated. Before `TokenPaychan` is enabled, a token `Amount` on `PaymentChannelCreate` or `PaymentChannelFund`, and a token `Balance` or `Amount` on `PaymentChannelClaim`, are rejected with `temBAD_AMOUNT`, and `PaymentChannelClawback` is rejected with `temDISABLED`. Before `TokenPaychan` is enabled, a `DelegateSet` that grants the `PaymentChannelClawback` permission is rejected with `temMALFORMED`. +The change is amendment-gated. Before `TokenPaychan` is enabled, a token `Amount` on `PaymentChannelCreate` or `PaymentChannelFund`, and a token `Balance` or `Amount` on `PaymentChannelClaim`, are rejected with `temBAD_AMOUNT`, and `PaymentChannelClawback` is rejected with `temDISABLED`. Once `TokenPaychan` is enabled, those token amounts and every `PaymentChannelClawback` also require the `fixCleanup3_2_0` and `fixTokenEscrowV1` amendments, and are rejected with `temDISABLED` while either is not enabled; an MPT `Amount` or `Balance` on any of the four transactions also requires `MPTokensV1` (`temDISABLED`). Before `TokenPaychan` is enabled, a `DelegateSet` that grants the `PaymentChannelClawback` permission is rejected with `temMALFORMED`. XRP channels are unchanged: the [Signature](#3811-signature) message for an XRP channel keeps its 44-byte layout, existing `PayChannel` entries gain no fields (`TransferRate` and `IssuerNode` are only ever set on token channels), and `channel_authorize` and `channel_verify` continue to accept `amount` as a string of drops. @@ -820,7 +926,7 @@ XRP channels are unchanged: the [Signature](#3811-signature) message for an XRP The reference implementation adds the `PayChanToken` test suite (`src/test/app/PayChanToken_test.cpp`), which covers, for IOUs and MPTs separately: amendment enablement, the issuer opt-in flags, preflight, preclaim and apply of `PaymentChannelCreate`, `PaymentChannelFund` and `PaymentChannelClaim`, closure, auto-creation of the destination's holding, balances and metadata, the locked transfer rate, require-auth, freeze and lock, trustline limits, precision loss, the interaction with ordinary `Clawback`, `PaymentChannelClawback` for both asset types, the `channel_authorize` and `channel_verify` methods, and the byte layout of the [Signature](#3811-signature) message. Within `PayChanToken` it also covers the exact source debit on create and fund, and a delegate submitting `PaymentChannelClawback` with and without the permission. -It also adds `InvariantsPayChan` (`src/test/app/invariants/InvariantsPayChan_test.cpp`) for the `PayChannel` invariants, `STAmount` cases for exact IOU sums and differences, `EscrowToken` cases showing that an IOU escrow refund or finish credit rounds at the receiver's balance precision as described in [Credit Precision](#3141-iou-tokens-transferrate), `Delegate` cases for delegating `PaymentChannelClawback` and its amendment gate, and `RPCCall` cases for a token `amount` on the command line. +It also adds `InvariantsPayChan` (`src/test/app/invariants/InvariantsPayChan_test.cpp`) for the `PayChannel` invariants, `STAmount` cases for exact IOU sums and differences, `EscrowToken` cases showing that an IOU escrow refund or finish credit rounds at the receiver's balance precision as described in [Credit Precision](#3151-iou-tokens-transferrate), `Delegate` cases for delegating `PaymentChannelClawback` and its amendment gate, and `RPCCall` cases for a token `amount` on the command line. ## 7. Reference Implementation @@ -831,7 +937,7 @@ It also adds `InvariantsPayChan` (`src/test/app/invariants/InvariantsPayChan_tes - **Payee protection.** Claims check only destination-side conditions, and the source-side conditions in [Channel Closure](#38222-channel-closure) apply only to refunding a positive remainder; a fully drained channel closes without them. A claim with `tfClose` that fails one of those conditions fails as a whole, payout included, but a claim without `tfClose` on a channel that has not expired never runs them, so the destination can always collect its earned funds that way. - **Issuer trust surface.** An issuer that uses `RequireAuth` can deauthorize the source and thereby block the refund leg of closure (`tecNO_AUTH`) until re-authorized. The channel and its locked funds remain on ledger; no funds are lost. This is the same issuer trust surface that exists for XLS-85 escrow refunds and for clawback generally. - **Transfer rate.** The claim rate is capped at the rate stored at creation (the lower of stored and current is applied), so an issuer cannot retroactively tax funds already locked by raising `TransferRate`/`TransferFee`. Funding does not re-capture the rate, so tokens added to an existing channel by `PaymentChannelFund` are also claimed at no more than the rate captured at creation, even if the issuer has raised its rate since. This is accepted: re-capturing on fund would raise the fee on tokens already locked (see [Rationale](#4-rationale)). -- **Precision.** IOU amounts carry a 16-digit mantissa, so a sum or difference of values at very different scales can round. Create and fund reject with `tecPRECISION_LOSS` unless the source's spendable balance minus the locked amount is exact, so the trustline is debited by exactly what the channel records. Fund, claim and partial clawback reject with `tecPRECISION_LOSS` unless the quantity each changes (the new `Amount` on fund and partial clawback, the paid difference on claim) and `Amount - Balance` are exact, so closure unlocks exactly what was not claimed and the channel's recorded `Amount` and `Balance` always equal the value locked and paid out of it. The credit of a payout or refund to the receiver's trustline is not checked: it follows IOU balance precision and rounds when it is much smaller than the receiver's balance, exactly as XLS-85 escrow refunds and finishes do through the same unlock logic (see [Credit Precision](#3141-iou-tokens-transferrate)). +- **Precision.** IOU amounts carry a 16-digit mantissa, so a sum or difference of values at very different scales can round. Create and fund reject with `tecPRECISION_LOSS` unless the source's spendable balance minus the locked amount is exact, so the trustline is debited by exactly what the channel records. Fund, claim and partial clawback reject with `tecPRECISION_LOSS` unless the quantity each changes (the new `Amount` on fund and partial clawback, the paid difference on claim) and `Amount - Balance` are exact, so closure unlocks exactly what was not claimed and the channel's recorded `Amount` and `Balance` always equal the value locked and paid out of it. The credit of a payout or refund to the receiver's trustline is not checked: it follows IOU balance precision and rounds when it is much smaller than the receiver's balance, exactly as XLS-85 escrow refunds and finishes do through the same unlock logic (see [Credit Precision](#3151-iou-tokens-transferrate)). - **Trustline deletion and limit.** The source can delete an empty trustline while a channel is open. A close submitted by any account other than the source then fails with `tecNO_LINE`, and also fails with `tecLIMIT_EXCEEDED` when the refund would push the source's trustline balance above its limit; a claim with `tfClose` that hits either fails as a whole, payout included. The source's own closing transaction re-creates the line (when authorization is not required), skips the limit check and receives the refund. This matches the activated XLS-85 implementation of `EscrowCancel` for a canceller other than the escrow owner: the refund never creates a trustline for, or credits past the trustline limit of, a source that did not submit the transaction. The destination's claims without `tfClose` are unaffected. `MPToken` deletion is blocked while locked (`tecHAS_OBLIGATIONS`). - **Signature domain.** Claim signatures bind the specific channel ID and amount, unchanged from XRP payment channels; the token layouts in [Signature](#3811-signature) also bind the currency and issuer or the `MPTokenIssuanceID`, so a signature for one asset cannot be presented against a channel holding another. - **Clawback scope.** `PaymentChannelClawback` reaches only the unclaimed remainder and only for an issuer who already holds the clawback opt-in for that token. It cannot reverse a claim the destination has settled, and it cannot touch XRP or a token whose issuer never enabled clawback. @@ -859,4 +965,4 @@ No. A vault share is an MPT issued by the vault's pseudo-account, which cannot c ### A.5: Can IOU precision create or destroy value when `Amount` or `Balance` change scale? -Not in the channel's accounting. An IOU amount is a normalized value with a 16-digit mantissa, so adding or subtracting values at very different scales can round: with `Amount` at 10^20 and `Balance` at 1, `Amount - Balance` rounds back to 10^20. Create and fund therefore reject with `tecPRECISION_LOSS` unless the source's spendable balance minus the locked amount is exact, so the source is debited exactly what the channel records. Every transaction that changes `Amount` or `Balance` also rejects with `tecPRECISION_LOSS` unless the result is exact: fund requires the new `Amount` and the new `Amount - Balance` to be exact, claim requires the paid difference and the new `Amount - Balance` to be exact, and a partial clawback requires the new `Amount` and the new `Amount - Balance` to be exact. Closure unlocks `Amount - Balance`, which these checks keep exact, so the refund equals what was not claimed. The one step not checked is the credit of a payout or refund to the receiver's trustline: it follows that balance's 16-digit precision, so a refund of 1 to a source whose balance is 10^20 leaves the balance at 10^20, exactly as an XLS-85 escrow refund or finish does (see [Credit Precision](#3141-iou-tokens-transferrate)). +Not in the channel's accounting. An IOU amount is a normalized value with a 16-digit mantissa, so adding or subtracting values at very different scales can round: with `Amount` at 10^20 and `Balance` at 1, `Amount - Balance` rounds back to 10^20. Create and fund therefore reject with `tecPRECISION_LOSS` unless the source's spendable balance minus the locked amount is exact, so the source is debited exactly what the channel records. Every transaction that changes `Amount` or `Balance` also rejects with `tecPRECISION_LOSS` unless the result is exact: fund requires the new `Amount` and the new `Amount - Balance` to be exact, claim requires the paid difference and the new `Amount - Balance` to be exact, and a partial clawback requires the new `Amount` and the new `Amount - Balance` to be exact. Closure unlocks `Amount - Balance`, which these checks keep exact, so the refund equals what was not claimed. The one step not checked is the credit of a payout or refund to the receiver's trustline: it follows that balance's 16-digit precision, so a refund of 1 to a source whose balance is 10^20 leaves the balance at 10^20, exactly as an XLS-85 escrow refund or finish does (see [Credit Precision](#3151-iou-tokens-transferrate)).