Compare commits

..

1 Commits

Author SHA1 Message Date
Calvin Jhunjhuwala
fd9b174adb merged main branch, tweaking design to utilize the grid 2026-02-02 17:31:58 -08:00
319 changed files with 7566 additions and 26208 deletions

2
.gitignore vendored
View File

@@ -9,8 +9,6 @@ yarn-error.log
.venv/
_code-samples/*/js/package-lock.json
*.css.map
_code-samples/*/*/*[Ss]etup.json
.claude/
# PHP
composer.lock

View File

@@ -2,7 +2,6 @@ navbar.about: Acerca de
navbar.docs: Docs
navbar.resources: Recursos
navbar.community: Comunidad
navbar.showcase: Escaparate
footer.about: Acerca de
footer.docs: Docs
footer.resources: Recursos

View File

@@ -75,10 +75,10 @@ Clioをインストールする前に、以下の条件を満たしている必
```
gpg: WARNING: no command supplied. Trying to guess what you mean ...
pub ed25519 2026-02-16 [SC] [expires: 2033-02-14]
E057C1CF72B0DF1A4559E8577DEE9236AB06FAA6
uid TechOps Team at Ripple <techops+rippled@ripple.com>
sub ed25519 2026-02-16 [S] [expires: 2029-02-15]
pub rsa3072 2019-02-14 [SC] [expires: 2026-02-17]
C0010EC205B35A3310DC90DE395F97FFCCAFD9A2
uid TechOps Team at Ripple <techops+rippled@ripple.com>
sub rsa3072 2019-02-14 [E] [expires: 2026-02-17]
```

View File

@@ -50,10 +50,10 @@ labels:
```
gpg: WARNING: no command supplied. Trying to guess what you mean ...
pub ed25519 2026-02-16 [SC] [expires: 2033-02-14]
E057C1CF72B0DF1A4559E8577DEE9236AB06FAA6
uid TechOps Team at Ripple <techops+rippled@ripple.com>
sub ed25519 2026-02-16 [S] [expires: 2029-02-15]
pub rsa3072 2019-02-14 [SC] [expires: 2026-02-17]
C0010EC205B35A3310DC90DE395F97FFCCAFD9A2
uid TechOps Team at Ripple <techops+rippled@ripple.com>
sub rsa3072 2019-02-14 [E] [expires: 2026-02-17]
```
特に、フィンガープリントが一致することを確認してください。(上記の例では、フィンガープリントは三行目の`C001`で始まる部分です。)

View File

@@ -16,7 +16,6 @@ navbar.about: 概要
navbar.docs: ドキュメント
navbar.resources: リソース
navbar.community: コミュニティ
navbar.showcase: ショーケース
footer.about: 概要
footer.docs: ドキュメント
footer.resources: リソース
@@ -156,8 +155,6 @@ amendment.table.status: ステータス
amendment.status.enabled: 有効
amendment.status.eta: 予定
amendment.status.openForVoting: 投票中
amendment.status.inactive: 無効
amendment.status.inactiveButton: 詳細を取得する
# index.page.tsx
home.hero.h1part1: ビジネスのための

View File

@@ -23,7 +23,6 @@ type AmendmentsCachePayload = {
// API data caching
const amendmentsEndpoint = 'https://vhs.prod.ripplex.io/v1/network/amendments/vote/main/'
const amendmentsInfoEndpoint = 'https://vhs.prod.ripplex.io/v1/network/amendments/info/main/'
const amendmentsCacheKey = 'xrpl.amendments.mainnet.cache'
const amendmentsTTL = 15 * 60 * 1000 // 15 minutes in milliseconds
@@ -181,8 +180,6 @@ function AmendmentBadge(props: { amendment: Amendment }) {
const enabledLabel = translate("amendment.status.enabled", "Enabled")
const votingLabel = translate("amendment.status.openForVoting", "Open for Voting")
const etaLabel = translate("amendment.status.eta", "Expected")
const inactiveLabel = translate("amendment.status.inactive", "Inactive")
const inactiveButton = translate("amendment.status.inactiveButton", "Get details")
React.useEffect(() => {
const amendment = props.amendment
@@ -205,16 +202,10 @@ function AmendmentBadge(props: { amendment: Amendment }) {
else if (amendment.consensus) {
setStatus(`${votingLabel}: ${amendment.consensus}`)
setColor('80d0e0')
setHref(undefined)
setHref(undefined) // No link for voting amendments
}
// Fallback: amendment is inactive
else {
setStatus(`${inactiveLabel}: ${inactiveButton}`)
setColor('lightgrey')
setHref(`/resources/known-amendments#${amendment.name.toLowerCase()}`)
}
}, [props.amendment, enabledLabel, etaLabel, votingLabel, inactiveLabel])
}, [props.amendment, enabledLabel, etaLabel, votingLabel])
// Split the status at the colon to create two-color badge
const parts = status.split(':')
const label = shieldsIoEscape(parts[0])
@@ -266,32 +257,15 @@ export function AmendmentDisclaimer(props: {
const response = await fetch(amendmentsEndpoint)
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`)
throw new Error(`HTTP error! status: ${response.status}`)
}
const data: AmendmentsResponse = await response.json()
writeAmendmentsCache(data.amendments)
const found = data.amendments.find(a => a.name === props.name)
// 3. If not found in live data, try the info endpoint.
if (!found) {
const infoResponse = await fetch(amendmentsInfoEndpoint)
if (!infoResponse.ok) {
throw new Error(`HTTP error from info endpoint! Status: ${infoResponse.status}`)
}
const infoData: AmendmentsResponse = await infoResponse.json()
const foundInInfo = infoData.amendments.find(a => a.name === props.name)
if (!foundInInfo) {
throw new Error(`Couldn't find ${props.name} amendment in status tables.`)
}
setStatus(foundInInfo)
return
throw new Error(`Couldn't find ${props.name} amendment in status table.`)
}
setStatus(found)
@@ -415,8 +389,6 @@ export function Badge(props: {
"更新": "blue", // ja: updated in
"in development": "lightgrey",
"開発中": "lightgrey", // ja: in development
"inactive": "lightgrey",
"無効": "lightgrey" // ja: inactive
}
let childstrings = ""

View File

@@ -11,11 +11,43 @@ export { default as chevronDown } from "../../../../static/img/navbar/chevron-do
export { default as hamburgerIcon } from "../../../../static/img/navbar/hamburger-icon.svg";
export { default as arrowUpRight } from "../../../../static/img/icons/arrow-up-right-custom.svg";
// Wallet icons for submenu
export { default as greenWallet } from "../../../../static/img/navbar/green-wallet.svg";
export { default as lilacWallet } from "../../../../static/img/navbar/lilac-wallet.svg";
export { default as yellowWallet } from "../../../../static/img/navbar/yellow-wallet.svg";
export { default as pinkWallet } from "../../../../static/img/navbar/pink-wallet.svg";
export { default as blueWallet } from "../../../../static/img/navbar/blue-wallet.svg";
// Develop submenu icons
export { default as devHomeIcon } from "../../../../static/img/navbar/dev_home.svg";
export { default as learnIcon } from "../../../../static/img/navbar/learn.svg";
export { default as codeSamplesIcon } from "../../../../static/img/navbar/code_samples.svg";
export { default as docsIcon } from "../../../../static/img/navbar/docs.svg";
export { default as clientLibIcon } from "../../../../static/img/navbar/client_lib.svg";
// Use Cases submenu icons
export { default as paymentsIcon } from "../../../../static/img/navbar/payments.svg";
export { default as tokenizationIcon } from "../../../../static/img/navbar/tokenization.svg";
export { default as creditIcon } from "../../../../static/img/navbar/credit.svg";
export { default as tradingIcon } from "../../../../static/img/navbar/trading.svg";
// Community submenu icons
export { default as communityIcon } from "../../../../static/img/navbar/community.svg";
// Network submenu icons
export { default as insightsIcon } from "../../../../static/img/navbar/insights.svg";
export { default as resourcesIcon } from "../../../../static/img/navbar/resources.svg";
// Network submenu pattern images (used for both light and dark mode)
export { default as resourcesIconPattern } from "../../../../static/img/navbar/resources-icon.svg";
export { default as insightsIconPattern } from "../../../../static/img/navbar/insights-icon.svg";
// Submenu icons - imported once, exported individually and used in navIcons mapping
// Wallet icon mapping for dynamic icon lookup
import greenWallet from "../../../../static/img/navbar/green-wallet.svg";
import lilacWallet from "../../../../static/img/navbar/lilac-wallet.svg";
import yellowWallet from "../../../../static/img/navbar/yellow-wallet.svg";
import pinkWallet from "../../../../static/img/navbar/pink-wallet.svg";
import blueWallet from "../../../../static/img/navbar/blue-wallet.svg";
import devHomeIcon from "../../../../static/img/navbar/dev_home.svg";
import learnIcon from "../../../../static/img/navbar/learn.svg";
import codeSamplesIcon from "../../../../static/img/navbar/code_samples.svg";
@@ -26,33 +58,15 @@ import tokenizationIcon from "../../../../static/img/navbar/tokenization.svg";
import creditIcon from "../../../../static/img/navbar/credit.svg";
import tradingIcon from "../../../../static/img/navbar/trading.svg";
import communityIcon from "../../../../static/img/navbar/community.svg";
import fundingIcon from "../../../../static/img/navbar/funding.svg";
import contributeIcon from "../../../../static/img/navbar/contribute.svg";
import ecosystemIcon from "../../../../static/img/navbar/ecosystem.svg";
import insightsIcon from "../../../../static/img/navbar/insights.svg";
import resourcesIcon from "../../../../static/img/navbar/resources.svg";
// Re-export submenu icons for direct imports
export default {
devHomeIcon,
learnIcon,
codeSamplesIcon,
docsIcon,
clientLibIcon,
paymentsIcon,
tokenizationIcon,
creditIcon,
tradingIcon,
communityIcon,
fundingIcon,
contributeIcon,
ecosystemIcon,
insightsIcon,
resourcesIcon,
};
// Dynamic icon lookup for navbar submenus
export const navIcons: Record<string, string> = {
export const walletIcons: Record<string, string> = {
green: greenWallet,
lilac: lilacWallet,
yellow: yellowWallet,
pink: pinkWallet,
blue: blueWallet,
dev_home: devHomeIcon,
learn: learnIcon,
code_samples: codeSamplesIcon,
@@ -63,9 +77,7 @@ export const navIcons: Record<string, string> = {
credit: creditIcon,
trading: tradingIcon,
community: communityIcon,
contribute: contributeIcon,
insights: insightsIcon,
resources: resourcesIcon,
ecosystem: ecosystemIcon,
funding: fundingIcon,
};

View File

@@ -14,7 +14,6 @@ export const navItems: NavItem[] = [
{ label: "Use Cases", labelTranslationKey: "navbar.usecases", href: "/use-cases", hasSubmenu: true },
{ label: "Community", labelTranslationKey: "navbar.community", href: "/community", hasSubmenu: true },
{ label: "Network", labelTranslationKey: "navbar.network", href: "/resources", hasSubmenu: true },
{ label: "Showcase (Temporary)", labelTranslationKey: "navbar.showcase", href: "/showcase", hasSubmenu: false },
];
// Develop submenu data structure
@@ -119,19 +118,19 @@ export const communitySubmenuData: {
{ label: "Blog", href: "/blog" },
],
},
{ label: "Funding", href: "/community/developer-funding", icon: "funding" },
{ label: "Funding", href: "/community/developer-funding", icon: "code_samples" },
],
right: [
{
label: "Contribute",
href: "/community/contribute",
icon: "contribute",
icon: "client_lib",
children: [
{ label: "Bug Bounty", href: "/blog/2020/rippled-1.5.0#bug-bounties-and-responsible-disclosures" },
{ label: "Research", href: "https://xls.xrpl.org/" },
],
},
{ label: "Ecosystem Map", href: "/about/uses", icon: "ecosystem" },
{ label: "Ecosystem Map", href: "/about/uses", icon: "learn" },
],
};

View File

@@ -1,7 +1,7 @@
import * as React from "react";
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { SubmenuArrow, SubmenuChildArrow } from "../icons";
import { navIcons } from "../constants/icons";
import { walletIcons } from "../constants/icons";
import { hasChildren, type SubmenuItem } from "../types";
interface MobileMenuSectionProps {
@@ -21,7 +21,7 @@ export function MobileMenuSection({ item }: MobileMenuSectionProps) {
<React.Fragment>
<a href={item.href} className="bds-mobile-menu__tier1 bds-mobile-menu__parent-link">
<span className="bds-mobile-menu__icon">
<img src={navIcons[item.icon]} alt={translate(item.label)} />
<img src={walletIcons[item.icon]} alt="" />
</span>
<span className="bds-mobile-menu__link bds-mobile-menu__link--bold">
{translate(item.label)}

View File

@@ -2,7 +2,7 @@ import * as React from "react";
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { SubmenuSection } from "./SubmenuSection";
import { ArrowIcon } from "../icons";
import { navIcons, resourcesIconPattern, insightsIconPattern } from "../constants/icons";
import { walletIcons, resourcesIconPattern, insightsIconPattern } from "../constants/icons";
import { developSubmenuData, useCasesSubmenuData, communitySubmenuData, networkSubmenuData } from "../constants/navigation";
import type { SubmenuItem, SubmenuItemWithChildren, NetworkSubmenuSection } from "../types";
@@ -239,7 +239,7 @@ function NetworkSubmenuContent({ isActive, isClosing, onClose }: { isActive: boo
<div key={section.label} className="bds-submenu__section">
<a href={section.href} className="bds-submenu__tier1 bds-submenu__parent-link">
<span className="bds-submenu__icon">
<img src={navIcons[section.icon]} alt={translate(section.label)} />
<img src={walletIcons[section.icon]} alt="" />
</span>
<span className="bds-submenu__link bds-submenu__link--bold">
{translate(section.label)}

View File

@@ -1,6 +1,6 @@
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { ArrowIcon } from "../icons";
import { navIcons } from "../constants/icons";
import { walletIcons } from "../constants/icons";
import { hasChildren, type SubmenuItem, type SubmenuItemWithChildren, type SubmenuItemBase } from "../types";
interface SubmenuSectionProps {
@@ -28,7 +28,7 @@ export function SubmenuSection({ item, showChildren = true }: SubmenuSectionProp
<div className="bds-submenu__section">
<a href={item.href} className="bds-submenu__tier1 bds-submenu__parent-link">
<span className="bds-submenu__icon">
<img src={navIcons[item.icon]} alt={translate(item.label)} />
<img src={walletIcons[item.icon]} alt="" />
</span>
<span className="bds-submenu__link bds-submenu__link--bold">
{translate(item.label)}

View File

@@ -1,3 +0,0 @@
# Lending Protocol Examples
Code samples showing how to create a loan broker, claw back first-loss capital, deposit and withdraw first-loss capital, create a loan, manage a loan, and repay a loan.

View File

@@ -1,394 +0,0 @@
# Lending Protocol Examples (JavaScript)
This directory contains JavaScript examples demonstrating how to create a loan broker, claw back first-loss capital, deposit and withdraw first-loss capital, create a loan, manage a loan, and repay a loan.
## Setup
Install dependencies before running any examples:
```sh
npm i
```
---
## Create a Loan Broker
```sh
node createLoanBroker.js
```
The script should output the LoanBrokerSet transaction, loan broker ID, and loan broker pseudo-account:
```sh
Loan broker/vault owner address: rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY
Vault ID: 33E51DD0333775E37F2CC1EB0DA788F9C663AF919DC23ED595A8D69330E5CD68
=== Preparing LoanBrokerSet transaction ===
{
"TransactionType": "LoanBrokerSet",
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY",
"VaultID": "33E51DD0333775E37F2CC1EB0DA788F9C663AF919DC23ED595A8D69330E5CD68",
"ManagementFeeRate": 1000
}
=== Submitting LoanBrokerSet transaction ===
Loan broker created successfully!
=== Loan Broker Information ===
LoanBroker ID: 0AA13C8A8E95D8F2D9EF1FA1B15EF4668EF779A678D1D24D099C532E126E8BBF
LoanBroker Psuedo-Account Address: rfhftuQGpqUVRcERZbY9htJshijKur7dS4
```
---
## Claw Back First-loss Capital
```sh
node coverClawback.js
```
The script should output the cover available, the LoanBrokerCoverDeposit transaction, cover available after the deposit, the LoanBrokerCoverClawback transaction, and the final cover available after the clawback:
```sh
Loan broker address: r9tQSk5rQdjjVGn1brt8K5XNYFvNSLv3xU
MPT issuer address: rJ7DiJdcThwLD5rZjC7D1neXmvLFAGk9t3
LoanBrokerID: 655C32ADFCA0712F3CB32CA034C29FE3DE9DE876A86141F0902FB1E05DA0E442
MPT ID: 00349F41BFA01892C83AC779E4BBB80C8CE3B92D401E4B6E
=== Cover Available ===
0 TSTUSD
=== Preparing LoanBrokerCoverDeposit transaction ===
{
"TransactionType": "LoanBrokerCoverDeposit",
"Account": "r9tQSk5rQdjjVGn1brt8K5XNYFvNSLv3xU",
"LoanBrokerID": "655C32ADFCA0712F3CB32CA034C29FE3DE9DE876A86141F0902FB1E05DA0E442",
"Amount": {
"mpt_issuance_id": "00349F41BFA01892C83AC779E4BBB80C8CE3B92D401E4B6E",
"value": "1000"
}
}
=== Submitting LoanBrokerCoverDeposit transaction ===
Cover deposit successful!
=== Cover Available After Deposit ===
1000 TSTUSD
=== Verifying Asset Issuer ===
MPT issuer account verified: rJ7DiJdcThwLD5rZjC7D1neXmvLFAGk9t3. Proceeding to clawback.
=== Preparing LoanBrokerCoverClawback transaction ===
{
"TransactionType": "LoanBrokerCoverClawback",
"Account": "rJ7DiJdcThwLD5rZjC7D1neXmvLFAGk9t3",
"LoanBrokerID": "655C32ADFCA0712F3CB32CA034C29FE3DE9DE876A86141F0902FB1E05DA0E442",
"Amount": {
"mpt_issuance_id": "00349F41BFA01892C83AC779E4BBB80C8CE3B92D401E4B6E",
"value": "1000"
}
}
=== Submitting LoanBrokerCoverClawback transaction ===
Successfully clawed back 1000 TSTUSD!
=== Final Cover Available After Clawback ===
0 TSTUSD
```
---
## Deposit and Withdraw First-loss Capital
```sh
node coverDepositAndWithdraw.js
```
The script should output the LoanBrokerCoverDeposit, cover balance after the deposit, the LoanBrokerCoverWithdraw transaction, and the cover balance after the withdrawal:
```sh
Loan broker address: rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY
LoanBrokerID: F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15
MPT ID: 0031034FF84EB2E8348A34F0A8889A54F45F180E80F12341
=== Preparing LoanBrokerCoverDeposit transaction ===
{
"TransactionType": "LoanBrokerCoverDeposit",
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY",
"LoanBrokerID": "F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15",
"Amount": {
"mpt_issuance_id": "0031034FF84EB2E8348A34F0A8889A54F45F180E80F12341",
"value": "2000"
}
}
=== Submitting LoanBrokerCoverDeposit transaction ===
Cover deposit successful!
=== Cover Balance ===
LoanBroker Pseudo-Account: rf5FREUsutDyDAaVPPvZnNmoEETr21sPDd
Cover balance after deposit: 2000 TSTUSD
=== Preparing LoanBrokerCoverWithdraw transaction ===
{
"TransactionType": "LoanBrokerCoverWithdraw",
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY",
"LoanBrokerID": "F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15",
"Amount": {
"mpt_issuance_id": "0031034FF84EB2E8348A34F0A8889A54F45F180E80F12341",
"value": "1000"
}
}
=== Submitting LoanBrokerCoverWithdraw transaction ===
Cover withdraw successful!
=== Updated Cover Balance ===
LoanBroker Pseudo-Account: rf5FREUsutDyDAaVPPvZnNmoEETr21sPDd
Cover balance after withdraw: 1000 TSTUSD
```
---
## Create a Loan
```sh
node createLoan.js
```
The script should output the LoanSet transaction, the updated LoanSet transaction with the loan broker signature, the final LoanSet transaction with the borrower signature added, and then the loan information:
```sh
Loan broker address: rn6CD8i3Yc3UGagSosZfegG7hpXxwgVAgz
Borrower address: rN2PMxegkEMZHin78o7wSs1JeYjxAvAfvt
LoanBrokerID: 3CDEA7CEB9F2ECDD76CD41A864F4E3B5DB9C91AEDBD0906EE466FDD21CCF49B5
=== Preparing LoanSet transaction ===
{
"TransactionType": "LoanSet",
"Account": "rn6CD8i3Yc3UGagSosZfegG7hpXxwgVAgz",
"Counterparty": "rN2PMxegkEMZHin78o7wSs1JeYjxAvAfvt",
"LoanBrokerID": "3CDEA7CEB9F2ECDD76CD41A864F4E3B5DB9C91AEDBD0906EE466FDD21CCF49B5",
"PrincipalRequested": "1000",
"InterestRate": 500,
"PaymentTotal": 12,
"PaymentInterval": 2592000,
"GracePeriod": 604800,
"LoanOriginationFee": "100",
"LoanServiceFee": "10",
"Flags": 0,
"Sequence": 3670743,
"LastLedgerSequence": 3673248,
"Fee": "2"
}
=== Adding loan broker signature ===
TxnSignature: F8B2F2AB960191991FC48120A48A089B479018A6469466E43E6F974E1345B32688D59D381E6BC18B6CA383235B708FE4FB44527C51E5B29BCDCC4A08C340A00A
SigningPubKey: EDDABC72936FF734FA56D6C60C064D48C5DA9911C8B7C26C4AEAC06534B5D7C530
Signed loanSetTx for borrower to sign over:
{
"TransactionType": "LoanSet",
"Flags": 0,
"Sequence": 3670743,
"LastLedgerSequence": 3673248,
"PaymentInterval": 2592000,
"GracePeriod": 604800,
"PaymentTotal": 12,
"InterestRate": 500,
"LoanBrokerID": "3CDEA7CEB9F2ECDD76CD41A864F4E3B5DB9C91AEDBD0906EE466FDD21CCF49B5",
"Fee": "2",
"SigningPubKey": "EDDABC72936FF734FA56D6C60C064D48C5DA9911C8B7C26C4AEAC06534B5D7C530",
"TxnSignature": "F8B2F2AB960191991FC48120A48A089B479018A6469466E43E6F974E1345B32688D59D381E6BC18B6CA383235B708FE4FB44527C51E5B29BCDCC4A08C340A00A",
"Account": "rn6CD8i3Yc3UGagSosZfegG7hpXxwgVAgz",
"Counterparty": "rN2PMxegkEMZHin78o7wSs1JeYjxAvAfvt",
"LoanOriginationFee": "100",
"LoanServiceFee": "10",
"PrincipalRequested": "1000"
}
=== Adding borrower signature ===
Borrower TxnSignature: 52E16B88F5640F637A05E59AB2BE0DBFE4FBE7F1D7580C2A39D4981F6066A7C42047A401B953CDAB4993954A85D73DE35F69317EE8279D23ECB4958AA10C0800
Borrower SigningPubKey: EDE624A07899AEF826DF2A3E2A325F69BC1F169D23F08091E9042644D6B06D3D62
Fully signed LoanSet transaction:
{
"TransactionType": "LoanSet",
"Flags": 0,
"Sequence": 3670743,
"LastLedgerSequence": 3673248,
"PaymentInterval": 2592000,
"GracePeriod": 604800,
"PaymentTotal": 12,
"InterestRate": 500,
"LoanBrokerID": "3CDEA7CEB9F2ECDD76CD41A864F4E3B5DB9C91AEDBD0906EE466FDD21CCF49B5",
"Fee": "2",
"SigningPubKey": "EDDABC72936FF734FA56D6C60C064D48C5DA9911C8B7C26C4AEAC06534B5D7C530",
"TxnSignature": "F8B2F2AB960191991FC48120A48A089B479018A6469466E43E6F974E1345B32688D59D381E6BC18B6CA383235B708FE4FB44527C51E5B29BCDCC4A08C340A00A",
"Account": "rn6CD8i3Yc3UGagSosZfegG7hpXxwgVAgz",
"Counterparty": "rN2PMxegkEMZHin78o7wSs1JeYjxAvAfvt",
"LoanOriginationFee": "100",
"LoanServiceFee": "10",
"PrincipalRequested": "1000",
"CounterpartySignature": {
"SigningPubKey": "EDE624A07899AEF826DF2A3E2A325F69BC1F169D23F08091E9042644D6B06D3D62",
"TxnSignature": "52E16B88F5640F637A05E59AB2BE0DBFE4FBE7F1D7580C2A39D4981F6066A7C42047A401B953CDAB4993954A85D73DE35F69317EE8279D23ECB4958AA10C0800"
}
}
=== Submitting signed LoanSet transaction ===
Loan created successfully!
=== Loan Information ===
{
"Borrower": "rN2PMxegkEMZHin78o7wSs1JeYjxAvAfvt",
"GracePeriod": 604800,
"InterestRate": 500,
"LoanBrokerID": "3CDEA7CEB9F2ECDD76CD41A864F4E3B5DB9C91AEDBD0906EE466FDD21CCF49B5",
"LoanOriginationFee": "100",
"LoanSequence": 6,
"LoanServiceFee": "10",
"NextPaymentDueDate": 826862960,
"PaymentInterval": 2592000,
"PaymentRemaining": 12,
"PeriodicPayment": "83.55610375293148956",
"PrincipalOutstanding": "1000",
"StartDate": 824270960,
"TotalValueOutstanding": "1003"
}
```
---
## Manage a Loan
```sh
node loanManage.js
```
The script should output the initial status of the loan, the LoanManage transaction, and the updated loan status and grace period after impairment. The script will countdown the grace period before outputting another LoanManage transaction, and then the final flags on the loan.
```sh
Loan broker address: rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY
LoanID: D28764B238CF3F7D7BF4AFD07394838EDD5F278B838F97A55BEAEC1E5152719C
=== Loan Status ===
Total Amount Owed: 1001 TSTUSD.
Payment Due Date: 2/25/2026, 11:58:20 PM
=== Preparing LoanManage transaction to impair loan ===
{
"TransactionType": "LoanManage",
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY",
"LoanID": "D28764B238CF3F7D7BF4AFD07394838EDD5F278B838F97A55BEAEC1E5152719C",
"Flags": 131072
}
=== Submitting LoanManage impairment transaction ===
Loan impaired successfully!
New Payment Due Date: 1/27/2026, 12:05:02 AM
Grace Period: 60 seconds
=== Countdown until loan can be defaulted ===
Grace period expired. Loan can now be defaulted.
=== Preparing LoanManage transaction to default loan ===
{
"TransactionType": "LoanManage",
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY",
"LoanID": "D28764B238CF3F7D7BF4AFD07394838EDD5F278B838F97A55BEAEC1E5152719C",
"Flags": 65536
}
=== Submitting LoanManage default transaction ===
Loan defaulted successfully!
=== Checking final loan status ===
Final loan flags (parsed): {"tfLoanDefault":true,"tfLoanImpair":true}
```
## Pay a Loan
```sh
node loanPay.js
```
The script should output the amount required to totally pay off a loan, the LoanPay transaction, the amount due after the payment, the LoanDelete transaction, and then the status of the loan ledger entry:
```sh
Borrower address: r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA
LoanID: 8AC2B4425E604E7BB1082DD2BF2CA902B5087143B7775BE0A4DA954D3F52D06E
MPT ID: 0031034FF84EB2E8348A34F0A8889A54F45F180E80F12341
=== Loan Status ===
Amount Owed: 1001 TSTUSD
Loan Service Fee: 10 TSTUSD
Total Payment Due (including fees): 1011 TSTUSD
=== Preparing LoanPay transaction ===
{
"TransactionType": "LoanPay",
"Account": "r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA",
"LoanID": "8AC2B4425E604E7BB1082DD2BF2CA902B5087143B7775BE0A4DA954D3F52D06E",
"Amount": {
"mpt_issuance_id": "0031034FF84EB2E8348A34F0A8889A54F45F180E80F12341",
"value": "1011"
}
}
=== Submitting LoanPay transaction ===
Loan paid successfully!
=== Loan Status After Payment ===
Outstanding Loan Balance: Loan fully paid off!
=== Preparing LoanDelete transaction ===
{
"TransactionType": "LoanDelete",
"Account": "r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA",
"LoanID": "8AC2B4425E604E7BB1082DD2BF2CA902B5087143B7775BE0A4DA954D3F52D06E"
}
=== Submitting LoanDelete transaction ===
Loan deleted successfully!
=== Verifying Loan Deletion ===
Loan has been successfully removed from the XRP Ledger!
```

View File

@@ -1,143 +0,0 @@
// IMPORTANT: This example deposits and claws back first-loss capital from a
// preconfigured LoanBroker entry. The first-loss capital is an MPT
// with clawback enabled.
import fs from 'fs'
import { execSync } from 'child_process'
import xrpl from 'xrpl'
// Connect to the network ----------------------
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
await client.connect()
// This step checks for the necessary setup data to run the lending protocol tutorials.
// If missing, lendingSetup.js will generate the data.
if (!fs.existsSync('lendingSetup.json')) {
console.log(`\n=== Lending tutorial data doesn't exist. Running setup script... ===\n`)
execSync('node lendingSetup.js', { stdio: 'inherit' })
}
// Load preconfigured accounts and LoanBrokerID.
const setupData = JSON.parse(fs.readFileSync('lendingSetup.json', 'utf8'))
// You can replace these values with your own
const loanBroker = xrpl.Wallet.fromSeed(setupData.loanBroker.seed)
const mptIssuer = xrpl.Wallet.fromSeed(setupData.depositor.seed)
const loanBrokerID = setupData.loanBrokerID
const mptID = setupData.mptID
console.log(`\nLoan broker address: ${loanBroker.address}`)
console.log(`MPT issuer address: ${mptIssuer.address}`)
console.log(`LoanBrokerID: ${loanBrokerID}`)
console.log(`MPT ID: ${mptID}`)
// Check cover available ----------------------
console.log(`\n=== Cover Available ===\n`)
const coverInfo = await client.request({
command: 'ledger_entry',
index: loanBrokerID,
ledger_index: 'validated'
})
let currentCoverAvailable = coverInfo.result.node.CoverAvailable || '0'
console.log(`${currentCoverAvailable} TSTUSD`)
// Prepare LoanBrokerCoverDeposit transaction ----------------------
console.log(`\n=== Preparing LoanBrokerCoverDeposit transaction ===\n`)
const coverDepositTx = {
TransactionType: 'LoanBrokerCoverDeposit',
Account: loanBroker.address,
LoanBrokerID: loanBrokerID,
Amount: {
mpt_issuance_id: mptID,
value: '1000'
}
}
// Validate the transaction structure before submitting
xrpl.validate(coverDepositTx)
console.log(JSON.stringify(coverDepositTx, null, 2))
// Sign, submit, and wait for deposit validation ----------------------
console.log(`\n=== Submitting LoanBrokerCoverDeposit transaction ===\n`)
const depositResponse = await client.submitAndWait(coverDepositTx, {
wallet: loanBroker,
autofill: true
})
if (depositResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
const resultCode = depositResponse.result.meta.TransactionResult
console.error('Error: Unable to deposit cover:', resultCode)
await client.disconnect()
process.exit(1)
}
console.log('Cover deposit successful!')
// Extract updated cover available after deposit ----------------------
console.log(`\n=== Cover Available After Deposit ===\n`)
let loanBrokerNode = depositResponse.result.meta.AffectedNodes.find(node =>
node.ModifiedNode?.LedgerEntryType === 'LoanBroker'
)
currentCoverAvailable = loanBrokerNode.ModifiedNode.FinalFields.CoverAvailable
console.log(`${currentCoverAvailable} TSTUSD`)
// Verify issuer of cover asset matches ----------------------
// Only the issuer of the asset can submit clawback transactions.
// The asset must also have clawback enabled.
console.log(`\n=== Verifying Asset Issuer ===\n`)
const assetIssuerInfo = await client.request({
command: 'ledger_entry',
mpt_issuance: mptID,
ledger_index: 'validated'
})
if (assetIssuerInfo.result.node.Issuer !== mptIssuer.address) {
console.error(`Error: ${assetIssuerInfo.result.node.Issuer} does not match account (${mptIssuer.address}) attempting clawback!`)
await client.disconnect()
process.exit(1)
}
console.log(`MPT issuer account verified: ${mptIssuer.address}. Proceeding to clawback.`)
// Prepare LoanBrokerCoverClawback transaction ----------------------
console.log(`\n=== Preparing LoanBrokerCoverClawback transaction ===\n`)
const coverClawbackTx = {
TransactionType: 'LoanBrokerCoverClawback',
Account: mptIssuer.address,
LoanBrokerID: loanBrokerID,
Amount: {
mpt_issuance_id: mptID,
value: currentCoverAvailable
}
}
// Validate the transaction structure before submitting
xrpl.validate(coverClawbackTx)
console.log(JSON.stringify(coverClawbackTx, null, 2))
// Sign, submit, and wait for clawback validation ----------------------
console.log(`\n=== Submitting LoanBrokerCoverClawback transaction ===\n`)
const clawbackResponse = await client.submitAndWait(coverClawbackTx, {
wallet: mptIssuer,
autofill: true
})
if (clawbackResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
const resultCode = clawbackResponse.result.meta.TransactionResult
console.error('Error: Unable to clawback cover:', resultCode)
await client.disconnect()
process.exit(1)
}
console.log(`Successfully clawed back ${currentCoverAvailable} TSTUSD!`)
// Extract final cover available ----------------------
console.log(`\n=== Final Cover Available After Clawback ===\n`)
loanBrokerNode = clawbackResponse.result.meta.AffectedNodes.find(node =>
node.ModifiedNode?.LedgerEntryType === 'LoanBroker'
)
console.log(`${loanBrokerNode.ModifiedNode.FinalFields.CoverAvailable || '0'} TSTUSD`)
await client.disconnect()

View File

@@ -1,108 +0,0 @@
// IMPORTANT: This example deposits and withdraws first-loss capital from a
// preconfigured LoanBroker entry.
import fs from 'fs'
import { execSync } from 'child_process'
import xrpl from 'xrpl'
// Connect to the network ----------------------
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
await client.connect()
// This step checks for the necessary setup data to run the lending protocol tutorials.
// If missing, lendingSetup.js will generate the data.
if (!fs.existsSync('lendingSetup.json')) {
console.log(`\n=== Lending tutorial data doesn't exist. Running setup script... ===\n`)
execSync('node lendingSetup.js', { stdio: 'inherit' })
}
// Load preconfigured accounts and LoanBrokerID.
const setupData = JSON.parse(fs.readFileSync('lendingSetup.json', 'utf8'))
// You can replace these values with your own
const loanBroker = xrpl.Wallet.fromSeed(setupData.loanBroker.seed)
const loanBrokerID = setupData.loanBrokerID
const mptID = setupData.mptID
console.log(`\nLoan broker address: ${loanBroker.address}`)
console.log(`LoanBrokerID: ${loanBrokerID}`)
console.log(`MPT ID: ${mptID}`)
// Prepare LoanBrokerCoverDeposit transaction ----------------------
console.log(`\n=== Preparing LoanBrokerCoverDeposit transaction ===\n`)
const coverDepositTx = {
TransactionType: 'LoanBrokerCoverDeposit',
Account: loanBroker.address,
LoanBrokerID: loanBrokerID,
Amount: {
mpt_issuance_id: mptID,
value: '2000'
}
}
// Validate the transaction structure before submitting
xrpl.validate(coverDepositTx)
console.log(JSON.stringify(coverDepositTx, null, 2))
// Sign, submit, and wait for deposit validation ----------------------
console.log(`\n=== Submitting LoanBrokerCoverDeposit transaction ===\n`)
const depositResponse = await client.submitAndWait(coverDepositTx, {
wallet: loanBroker,
autofill: true
})
if (depositResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
const resultCode = depositResponse.result.meta.TransactionResult
console.error('Error: Unable to deposit cover:', resultCode)
await client.disconnect()
process.exit(1)
}
console.log('Cover deposit successful!')
// Extract cover balance from the transaction result
console.log(`\n=== Cover Balance ===\n`)
let loanBrokerNode = depositResponse.result.meta.AffectedNodes.find(node =>
node.ModifiedNode?.LedgerEntryType === 'LoanBroker'
)
// First-loss capital is stored in the LoanBroker's pseudo-account.
console.log(`LoanBroker Pseudo-Account: ${loanBrokerNode.ModifiedNode.FinalFields.Account}`)
console.log(`Cover balance after deposit: ${loanBrokerNode.ModifiedNode.FinalFields.CoverAvailable} TSTUSD`)
// Prepare LoanBrokerCoverWithdraw transaction ----------------------
console.log(`\n=== Preparing LoanBrokerCoverWithdraw transaction ===\n`)
const coverWithdrawTx = {
TransactionType: 'LoanBrokerCoverWithdraw',
Account: loanBroker.address,
LoanBrokerID: loanBrokerID,
Amount: {
mpt_issuance_id: mptID,
value: '1000'
}
}
// Validate the transaction structure before submitting
xrpl.validate(coverWithdrawTx)
console.log(JSON.stringify(coverWithdrawTx, null, 2))
// Sign, submit, and wait for withdraw validation ----------------------
console.log(`\n=== Submitting LoanBrokerCoverWithdraw transaction ===\n`)
const withdrawResponse = await client.submitAndWait(coverWithdrawTx, {
wallet: loanBroker,
autofill: true
})
if (withdrawResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
const resultCode = withdrawResponse.result.meta.TransactionResult
console.error('Error: Unable to withdraw cover:', resultCode)
await client.disconnect()
process.exit(1)
}
console.log('Cover withdraw successful!')
// Extract updated cover balance from the transaction result
console.log(`\n=== Updated Cover Balance ===\n`)
loanBrokerNode = withdrawResponse.result.meta.AffectedNodes.find(node =>
node.ModifiedNode?.LedgerEntryType === 'LoanBroker'
)
console.log(`LoanBroker Pseudo-Account: ${loanBrokerNode.ModifiedNode.FinalFields.Account}`)
console.log(`Cover balance after withdraw: ${loanBrokerNode.ModifiedNode.FinalFields.CoverAvailable} TSTUSD`)
await client.disconnect()

View File

@@ -1,95 +0,0 @@
// IMPORTANT: This example creates a loan using a preconfigured
// loan broker, borrower, and private vault.
import fs from 'fs'
import { execSync } from 'child_process'
import xrpl from 'xrpl'
// Connect to the network ----------------------
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
await client.connect()
// This step checks for the necessary setup data to run the lending protocol tutorials.
// If missing, lendingSetup.js will generate the data.
if (!fs.existsSync('lendingSetup.json')) {
console.log(`\n=== Lending tutorial data doesn't exist. Running setup script... ===\n`)
execSync('node lendingSetup.js', { stdio: 'inherit' })
}
// Load preconfigured accounts and LoanBrokerID.
const setupData = JSON.parse(fs.readFileSync('lendingSetup.json', 'utf8'))
// You can replace these values with your own
const loanBroker = xrpl.Wallet.fromSeed(setupData.loanBroker.seed)
const borrower = xrpl.Wallet.fromSeed(setupData.borrower.seed)
const loanBrokerID = setupData.loanBrokerID
console.log(`\nLoan broker address: ${loanBroker.address}`)
console.log(`Borrower address: ${borrower.address}`)
console.log(`LoanBrokerID: ${loanBrokerID}`)
// Prepare LoanSet transaction ----------------------
// Account and Counterparty accounts can be swapped, but determines signing order.
// Account signs first, Counterparty signs second.
console.log(`\n=== Preparing LoanSet transaction ===\n`)
// Suppress unnecessary console warning from autofilling LoanSet.
console.warn = () => {}
const loanSetTx = await client.autofill({
TransactionType: 'LoanSet',
Account: loanBroker.address,
Counterparty: borrower.address,
LoanBrokerID: loanBrokerID,
PrincipalRequested: '1000',
InterestRate: 500,
PaymentTotal: 12,
PaymentInterval: 2592000,
GracePeriod: 604800,
LoanOriginationFee: '100',
LoanServiceFee: '10'
})
console.log(JSON.stringify(loanSetTx, null, 2))
// Loan broker signs first
console.log(`\n=== Adding loan broker signature ===\n`)
const loanBrokerSigned = loanBroker.sign(loanSetTx)
const loanBrokerSignedTx = xrpl.decode(loanBrokerSigned.tx_blob)
console.log(`TxnSignature: ${loanBrokerSignedTx.TxnSignature}`)
console.log(`SigningPubKey: ${loanBrokerSignedTx.SigningPubKey}\n`)
console.log(`Signed loanSetTx for borrower to sign over:\n${JSON.stringify(loanBrokerSignedTx, null, 2)}`)
// Borrower signs second
console.log(`\n=== Adding borrower signature ===\n`)
const fullySigned = xrpl.signLoanSetByCounterparty(borrower, loanBrokerSignedTx)
console.log(`Borrower TxnSignature: ${fullySigned.tx.CounterpartySignature.TxnSignature}`)
console.log(`Borrower SigningPubKey: ${fullySigned.tx.CounterpartySignature.SigningPubKey}`)
// Validate the transaction structure before submitting.
xrpl.validate(fullySigned.tx)
console.log(`\nFully signed LoanSet transaction:\n${JSON.stringify(fullySigned.tx, null, 2)}`)
// Submit and wait for validation ----------------------
console.log(`\n=== Submitting signed LoanSet transaction ===\n`)
const submitResponse = await client.submitAndWait(fullySigned.tx)
if (submitResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
const resultCode = submitResponse.result.meta.TransactionResult
console.error('Error: Unable to create loan:', resultCode)
await client.disconnect()
process.exit(1)
}
console.log('Loan created successfully!')
// Extract loan information from the transaction result.
console.log(`\n=== Loan Information ===\n`)
const loanNode = submitResponse.result.meta.AffectedNodes.find(node =>
node.CreatedNode?.LedgerEntryType === 'Loan'
)
console.log(JSON.stringify(loanNode.CreatedNode.NewFields, null, 2))
await client.disconnect()

View File

@@ -1,66 +0,0 @@
// IMPORTANT: This example creates a loan broker using an existing account
// that has already created a PRIVATE vault.
// If you want to create a loan broker for a PUBLIC vault, you can replace the vaultID
// and loanBroker values with your own.
import fs from 'fs'
import { execSync } from 'child_process'
import xrpl from 'xrpl'
// Connect to the network ----------------------
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
await client.connect()
// This step checks for the necessary setup data to run the lending protocol tutorials.
// If missing, lendingSetup.js will generate the data.
if (!fs.existsSync('lendingSetup.json')) {
console.log(`\n=== Lending tutorial data doesn't exist. Running setup script... ===\n`)
execSync('node lendingSetup.js', { stdio: 'inherit' })
}
// Load preconfigured accounts and VaultID.
const setupData = JSON.parse(fs.readFileSync('lendingSetup.json', 'utf8'))
// You can replace these values with your own
const loanBroker = xrpl.Wallet.fromSeed(setupData.loanBroker.seed)
const vaultID = setupData.vaultID
console.log(`\nLoan broker/vault owner address: ${loanBroker.address}`)
console.log(`Vault ID: ${vaultID}`)
// Prepare LoanBrokerSet transaction ----------------------
console.log(`\n=== Preparing LoanBrokerSet transaction ===\n`)
const loanBrokerSetTx = {
TransactionType: 'LoanBrokerSet',
Account: loanBroker.address,
VaultID: vaultID,
ManagementFeeRate: 1000
}
// Validate the transaction structure before submitting
xrpl.validate(loanBrokerSetTx)
console.log(JSON.stringify(loanBrokerSetTx, null, 2))
// Submit, sign, and wait for validation ----------------------
console.log(`\n=== Submitting LoanBrokerSet transaction ===\n`)
const submitResponse = await client.submitAndWait(loanBrokerSetTx, {
wallet: loanBroker,
autofill: true
})
if (submitResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
const resultCode = submitResponse.result.meta.TransactionResult
console.error('Error: Unable to create loan broker:', resultCode)
await client.disconnect()
process.exit(1)
}
console.log('Loan broker created successfully!')
// Extract loan broker information from the transaction result
console.log(`\n=== Loan Broker Information ===\n`)
const loanBrokerNode = submitResponse.result.meta.AffectedNodes.find(node =>
node.CreatedNode?.LedgerEntryType === 'LoanBroker'
)
console.log(`LoanBroker ID: ${loanBrokerNode.CreatedNode.LedgerIndex}`)
console.log(`LoanBroker Psuedo-Account Address: ${loanBrokerNode.CreatedNode.NewFields.Account}`)
await client.disconnect()

View File

@@ -1,337 +0,0 @@
// Setup script for lending protocol tutorials
import xrpl from 'xrpl'
import fs from 'fs'
process.stdout.write('Setting up tutorial: 0/6\r')
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
await client.connect()
// Create and fund wallets
const [
{ wallet: loanBroker },
{ wallet: borrower },
{ wallet: depositor },
{ wallet: credentialIssuer }
] = await Promise.all([
client.fundWallet(),
client.fundWallet(),
client.fundWallet(),
client.fundWallet()
])
process.stdout.write('Setting up tutorial: 1/6\r')
// Issue MPT with depositor
// Create tickets for later use with loanBroker
// Set up credentials and domain with credentialIssuer
const credentialType = xrpl.convertStringToHex('KYC-Verified')
const mptData = {
ticker: 'TSTUSD',
name: 'Test USD MPT',
desc: 'A sample non-yield-bearing stablecoin backed by U.S. Treasuries.',
icon: 'https://example.org/tstusd-icon.png',
asset_class: 'rwa',
asset_subclass: 'stablecoin',
issuer_name: 'Example Treasury Reserve Co.',
uris: [
{
uri: 'https://exampletreasury.com/tstusd',
category: 'website',
title: 'Product Page'
},
{
uri: 'https://exampletreasury.com/tstusd/reserve',
category: 'docs',
title: 'Reserve Attestation'
}
],
additional_info: {
reserve_type: 'U.S. Treasury Bills',
custody_provider: 'Example Custodian Bank',
audit_frequency: 'Monthly',
last_audit_date: '2026-01-15',
pegged_currency: 'USD'
}
}
const [ticketCreateResponse, mptIssuanceResponse] = await Promise.all([
client.submitAndWait({
TransactionType: 'TicketCreate',
Account: loanBroker.address,
TicketCount: 2
}, { wallet: loanBroker, autofill: true }),
client.submitAndWait({
TransactionType: 'MPTokenIssuanceCreate',
Account: depositor.address,
MaximumAmount: '100000000',
TransferFee: 0,
Flags:
xrpl.MPTokenIssuanceCreateFlags.tfMPTCanTransfer |
xrpl.MPTokenIssuanceCreateFlags.tfMPTCanClawback |
xrpl.MPTokenIssuanceCreateFlags.tfMPTCanTrade,
MPTokenMetadata: xrpl.encodeMPTokenMetadata(mptData)
}, { wallet: depositor, autofill: true }),
client.submitAndWait({
TransactionType: 'Batch',
Account: credentialIssuer.address,
Flags: xrpl.BatchFlags.tfAllOrNothing,
RawTransactions: [
{
RawTransaction: {
TransactionType: 'CredentialCreate',
Account: credentialIssuer.address,
Subject: loanBroker.address,
CredentialType: credentialType,
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
},
{
RawTransaction: {
TransactionType: 'CredentialCreate',
Account: credentialIssuer.address,
Subject: borrower.address,
CredentialType: credentialType,
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
},
{
RawTransaction: {
TransactionType: 'CredentialCreate',
Account: credentialIssuer.address,
Subject: depositor.address,
CredentialType: credentialType,
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
},
{
RawTransaction: {
TransactionType: 'PermissionedDomainSet',
Account: credentialIssuer.address,
AcceptedCredentials: [
{
Credential: {
Issuer: credentialIssuer.address,
CredentialType: credentialType
}
}
],
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
}
]
}, { wallet: credentialIssuer, autofill: true })
])
// Extract ticket sequence numbers
const tickets = ticketCreateResponse.result.meta.AffectedNodes
.filter(node => node.CreatedNode?.LedgerEntryType === 'Ticket')
.map(node => node.CreatedNode.NewFields.TicketSequence)
// Extract MPT issuance ID
const mptID = mptIssuanceResponse.result.meta.mpt_issuance_id
// Get domain ID
const credentialIssuerObjects = await client.request({
command: 'account_objects',
account: credentialIssuer.address,
ledger_index: 'validated'
})
const domainID = credentialIssuerObjects.result.account_objects.find(node =>
node.LedgerEntryType === 'PermissionedDomain'
).index
process.stdout.write('Setting up tutorial: 2/6\r')
// Accept credentials and authorize MPT for each account
await Promise.all([
...([loanBroker, borrower].map(wallet =>
client.submitAndWait({
TransactionType: 'Batch',
Account: wallet.address,
Flags: xrpl.BatchFlags.tfAllOrNothing,
RawTransactions: [
{
RawTransaction: {
TransactionType: 'CredentialAccept',
Account: wallet.address,
Issuer: credentialIssuer.address,
CredentialType: credentialType,
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
},
{
RawTransaction: {
TransactionType: 'MPTokenAuthorize',
Account: wallet.address,
MPTokenIssuanceID: mptID,
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
}
]
}, { wallet, autofill: true })
)),
// Depositor only needs to accept credentials
client.submitAndWait({
TransactionType: 'CredentialAccept',
Account: depositor.address,
Issuer: credentialIssuer.address,
CredentialType: credentialType
}, { wallet: depositor, autofill: true })
])
process.stdout.write('Setting up tutorial: 3/6\r')
// Create private vault and distribute MPT to accounts
const [vaultCreateResponse] = await Promise.all([
client.submitAndWait({
TransactionType: 'VaultCreate',
Account: loanBroker.address,
Asset: {
mpt_issuance_id: mptID
},
Flags: xrpl.VaultCreateFlags.tfVaultPrivate,
DomainID: domainID
}, { wallet: loanBroker, autofill: true }),
client.submitAndWait({
TransactionType: 'Batch',
Account: depositor.address,
Flags: xrpl.BatchFlags.tfAllOrNothing,
RawTransactions: [
{
RawTransaction: {
TransactionType: 'Payment',
Account: depositor.address,
Destination: loanBroker.address,
Amount: {
mpt_issuance_id: mptID,
value: '5000'
},
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
},
{
RawTransaction: {
TransactionType: 'Payment',
Account: depositor.address,
Destination: borrower.address,
Amount: {
mpt_issuance_id: mptID,
value: '2500'
},
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
}
]
}, { wallet: depositor, autofill: true })
])
const vaultID = vaultCreateResponse.result.meta.AffectedNodes.find(node =>
node.CreatedNode?.LedgerEntryType === 'Vault'
).CreatedNode.LedgerIndex
process.stdout.write('Setting up tutorial: 4/6\r')
// Create LoanBroker and deposit MPT into vault
const [loanBrokerSetResponse] = await Promise.all([
client.submitAndWait({
TransactionType: 'LoanBrokerSet',
Account: loanBroker.address,
VaultID: vaultID
}, { wallet: loanBroker, autofill: true }),
client.submitAndWait({
TransactionType: 'VaultDeposit',
Account: depositor.address,
VaultID: vaultID,
Amount: {
mpt_issuance_id: mptID,
value: '50000000'
}
}, { wallet: depositor, autofill: true })
])
const loanBrokerID = loanBrokerSetResponse.result.meta.AffectedNodes.find(node =>
node.CreatedNode?.LedgerEntryType === 'LoanBroker'
).CreatedNode.LedgerIndex
process.stdout.write('Setting up tutorial: 5/6\r')
// Create 2 identical loans with complete repayment due in 30 days
// Suppress unnecessary console warning from autofilling LoanSet.
console.warn = () => {}
// Helper function to create, sign, and submit a LoanSet transaction
async function createLoan (ticketSequence) {
const loanSetTx = await client.autofill({
TransactionType: 'LoanSet',
Account: loanBroker.address,
Counterparty: borrower.address,
LoanBrokerID: loanBrokerID,
PrincipalRequested: '1000',
InterestRate: 500,
PaymentTotal: 1,
PaymentInterval: 2592000,
LoanOriginationFee: '100',
LoanServiceFee: '10',
Sequence: 0,
TicketSequence: ticketSequence
})
const loanBrokerSigned = loanBroker.sign(loanSetTx)
const loanBrokerSignedTx = xrpl.decode(loanBrokerSigned.tx_blob)
const fullySigned = xrpl.signLoanSetByCounterparty(borrower, loanBrokerSignedTx)
const submitResponse = await client.submitAndWait(fullySigned.tx)
return submitResponse
}
const [submitResponse1, submitResponse2] = await Promise.all([
createLoan(tickets[0]),
createLoan(tickets[1])
])
const loanID1 = submitResponse1.result.meta.AffectedNodes.find(node =>
node.CreatedNode?.LedgerEntryType === 'Loan'
).CreatedNode.LedgerIndex
const loanID2 = submitResponse2.result.meta.AffectedNodes.find(node =>
node.CreatedNode?.LedgerEntryType === 'Loan'
).CreatedNode.LedgerIndex
process.stdout.write('Setting up tutorial: 6/6\r')
// Write setup data to JSON file
const setupData = {
description: 'This file is auto-generated by lendingSetup.js. It stores XRPL account info for use in lending protocol tutorials.',
loanBroker: {
address: loanBroker.address,
seed: loanBroker.seed
},
borrower: {
address: borrower.address,
seed: borrower.seed
},
depositor: {
address: depositor.address,
seed: depositor.seed
},
credentialIssuer: {
address: credentialIssuer.address,
seed: credentialIssuer.seed
},
domainID,
mptID,
vaultID,
loanBrokerID,
loanID1,
loanID2
}
fs.writeFileSync('lendingSetup.json', JSON.stringify(setupData, null, 2))
process.stdout.write('Setting up tutorial: Complete!\n')
await client.disconnect()

View File

@@ -1,144 +0,0 @@
// IMPORTANT: This example impairs an existing loan, which has a 60 second grace period.
// After the 60 seconds pass, this example defaults the loan.
import fs from 'fs'
import { execSync } from 'child_process'
import xrpl from 'xrpl'
// Connect to the network ----------------------
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
await client.connect()
// This step checks for the necessary setup data to run the lending protocol tutorials.
// If missing, lendingSetup.js will generate the data.
if (!fs.existsSync('lendingSetup.json')) {
console.log(`\n=== Lending tutorial data doesn't exist. Running setup script... ===\n`)
execSync('node lendingSetup.js', { stdio: 'inherit' })
}
// Load preconfigured accounts and LoanID.
const setupData = JSON.parse(fs.readFileSync('lendingSetup.json', 'utf8'))
// You can replace these values with your own
const loanBroker = xrpl.Wallet.fromSeed(setupData.loanBroker.seed)
const loanID = setupData.loanID1
console.log(`\nLoan broker address: ${loanBroker.address}`)
console.log(`LoanID: ${loanID}`)
// Check loan status before impairment ----------------------
console.log(`\n=== Loan Status ===\n`)
const loanStatus = await client.request({
command: 'ledger_entry',
index: loanID,
ledger_index: 'validated'
})
console.log(`Total Amount Owed: ${loanStatus.result.node.TotalValueOutstanding} TSTUSD.`)
// Convert Ripple Epoch timestamp to local date and time
let nextPaymentDueDate = loanStatus.result.node.NextPaymentDueDate
let paymentDue = new Date((nextPaymentDueDate + 946684800) * 1000)
console.log(`Payment Due Date: ${paymentDue.toLocaleString()}`)
// Prepare LoanManage transaction to impair the loan ----------------------
console.log(`\n=== Preparing LoanManage transaction to impair loan ===\n`)
const loanManageImpair = {
TransactionType: 'LoanManage',
Account: loanBroker.address,
LoanID: loanID,
Flags: xrpl.LoanManageFlags.tfLoanImpair
}
// Validate the impairment transaction before submitting
xrpl.validate(loanManageImpair)
console.log(JSON.stringify(loanManageImpair, null, 2))
// Sign, submit, and wait for impairment validation ----------------------
console.log(`\n=== Submitting LoanManage impairment transaction ===\n`)
const impairResponse = await client.submitAndWait(loanManageImpair, {
wallet: loanBroker,
autofill: true
})
if (impairResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
const resultCode = impairResponse.result.meta.TransactionResult
console.error('Error: Unable to impair loan:', resultCode)
await client.disconnect()
process.exit(1)
}
console.log('Loan impaired successfully!')
// Extract loan impairment info from transaction results ----------------------
let loanNode = impairResponse.result.meta.AffectedNodes.find(node =>
node.ModifiedNode?.LedgerEntryType === 'Loan'
)
// Check grace period and next payment due date
const gracePeriod = loanNode.ModifiedNode.FinalFields.GracePeriod
nextPaymentDueDate = loanNode.ModifiedNode.FinalFields.NextPaymentDueDate
const defaultTime = nextPaymentDueDate + gracePeriod
paymentDue = new Date((nextPaymentDueDate + 946684800) * 1000)
console.log(`New Payment Due Date: ${paymentDue.toLocaleString()}`)
console.log(`Grace Period: ${gracePeriod} seconds`)
// Convert current time to Ripple Epoch timestamp
const currentTime = Math.floor(Date.now() / 1000) - 946684800
let secondsUntilDefault = defaultTime - currentTime
// Countdown until loan can be defaulted ----------------------
console.log(`\n=== Countdown until loan can be defaulted ===\n`)
await new Promise((resolve) => {
const countdown = setInterval(() => {
if (secondsUntilDefault <= 0) {
clearInterval(countdown)
process.stdout.write('\rGrace period expired. Loan can now be defaulted.\n')
resolve()
} else {
process.stdout.write(`\r${secondsUntilDefault} seconds...`)
secondsUntilDefault--
}
}, 1000)
})
// Prepare LoanManage transaction to default the loan ----------------------
console.log(`\n=== Preparing LoanManage transaction to default loan ===\n`)
const loanManageDefault = {
TransactionType: 'LoanManage',
Account: loanBroker.address,
LoanID: loanID,
Flags: xrpl.LoanManageFlags.tfLoanDefault
}
// Validate the default transaction before submitting
xrpl.validate(loanManageDefault)
console.log(JSON.stringify(loanManageDefault, null, 2))
// Sign, submit, and wait for default validation ----------------------
console.log(`\n=== Submitting LoanManage default transaction ===\n`)
const defaultResponse = await client.submitAndWait(loanManageDefault, {
wallet: loanBroker,
autofill: true
})
if (defaultResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
const resultCode = defaultResponse.result.meta.TransactionResult
console.error('Error: Unable to default loan:', resultCode)
await client.disconnect()
process.exit(1)
}
console.log('Loan defaulted successfully!')
// Verify loan default status from transaction results ----------------------
console.log(`\n=== Checking final loan status ===\n`)
loanNode = defaultResponse.result.meta.AffectedNodes.find(node =>
node.ModifiedNode?.LedgerEntryType === 'Loan'
)
const loanFlags = loanNode.ModifiedNode.FinalFields.Flags
console.log(`Final loan flags (parsed): ${JSON.stringify(xrpl.parseTransactionFlags({
TransactionType: 'LoanManage',
Flags: loanFlags
}))}`)
await client.disconnect()

View File

@@ -1,134 +0,0 @@
// IMPORTANT: This example pays off an existing loan and then deletes it.
import fs from 'fs'
import { execSync } from 'child_process'
import xrpl from 'xrpl'
// Connect to the network ----------------------
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
await client.connect()
// This step checks for the necessary setup data to run the lending protocol tutorials.
// If missing, lendingSetup.js will generate the data.
if (!fs.existsSync('lendingSetup.json')) {
console.log(`\n=== Lending tutorial data doesn't exist. Running setup script... ===\n`)
execSync('node lendingSetup.js', { stdio: 'inherit' })
}
// Load preconfigured accounts and LoanID.
const setupData = JSON.parse(fs.readFileSync('lendingSetup.json', 'utf8'))
// You can replace these values with your own
const borrower = xrpl.Wallet.fromSeed(setupData.borrower.seed)
const loanID = setupData.loanID2
const mptID = setupData.mptID
console.log(`\nBorrower address: ${borrower.address}`)
console.log(`LoanID: ${loanID}`)
console.log(`MPT ID: ${mptID}`)
// Check initial loan status ----------------------
console.log(`\n=== Loan Status ===\n`)
const loanStatus = await client.request({
command: 'ledger_entry',
index: loanID,
ledger_index: 'validated'
})
const totalValueOutstanding = loanStatus.result.node.TotalValueOutstanding
const loanServiceFee = loanStatus.result.node.LoanServiceFee
const totalPayment = (BigInt(totalValueOutstanding) + BigInt(loanServiceFee)).toString()
console.log(`Amount Owed: ${totalValueOutstanding} TSTUSD`)
console.log(`Loan Service Fee: ${loanServiceFee} TSTUSD`)
console.log(`Total Payment Due (including fees): ${totalPayment} TSTUSD`)
// Prepare LoanPay transaction ----------------------
console.log(`\n=== Preparing LoanPay transaction ===\n`)
const loanPayTx = {
TransactionType: 'LoanPay',
Account: borrower.address,
LoanID: loanID,
Amount: {
mpt_issuance_id: mptID,
value: totalPayment
}
}
// Validate the transaction structure before submitting
xrpl.validate(loanPayTx)
console.log(JSON.stringify(loanPayTx, null, 2))
// Sign, submit, and wait for payment validation ----------------------
console.log(`\n=== Submitting LoanPay transaction ===\n`)
const payResponse = await client.submitAndWait(loanPayTx, {
wallet: borrower,
autofill: true
})
if (payResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
const resultCode = payResponse.result.meta.TransactionResult
console.error('Error: Unable to pay loan:', resultCode)
await client.disconnect()
process.exit(1)
}
console.log('Loan paid successfully!')
// Extract updated loan info from transaction results ----------------------
console.log(`\n=== Loan Status After Payment ===\n`)
const loanNode = payResponse.result.meta.AffectedNodes.find(node =>
node.ModifiedNode?.LedgerEntryType === 'Loan'
)
const finalBalance = loanNode.ModifiedNode.FinalFields.TotalValueOutstanding
? `${loanNode.ModifiedNode.FinalFields.TotalValueOutstanding} TSTUSD`
: 'Loan fully paid off!'
console.log(`Outstanding Loan Balance: ${finalBalance}`)
// Prepare LoanDelete transaction ----------------------
// Either the loan broker or borrower can submit this transaction.
console.log(`\n=== Preparing LoanDelete transaction ===\n`)
const loanDeleteTx = {
TransactionType: 'LoanDelete',
Account: borrower.address,
LoanID: loanID
}
// Validate the transaction structure before submitting
xrpl.validate(loanDeleteTx)
console.log(JSON.stringify(loanDeleteTx, null, 2))
// Sign, submit, and wait for deletion validation ----------------------
console.log(`\n=== Submitting LoanDelete transaction ===\n`)
const deleteResponse = await client.submitAndWait(loanDeleteTx, {
wallet: borrower,
autofill: true
})
if (deleteResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
const resultCode = deleteResponse.result.meta.TransactionResult
console.error('Error: Unable to delete loan:', resultCode)
await client.disconnect()
process.exit(1)
}
console.log('Loan deleted successfully!')
// Verify loan deletion ----------------------
console.log(`\n=== Verifying Loan Deletion ===\n`)
try {
await client.request({
command: 'ledger_entry',
index: loanID,
ledger_index: 'validated'
})
console.log('Warning: Loan still exists in the ledger.')
} catch (error) {
if (error.data.error === 'entryNotFound') {
console.log('Loan has been successfully removed from the XRP Ledger!')
} else {
console.error('Error checking loan status:', error)
}
}
await client.disconnect()

View File

@@ -1,8 +0,0 @@
{
"name": "lending-protocol-examples",
"description": "Example code for creating and managing loans.",
"dependencies": {
"xrpl": "^4.6.0"
},
"type": "module"
}

View File

@@ -1,403 +0,0 @@
# Lending Protocol Examples (Python)
This directory contains Python examples demonstrating how to create a loan broker, claw back first-loss capital, deposit and withdraw first-loss capital, create a loan, manage a loan, and repay a loan.
## Setup
Install dependencies before running any examples:
```sh
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
---
## Create a Loan Broker
```sh
python3 create_loan_broker.py
```
The script should output the LoanBrokerSet transaction, loan broker ID, and loan broker pseudo-account:
```sh
Loan broker/vault owner address: rBeEX3qQzP3UL5WMwZAzdPPpzckH73YvBn
Vault ID: 2B71E8E1323BFC8F2AC27F8C217870B63921EFA0C02DF7BA8B099C7DC6A1D00F
=== Preparing LoanBrokerSet transaction ===
{
"Account": "rBeEX3qQzP3UL5WMwZAzdPPpzckH73YvBn",
"TransactionType": "LoanBrokerSet",
"SigningPubKey": "",
"VaultID": "2B71E8E1323BFC8F2AC27F8C217870B63921EFA0C02DF7BA8B099C7DC6A1D00F",
"ManagementFeeRate": 1000
}
=== Submitting LoanBrokerSet transaction ===
Loan broker created successfully!
=== Loan Broker Information ===
LoanBroker ID: 86911896026EA9DEAEFC1A7959BC05D8B1A1EC25B9960E8C54424B7DC41F8DA8
LoanBroker Psuedo-Account Address: rPhpC2XGz7v5g2rPom7JSWJcic1cnkoBh9
```
---
## Claw Back First-loss Capital
```sh
python3 cover_clawback.py
```
The script should output the cover available, the LoanBrokerCoverDeposit transaction, cover available after the deposit, the LoanBrokerCoverClawback transaction, and the final cover available after the clawback:
```sh
Loan broker address: rBeEX3qQzP3UL5WMwZAzdPPpzckH73YvBn
MPT issuer address: rNzJg2EVwo56eAoBxz5WnTfmgoLbfaAT8d
LoanBrokerID: 041E256F124841FF81DF105C62A72676BFD746975F86786166B689F304BE96E0
MPT ID: 0037A8ED99701AFEC4BCC3A39299252CA41838059572E7F2
=== Cover Available ===
1000 TSTUSD
=== Preparing LoanBrokerCoverDeposit transaction ===
{
"Account": "rBeEX3qQzP3UL5WMwZAzdPPpzckH73YvBn",
"TransactionType": "LoanBrokerCoverDeposit",
"SigningPubKey": "",
"LoanBrokerID": "041E256F124841FF81DF105C62A72676BFD746975F86786166B689F304BE96E0",
"Amount": {
"mpt_issuance_id": "0037A8ED99701AFEC4BCC3A39299252CA41838059572E7F2",
"value": "1000"
}
}
=== Submitting LoanBrokerCoverDeposit transaction ===
Cover deposit successful!
=== Cover Available After Deposit ===
2000 TSTUSD
=== Verifying Asset Issuer ===
MPT issuer account verified: rNzJg2EVwo56eAoBxz5WnTfmgoLbfaAT8d. Proceeding to clawback.
=== Preparing LoanBrokerCoverClawback transaction ===
{
"Account": "rNzJg2EVwo56eAoBxz5WnTfmgoLbfaAT8d",
"TransactionType": "LoanBrokerCoverClawback",
"SigningPubKey": "",
"LoanBrokerID": "041E256F124841FF81DF105C62A72676BFD746975F86786166B689F304BE96E0",
"Amount": {
"mpt_issuance_id": "0037A8ED99701AFEC4BCC3A39299252CA41838059572E7F2",
"value": "2000"
}
}
=== Submitting LoanBrokerCoverClawback transaction ===
Successfully clawed back 2000 TSTUSD!
=== Final Cover Available After Clawback ===
0 TSTUSD
```
---
## Deposit and Withdraw First-loss Capital
```sh
python3 cover_deposit_and_withdraw.py
```
The script should output the LoanBrokerCoverDeposit, cover balance after the deposit, the LoanBrokerCoverWithdraw transaction, and the cover balance after the withdrawal:
```sh
Loan broker address: rBeEX3qQzP3UL5WMwZAzdPPpzckH73YvBn
LoanBrokerID: 041E256F124841FF81DF105C62A72676BFD746975F86786166B689F304BE96E0
MPT ID: 0037A8ED99701AFEC4BCC3A39299252CA41838059572E7F2
=== Preparing LoanBrokerCoverDeposit transaction ===
{
"Account": "rBeEX3qQzP3UL5WMwZAzdPPpzckH73YvBn",
"TransactionType": "LoanBrokerCoverDeposit",
"SigningPubKey": "",
"LoanBrokerID": "041E256F124841FF81DF105C62A72676BFD746975F86786166B689F304BE96E0",
"Amount": {
"mpt_issuance_id": "0037A8ED99701AFEC4BCC3A39299252CA41838059572E7F2",
"value": "2000"
}
}
=== Submitting LoanBrokerCoverDeposit transaction ===
Cover deposit successful!
=== Cover Balance ===
LoanBroker Pseudo-Account: rUrs1bkhQyh1nxE7u99H92U2Tg8Pogw1bZ
Cover balance after deposit: 2000 TSTUSD
=== Preparing LoanBrokerCoverWithdraw transaction ===
{
"Account": "rBeEX3qQzP3UL5WMwZAzdPPpzckH73YvBn",
"TransactionType": "LoanBrokerCoverWithdraw",
"SigningPubKey": "",
"LoanBrokerID": "041E256F124841FF81DF105C62A72676BFD746975F86786166B689F304BE96E0",
"Amount": {
"mpt_issuance_id": "0037A8ED99701AFEC4BCC3A39299252CA41838059572E7F2",
"value": "1000"
}
}
=== Submitting LoanBrokerCoverWithdraw transaction ===
Cover withdraw successful!
=== Updated Cover Balance ===
LoanBroker Pseudo-Account: rUrs1bkhQyh1nxE7u99H92U2Tg8Pogw1bZ
Cover balance after withdraw: 1000 TSTUSD
```
---
## Create a Loan
```sh
python3 create_loan.py
```
The script should output the LoanSet transaction, the updated LoanSet transaction with the loan broker signature, the final LoanSet transaction with the borrower signature added, and then the loan information:
```sh
Loan broker address: ra3aoaincCNBQ7uxvHDgFbtCbVw1VNQkZy
Borrower address: raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL
LoanBrokerID: 61A1D6B0F019C5D5BD039AC3DBE2E31813471567854D07D278564E4E2463ABD2
=== Preparing LoanSet transaction ===
{
"Account": "ra3aoaincCNBQ7uxvHDgFbtCbVw1VNQkZy",
"TransactionType": "LoanSet",
"Fee": "2",
"Sequence": 3652181,
"LastLedgerSequence": 3674792,
"SigningPubKey": "",
"LoanBrokerID": "61A1D6B0F019C5D5BD039AC3DBE2E31813471567854D07D278564E4E2463ABD2",
"Counterparty": "raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL",
"LoanOriginationFee": "100",
"LoanServiceFee": "10",
"InterestRate": 500,
"PrincipalRequested": "1000",
"PaymentTotal": 12,
"PaymentInterval": 2592000,
"GracePeriod": 604800
}
=== Adding loan broker signature ===
TxnSignature: 9756E70F33B359FAEA789D732E752401DE41CAB1A3711517B576DBFF4D89B6A01C234A379391C48B3D88CB031BD679A7EDE4F4BB67AA7297EEE25EA29FF6BD0D
SigningPubKey: ED0DC8C222C4BB86CE07165CD0486C598B8146C3150EE40AF48921983DED98FA47
Signed loan_set_tx for borrower to sign over:
{
"Account": "ra3aoaincCNBQ7uxvHDgFbtCbVw1VNQkZy",
"TransactionType": "LoanSet",
"Fee": "2",
"Sequence": 3652181,
"LastLedgerSequence": 3674792,
"SigningPubKey": "ED0DC8C222C4BB86CE07165CD0486C598B8146C3150EE40AF48921983DED98FA47",
"TxnSignature": "9756E70F33B359FAEA789D732E752401DE41CAB1A3711517B576DBFF4D89B6A01C234A379391C48B3D88CB031BD679A7EDE4F4BB67AA7297EEE25EA29FF6BD0D",
"LoanBrokerID": "61A1D6B0F019C5D5BD039AC3DBE2E31813471567854D07D278564E4E2463ABD2",
"Counterparty": "raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL",
"LoanOriginationFee": "100",
"LoanServiceFee": "10",
"InterestRate": 500,
"PrincipalRequested": "1000",
"PaymentTotal": 12,
"PaymentInterval": 2592000,
"GracePeriod": 604800
}
=== Adding borrower signature ===
Borrower TxnSignature: A0A515BFB131EDC7A8B74F7A66F9DA1DEE25B099373F581BDA340C95F918CEA91E3F4D2019A8DBAFEC53012038839FEA48436D61970B0834F6DDEA64B1776207
Borrower SigningPubKey: ED36B94636EC0F98BB5F6EC58039E23A8C8F1521D2EC1B32C0422A86718C9B95DC
Fully signed LoanSet transaction:
{
"Account": "ra3aoaincCNBQ7uxvHDgFbtCbVw1VNQkZy",
"TransactionType": "LoanSet",
"Fee": "2",
"Sequence": 3652181,
"LastLedgerSequence": 3674792,
"SigningPubKey": "ED0DC8C222C4BB86CE07165CD0486C598B8146C3150EE40AF48921983DED98FA47",
"TxnSignature": "9756E70F33B359FAEA789D732E752401DE41CAB1A3711517B576DBFF4D89B6A01C234A379391C48B3D88CB031BD679A7EDE4F4BB67AA7297EEE25EA29FF6BD0D",
"LoanBrokerID": "61A1D6B0F019C5D5BD039AC3DBE2E31813471567854D07D278564E4E2463ABD2",
"Counterparty": "raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL",
"CounterpartySignature": {
"SigningPubKey": "ED36B94636EC0F98BB5F6EC58039E23A8C8F1521D2EC1B32C0422A86718C9B95DC",
"TxnSignature": "A0A515BFB131EDC7A8B74F7A66F9DA1DEE25B099373F581BDA340C95F918CEA91E3F4D2019A8DBAFEC53012038839FEA48436D61970B0834F6DDEA64B1776207"
},
"LoanOriginationFee": "100",
"LoanServiceFee": "10",
"InterestRate": 500,
"PrincipalRequested": "1000",
"PaymentTotal": 12,
"PaymentInterval": 2592000,
"GracePeriod": 604800
}
=== Submitting signed LoanSet transaction ===
Loan created successfully!
=== Loan Information ===
{
"Borrower": "raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL",
"GracePeriod": 604800,
"InterestRate": 500,
"LoanBrokerID": "61A1D6B0F019C5D5BD039AC3DBE2E31813471567854D07D278564E4E2463ABD2",
"LoanOriginationFee": "100",
"LoanSequence": 4,
"LoanServiceFee": "10",
"NextPaymentDueDate": 826867870,
"PaymentInterval": 2592000,
"PaymentRemaining": 12,
"PeriodicPayment": "83.55610375293148956",
"PrincipalOutstanding": "1000",
"StartDate": 824275870,
"TotalValueOutstanding": "1003"
}
```
---
## Manage a Loan
```sh
python3 loan_manage.py
```
The script should output the initial status of the loan, the LoanManage transaction, and the updated loan status and grace period after impairment. The script will countdown the grace period before outputting another LoanManage transaction, and then the final flags on the loan.
```sh
Loan broker address: r9x3etrs2GZSF73vQ8endi9CWpKr5N2Rjn
LoanID: E86DB385401D361A33DD74C8E1B44D7F996E9BA02724BCD44127F60BE057A322
=== Loan Status ===
Total Amount Owed: 1001 TSTUSD.
Payment Due Date: 2026-03-14 02:01:51
=== Preparing LoanManage transaction to impair loan ===
{
"Account": "r9x3etrs2GZSF73vQ8endi9CWpKr5N2Rjn",
"TransactionType": "LoanManage",
"Flags": 131072,
"SigningPubKey": "",
"LoanID": "E86DB385401D361A33DD74C8E1B44D7F996E9BA02724BCD44127F60BE057A322"
}
=== Submitting LoanManage impairment transaction ===
Loan impaired successfully!
New Payment Due Date: 2026-02-12 01:01:50
Grace Period: 60 seconds
=== Countdown until loan can be defaulted ===
Grace period expired. Loan can now be defaulted.
=== Preparing LoanManage transaction to default loan ===
{
"Account": "r9x3etrs2GZSF73vQ8endi9CWpKr5N2Rjn",
"TransactionType": "LoanManage",
"Flags": 65536,
"SigningPubKey": "",
"LoanID": "E86DB385401D361A33DD74C8E1B44D7F996E9BA02724BCD44127F60BE057A322"
}
=== Submitting LoanManage default transaction ===
Loan defaulted successfully!
=== Checking final loan status ===
Final loan flags: ['TF_LOAN_DEFAULT', 'TF_LOAN_IMPAIR']
```
## Pay a Loan
```sh
python3 loan_pay.py
```
The script should output the amount required to totally pay off a loan, the LoanPay transaction, the amount due after the payment, the LoanDelete transaction, and then the status of the loan ledger entry:
```sh
Borrower address: raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL
LoanID: A9CC92540995E49B39E79883A22FF10A374BF2CB32763E89AA986B613E16D5FD
MPT ID: 0037BA4C909352D28BF9580F1D536AF4F7E07649B5B6E116
=== Loan Status ===
Amount Owed: 1001 TSTUSD
Loan Service Fee: 10 TSTUSD
Total Payment Due (including fees): 1011 TSTUSD
=== Preparing LoanPay transaction ===
{
"Account": "raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL",
"TransactionType": "LoanPay",
"SigningPubKey": "",
"LoanID": "A9CC92540995E49B39E79883A22FF10A374BF2CB32763E89AA986B613E16D5FD",
"Amount": {
"mpt_issuance_id": "0037BA4C909352D28BF9580F1D536AF4F7E07649B5B6E116",
"value": "1011"
}
}
=== Submitting LoanPay transaction ===
Loan paid successfully!
=== Loan Status After Payment ===
Outstanding Loan Balance: Loan fully paid off!
=== Preparing LoanDelete transaction ===
{
"Account": "raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL",
"TransactionType": "LoanDelete",
"SigningPubKey": "",
"LoanID": "A9CC92540995E49B39E79883A22FF10A374BF2CB32763E89AA986B613E16D5FD"
}
=== Submitting LoanDelete transaction ===
Loan deleted successfully!
=== Verifying Loan Deletion ===
Loan has been successfully removed from the XRP Ledger!
```

View File

@@ -1,124 +0,0 @@
# IMPORTANT: This example deposits and claws back first-loss capital from a
# preconfigured LoanBroker entry. The first-loss capital is an MPT
# with clawback enabled.
import json
import os
import subprocess
import sys
from xrpl.clients import JsonRpcClient
from xrpl.models import LedgerEntry, LoanBrokerCoverClawback, LoanBrokerCoverDeposit, MPTAmount
from xrpl.transaction import submit_and_wait
from xrpl.wallet import Wallet
# Set up client ----------------------
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
# This step checks for the necessary setup data to run the lending protocol tutorials.
# If missing, lending_setup.py will generate the data.
if not os.path.exists("lending_setup.json"):
print("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n")
subprocess.run([sys.executable, "lending_setup.py"], check=True)
# Load preconfigured accounts, loan_broker_id, and mpt_id.
with open("lending_setup.json") as f:
setup_data = json.load(f)
# You can replace these values with your own.
loan_broker = Wallet.from_seed(setup_data["loan_broker"]["seed"])
mpt_issuer = Wallet.from_seed(setup_data["depositor"]["seed"])
loan_broker_id = setup_data["loan_broker_id"]
mpt_id = setup_data["mpt_id"]
print(f"\nLoan broker address: {loan_broker.address}")
print(f"MPT issuer address: {mpt_issuer.address}")
print(f"LoanBrokerID: {loan_broker_id}")
print(f"MPT ID: {mpt_id}")
# Check cover available ----------------------
print("\n=== Cover Available ===\n")
cover_info = client.request(LedgerEntry(
index=loan_broker_id,
ledger_index="validated",
))
current_cover_available = cover_info.result["node"].get("CoverAvailable", "0")
print(f"{current_cover_available} TSTUSD")
# Prepare LoanBrokerCoverDeposit transaction ----------------------
print("\n=== Preparing LoanBrokerCoverDeposit transaction ===\n")
cover_deposit_tx = LoanBrokerCoverDeposit(
account=loan_broker.address,
loan_broker_id=loan_broker_id,
amount=MPTAmount(mpt_issuance_id=mpt_id, value="1000"),
)
print(json.dumps(cover_deposit_tx.to_xrpl(), indent=2))
# Sign, submit, and wait for deposit validation ----------------------
print("\n=== Submitting LoanBrokerCoverDeposit transaction ===\n")
deposit_response = submit_and_wait(cover_deposit_tx, client, loan_broker)
if deposit_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = deposit_response.result["meta"]["TransactionResult"]
print(f"Error: Unable to deposit cover: {result_code}")
sys.exit(1)
print("Cover deposit successful!")
# Extract updated cover available after deposit ----------------------
print("\n=== Cover Available After Deposit ===\n")
loan_broker_node = next(
node for node in deposit_response.result["meta"]["AffectedNodes"]
if node.get("ModifiedNode", {}).get("LedgerEntryType") == "LoanBroker"
)
current_cover_available = loan_broker_node["ModifiedNode"]["FinalFields"]["CoverAvailable"]
print(f"{current_cover_available} TSTUSD")
# Verify issuer of cover asset matches ----------------------
# Only the issuer of the asset can submit clawback transactions.
# The asset must also have clawback enabled.
print("\n=== Verifying Asset Issuer ===\n")
asset_issuer_info = client.request(LedgerEntry(
mpt_issuance=mpt_id,
ledger_index="validated",
))
if asset_issuer_info.result["node"]["Issuer"] != mpt_issuer.address:
issuer = asset_issuer_info.result["node"]["Issuer"]
print(f"Error: {issuer} does not match account ({mpt_issuer.address}) attempting clawback!")
sys.exit(1)
print(f"MPT issuer account verified: {mpt_issuer.address}. Proceeding to clawback.")
# Prepare LoanBrokerCoverClawback transaction ----------------------
print("\n=== Preparing LoanBrokerCoverClawback transaction ===\n")
cover_clawback_tx = LoanBrokerCoverClawback(
account=mpt_issuer.address,
loan_broker_id=loan_broker_id,
amount=MPTAmount(mpt_issuance_id=mpt_id, value=current_cover_available),
)
print(json.dumps(cover_clawback_tx.to_xrpl(), indent=2))
# Sign, submit, and wait for clawback validation ----------------------
print("\n=== Submitting LoanBrokerCoverClawback transaction ===\n")
clawback_response = submit_and_wait(cover_clawback_tx, client, mpt_issuer)
if clawback_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = clawback_response.result["meta"]["TransactionResult"]
print(f"Error: Unable to clawback cover: {result_code}")
sys.exit(1)
print(f"Successfully clawed back {current_cover_available} TSTUSD!")
# Extract final cover available ----------------------
print("\n=== Final Cover Available After Clawback ===\n")
loan_broker_node = next(
node for node in clawback_response.result["meta"]["AffectedNodes"]
if node.get("ModifiedNode", {}).get("LedgerEntryType") == "LoanBroker"
)
print(f"{loan_broker_node['ModifiedNode']['FinalFields'].get('CoverAvailable', '0')} TSTUSD")

View File

@@ -1,95 +0,0 @@
# IMPORTANT: This example deposits and withdraws first-loss capital from a
# preconfigured LoanBroker entry.
import json
import os
import subprocess
import sys
from xrpl.clients import JsonRpcClient
from xrpl.models import LoanBrokerCoverDeposit, LoanBrokerCoverWithdraw, MPTAmount
from xrpl.transaction import submit_and_wait
from xrpl.wallet import Wallet
# Set up client ----------------------
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
# This step checks for the necessary setup data to run the lending protocol tutorials.
# If missing, lending_setup.py will generate the data.
if not os.path.exists("lending_setup.json"):
print("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n")
subprocess.run([sys.executable, "lending_setup.py"], check=True)
# Load preconfigured accounts and loan_broker_id.
with open("lending_setup.json") as f:
setup_data = json.load(f)
# You can replace these values with your own.
loan_broker = Wallet.from_seed(setup_data["loan_broker"]["seed"])
loan_broker_id = setup_data["loan_broker_id"]
mpt_id = setup_data["mpt_id"]
print(f"\nLoan broker address: {loan_broker.address}")
print(f"LoanBrokerID: {loan_broker_id}")
print(f"MPT ID: {mpt_id}")
# Prepare LoanBrokerCoverDeposit transaction ----------------------
print("\n=== Preparing LoanBrokerCoverDeposit transaction ===\n")
cover_deposit_tx = LoanBrokerCoverDeposit(
account=loan_broker.address,
loan_broker_id=loan_broker_id,
amount=MPTAmount(mpt_issuance_id=mpt_id, value="2000"),
)
print(json.dumps(cover_deposit_tx.to_xrpl(), indent=2))
# Sign, submit, and wait for deposit validation ----------------------
print("\n=== Submitting LoanBrokerCoverDeposit transaction ===\n")
deposit_response = submit_and_wait(cover_deposit_tx, client, loan_broker)
if deposit_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = deposit_response.result["meta"]["TransactionResult"]
print(f"Error: Unable to deposit cover: {result_code}")
sys.exit(1)
print("Cover deposit successful!")
# Extract cover balance from the transaction result
print("\n=== Cover Balance ===\n")
loan_broker_node = next(
node for node in deposit_response.result["meta"]["AffectedNodes"]
if node.get("ModifiedNode", {}).get("LedgerEntryType") == "LoanBroker"
)
# First-loss capital is stored in the LoanBroker's pseudo-account.
print(f"LoanBroker Pseudo-Account: {loan_broker_node['ModifiedNode']['FinalFields']['Account']}")
print(f"Cover balance after deposit: {loan_broker_node['ModifiedNode']['FinalFields']['CoverAvailable']} TSTUSD")
# Prepare LoanBrokerCoverWithdraw transaction ----------------------
print("\n=== Preparing LoanBrokerCoverWithdraw transaction ===\n")
cover_withdraw_tx = LoanBrokerCoverWithdraw(
account=loan_broker.address,
loan_broker_id=loan_broker_id,
amount=MPTAmount(mpt_issuance_id=mpt_id, value="1000"),
)
print(json.dumps(cover_withdraw_tx.to_xrpl(), indent=2))
# Sign, submit, and wait for withdraw validation ----------------------
print("\n=== Submitting LoanBrokerCoverWithdraw transaction ===\n")
withdraw_response = submit_and_wait(cover_withdraw_tx, client, loan_broker)
if withdraw_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = withdraw_response.result["meta"]["TransactionResult"]
print(f"Error: Unable to withdraw cover: {result_code}")
sys.exit(1)
print("Cover withdraw successful!")
# Extract updated cover balance from the transaction result
print("\n=== Updated Cover Balance ===\n")
loan_broker_node = next(
node for node in withdraw_response.result["meta"]["AffectedNodes"]
if node.get("ModifiedNode", {}).get("LedgerEntryType") == "LoanBroker"
)
print(f"LoanBroker Pseudo-Account: {loan_broker_node['ModifiedNode']['FinalFields']['Account']}")
print(f"Cover balance after withdraw: {loan_broker_node['ModifiedNode']['FinalFields']['CoverAvailable']} TSTUSD")

View File

@@ -1,89 +0,0 @@
# IMPORTANT: This example creates a loan using a preconfigured
# loan broker, borrower, and private vault.
import json
import os
import subprocess
import sys
from xrpl.clients import JsonRpcClient
from xrpl.models import LoanSet
from xrpl.transaction import autofill, sign, sign_loan_set_by_counterparty, submit_and_wait
from xrpl.wallet import Wallet
# Set up client ----------------------
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
# This step checks for the necessary setup data to run the lending protocol tutorials.
# If missing, lending_setup.py will generate the data.
if not os.path.exists("lending_setup.json"):
print("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n")
subprocess.run([sys.executable, "lending_setup.py"], check=True)
# Load preconfigured accounts and loan_broker_id.
with open("lending_setup.json") as f:
setup_data = json.load(f)
# You can replace these values with your own.
loan_broker = Wallet.from_seed(setup_data["loan_broker"]["seed"])
borrower = Wallet.from_seed(setup_data["borrower"]["seed"])
loan_broker_id = setup_data["loan_broker_id"]
print(f"\nLoan broker address: {loan_broker.address}")
print(f"Borrower address: {borrower.address}")
print(f"LoanBrokerID: {loan_broker_id}")
# Prepare LoanSet transaction ----------------------
# Account and Counterparty accounts can be swapped, but determines signing order.
# Account signs first, Counterparty signs second.
print("\n=== Preparing LoanSet transaction ===\n")
loan_set_tx = autofill(LoanSet(
account=loan_broker.address,
counterparty=borrower.address,
loan_broker_id=loan_broker_id,
principal_requested="1000",
interest_rate=500,
payment_total=12,
payment_interval=2592000,
grace_period=604800,
loan_origination_fee="100",
loan_service_fee="10",
), client)
print(json.dumps(loan_set_tx.to_xrpl(), indent=2))
# Loan broker signs first.
print("\n=== Adding loan broker signature ===\n")
loan_broker_signed = sign(loan_set_tx, loan_broker)
print(f"TxnSignature: {loan_broker_signed.txn_signature}")
print(f"SigningPubKey: {loan_broker_signed.signing_pub_key}\n")
print(f"Signed loan_set_tx for borrower to sign over:\n{json.dumps(loan_broker_signed.to_xrpl(), indent=2)}")
# Borrower signs second.
print("\n=== Adding borrower signature ===\n")
fully_signed = sign_loan_set_by_counterparty(borrower, loan_broker_signed)
print(f"Borrower TxnSignature: {fully_signed.tx.counterparty_signature.txn_signature}")
print(f"Borrower SigningPubKey: {fully_signed.tx.counterparty_signature.signing_pub_key}")
print(f"\nFully signed LoanSet transaction:\n{json.dumps(fully_signed.tx.to_xrpl(), indent=2)}")
# Submit and wait for validation ----------------------
print("\n=== Submitting signed LoanSet transaction ===\n")
submit_response = submit_and_wait(fully_signed.tx, client)
if submit_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = submit_response.result["meta"]["TransactionResult"]
print(f"Error: Unable to create loan: {result_code}")
sys.exit(1)
print("Loan created successfully!")
# Extract loan information from the transaction result.
print("\n=== Loan Information ===\n")
loan_node = next(
node for node in submit_response.result["meta"]["AffectedNodes"]
if node.get("CreatedNode", {}).get("LedgerEntryType") == "Loan"
)
print(json.dumps(loan_node["CreatedNode"]["NewFields"], indent=2))

View File

@@ -1,64 +0,0 @@
# IMPORTANT: This example creates a loan broker using an existing account
# that has already created a PRIVATE vault.
# If you want to create a loan broker for a PUBLIC vault, you can replace the vault_id
# and loan_broker values with your own.
import json
import os
import subprocess
import sys
from xrpl.clients import JsonRpcClient
from xrpl.models import LoanBrokerSet
from xrpl.transaction import submit_and_wait
from xrpl.wallet import Wallet
# Set up client ----------------------
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
# This step checks for the necessary setup data to run the lending protocol tutorials.
# If missing, lending_setup.py will generate the data.
if not os.path.exists("lending_setup.json"):
print("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n")
subprocess.run([sys.executable, "lending_setup.py"], check=True)
# Load preconfigured accounts and vault_id.
with open("lending_setup.json") as f:
setup_data = json.load(f)
# You can replace these values with your own.
loan_broker = Wallet.from_seed(setup_data["loan_broker"]["seed"])
vault_id = setup_data["vault_id"]
print(f"\nLoan broker/vault owner address: {loan_broker.address}")
print(f"Vault ID: {vault_id}")
# Prepare LoanBrokerSet transaction ----------------------
print("\n=== Preparing LoanBrokerSet transaction ===\n")
loan_broker_set_tx = LoanBrokerSet(
account=loan_broker.address,
vault_id=vault_id,
management_fee_rate=1000,
)
print(json.dumps(loan_broker_set_tx.to_xrpl(), indent=2))
# Submit, sign, and wait for validation ----------------------
print("\n=== Submitting LoanBrokerSet transaction ===\n")
submit_response = submit_and_wait(loan_broker_set_tx, client, loan_broker)
if submit_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = submit_response.result["meta"]["TransactionResult"]
print(f"Error: Unable to create loan broker: {result_code}")
sys.exit(1)
print("Loan broker created successfully!")
# Extract loan broker information from the transaction result
print("\n=== Loan Broker Information ===\n")
loan_broker_node = next(
node for node in submit_response.result["meta"]["AffectedNodes"]
if node.get("CreatedNode", {}).get("LedgerEntryType") == "LoanBroker"
)
print(f"LoanBroker ID: {loan_broker_node['CreatedNode']['LedgerIndex']}")
print(f"LoanBroker Psuedo-Account Address: {loan_broker_node['CreatedNode']['NewFields']['Account']}")

View File

@@ -1,366 +0,0 @@
# Setup script for lending protocol tutorials
import asyncio
import json
from xrpl.asyncio.clients import AsyncWebsocketClient
from xrpl.asyncio.wallet import generate_faucet_wallet
from xrpl.asyncio.transaction import submit_and_wait, autofill, sign
from xrpl.transaction import sign_loan_set_by_counterparty
from xrpl.models import (
AccountObjects,
Batch,
BatchFlag,
CredentialAccept,
CredentialCreate,
LoanBrokerSet,
LoanSet,
MPTAmount,
MPTCurrency,
MPTokenAuthorize,
MPTokenIssuanceCreate,
MPTokenIssuanceCreateFlag,
Payment,
PermissionedDomainSet,
TicketCreate,
VaultCreate,
VaultDeposit,
)
from xrpl.models.transactions.vault_create import VaultCreateFlag
from xrpl.models.transactions.deposit_preauth import Credential
from xrpl.utils import encode_mptoken_metadata, str_to_hex
async def main():
async with AsyncWebsocketClient("wss://s.devnet.rippletest.net:51233") as client:
print("Setting up tutorial: 0/6", end="\r")
# Create and fund wallets
loan_broker, borrower, depositor, credential_issuer = await asyncio.gather(
generate_faucet_wallet(client),
generate_faucet_wallet(client),
generate_faucet_wallet(client),
generate_faucet_wallet(client),
)
print("Setting up tutorial: 1/6", end="\r")
# Issue MPT with depositor
# Create tickets for later use with loan_broker
# Set up credentials and domain with credential_issuer
credential_type = str_to_hex("KYC-Verified")
mpt_data = {
"ticker": "TSTUSD",
"name": "Test USD MPT",
"desc": "A sample non-yield-bearing stablecoin backed by U.S. Treasuries.",
"icon": "https://example.org/tstusd-icon.png",
"asset_class": "rwa",
"asset_subclass": "stablecoin",
"issuer_name": "Example Treasury Reserve Co.",
"uris": [
{
"uri": "https://exampletreasury.com/tstusd",
"category": "website",
"title": "Product Page",
},
{
"uri": "https://exampletreasury.com/tstusd/reserve",
"category": "docs",
"title": "Reserve Attestation",
},
],
"additional_info": {
"reserve_type": "U.S. Treasury Bills",
"custody_provider": "Example Custodian Bank",
"audit_frequency": "Monthly",
"last_audit_date": "2026-01-15",
"pegged_currency": "USD",
},
}
ticket_create_response, mpt_issuance_response, _ = await asyncio.gather(
submit_and_wait(
TicketCreate(
account=loan_broker.address,
ticket_count=2,
),
client,
loan_broker,
),
submit_and_wait(
MPTokenIssuanceCreate(
account=depositor.address,
maximum_amount="100000000",
transfer_fee=0,
flags=(
MPTokenIssuanceCreateFlag.TF_MPT_CAN_TRANSFER
| MPTokenIssuanceCreateFlag.TF_MPT_CAN_CLAWBACK
| MPTokenIssuanceCreateFlag.TF_MPT_CAN_TRADE
),
mptoken_metadata=encode_mptoken_metadata(mpt_data),
),
client,
depositor,
),
submit_and_wait(
Batch(
account=credential_issuer.address,
flags=BatchFlag.TF_ALL_OR_NOTHING,
raw_transactions=[
CredentialCreate(
account=credential_issuer.address,
subject=loan_broker.address,
credential_type=credential_type,
),
CredentialCreate(
account=credential_issuer.address,
subject=borrower.address,
credential_type=credential_type,
),
CredentialCreate(
account=credential_issuer.address,
subject=depositor.address,
credential_type=credential_type,
),
PermissionedDomainSet(
account=credential_issuer.address,
accepted_credentials=[
Credential(
issuer=credential_issuer.address,
credential_type=credential_type,
),
],
),
],
),
client,
credential_issuer,
),
)
# Extract ticket sequence numbers
tickets = [
node["CreatedNode"]["NewFields"]["TicketSequence"]
for node in ticket_create_response.result["meta"]["AffectedNodes"]
if node.get("CreatedNode", {}).get("LedgerEntryType") == "Ticket"
]
# Extract MPT issuance ID
mpt_id = mpt_issuance_response.result["meta"]["mpt_issuance_id"]
# Get domain ID
credential_issuer_objects = await client.request(AccountObjects(
account=credential_issuer.address,
ledger_index="validated",
))
domain_id = next(
node["index"]
for node in credential_issuer_objects.result["account_objects"]
if node["LedgerEntryType"] == "PermissionedDomain"
)
print("Setting up tutorial: 2/6", end="\r")
# Accept credentials and authorize MPT for each account
await asyncio.gather(
submit_and_wait(
Batch(
account=loan_broker.address,
flags=BatchFlag.TF_ALL_OR_NOTHING,
raw_transactions=[
CredentialAccept(
account=loan_broker.address,
issuer=credential_issuer.address,
credential_type=credential_type,
),
MPTokenAuthorize(
account=loan_broker.address,
mptoken_issuance_id=mpt_id,
),
],
),
client,
loan_broker,
),
submit_and_wait(
Batch(
account=borrower.address,
flags=BatchFlag.TF_ALL_OR_NOTHING,
raw_transactions=[
CredentialAccept(
account=borrower.address,
issuer=credential_issuer.address,
credential_type=credential_type,
),
MPTokenAuthorize(
account=borrower.address,
mptoken_issuance_id=mpt_id,
),
],
),
client,
borrower,
),
submit_and_wait(
CredentialAccept(
account=depositor.address,
issuer=credential_issuer.address,
credential_type=credential_type,
),
client,
depositor,
),
)
print("Setting up tutorial: 3/6", end="\r")
# Create private vault and distribute MPT to accounts
vault_create_response, _ = await asyncio.gather(
submit_and_wait(
VaultCreate(
account=loan_broker.address,
asset=MPTCurrency(mpt_issuance_id=mpt_id),
flags=VaultCreateFlag.TF_VAULT_PRIVATE,
domain_id=domain_id,
),
client,
loan_broker,
),
submit_and_wait(
Batch(
account=depositor.address,
flags=BatchFlag.TF_ALL_OR_NOTHING,
raw_transactions=[
Payment(
account=depositor.address,
destination=loan_broker.address,
amount=MPTAmount(mpt_issuance_id=mpt_id, value="5000"),
),
Payment(
account=depositor.address,
destination=borrower.address,
amount=MPTAmount(mpt_issuance_id=mpt_id, value="2500"),
),
],
),
client,
depositor,
),
)
vault_id = next(
node["CreatedNode"]["LedgerIndex"]
for node in vault_create_response.result["meta"]["AffectedNodes"]
if node.get("CreatedNode", {}).get("LedgerEntryType") == "Vault"
)
print("Setting up tutorial: 4/6", end="\r")
# Create LoanBroker and deposit MPT into vault
loan_broker_set_response, _ = await asyncio.gather(
submit_and_wait(
LoanBrokerSet(
account=loan_broker.address,
vault_id=vault_id,
),
client,
loan_broker,
),
submit_and_wait(
VaultDeposit(
account=depositor.address,
vault_id=vault_id,
amount=MPTAmount(mpt_issuance_id=mpt_id, value="50000000"),
),
client,
depositor,
),
)
loan_broker_id = next(
node["CreatedNode"]["LedgerIndex"]
for node in loan_broker_set_response.result["meta"]["AffectedNodes"]
if node.get("CreatedNode", {}).get("LedgerEntryType") == "LoanBroker"
)
print("Setting up tutorial: 5/6", end="\r")
# Create 2 identical loans with complete repayment due in 30 days
# Helper function to create, sign, and submit a LoanSet transaction
async def create_loan(ticket_sequence):
loan_set_tx = await autofill(LoanSet(
account=loan_broker.address,
counterparty=borrower.address,
loan_broker_id=loan_broker_id,
principal_requested="1000",
interest_rate=500,
payment_total=1,
payment_interval=2592000,
loan_origination_fee="100",
loan_service_fee="10",
sequence=0,
ticket_sequence=ticket_sequence,
), client)
loan_broker_signed = sign(loan_set_tx, loan_broker)
fully_signed = sign_loan_set_by_counterparty(borrower, loan_broker_signed)
submit_response = await submit_and_wait(fully_signed.tx, client)
return submit_response
submit_response_1, submit_response_2 = await asyncio.gather(
create_loan(tickets[0]),
create_loan(tickets[1]),
)
loan_id_1 = next(
node["CreatedNode"]["LedgerIndex"]
for node in submit_response_1.result["meta"]["AffectedNodes"]
if node.get("CreatedNode", {}).get("LedgerEntryType") == "Loan"
)
loan_id_2 = next(
node["CreatedNode"]["LedgerIndex"]
for node in submit_response_2.result["meta"]["AffectedNodes"]
if node.get("CreatedNode", {}).get("LedgerEntryType") == "Loan"
)
print("Setting up tutorial: 6/6", end="\r")
# Write setup data to JSON file
setup_data = {
"description": "This file is auto-generated by lending_setup.py. It stores XRPL account info for use in lending protocol tutorials.",
"loan_broker": {
"address": loan_broker.address,
"seed": loan_broker.seed,
},
"borrower": {
"address": borrower.address,
"seed": borrower.seed,
},
"depositor": {
"address": depositor.address,
"seed": depositor.seed,
},
"credential_issuer": {
"address": credential_issuer.address,
"seed": credential_issuer.seed,
},
"domain_id": domain_id,
"mpt_id": mpt_id,
"vault_id": vault_id,
"loan_broker_id": loan_broker_id,
"loan_id_1": loan_id_1,
"loan_id_2": loan_id_2,
}
with open("lending_setup.json", "w") as f:
json.dump(setup_data, f, indent=2)
print("Setting up tutorial: Complete!")
asyncio.run(main())

View File

@@ -1,130 +0,0 @@
# IMPORTANT: This example impairs an existing loan, which has a 60 second grace period.
# After the 60 seconds pass, this example defaults the loan.
import json
import os
import subprocess
import sys
import time
from datetime import datetime
from xrpl.clients import JsonRpcClient
from xrpl.models import LedgerEntry, LoanManage
from xrpl.models.transactions.loan_manage import LoanManageFlag
from xrpl.transaction import submit_and_wait
from xrpl.utils import posix_to_ripple_time, ripple_time_to_posix
from xrpl.wallet import Wallet
# Set up client ----------------------
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
# This step checks for the necessary setup data to run the lending protocol tutorials.
# If missing, lending_setup.py will generate the data.
if not os.path.exists("lending_setup.json"):
print("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n")
subprocess.run([sys.executable, "lending_setup.py"], check=True)
# Load preconfigured accounts and loan_id.
with open("lending_setup.json") as f:
setup_data = json.load(f)
# You can replace these values with your own.
loan_broker = Wallet.from_seed(setup_data["loan_broker"]["seed"])
loan_id = setup_data["loan_id_1"]
print(f"\nLoan broker address: {loan_broker.address}")
print(f"LoanID: {loan_id}")
# Check loan status before impairment ----------------------
print("\n=== Loan Status ===\n")
loan_status = client.request(LedgerEntry(
index=loan_id,
ledger_index="validated",
))
print(f"Total Amount Owed: {loan_status.result['node']['TotalValueOutstanding']} TSTUSD.")
# Convert Ripple Epoch timestamp to local date and time
next_payment_due_date = loan_status.result["node"]["NextPaymentDueDate"]
payment_due = datetime.fromtimestamp(ripple_time_to_posix(next_payment_due_date))
print(f"Payment Due Date: {payment_due}")
# Prepare LoanManage transaction to impair the loan ----------------------
print("\n=== Preparing LoanManage transaction to impair loan ===\n")
loan_manage_impair = LoanManage(
account=loan_broker.address,
loan_id=loan_id,
flags=LoanManageFlag.TF_LOAN_IMPAIR,
)
print(json.dumps(loan_manage_impair.to_xrpl(), indent=2))
# Sign, submit, and wait for impairment validation ----------------------
print("\n=== Submitting LoanManage impairment transaction ===\n")
impair_response = submit_and_wait(loan_manage_impair, client, loan_broker)
if impair_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = impair_response.result["meta"]["TransactionResult"]
print(f"Error: Unable to impair loan: {result_code}")
sys.exit(1)
print("Loan impaired successfully!")
# Extract loan impairment info from transaction results ----------------------
loan_node = next(
node for node in impair_response.result["meta"]["AffectedNodes"]
if node.get("ModifiedNode", {}).get("LedgerEntryType") == "Loan"
)
# Check grace period and next payment due date
grace_period = loan_node["ModifiedNode"]["FinalFields"]["GracePeriod"]
next_payment_due_date = loan_node["ModifiedNode"]["FinalFields"]["NextPaymentDueDate"]
default_time = next_payment_due_date + grace_period
payment_due = datetime.fromtimestamp(ripple_time_to_posix(next_payment_due_date))
print(f"New Payment Due Date: {payment_due}")
print(f"Grace Period: {grace_period} seconds")
# Convert current time to Ripple Epoch timestamp
current_time = posix_to_ripple_time(int(time.time()))
seconds_until_default = default_time - current_time
# Countdown until loan can be defaulted ----------------------
print("\n=== Countdown until loan can be defaulted ===\n")
while seconds_until_default >= 0:
print(f"{seconds_until_default} seconds...", end="\r")
time.sleep(1)
seconds_until_default -= 1
print("\rGrace period expired. Loan can now be defaulted.")
# Prepare LoanManage transaction to default the loan ----------------------
print("\n=== Preparing LoanManage transaction to default loan ===\n")
loan_manage_default = LoanManage(
account=loan_broker.address,
loan_id=loan_id,
flags=LoanManageFlag.TF_LOAN_DEFAULT,
)
print(json.dumps(loan_manage_default.to_xrpl(), indent=2))
# Sign, submit, and wait for default validation ----------------------
print("\n=== Submitting LoanManage default transaction ===\n")
default_response = submit_and_wait(loan_manage_default, client, loan_broker)
if default_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = default_response.result["meta"]["TransactionResult"]
print(f"Error: Unable to default loan: {result_code}")
sys.exit(1)
print("Loan defaulted successfully!")
# Verify loan default status from transaction results ----------------------
print("\n=== Checking final loan status ===\n")
loan_node = next(
node for node in default_response.result["meta"]["AffectedNodes"]
if node.get("ModifiedNode", {}).get("LedgerEntryType") == "Loan"
)
loan_flags = loan_node["ModifiedNode"]["FinalFields"]["Flags"]
active_flags = [f.name for f in LoanManageFlag if loan_flags & f.value]
print(f"Final loan flags: {active_flags}")

View File

@@ -1,117 +0,0 @@
# IMPORTANT: This example pays off an existing loan and then deletes it.
import json
import os
import subprocess
import sys
from xrpl.clients import JsonRpcClient
from xrpl.models import LedgerEntry, LoanDelete, LoanPay, MPTAmount
from xrpl.transaction import submit_and_wait
from xrpl.wallet import Wallet
# Set up client ----------------------
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
# This step checks for the necessary setup data to run the lending protocol tutorials.
# If missing, lending_setup.py will generate the data.
if not os.path.exists("lending_setup.json"):
print("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n")
subprocess.run([sys.executable, "lending_setup.py"], check=True)
# Load preconfigured accounts, loan_id, and mpt_id.
with open("lending_setup.json") as f:
setup_data = json.load(f)
# You can replace these values with your own.
borrower = Wallet.from_seed(setup_data["borrower"]["seed"])
loan_id = setup_data["loan_id_2"]
mpt_id = setup_data["mpt_id"]
print(f"\nBorrower address: {borrower.address}")
print(f"LoanID: {loan_id}")
print(f"MPT ID: {mpt_id}")
# Check initial loan status ----------------------
print("\n=== Loan Status ===\n")
loan_status = client.request(LedgerEntry(
index=loan_id,
ledger_index="validated",
))
total_value_outstanding = loan_status.result["node"]["TotalValueOutstanding"]
loan_service_fee = loan_status.result["node"]["LoanServiceFee"]
total_payment = str(int(total_value_outstanding) + int(loan_service_fee))
print(f"Amount Owed: {total_value_outstanding} TSTUSD")
print(f"Loan Service Fee: {loan_service_fee} TSTUSD")
print(f"Total Payment Due (including fees): {total_payment} TSTUSD")
# Prepare LoanPay transaction ----------------------
print("\n=== Preparing LoanPay transaction ===\n")
loan_pay_tx = LoanPay(
account=borrower.address,
loan_id=loan_id,
amount=MPTAmount(mpt_issuance_id=mpt_id, value=total_payment),
)
print(json.dumps(loan_pay_tx.to_xrpl(), indent=2))
# Sign, submit, and wait for payment validation ----------------------
print("\n=== Submitting LoanPay transaction ===\n")
pay_response = submit_and_wait(loan_pay_tx, client, borrower)
if pay_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = pay_response.result["meta"]["TransactionResult"]
print(f"Error: Unable to pay loan: {result_code}")
sys.exit(1)
print("Loan paid successfully!")
# Extract updated loan info from transaction results ----------------------
print("\n=== Loan Status After Payment ===\n")
loan_node = next(
node for node in pay_response.result["meta"]["AffectedNodes"]
if node.get("ModifiedNode", {}).get("LedgerEntryType") == "Loan"
)
final_balance = loan_node["ModifiedNode"]["FinalFields"].get("TotalValueOutstanding")
if final_balance:
print(f"Outstanding Loan Balance: {final_balance} TSTUSD")
else:
print("Outstanding Loan Balance: Loan fully paid off!")
# Prepare LoanDelete transaction ----------------------
# Either the loan broker or borrower can submit this transaction.
print("\n=== Preparing LoanDelete transaction ===\n")
loan_delete_tx = LoanDelete(
account=borrower.address,
loan_id=loan_id,
)
print(json.dumps(loan_delete_tx.to_xrpl(), indent=2))
# Sign, submit, and wait for deletion validation ----------------------
print("\n=== Submitting LoanDelete transaction ===\n")
delete_response = submit_and_wait(loan_delete_tx, client, borrower)
if delete_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = delete_response.result["meta"]["TransactionResult"]
print(f"Error: Unable to delete loan: {result_code}")
sys.exit(1)
print("Loan deleted successfully!")
# Verify loan deletion ----------------------
print("\n=== Verifying Loan Deletion ===\n")
verify_response = client.request(LedgerEntry(
index=loan_id,
ledger_index="validated",
))
if verify_response.is_successful():
print("Warning: Loan still exists in the ledger.")
elif verify_response.result.get("error") == "entryNotFound":
print("Loan has been successfully removed from the XRP Ledger!")
else:
print(f"Error checking loan status: {verify_response.result.get('error')}")

View File

@@ -1 +0,0 @@
xrpl-py>=4.5.0

View File

@@ -1,3 +0,0 @@
# Single Asset Vault Examples
Shows how to create, deposit into, and withdraw from single asset vaults on the XRP Ledger.

View File

@@ -1,187 +0,0 @@
# Single Asset Vault Examples (JavaScript)
This directory contains JavaScript examples demonstrating how to create, deposit into, and withdraw from single asset vaults on the XRP Ledger.
## Setup
Install dependencies before running any examples:
```sh
npm i
```
---
## Create a Vault
```sh
node createVault.js
```
The script should output the VaultCreate transaction, vault ID, and complete vault information:
```sh
Vault owner address: rLXZNDSS7gWvQZKunRUFiaViSiHo1yd4Ms
MPT issuance ID: 0003E3B486D3DACD8BB468AB33793B9626BD894A92AB3AB4
Permissioned domain ID: 3BB81D0D164456A2D74720F63FD923F16DE08FB3223D3ED103D09F525A8D69D1
=== VaultCreate transaction ===
{
"TransactionType": "VaultCreate",
"Account": "rLXZNDSS7gWvQZKunRUFiaViSiHo1yd4Ms",
"Asset": {
"mpt_issuance_id": "0003E3B486D3DACD8BB468AB33793B9626BD894A92AB3AB4"
},
"Flags": 65536,
"DomainID": "3BB81D0D164456A2D74720F63FD923F16DE08FB3223D3ED103D09F525A8D69D1",
"Data": "50726976617465207661756C74",
"MPTokenMetadata": "7B226163223A2264656669222C226169223A7B226578616D706C655F696E666F223A2274657374227D2C2264223A2250726F706F7274696F6E616C206F776E65727368697020736861726573206F6620746865207661756C742E222C2269223A226578616D706C652E636F6D2F61737365742D69636F6E2E706E67222C22696E223A22417373657420497373756572204E616D65222C226E223A225661756C7420736861726573222C2274223A22534841524531222C227573223A5B7B2263223A2277656273697465222C2274223A2241737365742057656273697465222C2275223A226578616D706C652E636F6D2F6173736574227D2C7B2263223A22646F6373222C2274223A22446F6373222C2275223A226578616D706C652E636F6D2F646F6373227D5D7D",
"AssetsMaximum": "0",
"WithdrawalPolicy": 1
}
=== Submitting VaultCreate transaction... ===
Vault created successfully!
Vault ID: 9D25282C143F0F7F71F0E6FC7ABB3BD6FB30B7DCF04DF4A1E31C701B1B332D29
Vault pseudo-account address: rnBAKKEBBTqswakdeJJkZtBs9SRgpMkThj
Share MPT issuance ID: 000000012DF200D67FF9DA7686FF8B6F32097337D7765211
=== Getting vault_info... ===
{
"api_version": 2,
"id": 12,
"result": {
"ledger_hash": "73B53C0608A9C87C2B97314F0BAD109F236C4A95FB53FE4E8CEAEFE826A1E7AB",
"ledger_index": 597229,
"validated": true,
"vault": {
"Account": "rnBAKKEBBTqswakdeJJkZtBs9SRgpMkThj",
"Asset": {
"mpt_issuance_id": "0003E3B486D3DACD8BB468AB33793B9626BD894A92AB3AB4"
},
"Data": "50726976617465207661756C74",
"Flags": 65536,
"LedgerEntryType": "Vault",
"Owner": "rLXZNDSS7gWvQZKunRUFiaViSiHo1yd4Ms",
"OwnerNode": "0",
"PreviousTxnID": "8B64609225F802258250824B2C6C0A8B752AB8CBB6FAF64D433DC2F35C09E131",
"PreviousTxnLgrSeq": 597229,
"Sequence": 597228,
"ShareMPTID": "000000012DF200D67FF9DA7686FF8B6F32097337D7765211",
"WithdrawalPolicy": 1,
"index": "9D25282C143F0F7F71F0E6FC7ABB3BD6FB30B7DCF04DF4A1E31C701B1B332D29",
"shares": {
"DomainID": "3BB81D0D164456A2D74720F63FD923F16DE08FB3223D3ED103D09F525A8D69D1",
"Flags": 60,
"Issuer": "rnBAKKEBBTqswakdeJJkZtBs9SRgpMkThj",
"LedgerEntryType": "MPTokenIssuance",
"MPTokenMetadata": "7B226163223A2264656669222C226169223A7B226578616D706C655F696E666F223A2274657374227D2C2264223A2250726F706F7274696F6E616C206F776E65727368697020736861726573206F6620746865207661756C742E222C2269223A226578616D706C652E636F6D2F61737365742D69636F6E2E706E67222C22696E223A22417373657420497373756572204E616D65222C226E223A225661756C7420736861726573222C2274223A22534841524531222C227573223A5B7B2263223A2277656273697465222C2274223A2241737365742057656273697465222C2275223A226578616D706C652E636F6D2F6173736574227D2C7B2263223A22646F6373222C2274223A22446F6373222C2275223A226578616D706C652E636F6D2F646F6373227D5D7D",
"OutstandingAmount": "0",
"OwnerNode": "0",
"PreviousTxnID": "8B64609225F802258250824B2C6C0A8B752AB8CBB6FAF64D433DC2F35C09E131",
"PreviousTxnLgrSeq": 597229,
"Sequence": 1,
"index": "4C3CC0AF1FE27EBE364F02AFF889D73D1F6F7CB5ED6126D1CD605E8952E18302",
"mpt_issuance_id": "000000012DF200D67FF9DA7686FF8B6F32097337D7765211"
}
}
},
"type": "response"
}
```
---
## Deposit into a Vault
```sh
node deposit.js
```
The script should output the vault state before and after the deposit, along with the depositor's share balance:
```sh
Depositor address: rnEmvWahVbNXzs8zGjhEfkBwo41Zn5wDDU
Vault ID: 6AC4EC2D775C6275D314996D6ECDD16DCB9382A29FDB769951C42192FCED76EF
Asset MPT issuance ID: 0003E3B486D3DACD8BB468AB33793B9626BD894A92AB3AB4
Vault share MPT issuance ID: 0000000152E7CD364F869E832EDB806C4A7AD8B3D0C151C5
=== Getting initial vault state... ===
- Total vault value: 1
- Available assets: 1
=== Checking depositor's balance... ===
Balance: 9937
=== VaultDeposit transaction ===
{
"TransactionType": "VaultDeposit",
"Account": "rnEmvWahVbNXzs8zGjhEfkBwo41Zn5wDDU",
"VaultID": "6AC4EC2D775C6275D314996D6ECDD16DCB9382A29FDB769951C42192FCED76EF",
"Amount": {
"mpt_issuance_id": "0003E3B486D3DACD8BB468AB33793B9626BD894A92AB3AB4",
"value": "1"
}
}
=== Submitting VaultDeposit transaction... ===
Deposit successful!
=== Vault state after deposit ===
- Total vault value: 2
- Available assets: 2
=== Depositor's share balance ==
Shares held: 2
```
---
## Withdraw from a Vault
```sh
node withdraw.js
```
The script should output the vault state before and after the withdrawal, along with updated share and asset balances:
```sh
Depositor address: rnEmvWahVbNXzs8zGjhEfkBwo41Zn5wDDU
Vault ID: 6AC4EC2D775C6275D314996D6ECDD16DCB9382A29FDB769951C42192FCED76EF
Asset MPT issuance ID: 0003E3B486D3DACD8BB468AB33793B9626BD894A92AB3AB4
Vault share MPT issuance ID: 0000000152E7CD364F869E832EDB806C4A7AD8B3D0C151C5
=== Getting initial vault state... ===
Initial vault state:
Assets Total: 2
Assets Available: 2
=== Checking depositor's share balance... ===
Shares held: 2
=== Preparing VaultWithdraw transaction ===
{
"TransactionType": "VaultWithdraw",
"Account": "rnEmvWahVbNXzs8zGjhEfkBwo41Zn5wDDU",
"VaultID": "6AC4EC2D775C6275D314996D6ECDD16DCB9382A29FDB769951C42192FCED76EF",
"Amount": {
"mpt_issuance_id": "0003E3B486D3DACD8BB468AB33793B9626BD894A92AB3AB4",
"value": "1"
}
}
=== Submitting VaultWithdraw transaction... ===
Withdrawal successful!
=== Vault state after withdrawal ===
Assets Total: 1
Assets Available: 1
=== Depositor's share balance ==
Shares held: 1
=== Depositor's asset balance ==
Balance: 9937
```

View File

@@ -1,111 +0,0 @@
import xrpl from "xrpl"
import { execSync } from "child_process"
import fs from "fs"
// Auto-run setup if needed
if (!fs.existsSync("vaultSetup.json")) {
console.log(`\n=== Vault setup data doesn't exist. Running setup script... ===\n`)
execSync("node vaultSetup.js", { stdio: "inherit" })
}
// Load setup data
const setupData = JSON.parse(fs.readFileSync("vaultSetup.json", "utf8"))
// Connect to the network
const client = new xrpl.Client("wss://s.devnet.rippletest.net:51233")
await client.connect()
// Create and fund vault owner account
const { wallet: vaultOwner } = await client.fundWallet()
// You can replace these values with your own
const mptIssuanceId = setupData.mptIssuanceId
const domainId = setupData.domainId
console.log(`Vault owner address: ${vaultOwner.address}`)
console.log(`MPT issuance ID: ${mptIssuanceId}`)
console.log(`Permissioned domain ID: ${domainId}\n`)
// Prepare VaultCreate transaction ----------------------
console.log(`\n=== VaultCreate transaction ===`)
const vaultCreateTx = {
TransactionType: "VaultCreate",
Account: vaultOwner.address,
Asset: { mpt_issuance_id: mptIssuanceId },
Flags: xrpl.VaultCreateFlags.tfVaultPrivate, // Omit tfVaultPrivate flag for public vaults
// To make vault shares non-transferable add the tfVaultShareNonTransferable flag:
// Flags: xrpl.VaultCreateFlags.tfVaultPrivate | xrpl.VaultCreateFlags.tfVaultShareNonTransferable
DomainID: domainId, // Omit for public vaults
// Convert Vault data to a string (without excess whitespace), then string to hex.
Data: xrpl.convertStringToHex(JSON.stringify(
{ n: "LATAM Fund II", w: "examplefund.com" })
),
// Encode JSON metadata as hex string per XLS-89 MPT Metadata Schema.
// See: https://xls.xrpl.org/xls/XLS-0089-multi-purpose-token-metadata-schema.html
MPTokenMetadata: xrpl.encodeMPTokenMetadata({
ticker: "SHARE1",
name: "Vault shares",
desc: "Proportional ownership shares of the vault.",
icon: "example.com/asset-icon.png",
asset_class: "defi",
issuer_name: "Asset Issuer Name",
uris: [
{
uri: "example.com/asset",
category: "website",
title: "Asset Website",
},
{
uri: "example.com/docs",
category: "docs",
title: "Docs",
},
],
additional_info: {
example_info: "test",
},
}),
AssetsMaximum: "0", // No cap
WithdrawalPolicy: xrpl.VaultWithdrawalPolicy.vaultStrategyFirstComeFirstServe,
};
// Validate the transaction structure before submitting
xrpl.validate(vaultCreateTx)
console.log(JSON.stringify(vaultCreateTx, null, 2))
// Submit, sign, and wait for validation ----------------------
console.log("\n=== Submitting VaultCreate transaction... ===")
const submit_response = await client.submitAndWait(vaultCreateTx, {
wallet: vaultOwner,
autofill: true,
})
if (submit_response.result.meta.TransactionResult !== "tesSUCCESS") {
const result_code = submit_response.result.meta.TransactionResult;
console.error("Error: Unable to create vault:", result_code)
await client.disconnect()
process.exit(1)
}
console.log("Vault created successfully!")
// Extract vault information from the transaction result
const affectedNodes = submit_response.result.meta.AffectedNodes || []
const vaultNode = affectedNodes.find(
(node) => node.CreatedNode?.LedgerEntryType === "Vault"
)
if (vaultNode) {
console.log(`\nVault ID: ${vaultNode.CreatedNode.LedgerIndex}`)
console.log(`Vault pseudo-account address: ${vaultNode.CreatedNode.NewFields.Account}`)
console.log(`Share MPT issuance ID: ${vaultNode.CreatedNode.NewFields.ShareMPTID}`)
}
// Call vault_info method to retrieve the vault's information
console.log("\n=== Getting vault_info... ===")
const vaultID = vaultNode.CreatedNode.LedgerIndex
const vault_info_response = await client.request({
command: "vault_info",
vault_id: vaultID,
ledger_index: "validated"
})
console.log(JSON.stringify(vault_info_response, null, 2))
await client.disconnect()

View File

@@ -1,147 +0,0 @@
// IMPORTANT: This example deposits into an existing PRIVATE vault.
// The depositor account used has valid credentials in the vault's Permissioned Domain.
// Without valid credentials, the VaultDeposit transaction will fail.
// If you want to deposit into a public vault, you can replace the vaultID and shareMPTIssuanceId
// values with your own.
import xrpl from "xrpl"
import { execSync } from "child_process"
import fs from "fs"
// Auto-run setup if needed
if (!fs.existsSync("vaultSetup.json")) {
console.log(`\n=== Vault setup data doesn't exist. Running setup script... ===\n`)
execSync("node vaultSetup.js", { stdio: "inherit" })
}
// Load setup data
const setupData = JSON.parse(fs.readFileSync("vaultSetup.json", "utf8"))
// Connect to the network
const client = new xrpl.Client("wss://s.devnet.rippletest.net:51233")
await client.connect()
// You can replace these values with your own
const depositor = xrpl.Wallet.fromSeed(setupData.depositor.seed)
const vaultID = setupData.vaultID
const assetMPTIssuanceId = setupData.mptIssuanceId
const shareMPTIssuanceId = setupData.vaultShareMPTIssuanceId
console.log(`Depositor address: ${depositor.address}`)
console.log(`Vault ID: ${vaultID}`)
console.log(`Asset MPT issuance ID: ${assetMPTIssuanceId}`)
console.log(`Vault share MPT issuance ID: ${shareMPTIssuanceId}`)
const depositAmount = 1
// Get initial vault state ----------------------
console.log("\n=== Getting initial vault state... ===")
const initialVaultInfo = await client.request({
command: "vault_info",
vault_id: vaultID,
ledger_index: "validated"
})
console.log(` - Total vault value: ${initialVaultInfo.result.vault.AssetsTotal}`)
console.log(` - Available assets: ${initialVaultInfo.result.vault.AssetsAvailable}`)
// Check depositor's asset balance ----------------------
console.log("\n=== Checking depositor's balance... ===")
try {
// Use ledger_entry to get specific MPT issuance balance
const ledgerEntryResult = await client.request({
command: "ledger_entry",
mptoken: {
mpt_issuance_id: assetMPTIssuanceId,
account: depositor.address
},
ledger_index: "validated"
})
const balance = ledgerEntryResult.result.node?.MPTAmount
console.log(`Balance: ${balance}`)
// Check if balance is sufficient
if (balance < depositAmount) {
console.error(`Error: Insufficient balance! Have ${balance}, need ${depositAmount}`)
await client.disconnect()
process.exit(1)
}
} catch (error) {
if (error.data?.error === 'entryNotFound') {
console.error(`Error: The depositor doesn't hold any assets with ID: ${assetMPTIssuanceId}`)
} else {
console.error(`Error checking MPT: ${error}`)
}
await client.disconnect()
process.exit(1)
}
// Prepare VaultDeposit transaction ----------------------
console.log(`\n=== VaultDeposit transaction ===`)
const vaultDepositTx = {
TransactionType: "VaultDeposit",
Account: depositor.address,
VaultID: vaultID,
Amount: {
mpt_issuance_id: assetMPTIssuanceId,
value: depositAmount.toString()
}
}
// Validate the transaction structure before submitting
xrpl.validate(vaultDepositTx)
console.log(JSON.stringify(vaultDepositTx, null, 2))
// Submit VaultDeposit transaction ----------------------
console.log("\n=== Submitting VaultDeposit transaction... ===")
const depositResult = await client.submitAndWait(vaultDepositTx, {
wallet: depositor,
autofill: true,
})
if (depositResult.result.meta.TransactionResult !== "tesSUCCESS") {
const result_code = depositResult.result.meta.TransactionResult
console.error("Error: Unable to deposit:", result_code)
await client.disconnect()
process.exit(1)
}
console.log("Deposit successful!")
// Extract vault state from transaction metadata ----------------------
console.log("\n=== Vault state after deposit ===")
const affectedNodes = depositResult.result.meta.AffectedNodes
const vaultNode = affectedNodes.find(
(node) => {
return (
node.ModifiedNode &&
node.ModifiedNode.LedgerEntryType === "Vault" &&
node.ModifiedNode.LedgerIndex === vaultID
)
}
)
if (vaultNode) {
const vaultFields = vaultNode.ModifiedNode.FinalFields
console.log(` - Total vault value: ${vaultFields.AssetsTotal}`)
console.log(` - Available assets: ${vaultFields.AssetsAvailable}`)
}
// Get the depositor's share balance ----------------------
console.log("\n=== Depositor's share balance ==")
const depositorShareNode = affectedNodes.find((node) => {
const shareNode = node.ModifiedNode || node.CreatedNode
const fields = shareNode?.FinalFields || shareNode?.NewFields
return (
shareNode &&
shareNode.LedgerEntryType === "MPToken" &&
fields?.Account === depositor.address &&
fields?.MPTokenIssuanceID === shareMPTIssuanceId
)
})
if (depositorShareNode) {
const shareNode = depositorShareNode.ModifiedNode || depositorShareNode.CreatedNode
const shareFields = shareNode.FinalFields || shareNode.NewFields
console.log(`Shares held: ${shareFields.MPTAmount}`)
}
await client.disconnect()

View File

@@ -1,8 +0,0 @@
{
"name": "vault-examples",
"description": "Example code for creating and managing vaults",
"dependencies": {
"xrpl": "^4.5.0"
},
"type": "module"
}

View File

@@ -1,274 +0,0 @@
import xrpl from 'xrpl'
import fs from 'fs'
// Setup script for vault tutorials
process.stdout.write('Setting up tutorial: 0/5\r')
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
await client.connect()
// Create and fund all wallets
const [
{ wallet: mptIssuer },
{ wallet: domainOwner },
{ wallet: depositor },
{ wallet: vaultOwner }
] = await Promise.all([
client.fundWallet(),
client.fundWallet(),
client.fundWallet(),
client.fundWallet()
])
// Step 1: Create MPT issuance, permissioned domain, and credentials in parallel
process.stdout.write('Setting up tutorial: 1/5\r')
const credType = 'VaultAccess'
const [mptCreateResult] = await Promise.all([
client.submitAndWait(
{
TransactionType: "MPTokenIssuanceCreate",
Account: mptIssuer.address,
Flags:
xrpl.MPTokenIssuanceCreateFlags.tfMPTCanTransfer |
xrpl.MPTokenIssuanceCreateFlags.tfMPTCanLock,
AssetScale: 2,
TransferFee: 0,
MaximumAmount: "1000000000000",
MPTokenMetadata: xrpl.encodeMPTokenMetadata({
ticker: "USTST",
name: "USTST Stablecoin",
desc: "A test stablecoin token",
icon: "example.org/ustst-icon.png",
asset_class: "rwa",
asset_subclass: "stablecoin",
issuer_name: "Test Stablecoin Inc",
uris: [
{
uri: "example.org/ustst",
category: "website",
title: "USTST Official Website",
},
{
uri: "example.org/ustst/reserves",
category: "attestation",
title: "Reserve Attestation Reports",
},
{
uri: "example.org/ustst/docs",
category: "docs",
title: "USTST Documentation",
},
],
additional_info: {
backing: "USD",
reserve_ratio: "1:1",
},
}),
},
{ wallet: mptIssuer, autofill: true },
),
client.submitAndWait(
{
TransactionType: "Batch",
Account: domainOwner.address,
Flags: xrpl.BatchFlags.tfAllOrNothing,
RawTransactions: [
{
RawTransaction: {
TransactionType: "PermissionedDomainSet",
Account: domainOwner.address,
AcceptedCredentials: [
{
Credential: {
Issuer: domainOwner.address,
CredentialType: xrpl.convertStringToHex(credType),
},
},
],
Flags: xrpl.GlobalFlags.tfInnerBatchTxn,
},
},
{
RawTransaction: {
TransactionType: "CredentialCreate",
Account: domainOwner.address,
Subject: depositor.address,
CredentialType: xrpl.convertStringToHex(credType),
Flags: xrpl.GlobalFlags.tfInnerBatchTxn,
},
},
],
},
{ wallet: domainOwner, autofill: true },
),
]);
const mptIssuanceId = mptCreateResult.result.meta.mpt_issuance_id
// Get domain ID
const domainOwnerObjects = await client.request({
command: 'account_objects',
account: domainOwner.address,
ledger_index: 'validated'
})
const domainId = domainOwnerObjects.result.account_objects.find(
(node) => node.LedgerEntryType === 'PermissionedDomain'
).index
// Step 2: Depositor accepts credential, authorizes MPT, and creates vault in parallel
process.stdout.write('Setting up tutorial: 2/5\r')
const [, vaultCreateResult] = await Promise.all([
client.submitAndWait(
{
TransactionType: 'Batch',
Account: depositor.address,
Flags: xrpl.BatchFlags.tfAllOrNothing,
RawTransactions: [
{
RawTransaction: {
TransactionType: 'CredentialAccept',
Account: depositor.address,
Issuer: domainOwner.address,
CredentialType: xrpl.convertStringToHex(credType),
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
},
{
RawTransaction: {
TransactionType: 'MPTokenAuthorize',
Account: depositor.address,
MPTokenIssuanceID: mptIssuanceId,
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
}
]
},
{ wallet: depositor, autofill: true }
),
client.submitAndWait(
{
TransactionType: 'VaultCreate',
Account: vaultOwner.address,
Asset: {
mpt_issuance_id: mptIssuanceId
},
Flags: xrpl.VaultCreateFlags.tfVaultPrivate,
DomainID: domainId,
Data: xrpl.convertStringToHex(
JSON.stringify({ n: "LATAM Fund II", w: "examplefund.com" })
),
MPTokenMetadata: xrpl.encodeMPTokenMetadata({
ticker: 'SHARE1',
name: 'Vault Shares',
desc: 'Proportional ownership shares of the vault',
icon: 'example.com/vault-shares-icon.png',
asset_class: 'defi',
issuer_name: 'Vault Owner',
uris: [
{
uri: 'example.com/asset',
category: 'website',
title: 'Asset Website'
},
{
uri: 'example.com/docs',
category: 'docs',
title: 'Docs'
}
],
additional_info: {
example_info: 'test'
}
}),
AssetsMaximum: '0',
WithdrawalPolicy: xrpl.VaultWithdrawalPolicy.vaultStrategyFirstComeFirstServe
},
{ wallet: vaultOwner, autofill: true }
)
])
const vaultNode = vaultCreateResult.result.meta.AffectedNodes.find(
(node) => node.CreatedNode?.LedgerEntryType === 'Vault'
)
const vaultID = vaultNode.CreatedNode.LedgerIndex
const vaultShareMPTIssuanceId = vaultNode.CreatedNode.NewFields.ShareMPTID
// Step 3: Issuer sends payment to depositor
process.stdout.write('Setting up tutorial: 3/5\r')
const paymentResult = await client.submitAndWait(
{
TransactionType: 'Payment',
Account: mptIssuer.address,
Destination: depositor.address,
Amount: {
mpt_issuance_id: mptIssuanceId,
value: '10000'
}
},
{ wallet: mptIssuer, autofill: true }
)
if (paymentResult.result.meta.TransactionResult !== 'tesSUCCESS') {
console.error('\nPayment failed:', paymentResult.result.meta.TransactionResult)
await client.disconnect()
process.exit(1)
}
// Step 4: Make an initial deposit so withdraw example has shares to work with
process.stdout.write('Setting up tutorial: 4/5\r')
const initialDepositResult = await client.submitAndWait(
{
TransactionType: 'VaultDeposit',
Account: depositor.address,
VaultID: vaultID,
Amount: {
mpt_issuance_id: mptIssuanceId,
value: '1000'
}
},
{ wallet: depositor, autofill: true }
)
if (initialDepositResult.result.meta.TransactionResult !== 'tesSUCCESS') {
console.error('\nInitial deposit failed:', initialDepositResult.result.meta.TransactionResult)
await client.disconnect()
process.exit(1)
}
// Step 5: Save setup data to file
process.stdout.write('Setting up tutorial: 5/5\r')
const setupData = {
mptIssuer: {
address: mptIssuer.address,
seed: mptIssuer.seed
},
mptIssuanceId,
domainOwner: {
address: domainOwner.address,
seed: domainOwner.seed
},
domainId,
credentialType: credType,
depositor: {
address: depositor.address,
seed: depositor.seed
},
vaultOwner: {
address: vaultOwner.address,
seed: vaultOwner.seed
},
vaultID,
vaultShareMPTIssuanceId
}
fs.writeFileSync('vaultSetup.json', JSON.stringify(setupData, null, 2))
process.stdout.write('Setting up tutorial: Complete!\n')
await client.disconnect()

View File

@@ -1,161 +0,0 @@
import xrpl from "xrpl"
import { execSync } from "child_process"
import fs from "fs"
// Auto-run setup if needed
if (!fs.existsSync("vaultSetup.json")) {
console.log(`\n=== Vault setup data doesn't exist. Running setup script... ===\n`)
execSync("node vaultSetup.js", { stdio: "inherit" })
}
// Load setup data
const setupData = JSON.parse(fs.readFileSync("vaultSetup.json", "utf8"))
// Connect to the network
const client = new xrpl.Client("wss://s.devnet.rippletest.net:51233")
await client.connect()
// You can replace these values with your own
const depositor = xrpl.Wallet.fromSeed(setupData.depositor.seed)
const vaultID = setupData.vaultID
const assetMPTIssuanceId = setupData.mptIssuanceId
const shareMPTIssuanceId = setupData.vaultShareMPTIssuanceId
console.log(`Depositor address: ${depositor.address}`)
console.log(`Vault ID: ${vaultID}`)
console.log(`Asset MPT issuance ID: ${assetMPTIssuanceId}`)
console.log(`Vault share MPT issuance ID: ${shareMPTIssuanceId}`)
const withdrawAmount = "1"
// Get initial vault state ----------------------
console.log("\n=== Getting initial vault state... ===")
const initialVaultInfo = await client.request({
command: "vault_info",
vault_id: vaultID,
ledger_index: "validated"
})
console.log(`Initial vault state:`)
console.log(` Assets Total: ${initialVaultInfo.result.vault.AssetsTotal}`)
console.log(` Assets Available: ${initialVaultInfo.result.vault.AssetsAvailable}`)
// Check depositor's share balance ----------------------
console.log("\n=== Checking depositor's share balance... ===")
try {
const shareBalanceResult = await client.request({
command: "ledger_entry",
mptoken: {
mpt_issuance_id: shareMPTIssuanceId,
account: depositor.address
},
ledger_index: "validated"
})
const shareBalance = shareBalanceResult.result.node?.MPTAmount
console.log(`Shares held: ${shareBalance}`)
} catch (error) {
if (error.data?.error === 'entryNotFound') {
console.error(`Error: The depositor doesn't hold any vault shares with ID: ${shareMPTIssuanceId}.`)
} else {
console.error(`Error checking MPT: ${error}`)
}
await client.disconnect()
process.exit(1)
}
// Prepare VaultWithdraw transaction ----------------------
console.log(`\n=== Preparing VaultWithdraw transaction ===`)
const vaultWithdrawTx = {
TransactionType: "VaultWithdraw",
Account: depositor.address,
VaultID: vaultID,
Amount: {
mpt_issuance_id: assetMPTIssuanceId,
value: withdrawAmount
},
// Optional: Add Destination field to send assets to a different account
// Destination: "rGg4tHPRGJfewwJkd8immCFx9uSo2GgcoY"
}
// Validate the transaction structure before submitting
xrpl.validate(vaultWithdrawTx)
console.log(JSON.stringify(vaultWithdrawTx, null, 2))
// Submit VaultWithdraw transaction ----------------------
console.log("\n=== Submitting VaultWithdraw transaction... ===")
const withdrawResult = await client.submitAndWait(vaultWithdrawTx, {
wallet: depositor,
autofill: true,
})
if (withdrawResult.result.meta.TransactionResult !== "tesSUCCESS") {
const result_code = withdrawResult.result.meta.TransactionResult
console.error("Error: Unable to withdraw from vault:", result_code)
await client.disconnect()
process.exit(1)
}
console.log("Withdrawal successful!")
// Extract vault state from transaction metadata ----------------------
console.log("\n=== Vault state after withdrawal ===")
const affectedNodes = withdrawResult.result.meta.AffectedNodes
const vaultNode = affectedNodes.find(
(node) => {
const modifiedNode = node.ModifiedNode || node.DeletedNode
return (
modifiedNode &&
modifiedNode.LedgerEntryType === "Vault" &&
modifiedNode.LedgerIndex === vaultID
)
}
)
if (vaultNode) {
if (vaultNode.DeletedNode) {
console.log(` Vault empty (all assets withdrawn)`)
} else {
const vaultFields = vaultNode.ModifiedNode.FinalFields
console.log(` Assets Total: ${vaultFields.AssetsTotal}`)
console.log(` Assets Available: ${vaultFields.AssetsAvailable}`)
}
}
// Get the depositor's share balance ----------------------
console.log("\n=== Depositor's share balance ==")
const depositorShareNode = affectedNodes.find((node) => {
const modifiedNode = node.ModifiedNode || node.DeletedNode
return (
modifiedNode &&
modifiedNode.LedgerEntryType === "MPToken" &&
modifiedNode.FinalFields?.Account === depositor.address &&
modifiedNode.FinalFields?.MPTokenIssuanceID === shareMPTIssuanceId
)
})
if (depositorShareNode) {
if (depositorShareNode.DeletedNode) {
console.log(`No more shares held (redeemed all shares)`)
} else {
const shareFields = depositorShareNode.ModifiedNode.FinalFields
console.log(`Shares held: ${shareFields.MPTAmount}`)
}
}
// Get the depositor's asset balance ----------------------
console.log("\n=== Depositor's asset balance ==")
const depositorAssetNode = affectedNodes.find((node) => {
const assetNode = node.ModifiedNode || node.CreatedNode
const fields = assetNode?.FinalFields || assetNode?.NewFields
return (
assetNode &&
assetNode.LedgerEntryType === "MPToken" &&
fields?.Account === depositor.address &&
fields?.MPTokenIssuanceID === assetMPTIssuanceId
)
})
if (depositorAssetNode) {
const assetNode = depositorAssetNode.ModifiedNode || depositorAssetNode.CreatedNode
const assetFields = assetNode.FinalFields || assetNode.NewFields
console.log(`Balance: ${assetFields.MPTAmount}`)
}
await client.disconnect()

View File

@@ -1,187 +0,0 @@
# Single Asset Vault Examples (Python)
This directory contains Python examples demonstrating how to create, deposit into, and withdraw from single asset vaults on the XRP Ledger.
## Setup
Install dependencies before running any examples:
```sh
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
---
## Create a Vault
```sh
python create_vault.py
```
The script should output the VaultCreate transaction, vault ID, and complete vault information:
```sh
Vault owner address: rfsTcqjyg7j2xfJFNbd9u8mt65yrGZvLnu
MPT issuance ID: 00385B21AF216177F319AC73F25F0FCBCDA09330D1D50D03
Permissioned domain ID: 76397457A19E093654F74848E5255E6111FDC0A2BF9FB2143F7C2C33424E1B3E
=== VaultCreate transaction ===
{
"Account": "rfsTcqjyg7j2xfJFNbd9u8mt65yrGZvLnu",
"TransactionType": "VaultCreate",
"Flags": 65536,
"SigningPubKey": "",
"Asset": {
"mpt_issuance_id": "00385B21AF216177F319AC73F25F0FCBCDA09330D1D50D03"
},
"Data": "7b226e223a20224c4154414d2046756e64204949222c202277223a20226578616d706c6566756e642e636f6d227d",
"AssetsMaximum": "0",
"MPTokenMetadata": "7B226163223A2264656669222C226169223A7B226578616D706C655F696E666F223A2274657374227D2C2264223A2250726F706F7274696F6E616C206F776E65727368697020736861726573206F6620746865207661756C742E222C2269223A226578616D706C652E636F6D2F61737365742D69636F6E2E706E67222C22696E223A22417373657420497373756572204E616D65222C226E223A225661756C7420736861726573222C2274223A22534841524531222C227573223A5B7B2263223A2277656273697465222C2274223A2241737365742057656273697465222C2275223A226578616D706C652E636F6D2F6173736574227D2C7B2263223A22646F6373222C2274223A22446F6373222C2275223A226578616D706C652E636F6D2F646F6373227D5D7D",
"DomainID": "76397457A19E093654F74848E5255E6111FDC0A2BF9FB2143F7C2C33424E1B3E",
"WithdrawalPolicy": 1
}
=== Submitting VaultCreate transaction... ===
Vault created successfully!
Vault ID: 3E5BB3E4789603CC20D7A874ECBA36B74188F1B991EC9199DFA129FDB44D846D
Vault pseudo-account address: rPgYFS3qFrUYQ3qWpF9RLKc9ECkGhgADtm
Share MPT issuance ID: 00000001F8CD8CC81FFDDC9887627F42390E85DB32D44D0E
=== Getting vault_info... ===
{
"ledger_hash": "5851C21E353DEDEC5C6CC285E1E9835C378DCBBE5BA69CF33124DAC7EE5A08AD",
"ledger_index": 3693379,
"validated": true,
"vault": {
"Account": "rPgYFS3qFrUYQ3qWpF9RLKc9ECkGhgADtm",
"Asset": {
"mpt_issuance_id": "00385B21AF216177F319AC73F25F0FCBCDA09330D1D50D03"
},
"Data": "7B226E223A20224C4154414D2046756E64204949222C202277223A20226578616D706C6566756E642E636F6D227D",
"Flags": 65536,
"LedgerEntryType": "Vault",
"Owner": "rfsTcqjyg7j2xfJFNbd9u8mt65yrGZvLnu",
"OwnerNode": "0",
"PreviousTxnID": "4B29E4DBA09CBDCAF591792ACFFB5F8717AD230185207C10F10B2A405FB2D576",
"PreviousTxnLgrSeq": 3693379,
"Sequence": 3693375,
"ShareMPTID": "00000001F8CD8CC81FFDDC9887627F42390E85DB32D44D0E",
"WithdrawalPolicy": 1,
"index": "3E5BB3E4789603CC20D7A874ECBA36B74188F1B991EC9199DFA129FDB44D846D",
"shares": {
"DomainID": "76397457A19E093654F74848E5255E6111FDC0A2BF9FB2143F7C2C33424E1B3E",
"Flags": 60,
"Issuer": "rPgYFS3qFrUYQ3qWpF9RLKc9ECkGhgADtm",
"LedgerEntryType": "MPTokenIssuance",
"MPTokenMetadata": "7B226163223A2264656669222C226169223A7B226578616D706C655F696E666F223A2274657374227D2C2264223A2250726F706F7274696F6E616C206F776E65727368697020736861726573206F6620746865207661756C742E222C2269223A226578616D706C652E636F6D2F61737365742D69636F6E2E706E67222C22696E223A22417373657420497373756572204E616D65222C226E223A225661756C7420736861726573222C2274223A22534841524531222C227573223A5B7B2263223A2277656273697465222C2274223A2241737365742057656273697465222C2275223A226578616D706C652E636F6D2F6173736574227D2C7B2263223A22646F6373222C2274223A22446F6373222C2275223A226578616D706C652E636F6D2F646F6373227D5D7D",
"OutstandingAmount": "0",
"OwnerNode": "0",
"PreviousTxnID": "4B29E4DBA09CBDCAF591792ACFFB5F8717AD230185207C10F10B2A405FB2D576",
"PreviousTxnLgrSeq": 3693379,
"Sequence": 1,
"index": "EAD6924CB5DDA61CC5B85A6776A32E460FBFB0C34F5076A6A52005459B38043D",
"mpt_issuance_id": "00000001F8CD8CC81FFDDC9887627F42390E85DB32D44D0E"
}
}
}
```
---
## Deposit into a Vault
```sh
python deposit.py
```
The script should output the vault state before and after the deposit, along with the depositor's share balance:
```sh
Depositor address: r4pfiPR5y4GTbajHXzUS29KBDHUdxR8kCK
Vault ID: 9966AF609568AFFCB3AEDEAC340B6AABB23C0483F013E186E83AF27EDA82C925
Asset MPT issuance ID: 00385B21AF216177F319AC73F25F0FCBCDA09330D1D50D03
Vault share MPT issuance ID: 00000001890BF384C217368D89BBB82B814B94B2597702B1
=== Getting initial vault state... ===
- Total vault value: 1000
- Available assets: 1000
=== Checking depositor's balance... ===
Balance: 9000
=== VaultDeposit transaction ===
{
"Account": "r4pfiPR5y4GTbajHXzUS29KBDHUdxR8kCK",
"TransactionType": "VaultDeposit",
"SigningPubKey": "",
"VaultID": "9966AF609568AFFCB3AEDEAC340B6AABB23C0483F013E186E83AF27EDA82C925",
"Amount": {
"mpt_issuance_id": "00385B21AF216177F319AC73F25F0FCBCDA09330D1D50D03",
"value": "1"
}
}
=== Submitting VaultDeposit transaction... ===
Deposit successful!
=== Vault state after deposit ===
- Total vault value: 1001
- Available assets: 1001
=== Depositor's share balance ===
Shares held: 1001
```
---
## Withdraw from a Vault
```sh
python withdraw.py
```
The script should output the vault state before and after the withdrawal, along with updated share and asset balances:
```sh
Depositor address: r4pfiPR5y4GTbajHXzUS29KBDHUdxR8kCK
Vault ID: 9966AF609568AFFCB3AEDEAC340B6AABB23C0483F013E186E83AF27EDA82C925
Asset MPT issuance ID: 00385B21AF216177F319AC73F25F0FCBCDA09330D1D50D03
Vault share MPT issuance ID: 00000001890BF384C217368D89BBB82B814B94B2597702B1
=== Getting initial vault state... ===
Initial vault state:
Assets Total: 1001
Assets Available: 1001
=== Checking depositor's share balance... ===
Shares held: 1001
=== Preparing VaultWithdraw transaction ===
{
"Account": "r4pfiPR5y4GTbajHXzUS29KBDHUdxR8kCK",
"TransactionType": "VaultWithdraw",
"SigningPubKey": "",
"VaultID": "9966AF609568AFFCB3AEDEAC340B6AABB23C0483F013E186E83AF27EDA82C925",
"Amount": {
"mpt_issuance_id": "00385B21AF216177F319AC73F25F0FCBCDA09330D1D50D03",
"value": "1"
}
}
=== Submitting VaultWithdraw transaction... ===
Withdrawal successful!
=== Vault state after withdrawal ===
Assets Total: 1000
Assets Available: 1000
=== Depositor's share balance ==
Shares held: 1000
=== Depositor's asset balance ==
Balance: 9000
```

View File

@@ -1,115 +0,0 @@
import json
import os
import subprocess
import sys
from xrpl.clients import JsonRpcClient
from xrpl.models import VaultCreate
from xrpl.models.requests import VaultInfo
from xrpl.models.transactions.vault_create import VaultCreateFlag, WithdrawalPolicy
from xrpl.transaction import submit_and_wait
from xrpl.utils import str_to_hex, encode_mptoken_metadata
from xrpl.wallet import generate_faucet_wallet
# Auto-run setup if needed
if not os.path.exists("vault_setup.json"):
print("\n=== Vault setup data doesn't exist. Running setup script... ===\n")
subprocess.run([sys.executable, "vault_setup.py"], check=True)
# Load setup data
with open("vault_setup.json", "r") as f:
setup_data = json.load(f)
# Connect to the network
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
# Create and fund vault owner account
vault_owner = generate_faucet_wallet(client)
# You can replace these values with your own
mpt_issuance_id = setup_data["mpt_issuance_id"]
domain_id = setup_data["domain_id"]
print(f"Vault owner address: {vault_owner.address}")
print(f"MPT issuance ID: {mpt_issuance_id}")
print(f"Permissioned domain ID: {domain_id}\n")
# Prepare VaultCreate transaction ----------------------
print("\n=== VaultCreate transaction ===")
vault_create_tx = VaultCreate(
account=vault_owner.address,
asset={"mpt_issuance_id": mpt_issuance_id},
flags=VaultCreateFlag.TF_VAULT_PRIVATE, # Omit TF_VAULT_PRIVATE flag for public vaults
# To make vault shares non-transferable add the TF_VAULT_SHARE_NON_TRANSFERABLE flag:
# flags=VaultCreateFlag.TF_VAULT_PRIVATE | VaultCreateFlag.TF_VAULT_SHARE_NON_TRANSFERABLE,
domain_id=domain_id, # Omit for public vaults
# Convert Vault data to a string (without excess whitespace), then string to hex.
data=str_to_hex(json.dumps(
{"n": "LATAM Fund II", "w": "examplefund.com"}
)),
# Encode JSON metadata as hex string per XLS-89 MPT Metadata Schema.
# See: https://xls.xrpl.org/xls/XLS-0089-multi-purpose-token-metadata-schema.html
mptoken_metadata=encode_mptoken_metadata({
"ticker": "SHARE1",
"name": "Vault shares",
"desc": "Proportional ownership shares of the vault.",
"icon": "example.com/asset-icon.png",
"asset_class": "defi",
"issuer_name": "Asset Issuer Name",
"uris": [
{
"uri": "example.com/asset",
"category": "website",
"title": "Asset Website",
},
{
"uri": "example.com/docs",
"category": "docs",
"title": "Docs",
},
],
"additional_info": {
"example_info": "test",
},
}),
assets_maximum="0", # No cap
withdrawal_policy=WithdrawalPolicy.VAULT_STRATEGY_FIRST_COME_FIRST_SERVE,
)
print(json.dumps(vault_create_tx.to_xrpl(), indent=2))
# Submit, sign, and wait for validation ----------------------
print("\n=== Submitting VaultCreate transaction... ===")
submit_response = submit_and_wait(vault_create_tx, client, vault_owner, autofill=True)
if submit_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = submit_response.result["meta"]["TransactionResult"]
print(f"Error: Unable to create vault: {result_code}", file=sys.stderr)
sys.exit(1)
print("Vault created successfully!")
# Extract vault information from the transaction result
affected_nodes = submit_response.result["meta"].get("AffectedNodes", [])
vault_node = next(
(node for node in affected_nodes
if "CreatedNode" in node and node["CreatedNode"].get("LedgerEntryType") == "Vault"),
None
)
if vault_node:
print(f"\nVault ID: {vault_node['CreatedNode']['LedgerIndex']}")
print(f"Vault pseudo-account address: {vault_node['CreatedNode']['NewFields']['Account']}")
print(f"Share MPT issuance ID: {vault_node['CreatedNode']['NewFields']['ShareMPTID']}")
# Call vault_info method to retrieve the vault's information
print("\n=== Getting vault_info... ===")
vault_id = vault_node["CreatedNode"]["LedgerIndex"]
vault_info_response = client.request(
VaultInfo(
vault_id=vault_id,
ledger_index="validated"
)
)
print(json.dumps(vault_info_response.result, indent=2))

View File

@@ -1,149 +0,0 @@
# IMPORTANT: This example deposits into an existing PRIVATE vault.
# The depositor account used has valid credentials in the vault's Permissioned Domain.
# Without valid credentials, the VaultDeposit transaction will fail.
# If you want to deposit into a public vault, you can replace the vault_id and share_mpt_issuance_id
# values with your own.
import json
import os
import subprocess
import sys
from xrpl.clients import JsonRpcClient
from xrpl.models import VaultDeposit
from xrpl.models.requests import VaultInfo, LedgerEntry
from xrpl.transaction import submit_and_wait
from xrpl.wallet import Wallet
# Auto-run setup if needed
if not os.path.exists("vault_setup.json"):
print("\n=== Vault setup data doesn't exist. Running setup script... ===\n")
subprocess.run([sys.executable, "vault_setup.py"], check=True)
# Load setup data
with open("vault_setup.json", "r") as f:
setup_data = json.load(f)
# Connect to the network
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
# You can replace these values with your own
depositor = Wallet.from_seed(setup_data["depositor"]["seed"])
vault_id = setup_data["vault_id"]
asset_mpt_issuance_id = setup_data["mpt_issuance_id"]
share_mpt_issuance_id = setup_data["vault_share_mpt_issuance_id"]
print(f"Depositor address: {depositor.address}")
print(f"Vault ID: {vault_id}")
print(f"Asset MPT issuance ID: {asset_mpt_issuance_id}")
print(f"Vault share MPT issuance ID: {share_mpt_issuance_id}")
deposit_amount = 1
# Get initial vault state
print("\n=== Getting initial vault state... ===")
initial_vault_info = client.request(
VaultInfo(
vault_id=vault_id,
ledger_index="validated"
)
)
print(f" - Total vault value: {initial_vault_info.result['vault']['AssetsTotal']}")
print(f" - Available assets: {initial_vault_info.result['vault']['AssetsAvailable']}")
# Check depositor's asset balance
print("\n=== Checking depositor's balance... ===")
try:
# Use ledger_entry to get specific MPT issuance balance
ledger_entry_result = client.request(
LedgerEntry(
mptoken={
"mpt_issuance_id": asset_mpt_issuance_id,
"account": depositor.address
},
ledger_index="validated"
)
)
balance = ledger_entry_result.result["node"]["MPTAmount"]
print(f"Balance: {balance}")
# Check if balance is sufficient
if int(balance) < deposit_amount:
print(f"Error: Insufficient balance! Have {balance}, need {deposit_amount}", file=sys.stderr)
sys.exit(1)
except Exception as error:
error_data = getattr(error, 'data', {})
if 'error' in error_data and error_data['error'] == 'entryNotFound':
print(f"Error: The depositor doesn't hold any assets with ID: {asset_mpt_issuance_id}", file=sys.stderr)
else:
print(f"Error checking MPT: {error}", file=sys.stderr)
sys.exit(1)
# Prepare VaultDeposit transaction
print("\n=== VaultDeposit transaction ===")
vault_deposit_tx = VaultDeposit(
account=depositor.address,
vault_id=vault_id,
amount={
"mpt_issuance_id": asset_mpt_issuance_id,
"value": str(deposit_amount)
}
)
print(json.dumps(vault_deposit_tx.to_xrpl(), indent=2))
# Submit VaultDeposit transaction
print("\n=== Submitting VaultDeposit transaction... ===")
deposit_result = submit_and_wait(vault_deposit_tx, client, depositor, autofill=True)
if deposit_result.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = deposit_result.result["meta"]["TransactionResult"]
print(f"Error: Unable to deposit: {result_code}", file=sys.stderr)
sys.exit(1)
print("Deposit successful!")
# Extract vault state from transaction metadata
print("\n=== Vault state after deposit ===")
affected_nodes = deposit_result.result["meta"]["AffectedNodes"]
vault_node = None
for node in affected_nodes:
if "ModifiedNode" in node:
modified = node["ModifiedNode"]
if modified["LedgerEntryType"] == "Vault" and modified["LedgerIndex"] == vault_id:
vault_node = node
break
if vault_node:
vault_fields = vault_node["ModifiedNode"]["FinalFields"]
print(f" - Total vault value: {vault_fields['AssetsTotal']}")
print(f" - Available assets: {vault_fields['AssetsAvailable']}")
# Get the depositor's share balance
print("\n=== Depositor's share balance ===")
depositor_share_node = None
for node in affected_nodes:
if "ModifiedNode" in node:
share_node = node["ModifiedNode"]
fields = share_node["FinalFields"]
elif "CreatedNode" in node:
share_node = node["CreatedNode"]
fields = share_node["NewFields"]
else:
continue
if (share_node["LedgerEntryType"] == "MPToken" and
fields["Account"] == depositor.address and
fields["MPTokenIssuanceID"] == share_mpt_issuance_id):
depositor_share_node = node
break
if depositor_share_node:
if "ModifiedNode" in depositor_share_node:
share_fields = depositor_share_node["ModifiedNode"]["FinalFields"]
else:
share_fields = depositor_share_node["CreatedNode"]["NewFields"]
print(f"Shares held: {share_fields['MPTAmount']}")

View File

@@ -1,2 +0,0 @@
xrpl-py==4.5.0

View File

@@ -1,273 +0,0 @@
import asyncio
import json
import sys
from xrpl.asyncio.clients import AsyncWebsocketClient
from xrpl.asyncio.transaction import submit_and_wait
from xrpl.asyncio.wallet import generate_faucet_wallet
from xrpl.models import (
Batch, BatchFlag, CredentialAccept, CredentialCreate, MPTokenAuthorize,
MPTokenIssuanceCreate, MPTokenIssuanceCreateFlag, Payment,
PermissionedDomainSet, VaultDeposit
)
from xrpl.models.requests import AccountObjects
from xrpl.models.transactions.deposit_preauth import Credential
from xrpl.models.transactions.vault_create import (
VaultCreate, VaultCreateFlag, WithdrawalPolicy
)
from xrpl.utils import encode_mptoken_metadata, str_to_hex
async def main():
# Setup script for vault tutorials
print("Setting up tutorial: 0/5", end="\r")
async with AsyncWebsocketClient("wss://s.devnet.rippletest.net:51233") as client:
# Create and fund all wallets concurrently
mpt_issuer, domain_owner, depositor, vault_owner = await asyncio.gather(
generate_faucet_wallet(client),
generate_faucet_wallet(client),
generate_faucet_wallet(client),
generate_faucet_wallet(client),
)
# Step 1: Create MPT issuance, permissioned domain, and credentials in parallel
print("Setting up tutorial: 1/5", end="\r")
cred_type = "VaultAccess"
mpt_create_result, _ = await asyncio.gather(
submit_and_wait(
MPTokenIssuanceCreate(
account=mpt_issuer.address,
flags=(
MPTokenIssuanceCreateFlag.TF_MPT_CAN_TRANSFER |
MPTokenIssuanceCreateFlag.TF_MPT_CAN_LOCK
),
asset_scale=2,
transfer_fee=0,
maximum_amount="1000000000000",
mptoken_metadata=encode_mptoken_metadata({
"ticker": "USTST",
"name": "USTST Stablecoin",
"desc": "A test stablecoin token",
"icon": "example.org/ustst-icon.png",
"asset_class": "rwa",
"asset_subclass": "stablecoin",
"issuer_name": "Test Stablecoin Inc",
"uris": [
{
"uri": "example.org/ustst",
"category": "website",
"title": "USTST Official Website",
},
{
"uri": "example.org/ustst/reserves",
"category": "attestation",
"title": "Reserve Attestation Reports",
},
{
"uri": "example.org/ustst/docs",
"category": "docs",
"title": "USTST Documentation",
},
],
"additional_info": {
"backing": "USD",
"reserve_ratio": "1:1",
},
}),
),
client,
mpt_issuer,
autofill=True
),
submit_and_wait(
Batch(
account=domain_owner.address,
flags=BatchFlag.TF_ALL_OR_NOTHING,
raw_transactions=[
PermissionedDomainSet(
account=domain_owner.address,
accepted_credentials=[
Credential(
issuer=domain_owner.address,
credential_type=str_to_hex(cred_type)
)
],
),
CredentialCreate(
account=domain_owner.address,
subject=depositor.address,
credential_type=str_to_hex(cred_type),
),
],
),
client,
domain_owner,
autofill=True
)
)
mpt_issuance_id = mpt_create_result.result["meta"]["mpt_issuance_id"]
# Get domain ID
domain_owner_objects = await client.request(AccountObjects(
account=domain_owner.address,
ledger_index="validated",
))
domain_id = next(
node["index"]
for node in domain_owner_objects.result["account_objects"]
if node["LedgerEntryType"] == "PermissionedDomain"
)
# Step 2: Depositor accepts credential, authorizes MPT, and creates vault in parallel
print("Setting up tutorial: 2/5", end="\r")
_, vault_create_result = await asyncio.gather(
submit_and_wait(
Batch(
account=depositor.address,
flags=BatchFlag.TF_ALL_OR_NOTHING,
raw_transactions=[
CredentialAccept(
account=depositor.address,
issuer=domain_owner.address,
credential_type=str_to_hex(cred_type),
),
MPTokenAuthorize(
account=depositor.address,
mptoken_issuance_id=mpt_issuance_id,
),
],
),
client,
depositor,
autofill=True
),
submit_and_wait(
VaultCreate(
account=vault_owner.address,
asset={"mpt_issuance_id": mpt_issuance_id},
flags=VaultCreateFlag.TF_VAULT_PRIVATE,
domain_id=domain_id,
data=str_to_hex(json.dumps(
{"n": "LATAM Fund II", "w": "examplefund.com"}
)),
mptoken_metadata=encode_mptoken_metadata({
"ticker": "SHARE1",
"name": "Vault Shares",
"desc": "Proportional ownership shares of the vault",
"icon": "example.com/vault-shares-icon.png",
"asset_class": "defi",
"issuer_name": "Vault Owner",
"uris": [
{
"uri": "example.com/asset",
"category": "website",
"title": "Asset Website",
},
{
"uri": "example.com/docs",
"category": "docs",
"title": "Docs",
},
],
"additional_info": {
"example_info": "test",
},
}),
assets_maximum="0",
withdrawal_policy=WithdrawalPolicy.VAULT_STRATEGY_FIRST_COME_FIRST_SERVE,
),
client,
vault_owner,
autofill=True
),
)
# Extract vault_id and vault_share_mpt_issuance_id
vault_node = next(
node for node in vault_create_result.result["meta"]["AffectedNodes"]
if "CreatedNode" in node and node["CreatedNode"].get("LedgerEntryType") == "Vault"
)
vault_id = vault_node["CreatedNode"]["LedgerIndex"]
vault_share_mpt_issuance_id = vault_node["CreatedNode"]["NewFields"]["ShareMPTID"]
# Step 3: Issuer sends payment to depositor
print("Setting up tutorial: 3/5", end="\r")
payment_result = await submit_and_wait(
Payment(
account=mpt_issuer.address,
destination=depositor.address,
amount={
"mpt_issuance_id": mpt_issuance_id,
"value": "10000",
},
),
client,
mpt_issuer,
autofill=True
)
if payment_result.result["meta"]["TransactionResult"] != "tesSUCCESS":
print(f"\nPayment failed: {payment_result.result['meta']['TransactionResult']}", file=sys.stderr)
sys.exit(1)
# Step 4: Make an initial deposit so withdraw example has shares to work with
print("Setting up tutorial: 4/5", end="\r")
initial_deposit_result = await submit_and_wait(
VaultDeposit(
account=depositor.address,
vault_id=vault_id,
amount={
"mpt_issuance_id": mpt_issuance_id,
"value": "1000",
},
),
client,
depositor,
autofill=True
)
if initial_deposit_result.result["meta"]["TransactionResult"] != "tesSUCCESS":
print(f"\nInitial deposit failed: {initial_deposit_result.result['meta']['TransactionResult']}", file=sys.stderr)
sys.exit(1)
# Step 5: Save setup data to file
print("Setting up tutorial: 5/5", end="\r")
setup_data = {
"mpt_issuer": {
"address": mpt_issuer.address,
"seed": mpt_issuer.seed,
},
"mpt_issuance_id": mpt_issuance_id,
"domain_owner": {
"address": domain_owner.address,
"seed": domain_owner.seed,
},
"domain_id": domain_id,
"credential_type": cred_type,
"depositor": {
"address": depositor.address,
"seed": depositor.seed,
},
"vault_owner": {
"address": vault_owner.address,
"seed": vault_owner.seed,
},
"vault_id": vault_id,
"vault_share_mpt_issuance_id": vault_share_mpt_issuance_id,
}
with open("vault_setup.json", "w") as f:
json.dump(setup_data, f, indent=2)
print("Setting up tutorial: Complete!")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -1,164 +0,0 @@
import json
import os
import subprocess
import sys
from xrpl.clients import JsonRpcClient
from xrpl.models import VaultWithdraw
from xrpl.models.requests import VaultInfo, LedgerEntry
from xrpl.transaction import submit_and_wait
from xrpl.wallet import Wallet
# Auto-run setup if needed
if not os.path.exists("vault_setup.json"):
print("\n=== Vault setup data doesn't exist. Running setup script... ===\n")
subprocess.run([sys.executable, "vault_setup.py"], check=True)
# Load setup data
with open("vault_setup.json", "r") as f:
setup_data = json.load(f)
# Connect to the network
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
# You can replace these values with your own
depositor = Wallet.from_seed(setup_data["depositor"]["seed"])
vault_id = setup_data["vault_id"]
asset_mpt_issuance_id = setup_data["mpt_issuance_id"]
share_mpt_issuance_id = setup_data["vault_share_mpt_issuance_id"]
print(f"Depositor address: {depositor.address}")
print(f"Vault ID: {vault_id}")
print(f"Asset MPT issuance ID: {asset_mpt_issuance_id}")
print(f"Vault share MPT issuance ID: {share_mpt_issuance_id}")
withdraw_amount = 1
# Get initial vault state
print("\n=== Getting initial vault state... ===")
initial_vault_info = client.request(
VaultInfo(
vault_id=vault_id,
ledger_index="validated"
)
)
print("Initial vault state:")
print(f" Assets Total: {initial_vault_info.result['vault']['AssetsTotal']}")
print(f" Assets Available: {initial_vault_info.result['vault']['AssetsAvailable']}")
# Check depositor's share balance
print("\n=== Checking depositor's share balance... ===")
try:
share_balance_result = client.request(
LedgerEntry(
mptoken={
"mpt_issuance_id": share_mpt_issuance_id,
"account": depositor.address
},
ledger_index="validated"
)
)
share_balance = share_balance_result.result["node"]["MPTAmount"]
print(f"Shares held: {share_balance}")
except Exception as error:
error_data = getattr(error, 'data', {})
if 'error' in error_data and error_data['error'] == 'entryNotFound':
print(f"Error: The depositor doesn't hold any vault shares with ID: {share_mpt_issuance_id}.", file=sys.stderr)
else:
print(f"Error checking MPT: {error}", file=sys.stderr)
sys.exit(1)
# Prepare VaultWithdraw transaction
print("\n=== Preparing VaultWithdraw transaction ===")
vault_withdraw_tx = VaultWithdraw(
account=depositor.address,
vault_id=vault_id,
amount={
"mpt_issuance_id": asset_mpt_issuance_id,
"value": str(withdraw_amount)
}
# Optional: Add destination field to send assets to a different account
# destination="rGg4tHPRGJfewwJkd8immCFx9uSo2GgcoY"
)
print(json.dumps(vault_withdraw_tx.to_xrpl(), indent=2))
# Submit VaultWithdraw transaction
print("\n=== Submitting VaultWithdraw transaction... ===")
withdraw_result = submit_and_wait(vault_withdraw_tx, client, depositor, autofill=True)
if withdraw_result.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = withdraw_result.result["meta"]["TransactionResult"]
print(f"Error: Unable to withdraw from vault: {result_code}", file=sys.stderr)
sys.exit(1)
print("Withdrawal successful!")
# Extract vault state from transaction metadata
print("\n=== Vault state after withdrawal ===")
affected_nodes = withdraw_result.result["meta"]["AffectedNodes"]
vault_node = None
for node in affected_nodes:
if "ModifiedNode" in node or "DeletedNode" in node:
modified_node = node["ModifiedNode"] if "ModifiedNode" in node else node["DeletedNode"]
if modified_node["LedgerEntryType"] == "Vault" and modified_node["LedgerIndex"] == vault_id:
vault_node = node
break
if vault_node:
if "DeletedNode" in vault_node:
print(" Vault empty (all assets withdrawn)")
else:
vault_fields = vault_node["ModifiedNode"]["FinalFields"]
print(f" Assets Total: {vault_fields['AssetsTotal']}")
print(f" Assets Available: {vault_fields['AssetsAvailable']}")
# Get the depositor's share balance
print("\n=== Depositor's share balance ===")
depositor_share_node = None
for node in affected_nodes:
if "ModifiedNode" in node or "DeletedNode" in node:
modified_node = node["ModifiedNode"] if "ModifiedNode" in node else node["DeletedNode"]
if "FinalFields" in modified_node:
fields = modified_node["FinalFields"]
if (modified_node["LedgerEntryType"] == "MPToken" and
fields["Account"] == depositor.address and
fields["MPTokenIssuanceID"] == share_mpt_issuance_id):
depositor_share_node = node
break
if depositor_share_node:
if "DeletedNode" in depositor_share_node:
print("No more shares held (redeemed all shares)")
else:
share_fields = depositor_share_node["ModifiedNode"]["FinalFields"]
print(f"Shares held: {share_fields['MPTAmount']}")
# Get the depositor's asset balance
print("\n=== Depositor's asset balance ===")
depositor_asset_node = None
for node in affected_nodes:
if "ModifiedNode" in node:
asset_node = node["ModifiedNode"]
fields = asset_node["FinalFields"]
elif "CreatedNode" in node:
asset_node = node["CreatedNode"]
fields = asset_node["NewFields"]
else:
continue
if (asset_node["LedgerEntryType"] == "MPToken" and
fields["Account"] == depositor.address and
fields["MPTokenIssuanceID"] == asset_mpt_issuance_id):
depositor_asset_node = node
break
if depositor_asset_node:
if "ModifiedNode" in depositor_asset_node:
asset_fields = depositor_asset_node["ModifiedNode"]["FinalFields"]
else:
asset_fields = depositor_asset_node["CreatedNode"]["NewFields"]
print(f"Balance: {asset_fields['MPTAmount']}")

View File

@@ -1,10 +1,7 @@
import * as React from 'react';
import clsx from 'clsx';
import { Button } from 'shared/components/Button';
import { PageGrid, PageGridCol, PageGridRow } from 'shared/components/PageGrid/page-grid';
const showcaseBorderedCol = clsx('grid-showcase-bordered-col', 'grid-showcase-bordered-col--start');
export const frontmatter = {
seo: {
title: 'BDS Button Component Showcase',
@@ -28,9 +25,7 @@ export default function ButtonShowcase() {
</div>
<p className="col-lg-8 mx-auto mt-10">
A scalable button component following the XRPL Brand Design System. This showcase demonstrates all states,
responsive behavior, and accessibility features of the Primary button variant. Hover uses a bottom-to-top fill
animation on a pseudo-element; the pressed state keeps the enabled background color (Green 300 on the green
theme).
responsive behavior, and accessibility features of the Primary button variant.
</p>
</section>
@@ -65,7 +60,7 @@ export default function ButtonShowcase() {
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Enabled State</h5>
<p className="mb-4 text-muted">Default state when button is ready for interaction.</p>
<Button variant="primary" onClick={handleClick}>
@@ -74,7 +69,7 @@ export default function ButtonShowcase() {
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Disabled State</h5>
<p className="mb-4 text-muted">Button cannot be interacted with.</p>
<Button variant="primary" disabled>
@@ -87,9 +82,7 @@ export default function ButtonShowcase() {
<div className="mt-10">
<h5 className="mb-4">Hover & Focus States</h5>
<p className="mb-4 text-muted">
Hover to see the fill animate to Green 200; the arrow line shortens while the chevron stays visible. Tab for
focus (same fill as hover plus a 2px focus ring). While pressed, the fill stays on Green 300 (enabled) for
the green theme.
Hover over the buttons below or use Tab to focus them. Notice the background color change and icon swap.
</p>
<div className="d-flex flex-wrap">
<Button variant="primary" onClick={handleClick} className="me-4 mb-4">
@@ -132,8 +125,8 @@ export default function ButtonShowcase() {
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }} className={showcaseBorderedCol}>
<div className="grid-showcase-demo">
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Enabled State</h5>
<p className="mb-4 text-muted">Black background with white text.</p>
<Button variant="primary" color="black" onClick={handleClick}>
@@ -141,8 +134,8 @@ export default function ButtonShowcase() {
</Button>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }} className={showcaseBorderedCol}>
<div className="grid-showcase-demo">
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Disabled State</h5>
<p className="mb-4 text-muted">Same disabled styling as green variant.</p>
<Button variant="primary" color="black" disabled>
@@ -155,8 +148,7 @@ export default function ButtonShowcase() {
<div className="mt-10">
<h5 className="mb-4">Hover & Focus States</h5>
<p className="mb-4 text-muted">
Hover or focus uses a lift fill to <strong>#434343</strong> (not a translucent black). Pressed/active returns
the fill to the enabled neutral black background.
Hover over the buttons or use Tab to focus them. Notice the background darkens slightly on hover.
</p>
<div className="d-flex flex-wrap">
<Button variant="primary" color="black" onClick={handleClick} className="me-4 mb-4">
@@ -290,27 +282,23 @@ export default function ButtonShowcase() {
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }} className={showcaseBorderedCol}>
<div className="grid-showcase-demo">
<h5 className="mb-4">Keyboard Navigation</h5>
<ul>
<li>Tab to focus buttons</li>
<li>Enter or Space to activate</li>
<li>Focus indicator: 2px black border</li>
<li>Disabled buttons are not focusable</li>
</ul>
</div>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Keyboard Navigation</h5>
<ul>
<li>Tab to focus buttons</li>
<li>Enter or Space to activate</li>
<li>Focus indicator: 2px black border</li>
<li>Disabled buttons are not focusable</li>
</ul>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }} className={showcaseBorderedCol}>
<div className="grid-showcase-demo">
<h5 className="mb-4">Screen Reader Support</h5>
<ul>
<li>Button labels are announced</li>
<li>Disabled: native <code>&lt;button&gt;</code> uses <code>disabled</code> and <code>aria-disabled</code>; if <code>href</code> was passed, the component still renders a <code>&lt;button&gt;</code> when disabled</li>
<li>Icons are hidden from screen readers (aria-hidden)</li>
<li>Semantic button element used</li>
</ul>
</div>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Screen Reader Support</h5>
<ul>
<li>Button labels are announced</li>
<li>Disabled state communicated via aria-disabled</li>
<li>Icons are hidden from screen readers (aria-hidden)</li>
<li>Semantic button element used</li>
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
@@ -324,10 +312,8 @@ export default function ButtonShowcase() {
<strong>Hover:</strong> Black text (#141414) on Green 200 (#70EE97) = 10.23:1 (AAA)
</li>
<li>
<strong>Disabled (Light Mode):</strong> Gray 500 (#72777E) on Gray 200 (#E6EAF0) = 3.24:1 (acceptable for disabled state)
</li>
<li>
<strong>Disabled (Dark Mode):</strong> Neutral 300 text (#CAD4DF) on Neutral 500 background (#72777E) styled for disabled controls
<strong>Disabled:</strong> Gray 500 (#838386) on Gray 200 (#E0E0E1) = 2.12:1 (acceptable for disabled
state)
</li>
</ul>
</div>
@@ -339,8 +325,8 @@ export default function ButtonShowcase() {
<h2 className="h4 mb-8">Code Examples</h2>
<h6 className="eyebrow mb-3">Implementation</h6>
</div>
<div className={clsx('mb-6', 'br-4', 'grid-showcase-code-block')}>
<pre>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#1e1e1e', color: '#d4d4d4' }}>
<pre style={{ margin: 0, overflow: 'auto' }}>
<code>{`import { Button } from 'shared/components/Button';
// Basic usage (green theme - default)
@@ -389,127 +375,112 @@ export default function ButtonShowcase() {
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }} className={showcaseBorderedCol}>
<div className="grid-showcase-demo">
<h5 className="mb-4">Typography</h5>
<ul>
<li>Font: Booton, sans-serif</li>
<li>Size: 16px</li>
<li>Weight: 400</li>
<li>Line Height: 23.2px</li>
<li>Letter Spacing: 0px</li>
</ul>
</div>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Typography</h5>
<ul>
<li>Font: Booton, sans-serif</li>
<li>Size: 16px</li>
<li>Weight: 400</li>
<li>Line Height: 23.2px</li>
<li>Letter Spacing: 0px</li>
</ul>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }} className={showcaseBorderedCol}>
<div className="grid-showcase-demo">
<h5 className="mb-4">Spacing & Layout</h5>
<ul>
<li>Border Radius: 100px (fully rounded)</li>
<li>Icon Size: 15px × 14px</li>
<li>Icon Gap: 16px (default), 22px (hover/focus desktop), 21px (hover/focus mobile)</li>
<li>Min Height: 40px (touch target)</li>
</ul>
</div>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Spacing & Layout</h5>
<ul>
<li>Border Radius: 100px (fully rounded)</li>
<li>Icon Size: 15px × 14px</li>
<li>Icon Gap: 16px (default), 22px (hover/focus desktop), 21px (hover/focus mobile)</li>
<li>Min Height: 40px (touch target)</li>
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">State Colors - Green Theme</h5>
<div className={clsx('grid-showcase-breakpoints-table', 'grid-showcase-breakpoints-table--4col')}>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__header">
<div className="grid-showcase-breakpoints-table__cell">State</div>
<div className="grid-showcase-breakpoints-table__cell">Text Color</div>
<div className="grid-showcase-breakpoints-table__cell">Background Color</div>
<div className="grid-showcase-breakpoints-table__cell">Border</div>
<div style={{ width: '100%', backgroundColor: '#FFFFFF' }}>
{/* Header */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '2px solid #E0E0E1' }}>
<div style={{ padding: '12px', fontWeight: 'bold' }}>State</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Text Color</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Background Color</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Border</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Enabled</div>
<div className="grid-showcase-breakpoints-table__cell">#141414 (Neutral Black)</div>
<div className="grid-showcase-breakpoints-table__cell">#21E46B (Green 300)</div>
<div className="grid-showcase-breakpoints-table__cell">None</div>
{/* Rows */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Enabled</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>#21E46B (Green 300)</div>
<div style={{ padding: '12px' }}>None</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Hover</div>
<div className="grid-showcase-breakpoints-table__cell">#141414 (Neutral Black)</div>
<div className="grid-showcase-breakpoints-table__cell">#70EE97 (Green 200) via animated fill</div>
<div className="grid-showcase-breakpoints-table__cell">None</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Hover</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>#70EE97 (Green 200)</div>
<div style={{ padding: '12px' }}>None</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Focus</div>
<div className="grid-showcase-breakpoints-table__cell">#141414 (Neutral Black)</div>
<div className="grid-showcase-breakpoints-table__cell">#70EE97 (Green 200) via fill</div>
<div className="grid-showcase-breakpoints-table__cell">2px solid #141414</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Focus</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>#70EE97 (Green 200)</div>
<div style={{ padding: '12px' }}>2px solid #141414</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Active (pressed)</div>
<div className="grid-showcase-breakpoints-table__cell">#141414 (Neutral Black)</div>
<div className="grid-showcase-breakpoints-table__cell">#21E46B (Green 300) same as enabled</div>
<div className="grid-showcase-breakpoints-table__cell">None</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Active</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>#21E46B (Green 300)</div>
<div style={{ padding: '12px' }}>None</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Disabled (Light)</div>
<div className="grid-showcase-breakpoints-table__cell">#72777E (Gray 500)</div>
<div className="grid-showcase-breakpoints-table__cell">#E6EAF0 (Gray 200)</div>
<div className="grid-showcase-breakpoints-table__cell">None</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Disabled (Dark)</div>
<div className="grid-showcase-breakpoints-table__cell">#72777E (Gray 500)</div>
<div className="grid-showcase-breakpoints-table__cell">#CAD4DF (Gray 300)</div>
<div className="grid-showcase-breakpoints-table__cell">None</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr' }}>
<div style={{ padding: '12px' }}>Disabled</div>
<div style={{ padding: '12px' }}>#838386 (Gray 500)</div>
<div style={{ padding: '12px' }}>#E0E0E1 (Gray 200)</div>
<div style={{ padding: '12px' }}>None</div>
</div>
</div>
</div>
<div className="mt-10">
<h5 className="mb-4">State Colors - Black Theme (Light Mode)</h5>
<div className={clsx('grid-showcase-breakpoints-table', 'grid-showcase-breakpoints-table--4col')}>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__header">
<div className="grid-showcase-breakpoints-table__cell">State</div>
<div className="grid-showcase-breakpoints-table__cell">Text Color</div>
<div className="grid-showcase-breakpoints-table__cell">Background Color</div>
<div className="grid-showcase-breakpoints-table__cell">Border</div>
<h5 className="mb-4">State Colors - Black Theme</h5>
<div style={{ width: '100%', backgroundColor: '#FFFFFF' }}>
{/* Header */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '2px solid #E0E0E1' }}>
<div style={{ padding: '12px', fontWeight: 'bold' }}>State</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Text Color</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Background Color</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Border</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Enabled</div>
<div className="grid-showcase-breakpoints-table__cell">#FFFFFF (White)</div>
<div className="grid-showcase-breakpoints-table__cell">#141414 (Neutral Black)</div>
<div className="grid-showcase-breakpoints-table__cell">None</div>
{/* Rows */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Enabled</div>
<div style={{ padding: '12px' }}>#FFFFFF (White)</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>None</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Hover</div>
<div className="grid-showcase-breakpoints-table__cell">#FFFFFF (White)</div>
<div className="grid-showcase-breakpoints-table__cell">#434343 (hover fill)</div>
<div className="grid-showcase-breakpoints-table__cell">None</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Hover</div>
<div style={{ padding: '12px' }}>#FFFFFF (White)</div>
<div style={{ padding: '12px' }}>rgba(20, 20, 20, 0.8) (80% Black)</div>
<div style={{ padding: '12px' }}>None</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Focus</div>
<div className="grid-showcase-breakpoints-table__cell">#FFFFFF (White)</div>
<div className="grid-showcase-breakpoints-table__cell">#434343 (hover fill)</div>
<div className="grid-showcase-breakpoints-table__cell">2px solid #141414</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Focus</div>
<div style={{ padding: '12px' }}>#FFFFFF (White)</div>
<div style={{ padding: '12px' }}>rgba(20, 20, 20, 0.8) (80% Black)</div>
<div style={{ padding: '12px' }}>2px solid #141414</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Active (pressed)</div>
<div className="grid-showcase-breakpoints-table__cell">#FFFFFF (White)</div>
<div className="grid-showcase-breakpoints-table__cell">#141414 (Neutral Black) enabled fill</div>
<div className="grid-showcase-breakpoints-table__cell">None</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Active</div>
<div style={{ padding: '12px' }}>#FFFFFF (White)</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>None</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Disabled (Light)</div>
<div className="grid-showcase-breakpoints-table__cell">#72777E (Gray 500)</div>
<div className="grid-showcase-breakpoints-table__cell">#E6EAF0 (Gray 200)</div>
<div className="grid-showcase-breakpoints-table__cell">None</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Disabled (Dark)</div>
<div className="grid-showcase-breakpoints-table__cell">#72777E (Gray 500)</div>
<div className="grid-showcase-breakpoints-table__cell">#CAD4DF (Gray 300)</div>
<div className="grid-showcase-breakpoints-table__cell">None</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr' }}>
<div style={{ padding: '12px' }}>Disabled</div>
<div style={{ padding: '12px' }}>#838386 (Gray 500)</div>
<div style={{ padding: '12px' }}>#E0E0E1 (Gray 200)</div>
<div style={{ padding: '12px' }}>None</div>
</div>
</div>
<p className="mt-4 text-muted">
<strong>Note:</strong> In dark mode, the <code>color=&quot;black&quot;</code> primary uses the same green fills as the green primary (black text on Green 300 / Green 200 hover); focus ring is white. The black-theme table above is for light mode only.
</p>
</div>
</section>
</div>

View File

@@ -1,5 +1,4 @@
import * as React from 'react';
import clsx from 'clsx';
import { Button } from 'shared/components/Button';
import { PageGrid, PageGridCol, PageGridRow } from 'shared/components/PageGrid/page-grid';
@@ -25,11 +24,9 @@ export default function ButtonShowcaseSecondary() {
<h6 className="eyebrow mb-3">Brand Design System</h6>
</div>
<p className="col-lg-8 mx-auto mt-10">
The Secondary button is an outline-style button used for secondary actions. It uses a transparent background,
a 2px border, and a bottom-to-top hover fill on a pseudo-element. On the green theme, pressed/active returns
text and border to Green 400 (enabled) while keeping hover padding/gap so the arrow animation is unchanged.
The black theme uses <strong>#434343</strong> for pressed text and border only; default and hover stay neutral
black and 15% black fill.
The Secondary button is an outline-style button used for secondary actions. It features a transparent
background with a green stroke/border, providing visual hierarchy below the Primary button while maintaining
brand consistency.
</p>
</section>
@@ -85,7 +82,7 @@ export default function ButtonShowcaseSecondary() {
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Enabled State</h5>
<p className="mb-4 text-muted">Default outline style with green border and text.</p>
<Button variant="secondary" onClick={handleClick}>
@@ -94,7 +91,7 @@ export default function ButtonShowcaseSecondary() {
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Disabled State</h5>
<p className="mb-4 text-muted">Gray border and text indicate non-interactive state.</p>
<Button variant="secondary" disabled>
@@ -107,8 +104,8 @@ export default function ButtonShowcaseSecondary() {
<div className="mt-10">
<h5 className="mb-4">Hover & Focus States</h5>
<p className="mb-4 text-muted">
Hover or focus: Green 500 text and border, Green 100 fill, arrow animation. Pressed (mouse down): Green 400
text and border (enabled colors) with no layout jump.
Hover over the buttons or use Tab to focus them. Notice the light green background fill and darker green
border on hover/focus.
</p>
<div className="d-flex flex-wrap">
<Button variant="secondary" onClick={handleClick} className="me-4 mb-4">
@@ -152,7 +149,7 @@ export default function ButtonShowcaseSecondary() {
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Enabled State</h5>
<p className="mb-4 text-muted">Black border and text with transparent background.</p>
<Button variant="secondary" color="black" onClick={handleClick}>
@@ -161,7 +158,7 @@ export default function ButtonShowcaseSecondary() {
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Disabled State</h5>
<p className="mb-4 text-muted">Same disabled styling as green variant.</p>
<Button variant="secondary" color="black" disabled>
@@ -174,8 +171,7 @@ export default function ButtonShowcaseSecondary() {
<div className="mt-10">
<h5 className="mb-4">Hover & Focus States</h5>
<p className="mb-4 text-muted">
Hover or focus: neutral black text/border with a 15% black fill. Pressed: text and border use{' '}
<strong>#434343</strong> (not the default #141414).
Hover over the buttons or use Tab to focus them. Notice the subtle black background fill on hover.
</p>
<div className="d-flex flex-wrap">
<Button variant="secondary" color="black" onClick={handleClick} className="me-4 mb-4">
@@ -291,7 +287,7 @@ export default function ButtonShowcaseSecondary() {
<ul>
<li>Tab to focus buttons</li>
<li>Enter or Space to activate</li>
<li>Focus: 2px neutral black outline; on secondary green, border is removed where the outline applies and padding compensates</li>
<li>Focus indicator: 2px black outline (additional to green border)</li>
<li>Disabled buttons are not focusable</li>
</ul>
</PageGridCol>
@@ -299,7 +295,7 @@ export default function ButtonShowcaseSecondary() {
<h5 className="mb-4">Screen Reader Support</h5>
<ul>
<li>Button labels are announced</li>
<li>Disabled state: <code>disabled</code> + <code>aria-disabled</code> on <code>&lt;button&gt;</code></li>
<li>Disabled state communicated via aria-disabled</li>
<li>Icons are hidden from screen readers (aria-hidden)</li>
<li>Semantic button element used</li>
</ul>
@@ -316,7 +312,7 @@ export default function ButtonShowcaseSecondary() {
<strong>Hover:</strong> Green 500 (#078139) on Green 100 (#EAFCF1) = 4.87:1 (AA)
</li>
<li>
<strong>Disabled:</strong> Gray 400 (#8A919A) text and border on white reduced contrast (disabled pattern)
<strong>Disabled:</strong> Gray 400 (#A2A2A4) on White = reduced contrast (acceptable for disabled state)
</li>
</ul>
</div>
@@ -328,7 +324,7 @@ export default function ButtonShowcaseSecondary() {
<h2 className="h4 mb-8">Code Examples</h2>
<h6 className="eyebrow mb-3">Implementation</h6>
</div>
<div className="p-4 br-8" style={{ backgroundColor: 'var(--code-bg, #1e1e1e)', color: 'var(--code-text, #d4d4d4)' }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#1e1e1e', color: '#d4d4d4' }}>
<pre style={{ margin: 0, overflow: 'auto' }}>
<code>{`import { Button } from 'shared/components/Button';
@@ -395,88 +391,87 @@ export default function ButtonShowcaseSecondary() {
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">State Colors - Green Theme</h5>
<div className={clsx('grid-showcase-breakpoints-table', 'grid-showcase-breakpoints-table--4col')}>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__header">
<div className="grid-showcase-breakpoints-table__cell">State</div>
<div className="grid-showcase-breakpoints-table__cell">Text Color</div>
<div className="grid-showcase-breakpoints-table__cell">Background</div>
<div className="grid-showcase-breakpoints-table__cell">Border</div>
<div style={{ width: '100%', backgroundColor: '#FFFFFF' }}>
{/* Header */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '2px solid #E0E0E1' }}>
<div style={{ padding: '12px', fontWeight: 'bold' }}>State</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Text Color</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Background</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Border</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Enabled</div>
<div className="grid-showcase-breakpoints-table__cell">#0DAA3E (Green 400)</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
<div className="grid-showcase-breakpoints-table__cell">2px #0DAA3E (Green 400)</div>
{/* Rows */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Enabled</div>
<div style={{ padding: '12px' }}>#0DAA3E (Green 400)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>2px #0DAA3E (Green 400)</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Hover</div>
<div className="grid-showcase-breakpoints-table__cell">#078139 (Green 500)</div>
<div className="grid-showcase-breakpoints-table__cell">#EAFCF1 (Green 100)</div>
<div className="grid-showcase-breakpoints-table__cell">2px #078139 (Green 500)</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Hover</div>
<div style={{ padding: '12px' }}>#078139 (Green 500)</div>
<div style={{ padding: '12px' }}>#EAFCF1 (Green 100)</div>
<div style={{ padding: '12px' }}>2px #078139 (Green 500)</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Focus</div>
<div className="grid-showcase-breakpoints-table__cell">#078139 (Green 500)</div>
<div className="grid-showcase-breakpoints-table__cell">#EAFCF1 (Green 100)</div>
<div className="grid-showcase-breakpoints-table__cell">2px #078139 + 2px #141414 outline</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Focus</div>
<div style={{ padding: '12px' }}>#078139 (Green 500)</div>
<div style={{ padding: '12px' }}>#EAFCF1 (Green 100)</div>
<div style={{ padding: '12px' }}>2px #078139 + 2px #141414 outline</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Active (pressed)</div>
<div className="grid-showcase-breakpoints-table__cell">#0DAA3E (Green 400)</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
<div className="grid-showcase-breakpoints-table__cell">2px #0DAA3E (Green 400)</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Active</div>
<div style={{ padding: '12px' }}>#0DAA3E (Green 400)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>2px #0DAA3E (Green 400)</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Disabled</div>
<div className="grid-showcase-breakpoints-table__cell">#8A919A (Gray 400)</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
<div className="grid-showcase-breakpoints-table__cell">2px #8A919A (Gray 400)</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr' }}>
<div style={{ padding: '12px' }}>Disabled</div>
<div style={{ padding: '12px' }}>#A2A2A4 (Gray 400)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>2px #A2A2A4 (Gray 400)</div>
</div>
</div>
<p className="mt-4 text-muted">
<strong>Dark mode:</strong> both green and black secondary variants use the same green palette (Green 300
enabled, Green 200 hover/focus, Green 500 fill). <strong>Pressed/active</strong> returns to Green 300 for both
light-mode black secondary still uses <strong>#434343</strong> only when pressed.
</p>
</div>
<div className="mt-10">
<h5 className="mb-4">State Colors - Black Theme</h5>
<div className={clsx('grid-showcase-breakpoints-table', 'grid-showcase-breakpoints-table--4col')}>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__header">
<div className="grid-showcase-breakpoints-table__cell">State</div>
<div className="grid-showcase-breakpoints-table__cell">Text Color</div>
<div className="grid-showcase-breakpoints-table__cell">Background</div>
<div className="grid-showcase-breakpoints-table__cell">Border</div>
<div style={{ width: '100%', backgroundColor: '#FFFFFF' }}>
{/* Header */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '2px solid #E0E0E1' }}>
<div style={{ padding: '12px', fontWeight: 'bold' }}>State</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Text Color</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Background</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Border</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Enabled</div>
<div className="grid-showcase-breakpoints-table__cell">#141414 (Neutral Black)</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
<div className="grid-showcase-breakpoints-table__cell">2px #141414 (Neutral Black)</div>
{/* Rows */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Enabled</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>2px #141414 (Neutral Black)</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Hover</div>
<div className="grid-showcase-breakpoints-table__cell">#141414 (Neutral Black)</div>
<div className="grid-showcase-breakpoints-table__cell">rgba(20, 20, 20, 0.15) (15% Black)</div>
<div className="grid-showcase-breakpoints-table__cell">2px #141414 (Neutral Black)</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Hover</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>rgba(20, 20, 20, 0.15) (15% Black)</div>
<div style={{ padding: '12px' }}>2px #141414 (Neutral Black)</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Focus</div>
<div className="grid-showcase-breakpoints-table__cell">#141414 (Neutral Black)</div>
<div className="grid-showcase-breakpoints-table__cell">rgba(20, 20, 20, 0.15) (15% Black)</div>
<div className="grid-showcase-breakpoints-table__cell">2px #141414 + 2px #141414 outline</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Focus</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>rgba(20, 20, 20, 0.15) (15% Black)</div>
<div style={{ padding: '12px' }}>2px #141414 + 2px #141414 outline</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Active (pressed)</div>
<div className="grid-showcase-breakpoints-table__cell">#434343</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
<div className="grid-showcase-breakpoints-table__cell">2px #434343</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Active</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>2px #141414 (Neutral Black)</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Disabled</div>
<div className="grid-showcase-breakpoints-table__cell">#8A919A (Gray 400)</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
<div className="grid-showcase-breakpoints-table__cell">2px #8A919A (Gray 400)</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr' }}>
<div style={{ padding: '12px' }}>Disabled</div>
<div style={{ padding: '12px' }}>#A2A2A4 (Gray 400)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>2px #A2A2A4 (Gray 400)</div>
</div>
</div>
</div>
@@ -488,41 +483,43 @@ export default function ButtonShowcaseSecondary() {
<h2 className="h4 mb-8">Key Differences from Primary</h2>
<h6 className="eyebrow mb-3">Comparison</h6>
</div>
<div className={clsx('grid-showcase-breakpoints-table', 'grid-showcase-breakpoints-table--3col')}>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__header">
<div className="grid-showcase-breakpoints-table__cell">Aspect</div>
<div className="grid-showcase-breakpoints-table__cell">Primary</div>
<div className="grid-showcase-breakpoints-table__cell">Secondary</div>
<div style={{ width: '100%', backgroundColor: '#FFFFFF' }}>
{/* Header */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', borderBottom: '2px solid #E0E0E1' }}>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Aspect</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Primary</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Secondary</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Background (Enabled)</div>
<div className="grid-showcase-breakpoints-table__cell">Green 300 (#21E46B)</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
{/* Rows */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Background (Enabled)</div>
<div style={{ padding: '12px' }}>Green 300 (#21E46B)</div>
<div style={{ padding: '12px' }}>Transparent</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Background (Hover)</div>
<div className="grid-showcase-breakpoints-table__cell">Green 200 (#70EE97)</div>
<div className="grid-showcase-breakpoints-table__cell">Green 100 (#EAFCF1)</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Background (Hover)</div>
<div style={{ padding: '12px' }}>Green 200 (#70EE97)</div>
<div style={{ padding: '12px' }}>Green 100 (#EAFCF1)</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Border (Enabled)</div>
<div className="grid-showcase-breakpoints-table__cell">None</div>
<div className="grid-showcase-breakpoints-table__cell">2px Green 400</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Border (Enabled)</div>
<div style={{ padding: '12px' }}>None</div>
<div style={{ padding: '12px' }}>2px Green 400</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Text Color (Enabled)</div>
<div className="grid-showcase-breakpoints-table__cell">Black (#141414)</div>
<div className="grid-showcase-breakpoints-table__cell">Green 400 (#0DAA3E)</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Text Color (Enabled)</div>
<div style={{ padding: '12px' }}>Black (#141414)</div>
<div style={{ padding: '12px' }}>Green 400 (#0DAA3E)</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Disabled Background</div>
<div className="grid-showcase-breakpoints-table__cell">Gray 200 (#E6EAF0)</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Disabled Background</div>
<div style={{ padding: '12px' }}>Gray 200 (#E0E0E1)</div>
<div style={{ padding: '12px' }}>Transparent</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Arrow Icon</div>
<div className="grid-showcase-breakpoints-table__cell"> Shared</div>
<div className="grid-showcase-breakpoints-table__cell"> Shared</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr' }}>
<div style={{ padding: '12px' }}>Arrow Icon</div>
<div style={{ padding: '12px' }}> Shared</div>
<div style={{ padding: '12px' }}> Shared</div>
</div>
</div>
</section>

View File

@@ -1,5 +1,4 @@
import * as React from 'react';
import clsx from 'clsx';
import { Button } from 'shared/components/Button';
import { PageGrid, PageGridCol, PageGridRow } from 'shared/components/PageGrid/page-grid';
@@ -25,10 +24,9 @@ export default function ButtonShowcaseTertiary() {
<h6 className="eyebrow mb-3">Brand Design System</h6>
</div>
<p className="col-lg-8 mx-auto mt-10">
The Tertiary button is a text-only style for low-emphasis actions: no border, transparent background, optional
arrow icon. On the green theme, hover/focus use Green 500 with underline; pressed/active returns label and icon
to Green 400. On the black theme, pressed/active uses <strong>#434343</strong> for text and icon stroke. Light
mode focus uses a 2px <strong>neutral black</strong> outline (not green); dark mode uses a white outline.
The Tertiary button is a text-only button style used for low-emphasis or contextual actions. It features no
background fill or border, appearing as a simple text link with optional arrow icon. This variant provides the
lowest visual emphasis while maintaining brand consistency through green text colors.
</p>
</section>
@@ -88,7 +86,7 @@ export default function ButtonShowcaseTertiary() {
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Enabled State</h5>
<p className="mb-4 text-muted">Text-only style with green text color, no background or border.</p>
<Button variant="tertiary" onClick={handleClick}>
@@ -97,7 +95,7 @@ export default function ButtonShowcaseTertiary() {
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Disabled State</h5>
<p className="mb-4 text-muted">Gray text indicates non-interactive state. Icon is hidden.</p>
<Button variant="tertiary" disabled>
@@ -110,9 +108,8 @@ export default function ButtonShowcaseTertiary() {
<div className="mt-10">
<h5 className="mb-4">Hover & Focus States</h5>
<p className="mb-4 text-muted">
Hover or focus: underline + Green 500 text; focus adds a 2px neutral black outline (negative offset). Pressed:
Green 400 text and arrow. Typography uses the Body R token (18px on large screens, 16px below the xl
breakpoint).
Hover over the buttons or use Tab to focus them. Notice the underline appears and text color darkens to
Green 500. The focus state adds a green outline around the text.
</p>
<div className="d-flex flex-wrap">
<Button variant="tertiary" onClick={handleClick} className="me-4 mb-4">
@@ -156,7 +153,7 @@ export default function ButtonShowcaseTertiary() {
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Enabled State</h5>
<p className="mb-4 text-muted">Black text with transparent background.</p>
<Button variant="tertiary" color="black" onClick={handleClick}>
@@ -165,7 +162,7 @@ export default function ButtonShowcaseTertiary() {
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Disabled State</h5>
<p className="mb-4 text-muted">Same disabled styling as green variant.</p>
<Button variant="tertiary" color="black" disabled>
@@ -178,7 +175,7 @@ export default function ButtonShowcaseTertiary() {
<div className="mt-10">
<h5 className="mb-4">Hover & Focus States</h5>
<p className="mb-4 text-muted">
Hover/focus: black text with underline. Pressed: text and arrow use <strong>#434343</strong>.
Hover over the buttons or use Tab to focus them. Notice the underline appears on hover/focus.
</p>
<div className="d-flex flex-wrap">
<Button variant="tertiary" color="black" onClick={handleClick} className="me-4 mb-4">
@@ -294,7 +291,7 @@ export default function ButtonShowcaseTertiary() {
<ul>
<li>Tab to focus buttons</li>
<li>Enter or Space to activate</li>
<li>Focus: 2px neutral black outline (green theme); white outline in dark mode</li>
<li>Focus indicator: 2px green outline (Green 500)</li>
<li>Disabled buttons are not focusable</li>
</ul>
</PageGridCol>
@@ -302,7 +299,7 @@ export default function ButtonShowcaseTertiary() {
<h5 className="mb-4">Screen Reader Support</h5>
<ul>
<li>Button labels are announced</li>
<li>Disabled state: <code>disabled</code> + <code>aria-disabled</code> on <code>&lt;button&gt;</code></li>
<li>Disabled state communicated via aria-disabled</li>
<li>Icons are hidden from screen readers (aria-hidden)</li>
<li>Semantic button element used</li>
</ul>
@@ -319,7 +316,7 @@ export default function ButtonShowcaseTertiary() {
<strong>Hover/Focus:</strong> Green 500 (#078139) on White = 5.12:1 (AA)
</li>
<li>
<strong>Disabled:</strong> Gray 400 (#8A919A) icon hidden when disabled
<strong>Disabled:</strong> Gray 400 (#A2A2A4) on White = reduced contrast (acceptable for disabled state)
</li>
</ul>
</div>
@@ -331,7 +328,7 @@ export default function ButtonShowcaseTertiary() {
<h2 className="h4 mb-8">Code Examples</h2>
<h6 className="eyebrow mb-3">Implementation</h6>
</div>
<div className="p-4 br-8" style={{ backgroundColor: 'var(--code-bg, #1e1e1e)', color: 'var(--code-text, #d4d4d4)' }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#1e1e1e', color: '#d4d4d4' }}>
<pre style={{ margin: 0, overflow: 'auto' }}>
<code>{`import { Button } from 'shared/components/Button';
@@ -381,8 +378,10 @@ export default function ButtonShowcaseTertiary() {
<h5 className="mb-4">Typography</h5>
<ul>
<li>Font: Booton, sans-serif</li>
<li>Body R token: 18px / 26.1px / -0.5px letter-spacing at lg+; 16px / 23.2px / 0 below xl</li>
<li>Size: 18px (Body R token - different from Primary/Secondary)</li>
<li>Weight: 400</li>
<li>Line Height: 26.1px</li>
<li>Letter Spacing: -0.5px</li>
</ul>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
@@ -400,87 +399,87 @@ export default function ButtonShowcaseTertiary() {
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">State Colors - Green Theme</h5>
<div className={clsx('grid-showcase-breakpoints-table', 'grid-showcase-breakpoints-table--4col')}>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__header">
<div className="grid-showcase-breakpoints-table__cell">State</div>
<div className="grid-showcase-breakpoints-table__cell">Text Color</div>
<div className="grid-showcase-breakpoints-table__cell">Background</div>
<div className="grid-showcase-breakpoints-table__cell">Text Decoration</div>
<div style={{ width: '100%', backgroundColor: '#FFFFFF' }}>
{/* Header */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '2px solid #E0E0E1' }}>
<div style={{ padding: '12px', fontWeight: 'bold' }}>State</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Text Color</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Background</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Text Decoration</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Enabled</div>
<div className="grid-showcase-breakpoints-table__cell">#0DAA3E (Green 400)</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
<div className="grid-showcase-breakpoints-table__cell">None</div>
{/* Rows */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Enabled</div>
<div style={{ padding: '12px' }}>#0DAA3E (Green 400)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>None</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Hover</div>
<div className="grid-showcase-breakpoints-table__cell">#078139 (Green 500)</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
<div className="grid-showcase-breakpoints-table__cell">Underline</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Hover</div>
<div style={{ padding: '12px' }}>#078139 (Green 500)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Underline</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Focus</div>
<div className="grid-showcase-breakpoints-table__cell">#078139 (Green 500)</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
<div className="grid-showcase-breakpoints-table__cell">Underline + 2px #141414 outline (inset)</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Focus</div>
<div style={{ padding: '12px' }}>#078139 (Green 500)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Underline + 2px Green 500 outline</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Active (pressed)</div>
<div className="grid-showcase-breakpoints-table__cell">#0DAA3E (Green 400) label + arrow</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
<div className="grid-showcase-breakpoints-table__cell">Underline</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Active</div>
<div style={{ padding: '12px' }}>#0DAA3E (Green 400)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Underline</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Disabled</div>
<div className="grid-showcase-breakpoints-table__cell">#8A919A (Gray 400)</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
<div className="grid-showcase-breakpoints-table__cell">None (no icon)</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr' }}>
<div style={{ padding: '12px' }}>Disabled</div>
<div style={{ padding: '12px' }}>#A2A2A4 (Gray 400)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>None</div>
</div>
</div>
<p className="mt-4 text-muted">
<strong>Dark mode (green tertiary):</strong> Green 300 default, Green 200 hover/focus, Green 400 pressed; focus outline is white.{' '}
<strong>Dark mode (black tertiary):</strong> green palette when themed; pressed uses <strong>#434343</strong> for text and icon where the black variant applies.
</p>
</div>
<div className="mt-10">
<h5 className="mb-4">State Colors - Black Theme</h5>
<div className={clsx('grid-showcase-breakpoints-table', 'grid-showcase-breakpoints-table--4col')}>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__header">
<div className="grid-showcase-breakpoints-table__cell">State</div>
<div className="grid-showcase-breakpoints-table__cell">Text Color</div>
<div className="grid-showcase-breakpoints-table__cell">Background</div>
<div className="grid-showcase-breakpoints-table__cell">Text Decoration</div>
<div style={{ width: '100%', backgroundColor: '#FFFFFF' }}>
{/* Header */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '2px solid #E0E0E1' }}>
<div style={{ padding: '12px', fontWeight: 'bold' }}>State</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Text Color</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Background</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Text Decoration</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Enabled</div>
<div className="grid-showcase-breakpoints-table__cell">#141414 (Neutral Black)</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
<div className="grid-showcase-breakpoints-table__cell">None</div>
{/* Rows */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Enabled</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>None</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Hover</div>
<div className="grid-showcase-breakpoints-table__cell">#141414 (Neutral Black)</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
<div className="grid-showcase-breakpoints-table__cell">Underline</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Hover</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Underline</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Focus</div>
<div className="grid-showcase-breakpoints-table__cell">#141414 (Neutral Black)</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
<div className="grid-showcase-breakpoints-table__cell">Underline + 2px Black outline</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Focus</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Underline + 2px Black outline</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Active (pressed)</div>
<div className="grid-showcase-breakpoints-table__cell">#434343 label + arrow</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
<div className="grid-showcase-breakpoints-table__cell">Underline</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Active</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Underline</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Disabled</div>
<div className="grid-showcase-breakpoints-table__cell">#8A919A (Gray 400)</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
<div className="grid-showcase-breakpoints-table__cell">None (no icon)</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr' }}>
<div style={{ padding: '12px' }}>Disabled</div>
<div style={{ padding: '12px' }}>#A2A2A4 (Gray 400)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>None</div>
</div>
</div>
</div>
@@ -492,66 +491,68 @@ export default function ButtonShowcaseTertiary() {
<h2 className="h4 mb-8">Key Differences from Primary/Secondary</h2>
<h6 className="eyebrow mb-3">Comparison</h6>
</div>
<div className={clsx('grid-showcase-breakpoints-table', 'grid-showcase-breakpoints-table--4col')}>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__header">
<div className="grid-showcase-breakpoints-table__cell">Aspect</div>
<div className="grid-showcase-breakpoints-table__cell">Primary</div>
<div className="grid-showcase-breakpoints-table__cell">Secondary</div>
<div className="grid-showcase-breakpoints-table__cell">Tertiary</div>
<div style={{ width: '100%', backgroundColor: '#FFFFFF' }}>
{/* Header */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '2px solid #E0E0E1' }}>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Aspect</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Primary</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Secondary</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Tertiary</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Background (Enabled)</div>
<div className="grid-showcase-breakpoints-table__cell">Green 300 (#21E46B)</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
{/* Rows */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Background (Enabled)</div>
<div style={{ padding: '12px' }}>Green 300 (#21E46B)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Transparent</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Background (Hover)</div>
<div className="grid-showcase-breakpoints-table__cell">Green 200 (#70EE97)</div>
<div className="grid-showcase-breakpoints-table__cell">Green 100 (#EAFCF1)</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Background (Hover)</div>
<div style={{ padding: '12px' }}>Green 200 (#70EE97)</div>
<div style={{ padding: '12px' }}>Green 100 (#EAFCF1)</div>
<div style={{ padding: '12px' }}>Transparent</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Border (Enabled)</div>
<div className="grid-showcase-breakpoints-table__cell">None</div>
<div className="grid-showcase-breakpoints-table__cell">2px Green 400</div>
<div className="grid-showcase-breakpoints-table__cell">None</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Border (Enabled)</div>
<div style={{ padding: '12px' }}>None</div>
<div style={{ padding: '12px' }}>2px Green 400</div>
<div style={{ padding: '12px' }}>None</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Text Color (Enabled)</div>
<div className="grid-showcase-breakpoints-table__cell">Black (#141414)</div>
<div className="grid-showcase-breakpoints-table__cell">Green 400 (#0DAA3E)</div>
<div className="grid-showcase-breakpoints-table__cell">Green 400 (#0DAA3E)</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Text Color (Enabled)</div>
<div style={{ padding: '12px' }}>Black (#141414)</div>
<div style={{ padding: '12px' }}>Green 400 (#0DAA3E)</div>
<div style={{ padding: '12px' }}>Green 400 (#0DAA3E)</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Text Decoration</div>
<div className="grid-showcase-breakpoints-table__cell">None</div>
<div className="grid-showcase-breakpoints-table__cell">None</div>
<div className="grid-showcase-breakpoints-table__cell">Underline when hover/focus/active (except disabled)</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Text Decoration</div>
<div style={{ padding: '12px' }}>None</div>
<div style={{ padding: '12px' }}>None</div>
<div style={{ padding: '12px' }}>Underline (hover/focus/active)</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Typography Token</div>
<div className="grid-showcase-breakpoints-table__cell">Label R (16px)</div>
<div className="grid-showcase-breakpoints-table__cell">Label R (16px)</div>
<div className="grid-showcase-breakpoints-table__cell">Body R (18px)</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Typography Token</div>
<div style={{ padding: '12px' }}>Label R (16px)</div>
<div style={{ padding: '12px' }}>Label R (16px)</div>
<div style={{ padding: '12px' }}>Body R (18px)</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Focus Indicator</div>
<div className="grid-showcase-breakpoints-table__cell">2px Black border</div>
<div className="grid-showcase-breakpoints-table__cell">2px Black outline</div>
<div className="grid-showcase-breakpoints-table__cell">2px Black outline (green tertiary)</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Focus Indicator</div>
<div style={{ padding: '12px' }}>2px Black border</div>
<div style={{ padding: '12px' }}>2px Black outline</div>
<div style={{ padding: '12px' }}>2px Green 500 outline</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Disabled Background</div>
<div className="grid-showcase-breakpoints-table__cell">Gray 200 (#E6EAF0)</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
<div className="grid-showcase-breakpoints-table__cell">Transparent</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Disabled Background</div>
<div style={{ padding: '12px' }}>Gray 200 (#E0E0E1)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Transparent</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell">Arrow Icon</div>
<div className="grid-showcase-breakpoints-table__cell"> Shared</div>
<div className="grid-showcase-breakpoints-table__cell"> Shared</div>
<div className="grid-showcase-breakpoints-table__cell"> Shared</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr' }}>
<div style={{ padding: '12px' }}>Arrow Icon</div>
<div style={{ padding: '12px' }}> Shared</div>
<div style={{ padding: '12px' }}> Shared</div>
<div style={{ padding: '12px' }}> Shared</div>
</div>
</div>
</section>

View File

@@ -1,6 +1,6 @@
import * as React from "react";
import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";
import { CalloutMediaBanner } from "shared/sections/CalloutMediaBanner";
import { CalloutMediaBanner } from "shared/patterns/CalloutMediaBanner";
export const frontmatter = {
seo: {
@@ -39,12 +39,9 @@ export default function CalloutMediaBannerShowcase() {
<CalloutMediaBanner
variant="green"
heading="The Compliant Ledger Protocol"
headingAs="h1"
subheading="A decentralized public Layer 1 blockchain for creating, transferring, and exchanging digital assets with a focus on compliance."
buttons={[
{ label: "Get Started", onClick: () => handleClick('responsive-demo-primary') },
{ label: "Learn More", onClick: () => handleClick('responsive-demo-tertiary') }
]}
primaryButton={{ label: "Get Started", onClick: () => handleClick('responsive-demo-primary') }}
tertiaryButton={{ label: "Learn More", onClick: () => handleClick('responsive-demo-tertiary') }}
/>
{/* Responsive Behavior */}
@@ -125,9 +122,7 @@ export default function CalloutMediaBannerShowcase() {
variant="default"
heading="Build on XRPL"
subheading="Start building your next decentralized application on the XRP Ledger."
buttons={[
{ label: "Start Building", href: "#start" }
]}
primaryButton={{ label: "Start Building", href: "#start" }}
/>
</div>
@@ -148,10 +143,8 @@ export default function CalloutMediaBannerShowcase() {
variant="light-gray"
heading="Developer Resources"
subheading="Access comprehensive documentation, tutorials, and code samples."
buttons={[
{ label: "View Docs", href: "#docs" },
{ label: "Browse Tutorials", href: "#tutorials" }
]}
primaryButton={{ label: "View Docs", href: "#docs" }}
tertiaryButton={{ label: "Browse Tutorials", href: "#tutorials" }}
/>
</div>
@@ -172,9 +165,7 @@ export default function CalloutMediaBannerShowcase() {
variant="lilac"
heading="New Feature Release"
subheading="Discover the latest enhancements and capabilities added to the XRP Ledger."
buttons={[
{ label: "Learn More", href: "#features" }
]}
primaryButton={{ label: "Learn More", href: "#features" }}
/>
</div>
@@ -195,10 +186,8 @@ export default function CalloutMediaBannerShowcase() {
variant="green"
heading="The Compliant Ledger Protocol"
subheading="A decentralized public Layer 1 blockchain for creating, transferring, and exchanging digital assets with a focus on compliance."
buttons={[
{ label: "Get Started", href: "#get-started" },
{ label: "Learn More", href: "#learn" }
]}
primaryButton={{ label: "Get Started", href: "#get-started" }}
tertiaryButton={{ label: "Learn More", href: "#learn" }}
/>
</div>
@@ -219,10 +208,8 @@ export default function CalloutMediaBannerShowcase() {
variant="gray"
heading="Join the Community"
subheading="Connect with developers building on XRPL."
buttons={[
{ label: "Join Discord", href: "#discord" },
{ label: "View Events", href: "#events" }
]}
primaryButton={{ label: "Join Discord", href: "#discord" }}
tertiaryButton={{ label: "View Events", href: "#events" }}
/>
</div>
@@ -268,9 +255,7 @@ export default function CalloutMediaBannerShowcase() {
<CalloutMediaBanner
backgroundImage={sampleBackgroundImage}
subheading="A decentralized public Layer 1 blockchain for creating, transferring, and exchanging digital assets with a focus on compliance."
buttons={[
{ label: "Start Building", onClick: () => handleClick('image-white-primary') }
]}
primaryButton={{ label: "Start Building", onClick: () => handleClick('image-white-primary') }}
/>
</div>
@@ -291,12 +276,9 @@ export default function CalloutMediaBannerShowcase() {
backgroundImage={sampleLightBackgroundImage}
textColor="black"
heading="Build the Future of Finance"
headingAs="h2"
subheading="Create powerful decentralized applications with XRPL's fast, efficient, and sustainable blockchain technology."
buttons={[
{ label: "Start Building", onClick: () => handleClick('image-black-primary') },
{ label: "Explore Features", onClick: () => handleClick('image-black-tertiary') }
]}
primaryButton={{ label: "Start Building", onClick: () => handleClick('image-black-primary') }}
tertiaryButton={{ label: "Explore Features", onClick: () => handleClick('image-black-tertiary') }}
/>
</div>
@@ -339,10 +321,8 @@ export default function CalloutMediaBannerShowcase() {
variant="default"
heading="Complete Feature Set"
subheading="Access all the tools you need to build on XRPL."
buttons={[
{ label: "Get Started", href: "#start" },
{ label: "Learn More", href: "#learn" }
]}
primaryButton={{ label: "Get Started", href: "#start" }}
tertiaryButton={{ label: "Learn More", href: "#learn" }}
/>
</div>
@@ -363,9 +343,7 @@ export default function CalloutMediaBannerShowcase() {
variant="light-gray"
heading="Simple Call-to-Action"
subheading="Focus user attention on a single primary action."
buttons={[
{ label: "Take Action", href: "#action" }
]}
primaryButton={{ label: "Take Action", href: "#action" }}
/>
</div>
@@ -405,10 +383,8 @@ export default function CalloutMediaBannerShowcase() {
<CalloutMediaBanner
variant="green"
subheading="Important information or announcement without requiring user action."
buttons={[
{ label: "Take Action", href: "#action" },
{ label: "Learn More", href: "#learn" }
]}
primaryButton={{ label: "Take Action", href: "#action" }}
tertiaryButton={{ label: "Learn More", href: "#learn" }}
/>
</div>
@@ -576,18 +552,10 @@ export default function CalloutMediaBannerShowcase() {
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>heading</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '120px', flexShrink: 0 }}><code>undefined</code></div>
<div style={{ width: '120px', flexShrink: 0 }}><em>required</em></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Main heading text</div>
</div>
{/* headingAs */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>headingAs</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'</code></div>
<div style={{ width: '120px', flexShrink: 0 }}><code>'h6'</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Heading element type - allows semantic HTML customization while maintaining visual styling</div>
</div>
{/* subheading */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>subheading</code></div>
@@ -596,12 +564,20 @@ export default function CalloutMediaBannerShowcase() {
<div style={{ flex: '1 1 0', minWidth: 0 }}>Subheading/description text</div>
</div>
{/* buttons */}
{/* primaryButton */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>buttons</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>{`Array<{ label, href?, onClick?, forceColor? }>`}</code></div>
<div style={{ width: '140px', flexShrink: 0 }}><code>primaryButton</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>{`{ label, href?, onClick? }`}</code></div>
<div style={{ width: '120px', flexShrink: 0 }}><code>undefined</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Button configurations (1-2 buttons supported)</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Primary button configuration</div>
</div>
{/* tertiaryButton */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>tertiaryButton</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>{`{ label, href?, onClick? }`}</code></div>
<div style={{ width: '120px', flexShrink: 0 }}><code>undefined</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Tertiary button configuration</div>
</div>
{/* className */}
@@ -630,7 +606,7 @@ export default function CalloutMediaBannerShowcase() {
</div>
<div>
<strong>Component Location:</strong>{' '}
<code>shared/sections/CalloutMediaBanner/</code>
<code>shared/patterns/CalloutMediaBanner/</code>
</div>
<div>
<strong>Color Tokens:</strong>{' '}

View File

@@ -223,9 +223,7 @@ export default function CardIconShowcase() {
<div className="mt-3 text-center">
<strong>Focused</strong>
<br />
<code className="small">+ focus ring</code>
<br />
<small className="text-muted">html.light: black · html.dark: white</small>
<code className="small">+ black border</code>
</div>
</div>
</PageGridCol>
@@ -329,9 +327,7 @@ export default function CardIconShowcase() {
<div className="mt-3 text-center">
<strong>Focused</strong>
<br />
<code className="small">+ focus ring</code>
<br />
<small className="text-muted">html.light: black · html.dark: white</small>
<code className="small">+ black border</code>
</div>
</div>
</PageGridCol>
@@ -457,20 +453,8 @@ export default function CardIconShowcase() {
<div><code>Hover/Focus: $gray-400</code> <small className="text-muted">#8A919A + white text</small></div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div
style={{
width: '32px',
height: '32px',
borderRadius: '4px',
flexShrink: 0,
border: '1px solid #ccc',
backgroundColor: '#56595E',
}}
/>
<div>
<code>Pressed: $gray-500-pressed-dark</code>{' '}
<small className="text-muted">#56595E (#72777E + 70% black)</small>
</div>
<div style={{ width: '32px', height: '32px', backgroundColor: 'rgba(114,119,126,0.7)', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Pressed: 70% $gray-500</code></div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: 'rgba(114,119,126,0.3)', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>

View File

@@ -6,7 +6,7 @@ import { Divider } from "shared/components/Divider";
export const frontmatter = {
seo: {
title: 'CardOffgrid Component Showcase',
description: "CardOffgrid showcase: neutral and green variants, hover and focus-visible overlays, responsive window-shade animation (wide viewports) vs instant pressed fill (tablet/mobile), and color tokens.",
description: "A comprehensive showcase of all CardOffgrid component variants, states, and interactions in the XRPL.org Design System.",
}
};
@@ -54,9 +54,7 @@ export default function CardOffgridShowcase() {
<h1 className="mb-4">CardOffgrid Component</h1>
<p className="longform">
A versatile card component for displaying feature highlights with an icon, title, and description.
Supports neutral and green color variants with hover, pressed, disabled, and keyboard focus states.
On wide viewports, a clip-path window shade reveals the hover overlay; on tablet and mobile (about 991px and below),
that animation is off and hover or focus goes straight to the pressed fill for a cleaner touch experience.
Supports neutral and green color variants with interactive states and bottom-to-top gradient hover animation.
</p>
</div>
</section>
@@ -69,7 +67,7 @@ export default function CardOffgridShowcase() {
<p className="mb-6">CardOffgrid supports two color variants: <strong>neutral</strong> (default) and <strong>green</strong>.</p>
<div className="d-flex flex-row gap-6 mb-6" style={{ flexWrap: 'wrap' }}>
<div className = "me-4">
<div>
<h6 className="mb-3">Neutral Variant (Default)</h6>
<CardOffgrid
variant="neutral"
@@ -106,10 +104,7 @@ export default function CardOffgridShowcase() {
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Interactive States</h2>
<p className="mb-6">
Hover, press, and Tab to focus the cards below. On wide screens, focus uses the same overlay fill as hover; on
narrow screens, hover and focus snap to the pressed look without the wipe.
</p>
<p className="mb-6">Hover, focus, and press the cards below to see the state transitions.</p>
{/* Neutral States */}
<h5 className="mb-4">Neutral Variant States</h5>
@@ -173,13 +168,6 @@ export default function CardOffgridShowcase() {
All colors are mapped from <code>styles/_colors.scss</code>.
The site defaults to <strong>dark mode</strong>. Light mode is activated via <code>html.light</code>.
</p>
<p className="mb-6 longform">
<strong>How states use these tokens:</strong> On viewports <strong>wider than 991px</strong>, mouse hover and{' '}
<code>:focus-visible</code> both reveal the same <strong>hover</strong> overlay (with the wipe animation).{' '}
<strong>Pressed</strong> applies while the pointer is active. At <strong>991px and below</strong>, the overlay
has no transition; hover and <code>:focus-visible</code> use the <strong>pressed</strong> overlay color immediately
(no hover-colored wipe).
</p>
{/* Dark Mode Colors */}
<h5 className="mb-4">Dark Mode (Default)</h5>
@@ -199,21 +187,17 @@ export default function CardOffgridShowcase() {
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#8A919A', borderRadius: '4px', flexShrink: 0, border: '1px solid #444' }}></div>
<div>
<strong>Hover</strong> <small className="text-muted">(desktop overlay; same fill on <code>:focus-visible</code>)</small>
<br />
<code>$gray-400</code>
<strong>Hover/Focus:</strong> <code>$gray-400</code>
<br />
<small className="text-muted">#8A919A (white text)</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#56595E', borderRadius: '4px', flexShrink: 0, border: '1px solid #444' }}></div>
<div style={{ width: '60px', height: '40px', backgroundColor: 'rgba(114, 119, 126, 0.7)', borderRadius: '4px', flexShrink: 0, border: '1px solid #444' }}></div>
<div>
<strong>Pressed</strong> <small className="text-muted">(also hover/focus overlay 991px)</small>
<strong>Pressed:</strong> <code>rgba($gray-500, 0.7)</code>
<br />
<code>$gray-500-pressed-dark</code>
<br />
<small className="text-muted">#56595E (opaque rgba on same base is invisible)</small>
<small className="text-muted">70% opacity</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
@@ -242,9 +226,7 @@ export default function CardOffgridShowcase() {
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#70EE97', borderRadius: '4px', flexShrink: 0, border: '1px solid #444' }}></div>
<div>
<strong>Hover</strong> <small className="text-muted">(desktop overlay; same fill on <code>:focus-visible</code>)</small>
<br />
<code>$green-200</code>
<strong>Hover/Focus:</strong> <code>$green-200</code>
<br />
<small className="text-muted">#70EE97 (black text)</small>
</div>
@@ -252,9 +234,7 @@ export default function CardOffgridShowcase() {
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#0DAA3E', borderRadius: '4px', flexShrink: 0, border: '1px solid #444' }}></div>
<div>
<strong>Pressed</strong> <small className="text-muted">(also hover/focus overlay 991px)</small>
<br />
<code>$green-400</code>
<strong>Pressed:</strong> <code>$green-400</code>
<br />
<small className="text-muted">#0DAA3E (black text)</small>
</div>
@@ -291,19 +271,15 @@ export default function CardOffgridShowcase() {
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#CAD4DF', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div>
<strong>Hover</strong> <small className="text-muted">(desktop overlay; <code>:focus-visible</code> matches hover + title goes black)</small>
<strong>Hover/Focus:</strong> <code>$gray-300</code>
<br />
<code>$gray-300</code>
<br />
<small className="text-muted">#CAD4DF (black text on hover/focus)</small>
<small className="text-muted">#CAD4DF (black text)</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#8A919A', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div>
<strong>Pressed</strong> <small className="text-muted">(also hover/focus overlay 991px)</small>
<br />
<code>$gray-400</code>
<strong>Pressed:</strong> <code>$gray-400</code>
<br />
<small className="text-muted">#8A919A (black text)</small>
</div>
@@ -334,9 +310,7 @@ export default function CardOffgridShowcase() {
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#21E46B', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div>
<strong>Hover</strong> <small className="text-muted">(desktop overlay; same fill on <code>:focus-visible</code>)</small>
<br />
<code>$green-300</code>
<strong>Hover/Focus:</strong> <code>$green-300</code>
<br />
<small className="text-muted">#21E46B (black text)</small>
</div>
@@ -344,9 +318,7 @@ export default function CardOffgridShowcase() {
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#0DAA3E', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div>
<strong>Pressed</strong> <small className="text-muted">(also hover/focus overlay 991px)</small>
<br />
<code>$green-400</code>
<strong>Pressed:</strong> <code>$green-400</code>
<br />
<small className="text-muted">#0DAA3E (black text)</small>
</div>
@@ -376,28 +348,24 @@ export default function CardOffgridShowcase() {
<div style={{ flex: '1 1 300px' }}>
<h6 className="mb-3">Timing</h6>
<ul className="mb-0">
<li><strong>Duration:</strong> 200ms (background/opacity on the card; overlay wipe when transition is enabled)</li>
<li><strong>Duration:</strong> 200ms</li>
<li><strong>Easing:</strong> <code>cubic-bezier(0.98, 0.12, 0.12, 0.98)</code></li>
<li><strong>991px:</strong> Overlay <code>transition</code> is <code>none</code> (no wipe)</li>
</ul>
</div>
<div style={{ flex: '1 1 300px' }}>
<h6 className="mb-3">Hover effect (window shade)</h6>
<h6 className="mb-3">Hover Effect ("Window Shade")</h6>
<ul className="mb-0">
<li><strong>Wide only (&gt;991px):</strong> Overlay uses <code>clip-path</code> with 200ms easing</li>
<li><strong>Hover in:</strong> Shade rises (bottom top)</li>
<li><strong>Hover out:</strong> Shade falls (top bottom)</li>
<li><strong>991px:</strong> No transition; hover / <code>:focus-visible</code> show pressed overlay only</li>
<li><strong>Pressed:</strong> Darker overlay while pointer is down (all widths)</li>
<li><strong>Hover in:</strong> Shade rises up (bottom top)</li>
<li><strong>Hover out:</strong> Shade falls down (top bottom)</li>
<li>Darker pressed state on click</li>
</ul>
</div>
<div style={{ flex: '1 1 300px' }}>
<h6 className="mb-3">State flow</h6>
<h6 className="mb-3">State Flow</h6>
<ul className="mb-0">
<li><strong>Desktop:</strong> Default hover overlay pressed while active</li>
<li><strong>Desktop:</strong> <code>:focus-visible</code> reveals same overlay as hover + focus ring</li>
<li><strong>Tablet/mobile:</strong> Default pressed overlay on hover or focus (instant)</li>
<li>Default Hover Pressed</li>
<li>Full card area is clickable</li>
<li>Focus ring on keyboard navigation</li>
</ul>
</div>
</div>

View File

@@ -1,5 +1,5 @@
import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";
import { CardStats, CardStatsCardConfig } from "shared/sections/CardStatsList";
import { CardStats, CardStatsCardConfig } from "shared/patterns/CardStats";
import { Divider } from "shared/components/Divider";
export const frontmatter = {
@@ -208,7 +208,7 @@ export default function CardStatsShowcase() {
<h5 className="mb-4">Basic Usage</h5>
<div className="p-4 mb-8 br-4" style={{ backgroundColor: '#f5f5f7', fontFamily: 'monospace', fontSize: '14px' }}>
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', color: '#000' }}>{`import { CardStats } from 'shared/sections/CardStatsList';
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', color: '#000' }}>{`import { CardStats } from 'shared/patterns/CardStats';
<CardStats
heading="Blockchain Trusted at Scale"
@@ -258,7 +258,7 @@ export default function CardStatsShowcase() {
</div>
<div>
<strong>Pattern Location:</strong>{' '}
<code>shared/sections/CardStatsList/</code>
<code>shared/patterns/CardStats/</code>
</div>
<div>
<strong>Component Used:</strong>{' '}

View File

@@ -1,5 +1,5 @@
import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";
import { CardsFeatured } from "shared/sections/CardsFeatured";
import { CardsFeatured } from "shared/patterns/CardsFeatured";
import { Divider } from "shared/components/Divider";
export const frontmatter = {

View File

@@ -1,5 +1,6 @@
import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";
import { CardsTwoColumn, TextCard } from "shared/sections/CardsTwoColumn";
import { CardsTwoColumn } from "shared/patterns/CardsTwoColumn";
import { TextCard } from "shared/patterns/CardsTwoColumn";
import { Divider } from "shared/components/Divider";
export const frontmatter = {
@@ -110,7 +111,7 @@ export default function CardsTwoColumnShowcase() {
{
title: "Developers",
description: "Build decentralized applications with comprehensive documentation, tutorials, and developer tools.",
href: "#dev",
href: "#developers",
color: "neutral-light"
},
{

View File

@@ -1,5 +1,5 @@
import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";
import { CarouselCardList } from "shared/sections/CarouselCardList";
import { CarouselCardList } from "shared/patterns/CarouselCardList";
import { CarouselButton } from "shared/components/CarouselButton";
import { Divider } from "shared/components/Divider";
@@ -615,7 +615,7 @@ export default function CarouselCardListShowcase() {
<h2 className="h4 mb-6">Usage Example</h2>
<div className="card p-4">
<pre className="mb-0" style={{ backgroundColor: 'var(--bs-gray-800)', padding: '1rem', borderRadius: '4px', overflow: 'auto' }}>
{`import { CarouselCardList } from 'shared/sections/CarouselCardList';
{`import { CarouselCardList } from 'shared/patterns/CarouselCardList';
// Basic usage - button color matches card color by default
<CarouselCardList
@@ -669,11 +669,11 @@ export default function CarouselCardListShowcase() {
</div>
<div>
<strong>Component Location:</strong>{' '}
<code>shared/sections/CarouselCardList/</code>
<code>shared/patterns/CarouselCardList/</code>
</div>
<div>
<strong>Documentation:</strong>{' '}
<code>shared/sections/CarouselCardList/CarouselCardList.md</code>
<code>shared/patterns/CarouselCardList/CarouselCardList.md</code>
</div>
</div>
</PageGridCol>

View File

@@ -109,7 +109,7 @@ export default function CarouselFeaturedShowcase() {
</PageGridRow>
</PageGrid>
<Divider weight="strong" color="gray" />
<Divider weight="thick" color="gray" />
{/* Default: gray-200 background */}
<section className="py-10">
@@ -128,15 +128,13 @@ export default function CarouselFeaturedShowcase() {
background="grey"
heading="Powered by Developers"
features={sampleFeatures}
buttons={[
{ label: "Get Started", href: "#get-started" },
{ label: "Learn More", href: "#learn-more" }
]}
slides={sampleSlides.slice(0,1)}
primaryButton={{ label: "Get Started", href: "#get-started" }}
tertiaryButton={{ label: "Learn More", href: "#learn-more" }}
slides={sampleSlides}
/>
</section>
<Divider weight="strong" color="gray" />
<Divider weight="thick" color="gray" />
{/* neutral background */}
<section className="py-10">
@@ -155,15 +153,13 @@ export default function CarouselFeaturedShowcase() {
background="neutral"
heading="Platform Updates"
features={sampleFeatures}
buttons={[
{ label: "View Updates", href: "#updates" },
{ label: "See All", href: "#all" }
]}
primaryButton={{ label: "View Updates", href: "#updates" }}
tertiaryButton={{ label: "See All", href: "#all" }}
slides={sampleSlides}
/>
</section>
<Divider weight="strong" color="gray" />
<Divider weight="thick" color="gray" />
{/* yellow background */}
<section className="py-10">
@@ -182,15 +178,13 @@ export default function CarouselFeaturedShowcase() {
background="yellow"
heading="Community Highlights"
features={sampleFeatures}
buttons={[
{ label: "Join Community", href: "#community" },
{ label: "Learn More", href: "#learn" }
]}
primaryButton={{ label: "Join Community", href: "#community" }}
tertiaryButton={{ label: "Learn More", href: "#learn" }}
slides={sampleSlides}
/>
</section>
<Divider weight="strong" color="gray" />
<Divider weight="thick" color="gray" />
{/* Single button example */}
<section className="py-10">
@@ -209,14 +203,12 @@ export default function CarouselFeaturedShowcase() {
background="grey"
heading="Single Button Example"
features={sampleFeatures}
buttons={[
{ label: "Get Started", href: "#get-started" }
]}
primaryButton={{ label: "Get Started", href: "#get-started" }}
slides={sampleSlides}
/>
</section>
<Divider weight="strong" color="gray" />
<Divider weight="thick" color="gray" />
{/* API Reference */}
<PageGrid className="py-26">
@@ -250,10 +242,18 @@ export default function CarouselFeaturedShowcase() {
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>buttons</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>ButtonConfig[]</code></div>
<div style={{ width: '140px', flexShrink: 0 }}><code>primaryButton</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>ButtonConfig</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>optional</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Array of button configurations (1-2 buttons supported, uses ButtonGroup)</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Primary button configuration (uses ButtonGroup)</div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>tertiaryButton</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>ButtonConfig</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>optional</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Tertiary button configuration (uses ButtonGroup)</div>
</div>
<Divider weight="thin" color="gray" />

View File

@@ -1,22 +1,16 @@
import {
PageGrid,
PageGridRow,
PageGridCol,
} from "shared/components/PageGrid/page-grid";
import { FeatureTwoColumn } from "shared/sections/FeatureTwoColumn";
import { PageGrid, PageGridRow, PageGridCol } from 'shared/components/PageGrid/page-grid';
import { FeatureTwoColumn } from 'shared/patterns/FeatureTwoColumn';
export const frontmatter = {
seo: {
title: "FeatureTwoColumn Pattern Showcase",
description:
"Interactive showcase of the FeatureTwoColumn pattern with all color variants, arrangements, and button configurations.",
title: 'FeatureTwoColumn Pattern Showcase',
description: 'Interactive showcase of the FeatureTwoColumn pattern with all color variants, arrangements, and button configurations.',
},
};
export default function FeatureTwoColumnShowcase() {
// Placeholder image
const placeholderImage =
"https://cdn.sanity.io/images/ior4a5y3/production/6e150606bc0a051a83b90aa830cc32854cc3f7df-2928x1920.jpg";
const placeholderImage = '/img/demo-bg.png';
return (
<div className="landing">
@@ -27,10 +21,9 @@ export default function FeatureTwoColumnShowcase() {
<h6 className="eyebrow mb-3">Pattern Showcase</h6>
<h1 className="mb-4">FeatureTwoColumn Pattern</h1>
<p className="longform">
A feature section pattern that pairs editorial content with a
media element in a two-column layout. Supports four color themes,
left/right arrangements, and automatic button configuration based
on link count.
A feature section pattern that pairs editorial content with a media element
in a two-column layout. Supports four color themes, left/right arrangements,
and automatic button configuration based on link count.
</p>
</div>
</section>
@@ -41,21 +34,12 @@ export default function FeatureTwoColumnShowcase() {
<PageGridCol span={12}>
<h2 className="h4 mb-6">Button Behavior</h2>
<p className="mb-4">
The component uses the ButtonGroup pattern which automatically
adjusts button rendering based on the number of links provided:
The component automatically adjusts button rendering based on the number of links provided:
</p>
<ul className="mb-6">
<li>
<strong>1 link:</strong> Secondary button
</li>
<li>
<strong>2 links:</strong> Primary + Tertiary buttons
(responsive layout)
</li>
<li>
<strong>3+ links:</strong> All Tertiary buttons in block
layout (vertical on all screen sizes)
</li>
<li><strong>1 link:</strong> Secondary button</li>
<li><strong>2 links:</strong> Primary + Tertiary buttons</li>
<li><strong>3-5 links:</strong> Primary + Tertiary (row), Secondary, then remaining Tertiary links</li>
</ul>
</PageGridCol>
</PageGridRow>
@@ -69,9 +53,7 @@ export default function FeatureTwoColumnShowcase() {
<div className="mb-3">
<strong>1 Link</strong> - Secondary Button
<br />
<small className="text-muted">
Single action rendered as a secondary (outline) button.
</small>
<small className="text-muted">Single action rendered as a secondary (outline) button.</small>
</div>
</PageGridCol>
</PageGridRow>
@@ -81,7 +63,9 @@ export default function FeatureTwoColumnShowcase() {
arrange="left"
title="Institutions"
description="Banks, asset managers, PSPs, and fintechs use XRPL to build financial products and DeFi solutions efficiently and with more flexibility."
links={[{ label: "Secondary Link", href: "#link1" }]}
links={[
{ label: "Secondary Link", href: "#link1" }
]}
media={{ src: placeholderImage, alt: "Feature illustration" }}
/>
</div>
@@ -94,9 +78,7 @@ export default function FeatureTwoColumnShowcase() {
<div className="mb-3">
<strong>2 Links</strong> - Primary + Tertiary Buttons
<br />
<small className="text-muted">
Primary action with a secondary tertiary link.
</small>
<small className="text-muted">Primary action with a secondary tertiary link.</small>
</div>
</PageGridCol>
</PageGridRow>
@@ -108,7 +90,7 @@ export default function FeatureTwoColumnShowcase() {
description="Banks, asset managers, PSPs, and fintechs use XRPL to build financial products and DeFi solutions efficiently and with more flexibility."
links={[
{ label: "Primary Link", href: "#link1" },
{ label: "Tertiary Link", href: "#link2" },
{ label: "Tertiary Link", href: "#link2" }
]}
media={{ src: placeholderImage, alt: "Feature illustration" }}
/>
@@ -122,10 +104,7 @@ export default function FeatureTwoColumnShowcase() {
<div className="mb-3">
<strong>5 Links</strong> - Multiple Links Configuration
<br />
<small className="text-muted">
For 3+ links, ButtonGroup renders all actions as tertiary
buttons in block layout.
</small>
<small className="text-muted">Primary + Tertiary in first row, Secondary below, remaining as Tertiary list.</small>
</div>
</PageGridCol>
</PageGridRow>
@@ -140,7 +119,7 @@ export default function FeatureTwoColumnShowcase() {
{ label: "Tertiary Link", href: "#link2" },
{ label: "Secondary Link", href: "#link3" },
{ label: "Tertiary Link", href: "#link4" },
{ label: "Tertiary Link", href: "#link5" },
{ label: "Tertiary Link", href: "#link5" }
]}
media={{ src: placeholderImage, alt: "Feature illustration" }}
/>
@@ -168,8 +147,7 @@ export default function FeatureTwoColumnShowcase() {
<strong>Neutral</strong> - <code>color="neutral"</code>
<br />
<small className="text-muted">
Light: <code>$gray-100</code> (#F0F3F7) | Dark:{" "}
<code>$gray-200</code> (#E6EAF0)
Light: <code>$gray-100</code> (#F0F3F7) | Dark: <code>$gray-200</code> (#E6EAF0)
<br />
From <code>styles/_colors.scss</code>
</small>
@@ -184,7 +162,7 @@ export default function FeatureTwoColumnShowcase() {
description="Banks, asset managers, PSPs, and fintechs use XRPL to build financial products and DeFi solutions efficiently and with more flexibility."
links={[
{ label: "Get Started", href: "#start" },
{ label: "Learn More", href: "#learn" },
{ label: "Learn More", href: "#learn" }
]}
media={{ src: placeholderImage, alt: "Neutral theme" }}
/>
@@ -199,8 +177,7 @@ export default function FeatureTwoColumnShowcase() {
<strong>Lilac</strong> - <code>color="lilac"</code>
<br />
<small className="text-muted">
Light: <code>$lilac-200</code> (#D9CAFF) | Dark:{" "}
<code>$lilac-200</code> (#D9CAFF)
Light: <code>$lilac-200</code> (#D9CAFF) | Dark: <code>$lilac-200</code> (#D9CAFF)
<br />
From <code>styles/_colors.scss</code>
</small>
@@ -215,7 +192,7 @@ export default function FeatureTwoColumnShowcase() {
description="Banks, asset managers, PSPs, and fintechs use XRPL to build financial products and DeFi solutions efficiently and with more flexibility."
links={[
{ label: "Get Started", href: "#start" },
{ label: "Learn More", href: "#learn" },
{ label: "Learn More", href: "#learn" }
]}
media={{ src: placeholderImage, alt: "Lilac theme" }}
/>
@@ -230,8 +207,7 @@ export default function FeatureTwoColumnShowcase() {
<strong>Yellow</strong> - <code>color="yellow"</code>
<br />
<small className="text-muted">
Light: <code>$yellow-100</code> (#F3F1EB) | Dark:{" "}
<code>$yellow-100</code> (#F3F1EB)
Light: <code>$yellow-100</code> (#F3F1EB) | Dark: <code>$yellow-100</code> (#F3F1EB)
<br />
From <code>styles/_colors.scss</code>
</small>
@@ -246,7 +222,7 @@ export default function FeatureTwoColumnShowcase() {
description="Banks, asset managers, PSPs, and fintechs use XRPL to build financial products and DeFi solutions efficiently and with more flexibility."
links={[
{ label: "Get Started", href: "#start" },
{ label: "Learn More", href: "#learn" },
{ label: "Learn More", href: "#learn" }
]}
media={{ src: placeholderImage, alt: "Yellow theme" }}
/>
@@ -261,8 +237,7 @@ export default function FeatureTwoColumnShowcase() {
<strong>Green</strong> - <code>color="green"</code>
<br />
<small className="text-muted">
Light: <code>$green-300</code> (#21E46B) | Dark:{" "}
<code>$green-300</code> (#21E46B)
Light: <code>$green-300</code> (#21E46B) | Dark: <code>$green-300</code> (#21E46B)
<br />
From <code>styles/_colors.scss</code>
</small>
@@ -277,7 +252,7 @@ export default function FeatureTwoColumnShowcase() {
description="Banks, asset managers, PSPs, and fintechs use XRPL to build financial products and DeFi solutions efficiently and with more flexibility."
links={[
{ label: "Get Started", href: "#start" },
{ label: "Learn More", href: "#learn" },
{ label: "Learn More", href: "#learn" }
]}
media={{ src: placeholderImage, alt: "Green theme" }}
/>
@@ -289,24 +264,14 @@ export default function FeatureTwoColumnShowcase() {
<PageGridCol span={12}>
<h2 className="h4 mb-6">Arrangement Variants</h2>
<p className="mb-6">
Control content position with the <code>arrange</code> prop. Use
alternating arrangements for visual variety on pages with
multiple sections.
Control content position with the <code>arrange</code> prop.
Use alternating arrangements for visual variety on pages with multiple sections.
</p>
<div
className="mb-4 p-3"
style={{ backgroundColor: "#f0f3f7", borderRadius: "8px" }}
>
<div className="mb-4 p-3" style={{ backgroundColor: '#f0f3f7', borderRadius: '8px' }}>
<strong>📱 Responsive Behavior:</strong>
<ul className="mb-0 mt-2">
<li>
<code>arrange="left"</code>: Content above media on
mobile/tablet, content left on desktop
</li>
<li>
<code>arrange="right"</code>: Media above content on
mobile/tablet, content right on desktop
</li>
<li><code>arrange="left"</code>: Content above media on mobile/tablet, content left on desktop</li>
<li><code>arrange="right"</code>: Media above content on mobile/tablet, content right on desktop</li>
</ul>
</div>
</PageGridCol>
@@ -319,12 +284,10 @@ export default function FeatureTwoColumnShowcase() {
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>Arrange Left (Default)</strong> -{" "}
<code>arrange="left"</code>
<strong>Arrange Left (Default)</strong> - <code>arrange="left"</code>
<br />
<small className="text-muted">
Desktop: Content left, media right | Mobile/Tablet: Content
above media
Desktop: Content left, media right | Mobile/Tablet: Content above media
</small>
</div>
</PageGridCol>
@@ -337,7 +300,7 @@ export default function FeatureTwoColumnShowcase() {
description="This content appears on the left side of the layout on desktop, and above the media on mobile/tablet. This is the default arrangement."
links={[
{ label: "Primary", href: "#primary" },
{ label: "Learn More", href: "#secondary" },
{ label: "Learn More", href: "#secondary" }
]}
media={{ src: placeholderImage, alt: "Left arrangement" }}
/>
@@ -352,8 +315,7 @@ export default function FeatureTwoColumnShowcase() {
<strong>Arrange Right</strong> - <code>arrange="right"</code>
<br />
<small className="text-muted">
Desktop: Content right, media left | Mobile/Tablet: Media
above content
Desktop: Content right, media left | Mobile/Tablet: Media above content
</small>
</div>
</PageGridCol>
@@ -366,7 +328,7 @@ export default function FeatureTwoColumnShowcase() {
description="This content appears on the right side on desktop, and below the media on mobile/tablet. The media-first approach works well for visual hierarchy."
links={[
{ label: "Primary", href: "#primary" },
{ label: "Learn More", href: "#secondary" },
{ label: "Learn More", href: "#secondary" }
]}
media={{ src: placeholderImage, alt: "Right arrangement" }}
/>
@@ -378,8 +340,7 @@ export default function FeatureTwoColumnShowcase() {
<PageGridCol span={12}>
<h2 className="h4 mb-6">Alternating Pattern</h2>
<p className="mb-6">
Use alternating arrangements and colors to create visual rhythm
on feature-heavy pages.
Use alternating arrangements and colors to create visual rhythm on feature-heavy pages.
</p>
</PageGridCol>
</PageGridRow>
@@ -401,7 +362,7 @@ export default function FeatureTwoColumnShowcase() {
description="Build powerful applications on XRPL with comprehensive documentation and tools."
links={[
{ label: "Get Started", href: "#start" },
{ label: "Documentation", href: "#docs" },
{ label: "Documentation", href: "#docs" }
]}
media={{ src: placeholderImage, alt: "Second feature" }}
/>
@@ -413,7 +374,7 @@ export default function FeatureTwoColumnShowcase() {
description="Scale your business with blockchain technology and enterprise-grade solutions."
links={[
{ label: "Contact Sales", href: "#contact" },
{ label: "View Plans", href: "#plans" },
{ label: "View Plans", href: "#plans" }
]}
media={{ src: placeholderImage, alt: "Third feature" }}
/>
@@ -426,7 +387,7 @@ export default function FeatureTwoColumnShowcase() {
links={[
{ label: "Start Building", href: "#build" },
{ label: "Tutorials", href: "#tutorials" },
{ label: "API Reference", href: "#api" },
{ label: "API Reference", href: "#api" }
]}
media={{ src: placeholderImage, alt: "Fourth feature" }}
/>
@@ -438,25 +399,22 @@ export default function FeatureTwoColumnShowcase() {
<h2 className="h4 mb-6">Design References</h2>
<div className="d-flex flex-column gap-3">
<div>
<strong>Figma Design:</strong>{" "}
<a
href="https://www.figma.com/design/3tmqxMrEvOVvpYhgOCxv2D/Pattern-Feature---Two-Column?node-id=20017-3501&m=dev"
target="_blank"
rel="noopener noreferrer"
>
<strong>Figma Design:</strong>{' '}
<a href="https://www.figma.com/design/3tmqxMrEvOVvpYhgOCxv2D/Pattern-Feature---Two-Column?node-id=20017-3501&m=dev" target="_blank" rel="noopener noreferrer">
Pattern - Feature - Two Column (Figma)
</a>
</div>
<div>
<strong>Component Location:</strong>{" "}
<code>shared/sections/FeatureTwoColumn/</code>
<strong>Component Location:</strong>{' '}
<code>shared/patterns/FeatureTwoColumn/</code>
</div>
<div>
<strong>Color Tokens:</strong>{" "}
<strong>Color Tokens:</strong>{' '}
<code>styles/_colors.scss</code>
</div>
<div>
<strong>Typography:</strong> <code>styles/_font.scss</code>
<strong>Typography:</strong>{' '}
<code>styles/_font.scss</code>
</div>
</div>
</PageGridCol>
@@ -466,3 +424,4 @@ export default function FeatureTwoColumnShowcase() {
</div>
);
}

View File

@@ -1,9 +1,6 @@
import * as React from "react";
import clsx from "clsx";
import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";
type BorderedColProps = React.ComponentProps<typeof PageGridCol>;
export const frontmatter = {
seo: {
title: 'PageGrid Showcase',
@@ -22,22 +19,36 @@ const GridDemo = ({ title, description, children, code }: {
<h3 className="h4 mb-4">{title}</h3>
{description && <p className="mb-6">{description}</p>}
{code && (
<div className={clsx("mb-6 br-4", "grid-showcase-code-block")}>
<pre>{code}</pre>
<div className="mb-6 p-4 bg-light br-4 text-black" style={{ fontFamily: 'monospace', fontSize: '14px', overflow: 'auto' }}>
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', color: '#000' }}>{code}</pre>
</div>
)}
<div className="grid-showcase-demo">
<div style={{
border: '1px dashed #ccc',
padding: '16px',
backgroundColor: '#f9f9f9',
borderRadius: '8px'
}}>
{children}
</div>
</div>
);
// Bordered column component for visualization (theme styles: .grid-showcase-bordered-col in _landings.scss)
const BorderedCol = ({ children, className, style, ...props }: BorderedColProps) => (
<PageGridCol
// Bordered column component for visualization
const BorderedCol = ({ children, ...props }: { children: React.ReactNode } & any) => (
<PageGridCol
{...props}
className={clsx("grid-showcase-bordered-col", className)}
style={style}
style={{
border: '1px solid #0069ff',
backgroundColor: 'rgba(0, 105, 255, 0.05)',
padding: '16px',
minHeight: '60px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
textAlign: 'center',
...props.style
}}
>
{children}
</PageGridCol>
@@ -266,43 +277,50 @@ export default function GridShowcase() {
<p className="mb-6">
The PageGrid system uses the following breakpoints:
</p>
<div className={clsx("grid-showcase-breakpoints-table", "mb-6")}>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__header">
<div className="grid-showcase-breakpoints-table__cell">Breakpoint</div>
<div className="grid-showcase-breakpoints-table__cell">Min Width</div>
<div className="grid-showcase-breakpoints-table__cell">Columns</div>
<div className="grid-showcase-breakpoints-table__cell">Container Padding</div>
<div style={{ width: '100%', backgroundColor: '#FFFFFF', borderRadius: '4px', overflow: 'hidden', border: '1px solid #EEE', marginBottom: '24px' }}>
{/* Header */}
<div style={{
display: 'grid',
gridTemplateColumns: '1.2fr 1fr 1fr 1.5fr',
borderBottom: '2px solid #E0E0E1',
background: '#FAFAFA'
}}>
<div style={{ padding: '12px', fontWeight: 600 }}>Breakpoint</div>
<div style={{ padding: '12px', fontWeight: 600 }}>Min Width</div>
<div style={{ padding: '12px', fontWeight: 600 }}>Columns</div>
<div style={{ padding: '12px', fontWeight: 600 }}>Container Padding</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell"><code>base</code></div>
<div className="grid-showcase-breakpoints-table__cell">0px</div>
<div className="grid-showcase-breakpoints-table__cell">4 columns</div>
<div className="grid-showcase-breakpoints-table__cell">16px</div>
{/* Rows */}
<div style={{ display: 'grid', gridTemplateColumns: '1.2fr 1fr 1fr 1.5fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}><code>base</code></div>
<div style={{ padding: '12px' }}>0px</div>
<div style={{ padding: '12px' }}>4 columns</div>
<div style={{ padding: '12px' }}>16px</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell"><code>sm</code></div>
<div className="grid-showcase-breakpoints-table__cell">576px</div>
<div className="grid-showcase-breakpoints-table__cell">8 columns</div>
<div className="grid-showcase-breakpoints-table__cell">24px</div>
<div style={{ display: 'grid', gridTemplateColumns: '1.2fr 1fr 1fr 1.5fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}><code>sm</code></div>
<div style={{ padding: '12px' }}>576px</div>
<div style={{ padding: '12px' }}>8 columns</div>
<div style={{ padding: '12px' }}>24px</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell"><code>md</code></div>
<div className="grid-showcase-breakpoints-table__cell">576px</div>
<div className="grid-showcase-breakpoints-table__cell">8 columns</div>
<div className="grid-showcase-breakpoints-table__cell">24px</div>
<div style={{ display: 'grid', gridTemplateColumns: '1.2fr 1fr 1fr 1.5fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}><code>md</code></div>
<div style={{ padding: '12px' }}>576px</div>
<div style={{ padding: '12px' }}>8 columns</div>
<div style={{ padding: '12px' }}>24px</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell"><code>lg</code></div>
<div className="grid-showcase-breakpoints-table__cell">992px</div>
<div className="grid-showcase-breakpoints-table__cell">12 columns</div>
<div className="grid-showcase-breakpoints-table__cell">32px</div>
<div style={{ display: 'grid', gridTemplateColumns: '1.2fr 1fr 1fr 1.5fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}><code>lg</code></div>
<div style={{ padding: '12px' }}>992px</div>
<div style={{ padding: '12px' }}>12 columns</div>
<div style={{ padding: '12px' }}>32px</div>
</div>
<div className="grid-showcase-breakpoints-table__grid grid-showcase-breakpoints-table__row">
<div className="grid-showcase-breakpoints-table__cell"><code>xl</code></div>
<div className="grid-showcase-breakpoints-table__cell">1280px</div>
<div className="grid-showcase-breakpoints-table__cell">12 columns</div>
<div className="grid-showcase-breakpoints-table__cell">112px (max-width: 1440px)</div>
<div style={{ display: 'grid', gridTemplateColumns: '1.2fr 1fr 1fr 1.5fr' }}>
<div style={{ padding: '12px' }}><code>xl</code></div>
<div style={{ padding: '12px' }}>1280px</div>
<div style={{ padding: '12px' }}>12 columns</div>
<div style={{ padding: '12px' }}>112px (max-width: 1440px)</div>
</div>
</div>
</div>
@@ -316,13 +334,13 @@ export default function GridShowcase() {
<h2 className="h3 mb-6">Usage</h2>
<h4 className="h5 mb-4">Import</h4>
<div className={clsx("mb-6 br-4", "grid-showcase-code-block")}>
<pre>{`import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";`}</pre>
<div className="p-4 bg-light br-4 mb-6" style={{ fontFamily: 'monospace', fontSize: '14px' }}>
<pre style={{ margin: 0, color: '#000' }}>{`import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";`}</pre>
</div>
<h4 className="h5 mb-4">Basic Example</h4>
<div className={clsx("mb-6 br-4", "grid-showcase-code-block")}>
<pre>{`<PageGrid>
<div className="p-4 bg-light br-4 mb-6" style={{ fontFamily: 'monospace', fontSize: '14px', overflow: 'auto' }}>
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', color: '#000' }}>{`<PageGrid>
<PageGrid.Row>
<PageGrid.Col span={6}>
Column 1

View File

@@ -4,7 +4,7 @@ import {
PageGridRow,
PageGridCol,
} from "shared/components/PageGrid/page-grid";
import HeaderHeroPrimaryMedia from "shared/sections/HeaderHeroPrimaryMedia/HeaderHeroPrimaryMedia";
import HeaderHeroPrimaryMedia from "shared/patterns/HeaderHeroPrimaryMedia/HeaderHeroPrimaryMedia";
export const frontmatter = {
seo: {
@@ -110,8 +110,8 @@ export default function HeaderHeroPrimaryMediaShowcase() {
code={`<HeaderHeroPrimaryMedia
headline="Build on XRPL"
subtitle="Start developing today with our comprehensive developer tools and APIs."
links={[
{ label: "Get Started", href: "/docs" }
callsToAction={[
{ children: "Get Started", href: "/docs" }
]}
media={{
type: "image",
@@ -123,7 +123,7 @@ export default function HeaderHeroPrimaryMediaShowcase() {
<HeaderHeroPrimaryMedia
headline="Build on XRPL"
subtitle="Start developing today with our comprehensive developer tools and APIs."
links={[{ label: "Get Started", href: "/docs" }]}
callsToAction={[{ children: "Get Started", href: "/docs" }]}
media={{
type: "image",
src: placeholderImage,
@@ -143,9 +143,9 @@ export default function HeaderHeroPrimaryMediaShowcase() {
code={`<HeaderHeroPrimaryMedia
headline="Real-world asset tokenization"
subtitle="Learn how to issue crypto tokens and build tokenization solutions."
links={[
{ label: "Get Started", href: "/docs" },
{ label: "Learn More", href: "/about" }
callsToAction={[
{ children: "Get Started", href: "/docs" },
{ children: "Learn More", href: "/about" }
]}
media={{
type: "image",
@@ -157,9 +157,9 @@ export default function HeaderHeroPrimaryMediaShowcase() {
<HeaderHeroPrimaryMedia
headline="Real-world asset tokenization"
subtitle="Learn how to issue crypto tokens and build tokenization solutions."
links={[
{ label: "Get Started", href: "/docs" },
{ label: "Learn More", href: "/about" },
callsToAction={[
{ children: "Get Started", href: "/docs" },
{ children: "Learn More", href: "/about" },
]}
media={{
type: "image",
@@ -180,8 +180,8 @@ export default function HeaderHeroPrimaryMediaShowcase() {
code={`<HeaderHeroPrimaryMedia
headline="Watch and Learn"
subtitle="Explore our video tutorials and guides."
links={[
{ label: "Watch Tutorials", href: "/tutorials" }
callsToAction={[
{ children: "Watch Tutorials", href: "/tutorials" }
]}
media={{
type: "video",
@@ -197,8 +197,8 @@ export default function HeaderHeroPrimaryMediaShowcase() {
<HeaderHeroPrimaryMedia
headline="Watch and Learn"
subtitle="Explore our video tutorials and guides."
links={[
{ label: "Watch Tutorials", href: "/tutorials" },
callsToAction={[
{ children: "Watch Tutorials", href: "/tutorials" },
]}
media={{
type: "video",
@@ -223,8 +223,8 @@ export default function HeaderHeroPrimaryMediaShowcase() {
code={`<HeaderHeroPrimaryMedia
headline="Interactive Experience"
subtitle="Engage with our custom interactive media."
links={[
{ label: "Explore", href: "/interactive" }
callsToAction={[
{ children: "Explore", href: "/interactive" }
]}
media={{
type: "custom",
@@ -235,7 +235,7 @@ export default function HeaderHeroPrimaryMediaShowcase() {
<HeaderHeroPrimaryMedia
headline="Interactive Experience"
subtitle="Engage with our custom interactive media."
links={[{ label: "Explore", href: "/interactive" }]}
callsToAction={[{ children: "Explore", href: "/interactive" }]}
media={{
type: "custom",
element: <SampleAnimation />,
@@ -254,8 +254,8 @@ export default function HeaderHeroPrimaryMediaShowcase() {
code={`<HeaderHeroPrimaryMedia
headline="Optimized Images"
subtitle="Use native image attributes for performance and security."
links={[
{ label: "View Gallery", href: "/gallery" }
callsToAction={[
{ children: "View Gallery", href: "/gallery" }
]}
media={{
type: "image",
@@ -270,7 +270,7 @@ export default function HeaderHeroPrimaryMediaShowcase() {
<HeaderHeroPrimaryMedia
headline="Optimized Images"
subtitle="Use native image attributes for performance and security."
links={[{ label: "View Gallery", href: "/gallery" }]}
callsToAction={[{ children: "View Gallery", href: "/gallery" }]}
media={{
type: "image",
src: placeholderImage,
@@ -292,8 +292,8 @@ export default function HeaderHeroPrimaryMediaShowcase() {
code={`<HeaderHeroPrimaryMedia
headline="Video Content"
subtitle="Rich video experiences with full control."
links={[
{ label: "Watch Now", href: "/videos" }
callsToAction={[
{ children: "Watch Now", href: "/videos" }
]}
media={{
type: "video",
@@ -308,7 +308,7 @@ export default function HeaderHeroPrimaryMediaShowcase() {
<HeaderHeroPrimaryMedia
headline="Video Content"
subtitle="Rich video experiences with full control."
links={[{ label: "Watch Now", href: "/videos" }]}
callsToAction={[{ children: "Watch Now", href: "/videos" }]}
media={{
type: "video",
src: placeholderVideo,
@@ -345,8 +345,8 @@ export default function HeaderHeroPrimaryMediaShowcase() {
code={`<HeaderHeroPrimaryMedia
headline="Build on XRPL"
subtitle={undefined}
links={[
{ label: "Get Started", href: "/docs" }
callsToAction={[
{ children: "Get Started", href: "/docs" }
]}
media={{
type: "image",
@@ -358,7 +358,7 @@ export default function HeaderHeroPrimaryMediaShowcase() {
<HeaderHeroPrimaryMedia
headline="Build on XRPL"
subtitle={undefined as any}
links={[{ label: "Get Started", href: "/docs" }]}
callsToAction={[{ children: "Get Started", href: "/docs" }]}
media={{
type: "image",
src: placeholderImage,
@@ -378,9 +378,9 @@ export default function HeaderHeroPrimaryMediaShowcase() {
code={`<HeaderHeroPrimaryMedia
headline="Single Call to Action"
subtitle="Focus on one primary action for better conversion."
links={[
{ label: "Get Started", href: "/docs" }
// No secondary link - this is valid
callsToAction={[
{ children: "Get Started", href: "/docs" }
// No secondary CTA - this is valid
]}
media={{
type: "image",
@@ -392,7 +392,7 @@ export default function HeaderHeroPrimaryMediaShowcase() {
<HeaderHeroPrimaryMedia
headline="Single Call to Action"
subtitle="Focus on one primary action for better conversion."
links={[{ label: "Get Started", href: "/docs" }]}
callsToAction={[{ children: "Get Started", href: "/docs" }]}
media={{
type: "image",
src: placeholderImage,
@@ -412,8 +412,8 @@ export default function HeaderHeroPrimaryMediaShowcase() {
code={`<HeaderHeroPrimaryMedia
headline="Content Without Media"
subtitle="Sometimes you may want to focus purely on the content without media."
links={[
{ label: "Learn More", href: "/about" }
callsToAction={[
{ children: "Learn More", href: "/about" }
]}
media={undefined}
/>`}
@@ -421,7 +421,7 @@ export default function HeaderHeroPrimaryMediaShowcase() {
<HeaderHeroPrimaryMedia
headline="Content Without Media"
subtitle="Sometimes you may want to focus purely on the content without media."
links={[{ label: "Learn More", href: "/about" }]}
callsToAction={[{ children: "Learn More", href: "/about" }]}
media={undefined as any}
/>
</CodeDemo>
@@ -499,9 +499,9 @@ export default function HeaderHeroPrimaryMediaShowcase() {
<code>React.ReactNode</code> - Hero subtitle text
</li>
<li>
<code>links</code> (optional) -{" "}
<code>DesignConstrainedLink[]</code> - Array of{" "}
<code>{`{ label, href }`}</code> for ButtonGroup
<code>callsToAction</code> (required) -{" "}
<code>[ButtonProps, ButtonProps?]</code> - Array with
primary CTA (required) and optional secondary CTA
</li>
<li>
<code>media</code> (required) - <code>HeaderHeroMedia</code>{" "}
@@ -568,12 +568,11 @@ export default function HeaderHeroPrimaryMediaShowcase() {
</ul>
</div>
<h4 className="h5 mb-4">Links (ButtonGroup)</h4>
<h4 className="h5 mb-4">Button Props (callsToAction)</h4>
<p className="mb-4">
The <code>links</code> prop accepts an array of{" "}
<code>{`{ label, href }`}</code> objects for consistent
ButtonGroup rendering; <code>variant</code> and{" "}
<code>color</code> are set by the component:
The <code>callsToAction</code> prop accepts Button component
props, but <code>variant</code> and <code>color</code> are
automatically set:
</p>
<ul>
<li>
@@ -606,7 +605,7 @@ export default function HeaderHeroPrimaryMediaShowcase() {
style={{ fontFamily: "monospace", fontSize: "14px" }}
>
<pre style={{ margin: 0, color: "#000" }}>
{`import HeaderHeroPrimaryMedia from "shared/sections/HeaderHeroPrimaryMedia/HeaderHeroPrimaryMedia";`}
{`import HeaderHeroPrimaryMedia from "shared/patterns/HeaderHeroPrimaryMedia/HeaderHeroPrimaryMedia";`}
</pre>
</div>
@@ -625,8 +624,8 @@ export default function HeaderHeroPrimaryMediaShowcase() {
{`<HeaderHeroPrimaryMedia
headline="Build on XRPL"
subtitle="Start developing today with our comprehensive developer tools."
links={[
{ label: "Get Started", href: "/docs" }
callsToAction={[
{ children: "Get Started", href: "/docs" }
]}
media={{
type: "image",
@@ -652,9 +651,9 @@ export default function HeaderHeroPrimaryMediaShowcase() {
{`<HeaderHeroPrimaryMedia
headline="Real-world asset tokenization"
subtitle="Learn how to issue crypto tokens and build solutions."
links={[
{ label: "Get Started", href: "/docs" },
{ label: "Learn More", href: "/about" }
callsToAction={[
{ children: "Get Started", href: "/docs" },
{ children: "Learn More", href: "/about" }
]}
media={{
type: "image",
@@ -680,8 +679,8 @@ export default function HeaderHeroPrimaryMediaShowcase() {
{`<HeaderHeroPrimaryMedia
headline="Watch and Learn"
subtitle="Explore our video tutorials."
links={[
{ label: "Watch Tutorials", href: "/tutorials" }
callsToAction={[
{ children: "Watch Tutorials", href: "/tutorials" }
]}
media={{
type: "video",
@@ -710,8 +709,8 @@ export default function HeaderHeroPrimaryMediaShowcase() {
{`<HeaderHeroPrimaryMedia
headline="Interactive Experience"
subtitle="Engage with custom media."
links={[
{ label: "Explore", href: "/interactive" }
callsToAction={[
{ children: "Explore", href: "/interactive" }
]}
media={{
type: "custom",

View File

@@ -1,6 +1,6 @@
import * as React from 'react';
import { PageGrid, PageGridRow, PageGridCol } from 'shared/components/PageGrid/page-grid';
import { HeaderHeroSplitMedia } from 'shared/sections/HeaderHeroSplitMedia';
import { HeaderHeroSplitMedia } from 'shared/patterns/HeaderHeroSplitMedia';
export const frontmatter = {
seo: {
@@ -491,7 +491,7 @@ export default function HeaderHeroSplitMediaShowcase() {
</div>
<div className="p-6-sm p-10-until-sm br-8 mb-6" style={{ backgroundColor: '#1e1e1e', color: '#d4d4d4' }}>
<pre style={{ margin: 0, overflow: 'auto' }}>
<code>{`import { HeaderHeroSplitMedia } from 'shared/sections/HeaderHeroSplitMedia';
<code>{`import { HeaderHeroSplitMedia } from 'shared/patterns/HeaderHeroSplitMedia';
// Full content - all props provided
<HeaderHeroSplitMedia
@@ -679,7 +679,7 @@ export default function HeaderHeroSplitMediaShowcase() {
</div>
<div>
<strong>Documentation:</strong>{' '}
<code>shared/sections/HeaderHeroSplitMedia/HeaderHeroSplitMedia.md</code>
<code>shared/patterns/HeaderHeroSplitMedia/HeaderHeroSplitMedia.md</code>
</div>
</div>
</PageGridCol>

View File

@@ -115,60 +115,6 @@ export default function LinkShowcase() {
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Sizes</h2>
<p className="mb-4">Typography and icon sizes are responsive, scaling from Mobile/Tablet to Desktop (xl: 1280px+).</p>
{/* Size Specs Table */}
<div className="mb-6">
<h6 className="mb-3">Typography Specs</h6>
<table className="w-100 mb-6" style={{ fontSize: '14px' }}>
<thead>
<tr className="text-muted">
<th style={{ fontWeight: 'normal', paddingBottom: '8px' }}>Size</th>
<th style={{ fontWeight: 'normal', paddingBottom: '8px' }}>Mobile/Tablet</th>
<th style={{ fontWeight: 'normal', paddingBottom: '8px' }}>Desktop (xl)</th>
<th style={{ fontWeight: 'normal', paddingBottom: '8px' }}>Gap</th>
</tr>
</thead>
<tbody className="text-muted">
<tr><td><strong>Small</strong></td><td>14px / 20.1px line</td><td>16px / 23.2px line</td><td>16px</td></tr>
<tr><td><strong>Medium</strong></td><td>16px / 23.2px line</td><td>18px / 26.1px line, -0.5px letter</td><td>16px</td></tr>
<tr><td><strong>Large</strong></td><td>18px / 30px line, -0.5px letter</td><td>24px / 30px line</td><td>24px</td></tr>
</tbody>
</table>
<h6 className="mb-3">Internal Arrow Icon Sizes</h6>
<table className="w-100 mb-6" style={{ fontSize: '14px' }}>
<thead>
<tr className="text-muted">
<th style={{ fontWeight: 'normal', paddingBottom: '8px' }}>Size</th>
<th style={{ fontWeight: 'normal', paddingBottom: '8px' }}>Mobile/Tablet</th>
<th style={{ fontWeight: 'normal', paddingBottom: '8px' }}>Desktop (xl)</th>
</tr>
</thead>
<tbody className="text-muted">
<tr><td><strong>Small</strong></td><td>14×13px</td><td>15×14px</td></tr>
<tr><td><strong>Medium</strong></td><td>17×14px</td><td>17×16px</td></tr>
<tr><td><strong>Large</strong></td><td>20×16px</td><td>26×22px</td></tr>
</tbody>
</table>
<h6 className="mb-3">External Arrow Icon Sizes</h6>
<table className="w-100 mb-6" style={{ fontSize: '14px' }}>
<thead>
<tr className="text-muted">
<th style={{ fontWeight: 'normal', paddingBottom: '8px' }}>Size</th>
<th style={{ fontWeight: 'normal', paddingBottom: '8px' }}>Mobile/Tablet</th>
<th style={{ fontWeight: 'normal', paddingBottom: '8px' }}>Desktop (xl)</th>
</tr>
</thead>
<tbody className="text-muted">
<tr><td><strong>Small</strong></td><td>14×14px</td><td>14×14px</td></tr>
<tr><td><strong>Medium</strong></td><td>16×16px</td><td>16×16px</td></tr>
<tr><td><strong>Large</strong></td><td>17×17px</td><td>21×21px</td></tr>
</tbody>
</table>
</div>
<div className="d-flex flex-column gap-4 mb-10">
<div>
<h6 className="mb-3">Small</h6>
@@ -198,7 +144,7 @@ export default function LinkShowcase() {
<PageGridCol span={12}>
<h2 className="h4 mb-6">Color States</h2>
<p className="mb-4">Links automatically handle color states via CSS per theme:</p>
<div className="d-flex flex-row gap-6 mb-6" style={{ flexWrap: 'wrap' }}>
{/* Light Mode Colors */}
<div style={{ flex: '1 1 300px', minWidth: '280px' }}>
@@ -207,12 +153,12 @@ export default function LinkShowcase() {
<li><strong>Enabled:</strong> Green 400 <code style={{ color: '#0DAA3E' }}>#0DAA3E</code></li>
<li><strong>Hover/Focus:</strong> Green 500 <code style={{ color: '#078139' }}>#078139</code> + underline</li>
<li><strong>Active:</strong> Green 400 <code style={{ color: '#0DAA3E' }}>#0DAA3E</code> + underline</li>
<li><strong>Visited:</strong> No change <small className="text-muted">(stays Green 400)</small></li>
<li><strong>Disabled:</strong> Gray 400 <code style={{ color: '#8A919A' }}>#8A919A</code></li>
<li><strong>Focus Outline:</strong> Black <code style={{ color: '#141414' }}>#141414</code></li>
<li><strong>Visited:</strong> Lilac 400 <code style={{ color: '#7649E3' }}>#7649E3</code></li>
<li><strong>Disabled:</strong> Gray 400 <code style={{ color: '#A2A2A4' }}>#A2A2A4</code></li>
<li><strong>Focus Outline:</strong> Black <code style={{ color: '#000000' }}>#000000</code></li>
</ul>
</div>
{/* Dark Mode Colors */}
<div style={{ flex: '1 1 300px', minWidth: '280px' }}>
<h6 className="mb-3">Dark Mode</h6>
@@ -221,12 +167,12 @@ export default function LinkShowcase() {
<li><strong>Hover/Focus:</strong> Green 200 <code style={{ color: '#70EE97' }}>#70EE97</code> + underline</li>
<li><strong>Active:</strong> Green 300 <code style={{ color: '#21E46B' }}>#21E46B</code> + underline</li>
<li><strong>Visited:</strong> Lilac 300 <code style={{ color: '#C0A7FF' }}>#C0A7FF</code></li>
<li><strong>Disabled:</strong> Gray 400 <code style={{ color: '#8A919A' }}>#8A919A</code></li>
<li><strong>Disabled:</strong> Gray 500 <code style={{ color: '#838386' }}>#838386</code></li>
<li><strong>Focus Outline:</strong> White <code style={{ color: '#FFFFFF', backgroundColor: '#333', padding: '0 4px' }}>#FFFFFF</code></li>
</ul>
</div>
</div>
<div className="d-flex flex-column gap-4 mb-10">
<div>
<h6 className="mb-3">Default (hover to see state changes and arrow animation)</h6>
@@ -248,30 +194,17 @@ export default function LinkShowcase() {
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Icon Types & Animation</h2>
<p className="mb-4">Arrow icons animate to chevron shape on hover/focus using CSS transform.</p>
{/* Animation Specs */}
<div className="mb-6 p-4 br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h6 className="mb-3">Animation Specs (per Figma)</h6>
<ul className="mb-0 text-muted">
<li><strong>Duration:</strong> 150ms</li>
<li><strong>Timing:</strong> <code>cubic-bezier(0.98, 0.12, 0.12, 0.98)</code></li>
<li><strong>Internal arrow:</strong> Horizontal line scales to 0 from right (transform-origin: right center)</li>
<li><strong>External arrow:</strong> Diagonal line scales to 0 toward top-right corner</li>
<li><strong>Disabled state:</strong> Animation disabled, icon opacity 0.5</li>
</ul>
</div>
<h2 className="h4 mb-6">Icon Types</h2>
<p className="mb-4">Arrow icons animate to chevron shape on hover (150ms cubic-bezier transition).</p>
<div className="d-flex flex-column gap-4 mb-10">
<div>
<h6 className="mb-3">Arrow (Internal) - horizontal line shrinks to chevron on hover</h6>
<h6 className="mb-3">Arrow (Internal) - animates to chevron on hover</h6>
<BdsLink href="/docs" size="medium" icon="arrow">
Arrow Link
</BdsLink>
</div>
<div>
<h6 className="mb-3">External - diagonal line shrinks to bracket on hover</h6>
<h6 className="mb-3">External</h6>
<BdsLink href="https://example.com" size="medium" variant="external" target="_blank" rel="noopener noreferrer">
External Link
</BdsLink>

View File

@@ -1,6 +1,6 @@
import * as React from "react";
import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";
import { LogoRectangleGrid } from "shared/sections/LogoRectangleGrid";
import { LogoRectangleGrid } from "shared/patterns/LogoRectangleGrid";
export const frontmatter = {
seo: {
@@ -515,7 +515,7 @@ export default function LogoRectangleGridShowcase() {
</div>
<div>
<strong>Component Location:</strong>{' '}
<code>shared/sections/LogoRectangleGrid/</code>
<code>shared/patterns/LogoRectangleGrid/</code>
</div>
<div>
<strong>Color Tokens:</strong>{' '}
@@ -523,7 +523,7 @@ export default function LogoRectangleGridShowcase() {
</div>
<div>
<strong>Related Component:</strong>{' '}
<code>shared/sections/LogoSquareGrid/</code> (square tiles variant)
<code>shared/patterns/LogoSquareGrid/</code> (square tiles variant)
</div>
</div>
</PageGridCol>

View File

@@ -1,6 +1,6 @@
import * as React from "react";
import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";
import { LogoSquareGrid } from "shared/sections/LogoSquareGrid";
import { LogoSquareGrid } from "shared/patterns/LogoSquareGrid";
export const frontmatter = {
seo: {
@@ -64,10 +64,14 @@ export default function LogoSquareGridShowcase() {
variant="green"
heading="Developer tools & APIs"
description="Streamline development and build powerful RWA tokenization solutions with XRP Ledger's comprehensive developer toolset."
buttons={[
{ label: "View Documentation", onClick: () => handleClick('green-primary') },
{ label: "Explore Tools", onClick: () => handleClick('green-tertiary') }
]}
primaryButton={{
label: "View Documentation",
onClick: () => handleClick('green-primary')
}}
tertiaryButton={{
label: "Explore Tools",
onClick: () => handleClick('green-tertiary')
}}
logos={sampleLogos}
/>
@@ -146,10 +150,8 @@ export default function LogoSquareGridShowcase() {
variant="gray"
heading="Our Partners"
description="Leading companies building innovative solutions on the XRP Ledger."
buttons={[
{ label: "View All Partners", href: "#partners" },
{ label: "Become a Partner", href: "#partner-program" }
]}
primaryButton={{ label: "View All Partners", href: "#partners" }}
tertiaryButton={{ label: "Become a Partner", href: "#partner-program" }}
logos={sampleLogos}
/>
</div>
@@ -171,9 +173,7 @@ export default function LogoSquareGridShowcase() {
variant="green"
heading="Featured Integrations"
description="Connect with leading platforms and services built on XRPL."
buttons={[
{ label: "See All Integrations", href: "#integrations" }
]}
primaryButton={{ label: "See All Integrations", href: "#integrations" }}
logos={sampleLogos}
/>
</div>
@@ -247,9 +247,7 @@ export default function LogoSquareGridShowcase() {
variant="green"
heading="Developer Resources"
description="Access comprehensive tools and libraries for building on XRPL."
buttons={[
{ label: "Get Started", onClick: () => handleClick('single-button') }
]}
primaryButton={{ label: "Get Started", onClick: () => handleClick('single-button') }}
logos={smallLogoSet}
/>
</div>
@@ -433,12 +431,20 @@ export default function LogoSquareGridShowcase() {
<div style={{ flex: '1 1 0', minWidth: 0 }}>Optional description text</div>
</div>
{/* buttons */}
{/* primaryButton */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>buttons</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>{`Array<{ label, href?, onClick?, forceColor? }>`}</code></div>
<div style={{ width: '140px', flexShrink: 0 }}><code>primaryButton</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>{`{ label, href?, onClick? }`}</code></div>
<div style={{ width: '120px', flexShrink: 0 }}><code>undefined</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Button configurations (1-2 buttons supported)</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Primary button configuration</div>
</div>
{/* tertiaryButton */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>tertiaryButton</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>{`{ label, href?, onClick? }`}</code></div>
<div style={{ width: '120px', flexShrink: 0 }}><code>undefined</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Tertiary button configuration</div>
</div>
{/* logos */}
@@ -508,7 +514,7 @@ export default function LogoSquareGridShowcase() {
</div>
<div>
<strong>Component Location:</strong>{' '}
<code>shared/sections/LogoSquareGrid/</code>
<code>shared/patterns/LogoSquareGrid/</code>
</div>
<div>
<strong>Color Tokens:</strong>{' '}

View File

@@ -4,7 +4,7 @@ import {
PageGridRow,
PageGridCol,
} from "shared/components/PageGrid/page-grid";
import { SmallTilesSection } from "shared/sections/SmallTilesSection/SmallTilesSection";
import { SmallTilesSection } from "shared/components/SmallTilesSection/SmallTilesSection";
export const frontmatter = {
seo: {

View File

@@ -6,7 +6,7 @@ import {
} from "shared/components/PageGrid/page-grid";
import StandardCardGroupSection, {
type StandardCardPropsWithoutVariant,
} from "shared/sections/StandardCardGroupSection/StandardCardGroupSection";
} from "shared/patterns/StandardCardGroupSection/StandardCardGroupSection";
export const frontmatter = {
seo: {
@@ -49,7 +49,9 @@ const CodeDemo = ({
<div
style={{
border: "1px dashed #ccc",
padding: "16px",
backgroundColor: "#f9f9f9",
borderRadius: "8px",
}}
>
{children}
@@ -439,7 +441,7 @@ export default function StandardCardGroupSectionShowcase() {
style={{ fontFamily: "monospace", fontSize: "14px" }}
>
<pre style={{ margin: 0, color: "#000" }}>
{`import StandardCardGroupSection from "shared/sections/StandardCardGroupSection/StandardCardGroupSection";`}
{`import StandardCardGroupSection from "shared/patterns/StandardCardGroupSection/StandardCardGroupSection";`}
</pre>
</div>

View File

@@ -5,7 +5,7 @@ import { TileLogo } from "shared/components/TileLogo";
export const frontmatter = {
seo: {
title: 'TileLogo Component Showcase',
description: "TileLogo showcase: square/rectangle shapes, neutral and green variants, window-shade hover (>991px), instant pressed overlay (≤991px), html.dark pressed neutral #56595E, focus matches hover overlay + ring, SVG logo filters in dark mode.",
description: "A comprehensive showcase of all TileLogo component variants, states, and themes in the XRPL.org Design System.",
}
};
@@ -26,12 +26,9 @@ export default function TileLogoShowcase() {
<h6 className="eyebrow mb-3">Component Showcase</h6>
<h1 className="mb-4">TileLogo Component</h1>
<p className="longform">
A tile component for brand logos with hover, pressed, disabled, and keyboard focus. Square (1:1) and rectangle (9:5)
shapes use responsive padding from the grid. On viewports wider than <strong>991px</strong>, a clip-path window shade
animates the hover overlay (200ms). At <strong>991px and below</strong>, that transition is offhover and{' '}
<code>:focus-visible</code> jump straight to the <strong>pressed</strong> overlay so quick taps do not glitch. On wider screens,{' '}
<code>:focus-visible</code> uses the <strong>same overlay color as hover</strong> (plus the focus ring). Theming uses{' '}
<code>html.light</code> and <code>html.dark</code>. In <code>html.dark</code>, neutral tiles use <code>$gray-500</code> with a solid pressed overlay <strong>#56595E</strong> (same token is used for hover/focus at 991px). SVG logos on neutral tiles are forced white; green + disabled inverts the logo on the disabled wash.
A tile/card component designed to display brand logos with interactive states.
Supports two shape variants (Square and Rectangle), two color variants (Neutral and Green),
and five interaction states. Sizes are responsive and change based on breakpoints.
</p>
</div>
</section>
@@ -159,11 +156,9 @@ export default function TileLogoShowcase() {
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Interaction States: Neutral Variant</h2>
<p className="mb-4 longform">
Hover for the overlay wipe on large screens; click and hold for pressed. Tab to focus: same overlay fill as hover (with the focus ring)
via <code>:focus-visible</code>; on <strong>991px</strong>, hover and focus jump to the pressed overlay with no transition.
Tile labels list <strong>light</strong> tokens by defaultsee <strong>html.dark</strong> in the Color Reference (neutral default{' '}
<code>$gray-500</code>, pressed overlay <code>#56595E</code>).
<p className="mb-4">
Hover over and interact with the tiles below to see the different states.
Use Tab key to see focus states.
</p>
<PageGridRow>
@@ -180,13 +175,11 @@ export default function TileLogoShowcase() {
<strong>Default</strong>
<br />
<code className="small">$gray-200</code>
<br />
<small className="text-muted">html.dark: $gray-500</small>
</div>
</div>
</PageGridCol>
{/* Hover */}
{/* Hover - Note: This shows the default, users hover to see state */}
<PageGridCol span={{ base: 4, sm: 4, lg: 2 }}>
<div className="d-flex flex-column align-items-center">
<TileLogo
@@ -199,13 +192,11 @@ export default function TileLogoShowcase() {
<strong>Hover</strong>
<br />
<code className="small">$gray-300</code>
<br />
<small className="text-muted">html.dark: $gray-400</small>
</div>
</div>
</PageGridCol>
{/* Focus */}
{/* Focus - Users tab to see */}
<PageGridCol span={{ base: 4, sm: 4, lg: 2 }}>
<div className="d-flex flex-column align-items-center">
<TileLogo
@@ -217,14 +208,12 @@ export default function TileLogoShowcase() {
<div className="mt-3 text-center">
<strong>Focused</strong>
<br />
<code className="small">hover overlay + ring</code>
<br />
<small className="text-muted">991px: pressed overlay</small>
<code className="small">$gray-300 + border</code>
</div>
</div>
</PageGridCol>
{/* Pressed */}
{/* Pressed - Users click to see */}
<PageGridCol span={{ base: 4, sm: 4, lg: 2 }}>
<div className="d-flex flex-column align-items-center">
<TileLogo
@@ -237,8 +226,6 @@ export default function TileLogoShowcase() {
<strong>Pressed</strong>
<br />
<code className="small">$gray-400</code>
<br />
<small className="text-muted">html.dark: #56595E</small>
</div>
</div>
</PageGridCol>
@@ -256,8 +243,6 @@ export default function TileLogoShowcase() {
<strong>Disabled</strong>
<br />
<code className="small">$gray-100</code>
<br />
<small className="text-muted">html.dark: rgba($gray-500, 0.3)</small>
</div>
</div>
</PageGridCol>
@@ -271,10 +256,8 @@ export default function TileLogoShowcase() {
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Interaction States: Green Variant</h2>
<p className="mb-4 longform">
Same model as neutral: on wide screens hover wipes and <code>:focus-visible</code> shows the <strong>hover</strong> overlay color
plus the ring; at <strong>991px</strong>, hover and focus use the <strong>pressed</strong> overlay (<code>$green-400</code>) with no
transition. In <code>html.dark</code>, disabled green tiles use <code>rgba($gray-500, 0.3)</code> and force the logo white.
<p className="mb-4">
The green variant follows the same interaction pattern but uses the brand green color palette.
</p>
<PageGridRow>
@@ -291,8 +274,6 @@ export default function TileLogoShowcase() {
<strong>Default</strong>
<br />
<code className="small">$green-200</code>
<br />
<small className="text-muted">same in html.dark</small>
</div>
</div>
</PageGridCol>
@@ -310,8 +291,6 @@ export default function TileLogoShowcase() {
<strong>Hover</strong>
<br />
<code className="small">$green-300</code>
<br />
<small className="text-muted">same in html.dark</small>
</div>
</div>
</PageGridCol>
@@ -328,9 +307,7 @@ export default function TileLogoShowcase() {
<div className="mt-3 text-center">
<strong>Focused</strong>
<br />
<code className="small">hover overlay + ring</code>
<br />
<small className="text-muted">991px: pressed overlay</small>
<code className="small">$green-300 + border</code>
</div>
</div>
</PageGridCol>
@@ -348,8 +325,6 @@ export default function TileLogoShowcase() {
<strong>Pressed</strong>
<br />
<code className="small">$green-400</code>
<br />
<small className="text-muted">same in html.dark</small>
</div>
</div>
</PageGridCol>
@@ -367,8 +342,6 @@ export default function TileLogoShowcase() {
<strong>Disabled</strong>
<br />
<code className="small">$gray-100</code>
<br />
<small className="text-muted">html.dark: same bg + logo white</small>
</div>
</div>
</PageGridCol>
@@ -382,19 +355,12 @@ export default function TileLogoShowcase() {
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Color Token Reference</h2>
<p className="mb-4">
Colors come from <code>styles/_colors.scss</code>. Base rules target the default (light) tile palette; <code>html.dark</code> and{' '}
<code>html.light</code> override focus borders and theme-specific surfaces (see <code>TileLogo.scss</code>).
</p>
<p className="mb-4 longform">
<strong>Viewports &gt;991px:</strong> hover animates the overlay; <code>:focus-visible</code> shows the <strong>same overlay color as hover</strong> (full <code>clip-path</code>) plus the focus ring; <code>:active</code> switches the overlay to the <strong>pressed</strong> color while the pointer is down.{' '}
<strong>991px:</strong> overlay <code>transition: none</code>; hover and <code>:focus-visible</code> jump straight to the pressed overlaylight neutral/green use <code>$gray-400</code> / <code>$green-400</code>; <code>html.dark</code> neutral uses opaque <strong>#56595E</strong> (not <code>$gray-400</code>, which is only the wide-screen hover there).
</p>
<p className="mb-4">All colors are mapped from <code>styles/_colors.scss</code>. The component uses <code>html.dark</code> selector for dark mode styles.</p>
<div className="d-flex flex-row gap-6 mb-6" style={{ flexWrap: 'wrap' }}>
{/* Light Mode Colors */}
<div style={{ flex: '1 1 400px', minWidth: '320px' }}>
<h6 className="mb-4">Light Mode (default / <code>html.light</code>)</h6>
<h6 className="mb-4">Light Mode</h6>
<div className="mb-4">
<strong className="d-block mb-2">Neutral Variant</strong>
@@ -405,11 +371,11 @@ export default function TileLogoShowcase() {
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#CAD4DF', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Hover: $gray-300</code> <small className="text-muted">#CAD4DF (wipe &gt;991px; 991px pressed)</small></div>
<div><code>Hover/Focus: $gray-300</code> <small className="text-muted">#CAD4DF</small></div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#8A919A', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Pressed: $gray-400</code> <small className="text-muted">#8A919A (also hover/focus overlay 991px)</small></div>
<div><code>Pressed: $gray-400</code> <small className="text-muted">#8A919A</small></div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#F0F3F7', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
@@ -427,11 +393,11 @@ export default function TileLogoShowcase() {
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#21E46B', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Hover: $green-300</code> <small className="text-muted">#21E46B (wipe &gt;991px; 991px pressed)</small></div>
<div><code>Hover/Focus: $green-300</code> <small className="text-muted">#21E46B</small></div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#0DAA3E', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Pressed: $green-400</code> <small className="text-muted">#0DAA3E (also hover/focus overlay 991px)</small></div>
<div><code>Pressed: $green-400</code> <small className="text-muted">#0DAA3E</small></div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#F0F3F7', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
@@ -441,10 +407,10 @@ export default function TileLogoShowcase() {
</div>
<div className="mt-4">
<strong className="d-block mb-2">Focus outline</strong>
<strong className="d-block mb-2">Focus Border</strong>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#141414', borderRadius: '4px', flexShrink: 0 }}></div>
<div><code>$black</code> <small className="text-muted">#141414 (html.light)</small></div>
<div style={{ width: '32px', height: '32px', backgroundColor: '#000000', borderRadius: '4px', flexShrink: 0 }}></div>
<div><code>$black</code> <small className="text-muted">#000000</small></div>
</div>
</div>
</div>
@@ -462,17 +428,11 @@ export default function TileLogoShowcase() {
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#8A919A', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Hover: $gray-400</code> <small className="text-muted">#8A919A (wipe &gt;991px; 991px #56595E)</small></div>
<div><code>Hover/Focus: $gray-400</code> <small className="text-muted">#8A919A</small></div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#56595E', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div>
<code>Pressed: $bds-tile-logo-neutral-pressed-dark (#56595E)</code>
<br />
<small className="text-muted">
opaque overlay on <code>$gray-500</code> tile; <code>:active</code> and 991px hover/focus (html.dark neutral)
</small>
</div>
<div style={{ width: '32px', height: '32px', backgroundColor: 'rgba(114,119,126,0.7)', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Pressed: rgba($gray-500, 0.7)</code></div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: 'rgba(114,119,126,0.3)', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
@@ -490,32 +450,24 @@ export default function TileLogoShowcase() {
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#21E46B', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Hover: $green-300</code> <small className="text-muted">#21E46B (wipe &gt;991px; 991px pressed)</small></div>
<div><code>Hover/Focus: $green-300</code> <small className="text-muted">#21E46B</small></div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#0DAA3E', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Pressed: $green-400</code> <small className="text-muted">#0DAA3E (also hover/focus overlay 991px)</small></div>
<div><code>Pressed: $green-400</code> <small className="text-muted">#0DAA3E</small></div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: 'rgba(114,119,126,0.3)', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Disabled: rgba($gray-500, 0.3)</code> <small className="text-muted">logo white</small></div>
<div><code>Disabled: rgba($gray-500, 0.3)</code></div>
</div>
</div>
</div>
<div className="mt-4">
<strong className="d-block mb-2">Logo asset (<code>html.dark</code>)</strong>
<ul className="small text-muted mb-0 ps-3">
<li><strong>Neutral + SVG</strong> (<code>.svg</code> path or <code>data:image/svg+xml</code>): <code>filter: brightness(0) invert(1)</code> for a white mark.</li>
<li><strong>Green + disabled:</strong> same filter on the <code>&lt;img&gt;</code> so the logo stays visible on the disabled wash.</li>
</ul>
</div>
<div className="mt-4">
<strong className="d-block mb-2">Focus outline</strong>
<strong className="d-block mb-2">Focus Border</strong>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#FFFFFF', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>$white</code> <small className="text-muted">#FFFFFF (html.dark)</small></div>
<div><code>$white</code> <small className="text-muted">#FFFFFF</small></div>
</div>
</div>
</div>
@@ -524,25 +476,6 @@ export default function TileLogoShowcase() {
</PageGridRow>
</PageGrid>
{/* Animation & responsive overlay */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Animation &amp; overlay behavior</h2>
<p className="mb-4 longform">
The hover layer uses the shared window-shade mixins in <code>styles/_animations.scss</code>: <strong>200ms</strong> and{' '}
<code>cubic-bezier(0.98, 0.12, 0.12, 0.98)</code> on <code>clip-path</code>. Implemented in <code>TileLogo.scss</code> as{' '}
<code>.bds-tile-logo__overlay</code>.
</p>
<ul className="mb-0">
<li className="mb-2"><strong>Wider than 991px:</strong> hover animates the overlay open and closed; <code>:focus-visible</code> reveals the <strong>same hover-colored overlay</strong> (plus the focus ring).</li>
<li className="mb-2"><strong>max-width: 991px:</strong> overlay <code>transition: none</code>; <code>.bds-tile-logo--hovered</code> and <code>:focus-visible</code> set the pressed overlay and full <code>clip-path</code> immediatelyneutral uses <code>$gray-400</code> (light) or <strong>#56595E</strong> (<code>html.dark</code>); green uses <code>$green-400</code> in both themes.</li>
<li><strong>Pointer down:</strong> <code>:active</code> keeps the pressed overlay at all breakpoints.</li>
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Real-World Examples */}
<PageGrid className="py-26">
<PageGridRow>
@@ -703,9 +636,7 @@ export default function TileLogoShowcase() {
<div style={{ width: '100px', flexShrink: 0 }}><code>logo</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><em>required</em></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
Logo image URL or path. In <code>html.dark</code>, SVGs on <strong>neutral</strong> use a white filter; <strong>green</strong> + <code>disabled</code> forces the image white (see Color Reference).
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Logo image source (URL or path)</div>
</div>
{/* alt */}

View File

@@ -0,0 +1,656 @@
import * as React from 'react';
import { PageGrid, PageGridRow, PageGridCol } from 'shared/components/PageGrid/page-grid';
import { Divider } from 'shared/components/Divider';
export const frontmatter = {
seo: {
title: 'Typography Showcase',
description: 'A comprehensive showcase of the XRPL.org typography system including displays, headings, subheads, body text, and labels.',
},
};
export default function TypographyShowcase() {
return (
<div className="landing">
<section className="container-new py-26">
<div className="d-flex flex-column-reverse col-lg-8 mx-auto">
<h1 className="mb-0">Typography System</h1>
<h6 className="eyebrow mb-3">Brand Design System</h6>
</div>
<p className="col-lg-8 mx-auto mt-10">
A comprehensive overview of the XRPL typography system featuring responsive type scales,
font families (Booton and Tobias), and semantic styling for consistent visual hierarchy.
</p>
</section>
{/* Display Styles */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Display Styles</h2>
<h6 className="eyebrow mb-3">Largest Headlines</h6>
</div>
<p className="mb-10 text-muted">
Display styles are used for the largest, most prominent headlines. They feature negative letter spacing
and are optimized for impact at large sizes.
</p>
<div className="d-flex flex-column gap-10">
<div>
<div className="mb-3">
<code className="text-muted">display-lg</code>
<span className="text-muted mx-2">·</span>
<span className="text-muted">Booton Light (300)</span>
</div>
<div className="display-lg">Display Large</div>
<div className="mt-3 text-muted">
<small>Mobile: 64px · Tablet: 72px · Desktop: 92px · XL: 112px</small>
</div>
</div>
<div>
<div className="mb-3">
<code className="text-muted">display-md</code>
<span className="text-muted mx-2">·</span>
<span className="text-muted">Tobias Light (300)</span>
</div>
<div className="display-md">Display Medium</div>
<div className="mt-3 text-muted">
<small>Mobile: 48px · Tablet: 60px · Desktop: 72px</small>
</div>
</div>
<div>
<div className="mb-3">
<code className="text-muted">display-sm</code>
<span className="text-muted mx-2">·</span>
<span className="text-muted">Tobias Light (300)</span>
</div>
<div className="display-sm">Display Small</div>
<div className="mt-3 text-muted">
<small>Mobile: 40px · Tablet: 56px · Desktop: 64px</small>
</div>
</div>
</div>
</section>
<Divider color="gray" weight="thin" />
{/* Heading Styles */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Heading Styles</h2>
<h6 className="eyebrow mb-3">Section Headers</h6>
</div>
<p className="mb-10 text-muted">
Heading styles are used for major section headers and page titles. All headings use the Tobias monospace
font family for a distinctive technical aesthetic.
</p>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 4 }}>
<div className="mb-6">
<div className="mb-3">
<code className="text-muted">heading-lg / .h-lg</code>
</div>
<div className="h-lg">Heading Large</div>
<div className="mt-3 text-muted">
<small>Mobile: 36px · Tablet: 42px · Desktop: 48px</small>
</div>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 4 }}>
<div className="mb-6">
<div className="mb-3">
<code className="text-muted">heading-md / .h-md</code>
</div>
<div className="h-md">Heading Medium</div>
<div className="mt-3 text-muted">
<small>Mobile: 32px · Tablet: 36px · Desktop: 40px</small>
</div>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 4 }}>
<div className="mb-6">
<div className="mb-3">
<code className="text-muted">heading-sm / .h-sm</code>
</div>
<div className="h-sm">Heading Small</div>
<div className="mt-3 text-muted">
<small>Mobile: 24px · Tablet: 28px · Desktop: 32px</small>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">Legacy H1-H6 Styles</h5>
<p className="mb-6 text-muted">
Traditional semantic HTML heading tags are also supported with responsive sizing.
</p>
<div className="d-flex flex-column gap-6">
<div>
<code className="text-muted mb-2 d-block">h1, .h1</code>
<h1 className="mb-2">The quick brown fox jumps</h1>
<small className="text-muted">Desktop: 62px / Mobile: 42px</small>
</div>
<div>
<code className="text-muted mb-2 d-block">h2, .h2</code>
<h2 className="mb-2">The quick brown fox jumps</h2>
<small className="text-muted">Desktop: 56px / Mobile: 28px</small>
</div>
<div>
<code className="text-muted mb-2 d-block">h3, .h3</code>
<h3 className="mb-2">The quick brown fox jumps</h3>
<small className="text-muted">Desktop: 48px / Mobile: 24px</small>
</div>
<div>
<code className="text-muted mb-2 d-block">h4, .h4</code>
<h4 className="mb-2">The quick brown fox jumps over the lazy dog</h4>
<small className="text-muted">Desktop: 32px / Mobile: 20px</small>
</div>
<div>
<code className="text-muted mb-2 d-block">h5, .h5</code>
<h5 className="mb-2">The quick brown fox jumps over the lazy dog</h5>
<small className="text-muted">Desktop: 24px / Mobile: 18px</small>
</div>
<div>
<code className="text-muted mb-2 d-block">h6, .h6</code>
<h6 className="mb-2">The quick brown fox jumps over the lazy dog</h6>
<small className="text-muted">Desktop: 20px / Mobile: 16px</small>
</div>
</div>
</div>
</section>
<Divider color="gray" weight="thin" />
{/* Subhead Styles */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Subhead Styles</h2>
<h6 className="eyebrow mb-3">Supporting Headers</h6>
</div>
<p className="mb-10 text-muted">
Subheads provide intermediate hierarchy between headings and body text. Available in two weights:
Regular (400) for emphasis and Light (300) for subtler styling.
</p>
<div className="d-flex flex-column gap-10">
{/* Large Subheads */}
<div>
<h5 className="mb-6">Large Subheads</h5>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">subhead-lg-r / .sh-lg-r</code>
<div className="sh-lg-r">Regular Weight Subhead</div>
<div className="mt-3 text-muted">
<small>Mobile: 24px · Tablet: 28px · Desktop: 32px · Weight: 400</small>
</div>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">subhead-lg-l / .sh-lg-l</code>
<div className="sh-lg-l">Light Weight Subhead</div>
<div className="mt-3 text-muted">
<small>Mobile: 24px · Tablet: 28px · Desktop: 32px · Weight: 300</small>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
{/* Medium Subheads */}
<div>
<h5 className="mb-6">Medium Subheads</h5>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">subhead-md-r / .sh-md-r</code>
<div className="sh-md-r">Regular Weight Subhead</div>
<div className="mt-3 text-muted">
<small>Mobile: 24px · Tablet: 26px · Desktop: 28px · Weight: 400</small>
</div>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">subhead-md-l / .sh-md-l</code>
<div className="sh-md-l">Light Weight Subhead</div>
<div className="mt-3 text-muted">
<small>Mobile: 24px · Tablet: 26px · Desktop: 28px · Weight: 300</small>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
{/* Small Subheads */}
<div>
<h5 className="mb-6">Small Subheads</h5>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">subhead-sm-r / .sh-sm-r</code>
<div className="sh-sm-r">Regular Weight Subhead</div>
<div className="mt-3 text-muted">
<small>Mobile: 18px · Tablet: 18px · Desktop: 24px · Weight: 400</small>
</div>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">subhead-sm-l / .sh-sm-l</code>
<div className="sh-sm-l">Light Weight Subhead</div>
<div className="mt-3 text-muted">
<small>Mobile: 18px · Tablet: 18px · Desktop: 24px · Weight: 300</small>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
</div>
</section>
<Divider color="gray" weight="thin" />
{/* Body Text */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Body Text</h2>
<h6 className="eyebrow mb-3">Content Typography</h6>
</div>
<p className="mb-10 text-muted">
Body text styles are used for the main content areas. Available in Regular (400) for standard text
and Light (300) for less prominent content.
</p>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">body-r / .body-r</code>
<div className="body-r">
The quick brown fox jumps over the lazy dog. This is the standard body text style
used throughout the site. It provides good readability at comfortable sizes across
all devices. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</div>
<div className="mt-4 text-muted">
<small>Mobile: 16px (line: 23.2px) · Desktop: 18px (line: 26.1px) · Weight: 400</small>
</div>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">body-l / .body-l</code>
<div className="body-l">
The quick brown fox jumps over the lazy dog. This is the light body text style
for secondary content. It maintains the same size as regular body but with lighter
weight for visual hierarchy. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</div>
<div className="mt-4 text-muted">
<small>Mobile: 16px (line: 23.2px) · Desktop: 18px (line: 26.1px) · Weight: 300</small>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">Standard Paragraph</h5>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">p / default paragraph</code>
<p>
The standard paragraph element uses a base size of 16px with a 24px line height.
This is the default for all unstyled paragraph text. The quick brown fox jumps over
the lazy dog. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua.
</p>
<div className="text-muted">
<small>Font-size: 16px · Line-height: 24px</small>
</div>
</div>
</div>
<div className="mt-10">
<h5 className="mb-4">Longform Text</h5>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">.longform</code>
<div className="longform">
Longform text is designed for extended reading experiences with larger sizing and
medium weight. Perfect for article introductions or key content blocks.
</div>
<div className="mt-4 text-muted">
<small>Mobile: 20px (line: 26px) · Desktop: 24px (line: 32px) · Weight: 500</small>
</div>
</div>
</div>
</section>
<Divider color="gray" weight="thin" />
{/* Labels */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Label Text</h2>
<h6 className="eyebrow mb-3">UI Elements</h6>
</div>
<p className="mb-10 text-muted">
Label styles are designed for UI elements, form labels, captions, and supplementary text.
Smaller and optimized for clarity at reduced sizes.
</p>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">label-r / .label-r</code>
<div className="label-r mb-3">
Regular label text for form inputs, captions, and UI elements.
Maintains readability at smaller sizes.
</div>
<div className="text-muted">
<small>Mobile: 14px (line: 20.1px) · Desktop: 16px (line: 23.2px) · Weight: 400</small>
</div>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">label-l / .label-l</code>
<div className="label-l mb-3">
Light label text for secondary UI text, metadata, and supplementary information.
Provides subtle hierarchy.
</div>
<div className="text-muted">
<small>Mobile: 14px (line: 20.1px) · Desktop: 16px (line: 23.2px) · Weight: 300</small>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</section>
<Divider color="gray" weight="thin" />
{/* Special Styles */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Special Styles</h2>
<h6 className="eyebrow mb-3">Distinctive Elements</h6>
</div>
<div className="d-flex flex-column gap-10">
<div>
<h5 className="mb-4">Eyebrow Text</h5>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">.eyebrow</code>
<div className="eyebrow">Brand Design System</div>
<p className="mt-4 mb-0 text-muted">
Small uppercase labels that appear above headings to provide context or categorization.
</p>
</div>
</div>
<div>
<h5 className="mb-4">Numbers (Statistics)</h5>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#141414', color: 'white' }}>
<code className="mb-3 d-block" style={{ color: '#70EE97' }}>.numbers</code>
<div className="numbers">1,234</div>
<p className="mt-4 mb-0" style={{ color: '#C1C1C2' }}>
Extra-large bold numbers for statistics and key metrics. Desktop: 96px / Mobile: 62px
</p>
</div>
</div>
</div>
</section>
<Divider color="gray" weight="thin" />
{/* Font Families */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Font Families</h2>
<h6 className="eyebrow mb-3">Type Stack</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Booton</h5>
<p className="mb-4 text-muted">Primary sans-serif font family</p>
<div style={{ fontFamily: 'Booton, sans-serif', fontSize: '32px', lineHeight: '1.2' }}>
ABCDEFGHIJKLMNOPQRSTUVWXYZ<br />
abcdefghijklmnopqrstuvwxyz<br />
0123456789
</div>
<div className="mt-4">
<p className="mb-2"><strong>Used for:</strong></p>
<ul className="mb-0">
<li>Display Large</li>
<li>All Subheads</li>
<li>Body Text</li>
<li>Labels</li>
</ul>
</div>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Tobias</h5>
<p className="mb-4 text-muted">Monospace font family</p>
<div style={{ fontFamily: 'Tobias, monospace', fontSize: '32px', lineHeight: '1.2' }}>
ABCDEFGHIJKLMNOPQRSTUVWXYZ<br />
abcdefghijklmnopqrstuvwxyz<br />
0123456789
</div>
<div className="mt-4">
<p className="mb-2"><strong>Used for:</strong></p>
<ul className="mb-0">
<li>Display Medium & Small</li>
<li>All Headings</li>
<li>Legacy H1-H6</li>
<li>Code blocks</li>
</ul>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</section>
<Divider color="gray" weight="thin" />
{/* Typography in Context */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Typography in Context</h2>
<h6 className="eyebrow mb-3">Real-World Examples</h6>
</div>
<div className="d-flex flex-column gap-10">
{/* Article Layout */}
<div>
<h5 className="mb-6">Article Layout</h5>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<div className="eyebrow mb-3">Documentation</div>
<h2 className="h4 mb-4">Understanding the XRP Ledger</h2>
<p className="longform mb-6">
A comprehensive introduction to the decentralized blockchain that powers fast,
low-cost global payments.
</p>
<h5 className="mb-3">What is the XRP Ledger?</h5>
<p className="mb-4">
The XRP Ledger is a decentralized public blockchain. It was built to be a better
blockchain specifically for payments, with a unique design optimized for enterprise
use cases. The XRP Ledger allows anyone to transfer money across borders instantly,
reliably, and for fractions of a penny.
</p>
<h5 className="mb-3">Key Features</h5>
<p className="mb-2">
The ledger provides a number of innovative features that make it ideal for financial
applications:
</p>
<ul>
<li>Fast transaction settlement (3-5 seconds)</li>
<li>Low transaction costs (fractions of a penny)</li>
<li>High throughput (1,500 transactions per second)</li>
</ul>
</div>
</div>
{/* Hero Section */}
<div>
<h5 className="mb-6">Hero Section</h5>
<div className="p-6-sm p-10-until-sm br-8 text-center" style={{ backgroundColor: '#141414', color: 'white' }}>
<div className="eyebrow mb-4" style={{ color: '#70EE97' }}>Blockchain for Payments</div>
<div className="display-md mb-6" style={{ color: 'white' }}>
Build the Future of Finance
</div>
<p className="longform col-lg-8 mx-auto mb-0" style={{ color: '#C1C1C2' }}>
Join a global community developing on the XRP Ledgerthe most sustainable
blockchain optimized for payments and tokenization.
</p>
</div>
</div>
{/* Card with Stats */}
<div>
<h5 className="mb-6">Statistics Card</h5>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#141414', color: 'white' }}>
<div className="text-center">
<div className="numbers" style={{ color: '#21E46B' }}>10+</div>
<div className="sh-md-r mb-3">Years of Operation</div>
<p className="label-l mb-0" style={{ color: '#C1C1C2' }}>
Reliably processing transactions since 2012
</p>
</div>
</div>
</div>
</div>
</section>
<Divider color="gray" weight="thin" />
{/* Responsive Behavior */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Responsive Behavior</h2>
<h6 className="eyebrow mb-3">Breakpoint Adjustments</h6>
</div>
<p className="mb-6 text-muted">
All typography styles automatically adjust across breakpoints for optimal readability on any device.
</p>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Breakpoint System</h5>
<ul className="mb-4">
<li><strong>Mobile:</strong> Base styles (0-767px)</li>
<li><strong>Tablet:</strong> Medium breakpoint (768px-1023px)</li>
<li><strong>Desktop:</strong> Large breakpoint (1024px+)</li>
<li><strong>XL:</strong> Extra large (1200px+) only used for Display Large</li>
</ul>
<p className="mb-0 text-muted">
Resize your browser window to see typography scale responsively. Font sizes, line heights,
and letter spacing all adjust to maintain optimal readability at every viewport size.
</p>
</div>
</section>
<Divider color="gray" weight="thin" />
{/* Usage Guidelines */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Usage Guidelines</h2>
<h6 className="eyebrow mb-3">Best Practices</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Do</h5>
<ul>
<li>Use Display styles sparingly for maximum impact</li>
<li>Maintain consistent hierarchy with heading levels</li>
<li>Pair Regular and Light weights for visual contrast</li>
<li>Use Eyebrow text above headings for context</li>
<li>Apply Longform class to article introductions</li>
<li>Let typography breathe with adequate spacing</li>
</ul>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Don't</h5>
<ul>
<li>Don't skip heading levels in the hierarchy</li>
<li>Don't use Display styles for body content</li>
<li>Don't mix too many font weights on one screen</li>
<li>Don't override letter spacing on Display text</li>
<li>Don't use Tobias for long-form reading</li>
<li>Don't ignore responsive scaling</li>
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
</section>
<Divider color="gray" weight="thin" />
{/* Code Examples */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Code Examples</h2>
<h6 className="eyebrow mb-3">Implementation</h6>
</div>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#1e1e1e', color: '#d4d4d4' }}>
<pre style={{ margin: 0, overflow: 'auto' }}>
<code>{`// Display Styles
<div className="display-lg">Largest headline</div>
<div className="display-md">Medium display</div>
<div className="display-sm">Small display</div>
// Headings
<div className="h-lg">Large heading</div>
<div className="h-md">Medium heading</div>
<div className="h-sm">Small heading</div>
// Or use semantic HTML
<h1>Heading Level 1</h1>
<h2>Heading Level 2</h2>
// Subheads
<div className="sh-lg-r">Large subhead regular</div>
<div className="sh-md-l">Medium subhead light</div>
<div className="sh-sm-r">Small subhead regular</div>
// Body Text
<div className="body-r">Regular body text</div>
<div className="body-l">Light body text</div>
<p className="longform">Extended reading text</p>
// Labels
<div className="label-r">UI label regular</div>
<div className="label-l">UI label light</div>
// Special Styles
<div className="eyebrow">Category Label</div>
<div className="numbers">1,234</div>`}</code>
</pre>
</div>
</section>
</div>
);
}

View File

@@ -26,7 +26,7 @@ const logos = {
],
developer_tooling: ["cryptum", "evernode", "threezy", "tokenize"],
interoperability: ["multichain"],
wallet: ["crossmark", "edge", "gem-wallet", "xumm", "joey-wallet", "bifrost-wallet"],
wallet: ["crossmark", "edge", "gem-wallet", "xumm", "joey-wallet"],
nfts: [
"aesthetes",
"audiotarky",
@@ -413,15 +413,6 @@ const cardsData = [
category_name: "Wallet",
link: "https://xaman.app/#team",
},
{
id: "bifrost-wallet",
title: "Bifrost Wallet",
description:
"Bifrost Wallet is a secure, independently audited self-custodial wallet for the XRP Ledger with multi-chain support. Purpose-built for XRPFi: a wallet where your XRP works for you and earns yield.",
category_id: "wallet",
category_name: "Wallet",
link: "https://bifrostwallet.com/",
},
];
const featured_categories = {
@@ -468,7 +459,7 @@ const uses = [
{
id: "wallet",
title: "Wallet",
number: 6,
number: 5,
description:
"Build digital wallets to store passwords and interact with various blockchains to send and receive digital assets, including XRP."
},

View File

@@ -18,7 +18,7 @@ const links = [
];
const softwallets = [
{ href: "https://bifrostwallet.com/", id: "wallet-bifrost", alt: "Bifrost Wallet" },
{ href: "https://towolabs.com/", id: "wallet-towo", alt: "Towo" },
{ href: "https://xaman.app/", id: "wallet-xumm", alt: "Xaman" },
{ href: "https://trustwallet.com/", id: "wallet-trust", alt: "Trust Wallet" },
{

View File

@@ -1,192 +0,0 @@
---
category: 2026
date: "2026-01-27"
template: '../../@theme/templates/blogpost'
seo:
title: Introducing Clio version 2.7.0
description: Version 2.7.0 of Clio, an XRP Ledger API server optimized for HTTP and WebSocket API calls, is now available. This release adds new features and bug fixes.
labels:
- Clio Release Notes
markdown:
editPage:
hide: true
---
# Introducing Clio version 2.7.0
Version 2.7.0 of Clio, an XRP Ledger API server optimized for HTTP and WebSocket API calls, is now available. This release adds new features and bug fixes.
## Install / Upgrade
| Package |
| :------- |
| [Clio Server Linux Release (GCC)](https://github.com/XRPLF/clio/releases/download/2.7.0/clio_server_Linux_Release_gcc.zip) |
| [Clio Server Linux Debian Release (amd64)](https://github.com/XRPLF/clio/releases/download/2.7.0/clio_2.7.0_amd64.deb) |
| [Clio Server macOS Release (Apple Clang 17)](https://github.com/XRPLF/clio/releases/download/2.7.0/clio_server_macOS_Release_apple-clang.zip) |
For other platforms, please [build from source](https://github.com/XRPLF/clio/releases/tag/2.7.0). The most recent commit in the git log should be:
```text
Author: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
Date: Thu Jan 15 19:03:47 2026 +0000
ci: Restart colima on macOS (#2923)
```
## What's Changed
See the [Full Changelog on GitHub](https://github.com/XRPLF/clio/compare/2.6.0...2.7.0).
### Features
- Adds `account_mptoken_issuances` API method to retrieve all `MPTokenIssuances` created by a specified account, and `account_mptokens` API method to retrieve all `MPTokens` held by a specified account. ([#2680](https://github.com/XRPLF/clio/pull/2680))
- Adds DynamicMPT support to `account_mptoken_issuances` handler. ([#2820](https://github.com/XRPLF/clio/pull/2820))
### Improvements
- Removed old ETL implementation and enabled ETLng by default. ([#2752](https://github.com/XRPLF/clio/pull/2752))
- Added async framework `submit` method for running one-shot tasks that don't require a handle to retrieve the result. ([#2751](https://github.com/XRPLF/clio/pull/2751))
- Updated the Ledger Publisher to use async framework instead of directly using `io_context`. ([#2756](https://github.com/XRPLF/clio/pull/2756))
- Added support for normal/high priority to `WorkQueue`. ([#2721](https://github.com/XRPLF/clio/pull/2721))
- Added ability to read and write `LedgerCache` to file. ([#2761](https://github.com/XRPLF/clio/pull/2761))
- Added graceful shutdown for old web server. ([#2786](https://github.com/XRPLF/clio/pull/2786))
- Prometheus requests are now handled in `WorkQueue`. ([#2790](https://github.com/XRPLF/clio/pull/2790))
- Added observable value utility to enable reactive approach across the codebase. ([#2831](https://github.com/XRPLF/clio/pull/2831))
- Added option to save cache asynchronously. ([#2883](https://github.com/XRPLF/clio/pull/2883))
- Added basic support for channels. ([#2859](https://github.com/XRPLF/clio/pull/2859))
- Added build information to `clio_server --version` command. ([#2893](https://github.com/XRPLF/clio/pull/2893))
### Bug Fixes
- Fixed an issue where `account_info` was omitting the `signer_lists` field when requested for accounts with no signer lists. ([#2746](https://github.com/XRPLF/clio/pull/2746))
- Fixed an issue where `ledger_entry` error codes didn't match with `rippled`. ([#2549](https://github.com/XRPLF/clio/pull/2549))
- Enhanced cache saving error to include more information. ([#2794](https://github.com/XRPLF/clio/pull/2794))
- Fixed `WorkQueue` contention issues. ([#2866](https://github.com/XRPLF/clio/pull/2866))
- Fixed issue where failed asserts in tests produced no output. ([#2905](https://github.com/XRPLF/clio/pull/2905))
- Added workaround for an edge case exception in `AmendmentCenter`. ([#2897](https://github.com/XRPLF/clio/pull/2897))
- Fixed `WorkQueue` performance. ([#2887](https://github.com/XRPLF/clio/pull/2887))
### Refactor
- Refactored duplicate `ledger_index` pattern in RPC handlers into a common function. ([#2755](https://github.com/XRPLF/clio/pull/2755))
- Refactored `getLedgerIndex` to return `std::expected` instead of throwing exceptions. ([#2788](https://github.com/XRPLF/clio/pull/2788))
- Added writing command to `etl::SystemState`. ([#2842](https://github.com/XRPLF/clio/pull/2842))
### Documentation
- Removed `logging.md` from README. ([#2710](https://github.com/XRPLF/clio/pull/2710))
- Fixed `graceful_period` description. ([#2791](https://github.com/XRPLF/clio/pull/2791))
### Styling
- Fixed pre-commit style issues. ([#2743](https://github.com/XRPLF/clio/pull/2743))
- Fixed comment in `pre-commit-autoupdate.yml`. ([#2750](https://github.com/XRPLF/clio/pull/2750))
- Fixed hadolint issues. ([#2777](https://github.com/XRPLF/clio/pull/2777))
- Added black pre-commit hook. ([#2811](https://github.com/XRPLF/clio/pull/2811))
- Updated pre-commit hooks. ([#2825](https://github.com/XRPLF/clio/pull/2825), [#2875](https://github.com/XRPLF/clio/pull/2875))
- Used `shfmt` for shell scripts. ([#2841](https://github.com/XRPLF/clio/pull/2841))
- Fixed clang-tidy error. ([#2901](https://github.com/XRPLF/clio/pull/2901))
### Testing
- Fixed flaky `DeadlineIsHandledCorrectly` test. ([#2716](https://github.com/XRPLF/clio/pull/2716))
- Fixed flaky test. ([#2729](https://github.com/XRPLF/clio/pull/2729))
### Miscellaneous Tasks
- Pinned all GitHub actions. ([#2712](https://github.com/XRPLF/clio/pull/2712))
- Updated CI to use intermediate environment variables for improved security. ([#2713](https://github.com/XRPLF/clio/pull/2713))
- Updated CI to save full logs for failed sanitizer tests. ([#2715](https://github.com/XRPLF/clio/pull/2715))
- Enabled clang asan builds. ([#2717](https://github.com/XRPLF/clio/pull/2717))
- [DEPENDABOT] bump actions/upload-artifact from 4.6.2 to 5.0.0. ([#2722](https://github.com/XRPLF/clio/pull/2722))
- [DEPENDABOT] bump actions/upload-artifact from 4.6.2 to 5.0.0 in /.github/actions/code-coverage. ([#2725](https://github.com/XRPLF/clio/pull/2725))
- [DEPENDABOT] bump actions/download-artifact from 5.0.0 to 6.0.0. ([#2723](https://github.com/XRPLF/clio/pull/2723))
- Improved pre-commit failure message. ([#2720](https://github.com/XRPLF/clio/pull/2720))
- Updated CI to use XRPLF/get-nproc Github action. ([#2727](https://github.com/XRPLF/clio/pull/2727))
- [DEPENDABOT] bump actions/checkout from 4.3.0 to 5.0.0. ([#2724](https://github.com/XRPLF/clio/pull/2724))
- Added date to nightly release version. ([#2731](https://github.com/XRPLF/clio/pull/2731))
- Fixed nightly commits link. ([#2738](https://github.com/XRPLF/clio/pull/2738))
- Updated CI to use new prepare-runner. ([#2742](https://github.com/XRPLF/clio/pull/2742))
- Updated tooling in Docker images. ([#2737](https://github.com/XRPLF/clio/pull/2737))
- Installed pre-commit in the main CI image. ([#2744](https://github.com/XRPLF/clio/pull/2744))
- Updated docker images. ([#2745](https://github.com/XRPLF/clio/pull/2745))
- Added date to nightly release title. ([#2748](https://github.com/XRPLF/clio/pull/2748))
- Updated prepare-runner to fix `ccache` on macOS. ([#2749](https://github.com/XRPLF/clio/pull/2749))
- Removed backticks from release date. ([#2754](https://github.com/XRPLF/clio/pull/2754))
- Specified apple-clang 17.0 in Conan profile. ([#2757](https://github.com/XRPLF/clio/pull/2757))
- Fixed pre-commit hook failing on empty file. ([#2766](https://github.com/XRPLF/clio/pull/2766))
- [DEPENDABOT] bump docker/setup-qemu-action from 3.6.0 to 3.7.0 in /.github/actions/build-docker-image. ([#2763](https://github.com/XRPLF/clio/pull/2763))
- [DEPENDABOT] bump docker/metadata-action from 5.8.0 to 5.9.0 in /.github/actions/build-docker-image. ([#2762](https://github.com/XRPLF/clio/pull/2762))
- Changed default `max_queue_size` to 1000. ([#2771](https://github.com/XRPLF/clio/pull/2771))
- Specified bash as default shell in Github workflows. ([#2772](https://github.com/XRPLF/clio/pull/2772))
- Updated CI to use `ucontext` in ASAN builds. ([#2775](https://github.com/XRPLF/clio/pull/2775))
- Updated `xrpl` version to 3.0.0-rc1. ([#2776](https://github.com/XRPLF/clio/pull/2776))
- Forced usage of `ucontext` with ASAN. ([#2774](https://github.com/XRPLF/clio/pull/2774))
- Removed redundant silencing of ASAN errors in CI. ([#2779](https://github.com/XRPLF/clio/pull/2779))
- Updated CI to use environment variables instead of input. ([#2781](https://github.com/XRPLF/clio/pull/2781))
- Improved cache implementation. ([#2780](https://github.com/XRPLF/clio/pull/2780))
- Updated nudb recipe to remove linker warnings. ([#2787](https://github.com/XRPLF/clio/pull/2787))
- Updated CI to use environment variables instead of input in cache-key. ([#2789](https://github.com/XRPLF/clio/pull/2789))
- Added defines for asan/tsan to Conan profile. ([#2784](https://github.com/XRPLF/clio/pull/2784))
- Enabled TSAN in CI. ([#2785](https://github.com/XRPLF/clio/pull/2785))
- Updated CI to stop downloading `ccache` on develop branch. ([#2792](https://github.com/XRPLF/clio/pull/2792))
- Updated CI to always upload cache on develop. ([#2793](https://github.com/XRPLF/clio/pull/2793))
- [DEPENDABOT] bump peter-evans/create-pull-request from 7.0.8 to 7.0.9. ([#2805](https://github.com/XRPLF/clio/pull/2805))
- [DEPENDABOT] bump actions/checkout from 5.0.0 to 6.0.0. ([#2806](https://github.com/XRPLF/clio/pull/2806))
- Updated `spdlog` and `fmt` libraries. ([#2804](https://github.com/XRPLF/clio/pull/2804))
- Ran clang-tidy multiple times to ensure all issues were resolved. ([#2803](https://github.com/XRPLF/clio/pull/2803))
- Fixed Repeat-based tests TSAN issues. ([#2810](https://github.com/XRPLF/clio/pull/2810))
- Fixed `WebServerAdminTestsSuit` TSAN issues. ([#2809](https://github.com/XRPLF/clio/pull/2809))
- Used `boost::asio::ssl::stream` instead of `boost::beast::ssl_stream`. ([#2814](https://github.com/XRPLF/clio/pull/2814))
- Installed latest Ninja in images. ([#2813](https://github.com/XRPLF/clio/pull/2813))
- Updated images to use latest Ninja. ([#2817](https://github.com/XRPLF/clio/pull/2817))
- Updated lockfile. ([#2818](https://github.com/XRPLF/clio/pull/2818))
- Added mathbunnyru to maintainers. ([#2823](https://github.com/XRPLF/clio/pull/2823))
- Fixed TSAN async-signal-unsafe issue. ([#2824](https://github.com/XRPLF/clio/pull/2824))
- [DEPENDABOT] bump docker/metadata-action from 5.9.0 to 5.10.0 in /.github/actions/build-docker-image. ([#2826](https://github.com/XRPLF/clio/pull/2826))
- [DEPENDABOT] bump actions/checkout from 6.0.0 to 6.0.1. ([#2837](https://github.com/XRPLF/clio/pull/2837))
- [DEPENDABOT] bump peter-evans/create-pull-request from 7.0.9 to 7.0.11. ([#2836](https://github.com/XRPLF/clio/pull/2836))
- [DEPENDABOT] bump ytanikin/pr-conventional-commits from 1.4.2 to 1.5.1. ([#2835](https://github.com/XRPLF/clio/pull/2835))
- Reduced delay in ETL taskman. ([#2802](https://github.com/XRPLF/clio/pull/2802))
- Added systemd file to the Debian package. ([#2844](https://github.com/XRPLF/clio/pull/2844))
- Switched to `xrpl` version 3.0.0. ([#2843](https://github.com/XRPLF/clio/pull/2843))
- Added a Debian package to the Github release. ([#2850](https://github.com/XRPLF/clio/pull/2850))
- Added a script to regenerate the Conan lockfile. ([#2849](https://github.com/XRPLF/clio/pull/2849))
- [DEPENDABOT] bump tj-actions/changed-files from 46.0.5 to 47.0.1. ([#2853](https://github.com/XRPLF/clio/pull/2853))
- [DEPENDABOT] bump peter-evans/create-pull-request from 7.0.11 to 8.0.0. ([#2854](https://github.com/XRPLF/clio/pull/2854))
- [DEPENDABOT] bump actions/download-artifact from 6.0.0 to 7.0.0. ([#2855](https://github.com/XRPLF/clio/pull/2855))
- [DEPENDABOT] bump actions/upload-artifact from 5.0.0 to 6.0.0. ([#2856](https://github.com/XRPLF/clio/pull/2856))
- [DEPENDABOT] bump codecov/codecov-action from 5.5.1 to 5.5.2. ([#2857](https://github.com/XRPLF/clio/pull/2857))
- [DEPENDABOT] bump actions/upload-artifact from 5.0.0 to 6.0.0 in /.github/actions/code-coverage. ([#2858](https://github.com/XRPLF/clio/pull/2858))
- Updated shared Github actions. ([#2852](https://github.com/XRPLF/clio/pull/2852))
- Removed unnecessary creation of build directory in CI. ([#2867](https://github.com/XRPLF/clio/pull/2867))
- [DEPENDABOT] bump docker/setup-buildx-action from 3.11.1 to 3.12.0 in /.github/actions/build-docker-image. ([#2872](https://github.com/XRPLF/clio/pull/2872))
- [DEPENDABOT] bump docker/setup-buildx-action from 3.11.1 to 3.12.0. ([#2870](https://github.com/XRPLF/clio/pull/2870))
- [DEPENDABOT] bump actions/cache from 4.3.0 to 5.0.1. ([#2871](https://github.com/XRPLF/clio/pull/2871))
- Updated prepare-runner in Github actions and workflows. ([#2889](https://github.com/XRPLF/clio/pull/2889))
- Fixed branch name and commit SHA for GitHub PRs. ([#2888](https://github.com/XRPLF/clio/pull/2888))
- Updated CI to show `ccache` stats. ([#2902](https://github.com/XRPLF/clio/pull/2902))
- Changed build process to pass version explicitly instead of relying on tags. ([#2904](https://github.com/XRPLF/clio/pull/2904))
- Updated `gtest` and `spdlog`. ([#2908](https://github.com/XRPLF/clio/pull/2908))
- Updated tooling in Docker images. ([#2907](https://github.com/XRPLF/clio/pull/2907))
- Updated CI workflows to use new Docker images and GitHub actions. ([#2909](https://github.com/XRPLF/clio/pull/2909))
- Updated CI to use actual build date instead of date of last commit. ([#2911](https://github.com/XRPLF/clio/pull/2911))
- Updated CI to use environment variable for `BUILD_TYPE` in `reusable-build.yml`. ([#2913](https://github.com/XRPLF/clio/pull/2913))
- Changed build date format. ([#2914](https://github.com/XRPLF/clio/pull/2914))
- Updated CI to restart colima on macOS. ([#2923](https://github.com/XRPLF/clio/pull/2923))
- Reverted "refactor: Add writing command to etl::SystemState". ([#2860](https://github.com/XRPLF/clio/pull/2860))
## Contributors
The following people contributed directly to this release:
- [@godexsoft](https://github.com/godexsoft)
- [@mathbunnyru](https://github.com/mathbunnyru)
- [@kuznetsss](https://github.com/kuznetsss)
- [@yinyiqian1](https://github.com/yinyiqian1)
- [@emreariyurek](https://github.com/emreariyurek)
- [@PeterChen13579](https://github.com/PeterChen13579)
- [@bthomee](https://github.com/bthomee)
## Feedback
To report an issue or propose a new idea, please [open an issue](https://github.com/XRPLF/clio/issues).

View File

@@ -1,60 +0,0 @@
---
category: 2026
date: "2026-02-18"
template: '../../@theme/templates/blogpost'
seo:
title: GPG Key Rotation
description: Ripple has rotated the GPG key used to sign rippled packages.
labels:
- Advisories
markdown:
editPage:
hide: true
---
# GPG Key Rotation
Ripple has rotated the GPG key used to sign `rippled` packages. If you have an existing installation, you should download and trust the new key to prevent issues upgrading in the future. **Automatic upgrades will not work** until you have trusted the new key.
## Action Needed
Add Ripple's package-signing GPG key, then verify the fingerprint of the newly-added key.
{% tabs %}
{% tab label="Red Hat / CentOS" %}
```bash
sudo rpm --import https://repos.ripple.com/repos/rippled-rpm/stable/repodata/repomd.xml.key
rpm -qi gpg-pubkey-ab06faa6 | gpg --show-keys --fingerprint
```
{% /tab %}
{% tab label="Ubuntu / Debian" %}
```bash
sudo install -d -m 0755 /etc/apt/keyrings && \
curl -fsSL https://repos.ripple.com/repos/api/gpg/key/public \
| gpg --dearmor \
| sudo tee /etc/apt/keyrings/ripple.gpg > /dev/null
gpg --show-keys --fingerprint /etc/apt/keyrings/ripple.gpg
```
Ensure the `signed-by` path in your Ripple source list refers to the location the key was downloaded. For example, on an Ubuntu 22.04 Jammy installation, `/etc/apt/sources.list.d/ripple.list` would contain:
```
deb [signed-by=/etc/apt/keyrings/ripple.gpg] https://repos.ripple.com/repos/rippled-deb jammy stable
```
{% /tab %}
{% /tabs %}
The output should include an entry for Ripple such as the following:
```
pub ed25519 2026-02-16 [SC] [expires: 2033-02-14]
E057 C1CF 72B0 DF1A 4559 E857 7DEE 9236 AB06 FAA6
uid TechOps Team at Ripple <techops+rippled@ripple.com>
sub ed25519 2026-02-16 [S] [expires: 2029-02-15]
```
{% admonition type="danger" name="Warning" %}
Only trust this key if its fingerprint exactly matches: `E057 C1CF 72B0 DF1A 4559 E857 7DEE 9236 AB06 FAA6`.
{% /admonition %}

View File

@@ -1,76 +0,0 @@
---
category: 2026
date: "2026-01-28"
template: '../../@theme/templates/blogpost'
seo:
title: Introducing XRP Ledger version 3.1.0
description: rippled version 3.1.0 is now available. This version introduces new amendments and bug fixes.
labels:
- rippled Release Notes
markdown:
editPage:
hide: true
---
# Introducing XRP Ledger version 3.1.0
Version 3.1.0 of `rippled`, the reference server implementation of the XRP Ledger protocol, is now available. This release introduces Single Asset Vaults, the Lending Protocol, and bug fixes.
## Action Required
If you run an XRP Ledger server, upgrade to version 3.1.0 as soon as possible to ensure service continuity.
## Install / Upgrade
On supported platforms, see the [instructions on installing or updating `rippled`](../../docs/infrastructure/installation/index.md).
| Package | SHA-256 |
|:--------|:--------|
| [RPM for Red Hat / CentOS (x86-64)](https://repos.ripple.com/repos/rippled-rpm/stable/rippled-3.1.0-1.el9.x86_64.rpm) | `8ac8c529718566e6ebef3cb177d170fda1efc81ee08c4ea99d7e8fa3db0a2c70` |
| [DEB for Ubuntu / Debian (x86-64)](https://repos.ripple.com/repos/rippled-deb/pool/stable/rippled_3.1.0-1_amd64.deb) | `58574a2299db2edf567e09efa25504677cdc66e4fa26f8a84322ab05f3a02996` |
For other platforms, please [build from source](https://github.com/XRPLF/rippled/blob/master/BUILD.md). The most recent commit in the git log should be the change setting the version:
```text
commit d325f20c76fa798d0286d25e80b126ec0a2ee679
Author: Ed Hennis <ed@ripple.com>
Date: Tue Jan 27 21:13:06 2026 -0400
Set version to 3.1.0 (#6284)
```
## Full Changelog
### Amendments
- **SingleAssetVault**: Adds vaults, which pool a single asset for use with the Lending Protocol. ([#5632](https://github.com/XRPLF/rippled/pull/5632))
- **LendingProtocol**: Adds the ability to create loans on the XRP Ledger. Loan brokers can create fixed-term, uncollateralized loans using the pooled funds from a Single Asset Vault. The protocol is highly configurable, enabling loan brokers to tune risk appetite, depostitor protections, and economic incentives. ([#5632](https://github.com/XRPLF/rippled/pull/5632))
- **fixBatchInnerSigs**: Fixes an issue where inner transactions of a `Batch` transaction would be flagged as having valid signatures. Inner transactions never have valid signatures. ([#6069](https://github.com/XRPLF/rippled/pull/6069))
### Bug Fixes
- Expand `Number` to support full integer range. ([#6192](https://github.com/XRPLF/rippled/pull/6192))
- Fix: Reorder Batch Preflight Errors. ([#6176](https://github.com/XRPLF/rippled/pull/6176))
- Fix dependencies so clio can use libxrpl. ([#6251](https://github.com/XRPLF/rippled/pull/6251))
- Fix: Remove DEFAULT fields that change to the default in associateAsset (was Add Vault creation tests for showing valid range for AssetsMaximum). ([#6259](https://github.com/XRPLF/rippled/pull/6259))
## Credits
The following RippleX teams and GitHub users contributed to this release:
- RippleX Engineering
- RippleX Docs
- RippleX Product
- @dangell7
## Bug Bounties and Responsible Disclosures
We welcome reviews of the `rippled` code and urge researchers to responsibly disclose any issues they may find.
To report a bug, please send a detailed report to: <bugs@xrpl.org>

View File

@@ -7,12 +7,6 @@
page: index.page.tsx
expanded: true
items:
- group: '2026'
expanded: false
items:
- page: 2026/gpg-key-rotation.md
- page: 2026/rippled-3.1.0.md
- page: 2026/clio-2.7.0.md
- group: '2025'
expanded: false
items:

View File

@@ -1317,61 +1317,6 @@ const events = [
image: require("../static/img/events/hackathon-kaigi.png"),
end_date: "December 06, 2025",
},
{
name: "XRP Community Day EMEA",
description:
"Join the EMEA XRP community on February 11, 2026 for XRP Community Day, a global virtual event celebrating innovation, utility, and growth across the XRP ecosystem.",
type: "meetup",
link: "https://luma.com/w118fmkh?utm_source=xrplorg",
location: "Virtual - X Spaces",
date: "February 11, 2026",
image: require("../static/img/events/emea_xrplorg.png"),
end_date: "February 11, 2026",
},
{
name: "XRP Community Day Americas",
description:
"Join the Americas XRP community on February 11, 2026 for XRP Community Day, a global virtual event celebrating innovation, utility, and growth across the XRP ecosystem.",
type: "meetup",
link: "https://luma.com/1powvqnc?utm_source=xrplorg",
location: "Virtual - X Spaces",
date: "February 11, 2026",
image: require("../static/img/events/amer_xrplorg.png"),
end_date: "February 11, 2026",
},
{
name: "XRP Community Day APAC",
description:
"Join the APAC XRP community on February 12 for XRP Community Day, a global virtual event celebrating innovation, utility, and growth across the XRP ecosystem.",
type: "meetup",
link: "https://luma.com/ckzg3l3r?utm_source=xrplorg",
location: "Virtual - X Spaces",
date: "February 12, 2026",
image: require("../static/img/events/apac_xrplorg.png"),
end_date: "February 12, 2026",
},
{
name: "Building On The XRP Ledger",
description:
"This 2-day intensive hands-on training is designed for developers who are curious to learn about XRP Ledger. Meet your peers, share insights, and join a community of builders.",
type: "meetup",
link: "https://luma.com/lxb5ttsc",
location: "Paris, France",
date: "January 26 - 27, 2026",
image: require("../static/img/events/building-xrpl.png"),
end_date: "January 27, 2026",
},
{
name: "XRPL Meetup in London",
description:
"Calling all blockchain and XRP Ledger enthusiasts in London! Join XRPL Meetups to share knowledge, build real-life connections, and foster communities centered around blockchain and XRP Ledger. We're establishing local “XRPL Hubs” across Europe, and we want you to be a part of it!",
type: "meetup",
link: "https://luma.com/xshnm19t",
location: "London, UK",
date: "February 18, 2026",
image: require("../static/img/events/meetup-london.png"),
end_date: "February 18, 2026",
},
];
@@ -1479,45 +1424,6 @@ export default function Events() {
</div>
</div>
</section>
<section className="container-new py-26">
<div className="event-hero card-grid card-grid-2xN">
<div className="pr-2 col">
<img
alt="xrp ledger events hero"
src={require("../static/img/events/xrpl-hero.png")}
className="w-100"
/>
</div>
<div className="pt-5 pr-2 col">
<div className="d-flex flex-column-reverse">
<h2 className="mb-8 h4 h2-sm">
{translate("XRP Community Night Denver")}
</h2>
<h6 className="mb-3 eyebrow">{translate("Save the Date")}</h6>
</div>
<p className="mb-4">
{translate(
"Attending ETHDenver? Join us for an evening with the XRP community in Denver. Connect with the users, builders and projects innovating with and utilizing XRP."
)}
</p>
<div className=" my-3 event-small-gray">
{translate("Location: Denver, CO")}
</div>
<div className="py-2 my-3 event-small-gray">
{translate("February 18, 2026")}
</div>
<div className="d-lg-block">
<a
className="btn btn-primary btn-arrow-out"
target="_blank"
href="https://luma.com/chz145tf?utm_source=xprlorg"
>
{translate("Register Now")}
</a>
</div>
</div>
</div>
</section>
{/* Upcoming Events */}
<section className="container-new py-26" id="upcoming-events">
<div className="p-0 pb-2 mb-4 d-flex flex-column-reverse col-lg-6 pr-lg-5">

View File

@@ -406,78 +406,6 @@ const events = [
start_date: "November 14, 2025",
end_date: "December 06, 2025",
},
{
name: "XRP Community Day EMEA",
description:
"Join the EMEA XRP community on February 11, 2026 for XRP Community Day, a global virtual event celebrating innovation, utility, and growth across the XRP ecosystem.",
type: "meetup",
link: "https://luma.com/w118fmkh?utm_source=xrplorg",
location: "Virtual - X Spaces",
date: "February 11, 2026",
image: require("../static/img/events/emea_xrplorg.png"),
start_date: "February 11, 2026",
end_date: "February 11, 2026",
},
{
name: "XRP Community Day Americas",
description:
"Join the Americas XRP community on February 11, 2026 for XRP Community Day, a global virtual event celebrating innovation, utility, and growth across the XRP ecosystem.",
type: "meetup",
link: "https://luma.com/1powvqnc?utm_source=xrplorg",
location: "Virtual - X Spaces",
date: "February 11, 2026",
image: require("../static/img/events/amer_xrplorg.png"),
start_date: "February 11, 2026",
end_date: "February 11, 2026",
},
{
name: "XRP Community Day APAC",
description:
"Join the APAC XRP community on February 12 for XRP Community Day, a global virtual event celebrating innovation, utility, and growth across the XRP ecosystem.",
type: "meetup",
link: "https://luma.com/ckzg3l3r?utm_source=xrplorg",
location: "Virtual - X Spaces",
date: "February 12, 2026",
image: require("../static/img/events/apac_xrplorg.png"),
start_date: "February 12, 2026",
end_date: "February 12, 2026",
},
{
name: "Building On The XRP Ledger",
description:
"This 2-day intensive hands-on training is designed for developers who are curious to learn about XRP Ledger. Meet your peers, share insights, and join a community of builders.",
type: "meetup",
link: "https://luma.com/lxb5ttsc",
location: "Paris, France",
date: "January 26 - 27, 2026",
image: require("../static/img/events/building-xrpl.png"),
start_date: "January 26, 2026",
end_date: "January 27, 2026",
},
{
name: "XRPL Meetup in London",
description:
"Calling all blockchain and XRP Ledger enthusiasts in London! Join XRPL Meetups to share knowledge, build real-life connections, and foster communities centered around blockchain and XRP Ledger. We're establishing local “XRPL Hubs” across Europe, and we want you to be a part of it!",
type: "meetup",
link: "https://luma.com/xshnm19t",
location: "London, UK",
date: "February 18, 2026",
image: require("../static/img/events/meetup-london.png"),
start_date: "February 18, 2026",
end_date: "February 18, 2026",
},
{
name: "XRP Community Night Denver",
description:
"Attending ETHDenver? Join us for an evening with the XRP community in Denver. Connect with the users, builders and projects innovating with and utilizing XRP.",
type: "meetup",
link: "https://luma.com/chz145tf?utm_source=xprlorg",
location: "Denver, CO",
date: "February 18, 2026",
image: require("../static/img/events/denver_xrplorg.png"),
start_date: "February 18, 2026",
end_date: "February 18, 2026",
},
];

View File

@@ -32,8 +32,8 @@
[AccountSet transactions]: /docs/references/protocol/transactions/types/accountset.md
[AccountSet]: /docs/references/protocol/transactions/types/accountset.md
[Address]: /docs/references/protocol/data-types/basic-data-types.md#addresses
[Amendments entry]: /docs/references/protocol/ledger-data/ledger-entry-types/amendments.md
[Amendments object]: /docs/references/protocol/ledger-data/ledger-entry-types/amendments.md
[Amendments entry]: /docs/concepts/networks-and-servers/amendments.md
[Amendments object]: /docs/concepts/networks-and-servers/amendments.md
[Batch amendment]: /resources/known-amendments.md#batch
[Batch]: /docs/references/protocol/transactions/types/batch.md
[Batch transaction]: /docs/references/protocol/transactions/types/batch.md
@@ -128,15 +128,6 @@
[LedgerStateFix transaction]: /docs/references/protocol/transactions/types/ledgerstatefix.md
[LedgerStateFix transactions]: /docs/references/protocol/transactions/types/ledgerstatefix.md
[LedgerStateFix]: /docs/references/protocol/transactions/types/ledgerstatefix.md
[LoanBrokerCoverClawback transaction]: /docs/references/protocol/transactions/types/loanbrokercoverclawback.md
[LoanBrokerCoverDeposit transaction]: /docs/references/protocol/transactions/types/loanbrokercoverdeposit.md
[LoanBrokerCoverWithdraw transaction]: /docs/references/protocol/transactions/types/loanbrokercoverwithdraw.md
[LoanBrokerDelete transaction]: /docs/references/protocol/transactions/types/loanbrokerdelete.md
[LoanBrokerSet transaction]: /docs/references/protocol/transactions/types/loanbrokerset.md
[LoanDelete transaction]: /docs/references/protocol/transactions/types/loandelete.md
[LoanManage transaction]: /docs/references/protocol/transactions/types/loanmanage.md
[LoanPay transaction]: /docs/references/protocol/transactions/types/loanpay.md
[LoanSet transaction]: /docs/references/protocol/transactions/types/loanset.md
[Marker]: /docs/references/http-websocket-apis/api-conventions/markers-and-pagination.md
[MPTokenIssuanceSet]: /docs/references/protocol/transactions/types/mptokenissuanceset.md
[MPTokenIssuanceSet transaction]: /docs/references/protocol/transactions/types/mptokenissuanceset.md
@@ -144,7 +135,6 @@
[MPTokensV1 amendment]: /resources/known-amendments.md#mptokensv1
[MultiSign amendment]: /resources/known-amendments.md#multisign
[MultiSignReserve amendment]: /resources/known-amendments.md#multisignreserve
[NFT]: /docs/references/protocol/data-types/nftoken.md
[NFTokenAcceptOffer transaction]: /docs/references/protocol/transactions/types/nftokenacceptoffer.md
[NFTokenAcceptOffer transactions]: /docs/references/protocol/transactions/types/nftokenacceptoffer.md
[NFTokenAcceptOffer]: /docs/references/protocol/transactions/types/nftokenacceptoffer.md
@@ -165,7 +155,6 @@
[NFTokenPage entry]: /docs/references/protocol/ledger-data/ledger-entry-types/nftokenpage.md
[NFTokenPage object]: /docs/references/protocol/ledger-data/ledger-entry-types/nftokenpage.md
[NFToken]: /docs/references/protocol/data-types/nftoken.md
[Negative UNL]: /docs/concepts/consensus-protocol/negative-unl.md
[NegativeUNL amendment]: /resources/known-amendments.md#negativeunl
[NegativeUNL entry]: /docs/references/protocol/ledger-data/ledger-entry-types/negativeunl.md
[NegativeUNL object]: /docs/references/protocol/ledger-data/ledger-entry-types/negativeunl.md
@@ -259,13 +248,6 @@
[UNLModify pseudo-transaction]: /docs/references/protocol/transactions/pseudo-transaction-types/unlmodify.md
[UNLModify pseudo-transactions]: /docs/references/protocol/transactions/pseudo-transaction-types/unlmodify.md
[UNLModify]: /docs/references/protocol/transactions/pseudo-transaction-types/unlmodify.md
[Vault entry]: /docs/references/protocol/ledger-data/ledger-entry-types/vault.md
[VaultCreate transaction]: /docs/references/protocol/transactions/types/vaultcreate.md
[VaultDelete transaction]: /docs/references/protocol/transactions/types/vaultdelete.md
[VaultDeposit transaction]: /docs/references/protocol/transactions/types/vaultdeposit.md
[VaultSet transaction]: /docs/references/protocol/transactions/types/vaultset.md
[VaultWithdraw transaction]: /docs/references/protocol/transactions/types/vaultwithdraw.md
[VaultClawback transaction]: /docs/references/protocol/transactions/types/vaultclawback.md
[XChainAddAccountCreateAttestation transaction]: /docs/references/protocol/transactions/types/xchainaddaccountcreateattestation.md
[XChainAddAccountCreateAttestation transactions]: /docs/references/protocol/transactions/types/xchainaddaccountcreateattestation.md
[XChainAddAccountCreateAttestation]: /docs/references/protocol/transactions/types/xchainaddaccountcreateattestation.md
@@ -284,7 +266,6 @@
[XChainOwnedClaimID entry]: /docs/references/protocol/ledger-data/ledger-entry-types/xchainownedclaimid
[XRP, in drops]: /docs/references/protocol/data-types/basic-data-types.md#specifying-currency-amounts
[XRPFees amendment]: /resources/known-amendments.md#xrpfees
[AccountID]: /docs/references/protocol/binary-format/#accountid-fields
[account_channels command]: /docs/references/http-websocket-apis/public-api-methods/account-methods/account_channels.md
[account_channels method]: /docs/references/http-websocket-apis/public-api-methods/account-methods/account_channels.md
[account_currencies command]: /docs/references/http-websocket-apis/public-api-methods/account-methods/account_currencies.md
@@ -314,7 +295,6 @@
[channel_verify command]: /docs/references/http-websocket-apis/public-api-methods/payment-channel-methods/channel_verify.md
[channel_verify method]: /docs/references/http-websocket-apis/public-api-methods/payment-channel-methods/channel_verify.md
[common fields]: /docs/references/protocol/transactions/common-fields.md
[common ledger entry fields]: /docs/references/protocol/ledger-data/common-fields/
[connect command]: /docs/references/http-websocket-apis/admin-api-methods/peer-management-methods/connect.md
[connect method]: /docs/references/http-websocket-apis/admin-api-methods/peer-management-methods/connect.md
[consensus_info command]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/consensus_info.md
@@ -329,7 +309,6 @@
[fee command]: /docs/references/http-websocket-apis/public-api-methods/server-info-methods/fee.md
[fee levels]: /docs/concepts/transactions/transaction-cost.md#fee-levels
[fee method]: /docs/references/http-websocket-apis/public-api-methods/server-info-methods/fee.md
[fee voting]: /docs/concepts/consensus-protocol/fee-voting.md
[fetch_info command]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/fetch_info.md
[fetch_info method]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/fetch_info.md
[fix1201 amendment]: /resources/known-amendments.md#fix1201
@@ -362,21 +341,17 @@
[fixRemoveNFTokenAutoTrustLine amendment]: /resources/known-amendments.md#fixremovenftokenautotrustline
[fixTakerDryOfferRemoval amendment]: /resources/known-amendments.md#fixtakerdryofferremoval
[fixTrustLinesToSelf amendment]: /resources/known-amendments.md#fixtrustlinestoself
[flags field]: /docs/references/protocol/transactions/common-fields#flags-field
[get_aggregate_price command]: /docs/references/http-websocket-apis/public-api-methods/path-and-order-book-methods/get_aggregate_price.md
[get_aggregate_price method]: /docs/references/http-websocket-apis/public-api-methods/path-and-order-book-methods/get_aggregate_price.md
[gateway_balances command]: /docs/references/http-websocket-apis/public-api-methods/account-methods/gateway_balances.md
[gateway_balances method]: /docs/references/http-websocket-apis/public-api-methods/account-methods/gateway_balances.md
[get_counts command]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/get_counts.md
[get_counts method]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/get_counts.md
[Get Started Using JavaScript]: /docs/tutorials/javascript/build-apps/get-started.md
[Get Started Using Python]: /docs/tutorials/python/build-apps/get-started.md
[hexadecimal]: https://en.wikipedia.org/wiki/Hexadecimal
[identifying hash]: /docs/concepts/transactions/index.md#identifying-transactions
[json command]: /docs/references/http-websocket-apis/public-api-methods/utility-methods/json.md
[json method]: /docs/references/http-websocket-apis/public-api-methods/utility-methods/json.md
[ledger command]: /docs/references/http-websocket-apis/public-api-methods/ledger-methods/ledger.md
[ledger entry ID]: /docs/references/protocol/ledger-data/common-fields.md#ledger-entry-id
[ledger format]: /docs/references/protocol/ledger-data/ledger-entry-types/index.md
[ledger index]: /docs/references/protocol/data-types/basic-data-types.md#ledger-index
[ledger method]: /docs/references/http-websocket-apis/public-api-methods/ledger-methods/ledger.md
@@ -394,13 +369,6 @@
[ledger_entry method]: /docs/references/http-websocket-apis/public-api-methods/ledger-methods/ledger_entry.md
[ledger_request command]: /docs/references/http-websocket-apis/admin-api-methods/logging-and-data-management-methods/ledger_request.md
[ledger_request method]: /docs/references/http-websocket-apis/admin-api-methods/logging-and-data-management-methods/ledger_request.md
[Lending Protocol]: /docs/concepts/tokens/lending-protocol.md
[Loan]: /docs/references/protocol/ledger-data/ledger-entry-types/loan.md
[Loan entry]: /docs/references/protocol/ledger-data/ledger-entry-types/loan.md
[Loan ledger entry]: /docs/references/protocol/ledger-data/ledger-entry-types/loan.md
[LoanBroker]: /docs/references/protocol/ledger-data/ledger-entry-types/loanbroker.md
[LoanBroker entry]: /docs/references/protocol/ledger-data/ledger-entry-types/loanbroker.md
[LoanBroker ledger entry]: /docs/references/protocol/ledger-data/ledger-entry-types/loanbroker.md
[log_level command]: /docs/references/http-websocket-apis/admin-api-methods/logging-and-data-management-methods/log_level.md
[log_level method]: /docs/references/http-websocket-apis/admin-api-methods/logging-and-data-management-methods/log_level.md
[logrotate command]: /docs/references/http-websocket-apis/admin-api-methods/logging-and-data-management-methods/logrotate.md
@@ -436,12 +404,10 @@
[public servers]: /docs/tutorials/public-servers.md
[random command]: /docs/references/http-websocket-apis/public-api-methods/utility-methods/random.md
[random method]: /docs/references/http-websocket-apis/public-api-methods/utility-methods/random.md
[reserves]: /docs/concepts/accounts/reserves.md
[result code]: /docs/references/protocol/transactions/transaction-results/index.md
[ripple-lib]: https://github.com/XRPLF/xrpl.js
[ripple_path_find command]: /docs/references/http-websocket-apis/public-api-methods/path-and-order-book-methods/ripple_path_find.md
[ripple_path_find method]: /docs/references/http-websocket-apis/public-api-methods/path-and-order-book-methods/ripple_path_find.md
[Ripple Epoch]: /docs/references/protocol/data-types/basic-data-types.md#specifying-time
[seconds since the Ripple Epoch]: /docs/references/protocol/data-types/basic-data-types.md#specifying-time
[server_definitions method]: /docs/references/http-websocket-apis/public-api-methods/server-info-methods/server_definitions.md
[server_info command]: /docs/references/http-websocket-apis/public-api-methods/server-info-methods/server_info.md
@@ -466,7 +432,6 @@
[transaction cost]: /docs/concepts/transactions/transaction-cost.md
[transaction_entry command]: /docs/references/http-websocket-apis/public-api-methods/transaction-methods/transaction_entry.md
[transaction_entry method]: /docs/references/http-websocket-apis/public-api-methods/transaction-methods/transaction_entry.md
[transaction result codes]: /docs/references/protocol/transactions/transaction-results
[tx command]: /docs/references/http-websocket-apis/public-api-methods/transaction-methods/tx.md
[tx method]: /docs/references/http-websocket-apis/public-api-methods/transaction-methods/tx.md
[tx_history command]: /docs/references/http-websocket-apis/public-api-methods/transaction-methods/tx_history.md
@@ -482,9 +447,5 @@
[validator_list_sites method]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/validator_list_sites.md
[validators command]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/validators.md
[validators method]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/validators.md
[vault_info command]: /docs/references/http-websocket-apis/public-api-methods/vault-methods/vault_info.md
[vault_info method]: /docs/references/http-websocket-apis/public-api-methods/vault-methods/vault_info.md
[wallet_propose command]: /docs/references/http-websocket-apis/admin-api-methods/key-generation-methods/wallet_propose.md
[wallet_propose method]: /docs/references/http-websocket-apis/admin-api-methods/key-generation-methods/wallet_propose.md
[xrpl.js library]: https://github.com/XRPLF/xrpl.js
[xrpl-py library]: https://github.com/XRPLF/xrpl-py

View File

@@ -10,7 +10,7 @@ status: not_enabled
Permission delegation is the function of granting various permissions to another account to send permissions on behalf of your account. You can use permission delegation to enable flexible security paradigms such as role-based access control, instead of or alongside techniques such as [multi-signing](./multi-signing.md).
{% amendment-disclaimer name="PermissionDelegation" /%}
{% amendment-disclaimer name="PermissionDelegation" /%}
## Background: The Need for Permission Delegation

View File

@@ -1,33 +0,0 @@
---
seo:
description: A pseudo-account is a special type of XRPL account that holds assets on behalf of an on-chain protocol.
labels:
- Single Asset Vault
- AMM
- Lending Protocol
status: not_enabled
---
# Pseudo-Accounts
The XRP Ledger is an account-based blockchain where assets like XRP, trust line tokens, and Multi-Purpose Tokens (MPTs) are held by accounts, and are represented on-chain by an [AccountRoot](../../references/protocol/ledger-data/ledger-entry-types/accountroot) ledger entry. However, certain use cases require assets to be transferable to and from an object, which is why a pseudo-account is needed.
A pseudo-account is a special type of account that holds assets on behalf of an on-chain protocol. Use cases for pseudo-accounts include:
- **Automated Market Makers (AMM)**: The [XLS-30 amendment](../../../resources/known-amendments#amm) introduced pseudo-accounts for AMMs by adding the `AMMID` field to the `AccountRoot` ledger entry. This field links a pseudo-account to an AMM instance, allowing it to track XRP and token balances in the pool and issue `LPTokens` on behalf of the AMM instance.
- **Single Asset Vaults**: A single asset vault pseudo-account is used to store deposited funds and issue MPT shares. A new `VaultID` field is introduced in the `AccountRoot` ledger entry, which links the pseudo-account with the vault.
- **Lending Protocol**: The Lending Protocol also uses the single asset vault's pseudo-account, with each `LoanBroker` tracked in the pseudo-account's owner directory. The pseudo-account holds first-loss capital that protects vault depositors from loan defaults, as well as the loan funds themselves.
A pseudo-account has strict limitations. It cannot receive payments from other accounts, cannot send transactions since it has no signing authority, and exists solely to store or issue assets.
## Reserve Requirements
The cost of creating a pseudo-account depends on whether it is owned and controlled by another account:
- **Owned pseudo-accounts**: For objects like a `Vault` where a single account owns and controls the associated pseudo-account, the creation transaction increases the owner's XRP reserve by one [incremental owner reserve](../accounts/reserves#base-reserve-and-owner-reserve) (currently {% $env.PUBLIC_OWNER_RESERVE %}). This is in addition to any other reserve requirements of the transaction (for example, the Vault object itself).
- **Unowned pseudo-accounts**: For objects like an `AMM` that are not owned by any account, the creation transaction must charge a special, higher-than-normal transaction fee. This fee must be at least the value of one incremental owner reserve. This amount is burned, compensating for the permanent ledger space without tying the reserve to a specific owner.
{% raw-partial file="/docs/_snippets/common-links.md" /%}

View File

@@ -1,202 +0,0 @@
---
seo:
description: The XRPL Lending Protocol enables on-chain, uncollateralized fixed-term loans.
labels:
- Decentralized Finance
- Lending Protocol
status: not_enabled
---
# Lending Protocol
The Lending Protocol is an XRP Ledger DeFi primitive that enables on-chain, fixed-term, uncollateralized loans using pooled funds from a [Single Asset Vault](./single-asset-vaults.md). The protocol is highly configurable, enabling loan brokers to easily tune risk appetite, depostitor protections, and economic incentives.
The implementation relies on off-chain underwriting and risk management to assess the creditworthiness of borrowers, but offers peer-to-peer loans without intermediaries like banks or financial institutions. First-loss capital protection is used to help offset losses from loan defaults.
The current implementation of the lending protocol doesn't include automated on-chain collateral and liquidation management, instead focusing on on-chain credit origination.
To ensure compliance needs are met, asset issuers can [claw back](../../references/protocol/transactions/types/clawback.md) funds from the vault associated with the lending protocol. Issuers can also [freeze](./fungible-tokens/freezes.md) individual accounts or issue a global freeze.
{% amendment-disclaimer name="LendingProtocol" /%}
## Protocol Flow
There are three parties involved in the process of creating a loan:
- **Loan Brokers**: Create asset vaults and manage associated loans.
- **Depositors**: Add assets to vaults.
- **Borrowers**: Receive loans, making repayments as defined by their loan terms.
[{% inline-svg file="../../img/lending-protocol-diagram.svg" /%}](../../img/lending-protocol-diagram.svg "Diagram: The lifecycle of a loan.")
The lifecycle of a loan is as follows:
1. A loan broker creates a vault.
2. Depositors add assets to the vault.
3. (Optional) The loan broker deposits first-loss capital.
4. A loan broker and borrower create a loan, defining the terms of the loan, and the requested principal (excluding fees) is transferred to the borrower.
5. If payments are missed, the loan enters a grace period. Once the grace period expires, the loan broker has the option to default the loan.
6. The loan is deleted when matured or defaulted.
7. (Optional) The loan broker can withdraw first-loss capital.
8. After all loans are paid, the loan broker can delete the `LoanBroker` ledger entry, and then the corresponding `Vault` ledger entry.
## Accounting
### Risk Management
#### First-Loss Capital
First-Loss Capital is an optional mechanism to mitigate the risks associated with lending. To protect investors' assets, a loan broker can deposit assets as first-loss capital, which acts as a buffer in the event of loan defaults. The first-loss capital is placed into the vault to cover a percentage of losses from missed payments.
Three parameters control the First-Loss Capital:
- `CoverAvailable`: The total amount of cover deposited by the lending protocol owner.
- `CoverRateMinimum`: The percentage of debt that must be covered by `CoverAvailable`.
- `CoverRateLiquidation`: The maximum percentage of the minimum required cover _(DebtTotal x CoverRateMinimum)_ that will be placed in the asset vault to cover a loan default.
Whenever the available cover falls below the minimum required:
- The loan broker can't issue new loans.
- The loan broker can't receive fees. All fees are added to the First-Loss Capital to cover the deficit.
Below is an example of how first-loss capital is used to cover a loan default:
```
** Initial States **
-- Vault --
AssetsTotal = 100,090 Tokens
AssetsAvailable = 99,000 Tokens
SharesTotal = 100,000 Tokens
-- Lending Protocol --
DebtTotal = 1,090 Tokens
CoverRateMinimum = 0.1 (10%)
CoverRateLiquidation = 0.1 (10%)
CoverAvailable = 1,000 Tokens
-- Loan --
PrincipleOutstanding = 1,000 Tokens
InterestOutstanding = 90 Tokens
# First-Loss Capital liquidation maths
DefaultAmount = PrincipleOutstanding + InterestOutstanding
= 1,000 + 90
= 1,090
# The amount of the default that the first-loss capital scheme will cover
DefaultCovered = min((DebtTotal x CoverRateMinimum) x CoverRateLiquidation, DefaultAmount)
= min((1,090 * 0.1) * 0.1, 1,090) = min(10.9, 1,090)
= 10.9 Tokens
Loss = DefaultAmount - DefaultCovered
= 1,090 - 10.9
= 1,079.1 Tokens
FundsReturned = DefaultCovered
= 10.9
# Note, Loss + FundsReturned MUST be equal to PrincipleOutstanding + InterestOutstanding
** State Changes **
-- Vault --
AssetsTotal = AssetsTotal - Loss
= 100,090 - 1,079.1
= 99,010.9 Tokens
AssetsAvailable = AssetsAvailable + FundsReturned
= 99,000 + 10.9
= 99,010.9 Tokens
SharesTotal = (UNCHANGED)
-- Lending Protocol --
DebtTotal = DebtTotal - PrincipleOutstanding + InterestOutstanding
= 1,090 - (1,000 + 90)
= 0 Tokens
CoverAvailable = CoverAvailable - DefaultCovered
= 1,000 - 10.9
= 989.1 Tokens
```
#### Impairment
If the loan broker discovers a borrower can't make an upcoming payment, impairment allows the loan broker to register a "paper loss" with the vault. The impairment mechanism moves the due date of the next payment to the time the loan is impaired, allowing the loan to default more quickly. However, if the borrower makes a payment before that date, the impairment status is automatically cleared.
### Compliance
#### Clawback
Issuers (trust line token or MPT, not XRP) can claw back funds from First-Loss Capital. To ensure there is always a minimum amount of capital available to protect depositors, issuers can't claw back the entire available amount. Instead, they can claw back up to a minimum amount of First-Loss Capital that the loan broker must maintain for the lending protocol; the minimum amount is calculated as `LoanBroker.DebtTotal * LoanBroker.CoverRateMinimum`.
#### Freeze
Freezing is a mechanism by which an asset issuer (trust line token or MPT, not XRP) prevents an account from sending their issued asset. _Deep freeze_ takes this a step further by preventing an account from sending _and_ receiving issued assets. Issuers can also enact a _global freeze_, which prevents everyone from sending or receiving their issued asset.
{% admonition type="info" name="Note" %}
In all freeze scenarios, assets can be sent back to the issuer.
{% /admonition %}
If a borrower has their account frozen or deep frozen, they can't make loan payments. This doesn't absolve a borrower of their repayment obligations, and they will eventually default on their loan.
Freezing a borrower's account won't affect a loan broker's functions, but it will prevent them from receiving any lending protocol fees. However, issuers can freeze a loan broker's _pseudo-account_ and prevent the loan broker from creating new loans; existing loans won't be affected. A deep freeze on a loan broker's _pseudo-account_ also prevents loans from being repaid.
### Interest Rates
There are three interest rates associated with a loan:
- **Interest Rate**: The regular interest rate based on the principal amount. It is the cost of borrowing funds.
- **Late Interest Rate**: A higher interest rate charged for a late payment.
- **Full Payment Rate**: An interest rate charged for repaying the total loan early.
### Fees
The lending protocol charges a number of fees that the loan broker can configure. The protocol won't charge these fees if the loan broker hasn't deposited enough first-loss capital.
- **Management Fee**: This is a percentage of interest charged by the loan broker. Vault depositors pay this fee.
- **Loan Origination Fee**: A fee paid to the loan broker, taken from the principal amount loaned out.
- **Loan Service Fee**: A fee charged on top of each loan payment.
- **Late Payment Fee**: A fee paid on top of a late payment.
- **Early Payment Fee**: A fee paid on top of an early payment.
## Loan Payment Processing
Loan payments are evaluated and processed around three criteria: amount, timing, and specified flags. The combination of these criteria determine how funds are applied to the loan's principal, interest, and associated fees.
Each payment consists of four components:
- **Principal**: The portion that reduces the outstanding loan principle.
- **Interest**: The portion that covers the cost of borrowing for the period.
- **Fees**: The portion that covers any applicable service fees, management fees, late payment fees, or other charges.
- **ValueChange**: The amount by which the payment changes the loan balance.
When the loan payment is submitted, the lending protocol then checks these parameters:
- **Timing**: Is the payment on time or late?
- **Amount**: Does the payment amount meet the minimum required amount, or exceed it?
Based on the timing and transaction flags, the lending protocol processes the payment as one of four types:
- **On-Time Payments**: If the payment is on-time, it's further classified into these payment scenarios:
- **Sequential Periodic Payments**: The payment is applied to as many complete payment cycles as possible; cycles are calculated as the amount due each payment period (including fees).
- **Overpayments**: After all possible cycles are fully paid, any remaining amount is treated as an overpayment and applied to the principal. This type of payment requires the `lsfLoanOverpayment` flag to be enabled on the `Loan` ledger entry, as well as the `tfLoanOverpayment` flag to be enabled on the `LoanPay` transaction. If these flags are missing, the excess amount is ignored.
- **Full Early Repayment**: The payment has the `tfLoanFullPayment` flag set, and the amount covers the remainder of the loan (including fees).
- **Late Payments**: The payment is late on a payment cycle. Late payments must be for an exact amount, calculated as:
`totalDue = periodicPayment + loanServiceFee + latePaymentFee + latePaymentInterest`
Overpayments aren't permitted on late payments; any excess amount is ignored.
{% admonition type="info" name="Note" %}
In scenarios where excess payment amounts are "ignored", the transaction succeeds, but the borrower is only charged on the expected amount.
{% /admonition %}
{% raw-partial file="/docs/_snippets/common-links.md" /%}

View File

@@ -1,251 +0,0 @@
---
seo:
description: Single asset vaults aggregate assets from multiple depositors and make them available to other on-chain protocols.
labels:
- Single Asset Vault
status: not_enabled
---
# Single Asset Vaults
A single asset vault is an XRP Ledger primitive that aggregates assets from multiple depositors and makes them available to other on-chain protocols, such as the Lending Protocol. A vault asset can be [XRP](../../introduction/what-is-xrp.md), a [trust line token](../tokens/fungible-tokens/index.md), or an [MPT (Multi-Purpose Token)](../tokens/fungible-tokens/multi-purpose-tokens.md).
A Vault Owner account manages the vault and can create, update, or delete it as needed. When creating a vault, the Vault Owner can also specify whether shares are transferable or non-transferable. Non-transferable shares cannot be transferred to any other account, and can only be redeemed.
{% amendment-disclaimer name="SingleAssetVault" /%}
## Public vs. Private Vaults
A vault can be **public** or **private**, depending on the required level of access control.
In a public vault, anyone can deposit or redeem liquidity as long as they hold sufficient shares. In contrast, a private vault restricts access, allowing only depositors with the necessary [Credentials](../../concepts/decentralized-storage/credentials.md), managed through [Permissioned Domains](./decentralized-exchange/permissioned-domains.md), to deposit assets.
{% admonition type="warning" name="Warning" %}
If a depositor's credentials expire, they can no longer deposit assets in a private vault, but can always redeem their existing shares.
{% /admonition %}
To prevent the Vault Owner from locking funds away, any shareholder in a private vault can redeem their shares for assets.
Choosing between a public or private vault depends on your use case. For example, if depositor identity verification is required, use a private vault and issue credentials only to verified accounts.
## Vault Share Distribution and Redemption
Depositors can deposit assets to receive shares, which represent their proportional ownership of the vault, or redeem shares for assets.
[{% inline-svg file="../../img/single-asset-vault-img.svg" /%}](../../img/single-asset-vault-img.svg "Diagram: an example of an asset being deposited into the vault and shares being redeemed.")
Since the XRP Ledger is an account-based blockchain, all assets must be held by an account. A `Vault` ledger entry cannot hold assets directly, so a [pseudo-account](../accounts/pseudo-accounts.md) is created to hold assets on its behalf. This stand-alone account cannot receive funds or send transactions, and exists solely to store assets and issue shares.
Each share is represented on-chain as an MPT, issued by the vault's pseudo-account. Since MPTs can only exist as whole number units, the vault uses a `Scale` setting to convert fractional asset amounts into whole number shares.
The scale behavior varies based on the type of asset held by the vault:
- **XRP**: Uses a fixed scale that aligns with XRP's native structure, where one share represents one drop.
- **Trust Line Token**: Allows configurable precision (default preserves 6 decimal places).
- **MPT**: Uses a 1-to-1 relationship between MPT units and shares.
Depending on the connected protocol, vault shares may be yield-bearing, meaning shareholders could redeem shares for more or less liquidity than they originally deposited. This is because the total asset balance in the vault can grow or shrink over time, affecting the value of each share. However, the vault asset (e.g., USDC, XRP) does not generate yield on its own.
The value of each share depends on the total assets in the vault:
- If the vault earns yield over time, shares represent a larger claim, allowing depositors to redeem them for more assets.
- If the vault incurs losses, shares hold less value, resulting in lower redemptions.
A vault could generate yield through mechanisms like lending or staking, with yield paid in the same asset deposited. The specific logic for this depends on how the connected on-chain protocol generates yield. For example, if a vault is used by a lending protocol, it could earn yield from interest paid by borrowers.
### Exchange Algorithm
A single asset vault uses an **exchange algorithm** to define how assets convert into shares during deposits and how shares convert back into assets during redemptions.
A vault's total value can fluctuate due to factors like _unrealized losses_, which impact the exchange rate for deposits and redemptions. To ensure fairness, the algorithm adjusts the exchange rate dynamically, so depositors receive shares or redeem them for assets at a rate that accurately reflects the vaults true value.
#### Unrealized Loss
To prevent depositors from exploiting potential losses by redeeming shares early and shifting the full loss onto the remaining depositors, the vault tracks unrealized losses (or paper loss) using the `LossUnrealized` attribute in the `Vault` ledger entry.
Because the unrealized loss temporarily decreases the vault's value, a malicious depositor may take advantage of this by depositing assets at a lowered price and redeeming shares once the price increases.
For example, consider a vault with a total value of $1.0m and total shares of 1.0m. Let's assume the unrealized loss for the vault is $900k:
1. The new exchange rate is calculated as:
```js
// ExchangeRate = (AssetsTotal - LossUnrealized) / SharesTotal
exchangeRate = (1,000,000 - 900,000) / 1,000,000
```
The exchange rate value is now **0.1**.
2. After the unrealized loss is cleared, the new effective exchange rate would be:
```js
// ExchangeRate = AssetsTotal / SharesTotal
exchangeRate = 1,000,000 / 1,000,000
```
The exchange rate is now **1.0**.
A depositor could deposit $100k assets at a 0.1 exchange rate and get 1.0m shares. Once the unrealized loss is cleared, their shares would be worth $1.0m.
To mitigate this, the vault uses separate exchange rates for deposits and redemptions.
#### Exchange Rates
A single asset vault uses **two distinct exchange rates**:
- **Deposit Exchange Rate**: Protects new depositors from prior losses and ensures fair share allocation.
- **Withdrawal Exchange Rate**: Ensures all shareholders share losses proportionally. Whether redeeming shares or withdrawing assets, the vault always calculates payouts using the actual current value (total assets minus losses), so depositors get their fair share of what's actually in the vault.
- **Redemptions**: The vault burns shares so the depositor can receive proportional assets.
- **Withdrawals**: The vault determines the shares to burn based on the requested asset amount.
These exchange rates ensure fairness and prevent manipulation, maintaining the integrity of deposits and redemptions.
To understand how the exchange rates are applied, here are the key variables used in the calculations:
- `Γ_assets`: The total balance of assets held within the vault.
- `Γ_shares`: The total number of shares currently issued by the vault.
- `Δ_assets`: The amount of assets being deposited, withdrawn, or redeemed.
- `Δ_shares`: The number of shares being issued or burned.
- `l`: The vault's total unrealized loss.
- `σ`: The scaling factor (σ = 10<sup>Scale</sup>) used to convert fractional assets into whole number shares.
{% tabs %}
{% tab label="Deposit" %}
The vault computes the number of shares a depositor will receive as follows:
- **Initial Deposit (Empty Vault)**: For the first deposit into an empty vault, shares are calculated using the scaling factor to properly represent fractional assets as whole numbers.
```js
Δ_shares = Δ_assets * σ // σ = 10^Scale
```
- **Subsequent Deposits**: For all other deposits, shares are calculated proportionally. The resulting share value is rounded **down** to the nearest whole number.
```js
Δ_shares = (Δ_assets * Γ_shares) / Γ_assets
```
Because the share amount is rounded down, the actual assets taken from the depositor are recalculated. This ensures the depositor isn't overcharged and that new shares are valued against the vault's true value, accounting for any unrealized loss:
```js
Δ_assets = (Δ_shares * (Γ_assets - l)) / Γ_shares
```
After a successful deposit, the _total assets_ and _total shares_ values are updated like so:
```js
Γ_assets = Γ_assets + Δ_assets // New balance of assets in the vault.
Γ_shares = Γ_shares + Δ_shares // New share balance in the vault.
```
{% /tab %}
{% tab label="Redeem" %}
The vault computes the number of assets returned by burning shares as follows:
```js
Δ_assets = (Δ_shares * (Γ_assets - l)) / Γ_shares
```
After a successful redemption, the _total assets_ and _total shares_ values are updated like so:
```js
Γ_assets = Γ_assets - Δ_assets // New balance of assets in the vault.
Γ_shares = Γ_shares - Δ_shares // New share balance in the vault.
```
{% /tab %}
{% tab label="Withdraw" %}
When a depositor requests a specific asset amount, the vault uses a two-step process to determine the final payout:
1. The requested asset amount (`Δ_assets_requested`) is converted into shares.
```js
Δ_shares = (Δ_assets_requested * Γ_shares) / (Γ_assets - l)
```
The calculated share amount is rounded to the **nearest** whole number.
2. The rounded number of shares is used to calculate the final asset payout (`Δ_assets_out`), using the same logic as a redemption.
```js
Δ_assets_out = (Δ_shares * (Γ_assets - l)) / Γ_shares
```
Due to rounding in step 1, the final payout may differ slightly from the requested amount.
After a successful withdrawal, the _total asset_ and _total share_ values are updated like so:
```js
Γ_assets = Γ_assets - Δ_assets_out // New balance of assets in the vault.
Γ_shares = Γ_shares - Δ_shares // New share balance in the vault.
```
{% /tab %}
{% /tabs %}
### Can a Depositor Transfer Shares to Another Account?
Vault shares are a first-class asset, meaning that they can be transferred and used in other on-ledger protocols that support MPTs. However, the payee (or the receiver) must have permission to hold both the shares and the underlying asset.
For example, if a private vault holds USDC, the destination account must belong to the vaults Permissioned Domain and have permission to hold USDC. Any compliance mechanisms applied to USDC also apply to the shares. If the USDC issuer freezes the payees trust line, the payee cannot receive shares representing USDC.
{% admonition type="info" name="Note" %}
It is important to remember that a vault must be **configured** to allow share transfers, or this will not be possible.
{% /admonition %}
A depositor can transfer vault shares to another account by making a [Payment](../../references/protocol/transactions/types/payment) transaction. Nothing changes in the way the payment transaction is submitted for transferring vault shares. However, there are new failure scenarios to watch out for if the transaction fails:
- The vault is private and the payee lacks credentials in the vault's permissioned domain.
- The vault shares are configured as non-transferable.
- There is a global freeze (trust line tokens) or lock (MPTs) on the underlying asset.
- The underlying asset is an MPT and is locked for the payer, payee, or vault pseudo-account.
- The underlying asset is a trust line token and the trust line is frozen between the issuer and the payer, payee, or vault pseudo-account.
If the transfer succeeds and the payee already holds vault shares, their balance increases. Otherwise, a new MPT entry is created for their account.
## Compliance
### Frozen Assets
The issuer of a vault asset can enact a [freeze](./fungible-tokens/freezes) for trust line tokens or [lock an MPT](./fungible-tokens/deep-freeze#how-does-mpt-freeze/lock-behavior-differ-from-iou). When a vault asset is frozen:
1. Withdrawals can only be made to the assets issuer.
2. The asset cannot be deposited into the vault.
3. Its corresponding shares also cannot be transferred.
### Clawback
An asset issuer can perform a [Clawback](../../use-cases/tokenization/stablecoin-issuer#clawback) on vault assets by forcing redemption of shares held by an account. This exchanges the holder's shares for the underlying assets, which are sent directly to the issuer. This mechanism allows asset issuers to recover their issued assets from vault depositors when necessary for fraud prevention or regulatory compliance.
## Why Use a Single Asset Vault?
With a single asset vault you don't have to manage liquidity at the protocol level. Instead, you can use the vault to handle deposits, redemptions, and asset tracking separately.
Vaults handle asset-to-share conversion, ensure accurate pricing, and eliminate the need to add custom logic to calculate exchange rates or account for unrealized losses.
Depending on the connected on-chain protocol, vaults can be applied to various use cases, such as:
- Lending markets
- Aggregators
- Yield-bearing tokens
- Asset management
The only supported use cases right now are _asset management_ and [_lending markets_](./lending-protocol.md).
{% raw-partial file="/docs/_snippets/common-links.md" /%}
## See Also
- **Concepts:**
- [Credentials](../../concepts/decentralized-storage/credentials.md) - Define access requirements for private vaults.
- [Permissioned Domains](../tokens/decentralized-exchange/permissioned-domains.md) - Control access to private vaults.
- [Pseudo-Accounts](../accounts/pseudo-accounts.md) - Special accounts that hold assets on behalf of on-chain protocols.
- **References:**
- [Vault entry][] - Data structure on the ledger that records vault information.
- [VaultClawback transaction][] - Allow asset issuers to recover assets from the vault.
- [VaultCreate transaction][] - Create a new vault for aggregating assets.
- [VaultDelete transaction][] - Delete an existing vault entry.
- [VaultDeposit transaction][] - Add assets to a vault in exchange for shares.
- [VaultSet transaction][] - Update the configuration of an existing vault.
- [VaultWithdraw transaction][] - Redeem liquidity from a vault.
- [vault_info method][] - Retrieve information about a vault and its shares.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 100 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -1,470 +1,424 @@
import * as React from 'react';
import HeaderHeroPrimaryMedia from 'shared/sections/HeaderHeroPrimaryMedia/HeaderHeroPrimaryMedia';
import { CarouselCardList } from 'shared/sections/CarouselCardList/CarouselCardList';
import { CardsTextGrid } from 'shared/sections/CardsTextGrid/CardsTextGrid';
import FeaturedVideoHero from 'shared/sections/FeaturedVideoHero/FeaturedVideoHero';
import { LinkSmallGrid } from 'shared/sections/LinkSmallGrid/LinkSmallGrid';
import { LinkTextDirectory } from 'shared/sections/LinkTextDirectory/LinkTextDirectory';
import { FeatureTwoColumn } from 'shared/sections/FeatureTwoColumn/FeatureTwoColumn';
import { SmallTilesSection } from 'shared/sections/SmallTilesSection/SmallTilesSection';
import { CardsIconGrid } from 'shared/sections/CardsIconGrid/CardsIconGrid';
import { StandardCardGroupSection } from 'shared/sections/StandardCardGroupSection/StandardCardGroupSection';
import { useThemeHooks } from '@redocly/theme/core/hooks';
import { NavList } from "shared/components/nav-list";
import { Link } from "@redocly/theme/components/Link/Link";
export const frontmatter = {
seo: {
title: 'XRP Ledger Documentation & Developer Resources',
description:
'Explore XRP Ledger documentation and other blockchain developer resources needed to start building and integrating with the ledger.',
},
description: "Explore XRP Ledger documentation and other blockchain developer resources needed to start building and integrating with the ledger.",
}
};
export default function Docs() {
const recommendedPages = [
{
description: 'Public API Methods',
link: '/docs/references/http-websocket-apis/public-api-methods/',
},
{
description: 'Run a Validator',
link: '/docs/infrastructure/configuration/server-modes/run-rippled-as-a-validator/',
},
{
description: 'Reserves',
link: '/docs/concepts/accounts/reserves/',
},
{
description: 'Transaction Types',
link: '/docs/references/protocol/transactions/types/',
}
];
const useCases = [
{
title: 'On-Chain Finance',
id: 'on-chain-finance-use-cases',
imgClass: 'wallet-illustration',
subItems: [
{
description: 'Algorithmic Trading',
link: '/docs/use-cases/defi/algorithmic-trading/',
},
{
description: 'List XRP as an Exchange',
link: '/docs/use-cases/defi/list-xrp-as-an-exchange/',
},
{
description: 'Payment Types',
link: '/docs/concepts/payment-types/',
},
],
},
{
title: 'Tokens',
id: 'token-use-cases',
imgClass: 'token-illustration',
subItems: [
{
description: 'Stablecoin Issuer',
link: '/docs/use-cases/tokenization/stablecoin-issuer/',
},
{
description: 'NFT Marketplace',
link: '/docs/use-cases/tokenization/nft-mkt-overview/',
},
{
description: 'Digital Artist',
link: '/docs/use-cases/tokenization/digital-artist/',
},
],
},
{
title: 'Payments',
id: 'payments-use-cases',
imgClass: 'connections-illustration',
subItems: [
{
description: 'Peer-to-Peer Payments',
link: '/docs/use-cases/payments/peer-to-peer-payments-uc/',
},
{
description: 'Cross-Currency Payments',
link: '/docs/concepts/payment-types/cross-currency-payments/',
},
{
description: 'Smart Contracts',
link: '/docs/use-cases/payments/smart-contracts-uc/',
},
],
},
];
const intermediateVideos = [
{
src: require('../static/img/backgrounds/docs-advanced-payment-features@2x.png'),
title: 'Advanced Payment Features',
url: 'https://www.youtube.com/embed/e2Iwsk37LMk?rel=0&amp;showinfo=0&amp;autoplay=1',
},
{
src: require('../static/img/backgrounds/docs-governance@2x.png'),
title: 'Governance and the Amendment Process',
url: 'https://www.youtube.com/embed/4GbRdanHoR4?rel=0&amp;showinfo=0&amp;autoplay=1',
},
{
src: require('../static/img/backgrounds/docs-sidechains@2x.png'),
title: 'Federated Sidechains',
url: 'https://www.youtube.com/embed/NhH4LM8NxgY?rel=0&amp;showinfo=0&amp;autoplay=1',
},
];
const getStartedVideos = [
{
src: require('../static/img/backgrounds/docs-intro-to-XRP-ledger@2x.png'),
title: 'Intro to XRP Ledger',
url: 'https://www.youtube.com/embed/sVTybJ3cNyo?rel=0&amp;showinfo=0&amp;autoplay=1',
},
{
src: require('../static/img/backgrounds/docs-accounts@2x.png'),
title: 'Accounts',
url: 'https://www.youtube.com/embed/eO8jE6PftX8?rel=0&amp;showinfo=0&amp;autoplay=1',
},
{
src: require('../static/img/backgrounds/docs-decentralized-exchange@2x.png'),
title: 'Decentralized Exchange',
url: 'https://www.youtube.com/embed/VWNrHBDfXvA?rel=0&amp;showinfo=0&amp;autoplay=1',
},
{
src: require('../static/img/backgrounds/docs-tokenization@2x.png'),
title: 'Tokenization',
url: 'https://www.youtube.com/embed/Oj4cWOiWf4A?rel=0&amp;showinfo=0&amp;autoplay=1',
},
];
const devTools = [
{
title: 'XRP Faucets',
link: '/resources/dev-tools/xrp-faucets',
description: 'Get credentials and test-XRP for XRP Ledger Testnet or Devnet.',
},
{
title: 'WebSocket Tool',
link: '/resources/dev-tools/websocket-api-tool',
description: 'Send sample requests and get responses from the rippled API.',
},
{
title: 'XRP Ledger Explorer',
link: 'https://livenet.xrpl.org',
description:
'View validations of new ledger versions in real-time, chart the location of servers in the XRP Ledger.',
},
{
title: 'Transaction Sender',
link: '/resources/dev-tools/tx-sender',
description:
'Test how your code handles various XRP Ledger transactions by sending them over the Testnet to the address.',
},
];
function UseCasesCard(props: {
useCase: {
id: string;
title: string;
imgClass: string;
subItems: { description: string; link: string }[];
};
}) {
const { useCase } = props;
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
return (
<div className="landing page-docs page-docs-index">
{/* 1. Hero */}
<HeaderHeroPrimaryMedia
headline="XRP Ledger (XRPL) Documentation"
subtitle="Explore XRPL documentation with our essential guide for developers and admins who want to start building and integrating with the XRP Ledger."
media={{ type: 'image', src: require('../static/img/bds-2026/HeroMedia.jpg'), alt: 'XRPL Documentation' }}
/>
{/* 2. Get Started Carousel */}
<CarouselCardList
variant="green"
buttonVariant="green"
heading="Get Started: XRPL Developer & Admin Resources"
description="Learn your way. Read docs, watch videos, or get hands-on with code samples. Explore different ways to learn on the XRP Ledger."
cards={[
{
icon: '',
title: 'Ready-to-Use Code Samples',
description:
'Run complete code snippets to understand XRPL integration in seconds.',
href: '/docs/tutorials/',
},
{
icon: '',
title: 'Launch Your First Project',
description:
'Explore funding and development opportunities for your project on the XRPL.',
href: '/resources/grant-funding/',
},
{
icon: '',
title: 'Step-by-Step Tutorials',
description:
'Follow guided walkthroughs to master XRPL fundamentals and industry best practices.',
href: '/docs/tutorials/',
},
]}
/>
{/* 3. Core Concepts Text Grid */}
<CardsTextGrid
heading="Dig Into Core Concepts & Tools"
cards={[
{
heading: 'XRPL Fundamentals',
description: (
<>
Discover the basics of the XRPL by learning about accounts,
transactions, and the ledger structure.{' '}<br/><br/>
<a href="/docs/concepts/">Read More</a>
</>
),
},
{
heading: 'Advanced XRPL Topics: Go Deeper',
description: (
<>
Implement real-world solutions by combining XRPL primitives for
lending, token issuance, DEX trading, and more.{' '}<br/><br/>
<a href="/docs/use-cases/">Read More</a>
</>
),
},
]}
/>
{/* 4. Featured Video — Advanced Payment Features */}
<FeaturedVideoHero
headline="Advanced Payment Features"
subtitle="Master sophisticated movement of value through features such as escrows, checks, or payment channels."
video={{
source: {
type: 'embed',
embedUrl: 'https://www.youtube.com/embed/e2Iwsk37LMk',
},
}}
links={[
{
label: 'Watch Now',
href: 'https://youtube.com/playlist?list=PLJQ55Tj1hIVZtJ_JdTvSum2qMTsedWkNi',
},
]}
/>
{/* 5. Featured Video — Governance */}
<FeaturedVideoHero
headline="Governance and the Amendment Process"
subtitle="Understand how the decentralized network evolves through validator voting and how new features are activated."
video={{
source: {
type: 'embed',
embedUrl: 'https://www.youtube.com/embed/4GbRdanHoR4',
},
}}
links={[
{
label: 'Watch Now',
href: 'https://youtube.com/playlist?list=PLJQ55Tj1hIVZtJ_JdTvSum2qMTsedWkNi',
},
]}
/>
{/* 6. Developer Reference Links */}
<LinkSmallGrid
variant="gray"
heading="Developers"
description="Master the Protocol: Essential References"
links={[
{
label: 'Transaction Types',
href: '/docs/references/protocol/transactions/types',
},
{
label: 'Account Methods',
href: '/docs/references/http-websocket-apis/public-api-methods/account-methods',
},
{
label: 'Ledger Entry Types',
href: '/docs/references/protocol/ledger-data/ledger-entry-types',
},
{
label: 'Transaction Methods',
href: '/docs/references/http-websocket-apis/public-api-methods/transaction-methods',
},
{
label: 'Basic Data Types',
href: '/docs/references/protocol/data-types/basic-data-types',
},
{
label: 'Path and Orderbook Methods',
href: '/docs/references/http-websocket-apis/public-api-methods/path-and-order-book-methods',
},
]}
/>
{/* 7. Server Admin Reference Links */}
<LinkSmallGrid
variant="gray"
heading="Server Admins"
description="Master the Protocol: Essential References"
links={[
{
label: 'Commandline Usage',
href: '/docs/infrastructure/commandline-usage',
},
{
label: 'Admin API Methods',
href: '/docs/references/http-websocket-apis/admin-api-methods',
},
]}
/>
{/* 8. Dev Tools Directory */}
<LinkTextDirectory
heading="Tools to Test & Deploy"
cards={[
{
heading: 'Get Test XRP (Faucets)',
description:
'Get credentials and test-XRP for XRP Ledger Testnet or Devnet.',
buttons: [
{ label: 'Access Here', href: '/resources/dev-tools/xrp-faucets' },
],
},
{
heading: 'Send Test Transactions',
description:
'Test how your code handles various XRP Ledger transactions by sending them over the Testnet to the address.',
buttons: [
{ label: 'Access Here', href: '/resources/dev-tools/tx-sender' },
],
},
{
heading: 'Explore WebSocket API',
description:
'Send sample requests and get responses from the rippled API.',
buttons: [
{
label: 'Access Here',
href: '/resources/dev-tools/websocket-api-tool',
},
],
},
{
heading: 'Monitor the XRP Ledger',
description:
'View validations of new ledger versions in real-time, chart the location of servers in the XRP Ledger.',
buttons: [
{ label: 'Access Here', href: 'https://livenet.xrpl.org/' },
],
},
]}
/>
{/* 9a. Use Cases — Payments */}
<FeatureTwoColumn
color="lilac"
arrange="left"
title="Payments"
description=""
media={{ src: require('../static/img/bds-2026/FeatureMedia-1.jpg'), alt: 'Payments' }}
links={[
{
label: 'Peer-to-Peer Payments',
href: '/docs/use-cases/payments/peer-to-peer-payments-uc',
},
{
label: 'Cross-Currency Payments',
href: '/docs/concepts/payment-types/cross-currency-payments',
},
{
label: 'Smart Contracts',
href: '/docs/use-cases/payments/smart-contracts-uc',
},
]}
/>
{/* 9b. Use Cases — Tokens */}
<FeatureTwoColumn
color="lilac"
arrange="right"
title="Tokens"
description=""
media={{ src: require('../static/img/bds-2026/FeatureMedia-2.jpg'), alt: 'Tokens' }}
links={[
{
label: 'Stablecoin Issuer',
href: '/docs/use-cases/tokenization/stablecoin-issuer',
},
{
label: 'NFT Marketplace',
href: '/docs/use-cases/tokenization/nft-mkt-overview',
},
{
label: 'Digital Artist',
href: '/docs/use-cases/tokenization/digital-artist',
},
]}
/>
{/* 9c. Use Cases — On-Chain Finance */}
<FeatureTwoColumn
color="lilac"
arrange="left"
title="On-Chain Finance"
description=""
media={{ src: require('../static/img/bds-2026/FeatureMedia-3.jpg'), alt: 'On-Chain Finance' }}
links={[
{
label: 'List XRP as an Exchange',
href: '/docs/use-cases/defi/list-xrp-as-an-exchange',
},
{
label: 'Trade with an Auction Slot',
href: '/docs/tutorials/javascript/amm/trade-with-auction-slot',
},
]}
/>
{/* 9d. Use Cases — Compliance Features */}
<FeatureTwoColumn
color="lilac"
arrange="right"
title="Compliance Features"
description=""
media={{ src: require('../static/img/bds-2026/FeatureMedia.jpg'), alt: 'Compliance Features' }}
links={[
{
label: 'Build a Credential Issuing Service',
href: '/docs/tutorials/javascript/build-apps/credential-issuing-service',
},
{
label: 'Create a Permissioned Domain',
href: '/docs/tutorials/javascript/compliance/create-permissioned-domains',
},
]}
/>
{/* 10. SDK Tiles */}
<SmallTilesSection
headline="Build with SDKs"
cardVariant="neutral"
cards={[
{
icon: require('../static/img/logos/javascript.svg'),
iconAlt: 'JavaScript',
label: 'Get Started with Javascript',
href: '/docs/tutorials/javascript',
},
{
icon: require('../static/img/logos/python.svg'),
iconAlt: 'Python',
label: 'Python',
href: '/docs/tutorials/python',
},
{
icon: require('../static/img/logos/java.svg'),
iconAlt: 'Java',
label: 'Java',
href: '/docs/tutorials/java/build-apps/get-started',
},
{
icon: require('../static/img/logos/golang.svg'),
iconAlt: 'Go',
label: 'Go',
href: '/docs/tutorials/go',
},
{
icon: '',
iconAlt: 'PHP',
label: 'PHP',
href: '/docs/tutorials/php',
},
]}
/>
{/* 11. Infrastructure Cards */}
<CardsIconGrid
heading="XRPL Infrastructure: Running a Server"
cards={[
{
icon: '',
iconAlt: 'Server',
heading: 'Install Your XRPL Server: Rippled & Clio',
description: (
<>
Take ownership of your connection to the blockchain with a core
server that can submit transactions, read balances, and store a
complete copy of the ledger data.{' '}
<a href="/docs/infrastructure/installation">Learn More</a>
</>
),
},
{
icon: '',
iconAlt: 'Network',
heading: 'Node Configuration',
description: (
<>
Customize your server configuration for your use case, including
data retention, network connectivity, and performance tuning.{' '}
<a href="/docs/infrastructure/configuration">Learn More</a>
</>
),
},
{
icon: '',
iconAlt: 'Tools',
heading: 'Troubleshooting Your Node',
description: (
<>
Diagnose and solve problems with your server to maximize uptime
and reliability.{' '}
<a href="/docs/infrastructure/troubleshooting">Learn More</a>
</>
),
},
]}
/>
{/* 12. Contribute Cards */}
<StandardCardGroupSection
headline="Get Involved: Contribute to the XRP Ledger"
description=""
variant="neutral"
cards={[
{
headline: 'Contribute to the Protocol',
callsToAction: [
{ children: 'Contribute Now', href: '/resources/contribute-code' },
],
children:
'Have you identified gaps, edge cases, or improvements through real-world use? Contribute code or proposals and help shape the future of XRPL at the protocol level.',
},
{
headline: 'Contribute to Docs',
callsToAction: [
{
children: 'Contribute Now',
href: '/resources/contribute-documentation',
},
],
children: (
<>
Contribute to{' '}
<a href="https://xrpl.org/" target="_blank" rel="noreferrer">
XRPL.org
</a>
, the go-to resource for all things XRP Ledger.
</>
),
},
{
headline: 'Contribute a Blog Post',
callsToAction: [
{
children: 'Contribute Now',
href: '/resources/contribute-blog',
},
],
children:
'Share how you solved a real-world problem using the XRP Ledger, including your architecture decisions, workflows, tradeoffs, and lessons learned. Contribute a blog post to help other developers build faster.',
},
]}
/>
{/* 13. Continuous Learning Cards */}
<StandardCardGroupSection
headline="Continuous Learning: Additional XRPL Resources"
description=""
variant="neutral"
cards={[
{
headline: 'XRPL Learning Portal',
callsToAction: [
{ children: 'Learn More', href: 'https://learn.xrpl.org/' },
],
children:
'From blockchain fundamentals to building on the XRPL, create your own learning journey and progress at your own pace.',
},
{
headline: 'Aquarium Residency Program',
callsToAction: [
{
children: 'Learn More',
href: 'https://www.xrpl-commons.org/residency',
},
],
children:
'A hands-on residency program helping teams build, test, and launch real-world solutions on the XRP Ledger.',
},
{
headline: 'Developer Community Forum',
callsToAction: [
{
children: 'Learn More',
href: 'https://discord.gg/sfX3ERAMjH',
},
],
children:
'Join the XRPL developer community on Discord! Ask questions, share tips, and collaborate with builders turning ideas into real-world projects.',
},
]}
/>
<div className="col">
<img className={'use-cases-img img-fluid mb-2 shadow ' + useCase.imgClass} alt={useCase.title} id={useCase.id} />
<h5 className="mt-4">{translate(useCase.title)}</h5>
<NavList pages={useCase.subItems} />
</div>
);
}
function FlatCard(props: { href: string; title: string; description: string; linkText: string; imgClass }) {
const { title, description, linkText, href, imgClass } = props;
return (
<Link to={href} className="card flat-card float-up-on-hover">
<img className={'mb-2 ' + imgClass} alt={title} />
<h5 className="row">
<div className="nav-link">{title}</div>
</h5>
<p className="row faded-text flat-card-padding">{description}</p>
<div className="col align-button-on-bottom">
<div className="btn btn-primary btn-arrow" id={href + '-button'}>
{linkText}
</div>
</div>
</Link>
);
}
function VideoCard(props: { url: string; title: string; src: string }) {
const { url, title, src } = props;
return (
<div className="col float-up-on-hover">
<a href={url} id="playvideo" className="btn1" data-url={url}>
<img alt={title} className="get-started-img video-image" id={title} src={src} />
<h6 className="pt-3">{title}</h6>
</a>
</div>
);
}
function DevToolCard(props: { link: string; title: string; description: string }) {
const { link, title, description } = props;
return (
<Link to={link} className="col dev-tools-link">
<h6 className="btn-arrow">{title}</h6>
<p> {description}</p>
</Link>
);
}
function PrimaryButton(props: { href: string; text: string; isArrowUp: boolean }) {
const { href, text, isArrowUp } = props;
return (
<Link className={`btn btn-primary ${isArrowUp ? 'btn-arrow-out' : 'btn-arrow'}`} id={href + '-button'} to={href}>
{text}
</Link>
);
}
export default function Docs() {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
return (
<div className="landing page-docs page-docs-index landing-builtin-bg overflow-hidden styled-page">
<div>
<section className="text-center title-space">
<div className="col-lg-9 mx-auto text-center">
<div className="d-flex flex-column-reverse">
<h1>{translate('Documentation')}</h1>
<h6 className="eyebrow mb-3">{translate('XRP Ledger Developer Resources')}</h6>
</div>
</div>
</section>
<section className="container-new ">
<div className="nav card-grid flat-card-grid card-grid-3xN">
<div className="col">
<FlatCard
href="/docs/concepts/"
title={translate('Concepts')}
description={translate('Learn the "what" and the "why" behind fundamental aspects of the XRP Ledger.')}
linkText={translate('Read the Docs')}
imgClass="concepts-doc-illustration"
/>
</div>
<div className="col">
<FlatCard
href="/docs/tutorials/"
title={translate('Tutorials')}
description={translate('Get step-by-step guidance to perform common tasks with the XRP Ledger.')}
linkText={translate('View Tutorials')}
imgClass="tutorial-illustration"
/>
</div>
<div className="col">
<FlatCard
href="/docs/references/"
title={translate('References')}
description={translate(
'Look up reference documentation for the XRP Ledger protocol, API methods, and more.'
)}
linkText={translate('View References')}
imgClass="ref-book-illustration"
/>
</div>
</div>
</section>
<section className="container-new">
<h4 className="pb-4">{translate('Use Cases')}</h4>
<div className="card-grid card-grid-3xN use-cases">
{useCases.map(useCase => (
<UseCasesCard useCase={useCase} key={useCase.id} />
))}
</div>
</section>
<section className="container-new ">
<h4 className="pb-4">{translate('Getting Started')}</h4>
<div className="card-grid card-grid-2xN quickstart-card">
<div className="col">
<Link to="/docs/introduction/" className="card float-up-on-hover">
<h5 className="mt-7">{translate('Introduction to the XRP Ledger')}</h5>
<p className="mb-8 mt-4">{translate('An introduction to fundamental aspects of the XRP Ledger.')}</p>
<div className="dg-lg-block mb-3">
<div className="btn btn-primary btn-arrow get-started-button">{translate('Introduction')}</div>
</div>
<img alt="quick-start" id="quick-start-img" className="quickstart-image" />
</Link>
</div>
<div className="col">
<div className="card-grid card-grid-2xN video-grid">
{getStartedVideos.map(video => (
<VideoCard url={video.url} title={translate(video.title)} src={video.src} key={video.url} />
))}
</div>
<div className="align-button-on-bottom">
<PrimaryButton
href="https://www.youtube.com/playlist?list=PLJQ55Tj1hIVZtJ_JdTvSum2qMTsedWkNi"
text={translate('Watch Full Series')}
isArrowUp={true}
/>
</div>
</div>
</div>
</section>
<section className="container-new ">
<div className="d-flex flex-column-reverse col-sm-8 p-0">
<h3 className="h4 h2-sm">{translate('Interact with the XRP Ledger in a language of your choice')}</h3>
<h6 className="eyebrow mb-3">{translate('Explore SDKs')}</h6>
</div>
<div className="card-grid card-grid-2xN">
<div className="col">
<div className="card-grid langs-cards card-grid-2xN mt-10" id="langs-cards">
<div className="col langs">
<Link to="/docs/tutorials/javascript/">
<img alt="Javascript Logo" src={require('../static/img/logos/javascript.svg')} className="circled-logo" />
<h5 className="btn-arrow">{translate('Javascript')}</h5>
</Link>
</div>
<div className="col langs">
<Link to="/docs/tutorials/python/">
<img alt="Python Logo" src={require('../static/img/logos/python.svg')} className="circled-logo" />
<h5 className="btn-arrow">{translate('Python')}</h5>
</Link>
</div>
<div className="col langs">
<Link to="/docs/tutorials/java/build-apps/get-started/">
<img alt="Java Logo" src={require('../static/img/logos/java.svg')} className="circled-logo" />
<h5 className="btn-arrow">{translate('Java')}</h5>
</Link>
</div>
<div className="col langs">
<Link to="/docs/tutorials/go/">
<img
alt="Go Logo"
src={require("../static/img/logos/golang.svg")}
className="circled-logo"
/>
<h5 className="btn-arrow">{translate("GoLang")}</h5>
</Link>
</div>
</div>
</div>
<div className="col center-image">
<img className="img-fluid sdk-img" />
</div>
</div>
</section>
<section className="container-new ">
<h4 className="pb-4">{translate('Intermediate Learning Sources')}</h4>
<div className="card-grid card-grid-3xN">
{intermediateVideos.map(video => (
<VideoCard url={video.url} title={translate(video.title)} src={video.src} key={video.url} />
))}
</div>
</section>
<section className="container-new ">
<div className="card-grid card-grid-2xN">
<div className="col d-flex align-items-center justify-content-center">
<img className="dev-tools-img" />
</div>
<div className="col explore-links">
<div className="d-flex flex-column-reverse w-100">
<h4 className="mb-10">{translate('Explore, Test, Verify')}</h4>
<h6 className="mb-3">{translate('Explore Dev Tools')}</h6>
</div>
<p className="mb-20">
{translate(
'Use these web-based tools to assist during all stages of development, from getting your first payment to testing your implementation for best practices.'
)}
</p>
<div className="card-grid card-grid-2xN">
{devTools.map(card => (
<DevToolCard
link={card.link}
title={translate(card.title)}
description={translate(card.description)}
key={card.link}
/>
))}
</div>
<PrimaryButton href="/resources/dev-tools" text={translate('View All tools')} isArrowUp={false} />
</div>
</div>
</section>
<section className="container-new " id="docs-browse-by">
<div className="row card-grid card-grid-2xN">
<div className="col" id="popular-topics">
<h2 className="h4">{translate('Browse By Recommended Pages')}</h2>
<NavList pages={recommendedPages} />
</div>
<div className="col">
<div className="card cta-card p-8-sm p-10-until-sm br-8">
<div className="z-index-1 position-relative">
<h2 className="h4 mb-8-sm mb-10-until-sm">{translate('Get Free Test XRP')}</h2>
<p className="mb-10">
{translate(
'Connect to the XRP Ledger Testnet network to develop and test your apps built on the XRP Ledger, without risking real money or impacting production XRP Ledger users.'
)}
</p>
<Link className="btn btn-primary btn-arrow" to="/resources/dev-tools/xrp-faucets/">
{translate('Generate Testnet Credentials')}
</Link>
</div>
</div>
</div>
</div>
</section>
{/* full docs index isn't ported to Redocly
<section className="container-new">
<a href="/docs/full-index" className="btn-arrow arrow-purple documentation-index mr-auto">
{translate('See full documentation index')}
</a>
</section>
*/}
</div>
</div>
);
}

View File

@@ -13,7 +13,7 @@ In its default configuration, the `rippled` server automatically deletes outdate
## Warnings
Storing full history is expensive. As of 2026-01-25, the full history of the XRP Ledger occupies approximately **39 terabytes** of disk space, which must be entirely stored on fast solid state disk drives for proper server performance. Such a large amount of solid state storage is not cheap, and the total amount of history you must store increases by approximately 12 GB per day.
Storing full history is expensive. As of 2023-07-19, the full history of the XRP Ledger occupies approximately **26 terabytes** of disk space, which must be entirely stored on fast solid state disk drives for proper server performance. Such a large amount of solid state storage is not cheap, and the total amount of history you must store increases by approximately 12 GB per day.
Additionally, storing full history in NuDB requires single files that are larger than the 16 TB limit of ext4 filesystems, which is the default on many Linux distributions. You must use a filesystem with a larger single-file limit, such as XFS (recommended) or ZFS.

View File

@@ -81,10 +81,10 @@ Before you install Clio, you must meet the following requirements.
```
gpg: WARNING: no command supplied. Trying to guess what you mean ...
pub ed25519 2026-02-16 [SC] [expires: 2033-02-14]
E057C1CF72B0DF1A4559E8577DEE9236AB06FAA6
uid TechOps Team at Ripple <techops+rippled@ripple.com>
sub ed25519 2026-02-16 [S] [expires: 2029-02-15]
pub rsa3072 2019-02-14 [SC] [expires: 2026-02-17]
C0010EC205B35A3310DC90DE395F97FFCCAFD9A2
uid TechOps Team at Ripple <techops+rippled@ripple.com>
sub rsa3072 2019-02-14 [E] [expires: 2026-02-17]
```

View File

@@ -49,10 +49,10 @@ Before you install `rippled`, you must meet the [System Requirements](system-req
The output should include an entry for Ripple such as the following:
```
pub ed25519 2026-02-16 [SC] [expires: 2033-02-14]
E057C1CF72B0DF1A4559E8577DEE9236AB06FAA6
uid TechOps Team at Ripple <techops+rippled@ripple.com>
sub ed25519 2026-02-16 [S] [expires: 2029-02-15]
pub rsa3072 2019-02-14 [SC] [expires: 2026-02-17]
C0010EC205B35A3310DC90DE395F97FFCCAFD9A2
uid TechOps Team at Ripple <techops+rippled@ripple.com>
sub rsa3072 2019-02-14 [E] [expires: 2026-02-17]
```
In particular, make sure that the fingerprint matches. (In the above example, the fingerprint is on the second line, starting with `C001`.)

View File

@@ -87,7 +87,6 @@ The request includes the following parameters:
| `Field` | Type | Description |
|:---------------|:--------|:--------------------------------------------------|
| `tx_json` | Object | [Transaction definition](../../../protocol/transactions/index.md) in JSON format |
| `signature_target` | String | _(Optional)_ Specifies where in the transaction metadata the signature information should be stored. Currently, the only valid value is `CounterpartySignature`. |
| `secret` | String | _(Optional)_ The secret seed of the account supplying the transaction, used to sign it. Do not send your secret to untrusted servers or through unsecured network connections. Cannot be used with `key_type`, `seed`, `seed_hex`, or `passphrase`. |
| `seed` | String | _(Optional)_ The secret seed of the account supplying the transaction, used to sign it. Must be in the XRP Ledger's [base58][] format. If provided, you must also specify the `key_type`. Cannot be used with `secret`, `seed_hex`, or `passphrase`. |
| `seed_hex` | String | _(Optional)_ The secret seed of the account supplying the transaction, used to sign it. Must be in hexadecimal format. If provided, you must also specify the `key_type`. Cannot be used with `secret`, `seed`, or `passphrase`. |

View File

@@ -94,7 +94,6 @@ The request includes the following parameters:
|:-------------|:---------------------|:---------------------------------------|
| `account` | String - [Address][] | The address which is providing the signature. |
| `tx_json` | Object | The [Transaction](../../../protocol/transactions/index.md) to sign. Unlike using the [sign method][], all fields of the transaction must be provided, including `Fee` and `Sequence`. The transaction must include the field `SigningPubKey` with an empty string as the value. The object may optionally contain a `Signers` array with previously-collected signatures. |
| `signature_target` | String | _(Optional)_ Specifies where in the transaction metadata the signature information should be stored. Currently, the only valid value is `CounterpartySignature`. |
| `secret` | String | _(Optional)_ Secret key of the account supplying the transaction, used to sign it. Do not send your secret to untrusted servers or through unsecured network connections. Cannot be used with `key_type`, `seed`, `seed_hex`, or `passphrase`. |
| `seed` | String | _(Optional)_ Secret key of the account supplying the transaction, used to sign it. Must be in the XRP Ledger's [base58][] format. If provided, you must also specify the `key_type`. Cannot be used with `secret`, `seed_hex`, or `passphrase`. |
| `seed_hex` | String | _(Optional)_ Secret key of the account supplying the transaction, used to sign it. Must be in hexadecimal format. If provided, you must also specify the `key_type`. Cannot be used with `secret`, `seed`, or `passphrase`. |

View File

@@ -115,11 +115,6 @@ Use these methods to perform convenient tasks, such as ping and random number ge
* **[`ping`](utility-methods/ping.md)** - Confirm connectivity with the server.
* **[`random`](utility-methods/random.md)** - Generate a random number.
## [Vault Methods](vault-methods/index.md)
Use these methods to retrieve vault information.
* **[`vault_info`](vault-methods/vault_info.md)** - Get information about a specific vault.
## Deprecated Methods

View File

@@ -81,7 +81,6 @@ The request includes the following parameters:
| `Field` | Type | Description |
|:---------------|:--------|:--------------------------------------------------|
| `tx_json` | Object | [Transaction definition](../../../protocol/transactions/index.md) in JSON format, optionally omitting any auto-fillable fields. |
| `signature_target` | String | _(Optional)_ Specifies where in the transaction metadata the signature information should be stored. Currently, the only valid value is `CounterpartySignature`. |
| `secret` | String | _(Optional)_ Secret key of the account supplying the transaction, used to sign it. Do not send your secret to untrusted servers or through unsecured network connections. Cannot be used with `key_type`, `seed`, `seed_hex`, or `passphrase`. |
| `seed` | String | _(Optional)_ Secret key of the account supplying the transaction, used to sign it. Must be in the XRP Ledger's [base58][] format. If provided, you must also specify the `key_type`. Cannot be used with `secret`, `seed_hex`, or `passphrase`. |
| `seed_hex` | String | _(Optional)_ Secret key of the account supplying the transaction, used to sign it. Must be in hexadecimal format. If provided, you must also specify the `key_type`. Cannot be used with `secret`, `seed`, or `passphrase`. |

View File

@@ -1,9 +0,0 @@
---
metadata:
indexPage: true
---
# Vault Methods
A `Vault` object in the XRP Ledger represents the state of a tokenized vault. Use these methods to interact with vaults.
{% child-pages /%}

View File

@@ -1,274 +0,0 @@
---
seo:
description: Retrieve information about a vault, its owner, available assets, and details on issued shares.
labels:
- Single Asset Vault
---
# vault_info
[[Source]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/rpc/handlers/VaultInfo.cpp "Source")
The `vault_info` command retrieves information about a vault, its owner, available assets, and details on issued shares. All information retrieved is relative to a particular version of the ledger. {% badge href="https://github.com/XRPLF/rippled/releases/tag/3.1.0" %}New in: rippled 3.1.0{% /badge %}
## Request Format
An example of the request format:
{% tabs %}
{% tab label="WebSocket" %}
```json
{
"command": "vault_info",
"vault_id": "9E48171960CD9F62C3A7B6559315A510AE544C3F51E02947B5D4DAC8AA66C3BA"
}
```
{% /tab %}
{% tab label="JSON-RPC" %}
```json
{
"method": "vault_info",
"params": [
{
"vault_id": "9E48171960CD9F62C3A7B6559315A510AE544C3F51E02947B5D4DAC8AA66C3BA"
}
]
}
```
{% /tab %}
{% tab label="Commandline" %}
```sh
#Syntax: vault_info [<vault_id>]
rippled vault_info 9E48171960CD9F62C3A7B6559315A510AE544C3F51E02947B5D4DAC8AA66C3BA
```
{% /tab %}
{% /tabs %}
{% try-it method="vault_info" server="devnet" /%}
The request includes the following parameters:
| Field | Type | Required? | Description |
| :--------- | :------------------- | :-------- | :------------------------------------------------------ |
| `vault_id` | String | No | The [ledger entry ID][] of the `Vault` to be returned. |
| `owner` | String - [Address][] | No | The account address of the `Vault` owner. |
| `seq` | Number | No | The transaction sequence number that created the vault. |
You can provide either the `vault_id`, or both `owner` and `seq` values in the request.
## Response Format
An example of a successful response:
{% tabs %}
{% tab label="WebSocket" %}
```json
{
"result": {
"ledger_current_index": 3280200,
"validated": false,
"vault": {
"Account": "rQhUcbJoDfvgXr1EkMwarLP5QT3XinEBDg",
"Asset": {
"mpt_issuance_id": "002F830036E4E56185F871D70CFFC7BDD554F897606BB6D3"
},
"Data": "50726976617465207661756C7420666F72207475746F7269616C73",
"Flags": 65536,
"LedgerEntryType": "Vault",
"Owner": "rJdYtgaiEgzL7xD2QdPKg5xoHkWc7CZjvm",
"OwnerNode": "0",
"PreviousTxnID": "F73B073028D7EF14C5DD907591E579EBFEDBA891F4AE0B951439C240C42AE0D4",
"PreviousTxnLgrSeq": 3113735,
"Sequence": 3113728,
"ShareMPTID": "00000001FCE5D5E313303F3D0C700789108CC6BE7D711493",
"WithdrawalPolicy": 1,
"index": "9E48171960CD9F62C3A7B6559315A510AE544C3F51E02947B5D4DAC8AA66C3BA",
"shares": {
"DomainID": "17060E04AD63975CDE5E4B0C6ACB95ABFA2BA1D569473559448B6E556F261D4A",
"Flags": 60,
"Issuer": "rQhUcbJoDfvgXr1EkMwarLP5QT3XinEBDg",
"LedgerEntryType": "MPTokenIssuance",
"MPTokenMetadata": "7B226163223A2264656669222C226169223A7B226578616D706C655F696E666F223A2274657374227D2C2264223A2250726F706F7274696F6E616C206F776E65727368697020736861726573206F6620746865207661756C74222C2269223A226578616D706C652E636F6D2F7661756C742D7368617265732D69636F6E2E706E67222C22696E223A225661756C74204F776E6572222C226E223A225661756C7420536861726573222C2274223A22534841524531222C227573223A5B7B2263223A2277656273697465222C2274223A2241737365742057656273697465222C2275223A226578616D706C652E636F6D2F6173736574227D2C7B2263223A22646F6373222C2274223A22446F6373222C2275223A226578616D706C652E636F6D2F646F6373227D5D7D",
"OutstandingAmount": "0",
"OwnerNode": "0",
"PreviousTxnID": "F73B073028D7EF14C5DD907591E579EBFEDBA891F4AE0B951439C240C42AE0D4",
"PreviousTxnLgrSeq": 3113735,
"Sequence": 1,
"index": "F231A0382544EC0ABE810A9D292F3BD455A21CD13CC1DFF75EAFE957A1C8CAB4",
"mpt_issuance_id": "00000001FCE5D5E313303F3D0C700789108CC6BE7D711493"
}
}
},
"status": "success",
"type": "response"
}
```
{% /tab %}
{% tab label="JSON-RPC" %}
```json
200 OK
{
"result": {
"ledger_hash": "DC8D26A6DC85C70A112F5D798A3B5AF599A730098AFCC20CE18BFE6ADA5E66F9",
"ledger_index": 3279463,
"status": "success",
"validated": true,
"vault": {
"Account": "rQhUcbJoDfvgXr1EkMwarLP5QT3XinEBDg",
"Asset": {
"mpt_issuance_id": "002F830036E4E56185F871D70CFFC7BDD554F897606BB6D3"
},
"Data": "50726976617465207661756C7420666F72207475746F7269616C73",
"Flags": 65536,
"LedgerEntryType": "Vault",
"Owner": "rJdYtgaiEgzL7xD2QdPKg5xoHkWc7CZjvm",
"OwnerNode": "0",
"PreviousTxnID": "F73B073028D7EF14C5DD907591E579EBFEDBA891F4AE0B951439C240C42AE0D4",
"PreviousTxnLgrSeq": 3113735,
"Sequence": 3113728,
"ShareMPTID": "00000001FCE5D5E313303F3D0C700789108CC6BE7D711493",
"WithdrawalPolicy": 1,
"index": "9E48171960CD9F62C3A7B6559315A510AE544C3F51E02947B5D4DAC8AA66C3BA",
"shares": {
"DomainID": "17060E04AD63975CDE5E4B0C6ACB95ABFA2BA1D569473559448B6E556F261D4A",
"Flags": 60,
"Issuer": "rQhUcbJoDfvgXr1EkMwarLP5QT3XinEBDg",
"LedgerEntryType": "MPTokenIssuance",
"MPTokenMetadata": "7B226163223A2264656669222C226169223A7B226578616D706C655F696E666F223A2274657374227D2C2264223A2250726F706F7274696F6E616C206F776E65727368697020736861726573206F6620746865207661756C74222C2269223A226578616D706C652E636F6D2F7661756C742D7368617265732D69636F6E2E706E67222C22696E223A225661756C74204F776E6572222C226E223A225661756C7420536861726573222C2274223A22534841524531222C227573223A5B7B2263223A2277656273697465222C2274223A2241737365742057656273697465222C2275223A226578616D706C652E636F6D2F6173736574227D2C7B2263223A22646F6373222C2274223A22446F6373222C2275223A226578616D706C652E636F6D2F646F6373227D5D7D",
"OutstandingAmount": "0",
"OwnerNode": "0",
"PreviousTxnID": "F73B073028D7EF14C5DD907591E579EBFEDBA891F4AE0B951439C240C42AE0D4",
"PreviousTxnLgrSeq": 3113735,
"Sequence": 1,
"index": "F231A0382544EC0ABE810A9D292F3BD455A21CD13CC1DFF75EAFE957A1C8CAB4",
"mpt_issuance_id": "00000001FCE5D5E313303F3D0C700789108CC6BE7D711493"
}
}
}
}
```
{% /tab %}
{% tab label="Commandline" %}
```json
Loading: "/etc/rippled.cfg"
Connecting to 127.0.0.1:5005
{
"result": {
"ledger_current_index": 3280200,
"validated": false,
"vault": {
"Account": "rQhUcbJoDfvgXr1EkMwarLP5QT3XinEBDg",
"Asset": {
"mpt_issuance_id": "002F830036E4E56185F871D70CFFC7BDD554F897606BB6D3"
},
"Data": "50726976617465207661756C7420666F72207475746F7269616C73",
"Flags": 65536,
"LedgerEntryType": "Vault",
"Owner": "rJdYtgaiEgzL7xD2QdPKg5xoHkWc7CZjvm",
"OwnerNode": "0",
"PreviousTxnID": "F73B073028D7EF14C5DD907591E579EBFEDBA891F4AE0B951439C240C42AE0D4",
"PreviousTxnLgrSeq": 3113735,
"Sequence": 3113728,
"ShareMPTID": "00000001FCE5D5E313303F3D0C700789108CC6BE7D711493",
"WithdrawalPolicy": 1,
"index": "9E48171960CD9F62C3A7B6559315A510AE544C3F51E02947B5D4DAC8AA66C3BA",
"shares": {
"DomainID": "17060E04AD63975CDE5E4B0C6ACB95ABFA2BA1D569473559448B6E556F261D4A",
"Flags": 60,
"Issuer": "rQhUcbJoDfvgXr1EkMwarLP5QT3XinEBDg",
"LedgerEntryType": "MPTokenIssuance",
"MPTokenMetadata": "7B226163223A2264656669222C226169223A7B226578616D706C655F696E666F223A2274657374227D2C2264223A2250726F706F7274696F6E616C206F776E65727368697020736861726573206F6620746865207661756C74222C2269223A226578616D706C652E636F6D2F7661756C742D7368617265732D69636F6E2E706E67222C22696E223A225661756C74204F776E6572222C226E223A225661756C7420536861726573222C2274223A22534841524531222C227573223A5B7B2263223A2277656273697465222C2274223A2241737365742057656273697465222C2275223A226578616D706C652E636F6D2F6173736574227D2C7B2263223A22646F6373222C2274223A22446F6373222C2275223A226578616D706C652E636F6D2F646F6373227D5D7D",
"OutstandingAmount": "0",
"OwnerNode": "0",
"PreviousTxnID": "F73B073028D7EF14C5DD907591E579EBFEDBA891F4AE0B951439C240C42AE0D4",
"PreviousTxnLgrSeq": 3113735,
"Sequence": 1,
"index": "F231A0382544EC0ABE810A9D292F3BD455A21CD13CC1DFF75EAFE957A1C8CAB4",
"mpt_issuance_id": "00000001FCE5D5E313303F3D0C700789108CC6BE7D711493"
}
}
},
"status": "success"
}
```
{% /tab %}
{% /tabs %}
The response follows the [standard format][], with a successful result containing the following fields:
| Field | Type | Description |
| :--------------------- | :--------------- | :---------- |
| `ledger_hash` | [Hash][] | _(Omitted if either `ledger_current_index` or `ledger_index` is provided instead)_ The identifying hash of the ledger version that was used when retrieving this data. |
| `ledger_current_index` | [Ledger Index][] | _(Omitted if `ledger_index` is provided instead)_ The [ledger index][] of the current in-progress ledger, which was used when retrieving this information. |
| `ledger_index` | [Ledger Index][] | _(Omitted if `ledger_current_index` is provided instead)_ The [ledger index][] of the ledger version used when retrieving this information. |
| `validated` | Boolean | True if this data is from a validated ledger version; if omitted or set to false, this data is not final. |
| `vault` | Object | The [**Vault Description Object**](#vault-description-object) that represents the current status of the vault. |
### Vault Description Object
The `vault` field is an object describing the current status of a `Vault` entry in the ledger, and contains the following fields:
| `Field` | Type | Description |
| :--------------------- | :------------------- | :---------- |
| `Account` | String - [Address][] | The address of the vault's pseudo-account. |
| `Asset` | Object | The [**Asset**](#asset-object) of the vault. An asset can be XRP, a trust line token, or an MPT. |
| `AssetsAvailable` | Number | The asset amount that is available in the vault. |
| `AssetsMaximum` | Number | The maximum asset amount that can be held in the vault. If set to 0, this indicates there is no cap. |
| `AssetsTotal` | Number | The total value of the vault. |
| `Flags` | String | Set of bit-flags for this ledger object. |
| `LossUnrealized` | Number | The potential loss amount that is not yet realized, expressed as the vault's asset. |
| `ShareMPTID` | String | The identifier of the share `MPTokenIssuance` object. |
| `WithdrawalPolicy` | String | Indicates the withdrawal strategy used by the vault. |
| `index` | String | The unique index of the vault ledger entry. |
| `shares` | Object | A [**Shares Object**](#shares-object) containing details about the vault's issued shares. |
| `Scale` | Number | Specifies decimal precision for share calculations. Assets are multiplied by 10<sup>Scale</sup > to convert fractional amounts into whole number shares. For example, with a `Scale` of `6`, depositing 20.3 units creates 20,300,000 shares (20.3 × 10<sup>Scale</sup >). For **trust line tokens** this can be configured at vault creation, and valid values are between 0-18, with the default being `6`. For **XRP** and **MPTs**, this is fixed at `0`. |
### Asset Object
The `asset` object contains the following nested fields:
| `Field` | Type | Description |
| :--------------------- | :------------------- | :---------- |
| `currency` | String | _(Omitted if the asset is an MPT)_ The currency code of the asset stored in the vault. |
| `issuer` | String - [Address][] | _(Omitted if the asset is XRP or an MPT)_ The address of the asset issuer. |
| `mpt_issuance_id` | String | _(Omitted if the asset is XRP or a trust line token)_ The identifier of the asset's `MPTokenIssuance` object. |
### Shares Object
The `shares` object contains the following nested fields:
| `Field` | Type | Description |
| :--------------------- | :--------------- | :---------- |
| `DomainID` | String | _(Omitted if the vault is public)_ The permissioned domain associated with the vault's shares. |
| `Flags` | Number | Set of bit-flags for this ledger object. |
| `Issuer` | String | The address issuing the shares. This is always the vault's pseudo-account. |
| `LedgerEntryType` | String | The ledger object type (i.e., `MPTokenIssuance`). |
| `OutstandingAmount` | String | The total outstanding shares issued. |
| `OwnerNode` | String | Identifies the page where this item is referenced in the owner's directory. |
| `PreviousTxnID` | String | Identifies the transaction ID that most recently modified this object. |
| `PreviousTxnLgrSeq` | Number | The sequence of the ledger that contains the transaction that most recently modified this object. |
| `Sequence` | Number | The transaction sequence number that created the shares. |
| `index` | String | The unique index of the shares ledger entry. |
| `mpt_issuance_id` | String | The identifier of the `MPTokenIssuance` object. This is always equal to the vault's `ShareMPTID`. |
| `AssetScale` | Number | The decimal precision for share calculations. |
## Possible Errors
- Any of the [universal error types][].
- `invalidParams` - One or more fields are specified incorrectly, or one or more required fields are missing.
## See Also
- [Vault entry][]
{% raw-partial file="/docs/_snippets/common-links.md" /%}

View File

@@ -39,7 +39,7 @@ In addition to the [common fields](../common-fields.md), {% code-page-name /%} e
|:------------------------------|:----------|:------------------|:----------|:-------------|
| `Account` | String | AccountID | Yes | The identifying (classic) address of this [account](../../../../concepts/accounts/index.md). |
| `AccountTxnID` | String | UInt256 | No | The identifying hash of the transaction most recently sent by this account. This field must be enabled to use the [`AccountTxnID` transaction field](../../transactions/common-fields.md#accounttxnid). To enable it, send an [AccountSet transaction with the `asfAccountTxnID` flag enabled](../../transactions/types/accountset.md#accountset-flags). |
| `AMMID` | String | UInt256 | No | {% amendment-disclaimer name="AMM" /%} The ledger entry ID of the corresponding AMM ledger entry. Set during account creation; cannot be modified. If present, indicates that this is a special AMM [pseudo-account](../../../../concepts/accounts/pseudo-accounts.md) AccountRoot; always omitted on non-AMM accounts. |
| `AMMID` | String | UInt256 | No | {% amendment-disclaimer name="AMM" /%} The ledger entry ID of the corresponding AMM ledger entry. Set during account creation; cannot be modified. If present, indicates that this is a special AMM AccountRoot; always omitted on non-AMM accounts. |
| `Balance` | String | Amount | No | The account's current [XRP balance in drops][XRP, in drops], represented as a string. |
| `BurnedNFTokens` | Number | UInt32 | No | How many total of this account's issued [non-fungible tokens](../../../../concepts/tokens/nfts/index.md) have been burned. This number is always equal or less than `MintedNFTokens`. |
| `Domain` | String | Blob | No | A domain associated with this account. In JSON, this is the hexadecimal for the ASCII representation of the domain. [Cannot be more than 256 bytes in length.](https://github.com/XRPLF/rippled/blob/70d5c624e8cf732a362335642b2f5125ce4b43c1/include/xrpl/protocol/Protocol.h#L98) |
@@ -53,18 +53,15 @@ In addition to the [common fields](../common-fields.md), {% code-page-name /%} e
| `PreviousTxnLgrSeq` | Number | UInt32 | Yes |The [index of the ledger][Ledger Index] that contains the transaction that most recently modified this object. |
| `RegularKey` | String | AccountID | No | The address of a [key pair](../../../../concepts/accounts/cryptographic-keys.md) that can be used to sign transactions for this account instead of the master key. Use a [SetRegularKey transaction][] to change this value. |
| `Sequence` | Number | UInt32 | Yes | The [sequence number](../../data-types/basic-data-types.md#account-sequence) of the next valid transaction for this account. |
| `TicketCount` | Number | UInt32 | No | How many [Tickets](../../../../concepts/accounts/tickets.md) this account owns in the ledger. This is updated automatically to ensure that the account stays within the hard limit of 250 Tickets at a time. This field is omitted if the account has zero Tickets. |
| `TickSize` | Number | UInt8 | No | How many significant digits to use for exchange rates of Offers involving currencies issued by this address. Valid values are `3` to `15`, inclusive. |
| `TicketCount` | Number | UInt32 | No | How many [Tickets](../../../../concepts/accounts/tickets.md) this account owns in the ledger. This is updated automatically to ensure that the account stays within the hard limit of 250 Tickets at a time. This field is omitted if the account has zero Tickets. {% amendment-disclaimer name="TicketBatch" /%} |
| `TickSize` | Number | UInt8 | No | How many significant digits to use for exchange rates of Offers involving currencies issued by this address. Valid values are `3` to `15`, inclusive. {% amendment-disclaimer name="TickSize" /%} |
| `TransferRate` | Number | UInt32 | No | A [transfer fee](../../../../concepts/tokens/fungible-tokens/transfer-fees.md) to charge other users for sending currency issued by this account to each other. |
| `VaultID` | String | UInt256 | No | {% amendment-disclaimer name="SingleAssetVault" /%} The ID of the `Vault` entry associated with this account. Set during account creation; cannot be modified. If present, indicates that this is a special Vault [pseudo-account](../../../../concepts/accounts/pseudo-accounts.md) AccountRoot; always omitted on non-Vault accounts. |
| `WalletLocator` | String | UInt256 | No | An arbitrary 256-bit value that users can set. |
| `WalletSize` | Number | UInt32 | No | Unused. (The code supports this field but there is no way to set it.) |
## Special AMM AccountRoot (Pseudo-Account)
## Special AMM AccountRoot Entries
{% amendment-disclaimer name="AMM" /%}
Automated Market Makers use an AccountRoot ledger entry (pseudo-account) to issue their LP Tokens and hold the assets in the AMM pool, and an [AMM ledger entry](amm.md) for tracking some of the details of the AMM. The address of an AMM's AccountRoot is randomized so that users cannot identify and fund the address in advance of the AMM being created. Unlike normal accounts, AMM AccountRoot objects are created with the following settings:
Automated Market Makers use an AccountRoot ledger entry to issue their LP Tokens and hold the assets in the AMM pool, and an [AMM ledger entry](amm.md) for tracking some of the details of the AMM. The address of an AMM's AccountRoot is randomized so that users cannot identify and fund the address in advance of the AMM being created. Unlike normal accounts, AMM AccountRoot objects are created with the following settings:
- `lsfDisableMaster` **enabled** and no means of authorizing transactions. This ensures no one can control the account directly, and it cannot send transactions.
- `lsfDepositAuth` **enabled** and no accounts preauthorized. This ensures that the only way to add money to the AMM Account is using the [AMMDeposit transaction][].
@@ -79,21 +76,7 @@ In addition, the following special rules apply to an AMM's AccountRoot entry:
Other than those exceptions, these accounts are like ordinary accounts; the LP Tokens they issue behave like other [tokens](../../../../concepts/tokens/index.md) except that those tokens can also be used in AMM-related transactions. You can check an AMM's balances and the history of transactions that affected it the same way you would with a regular account.
## Special Vault AccountRoot (Pseudo-Account)
{% amendment-disclaimer name="SingleAssetVault" /%}
Vaults use an AccountRoot ledger entry (pseudo-account) to issue their shares and hold the assets deposited into the vault, and a [Vault entry][] for tracking the vault's configuration and state. The address of a vault's AccountRoot is randomized so that users cannot identify and fund the address in advance of the vault being created. Unlike normal accounts, vault AccountRoot objects are created with the following settings:
- `lsfDisableMaster` **enabled** and no means of authorizing transactions. This ensures no one can control the account directly, and it cannot send transactions.
- `lsfDepositAuth` **enabled** and no accounts pre-authorized. This ensures that the only way to add money to the vault's AccountRoot is using the [VaultDeposit transaction][].
- `lsfDefaultRipple` **enabled**. This enables rippling for the vault's pseudo-account.
In addition, the following special rules apply to a Vault's AccountRoot entry:
- The vault owner account must pay one [incremental owner reserve](../../../../concepts/accounts/reserves#base-reserve-and-owner-reserve) (currently {% $env.PUBLIC_OWNER_RESERVE %}) when creating the vault to cover the pseudo-account.
- The `Sequence` number is always `0` and never changes, preventing the pseudo-account from submitting transactions.
- A pseudo-account is automatically deleted when the vault is deleted, and cannot exist independently of a Vault entry.
{% amendment-disclaimer name="AMM" /%}
## AccountRoot Flags
@@ -121,7 +104,7 @@ AccountRoot objects can have the following flags combined in the `Flags` field:
## {% $frontmatter.seo.title %} Reserve
The [reserve](../../../../concepts/accounts/reserves.md) for an AccountRoot entry is the base reserve, currently {% $env.PUBLIC_BASE_RESERVE %}, except in the case of a special AMM or Vault AccountRoot.
The [reserve](../../../../concepts/accounts/reserves.md) for an AccountRoot entry is the base reserve, currently {% $env.PUBLIC_BASE_RESERVE %}, except in the case of a special AMM AccountRoot.
This XRP cannot be sent to others but it can be burned as part of the [transaction cost][].
@@ -134,9 +117,6 @@ The ID of an AccountRoot entry is the [SHA-512Half][] of the following values, c
## See Also
- **Concepts:**
- [Pseudo-Accounts](../../../../concepts/accounts/pseudo-accounts.md)
- **Transactions:**
- [AccountSet transaction][]
- [AccountDelete transaction][]

View File

@@ -1,121 +0,0 @@
---
seo:
description: A Loan ledger entry represents the terms of a loan between a borrower and loan issuer.
labels:
- Decentralized Finance
- Lending Protocol
status: not_enabled
---
# Loan
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/include/xrpl/protocol/detail/ledger_entries.macro#L543-L623 "Source")
A `Loan` ledger entry defines the state of an on-chain loan agreement between a _Loan Broker_ and a _Borrower_. It contains all the details of the loan, such as fees and interest rates. You can create a `Loan` ledger entry with the [LoanSet transaction][].
The `Loan` ledger entry is tracked in two [Owner Directories](./directorynode.md):
1. The owner directory of the _Borrower_ on the loan.
2. The owner directory of the `LoanBroker` pseudo-account.
{% amendment-disclaimer name="LendingProtocol" /%}
## Example {% $frontmatter.seo.title %} JSON
```json
{
"LedgerEntryType": "Loan",
"LedgerIndex": "E123F4567890ABCDE123F4567890ABCDEF1234567890ABCDEF1234567890ABCD",
"Flags": "0",
"PreviousTxnID": "9A8765B4321CDE987654321CDE987654321CDE987654321CDE987654321CDE98",
"PreviousTxnLgrSeq": 12345678,
"LoanSequence": 1,
"OwnerNode": 2,
"LoanBrokerNode": 1,
"LoanBrokerID": "ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890",
"Borrower": "rEXAMPLE9AbCdEfGhIjKlMnOpQrStUvWxYz",
"LoanOriginationFee": 100,
"LoanServiceFee": 10,
"LatePaymentFee": 5,
"ClosePaymentFee": 20,
"OverpaymentFee": 5,
"InterestRate": 500,
"LateInterestRate": 1000,
"CloseInterestRate": 200,
"OverpaymentInterestRate": 5,
"StartDate": 1234567890,
"PaymentInterval": 2592000,
"GracePeriod": 604800,
"PreviousPaymentDueDate": 1234587890,
"NextPaymentDueDate": 1234597890,
"PaymentRemaining": 12,
"PrincipalOutstanding": 10000,
"TotalValueOutstanding": 12000,
"ManagementFeeOutstanding": 2000,
"PeriodicPayment": 1000
}
```
## {% $frontmatter.seo.title %} Fields
In addition to the [common ledger entry fields][], {% code-page-name /%} entries have the following fields:
| Name | JSON Type | Internal Type | Required? | Description |
| :-------------------- | :-------- | :------------ | :-------- | :-----------|
| `PreviousTxnID` | String | Hash256 | Yes | Identifies the transaction ID that most recently modified this object. |
| `PreviousTxnLgrSeq` | Number | UInt32 | Yes | The sequence of the ledger that contains the transaction that most recently modified this object. |
| `LoanSequence` | Number | UInt32 | Yes | The sequence number of the loan. |
| `OwnerNode` | Number | UInt64 | Yes | Identifies the page where this item is referenced in the owner's directory. |
| `LoanBrokerNode` | Number | UInt64 | Yes | Identifies the page where this item is referenced in the `LoanBroker` owner directory. |
| `LoanBrokerID` | String | Hash256 | Yes | The ID of the _Loan Broker_ associated with this loan. |
| `Borrower` | String | AccountID | Yes | The account address of the _Borrower_. |
| `LoanOriginationFee` | String | Number | Yes | The amount paid to the _Loan Broker_, taken from the principal loan at creation. |
| `LoanServiceFee` | String | Number | Yes | The amount paid to the _Loan Broker_ with each loan payment. |
| `LatePaymentFee` | String | Number | Yes | The amount paid to the _Loan Broker_ for each late payment. |
| `ClosePaymentFee` | String | Number | Yes | The amount paid to the _Loan Broker_ when a full early payment is made. |
| `OverpaymentFee` | Number | UInt32 | Yes | The fee charged on overpayments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
| `InterestRate` | Number | UInt32 | Yes | The annualized interest rate of the loan, in 1/10th basis points. |
| `LateInterestRate` | Number | UInt32 | Yes | The premium added to the interest rate for late payments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
| `CloseInterestRate` | Number | UInt32 | Yes | The interest rate charged for repaying the loan early, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
| `OverpaymentInterestRate` | Number | UInt32 | Yes | The interest rate charged on overpayments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
| `StartDate` | Number | UInt32 | Yes | The timestamp of when the loan started, in [seconds since the Ripple Epoch][]. |
| `PaymentInterval` | Number | UInt32 | Yes | The number of seconds between loan payments. |
| `GracePeriod` | Number | UInt32 | Yes | The number of seconds after a loan payment is due before the loan defaults. |
| `PreviousPaymentDueDate` | Number | UInt32 | Yes | The timestamp of when the previous payment was made, in [seconds since the Ripple Epoch][]. |
| `NextPaymentDueDate` | Number | UInt32 | Yes | The timestamp of when the next payment is due, in [seconds since the Ripple Epoch][]. |
| `PaymentRemaining` | Number | UInt32 | Yes | The number of payments remaining on the loan. |
| `PrincipalOutstanding` | String | Number | Yes | The principal amount still owed on the loan. |
| `TotalValueOutstanding` | String | Number | Yes | The total amount owed on the loan, including remaining principal and fees. |
| `ManagementFeeOutstanding` | String | Number | Yes | The remaining management fee owed to the loan broker. |
| `PeriodicPayment` | String | Number | Yes | The amount due for each payment interval. |
| `LoanScale` | Number | Int32 | No | The scale factor that ensures all computed amounts are rounded to the same number of decimal places. It is based on the total loan value at creation time. |
{% admonition type="info" name="Note" %}
When the loan broker discovers that the borrower can't make an upcoming payment, they can impair the loan to register a "paper loss" with the vault. The impairment mechanism moves up the `NextPaymentDueDate` to the time the loan is impaired, allowing the loan to default quicker. However, if the borrower makes a payment in the subsequent `GracePeriod`, the impairment status is removed.
{% /admonition %}
## {% $frontmatter.seo.title %} Flags
{% code-page-name /%} entries can have the following flags:
| Field Name | Hex Value | Decimal Value | Description |
|:---------------------|:-------------|:--------------|:------------|
| `lsfLoanDefault` | `0x00010000` | `65536` | Indicates the loan is defaulted. |
| `lsfLoanImpaired` | `0x00020000` | `131072` | Indicates the loan is impaired. |
| `lsfLoanOverpayment` | `0x00040000` | `262144` | Indicates the loan supports overpayments. |
## {% $frontmatter.seo.title %} Reserve
`Loan` entries incur one owner reserve from the borrower.
## {% $frontmatter.seo.title %} ID Format
The ID of a `Loan` ledger entry is the [SHA-512Half][] of the following values, concatenated in order:
- The `Loan` space key `0x004C`.
- The `LoanBrokerID` of the associated `LoanBroker` ledger entry.
- The `LoanSequence` number of the `LoanBroker` ledger entry.
{% raw-partial file="/docs/_snippets/common-links.md" /%}

View File

@@ -1,93 +0,0 @@
---
seo:
description: A LoanBroker ledger entry represents the configuration and state of a lending protocol instance.
labels:
- Decentralized Finance
- Lending Protocol
status: not_enabled
---
# LoanBroker
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/include/xrpl/protocol/detail/ledger_entries.macro#L519-L537 "Source")
A `LoanBroker` ledger entry defines the configuration and state of a lending protocol instance. It tracks details such as fees and first-loss capital cover. You can create a `LoanBroker` object with the [LoanBrokerSet transaction][].
The `LoanBroker` entry is tracked in an [Owner Directory](./directorynode.md) owned by the account that submitted the `LoanBrokerSet` transaction. To facilitate lookup, it is also tracked in the `OwnerDirectory` of the associated vault's _pseudo-account_.
{% admonition type="info" name="Note" %}
The lending protocol uses the pseudo-account of the associated `Vault` entry to hold the first-loss capital.
{% /admonition %}
{% amendment-disclaimer name="LendingProtocol" /%}
## Example {% $frontmatter.seo.title %} JSON
```json
{
"LedgerEntryType": "LoanBroker",
"LedgerIndex": "E123F4567890ABCDE123F4567890ABCDEF1234567890ABCDEF1234567890ABCD",
"Flags": "0",
"PreviousTxnID": "9A8765B4321CDE987654321CDE987654321CDE987654321CDE987654321CDE98",
"PreviousTxnLgrSeq": 12345678,
"Sequence": 1,
"LoanSequence": 2,
"OwnerNode": 2,
"VaultNode": 1,
"VaultID": "ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890",
"Account": "rBROKER9AbCdEfGhIjKlMnOpQrStUvWxYz",
"Owner": "rEXAMPLE9AbCdEfGhIjKlMnOpQrStUvWxYz",
"Data": "5468697320697320617262697472617279206D657461646174612061626F757420746865206C6F616E62726F6B65722E",
"ManagementFeeRate": 100,
"OwnerCount": 3,
"DebtTotal": 50000,
"DebtMaximum": 100000,
"CoverAvailable": 10000,
"CoverRateMinimum": 1000,
"CoverRateLiquidation": 500
}
```
## {% $frontmatter.seo.title %} Fields
In addition to the [common ledger entry fields][], {% code-page-name /%} entries have the following fields:
| Name | JSON Type | Internal Type | Required? | Description |
| :-------------------- | :-------- | :------------ | :-------- | :-----------|
| `PreviousTxnID` | String | Hash256 | Yes | Identifies the transaction ID that most recently modified this object. |
| `PreviousTxnLgrSeq` | Number | UInt32 | Yes | The sequence of the ledger that contains the transaction that most recently modified this object. |
| `Sequence` | Number | UInt32 | Yes | The transaction sequence number that created the LoanBroker. |
| `LoanSequence` | Number | UInt32 | Yes | A sequential identifier for `Loan` ledger entires, incremented each time a new loan is created by this `LoanBroker`. |
| `OwnerNode` | Number | UInt64 | Yes | Identifies the page where this item is referenced in the owner's directory. |
| `VaultNode` | Number | UInt64 | Yes | Identifies the page where this item is referenced in the `Vault` pseudo-account owner's directory. |
| `VaultID` | String | Hash256 | Yes | The ID of the vault that provides the loaned assets. |
| `Account` | String | AccountID | Yes | The address of the `LoanBroker` pseudo-account. |
| `Owner` | String | AccountID | Yes | The account address of the vault owner. |
| `Data` | String | Blob | No | Arbitrary metadata about the vault. Limited to 256 bytes. |
| `ManagementFeeRate` | Number | UInt16 | No | The fee charged by the lending protocol, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
| `OwnerCount` | Number | UInt32 | Yes | The number of active loans issued by the LoanBroker. |
| `DebtTotal` | String | Number | Yes | The total asset amount the protocol owes the vault, including interest. |
| `DebtMaximum` | String | Number | Yes | The maximum amount the protocol can owe the vault. The default value of `0` means there is no limit to the debt. |
| `CoverAvailable` | String | Number | Yes | The total amount of first-loss capital deposited into the lending protocol. |
| `CoverRateMinimum` | Number | UInt32 | Yes | The 1/10th basis point of the `DebtTotal` that the first-loss capital must cover. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
| `CoverRateLiquidation`| Number | UInt12 | Yes | The 1/10th basis point of minimum required first-loss capital that is moved to an asset vault to cover a loan default. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
## {% $frontmatter.seo.title %} Flags
There are no flags defined for {% code-page-name /%} ledger entries.
## {% $frontmatter.seo.title %} Reserve
`Loan` entries incur one owner reserve from the account that creates it.
## {% $frontmatter.seo.title %} ID Format
The ID of a `LoanBroker` entry is the [SHA512-Half][] of the following values, concatenated in order:
- The `LoanBroker` space key `0x006C`.
- The [AccountID][] of the account submitting the `LoanBrokerSet` transaction.
- The transaction `Sequence` number. If the transaction used a [Ticket][], the `TicketSequence` value is used instead.
{% raw-partial file="/docs/_snippets/common-links.md" /%}

View File

@@ -1,150 +0,0 @@
---
seo:
description: A Vault object defines the state of a tokenized vault.
labels:
- Vault
- Single Asset Vault
status: not_enabled
---
# Vault
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/include/xrpl/protocol/detail/ledger_entries.macro#L493-L511 "Source")
A {% code-page-name /%} object defines the state of a tokenized vault. It contains key details such as available assets, shares, total value, and other relevant information. You can create a {% code-page-name /%} object with the [VaultCreate](../../transactions/types/vaultcreate.md) transaction.
The {% code-page-name /%} object is tracked in an [Owner Directory](../../../protocol/ledger-data/ledger-entry-types/directorynode) owned by the Vault Owner account.
Additionally, to facilitate `Vault` object lookup, the object is tracked in the owner directory of the vault's [pseudo-account](../../../../concepts/accounts/pseudo-accounts.md).
{% amendment-disclaimer name="SingleAssetVault" /%}
## Example Vault JSON
```json
{
"LedgerEntryType": "Vault",
"Account": "rwCNM7SeUHTajEBQDiNqxDG8p1Mreizw85",
"Asset": {
"currency": "USD",
"issuer": "rXJSJiZMxaLuH3kQBUV5DLipnYtrE6iVb"
},
"AssetsAvailable": "0",
"AssetsMaximum": "1000000",
"AssetsTotal": "0",
"Data": "5661756C74206D65746164617461",
"Flags": 0,
"LossUnrealized": "0",
"Owner": "rNGHoQwNG753zyfDrib4qDvvswbrtmV8Es",
"OwnerNode": "0",
"Scale": 6,
"Sequence": 200370,
"ShareMPTID": "0000000169F415C9F1AB6796AB9224CE635818AFD74F8175",
"WithdrawalPolicy": 1,
}
```
## {% $frontmatter.seo.title %} Fields
In addition to the [common ledger entry fields](../../../protocol/ledger-data/common-fields), {% code-page-name /%} entries have the following fields:
| Name | JSON Type | [Internal Type][] | Required? | Description |
| :------------------ | :------------ | :---------------- | :-------- | -----------------|
| `PreviousTxnID` | String | Hash256 | Yes | Identifies the transaction ID that most recently modified this object. |
| `PreviousTxnLgrSeq` | Number | UInt32 | Yes | The sequence of the ledger that contains the transaction that most recently modified this object. |
| `Sequence` | Number | UInt32 | Yes | The transaction sequence number that created the vault. |
| `OwnerNode` | Number | UInt64 | Yes | Identifies the page where this item is referenced in the owner's directory. |
| `Owner` | String | AccountID | Yes | The account address of the Vault Owner. |
| `Account` | String | AccountID | Yes | The address of the vault's pseudo-account. |
| `Data` | String | Blob | No | Arbitrary metadata, in hex format, about the vault. Limited to 256 bytes. See [Data Field Format](#data-field-format) for more information. |
| `Asset` | Object | Issue | Yes | The asset of the vault. The vault supports XRP, trust line tokens, and MPTs. |
| `AssetsTotal` | Number | Number | Yes | The total value of the vault. |
| `AssetsAvailable` | Number | Number | Yes | The asset amount that is available in the vault. |
| `AssetsMaximum` | Number | Number | No | The maximum asset amount that can be held in the vault. If set to 0, this indicates there is no cap. |
| `LossUnrealized` | Number | Number | Yes | The potential loss amount that is not yet realized, expressed as the vault's asset. Only a protocol connected to the vault can modify this attribute. |
| `ShareMPTID` | String | UInt192 | Yes | The identifier of the share `MPTokenIssuance` object. |
| `WithdrawalPolicy` | String | UInt8 | Yes | Indicates the withdrawal strategy used by the vault. |
| `Scale` | Number | UInt8 | No | Specifies decimal precision for share calculations. Assets are multiplied by 10<sup>Scale</sup > to convert fractional amounts into whole number shares. For example, with a `Scale` of `6`, depositing 20.3 units creates 20,300,000 shares (20.3 × 10<sup>Scale</sup >). For **trust line tokens** this can be configured at vault creation, and valid values are between 0-18, with the default being `6`. For **XRP** and **MPTs**, this is fixed at `0`. See [Scaling Factor](#scaling-factor) for more information. |
### Data Field Format
While any data structure is allowed in the `Data` field, the following format is recommended:
| Field Name | Key | Type | Description |
| ---------- | --- | ------ | ------------------------------------------------------------------------------------------ |
| Name | n | String | Human-readable name of the vault. Should clearly reflect the vault's strategy or mandate. |
| Website | w | String | Website associated with the vault. Omit protocol (`https://`) and `www` to conserve space. |
To fit within the 256-byte limit, vault metadata should use the _compressed_ JSON keys.
Following this format helps XRPL explorers and other tools parse and display vault information in a standardized way, improving discoverability and user experience.
#### Example JSON
For a vault named "LATAM Fund II" with website "examplefund.com":
```json
{
"n": "LATAM Fund II",
"w": "examplefund.com"
}
```
1. Remove any whitespace from the JSON:
`{"n":"LATAM Fund II","w":"examplefund.com"}`
2. Hex-encode the JSON. For example:
```sh
# Using xxd (macOS/Linux)
echo -n '{"n":"LATAM Fund II","w":"examplefund.com"}' | xxd -p | tr -d '\n'
```
You should see this result: `7b226e223a224c4154414d2046756e64204949222c2277223a226578616d706c6566756e642e636f6d227d`
### Scaling Factor
The **`Scale`** field enables the vault to accurately represent fractional asset values using integer-only MPT shares, which prevents the loss of value from decimal truncation. It defines a scaling factor, calculated as 10<sup>Scale</sup>, that converts a decimal asset amount into a corresponding whole number of shares.
The scaling factor behavior varies by asset type:
- **Trust line token**: When a vault holds a trust line token, the `Scale` is configurable by the Vault Owner when creating the vault. The value can range from **0** to a maximum of **18**, with a default of **6**. This flexibility allows issuers to set a level of precision appropriate for their specific token.
- **XRP**: When a vault holds XRP, the `Scale` is fixed at **0**. This aligns with XRP's native structure, where one share represents one drop, and one XRP equals 1,000,000 drops. Therefore, a deposit of 10 XRP to an empty vault will result in the issuance of 10,000,000 shares.
- **MPT**: When a vault holds an MPT, its `Scale` is fixed at **0**. This creates a 1-to-1 relationship between deposited MPT units and the shares issued. For example, depositing 10 MPTs to an empty vault issues 10 shares. The value of a single MPT is determined at the issuer's discretion.
{% admonition type="warning" name="Warning" %}
If an MPT is set to represent a large value, the vault owner and the depositor must be cautious. Since only whole MPT units are used in calculations, any value that is not a multiple of a single MPT's value may be lost due to rounding during a transaction.
{% /admonition %}
## {% $frontmatter.seo.title %} Flags
{% code-page-name /%} entries can have the following flags:
| Flag Name | Flag Value | Description |
| :---------------- | :----------- | :---------------------------|
| `lsfVaultPrivate` | `0x00010000` | If set, indicates that the vault is private. This flag can only be set when _creating_ the vault. |
## Vault ID Format
The ID of a {% code-page-name /%} entry is the [SHA-512Half][] of the following values, concatenated in order:
- The {% code-page-name /%} space key `0x0056` (capital V).
- The [AccountID](../../../protocol/binary-format/#accountid-fields) of the account submitting the transaction (for example, the vault owner).
- The transaction `Sequence` number. If the transaction used a [Ticket](../../../../concepts/accounts/tickets), use the `TicketSequence` value.
## See Also
**API Methods**:
- [vault_info method][]
**Transactions**:
- [VaultClawback transaction][]
- [VaultCreate transaction][]
- [VaultDelete transaction][]
- [VaultDeposit transaction][]
- [VaultSet transaction][]
- [VaultWithdraw transaction][]
{% raw-partial file="/docs/_snippets/common-links.md" /%}

View File

@@ -170,7 +170,7 @@ A transaction is considered successful if it receives a `tesSUCCESS` result.
| Error Code | Description |
|:--------------------------|:--------------------------------------------------|
| `temARRAY_EMPTY` | The batch transaction contains zero or one inner transaction. You must submit at least two inner transactions. |
| `temINVALID_INNER_BATCH` | - An inner transaction is malformed.<br>- You are attempting to submit a lending or vault-related transaction, which are currently disabled. Invalid transactions include: `LoanBrokerCoverClawback`, `LoanBrokerCoverDeposit`, `LoanBrokerCoverWithdraw`, `LoanBrokerDelete`, `LoanBrokerSet`, `LoanDelete`, `LoanManage`, `LoanPay`, `LoanSet`, `VaultCreate`, `VaultSet`, `VaultDelete`, `VaultDeposit`, `VaultWithdraw`, and `VaultClawback`. |
| `temINVALID_INNER_BATCH` | An inner transaction is malformed. |
| `temSEQ_AND_TICKET` | The transaction contains both a `TicketSequence` field and a non-zero `Sequence` value. A transaction can't include both fields, but must have at least one. |
{% raw-partial file="/docs/_snippets/common-links.md" /%}

View File

@@ -1,61 +0,0 @@
---
seo:
description: Claw back first-loss capital from a `LoanBroker` ledger entry.
labels:
- Transactions
- Lending Protocol
status: not_enabled
---
# LoanBrokerCoverClawback
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/src/xrpld/app/tx/detail/LoanBrokerCoverClawback.cpp "Source")
The `LoanBrokerCoverClawback` transaction claws back first-loss capital from a `LoanBroker` ledger entry. The transaction can only be submitted by the issuer of the asset used in the lending protocol, and can't clawback an amount that would cause the available first-loss capital to drop below the minimum amount defined by the `LoanBroker.CoverRateMinimum` value.
{% amendment-disclaimer name="LendingProtocol" /%}
## Example {% $frontmatter.seo.title %} JSON
```json
{
"TransactionType": "LoanBrokerCoverClawback",
"Account": "rEXAMPLE9AbCdEfGhIjKlMnOpQrStUvWxYz",
"Fee": "12",
"Flags": 0,
"LastLedgerSequence": 7108682,
"Sequence": 8,
"LoanBrokerID": "E123F4567890ABCDE123F4567890ABCDEF1234567890ABCDEF1234567890ABCD",
"Amount": {
"currency": "USD",
"issuer": "rIssuer1234567890abcdef1234567890abcdef",
"value": "1000"
}
}
```
## {% $frontmatter.seo.title %} Fields
In addition to the [common fields][], {% code-page-name /%} transactions use the following fields:
| Field Name | JSON Type | Internal Type | Required? | Description |
|:-------------- |:--------------------|:--------------|:----------|:------------|
| `LoanBrokerID` | String | Hash256 | No | The ID of the `LoanBroker` ledger entry to clawback first-loss capital. Must be provided if `Amount` is an MPT, or `Amount` is an IOU and the specified `issuer` matches the `Account` submitting the transaction. |
| `Amount` | [Currency Amount][] | Amount | No | The amount of first-loss capital to claw back. If the value is `0` or empty, claw back all assets down to the minimum cover (`DebtTotal * CoverRateMinimum`). |
## Error Cases
Besides errors that can occur for all transactions, {% code-page-name /%} transactions can result in the following [transaction result codes][]:
| Error Code | Description |
| :----------------------- | :----------------------------------|
| `temINVALID` | - The transaction is missing the `LoanBrokerID` and `Amount` fields.<br>- `LoanBrokerID` is invalid.<br>- `Amount` is an MPT and `LoanBrokerID` is missing.<br>- Amount is an IOU and its `issuer` matches `Account`. |
| `temBAD_AMOUNT` | - `Amount` is less than or equal to zero.<br>- `Amount` specifies the native XRP. |
| `tecNO_ENTRY` | - The `LoanBroker` ledger entry doesn't exist, or the asset `issuer` doesn't exist. |
| `tecNO_PERMISSION` | - The asset doesn't support clawback, or the asset can't be frozen.<br>- The transaction is attempting to clawback native XRP.<br>- The `Account` isn't the asset issuer. |
| `tecWRONG_ASSET` | The specified asset to clawback doesn't match the asset in the lending protocol vault. |
| `tecINTERNAL` | The balance of the trust line doesn't match the `CoverAvailable` field. |
| `tecINSUFFICIENT_FUNDS` | Clawing back the specified `Amount` puts the available cover below the minimum required. You can also receive this error if the issuer of the asset has frozen the account or placed a global freeze. |
{% raw-partial file="/docs/_snippets/common-links.md" /%}

View File

@@ -1,62 +0,0 @@
---
seo:
description: Deposits first-loss capital into a `LoanBroker` ledger entry.
labels:
- Transactions
- Lending Protocol
status: not_enabled
---
# LoanBrokerCoverDeposit
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/src/xrpld/app/tx/detail/LoanBrokerCoverDeposit.cpp "Source")
Deposits first-loss capital into a `LoanBroker` ledger entry to provide protection for vault depositors.
Only the owner of the associated `LoanBroker` entry can initiate this transaction.
{% amendment-disclaimer name="LendingProtocol" /%}
## Example {% $frontmatter.seo.title %} JSON
```json
{
"TransactionType": "LoanBrokerCoverDeposit",
"Account": "rEXAMPLE9AbCdEfGhIjKlMnOpQrStUvWxYz",
"Fee": "12",
"Flags": 0,
"LastLedgerSequence": 7108682,
"Sequence": 8,
"LoanBrokerID": "E123F4567890ABCDE123F4567890ABCDEF1234567890ABCDEF1234567890ABCD",
"Amount": {
"currency": "USD",
"issuer": "rIssuer1234567890abcdef1234567890abcdef",
"value": "1000"
}
}
```
## {% $frontmatter.seo.title %} Fields
In addition to the [common fields][], {% code-page-name /%} transactions use the following fields:
| Field Name | JSON Type | Internal Type | Required? | Description |
|:-------------- |:--------------------|:--------------|:----------|:------------|
| `LoanBrokerID` | String | Hash256 | Yes | The ID of the `LoanBroker` ledger entry to deposit the first-loss capital. |
| `Amount` | [Currency Amount][] | Amount | Yes | The amount of first-loss capital to deposit. |
## Error Cases
Besides errors that can occur for all transactions, {% code-page-name /%} transactions can result in the following [transaction result codes][]:
| Error Code | Description |
| :----------------------- | :----------------------------------|
| `temINVALID` | The `LoanBrokerID` field is invalid. |
| `temBAD_AMOUNT` | The `Amount` field is less than or equal to zero. |
| `tecNO_ENTRY` | The `LoanBroker` ledger entry doesn't exist. |
| `tecNO_PERMISSION` | The account sending the transaction isn't the owner of the `LoanBroker` ledger entry. |
| `tecWRONG_ASSET` | The asset being deposited doesn't match the asset in the `LoanBroker` vault. |
| `tecINSUFFICIENT_FUNDS` | The account depositing first-loss capital doesn't hold enough of the asset. You can also receive this error if the issuer of the asset has frozen the account or placed a global freeze. |
{% raw-partial file="/docs/_snippets/common-links.md" /%}

Some files were not shown because too many files have changed in this diff Show More