Code samples come in two flavors with very different conventions. Identify which you're writing first.
| Flavor | Filename pattern | Audience | Priority |
|---|---|---|---|
| Tutorial | <verb><Thing>.js (e.g., createLoanBroker.js) | A dev reading & learning the protocol | Clarity over speed |
| Setup | <topic>Setup.js (e.g., lendingSetup.js) | A dev who never opens this file — runs to prep network data (accounts, tokens, etc.) for all tutorials in the subject folder | Speed over clarity |
If a file isn't clearly one or the other, prompt the user for clarity.
- 2-space indent
- Single quotes
- No semicolons
- File names:
camelCase(e.g.,createLoan.js) - Variables:
camelCasewith acronyms uppercased —loanBroker,mptID,vaultID,loanBrokerID,credentialIssuer - Transaction object keys: XRPL native PascalCase (
TransactionType,Account,Amount) — never transform them - Setup JSON keys:
camelCase(loanBroker,credentialIssuer,mptID,vaultID,loanBrokerID)
Each code sample lives at _code-samples/<topic>/js/:
_code-samples/<topic>/js/
├── README.md
├── package.json
├── package-lock.json
├── <topic>Setup.js # Optional — runs once to prep network state
├── <topic>Setup.json # Auto-generated by the setup script and is gitignored
└── <verb><Thing>.js # Tutorial scripts (one per user action)README.md is the entry point for a reader running the samples.
- Title:
# <Topic> Examples (JavaScript) - One-sentence description listing what the directory demonstrates
## Setupsection with a singlenpm ifenced block- One
##section per tutorial script, in the order a reader should run them:
- Heading describes the action (e.g.,
## Create a Loan Broker), not the filename - Fenced
shblock withnode <file>.js - One-sentence summary of what the script will output
- Fenced
shblock showing actual expected console output (real addresses, tx IDs, JSON dumps — captured from a successful sample code run)
---separator between tutorial sections
The expected-output blocks document the golden path. Update them when a script's output format changes.
Minimal — no scripts, no devDependencies, no version unless an external dep requires one:
{
"name": "<topic>-examples",
"description": "Example code for <one-line summary>.",
"dependencies": {
"xrpl": "^<latest-stable>"
},
"type": "module"
}- Multi-line
// IMPORTANT:header explaining what the script demonstrates and any preconditions (e.g., "uses an existing account that has a PRIVATE vault") - Imports
- Connect to the network:
// Connect to the network ----------------------
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
await client.connect()- (Optional) If the tutorial is using setup script data:
// This step checks for the necessary setup data to run the lending 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}`)- (Optional) If no setup data is used, fund new wallets for the tutorial.
const { wallet } = await client.fundWallet()If creating multiple wallets, parallelize the process:
const [
{ wallet: loanBroker },
{ wallet: borrower },
{ wallet: depositor },
{ wallet: credentialIssuer }
] = await Promise.all([
client.fundWallet(),
client.fundWallet(),
client.fundWallet(),
client.fundWallet()
])- Tutorial code steps.
- Disconnect at the end of the code sample.
await client.disconnect()- Before each major step, add a comment and print a section banner.
- Build transactions as plain object literals and validate before submitting:
// 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)) - Autofill transactions and handle results by checking for
tesSUCCESSand exiting on failure:// 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 metadata relevant to the tutorial:
// 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 Pseudo-Account Address: ${loanBrokerNode.CreatedNode.NewFields.Account}`)
- Run independent transactions concurrently with
await Promise.all([...]) - When fanning out parallel transactions from the same account, batch them first via
TicketCreatewithTicketCount: N, then passSequence: 0andTicketSequence: ticketArr[i]on each parallel tx - Destructure response arrays:
const [{ wallet: loanBroker }, { wallet: borrower }] = await Promise.all([client.fundWallet(), client.fundWallet()])
- Top comment: single line,
// Setup script for <topic> tutorials - Only output is a carriage-return progress indicator:
process.stdout.write('Setting up tutorial: N/D\r')between phases, where N is the step number and D is the total steps - No
=== Section ===banners, noxrpl.validate(tx), no transaction dumps — the user never sees this file's output beyond the progress counter - Section comments in code are short:
// Section description(no dash visual) - If a library call emits a warning the reader doesn't need (e.g.,
LoanSetautofill warning), silence it locally with a one-line comment explaining why:console.warn = () => {}
At the end, write all data the tutorials will need:
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
},
domainID,
mptID
}
fs.writeFileSync('lendingSetup.json', JSON.stringify(setupData, null, 2))