Skip to content

Instantly share code, notes, and snippets.

@dangell7
Last active September 22, 2026 21:44
Show Gist options
  • Select an option

  • Save dangell7/a7bc0c5b3ef0767b36f09f21543abd81 to your computer and use it in GitHub Desktop.

Select an option

Save dangell7/a7bc0c5b3ef0767b36f09f21543abd81 to your computer and use it in GitHub Desktop.
XLS Draft: Repo (Repurchase Agreements)
  xls: XX
  title: Repo
  description: Bilateral repurchase agreements with on-ledger collateral and time-proportional interest.
  author: Denis Angell (@dangell7)
  category: Amendment
  status: Draft
  proposal-from: TBD
  requires: XLS-85
  created: 2026-09-22
  updated: 2026-09-22

Repo

1. Abstract

This amendment adds a repurchase agreement (repo) primitive to the XRP Ledger. A repurchase agreement is a secured loan written as two sales: the borrower sells an asset for cash and commits to buy it back later at a higher price, and the difference between the two prices is the interest. The opening sale is leg 1 and the repurchase is leg 2.

A seller locks collateral and receives cash from a buyer (leg 1). The seller has a committed, enforceable right to buy the collateral back by paying principal plus interest proportional to the elapsed time, before maturity (leg 2). If the seller does not pay by maturity plus a grace period, anyone can trigger default and the buyer keeps the collateral.

It is one ledger entry and five transactions, using the ledger's native offer/accept idiom. The locking comes from Token Escrow (XLS-85) and the rate math, rounding and grace/default semantics come from Lending (XLS-66). RepoClose performs the payment of RepurchaseAmount and the unlock of CollateralAmount in one transaction, and a failure of the payment fails the transaction, so the collateral is never released without the payment. No existing transaction releases an asset conditional on a payment landing.

2. Motivation

The US market alone runs about $12.6T in daily exposures, and roughly 80% of tri-party volume is overnight. Tri-party repo is repo where a third-party agent holds the collateral and handles valuation and substitution for both sides. Broadridge clears about $9T a month on DLR, and JPMorgan's Kinexys has settled over $3T. Kinexys' signature product is intraday repo, borrowing against collateral for a few hours and paying interest only for the minutes outstanding.

Every live platform automates the same slice: atomic delivery versus payment (DvP) at creation, meaning the collateral and the cash move together or neither moves; collateral immobilized, meaning held where neither party can spend or move it for the term; and a deterministic unwind. None of them put margin calls, substitution or netting on-chain. A margin call is a demand for more collateral when the existing collateral falls in value. Substitution is the seller swapping one piece of collateral for another during the term. Netting is settling many trades between the same two parties as one net amount. All three fall back to the Global Master Repurchase Agreement (GMRA), the standard bilateral contract the repo market trades under, which sets what happens when a party fails to perform. Adoption happened anyway. This spec follows the same line: automate the performing trade, leave the rest to the contract referenced in the Data field.

3. Specification

The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT" and "MAY" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

3.1. Ledger Entry: Repo

3.1.1. Object Identifier

Key Space: 0xXXXX (TBD, next free value)

The ID is the tagged hash of the key space prefix, the seller's AccountID, and the transaction Sequence (or ticket) of the creating RepoCreate.

3.1.2. Fields

Field Name Constant Required Internal Type Default Description
LedgerEntryType Yes Yes UINT16 TBD Identifies this as a Repo entry
Account No Yes ACCOUNT N/A Seller (cash borrower). Owns the entry
Counterparty No Yes ACCOUNT N/A Buyer (cash lender)
CollateralAmount No Yes AMOUNT N/A XRP, IOU or MPT locked for the term
PurchasePrice No Yes AMOUNT N/A Cash delivered to the seller at accept. MUST be a different asset than the collateral
InterestRate No Yes UINT32 N/A Annualized rate in 1/10 basis points, the same scale sfInterestRate carries on ltLOAN, so 100,000 is 100 percent per year. 0 is valid, and the ceiling is kMaxInterestRate
Expiration No Yes UINT32 N/A Pending offer expiry. After this, anyone MAY cancel
StartDate No Conditional UINT32 N/A Set by RepoAccept to that ledger's close time. Absent while pending
MaturityDate No Yes UINT32 N/A Latest permissible repurchase. MUST be greater than StartDate
GracePeriod No Yes UINT32 N/A Seconds after MaturityDate before default becomes possible
TransferRate No Optional UINT32 N/A Collateral issuer's transfer rate captured at create (XLS-85 pattern)
Data No Optional BLOB N/A Up to 256 bytes. Pointer to the governing legal annex or trade ID. Institutions need the GMRA reference
OwnerNode No Yes UINT64 N/A Owner directory hint (seller)
DestinationNode No Yes UINT64 N/A Owner directory hint (buyer)
IssuerNode No Conditional UINT64 N/A Issuer directory hint for issued-asset collateral, per XLS-85
PreviousTxnID No Yes HASH256 N/A Standard
PreviousTxnLgrSeq No Yes UINT32 N/A Standard

The buyer normally lends less cash than the collateral is worth, and that shortfall is the haircut, the buyer's protection against the collateral falling in value. There is no haircut field. It is implicit in PurchasePrice relative to the collateral's value, and in cleared dealer flow it is often 0% anyway.

The repurchase price is computed at close, not stored:

RepurchaseAmount = PurchasePrice * (1 + InterestRate * elapsed / (100,000 * 31,536,000))
elapsed          = min(closeTime, MaturityDate) - StartDate      [seconds]

Computed in Number, rounded up to the cash asset's precision per the XLS-66 rounding rules, so the buyer is never short by a rounding step.

3.1.3. Flags

No flags are defined. Pending and active are distinguished by whether StartDate is present.

3.1.4. Ownership

The entry is owned by Account (the seller) and appears in both parties' owner directories, plus the issuer directory for issued-asset collateral (XLS-85 pattern).

3.1.5. Reserves

One owner reserve, charged to Account.

3.1.6. Deletion

Deleted by exactly three paths: RepoCancel (pending), RepoClose (active, paid) and RepoDefault (active, expired). An account with an open Repo cannot be deleted: AccountDelete returns tecHAS_OBLIGATIONS, and because the entry sits in both owner directories that holds for the buyer as well as the seller, and for an IOU issuer whose asset is the collateral.

A transaction of any other type that removes a Repo fails the ValidRepo invariant.

Applying an action to a repo in the wrong state returns tecREPO_ACTIVE when the action requires a pending repo and tecREPO_PENDING when it requires an active one.

3.1.7. Freeze/Lock

Collateral locking follows XLS-85 exactly: issuer opt-in via lsfAllowTrustLineLocking (IOU) and lsfMPTCanEscrow (MPT), sfLockedAmount accounting, and the XLS-85 freeze tables at create, close and default time.

3.1.8. Invariants

  1. A Repo is created (pending) if and only if exactly CollateralAmount becomes newly locked from Account and zero cash moves.
  2. Pending to active via RepoAccept requires exactly PurchasePrice delivered from Counterparty to Account and StartDate set to that ledger's close time.
  3. Deletion via RepoCancel requires full unlock to Account and zero cash moved. If the submitter is not the seller, the close time MUST be past Expiration.
  4. Deletion via RepoClose requires the buyer credited at least the computed RepurchaseAmount (net of the cash asset's transfer rate) and the full collateral unlocked to the seller. RepurchaseAmount >= PurchasePrice always, since the rate is non-negative and rounding goes up.
  5. Deletion via RepoDefault requires close time past MaturityDate + GracePeriod, full collateral delivered to Counterparty, and zero cash moved.
  6. While any Repo exists, aggregate locked balances are at least the sum of open Repo and Escrow collateral. This extends the XLS-85 locked-balance invariant.
  7. CollateralAmount, PurchasePrice, InterestRate and MaturityDate are immutable after create. Changing terms means close and recreate.

Invariants 1, 2, 4 and 5 are statements about how much value moved. An invariant checker is handed the entries a transaction touched and not the amounts it paid, so those four are enforced by balance assertions in the test suite rather than by a checker. The ValidRepo checker enforces 3 and 7, the state machine (no active repo reverting to pending, no repo deleted by any transaction but RepoCancel, RepoClose and RepoDefault), and the field bounds: positive collateral and price, MaturityDate after Expiration, and the seller never equal to the counterparty. Invariant 6 is XLS-85's locked-balance invariant, and the XRP half is enforced by XRPNotCreated, which ltREPO was added to.

3.1.9. RPC Name

repo, in account_objects as a type filter and in ledger_entry either as an object index or as an object of account (the seller) and seq (the sequence of the creating RepoCreate).

3.2. Transaction: RepoCreate

The seller's offer. Locks the collateral immediately and creates the entry in a pending state. Locking at create means the buyer accepts against collateral already immobilized.

3.2.1. Fields

Field Name Required Internal Type Description
Account Yes ACCOUNT Seller
Counterparty Yes ACCOUNT Buyer. Only this account may submit RepoAccept. An offer open to any acceptor is not defined here
CollateralAmount Yes AMOUNT Asset to lock
PurchasePrice Yes AMOUNT Cash the buyer must deliver at accept
InterestRate Yes UINT32 Annualized, 1/10 basis points. Above kMaxInterestRate returns temBAD_AMOUNT
Expiration Yes UINT32 Offer expiry
MaturityDate Yes UINT32 Repurchase deadline
GracePeriod Yes UINT32 Seconds of grace after maturity
Data Optional BLOB Legal annex or trade ID reference

3.2.2. Failure Conditions

Follows the XLS-85 create table: frozen, unauthorized or non-lockable assets fail. Additionally: the issuer of the collateral MUST be neither party, collateral and cash MUST be different assets, Counterparty MUST NOT be Account, and MaturityDate MUST be sane relative to Expiration.

3.2.3. State Changes

Collateral locked from Account (XLS-85 machinery), Repo entry created in both owner directories (plus issuer directory for issued assets), one owner reserve charged.

3.3. Transaction: RepoAccept

The buyer's activation. Valid only from the account named in Counterparty, while pending, before Expiration.

3.3.1. Fields

Field Name Required Internal Type Description
Account Yes ACCOUNT Buyer (must equal the entry's Counterparty)
RepoID Yes HASH256 The pending Repo entry

3.3.2. Failure Conditions

Not the account named in Counterparty, already active, past Expiration, or the payment of PurchasePrice fails (insufficient funds, frozen cash asset).

3.3.3. State Changes

PurchasePrice delivered from buyer to seller through the payment engine, StartDate set to this ledger's close time. The repo is active. Interest runs from when the cash actually moved, not from when the offer was posted.

3.4. Transaction: RepoCancel

Pre-accept exit. The seller at any time while pending, or any account after Expiration, the same permissioning as EscrowCancel.

3.4.1. Failure Conditions

Entry is active, or the submitter is neither the seller nor past Expiration.

3.4.2. State Changes

Collateral unlocked back to the seller, entry deleted, reserve released. No cash moves.

3.5. Transaction: RepoClose

Leg 2. Submitted by the seller any time up to MaturityDate + GracePeriod.

3.5.1. Failure Conditions

Not the seller, entry still pending, past MaturityDate + GracePeriod, or the payment of RepurchaseAmount fails. A failed payment fails the whole transaction and the collateral stays locked.

3.5.2. State Changes

In one transaction: RepurchaseAmount computed and paid seller to buyer through the payment engine (buyer-side trustline/MPToken auto-creation per the XLS-85 finish rules), collateral unlocked back to the seller, entry deleted.

3.6. Transaction: RepoDefault

Submittable by any account once close time passes MaturityDate + GracePeriod, the same permissioning as EscrowFinish and EscrowCancel.

3.6.1. Failure Conditions

Entry pending, or close time not yet past MaturityDate + GracePeriod.

3.6.2. State Changes

Collateral delivered to Counterparty at the stored TransferRate, entry deleted. There is no partial settlement. The buyer keeps all the collateral, which is the close-out outcome the GMRA gives for a fully collateralized single trade. Close-out is the termination of the trade on a failure to perform, with the non-defaulting party keeping or liquidating what it holds.

3.7. Lifecycle

  RepoCreate (seller)                       RepoAccept (buyer, before Expiration)
  collateral locked --> [ Repo: PENDING ] -- cash to seller --> [ Repo: ACTIVE ]
                              |                                      |
                   RepoCancel (seller anytime,     +-----------------+------------------+
                   anyone after Expiration)        |                                    |
                   unlock to seller, delete   RepoClose (seller,               RepoDefault (anyone,
                                              t <= Maturity+Grace)             t > Maturity+Grace)
                                              pays P*(1+r*elapsed/yr)          collateral to buyer,
                                              unlock to seller, delete         delete

Pending has one entrance and two exits. Active has two exits. There are no other states. Batch(RepoCreate, RepoAccept) (XLS-56) settles creation and acceptance atomically. An open (rolling) repo is a daily Batch(RepoClose, RepoCreate, RepoAccept).

4. Rationale

Offer/accept, not one jointly signed transaction. Requiring both parties to sign a single transaction has no wallet support, forces both signing ceremonies into the same window, and offers no asynchronous path. Offers, Checks, NFTokenOffers and PayChan are all offer/accept, each party signing its own transaction on its own schedule. A desk that can coordinate signatures wraps Create and Accept in a Batch.

Computed repurchase price. A stored fixed repurchase price would only fit term repo. Computing it from elapsed time covers overnight, term and intraday repo with one object.

No collateral pricing. The protocol does not value collateral. Parties negotiate the price, the ledger enforces the mechanics.

All-collateral default with no oracle. An oracle-priced surplus refund adds an oracle to the trust model of a settlement primitive. Surplus refund can come later as an opt-in.

What v1 leaves out. Collateral substitution, variation margin (topping up collateral as prices move), repricing, tri-party agent roles, general collateral baskets (any bond from an agreed list rather than one named bond), netting, negative rates, coupon pass-through, rehypothecation (the buyer lending out or reusing the locked collateral during the term), and on-ledger matching. Matching and negotiation stay off-ledger, as on every live platform.

Privacy and market transparency. Everything in a v1 Repo is public: sizes, rates, maturities, counterparties. That is a deliberate trade. A public ledger of real repo trades produces a verifiable overnight rate curve. SOFR, the Secured Overnight Financing Rate published by the New York Fed, is computed from data nobody outside can audit. Broadridge and Kinexys get their confidentiality from running permissioned chains. Desks that need privacy today use omnibus accounts, where many clients' positions sit under one account name, and agent accounts, where a third party holds the position on the client's behalf. Once Confidential MPT (XLS-96) lands, v2 can make the amounts commitments while the structure stays publicly enforceable. What we will not do is move the terms off-chain into Data: if the rate and price are not on-chain, RepoClose cannot compute or enforce anything.

5. Backwards Compatibility

None. This is a new amendment gated behind featureRepo, purely additive. No existing entry, transaction or RPC changes behavior.

6. Registration

Field and type codes are allocated clear of XRPLF develop at commit 9403736199.

Code
featureRepo Supported::No, VoteBehavior::DefaultNo
ltREPO 0x0095
Ledger name space 'Z'
ttREPO_CREATE 114
ttREPO_ACCEPT 115
ttREPO_CANCEL 116
ttREPO_CLOSE 117
ttREPO_DEFAULT 118
sfCollateralAmount AMOUNT 39
sfPurchasePrice AMOUNT 40
sfMaturityDate UINT32 95
sfRepoID UINT256 47
tecREPO_ACTIVE 211
tecREPO_PENDING 212

Eleven of the entry's fields are develop's own and are reused rather than allocated: sfAccount, sfCounterparty, sfInterestRate, sfStartDate, sfExpiration, sfGracePeriod, sfTransferRate, sfData, sfOwnerNode, sfDestinationNode and sfIssuerNode.

7. Test Plan

src/test/app/Repo_test.cpp (xrpl.app.Repo) covers the amendment gate, the malformed creates, accept-and-close, cancel before and after Expiration, default after the grace period, interest accrual asserted to the exact repurchase amount, the kMaxInterestRate ceiling, IOU collateral with and without the issuer's opt-in, IOU collateral frozen on either side, MPT collateral including the unlock to the buyer, account deletion blocked for both parties while a repo is open, and the account_objects and ledger_entry lookups.

src/test/app/invariants/InvariantsRepo_test.cpp (xrpl.app.InvariantsRepo) drives ValidRepo with deliberate violations, including a deletion by a transaction that is not one of the three.

8. Reference Implementation

A reference implementation exists and will be published with the amendment proposal. It is registered Supported::No, VoteBehavior::DefaultNo while it is unaudited.

Partial repurchase and rehypothecation are not implemented and are not in this spec. There is no RPC beyond the account_objects filter and ledger_entry.

9. Security Considerations

  • RepoClose is the novel money-movement path: a payment and an unlock on two different assets in one transaction, each with its own transfer rate. This is where the audit should spend its time.
  • Accrual rounding reuses the XLS-66 round-up rules verbatim.
  • Freeze, lock and authorization edge cases at create, close and default time follow the XLS-85 tables.
  • Default is deliberately non-blockable. Neither party can prevent RepoDefault after the grace period, since any account can submit it. A frozen cash asset can block RepoClose; the seller's remedy is the grace period and, failing that, default, which moves no cash.
  • The pending state is bounded by Expiration and cancellable by any account after it, so a pending entry cannot hold the seller's reserve indefinitely.

Appendix

Appendix A: FAQ

A.1: Isn't this just Batch + TokenEscrow?

No. Escrow debits the funding account at create, and the repo seller by definition does not hold the repurchase cash during the term. That is why the trade exists, and it is why the leg-2 cash cannot be pre-funded.

A.2: Why a new ledger object instead of building it into Escrow?

Repo is an inverted escrow. Return to the owner is the common outcome and requires a payment, and delivery to the destination is the outcome after the deadline. Building that into Escrow means a mode flag that flips the meaning of Finish and Cancel and puts a payment inside EscrowCancel. Every existing integrator assumes cancel refunds, finish delivers, and no other money moves. Changing that adds a second meaning to every branch in transactors that are already audited, and puts repo-only fields in Escrow permanently. This spec reuses Escrow's machinery (lock helpers, locked-balance accounting, freeze tables) as shared code, not its object or its transactors.

A.3: Doesn't the Lending Protocol (XLS-66) already cover this?

No. Lending is vault-intermediated, under-collateralized and amortizing, with principal repaid in instalments across the term. Repo is bilateral, fully collateralized and bullet-maturity, with the whole principal repaid in one payment at the end. Different products for different counterparties.

A.4: What happens if the collateral is worth less than the cash at default?

The buyer takes the loss, as with under-margined traditional repo.

A.5: Can the buyer use the collateral during the term (rehypothecation)?

No. The collateral sits in the ledger lock, not in the buyer's account, so the buyer cannot fail to return it. GMRA mini close-out, the clause that terminates the trade when one side fails to deliver the securities back, exists to handle that failure.

Appendix B: References

  • XLS-85 Token Escrow: locking model, issuer flags, freeze tables
  • XLS-66 Lending Protocol: rate math, GracePeriod and default handling
  • XLS-56 Batch: atomic create+accept, rollover composition
  • GMRA 2011: mini close-out, close-out netting. OFR and NY Fed US repo statistics
  • Broadridge DLR, JPMorgan Kinexys intraday repo, Canton/Circle tokenized-UST repo: the adoption precedents this design follows
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment