# XRPL Python Code Sample Conventions

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

| Flavor | Filename pattern | Audience | Priority |
|  --- | --- | --- | --- |
| **Tutorial** | `<verb>_<thing>.py` (e.g., `create_loan_broker.py`) | A dev reading & learning the protocol | Clarity over speed |
| **Setup** | `<topic>_setup.py` (e.g., `lending_setup.py`) | 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.

## Style

### Formatting

- 4-space indent
- Double quotes
- Trailing commas on multi-line collections and call args


### Naming

- File names: `snake_case` (e.g., `create_loan.py`)
- Variables: `snake_case` — `loan_broker`, `mpt_id`, `vault_id`, `loan_broker_id`, `credential_issuer`
- Transaction model fields: `snake_case` per `xrpl-py` (e.g., `account=`, `vault_id=`, `management_fee_rate=`) — `xrpl-py` handles the PascalCase translation on the wire
- Setup JSON keys: `snake_case` (`loan_broker`, `credential_issuer`, `mpt_id`, `vault_id`, `loan_broker_id`)


## Structure

### Folder layout

Each code sample lives at `_code-samples/<topic>/py/`:


```
_code-samples/<topic>/py/
├── README.md
├── requirements.txt
├── <topic>_setup.py        # Optional — runs once to prep network state
├── <topic>_setup.json      # Auto-generated by the setup script and is gitignored
└── <verb>_<thing>.py       # Tutorial scripts (one per user action)
```

### README

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

1. Title: `# <Topic> Examples (Python)`
2. One-sentence description listing what the directory demonstrates
3. `## Setup` section with a single fenced block showing venv creation + `pip install -r requirements.txt`:



```sh
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```

1. 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 `python3 <file>.py`
- 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.

### requirements.txt

Minimal — pin only what's needed:


```
xrpl-py>=<latest-stable>
```

Add other deps only when a sample requires them.

## Tutorial files

**Sync API only** — `xrpl.clients.JsonRpcClient`, `xrpl.transaction.submit_and_wait`, `xrpl.wallet.Wallet`, `xrpl.wallet.generate_faucet_wallet`. No `asyncio`, no `main()` wrapper, no `if __name__ == "__main__":` — scripts run top-to-bottom and exit.

### 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 — stdlib first, blank line, then `xrpl` imports
3. Set up the client:



```python
# Set up client ----------------------
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
```

1. (Optional) If the tutorial is using setup script data:



```python
# This step checks for the necessary setup data to run the lending 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}")
```

1. (Optional) If no setup data is used, fund new wallets for the tutorial.



```python
wallet = generate_faucet_wallet(client)
```

Funding multiple wallets requires sequential calls.
6. Tutorial code steps.

### Tutorial code step guide

- Before each major step, add a comment and print a section banner.
- Build transactions as `xrpl-py` model instances and print the wire form before submitting:

```python
# 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 with `submit_and_wait` and handle results by checking for `tesSUCCESS` and exiting on failure:

```python
# 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 metadata relevant to the tutorial:

```python
# 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']}")
```


## Setup files

**Async API only** — `xrpl.asyncio.clients.AsyncWebsocketClient`, `xrpl.asyncio.wallet.generate_faucet_wallet`, `xrpl.asyncio.transaction` (`submit_and_wait`, `autofill`, `sign`). Wrap in `async def main():` + `async with AsyncWebsocketClient(WSS_URL) as client:`, and call `asyncio.run(main())` at the bottom.

WebSocket endpoints: Devnet (`wss://s.devnet.rippletest.net:51233`) or Testnet (`wss://s.altnet.rippletest.net:51233`).

### Speed-first patterns when possible

- Run independent transactions concurrently with `await asyncio.gather(...)`
- When fanning out parallel transactions from the same account, batch them first via `TicketCreate(ticket_count=N)`, then pass `sequence=0` and `ticket_sequence=...` on each parallel tx
- Destructure gather results: `loan_broker, borrower = await asyncio.gather(generate_faucet_wallet(client), generate_faucet_wallet(client))`
- Group `xrpl.models` imports into a single alphabetized parenthesized block


### Setup code guide

- Top comment: single line, `# Setup script for <topic> tutorials`
- Only output is a carriage-return progress indicator: `print("Setting up tutorial: N/D", end="\r")` between phases, where N is the step number and D is the total steps
- No `=== Section ===` banners, no transaction dumps — the reader never sees this file's output beyond the progress counter
- Section comments in code are short: `# Section description` (no dash visual)


### Output file

At the end, write all data the tutorials will need:


```python
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,
    },
    "domain_id": domain_id,
    "mpt_id": mpt_id,
}

with open("lending_setup.json", "w") as f:
    json.dump(setup_data, f, indent=2)
```