Skip to content

Latest commit

 

History

55 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🌳 patapim

Securities lending, native on the XRP Ledger: a fixed-term, credential-gated fund that lends a tokenised security through a lending agent who puts first-loss capital on the line.

XRPL Devnet Track 2 xrpl.js Contribution XLS-65 · XLS-66

🥈 Second place — XRPL Lending Protocol Hackathon ($1,000 + a Swell 2026 travel grant) · Paris, September 2026

Open the app · Video demo · Jury deck · Developer report · One-pager · En français

patapim lets holders of a tokenised security lend it, for a fee, through a lending agent. Eligible holders subscribe to a fixed-term fund, the agent posts first-loss capital in the same security, a market maker borrows the securities against cash collateral, and if the securities do not come back the agent's capital repays the fund. No smart contract: every step is a native XRPL object, and every figure in the app is read from the validated ledger.

Tokenised treasuries and money market funds are arriving on XRPL, and a holder can only sit on them. In traditional finance, agency securities lending is how long-only portfolios earn on their holdings. On chain, for regulated securities, that market does not exist yet, and every primitive it needs already shipped.

🥈 Second place at the XRPL Lending Protocol Hackathon, Paris, 12-13 September 2026. A curated 36-hour event organised by DeVinci Blockchain and sponsored by Ripple, built on the freshly merged XLS-65 and XLS-66 amendments: 14 projects and 45 builders, judged by Ripple's developer relations and product teams on the quality of the developer feedback first. The prize: $1,000, and for the two top teams a full travel grant and a pass to Ripple Swell, New York, 27-29 October 2026.

Table of contents

Try it yourself

The app is deployed at https://patapim-gamma.vercel.app, and a video demo of it is on YouTube. It opens Fund I; the vault picker also lists Fund II, and any vault id can be typed in. Every figure is read from the validated ledger, and the phase shown is the ledger's, not the browser clock's.

The two funds were provisioned for the event in two different phases, so that the jury could see a fund in its term and a fund open for subscription at the same time, and sign into the second one. A closed-ended vault's dates are immutable, so both have moved on since:

Fund I Fund II
During the event in term: one loan of 2,000,000 TBL out, deposits refused tecEXPIRED open for subscription: the demo investor with a credential deposited, tesSUCCESS; the one without was refused, tecNO_AUTH
Since 16 September 2026 the borrower never returned the securities. Once the grace period ran out the agent settled the fund the way the default arc below describes, live: LoanManage impair took NAV per share to 0.60, LoanManage default made the first-loss cover repay the 2,000,000 TBL and NAV back to 1.00, and EscrowFinish claimed the borrower's 2,014,500 USDX collateral. The fund is in redemption with its capital whole; hashes in docs/ON-CHAIN.md in term until 16 December 2026: deposits and withdrawals refused, lending open

The signatures made during the event, through the app's own relay, are in docs/evidence/fund-offering.json and docs/evidence/fund-term.json. To replay the eligibility demo on a fresh pair of funds, node scripts/standing.mjs provisions them in about eight minutes; the two demo investor accounts are described in docs/DEMO-ACCOUNTS.md.

  1. Open a fund and choose Connect Wallet in the header, or go to the Sign tab.
  2. Connect a Xaman wallet on XRPL Devnet, or paste a Devnet key into the demo-key signer.
  3. Sign a VaultDeposit or a VaultWithdraw. The ledger answers according to the fund's phase and the account's credential, and the app shows the result with its explorer link.

The wallet connection is XRPL Commons' xrpl-connect, themed to the app. The wallet signs; the server only relays the signed transaction to XRPL Devnet and reads the result from a validated ledger. Wallets differ in what they can sign (finding F-019):

Wallet Offered Can sign a vault transaction with an MPT amount
Xaman on the deployed app yes in its code, Single Asset Vault support since Xaman 5.1, and its backend accepts a VaultDeposit payload forced to DEVNET
Otsu always, if installed yes, in its code (ripple-binary-codec 2.7.0); a developer build
WalletConnect when NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID is set depends on the wallet; none we checked documents XRPL Devnet. The app re-encodes the signed tx_json the adapter returns
Crossmark, GemWallet always, if installed no: their signing libraries predate vault transactions

No wallet at hand: the Sign tab also accepts a Devnet demo key, which signs in the browser with xrpl.js@5.2.0-beta.0 and never leaves the tab. That path is proven end to end, including from the deployed app.

The trade

Each parameter follows the convention of the market it comes from. Sources and quotes: docs/research/lending-conventions.md.

  1. Subscription. Holders carrying an on-chain credential accepted by the agent's permissioned domain deposit the tokenised security into a closed-ended fund: a subscription period, then an investment period, then redemption, the lifecycle of a closed-end fund.
  2. First-loss capital. The lending agent owns the fund's vault and loan broker and posts cover in the same security. At a 100% cover rate the ledger refuses any loan the cover could not absorb. It is capped capital, not the unlimited indemnity a bank agent lender gives, and the app says so.
  3. The loan. A market maker borrows 2,000,000 TBL for a fixed term, at a lending fee of 25 bps a year. The agent signs, the borrower counter-signs: one LoanSet, two signatures. It is a term loan: the protocol has no recall, which is the proposal at the end of the developer report.
  4. Collateral. The borrower posts cash collateral at 102% of the loan's market value, the customary same-currency margin, in a token escrow to the agent. The market value comes from an on-ledger Price Oracle. The agent can claim the escrow only after the payment date and a one-day grace period, the settlement cycle of a returned Treasury.
  5. Return. Lenders receive the interest net of the agent's fee split, 90/10, the lender-friendly end of published agency splits and the most XLS-66 allows (ManagementFeeRate caps at 10%). The app reports the fund's lending return as fee × utilisation × (1 − agent share), the identity lending pools use for supply rates.
  6. Default. Past the grace period the agent can declare default, and the first-loss capital pays the fund min(DebtTotal × CoverRateMinimum × CoverRateLiquidation, principal), capped by the cover available. At 100% the fund's NAV per share does not move. Verified on chain, not asserted.

On the ledger

Securities lending XRPL object Notes
Lender pool Closed-ended Single Asset Vault XLS-65, VaultKind: 1, the asset is the tokenised security
Lending agent LoanBroker XLS-66, owned by the agent, one per fund
First-loss capital LoanBrokerCoverDeposit posted and paid in the security itself
Eligibility Credentials and a Permissioned Domain the vault's share issuance carries the DomainID
The security Multi-purpose token, TBL require-auth: the transfer agent authorises every holder
Cash collateral Multi-purpose token, USDX, in a token escrow FinishAfter for the agent, CancelAfter for the borrower
Reference price Price Oracle, TBL/USD named in the vault's Data, read by the app
Loan of securities LoanSet, two signatures agent signs, borrower counter-signs
Settlement delay GracePeriod one day, before default can be declared

Holding the security is not enough to lend it. The share issuance carries its own DomainID, so the ledger enforces lender eligibility on the fund position, independently of the security's require-auth gate: a holder needs both, which is exactly what the demo account without a credential shows.

What building it made visible

Developer experience is the event's primary deliverable; the full report is DEVELOPER-REPORT.md. The three that cost us the most:

  • The library the brief mandates cannot originate a loan. signLoanSetByCounterparty in xrpl.js@5.2.0-beta.0 signs with the ordinary transaction prefix and rippled rejects it. Stable 5.2.0 signs correctly but lacks the closed-ended vault types; 5.2.0-beta.1, published during the event, has both. Reproduced offline in one second: node scripts/experiments/counterparty-signature.mjs.
  • The two hackathon networks enforce different lending rules behind the same lending amendments. LoanBrokerSet against an open-ended vault is tesSUCCESS on one and tecNO_PERMISSION on the other, and nothing in server_info or the amendment set tells them apart.
  • A closed-ended vault protects the calendar, not the cash. The ledger refuses a loan whose schedule outlives the vault, but a loan left unpaid leaves lenders unable to withdraw, with no recall path. We propose tfLoanCall.

The default arc

Run twice, the only difference being CoverRateMinimum. At 100%:

state vault assets unrealised loss NAV per share agent cover
loan drawn, 2,000,000 TBL 5,000,000 0 1.00 2,500,000
impaired 5,000,000 2,000,000 0.60 2,500,000
defaulted 5,000,000 0 1.00 500,000

Impairment is a paper loss: NAV per share, (AssetsTotal − LossUnrealized) / shares, drops to 0.60 while AssetsTotal does not move. On default the cover pays the whole loan and the lenders are made whole. At a ten percent cover rate the same default takes the vault to 3,200,000 and NAV per share to 0.64, although the agent had posted enough to absorb half the loan: the rate, before the balance, decides what the cover pays. That contrast is finding F-014.

Every transaction, verified

The flagship lifecycle on XRPL Devnet, regenerated from the chain by node scripts/gen-evidence-table.mjs. The standing funds, the default arcs and the demo account transactions are in docs/ON-CHAIN.md: 79 transactions, each re-verified against the ledger by node scripts/gen-onchain-inventory.mjs.

Transaction Role in patapim Result Hash
MPTokenIssuanceCreate the tokenised security, require-auth so the transfer agent keeps control tesSUCCESS 69A38645
PermissionedDomainSet the eligibility whitelist the vault carries on its share issuance tesSUCCESS 878653B1
VaultCreate the fixed-term lender pool, VaultKind: 1, asset is the security, gated by DomainID tesSUCCESS 565318B0
VaultDeposit an eligible holder subscribes tesSUCCESS F8D70563
VaultDeposit a holder with no credential is refused by the domain tecNO_AUTH 30C4B86F
LoanBrokerSet the lending agent, with its debt ceiling and cover rates tesSUCCESS 98BB53E0
LoanBrokerCoverDeposit first-loss capital, posted in the security tesSUCCESS C5D955D8
VaultDeposit the subscription window has closed, the phase gate fires tecEXPIRED E4F68FB0
VaultWithdraw capital is locked for the term, the second phase gate tecTOO_SOON 32077697
LoanSet the loan of securities, agent signs, borrower counter-signs tesSUCCESS DDD61141
EscrowCreate the borrower posts collateral in escrow to the agent, reclaimable after CancelAfter: XRP on this run, USDX on the standing funds tesSUCCESS 05F9AF64
LoanPay the borrower returns the securities with the interest, in full tesSUCCESS 71DE04C6
EscrowCancel the borrower recovers the collateral after CancelAfter tesSUCCESS 7CE7D679
LoanSet new lending refused once redemption opens, the third phase gate tecEXPIRED 3C01AF18
VaultWithdraw the lender redeems, denominated in shares tesSUCCESS 2FAB3F9D

XLS-65 and XLS-66 transactions used, and where: VaultCreate, VaultDeposit, VaultWithdraw, VaultSet, LoanBrokerSet, LoanBrokerCoverDeposit, LoanSet, LoanPay above and in the standing funds; LoanManage impair and default in the default arcs; VaultClawback and LoanBrokerCoverWithdraw in scripts/experiments/. The primitives of the Loaded flavour alongside them: MPTokenIssuanceCreate, MPTokenAuthorize, CredentialCreate, CredentialAccept, PermissionedDomainSet, OracleSet, EscrowCreate with an MPT amount, EscrowCancel.

On yield. The lender's return is interest. On the flagship run, compressed into minutes, it rounded to nothing: the LoanPay declared 2,000,001 and settled 2,000,000. The standing Fund I is dated in days instead, so its 25 bps accrue visibly: 35 TBL due at return, 32 to the lenders after the agent's tenth. LoanOriginationFee does not help, it goes to the broker owner (F-016). The flag the workshop teaches for injecting yield, a donation on VaultDeposit, is not in the implementation: it is open rippled pull request #6383, tfVaultDonate, behind LendingProtocolV1_2.

Deployed addresses

The XRP Ledger has no contract addresses: what a contract address names elsewhere is a ledger object id here. The explorer's vault page shows the vault, its loan broker and its loans together.

Fund I Fund II
Vault B5EC8B2C…4730 B8286CD5…4530
Loan broker 22731477DB5E866A4FEB929A99091B09C686BE098A63F40F88D2D98C737A6AD5 3D4E887081C40BC126D26C6E2FD2883476CBBC023C194A0AF941A6770FFFBEBA
Loan F9B11DDA5EFA85457CB17F89EFC99C813B155B41A19A6E2A67242EDB2A596D1F, defaulted on 16 September, repaid by the cover none during the event
Vault shares, MPT 00000001FB4ECA99A091C2574B7A586C58ECDB41E83357FD 000000019E4ED3A9645729D633558E005BBF860311912687
Subscription closes 12 September 2026, 22:04 UTC 16 September 2026, 16:00 UTC
Redemption opens 16 September 2026, 16:00 UTC 16 December 2026, 17:00 UTC

Shared by both funds:

Lending agent, vault and broker owner rU57MqUwaPcKF4rTYx4cN8osyT8SkDV6ks
Security TBL, MPT issuance 00504E4C3295762322513439250B2F050A1B016CE5563126, issuer rncTqM69…pUSu
Cash USDX, MPT issuance 00504E4C2BD6C9B523B46C3A87963369E2205F00DD1C8CE7, issuer rhzoMyZp…L1i5
Permissioned domain E6B24E9C6C23DBE2098364844AEEFD418E15CCC4039FB1B87773CA79A9F1625F
Price Oracle TBL/USD owner rJhyrQk6XxJcdFeyTWVcn2aSxRS8LZjFrw, OracleDocumentID 1
Borrower, the market maker rnLnMVxhicYy3Jg4LGFoCvXwbnejyx43cE
Demo investors see docs/DEMO-ACCOUNTS.md

Every account and object, including the historical runs, with a link for each: docs/ON-CHAIN.md.

Repository structure

scripts/              the ledger work
  lib/lending.mjs     shared client, including our own counterparty signer
  standing.mjs        provisions the two standing funds, collateral, oracle and demo investors
  recall-spine.mjs    the flagship lifecycle, end to end
  default-arc.mjs     impairment, default, and the cover settling it
  demo.mjs            the pitch harness: a vault timed to the slot, then one step per beat
  read-vault.mjs      the read path, and the calls it takes
  experiments/        what we fired at the ledger to establish the findings
web/                  the app: Next.js; server routes read the ledger and relay signed transactions
  README.md           routes, environment, wallets, structure and tests
  lib/wallet-manager.ts  xrpl-connect on Devnet, and what each wallet can sign
  lib/finance.ts      every lending metric the dashboard shows, with its definition
docs/
  PATAPIM-DECK.pdf    the jury deck, ten slides
  ON-CHAIN.md         every account and object we created, with explorer links
  DEMO-ACCOUNTS.md    the two demo investor accounts and what the ledger answered them
  ONE-PAGER.en.md     the product on one page, and ONE-PAGER.fr.md in French
  CONTRIBUTION-explorer-search.md  the pull request to the XRPL Explorer, with prior art
  evidence/           transaction hashes per run, machine readable
  research/           the sourced notes behind the report and the lending conventions
  feedback/           the friction log the report was written from
  reference/          where the front end's art direction comes from
DEVELOPER-REPORT.md   the deliverable: three pages, every claim with a hash, a file or a pull request

Getting started

Node 20.19 or newer, the floor xrpl.js declares.

npm install                          # xrpl.js@5.2.0-beta.0, ripple-binary-codec, ripple-keypairs

node scripts/experiments/counterparty-signature.mjs   # finding 1, offline, one second
node scripts/read-vault.mjs t2 B5EC8B2CFF11828C7A3B2552FD14F370659D1CB6857547A3A720E568E1F14730
node scripts/gen-onchain-inventory.mjs                # re-verifies every hash against the ledger

node scripts/standing.mjs            # a fresh standing world on Devnet, about eight minutes
node scripts/recall-spine.mjs        # the flagship lifecycle, about seven minutes
node scripts/default-arc.mjs         # the default arc at a 100% cover rate

cd web && npm install && npm run build && npm run start   # the app, http://localhost:3000

scripts/lib/lending.mjs holds the shared client. Its signCounterparty() deliberately does not use signLoanSetByCounterparty from xrpl.js, which is broken in the version this track mandates.

Contributing back

ripple/explorer#1342, opened during the event. Pasting a LoanBroker or Loan identifier into the XRPL Explorer search returns not-found although the explorer already renders both on the vault page. One ledger_entry call switched on LedgerEntryType resolves all three: the same request count for vaults and brokers, one extra hop for a loan. Detail and prior art in docs/CONTRIBUTION-explorer-search.md.

Team

patapim: Sofiane Ben Taleb (@gamween) and Armand Séchon (@STOOOKEEE).

Demonstration on XRPL Devnet with a fictitious security and a fictitious cash token. Funds and institutions named in this repository are market context, not partners. The front end's art direction is adapted from a public reference study whose provenance is recorded in docs/reference/.

License

MIT © 2026 Sofiane Ben Taleb and Armand Séchon.

Built for the XRPL Lending Protocol Hackathon, Paris · September 2026 · Track 2, closed-ended vaults

Releases

Packages

Contributors

Languages