Skip to content
Last updated

XRPL JavaScript Code Sample Conventions

Code samples come in two flavors with very different conventions. Identify which you're writing first.

FlavorFilename patternAudiencePriority
Tutorial<verb><Thing>.js (e.g., createLoanBroker.js)A dev reading & learning the protocolClarity 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 folderSpeed over clarity

If a file isn't clearly one or the other, prompt the user for clarity.

Style

Formatting

  • 2-space indent
  • Single quotes
  • No semicolons

Naming

  • File names: camelCase (e.g., createLoan.js)
  • Variables: camelCase with 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)

Structure

Folder layout

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

README.md is the entry point for a reader running the samples.

  1. Title: # <Topic> Examples (JavaScript)
  2. One-sentence description listing what the directory demonstrates
  3. ## Setup section with a single npm i fenced block
  4. 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 sh block with node <file>.js
  • One-sentence summary of what the script will output
  • Fenced sh block showing actual expected console output (real addresses, tx IDs, JSON dumps — captured from a successful sample code run)
  1. --- separator between tutorial sections

The expected-output blocks document the golden path. Update them when a script's output format changes.

package.json

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"
}

Tutorial files

Structure

  1. Multi-line // IMPORTANT: header explaining what the script demonstrates and any preconditions (e.g., "uses an existing account that has a PRIVATE vault")
  2. Imports
  3. Connect to the network:
// Connect to the network ----------------------
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
await client.connect()
  1. (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}`)
  1. (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()
])
  1. Tutorial code steps.
  2. Disconnect at the end of the code sample.
await client.disconnect()

Tutorial code step guide

  • 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 tesSUCCESS and 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}`)

Setup files

Speed-first patterns when possible

  • Run independent transactions concurrently with await Promise.all([...])
  • When fanning out parallel transactions from the same account, batch them first via TicketCreate with TicketCount: N, then pass Sequence: 0 and TicketSequence: ticketArr[i] on each parallel tx
  • Destructure response arrays: const [{ wallet: loanBroker }, { wallet: borrower }] = await Promise.all([client.fundWallet(), client.fundWallet()])

Setup code guide

  • 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, no xrpl.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., LoanSet autofill warning), silence it locally with a one-line comment explaining why: console.warn = () => {}

Output file

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))