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.
🥈 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.
- Try it yourself
- The trade
- On the ledger
- What building it made visible
- The default arc
- Every transaction, verified
- Deployed addresses
- Repository structure
- Getting started
- Contributing back
- Team
- License
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.
- Open a fund and choose Connect Wallet in the header, or go to the Sign tab.
- Connect a Xaman wallet on XRPL Devnet, or paste a Devnet key into the demo-key signer.
- Sign a
VaultDepositor aVaultWithdraw. 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.
Each parameter follows the convention of the market it comes from. Sources and quotes:
docs/research/lending-conventions.md.
- 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.
- 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.
- 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. - 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.
- 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 (
ManagementFeeRatecaps at 10%). The app reports the fund's lending return as fee × utilisation × (1 − agent share), the identity lending pools use for supply rates. - 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.
| 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.
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.
signLoanSetByCounterpartyinxrpl.js@5.2.0-beta.0signs 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.
LoanBrokerSetagainst an open-ended vault istesSUCCESSon one andtecNO_PERMISSIONon the other, and nothing inserver_infoor 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.
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.
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.
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.
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
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:3000scripts/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.
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.
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/.
MIT © 2026 Sofiane Ben Taleb and Armand Séchon.