xls: 78 title: Subscriptions description: A subscription mechanism for recurring payments on the XRP Ledger author: Kris Dangerfield (@krisdangerfield), Denis Angell (@angell_denis) discussion-from: https://github.com/XRPLF/XRPL-Standards/discussions/222 status: Draft category: Amendment created: 2024-08-30 updated: 2025-09-10
XLS-78: Subscriptions¶
Abstract¶
This proposal introduces a subscription (recurring payments) mechanism to the XRP Ledger (XRPL), enabling functionality similar to direct debits. The amendment allows account owners to authorize automated recurring payments with predefined parameters such as amount, frequency, and destination, supporting XRP, Trustline-based tokens (IOUs), and Multi-Purpose Tokens (MPTs).
Motivation¶
Currently, payments on the XRP Ledger require the sender to initiate and sign each transaction. While off-chain solutions like Xaman API have introduced pull payments where transactions can be generated by the destination and signed by the sender, this still requires per-transaction authorization, which is impractical for subscription services.
This proposal addresses this limitation by introducing a subscription feature that allows pre-authorized recurring payments, improving user experience and enabling a broader range of services on the XRP Ledger. The subscription model balances user convenience with security by allowing pre-authorized payments within defined parameters while maintaining user control over amounts and frequency.
1. Implementation¶
This amendment introduces new ledger objects and transactions to support recurring payments for XRP, IOUs, and MPTs, accounting for the specific behaviors and constraints associated with each token type.
2. Specification¶
2.1. Ledger Entries¶
2.1.1. Subscription¶
2.1.1.1. Object Identifier¶
The Subscription object ID is computed as the SHA-512Half of:
- The Subscription space key (0x0055)
- The Account ID
- The Destination ID
- The Transaction Sequence
2.1.1.2. Fields¶
Field Name | Constant | Required | Internal Type | Default Value | Description |
---|---|---|---|---|---|
LedgerEntryType | Yes | Yes | UINT16 | 0x0055 | Identifies this as a Subscription object |
Flags | No | Yes | UINT32 | 0 | Reserved for future use |
PreviousTxnID | No | Yes | HASH256 | N/A | Hash of the transaction that most recently modified this object |
PreviousTxnLgrSeq | No | Yes | UINT32 | N/A | Ledger index containing the transaction that most recently modified this object |
Account | Yes | Yes | ACCOUNTID | N/A | The account that owns the subscription |
Destination | Yes | Yes | ACCOUNTID | N/A | The account authorized to receive subscription payments |
Data | Yes | Conditional | BLOB | N/A | Data field for payment categorization |
SendMax | No | Yes | AMOUNT | N/A | Maximum amount that can be withdrawn per period (XRP, IOU, or MPT) |
Balance | No | Yes | AMOUNT | N/A | Remaining balance available for the current period |
Frequency | Yes | Yes | UINT32 | N/A | Time period in seconds between consecutive payments (minimum 3600) |
NextClaimTime | No | Yes | UINT32 | N/A | Ripple epoch time when the next payment can be claimed |
StartTime | Yes | Conditional | UINT32 | N/A | Ripple epoch time when the subscription started (set at creation) |
Expiration | Yes | Conditional | UINT32 | N/A | Ripple epoch time when the subscription expires |
Sequence | Yes | Yes | UINT32 | N/A | Transaction sequence number used to create this subscription |
OwnerNode | No | Yes | UINT64 | N/A | Page of the source account's owner directory |
DestinationNode | No | Yes | UINT64 | N/A | Page of the destination account's owner directory |
2.1.1.3. Ownership¶
The Subscription object is linked to two OwnerDirectory entries:
- The source account's OwnerDirectory (via OwnerNode) - increments owner count
- The destination account's OwnerDirectory (via DestinationNode) - does not increment owner count
Only the source account's owner count is affected because they bear the reserve requirement.
2.1.1.4. Reserves¶
Creating a Subscription object increases the owner's XRP reserve by one increment. This reserve is returned when the subscription is deleted.
2.1.1.5. Deletion¶
The Subscription can be deleted through:
SubscriptionCancel
transaction from either the owner or destinationSubscriptionClaim
transaction when the expiration time is reached and the claim is successful- Account deletion is blocked while any subscriptions exist (either as owner or destination)
2.1.1.6. Freeze/Lock Compliance¶
For IOUs:
- Global Freeze: Prevents creation and claims
- Individual Freeze: Prevents creation and claims for frozen account
- Deep Freeze: Prevents all operations
For MPTs:
- Lock Conditions: Prevent creation and claims for locked accounts
2.1.1.7. Invariants¶
Before and after any transaction:
Balance
≤SendMax
NextClaimTime
≥StartTime
(if StartTime present)Expiration
>NextClaimTime
(if Expiration present)Frequency
> 0Account
≠Destination
- If non-XRP: asset must exist and be valid
2.1.1.8. Example JSON¶
{
"LedgerEntryType": "Subscription",
"Flags": 0,
"Account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"Destination": "rLdCa1mLK5R5Am25ArfXFmqgNwjZgnfy91",
"Data": "DEADBEEF",
"SendMax": "100000000",
"Balance": "50000000",
"Frequency": 2592000,
"NextClaimTime": 711232800,
"StartTime": 708640800,
"Expiration": 721600800,
"Sequence": 42,
"OwnerNode": "0000000000000001",
"DestinationNode": "0000000000000002",
"PreviousTxnID": "E8A6F9B99B041DF5C932DF8DA29B2A84B42D4D4F0F4A08931D76D62F42E1F1B9",
"PreviousTxnLgrSeq": 8675309
}
2.2. Transactions¶
2.2.1. SubscriptionSet¶
Creates a new subscription or updates an existing one.
2.2.1.1. Fields¶
Field Name | Required? | JSON Type | Internal Type | Default Value | Description |
---|---|---|---|---|---|
TransactionType | Yes | String | UINT16 | N/A | Value: "SubscriptionSet" |
Destination | Conditional | String | ACCOUNTID | N/A | Destination account (required for creation, forbidden for updates) |
Data | No | String | BLOB | N/A | Data field for categorization |
Amount | Yes | Object/String | AMOUNT | N/A | Maximum amount per period (XRP, IOU, or MPT) |
Frequency | Conditional | Number | UINT32 | N/A | Period in seconds between payments (required for creation, forbidden for updates) |
StartTime | No | Number | UINT32 | Current Time | When subscription starts (creation only) |
Expiration | No | Number | UINT32 | N/A | When subscription expires |
SubscriptionID | Conditional | String | HASH256 | N/A | ID for updates (mutually exclusive with creation fields) |
2.2.1.2. Failure Conditions¶
Creation Mode (no SubscriptionID):
- General Validation:
- Destination equals Account (
temDST_IS_SRC
) - Destination doesn't exist (
tecNO_DST
) - Destination requires tag but none provided (
tecDST_TAG_NEEDED
) - Amount ≤ 0 or invalid (
temBAD_AMOUNT
) - Frequency ≤ 0 (
temMALFORMED
) - StartTime < current time (
temMALFORMED
) - Expiration < current time or StartTime (
temBAD_EXPIRATION
) -
Insufficient reserve (
tecINSUFFICIENT_RESERVE
) -
XRP-specific:
-
Amount not positive or exceeds max XRP (
temBAD_AMOUNT
) -
IOU-specific:
- Bad currency (
temBAD_CURRENCY
) - Issuer doesn't exist (
tecNO_ISSUER
) - Account lacks trustline (
tecNO_LINE
) - Account not authorized when lsfRequireAuth set (
tecNO_AUTH
) - Destination not authorized when lsfRequireAuth set (
tecNO_AUTH
) - Account frozen (
tecFROZEN
) - Destination frozen (
tecFROZEN
) - Insufficient spendable balance (
tecINSUFFICIENT_FUNDS
) -
Precision loss in amount (
tecPRECISION_LOSS
) -
MPT-specific:
- MPT issuance doesn't exist (
tecOBJECT_NOT_FOUND
) - Account doesn't hold MPT (
tecOBJECT_NOT_FOUND
) - MPT lacks tfMPTCanTransfer flag unless destination is issuer (
tecNO_AUTH
) - Account not authorized when tfMPTRequireAuth set (
tecNO_AUTH
) - Destination not authorized when tfMPTRequireAuth set (
tecNO_AUTH
) - Account locked (
tecLOCKED
) - Destination locked (
tecLOCKED
) - Insufficient spendable balance (
tecINSUFFICIENT_FUNDS
)
Update Mode (with SubscriptionID):
- Update Validation:
- XRP, IOU and MPT Validation (as above)
- SubscriptionID doesn't exist (
tecNO_ENTRY
) - Account not owner (
tecNO_PERMISSION
) - Invalid Amount (
temBAD_AMOUNT
) - Expiration in past or before NextClaimTime (
temBAD_EXPIRATION
) - Destination, Frequency, or StartTime present (
temMALFORMED
) - Asset type differs from original (
tecWRONG_ASSET
)
2.2.1.3. State Changes¶
Creation:
- Create Subscription object with all specified fields
- Set Balance = Amount
- Set NextClaimTime = StartTime (if provided) or current ledger time
- Add to source account's owner directory (increment owner count)
- Add to destination account's owner directory (no owner count change)
- Deduct reserve from source account
Update:
- Update Amount field
- If Balance > new Amount, set Balance = Amount
- Update Expiration if provided
2.2.1.4. Example JSON¶
Creation:
{
"TransactionType": "SubscriptionSet",
"Account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"Destination": "rLdCa1mLK5R5Am25ArfXFmqgNwjZgnfy91",
"Data": "DEADBEEF",
"Amount": {
"currency": "USD",
"value": "100",
"issuer": "rUSDIssuerAddress"
},
"Frequency": 2592000,
"StartTime": 711232800,
"Expiration": 721600800,
"Fee": "12",
"Sequence": 42
}
Update:
{
"TransactionType": "SubscriptionSet",
"Account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"SubscriptionID": "E8A6F9B99B041DF5C932DF8DA29B2A84B42D4D4F0F4A08931D76D62F42E1F1B9",
"Amount": "150000000",
"Expiration": 724192800,
"Fee": "12",
"Sequence": 43
}
2.2.2. SubscriptionCancel¶
Cancels an existing subscription.
2.2.2.1. Fields¶
Field Name | Required? | JSON Type | Internal Type | Default Value | Description |
---|---|---|---|---|---|
TransactionType | Yes | String | UINT16 | N/A | Value: "SubscriptionCancel" |
SubscriptionID | Yes | String | HASH256 | N/A | ID of subscription to cancel |
2.2.2.2. Failure Conditions¶
- SubscriptionID doesn't exist (
tecNO_ENTRY
) - Account not owner or destination (
tecNO_PERMISSION
)
2.2.2.3. State Changes¶
- Remove from source account's owner directory
- Remove from destination account's owner directory
- Decrement source account's owner count
- Return reserve to source account
- Delete Subscription object
2.2.2.4. Example JSON¶
{
"TransactionType": "SubscriptionCancel",
"Account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"SubscriptionID": "E8A6F9B99B041DF5C932DF8DA29B2A84B42D4D4F0F4A08931D76D62F42E1F1B9",
"Fee": "12",
"Sequence": 44
}
2.2.3. SubscriptionClaim¶
Claims a payment from an active subscription.
2.2.3.1. Fields¶
Field Name | Required? | JSON Type | Internal Type | Default Value | Description |
---|---|---|---|---|---|
TransactionType | Yes | String | UINT16 | N/A | Value: "SubscriptionClaim" |
SubscriptionID | Yes | String | HASH256 | N/A | ID of subscription to claim |
Amount | Yes | Object/String | AMOUNT | N/A | Amount to claim (≤ available balance) |
2.2.3.2. Failure Conditions¶
- General Validation:
- SubscriptionID doesn't exist (
tecNO_ENTRY
) - Account not destination (
tecNO_PERMISSION
) - Account equals owner (
tecNO_PERMISSION
) - Wrong asset type (
tecWRONG_ASSET
) - Amount > subscription SendMax (
temBAD_AMOUNT
) - Amount > available Balance (
tecINSUFFICIENT_FUNDS
) -
Current time < NextClaimTime unless in arrears (
tecTOO_SOON
) -
XRP-specific:
-
Source has insufficient liquid XRP (
tecINSUFFICIENT_FUNDS
) -
IOU-specific:
- Source lacks trustline (
tecNO_LINE
) - Source has insufficient balance (
tecINSUFFICIENT_FUNDS
) - Source not authorized (
tecNO_AUTH
) - Destination not authorized (
tecNO_AUTH
) - Source frozen (
tecFROZEN
) - Destination frozen (deep freeze only) (
tecFROZEN
) - Cannot create destination trustline - insufficient reserve (
tecNO_LINE_INSUF_RESERVE
) -
Cannot create destination trustline - authorization required (
tecNO_AUTH
) -
MPT-specific:
- Source doesn't hold MPT (
tecOBJECT_NOT_FOUND
) - Source has insufficient balance (
tecINSUFFICIENT_FUNDS
) - MPT cannot be transferred unless destination is issuer (
tecNO_AUTH
) - Source not authorized (
tecNO_AUTH
) - Destination not authorized (
tecNO_AUTH
) - Source locked (
tecLOCKED
) - Destination locked (
tecLOCKED
) - Cannot create destination MPToken - insufficient reserve (
tecINSUFFICIENT_RESERVE
) - Cannot create destination MPToken - authorization required (
tecNO_AUTH
)
2.2.3.3. State Changes¶
- Arrears Handling:
-
If currentTime ≥ NextClaimTime + Frequency AND Balance < SendMax:
- Forfeit remaining Balance
- Advance NextClaimTime by exactly one Frequency
- Reset Balance to SendMax
-
Token Transfer:
For XRP: - Deduct amount from source account balance - Add amount to destination account balance
For IOUs: - Auto-create trustline if needed and possible - Adjust source trustline balance (decrease) - Adjust destination trustline balance (increase) - Apply current TransferRate if applicable
For MPTs: - Auto-create MPToken if needed and possible - Adjust source MPToken balance (decrease) - Adjust destination MPToken balance (increase) - Apply current TransferFee if applicable - Handle issuer special cases (no MPToken for issuer)
- Balance Management:
- Deduct claimed amount from Balance
-
If Balance reaches zero:
- Advance NextClaimTime by one Frequency
- Reset Balance to SendMax
-
Expiration Check:
-
If currentTime ≥ Expiration after successful claim:
- Delete subscription (follow deletion state changes)
-
Update Metadata:
- Update PreviousTxnID and PreviousTxnLgrSeq
2.2.3.4. Example JSON¶
{
"TransactionType": "SubscriptionClaim",
"Account": "rLdCa1mLK5R5Am25ArfXFmqgNwjZgnfy91",
"SubscriptionID": "E8A6F9B99B041DF5C932DF8DA29B2A84B42D4D4F0F4A08931D76D62F42E1F1B9",
"Amount": "50000000",
"Fee": "12",
"Sequence": 100
}
3. Key Differences Between Token Types in Subscriptions¶
Aspect | XRP | IOU Tokens | Multi-Purpose Tokens (MPTs) |
---|---|---|---|
Holdings | Native balance | Trustline required | MPToken object required |
Authorization | N/A | lsfRequireAuth flag | tfMPTRequireAuth flag |
Transfer Capability | Always allowed | Subject to freeze | Requires tfMPTCanTransfer |
Freeze/Lock | N/A | Global/Individual/Deep freeze | Lock conditions |
Transfer Costs | None | TransferRate (current) | TransferFee (current) |
Auto-creation | N/A | Trustline during claim | MPToken during claim |
Reserve for Auto-creation | N/A | Required from destination | Required from destination |
Issuer Special Cases | N/A | Can be source or destination | Doesn't hold MPToken objects |
4. Arrears and Balance Management¶
The subscription system implements a specific arrears mechanism:
- Period Tracking: Each subscription tracks the current period via NextClaimTime
- Balance per Period: Each period has an available Balance (≤ SendMax)
- Partial Claims: Destinations can claim less than the full Balance
- Forfeit on Arrears: If a period ends with unused Balance and the next period is entered:
- The unused Balance is forfeited
- NextClaimTime advances by exactly one Frequency
- Balance resets to the full SendMax
- No Bulk Claims: Only one period can be processed per claim transaction
This design prevents transaction spam while allowing flexible billing patterns.
5. Rationale¶
Key design decisions and their justifications:
5.1. Single Claim per Period¶
Limiting to one claim per period simplifies implementation and prevents spam. Services requiring multiple payments should use appropriate frequencies (e.g., weekly instead of monthly).
5.2. Balance Tracking with Forfeit¶
Tracking balance within periods allows partial claims while the forfeit mechanism prevents indefinite accumulation of claimable amounts.
5.3. No Transfer Rate Storage¶
Using current transfer rates/fees at claim time (rather than storing at creation) simplifies the implementation and aligns with standard XRPL token transfer behavior.
5.4. Dual Ownership Model¶
Allowing both source and destination to cancel provides flexibility for both parties while the reserve requirement remains only with the source.
5.5. Auto-creation of Holdings¶
Allowing trustlines and MPTokens to be created during claims (when authorized) improves user experience for new token recipients.
6. Backwards Compatibility¶
This amendment introduces:
- New ledger object type (Subscription)
- Three new transaction types
- No modifications to existing functionality
The amendment is fully backwards compatible. Existing transactions and ledger objects are unaffected.
7. Security Considerations¶
7.1. Built-in Protections¶
- Amount Limits: Claims cannot exceed authorized amounts
- Time Restrictions: Claims limited by frequency periods
- Balance Tracking: Prevents over-claiming within periods
- Asset Type Validation: Ensures claims match subscription asset
- Authorization Compliance: Respects all issuer requirements
- Reserve Requirements: Economic cost prevents spam
7.2. Potential Risks and Mitigations¶
Risk: Services claiming maximum amounts unnecessarily
- Mitigation: Users can cancel at any time
- Mitigation: Services risk losing customers through abuse
Risk: Users canceling after receiving services
- Mitigation: Services enforce terms of service
- Mitigation: Services can implement deposit/prepayment models
Risk: Timing attacks on period boundaries
- Mitigation: Strict time validation in claim logic
- Mitigation: Atomic period advancement
Risk: Griefing through subscription spam
- Mitigation: Reserve requirements make spam expensive
- Mitigation: Both parties can cancel unwanted subscriptions
7.3. Compliance Features¶
The system respects all existing XRPL compliance mechanisms:
- IOU freeze capabilities remain effective
- MPT lock capabilities remain effective
- Authorization requirements are enforced
- All standard AML/KYC controls apply
8. Reference Implementation¶
A reference implementation is available at: [Link to rippled PR when available]
9. FAQ¶
Q: Why can't I claim multiple periods at once? A: This prevents transaction spam and aligns with typical billing patterns. Services should set appropriate frequencies.
Q: What happens if I don't claim for several periods? A: You can claim as many times as needed to advance to the current period.
Q: Can partial amounts be claimed? A: Yes, useful for usage-based billing where the exact amount varies.
Q: How do transfer rates/fees work? A: Current rates are applied at claim time, not stored at creation, ensuring consistency with normal XRPL transfers.
Q: Can subscriptions be paused? A: Not directly, but services can claim zero amounts or users can cancel and recreate subscriptions.
Q: What if the destination doesn't have a trustline/MPToken? A: If authorization isn't required and the destination has sufficient reserve, these are auto-created during the first claim.
Q: Can I update the frequency or destination? A: No, these are immutable. Cancel and create a new subscription if changes are needed.