Amendment XLS: Direct Debit #574
Replies: 4 comments 10 replies
-
|
Interesting proposal! This seems very close to a check, but is there something different in your proposal? For pull-payments, Checks are pretty nice but they do have one drawback in the general case: they're tied to a single payee account. That's actually what you want in a check, however I've thought a lot that a derivative idea would be something like a bearer instrument. So, imagine a check but where there's no single (or even predefined) payee. So, anyone with the instrument can redeem it. This reduces to something like a "bearer token" in some sense. All that to say, is your goal to crate a type of "pull payment" on XRPL, or instead to replicate a sort of "e-cash" on XRPL (or neither)? |
Beta Was this translation helpful? Give feedback.
-
|
Is there a difference between this and XLS-78? |
Beta Was this translation helpful? Give feedback.
-
|
Overriding the existing
This fundamentally changes what a
This is not true when it comes to tooling.
This logic is already used across many transactors - e.g. Since this design doesn't use pathing/cross-currency payments, this applies even moreso.
This is actually a disadvantage, not an advantage. Given how different pull payments operate from regular payments, you don't want any integration to be happening automatically - you want it to be carefully reasoned about. It's likely that an automatic integration would be incorrect, given how different pull payments are from push payments. |
Beta Was this translation helpful? Give feedback.
-
|
Hi @dangell7 and @mvadari Regarding @dangell7 on Regardless of the answer, I can see some benefits of the underlying point. In that way, @dangell7 regarding Checks, I partially agree there. As you mentioned, ledger has Checks for one time pull and is payee timed, capped, expiring, single use,... And for the plain one-shot case it is the right tool. However there are some gaps worth mentioning in my opiniont - now I am thinking about some additional fields if we merge both XLS-78 and direct debit - adding
On the arrears, I need the branch semantics before responding properly. I think this is the most consequential technical point in your post and now we have three incompatible description of XLS-78 period accounting in this thread: the published text (single claim per period - Kris flagged that as a remnamt), Kris's description (multiple partial claims, forfeit at rollover) and yours (missed periods remain claimable in arrears). I would say that before doing anything in terms of accounting, can you please write up what the branch actually implements? More specifically, I would like to know
Based on my understanding of everything, I would say that unbounded arrears are absobable into the merged model at zero state cost. Cumulative accounting ( Regarding the XLS-78 as a base, this is the only thing where I would push back. A subscription ojbect expresses one shape: anchored periods, per-period allowance. The current proposal (+draft that I have with I do need to point out also one small corection: Regarding naming, I am not emotionally attached to it :) What I am holding is that the ledger object be the general mandate, not the label on the amendment. If the community reads "Subscriptions" better than "Direct Debit" or pull payments or whatever for a mandate primitive, no reservation from my side. I am open to a call between us, to lock the merge structure after the arrears discussion is locked down. Please note, I will also update the original spec with draft that I wrote during the end of previous week and during the weekend, that incorporates some of the XLS-78 core functionalities into this one. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Table of Contents
1. Abstract
This specification introduces the
Authorizationledger object and theAuthorizationCreate,AuthorizationUpdate, andAuthorizationDeletetransactions to enable Payee-initiated ("pull") payments on the XRP Ledger. A new optionalAuthorizationIDfield on thePaymenttransaction allows a designated Payee to debit funds from a Payer's account, bounded by a per-pull amount cap, an optional per-interval aggregate cap, and an optional expiration, with an optional single-use flag that terminates the authorization after the first successful pull. An odometer mechanism tracks per-interval consumption on rate-limited authorizations, enabling protocol-level enforcement of limits without external monitoring.2. Motivation
The XRP Ledger currently supports only "push" payments, requiring the Payer to sign every transaction. This is insufficient for recurring financial flows — subscriptions, utility billing, payroll-on-chain, automated service fees - where the Payee must pull funds based on a prior agreement.
Existing primitives partially address the need but each has limitations:
lsfDepositAuthenabled; it does not authorize pulling.This proposal complements existing primitives by providing a secure, revocable, just-in-time pull mechanism. It mirrors the semantic model of SEPA Direct Debit mandates: a standing authorization keyed by an identifier, with creditor-initiated debits bounded by mandate terms, and unilateral revocation by the debtor at any time.
3. Introduction
3.1 Terminology
Authorizationobject. The source of funds debited on a successful pull.AuthorizedAccounton theAuthorizationobject. The only account permitted to initiate a pull against the authorization.Paymenttransaction signed by the Payee that includes anAuthorizationID, debiting the Payer and delivering to the transaction'sDestination.LimitAmountacts as an upper bound on the amount of any single pull, whether or not an interval is set.Interval > 0. The Payee may pull up toLimitAmountin aggregate per rolling interval, tracked by the odometer.Interval == 0and without the single-use flag. Each pull is capped atLimitAmount, but there is no aggregate cap and no odometer state; unlimited pulls are permitted until the Payer revokes or the authorization expires.tfSingleUse. The object is deleted automatically on the first successful pull.DeliveredInPeriodfield on theAuthorizationobject, which tracks cumulative pulls within the current interval. Only maintained whenInterval > 0.LastPullTimestampfirst exceedsInterval,DeliveredInPeriodis reset to zero. The check is performed on the next pull attempt, against the parent ledger close time.3.2 Summary
Ledger Entries introduced
Authorization: stores the terms (Payee, currency, per-pull cap, optional interval, optional expiration, optional single-use flag) under which the Payer permits pulls.Transactions introduced
AuthorizationCreate: Payer establishes a pull authorization. Supports atfSingleUseflag for one-shot authorizations that self-destruct on first pull.AuthorizationUpdate: Payer modifies limit, interval, or expiration without rotating theAuthorizationID. The single-use flag is immutable after creation.AuthorizationDelete: Payer revokes an authorization, or any account reaps an expired authorization.Transactions modified
Payment: gains an optionalAuthorizationIDfield. When present, the transaction is a pull payment whose debit side is bound to the Payer named in the referencedAuthorization.APIs introduced
None. Existing
account_objectsandledger_entryRPCs cover the new object via standard typing.4. Specification
4.1 Ledger Entries
4.1.1 Authorization
The
Authorizationobject stores the terms granted by a Payer to a Payee.4.1.1.1 Object Identifier
The key space for the
Authorizationobject is0x0050. (Subject to confirmation against the current reservation table at implementation time.)If
AuthorizationCreateis submitted via a Ticket,Sequencein the above derivation is replaced by theTicketSequenceconsumed by the transaction.4.1.1.2 Fields
LedgerEntryType0x0050(Authorization).AccountAuthorizedAccountLimitAmountInterval > 0, also serves as the aggregate cap for the current interval (tracked byDeliveredInPeriod). Defines the currency and issuer for all pulls.Interval00disables the aggregate cap (unmetered — each pull remains capped byLimitAmount, no per-interval accounting). Non-zero values must be>= 60.Expiration00means no expiration.DeliveredInPeriod0LimitAmount. Maintained only whenInterval > 0; whenInterval == 0the field is present but never updated.LastPullTimestamp0Interval > 0.OwnerNodePreviousTxnIDPreviousTxnLgrSeqFlags0lsfflags. See §4.1.1.3.4.1.1.3 Flags
lsfSingleUse0x000100004.1.1.4 Ownership
The
Authorizationobject is owned by theAccount(Payer) and is recorded in that account'sOwnerDirectory. TheAuthorizedAccountis not an owner of the object and has no entry in its own owner directory for this purpose.4.1.1.5 Reserves
Each
Authorizationobject increments the Payer's owner count by 1, requiring one incremental owner reserve. Standard fee voting applies. There is no deviation from default behavior.4.1.1.6. Deletion
An
Authorizationobject is deleted in any of the following cases:AuthorizationDeletefor the object'sAuthorizationID.AuthorizationDeletefor the object whenparentCloseTime() >= ExpirationandExpiration != 0.Paymentis applied against anAuthorizationwithlsfSingleUseset.The owner reserve is always returned to the Payer regardless of which path triggers deletion.
The
Authorizationobject is a deletion blocker for its owner: an account holding one or moreAuthorizationobjects as Payer cannot be deleted viaAccountDeleteuntil all such objects are removed.4.1.1.7 Freeze / Lock
Authorization itself holds no assets. Freeze and lock semantics apply at pull time to the Payer's trust line for the asset described by
LimitAmount, exactly as for an ordinaryPaymentwhose source is the Payer:LimitAmount's currency blocks pulls.Pulls blocked by freeze/lock fail with the corresponding standard
Paymentfailure code (tecFROZEN,tecLOCKED, etc.). TheAuthorizationobject itself is not deleted by a freeze/lock event.4.1.1.8 Invariants
Let
<auth>denote theAuthorizationobject before transaction application and<auth>'after.<auth>'.LedgerEntryType == <auth>.LedgerEntryType<auth>'.Account == <auth>.Account<auth>'.AuthorizedAccount == <auth>.AuthorizedAccount<auth>'.Flags == <auth>.Flags(alllsfflags, includinglsfSingleUse, are constant after creation)currency(<auth>'.LimitAmount) == currency(<auth>.LimitAmount)andissuer(<auth>'.LimitAmount) == issuer(<auth>.LimitAmount)<auth>'.DeliveredInPeriod <= <auth>'.LimitAmount<auth>.Interval > 0, then<auth>'.LastPullTimestamp >= <auth>.LastPullTimestamp<auth>.Interval == 0, then<auth>'.DeliveredInPeriod == <auth>.DeliveredInPeriodand<auth>'.LastPullTimestamp == <auth>.LastPullTimestamp(odometer state is inert)AuthorizationUpdate: if<auth>'.Expiration != 0, then<auth>'.Expiration > parentCloseTime()at application4.1.1.9 RPC Name
In
account_objectsandledger_datatypefilters:authorization.4.1.1.10. Example JSON
{ "LedgerEntryType": "Authorization", "Account": "rPayerAccount...", "AuthorizedAccount": "rPayeeAccount...", "LimitAmount": { "currency": "EUR", "issuer": "rIssuerAccount...", "value": "50" }, "Interval": 2592000, "Expiration": 820454400, "DeliveredInPeriod": { "currency": "EUR", "issuer": "rIssuerAccount...", "value": "12.50" }, "LastPullTimestamp": 798220800, "OwnerNode": "0", "PreviousTxnID": "F4D7C9...A2B1", "PreviousTxnLgrSeq": 87234901, "Flags": 0, "index": "9C8E2A..." }4.2 Transactions
4.2.1 AuthorizationCreate
Submitted by the Payer to establish a pull authorization.
4.2.1.1 Fields
TransactionTypeAuthorizationCreate.AccountAuthorizedAccountAccountand reference an existing, funded account.LimitAmountInterval > 0, also serves as the aggregate cap for the current interval. Must be strictly positive.Interval00or>= 60.0= no aggregate cap (each pull remains capped byLimitAmount).Expiration0parentCloseTimeat submission.Flags0tfflags defined below (§4.2.1.2).4.2.1.2 Flags
tfSingleUse0x00010000AuthorizationwithlsfSingleUseset. On the first successful pull, the object is deleted and the owner reserve is returned to the Payer. Compatible with anyIntervalvalue (though most commonly used withInterval == 0).4.2.1.3. Transaction Fee
Standard transaction fee applies; no deviation.
4.2.1.4 Failure Conditions
Data validation (
tem):temBAD_AMOUNT—LimitAmountis non-positive or malformed.temBAD_EXPIRATION—Expirationis not strictly in the future.temBAD_INTERVAL—Intervalis positive and less than 60.temDST_IS_SRC—AuthorizedAccountequalsAccount.temINVALID_FLAG— Reservedtfflag bits are set.Protocol-level (
tec):tecNO_DST—AuthorizedAccountdoes not exist on the ledger.tecINSUFFICIENT_RESERVE— Payer cannot meet the incremented owner reserve.4.2.1.5 State Changes
Authorizationledger entry is created with the fields supplied in the transaction. ThelsfSingleUsebit of the object'sFlagsis set ifftfSingleUsewas set on the transaction.DeliveredInPeriodis initialized to zero in the currency/issuer ofLimitAmount.LastPullTimestampis initialized to0.Authorization'sAuthorizationIDis added to the Payer'sOwnerDirectory.4.2.1.6 Example JSON
Recurring subscription (€50 per 30 days, fixed 1-year term):
{ "TransactionType": "AuthorizationCreate", "Account": "rPayerAccount...", "AuthorizedAccount": "rPayeeAccount...", "LimitAmount": { "currency": "EUR", "issuer": "rIssuerAccount...", "value": "50" }, "Interval": 2592000, "Expiration": 820454400, "Fee": "12" }Unmetered per-pull cap for a card-like flow (€500 max per pull, no aggregate cap, expires with card):
{ "TransactionType": "AuthorizationCreate", "Account": "rPayerAccount...", "AuthorizedAccount": "rProcessorAccount...", "LimitAmount": { "currency": "EUR", "issuer": "rIssuerAccount...", "value": "500" }, "Expiration": 883008000, "Fee": "12" }Single-use authorization for a one-off invoice (€1,200, must be pulled within 7 days):
{ "TransactionType": "AuthorizationCreate", "Account": "rPayerAccount...", "AuthorizedAccount": "rMerchantAccount...", "LimitAmount": { "currency": "EUR", "issuer": "rIssuerAccount...", "value": "1200" }, "Expiration": 798825600, "Flags": 65536, "Fee": "12" }4.2.2 AuthorizationUpdate
Submitted by the Payer to modify an existing authorization without rotating its
AuthorizationID.4.2.2.1 Fields
TransactionTypeAuthorizationUpdate.AccountAuthorizationIDLimitAmountInterval > 0). Currency and issuer must match the existing object.Interval0disables the aggregate cap; non-zero values must be>= 60.Expiration0removes any existing expiration.At least one of
LimitAmount,Interval, orExpirationmust be present. ThelsfSingleUseflag is fixed at creation and cannot be modified here (see §4.1.1.8 Invariants).AuthorizationUpdatedoes not resetDeliveredInPeriodorLastPullTimestamp. If the newLimitAmountis lower than the currentDeliveredInPeriod, subsequent pulls fail withtecAUTH_LIMIT_EXCEEDEDuntil the next interval rollover. ChangingIntervalfrom a non-zero value to0freezes the odometer at its current value (no further increments, no rollover); changingIntervalfrom0to a non-zero value starts the odometer from zero at the next pull.4.2.2.2 Flags
No
tfflags are defined for this transaction.4.2.2.3 Transaction Fee
Standard transaction fee applies; no deviation.
4.2.2.4 Failure Conditions
Data validation (
tem):temBAD_AMOUNT—LimitAmountis non-positive or malformed.temBAD_CURRENCY—LimitAmountcurrency or issuer does not match the referenced object.temBAD_EXPIRATION—Expirationis positive and not strictly in the future.temBAD_INTERVAL—Intervalis positive and less than 60.temMALFORMED— None ofLimitAmount,Interval,Expirationprovided.Protocol-level (
tec):tecAUTH_NOT_FOUND— NoAuthorizationobject with the givenAuthorizationID.tecNO_PERMISSION—Accountis not the Payer of the referenced authorization.4.2.2.5 State Changes
Authorizationobject'sLimitAmount,Interval, and/orExpirationare updated to the supplied values.4.2.2.6 Example JSON
{ "TransactionType": "AuthorizationUpdate", "Account": "rPayerAccount...", "AuthorizationID": "9C8E2A...", "LimitAmount": { "currency": "EUR", "issuer": "rIssuerAccount...", "value": "75" }, "Fee": "12" }4.2.3 AuthorizationDelete
Submitted by the Payer to revoke an authorization at any time, or by any account to reap an expired authorization.
4.2.3.1 Fields
TransactionTypeAuthorizationDelete.AccountExpiration— any funded account.AuthorizationID4.2.3.2. Flags
No
tfflags are defined for this transaction.4.2.3.3. Transaction Fee
Standard transaction fee applies; no deviation.
4.2.3.4. Failure Conditions
Protocol-level (
tec):tecAUTH_NOT_FOUND— NoAuthorizationobject with the givenAuthorizationID.tecNO_PERMISSION— Submitted by a non-Payer while the authorization is unexpired or hasExpiration == 0.4.2.3.5. State Changes
Authorizationobject is removed from the ledger.AuthorizationIDis removed from the Payer'sOwnerDirectory.4.2.3.6. Example JSON
{ "TransactionType": "AuthorizationDelete", "Account": "rPayerAccount...", "AuthorizationID": "9C8E2A...", "Fee": "12" }4.2.4. Payment (modified)
A new optional
AuthorizationIDfield is added toPayment. When present, thePaymentis a pull payment: the transaction signer is the Payee, but balance and trust-line validation are performed against the Payer named in the referencedAuthorization.4.2.4.1. Fields (additions and modified semantics)
AccountAuthorizationIDis present: the Payee (signer). Must equalAuthorization.AuthorizedAccount.DestinationAmountAuthorization.LimitAmount.AuthorizationIDAuthorizationobject that the pull is executed against. When absent, thePaymentbehaves identically to its pre-amendment semantics.SendMaxAuthorizationIDis present (see §4.2.4.4).PathsAuthorizationIDis present (see §4.2.4.4).All other
Paymentfields retain their standard meanings.Source-of-funds remapping. When
AuthorizationIDis present, all standardPaymentvalidation that normally applies totx.Accountfor the debit side — balance, trust lines, freeze, RequireAuth, sender DepositAuth — is performed againstAuthorization.Account(the Payer). The transaction signer remainstx.Account(the Payee), and the transaction fee is paid by the Payee.4.2.4.2. Flags
No new
tfflags are introduced forPaymentby this amendment. ExistingPaymentflags retain their meanings;tfPartialPaymentandtfLimitQualityare inapplicable to pull payments because cross-currency and partial-payment semantics are prohibited (see §4.2.4.4) and are rejected astemINVALID_FLAG.4.2.4.3. Transaction Fee
Standard transaction fee applies, paid by the Payee. No deviation.
4.2.4.4. Failure Conditions
In addition to all existing
Paymentfailure conditions, the following apply whenAuthorizationIDis present.Data validation (
tem):temMALFORMED—SendMaxorPathspresent alongsideAuthorizationIDand statically detectable at parse time.temINVALID_FLAG—tfPartialPaymentortfLimitQualityset on a pull payment.temDISABLED—featureDirectDebitis not enabled.Protocol-level (
tec):tecAUTH_NOT_FOUND— NoAuthorizationobject with the givenAuthorizationID.tecAUTH_WRONG_PAYEE—tx.Accountdoes not equalAuthorization.AuthorizedAccount.tecAUTH_EXPIRED—Authorization.Expiration != 0andparentCloseTime() >= Authorization.Expiration.tecAUTH_BAD_CURRENCY—tx.Amountcurrency or issuer does not matchAuthorization.LimitAmount.tecAUTH_LIMIT_EXCEEDED— Pull would exceed the applicable limit: eithertx.Amount > LimitAmount(per-pull cap, always applied) or, whenInterval > 0,DeliveredInPeriod + tx.Amount > LimitAmount(aggregate cap).Paymentteccodes evaluated against the Payer's balance and trust lines (tecUNFUNDED_PAYMENT,tecNO_LINE,tecPATH_DRY,tecFROZEN,tecLOCKED,tecNO_AUTH, …).4.2.4.5. State Changes
On
tesSUCCESS:Authorization.Interval > 0:Authorization.DeliveredInPeriodis incremented bytx.Amount(after lazy interval reset, per §4.2.4.7) andAuthorization.LastPullTimestampis set toparentCloseTime().Authorization.Interval == 0: no odometer update is made;DeliveredInPeriodandLastPullTimestampremain unchanged.Authorization.Flags & lsfSingleUse: theAuthorizationobject is removed, its owner reserve returned, and its ID removed from the Payer'sOwnerDirectory.tx.Amountexactly as in a standardPaymentwhere the Payer is the source.Destination's relevant balance or trust line is credited bytx.Amount.4.2.4.6. Metadata Fields
A synthetic metadata field is added to the validated transaction to ease indexer and explorer disambiguation:
pulled_from_accountAuthorizationIDis present and transaction succeeds)Authorization.Account) from which funds were debited.4.2.4.7 Example JSON
{ "TransactionType": "Payment", "Account": "rPayeeAccount...", "Destination": "rPayeeAccount...", "Amount": { "currency": "EUR", "issuer": "rIssuerAccount...", "value": "12.50" }, "AuthorizationID": "9C8E2A...", "Fee": "12" }5. Rationale
Why not Payment Channels? Channels require pre-funding, which locks capital for the life of the channel. Direct debit keeps capital liquid in the Payer's account until the moment of pull, mirroring how SEPA Direct Debit and ACH function in traditional finance. A user with a dozen subscriptions should not need a dozen funded channels.
Why not DepositPreauth? DepositPreauth authorizes a counterparty to send funds into an account with
lsfDepositAuthenabled — it gates inbound flow, not outbound. It is structurally the inverse of what direct debit requires.Why reuse
Paymentinstead of a newPullPaymenttransaction? ReusingPaymentensures pull payments inherit standard semantics (fee model, account flags, freezes, RequireAuth checks). It also keeps the amendment surface minimal and avoids duplicating logic across transactors.Why an integer-seconds
Interval? Deterministic, cheap to compare at consensus, and consistent with existing temporal fields (ExpirationonEscrow,CancelAfteronPayChannel). String formats (ISO-8601, RFC 5545 RRULE) would introduce parsing complexity and ambiguity at validator level.Why three orthogonal knobs (
LimitAmount,Interval,lsfSingleUse) instead of overloadingInterval? My first idea of this proposal actually overloadedInterval == 0to mean "single-use authorization." That conflates three independent concerns — per-pull cap, aggregate rate limit, and object lifecycle — into a single field. At GateHub, few people gave me a comment about this and I decided that I will separate them:LimitAmountalways caps any individual pull.Intervaloptionally adds an aggregate cap over a rolling window (odometer). Absent, only the per-pull cap applies.lsfSingleUseoptionally terminates the authorization after the first successful pull, regardless ofInterval.The resulting matrix cleanly covers real-world shapes:
IntervallsfSingleUse€Xper N days)> 000(or any)Why the odometer pattern (
DeliveredInPeriod)? Lazy reset on next pull is O(1) per transaction and avoids any per-ledger sweep job. Validators evaluate state only when a pull is attempted, and only for interval-capped authorizations.Why lazy reset over fixed windows? Lazy reset is simpler and avoids edge cases around the "first" window boundary. A known property of lazy reset is that a Payee may pull
LimitAmountatt = Interval − εand again att = Interval + ε, effectively doubling the per-interval cap in a short burst. This is judged acceptable because (a) the Payer can revoke at any time, (b) the long-run rate remains bounded byLimitAmount / Interval, and (c) fixed anchored windows introduce their own gameable boundaries without materially better security.Why is
lsfSingleUseimmutable after creation? A mutable single-use bit would let a Payer flip an already-existing recurring authorization into a self-destructing one, which effectively achieves cancellation without the transparency ofAuthorizationDelete(noDeletedtransaction in the metadata trail). Immutability preserves clean audit semantics: authorizations are either created single-use or created recurring, and both paths to termination — expiration and explicit delete — are visible on-chain.Why allow third-party
Destination? Real-world billing flows often involve a payment processor pulling on behalf of an underlying merchant. RestrictingDestinationto the Payee would force the processor either to become the merchant of record or to immediately forward funds in a second transaction, doubling fees. Security is preserved by the Payer's explicit authorization of the Payee — the Payer trusts the Payee to direct funds appropriately, exactly as with a SEPA mandate.Why a minimum
Intervalof 60 seconds? Prevents ledger churn from pathological high-frequency authorizations while remaining far below any plausible legitimate use case. The genuine technical floor is one ledger close (roughly 3–5 seconds); the 60-second minimum is a UX guardrail rather than a protocol requirement.Why a separate
AuthorizationUpdate? Payees and their billing systems typically cache theAuthorizationIDas a stable mandate reference. Forcing a delete/recreate cycle to change a limit would invalidate that reference and create a window of revocation, both of which are operationally hostile to legitimate subscription billing.Why prohibit
SendMaxandPathsinitially? A pull payment is signed by the Payee, but slippage in a cross-currency path is a cost borne by the Payer. The Payer has no way to express slippage tolerance in the currentAuthorizationschema, so cross-currency pulls are deferred until a future amendment extends the schema with an explicit per-authorization slippage bound (see §10).6. Backwards Compatibility
This proposal is strictly additive. The modified
Paymenttransaction behaves identically to its current form whenAuthorizationIDis absent. New ledger object and transaction types are introduced behind thefeatureDirectDebitamendment and have no effect on existing applications until activation.Before activation:
AuthorizationCreate,AuthorizationUpdate, orAuthorizationDeleteare rejected withtemDISABLED.Paymenttransactions containing anAuthorizationIDare rejected withtemDISABLED.0x0050exists.7. Test Plan
A reference test plan is to be provided alongside the
rippledpull request. At minimum it must cover:AuthorizationCreateLimitAmounts.Interval > 0,lsfSingleUseunset), unmetered per-pull cap (Interval == 0,lsfSingleUseunset), and single-use (lsfSingleUseset).lsfSingleUseis correctly reflected in the resultingAuthorizationobject whentfSingleUseis set on the transaction.temfailure paths (bad amount, bad expiration, bad interval, source-is-destination, invalid flag bits).tecINSUFFICIENT_RESERVEagainst an account at its reserve floor.tecNO_DSTagainst an unfundedAuthorizedAccount.AuthorizationUpdateLimitAmount,Interval, andExpiration.temBAD_CURRENCYon currency mismatch.tecNO_PERMISSIONon submission by non-Payer.LimitAmountbelow currentDeliveredInPeriodand verify subsequent pull fails withtecAUTH_LIMIT_EXCEEDEDuntil rollover.Intervalfrom non-zero to0: verify odometer state is frozen and subsequent pulls only enforce the per-pull cap.Intervalfrom0to non-zero: verify next pull starts a fresh odometer from zero.lsfSingleUseis immutable — no update path modifies the object'sFlags.AuthorizationDeleteExpiration: reserve returned to Payer.tecNO_PERMISSIONon third-party delete beforeExpiration.Payment(pull)Destination(with and without DepositPreauth of Payer), Clawback-enabled trust line.tecAUTH_WRONG_PAYEEwhen a non-authorized account attempts the pull.tecAUTH_EXPIREDat and after expiration time.tecAUTH_BAD_CURRENCYagainst a mismatched amount.tecAUTH_LIMIT_EXCEEDEDwhentx.Amount > LimitAmount, on both interval-capped and unmetered authorizations, at exact boundary and over.tecAUTH_LIMIT_EXCEEDEDwhenDeliveredInPeriod + tx.Amount > LimitAmount, at exact boundary and over.Interval == 0,lsfSingleUseunset): verify unlimited successful pulls up toLimitAmounteach, and confirmDeliveredInPeriodandLastPullTimestampare never modified.lsfSingleUseset): first successful pull deletes the object and returns the reserve; second attempt fails withtecAUTH_NOT_FOUND.Interval > 0: object is still deleted on first pull (single-use dominates the object's lifecycle regardless ofInterval).temMALFORMEDwithSendMaxand withPaths.Paymentfailure paths (unfunded, no line, path dry, frozen, locked) propagate.pulled_from_accountis present and correct on success.Account lifecycle
AccountDeleteof a Payer with outstanding authorizations: blocked.AccountDeleteof a Payee with an authorization naming it: succeeds; subsequent pulls fail; Payer can still revoke and reclaim reserve.Amendment gating
AuthorizationIDfield rejected withtemDISABLEDbefore activation.8. Reference Implementation
To be added: link to
rippledPR(s) andxrpl.js/xrpl-pysupport PRs.9. Security Considerations
Payer authority. Only the Payer can create, update, or unilaterally revoke an
Authorization. The Payee has no path to widen the terms or to assign the authorization to a different account.Signer verification. The ledger validates that only the
AuthorizedAccountlisted in the object may use theAuthorizationIDin a pullPayment. The signature check follows standard transaction signing rules and supports Regular Keys and multisigning via SignerList.Limit enforcement. Enforced at consensus by all validators. No external monitoring required.
Interval boundary behavior. As discussed in §5, lazy reset permits a 2× burst at the interval boundary. Payers needing strict rate limits should set
LimitAmountto half the desired ceiling, or revoke and recreate with a fresh window.Reserve and spam. Each authorization carries an owner reserve, bounding the number of outstanding authorizations a Payer can hold. Failed pulls still incur the standard transaction fee, paid by the Payee, deterring spam-pulls against revoked or limit-exhausted authorizations.
Griefing vectors.
Expired-authorization reaping. Any account may delete an expired authorization, with the reserve returned to the Payer. This avoids reserve-lock by neglect, mirroring the
EscrowCancelreaping pattern.Account deletion as Payer. Blocked while authorizations exist, preventing dangling references and ensuring the reserve is properly accounted for at account deletion.
Replay protection. Standard XRPL
Sequence/TicketSequencesemantics on the pullPaymentprevent replay.Path injection.
PathsandSendMaxare prohibited on pull payments in this version, eliminating an entire class of pathfinding-based attacks. When cross-currency pull semantics are added in a future amendment, they will require explicit per-authorization slippage bounds set by the Payer.Interaction with DepositAuth. Standard DepositAuth rules apply to the
Destinationaccount exactly as for ordinaryPayments. If the Destination haslsfDepositAuthset, the Payer (as the effective sender of funds) must be on the Destination's DepositPreauth list. A Payer withlsfDepositAuthset may still create authorizations:AuthorizationCreateis the Payer's explicit consent and is independent of inbound-deposit gating.Interaction with Clawback (XLS-39). If
LimitAmountis an IOU whose issuer haslsfAllowTrustLineClawbackset, balances delivered to theDestinationremain subject to clawback exactly as with ordinaryPayments.Amendment safety. Gated behind
featureDirectDebit. Before activation, all new transaction types and theAuthorizationIDfield onPaymentare rejected withtemDISABLED.10. Appendix
10.1. Design Discussion
Cross-currency pulls are deferred. The natural way to support them is to add a
MinDeliveredAmountfield (or equivalent slippage bound) to theAuthorizationobject. The Payer thereby expresses, at authorization time, the minimum acceptable delivered amount for any pull, decoupling the Payee's slippage choices from the Payer's exposure. This is left to a future amendment to keep the present surface area minimal and focused.Why
0x0050? Chosen as a plausible-looking unused value at the time of writing; subject to confirmation against the live reservation table at PR time.Why a Default Value of
0forInterval?0reads as "no aggregate rate limit" — the authorization enforces only the per-pullLimitAmount. This makes the most permissive setting (from the Payee's perspective) the default, while the Payer still bounds exposure throughLimitAmount,Expiration, and revocation. Recurring authorizations must be explicit about their reset period, which is the right default given that stating anIntervalis a deliberate rate-limiting choice.Why not a
MaxPerPullfield in addition toLimitAmount? BecauseLimitAmountalready serves this role uniformly across all three authorization shapes. For interval-capped authorizations,LimitAmountacts as both per-pull cap and aggregate cap; for unmetered authorizations, it acts as per-pull cap only. Splitting these into two fields would double the schema surface without adding expressive power, since a Payer wanting distinct per-pull and aggregate caps can compose twoAuthorizationobjects.Card-like flows. For card programs the recommended shape is
Interval == 0withLimitAmountset to the per-transaction cap andExpirationset to the card expiry. Per-day, per-month, and MCC restrictions are enforced by the card processor off-chain, inside the trust envelope the on-chain authorization establishes. This mirrors how existing card networks operate: the network primitive expresses cardholder-to-issuer trust; program rules are enforced by the issuer, not the network.10.2. FAQ
Q. Can the Payee assign or sublicense the authorization?
No. The
AuthorizedAccountfield is constant after creation and the Payee has no transaction available to modify or delegate it.Q. What happens if the Payer's account is reorganized via
SetRegularKeyafter authorization?Nothing changes from the authorization's perspective. The
Accountfield references the AccountID, not a particular key. SubsequentAuthorizationUpdate/AuthorizationDeletefrom the Payer use whatever signer is currently valid for that AccountID.Q. Can the same
(Payer, Payee)pair have multipleAuthorizationobjects?Yes. Each
AuthorizationCreatecreates a distinct object with its ownAuthorizationIDderived from the Payer'sSequence. This allows, for example, separate authorizations per currency or per service line, or one small-per-pull authorization plus one large single-use authorization.Q. Why isn't there an
AuthorizationFreezeto temporarily suspend pulls without losing the ID?AuthorizationUpdatesettingLimitAmountto a one-drop minimum, or setting anExpirationin the very near future, can achieve effective suspension. A dedicated freeze field can be added in a later revision if real-world usage shows demand.Q. How does this handle card-like flows with both per-transaction and per-day limits?
The on-chain primitive expresses one cap dimension. Card processors typically want per-transaction, per-day, per-month, and per-MCC caps simultaneously — those layered rules are enforced by the processor off-chain, using the on-chain authorization only as the outer trust envelope (e.g.,
Interval == 0andLimitAmount= per-transaction cap). A Payer wanting layered on-chain caps can hold multipleAuthorizationobjects with differentIntervalandLimitAmountcombinations, but the ledger will only enforce whicheverAuthorizationIDthe Payee references on a given pull.AI Disclosure: I used ClaudeAI model Opus 4.7 only for grammar issues and for using it to conform to the https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0001-xls-process.
Beta Was this translation helpful? Give feedback.
All reactions