mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2026-06-07 10:46:45 +00:00
Compare commits
10 Commits
ledger-ent
...
claude-rul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1146c5faf5 | ||
|
|
1d88320181 | ||
|
|
5302657867 | ||
|
|
fe2236c3f7 | ||
|
|
d067d89eb0 | ||
|
|
f1310f97bb | ||
|
|
f5e798570d | ||
|
|
d4de90ac86 | ||
|
|
59c2c07d22 | ||
|
|
21b03a8bf0 |
@@ -6,6 +6,33 @@
|
||||
- **Production branch:** `master`
|
||||
- **Local preview:** `npm start`
|
||||
|
||||
## XRPL Reference Sources
|
||||
|
||||
For up-to-date XRPL protocol, API, or SDK info, use the `context7` MCP server. The following authoritative sources are indexed:
|
||||
|
||||
### Documentation Sites
|
||||
|
||||
- `/websites/xrpl` — xrpl.org (this dev portal's published content)
|
||||
- `/websites/opensource_ripple` — opensource.ripple.com (Ripple's open-source projects)
|
||||
- `/websites/xrplevm` — docs.xrplevm.org (XRPL EVM sidechain)
|
||||
|
||||
### Protocol Implementation
|
||||
|
||||
- `/xrplf/rippled` — rippled (C++ reference implementation; authoritative for protocol behavior, transaction validation, and ledger entry structure)
|
||||
|
||||
### SDK Libraries
|
||||
|
||||
- `/xrplf/xrpl-py` — Python
|
||||
- `/xrplf/xrpl.js` — JavaScript / TypeScript
|
||||
- `/xrplf/xrpl-go` — Go
|
||||
- `/xrplf/xrpl4j` — Java
|
||||
|
||||
Since the library IDs are listed above, skip `mcp__context7__resolve-library-id` and call `mcp__context7__query-docs` directly with the relevant ID. Prefer this over web search or memory when writing code samples, documenting protocol behavior, or answering SDK API questions.
|
||||
|
||||
### Live xrpl.org Content
|
||||
|
||||
Use the `xrpl-dev-portal` MCP server (xrpl.org content only) as a fallback if `context7` is unavailable. **Only `mcp__xrpl-dev-portal__search` is functional**. Do not call the other tools.
|
||||
|
||||
## Localization
|
||||
|
||||
- Default: `en-US`
|
||||
|
||||
20
.claude/rules/code-guide.md
Normal file
20
.claude/rules/code-guide.md
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
paths:
|
||||
- "_code-samples/**"
|
||||
---
|
||||
|
||||
# XRPL Sample Code Baseline
|
||||
|
||||
This guide and language-specific rules are not concrete. If the user gives you directions that contradict these rules, note it to the user but their instructions take priority.
|
||||
|
||||
1. Before creating or updating a sample code file, confirm with the user:
|
||||
- High-level steps required
|
||||
- Which network to use:
|
||||
| | Devnet | Testnet |
|
||||
|:-----|:-------|:--------|
|
||||
| HTTP | `https://s.devnet.rippletest.net:51234` | `https://s.altnet.rippletest.net:51234` |
|
||||
| WSS | `wss://s.devnet.rippletest.net:51233` | `wss://s.altnet.rippletest.net:51233` |
|
||||
2. Check the SDK library documentation via the `context7` MCP server.
|
||||
- Get the latest stable version to pin the SDK at.
|
||||
- Get all relevant documentation for the transactions and helpers used.
|
||||
- Prefer the SDK's built-in helpers over custom code. Only use your own helpers if the library has no equivalent.
|
||||
294
.claude/rules/go-code-samples.md
Normal file
294
.claude/rules/go-code-samples.md
Normal file
@@ -0,0 +1,294 @@
|
||||
---
|
||||
paths:
|
||||
- "_code-samples/**/*.go"
|
||||
---
|
||||
|
||||
# XRPL Go Code Sample Conventions
|
||||
|
||||
Code samples come in **two flavors** with very different conventions. Identify which you're writing first.
|
||||
|
||||
| Flavor | Folder pattern | Audience | Priority |
|
||||
|---|---|---|---|
|
||||
| **Tutorial** | `<verb>-<thing>/main.go` (e.g., `create-loan-broker/main.go`) | A dev reading & learning the protocol | Clarity over speed |
|
||||
| **Setup** | `<topic>-setup/main.go` (e.g., `lending-setup/main.go`) | 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
|
||||
- `gofmt`-formatted (tabs, standard layout)
|
||||
|
||||
### Naming
|
||||
- Folder/binary names: `kebab-case` (e.g., `create-loan-broker/`)
|
||||
- Variables: `camelCase` with acronyms uppercased — `loanBrokerWallet`, `mptID`, `vaultID`, `loanBrokerID`, `credIssuerWallet`
|
||||
- Transaction struct fields: native XRPL `PascalCase` (`Account`, `VaultID`, `ManagementFeeRate`) — matches both Go's exported-field rule and the XRPL wire format. Common fields (`Account`, `Sequence`, `Fee`, `TicketSequence`) go in the embedded `BaseTx` substruct.
|
||||
- Setup JSON keys: `camelCase` (`loanBroker`, `credentialIssuer`, `mptID`, `vaultID`, `loanBrokerID`)
|
||||
|
||||
## Structure
|
||||
|
||||
### Folder layout
|
||||
|
||||
Each code sample lives at `_code-samples/<topic>/go/`. Every command is its own `kebab-case` subdir containing one `main.go`:
|
||||
|
||||
```
|
||||
_code-samples/<topic>/go/
|
||||
├── README.md
|
||||
├── go.mod
|
||||
├── go.sum # Auto-generated by `go mod tidy`; gitignored
|
||||
├── <topic>-setup/
|
||||
│ └── main.go # Optional — runs once to prep network state
|
||||
├── <topic>-setup.json # Auto-generated by the setup script; gitignored
|
||||
└── <verb>-<thing>/
|
||||
└── main.go # Tutorial commands (one per user action)
|
||||
```
|
||||
|
||||
Run any command with `go run ./<verb>-<thing>` from the language root directory.
|
||||
|
||||
### README
|
||||
|
||||
`README.md` is the entry point for a reader running the samples.
|
||||
|
||||
1. Title: `# <Topic> Examples (Go)`
|
||||
2. One-sentence description listing what the directory demonstrates
|
||||
3. `## Setup` section with the note "All commands should be run from this `go/` directory." and a `go mod tidy` fenced block
|
||||
4. One `##` section per tutorial command, in the order a reader should run them:
|
||||
- Heading describes the action (e.g., `## Create a Loan Broker`), not the folder name
|
||||
- Fenced ```sh``` block with `go run ./<verb>-<thing>`
|
||||
- One-sentence summary of what the command will output
|
||||
- Fenced ```sh``` block showing actual expected console output (real addresses, tx IDs, JSON dumps — captured from a successful sample code run)
|
||||
5. `---` separator between tutorial sections
|
||||
|
||||
The expected-output blocks document the golden path. Update them when a command's output format changes.
|
||||
|
||||
### go.mod
|
||||
|
||||
One `go.mod` per sample at the language root. Pin the xrpl-go version:
|
||||
|
||||
```
|
||||
module github.com/XRPLF
|
||||
|
||||
go 1.24.3
|
||||
|
||||
require github.com/Peersyst/xrpl-go v<latest-stable>
|
||||
```
|
||||
|
||||
`go mod tidy` populates the indirect dependency block at the bottom — that block is auto-managed and shouldn't be hand-edited.
|
||||
|
||||
### Pointer helper
|
||||
|
||||
Any `main.go` that sets optional pointer fields includes this helper near the top of the file:
|
||||
|
||||
```go
|
||||
// ptr is a helper that returns a pointer to the given value,
|
||||
// used for setting optional transaction fields in Go.
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
```
|
||||
|
||||
## Tutorial files
|
||||
|
||||
**WebSocket client** — `github.com/Peersyst/xrpl-go/xrpl/websocket`. Always wrap with `defer client.Disconnect()` right after `NewClient` so the connection closes on any exit path.
|
||||
|
||||
### Structure
|
||||
|
||||
1. Multi-line `// IMPORTANT:` header explaining what the command demonstrates and any preconditions (e.g., "uses an existing account that has a PRIVATE vault")
|
||||
2. `package main` + imports
|
||||
3. Connect to the network:
|
||||
```go
|
||||
// Connect to the network ----------------------
|
||||
client := websocket.NewClient(
|
||||
websocket.NewClientConfig().
|
||||
WithHost("wss://s.devnet.rippletest.net:51233"),
|
||||
)
|
||||
defer client.Disconnect()
|
||||
|
||||
if err := client.Connect(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
```
|
||||
4. (Optional) If the tutorial is using setup data:
|
||||
```go
|
||||
// Check for setup data; run lending-setup if missing
|
||||
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
|
||||
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
|
||||
cmd := exec.Command("go", "run", "./lending-setup")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and VaultID
|
||||
data, err := os.ReadFile("lending-setup.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var setup map[string]any
|
||||
if err := json.Unmarshal(data, &setup); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// You can replace these values with your own
|
||||
loanBrokerWallet, err := wallet.FromSecret(setup["loanBroker"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
vaultID := setup["vaultID"].(string)
|
||||
```
|
||||
5. (Optional) If the tutorial funds its own wallets instead of loading them from setup data, add `WithFaucetProvider` to the client config in step 3 and fund wallets after `client.Connect()`:
|
||||
```go
|
||||
// In step 3, extend the client config chain:
|
||||
client := websocket.NewClient(
|
||||
websocket.NewClientConfig().
|
||||
WithHost("wss://s.devnet.rippletest.net:51233").
|
||||
WithFaucetProvider(faucet.NewDevnetFaucetProvider()),
|
||||
)
|
||||
|
||||
// After client.Connect():
|
||||
testWallet, err := wallet.New(crypto.ED25519())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := client.FundWallet(&testWallet); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
```
|
||||
6. Tutorial code steps.
|
||||
|
||||
### Tutorial code step guide
|
||||
|
||||
- Before each major step, add a comment and print a section banner.
|
||||
- Build transactions as model structs, call `.Flatten()`, and print before submitting:
|
||||
```go
|
||||
// Prepare LoanBrokerSet transaction ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanBrokerSet transaction ===\n\n")
|
||||
mgmtFeeRate := types.InterestRate(1000)
|
||||
loanBrokerSetTx := transaction.LoanBrokerSet{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.ClassicAddress,
|
||||
},
|
||||
VaultID: vaultID,
|
||||
ManagementFeeRate: &mgmtFeeRate,
|
||||
}
|
||||
|
||||
// Flatten() converts the struct to a map and adds the TransactionType field
|
||||
flatLoanBrokerSetTx := loanBrokerSetTx.Flatten()
|
||||
loanBrokerSetTxJSON, _ := json.MarshalIndent(flatLoanBrokerSetTx, "", " ")
|
||||
fmt.Printf("%s\n", string(loanBrokerSetTxJSON))
|
||||
```
|
||||
- Submit with `SubmitTxAndWait` and handle results by checking for `tesSUCCESS` and exiting on failure:
|
||||
```go
|
||||
// Submit, sign, and wait for validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanBrokerSet transaction ===\n\n")
|
||||
loanBrokerSetResponse, err := client.SubmitTxAndWait(flatLoanBrokerSetTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &loanBrokerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if loanBrokerSetResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to create loan broker: %s\n", loanBrokerSetResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Loan broker created successfully!\n")
|
||||
```
|
||||
- Extract metadata relevant to the tutorial:
|
||||
```go
|
||||
// Extract loan broker information from the transaction result
|
||||
fmt.Printf("\n=== Loan Broker Information ===\n\n")
|
||||
for _, node := range loanBrokerSetResponse.Meta.AffectedNodes {
|
||||
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "LoanBroker" {
|
||||
fmt.Printf("LoanBroker ID: %s\n", node.CreatedNode.LedgerIndex)
|
||||
fmt.Printf("LoanBroker Pseudo-Account Address: %s\n", node.CreatedNode.NewFields["Account"])
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Setup files
|
||||
|
||||
**RPC client** — `github.com/Peersyst/xrpl-go/xrpl/rpc` with a faucet provider. Setup uses RPC, not WebSocket: xrpl-go's WS client is built on `gorilla/websocket`, which doesn't allow concurrent writes on a single connection.
|
||||
|
||||
```go
|
||||
cfg, err := rpc.NewClientConfig(
|
||||
"https://s.devnet.rippletest.net:51234",
|
||||
rpc.WithFaucetProvider(faucet.NewDevnetFaucetProvider()),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
client := rpc.NewClient(cfg)
|
||||
|
||||
submitOpts := func(w *wallet.Wallet) *rpctypes.SubmitOptions {
|
||||
return &rpctypes.SubmitOptions{Autofill: true, Wallet: w}
|
||||
}
|
||||
```
|
||||
|
||||
RPC endpoints: Devnet (`https://s.devnet.rippletest.net:51234`) or Testnet (`https://s.altnet.rippletest.net:51234`).
|
||||
|
||||
### Speed-first patterns when possible
|
||||
- Use goroutines + buffered channels for fan-out parallelism (not `errgroup` or `sync.WaitGroup`)
|
||||
- Each goroutine handles one independent task — often a single transaction, sometimes a multi-step pipeline wrapped in a helper closure
|
||||
- When fanning out parallel transactions from the same account, create tickets first via `TicketCreate` with `TicketCount: N`, then set `Sequence: 0` and `TicketSequence: ...` on the `BaseTx` of each parallel tx
|
||||
- xrpl-go doesn't include a fund-and-wait helper, use this:
|
||||
```go
|
||||
// Create and fund wallets concurrently
|
||||
createAndFund := func(ch chan<- wallet.Wallet) {
|
||||
w, err := wallet.New(crypto.ED25519())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := client.FundWallet(&w); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Poll until account is validated on ledger
|
||||
funded := false
|
||||
for range 20 {
|
||||
_, err := client.Request(&account.InfoRequest{
|
||||
Account: w.GetAddress(),
|
||||
LedgerIndex: common.Validated,
|
||||
})
|
||||
if err == nil {
|
||||
funded = true
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
if !funded {
|
||||
panic("Issue funding account: " + w.GetAddress().String())
|
||||
}
|
||||
ch <- w
|
||||
}
|
||||
```
|
||||
|
||||
### Setup code guide
|
||||
- Top comment: single line, `// Setup script for <topic> tutorials` above `package main`
|
||||
- Only output is a carriage-return progress indicator: `fmt.Print("Setting up tutorial: N/D\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)
|
||||
- Use `panic(err)` on every error path — setup is fail-fast, and a panic surfaces the failing line clearly. Don't silently `continue` or `_ = err`.
|
||||
|
||||
### Output file
|
||||
At the end, write all data the tutorials will need. Use an anonymous struct with `json:"camelCase"` tags so field order is preserved:
|
||||
|
||||
```go
|
||||
setupData := struct {
|
||||
Description string `json:"description"`
|
||||
LoanBroker any `json:"loanBroker"`
|
||||
DomainID string `json:"domainID"`
|
||||
MptID string `json:"mptID"`
|
||||
VaultID string `json:"vaultID"`
|
||||
LoanBrokerID string `json:"loanBrokerID"`
|
||||
}{ ... }
|
||||
|
||||
jsonData, err := json.MarshalIndent(setupData, "", " ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := os.WriteFile("lending-setup.json", jsonData, 0644); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
```
|
||||
345
.claude/rules/java-code-samples.md
Normal file
345
.claude/rules/java-code-samples.md
Normal file
@@ -0,0 +1,345 @@
|
||||
---
|
||||
paths:
|
||||
- "_code-samples/**/*.java"
|
||||
---
|
||||
|
||||
# XRPL Java Code Sample Conventions
|
||||
|
||||
Java samples currently exist only in **tutorial form**.
|
||||
|
||||
## Style
|
||||
|
||||
### Formatting
|
||||
- 2-space indent
|
||||
- UTF-8 source encoding (declared in `pom.xml`)
|
||||
|
||||
### Naming
|
||||
- Class/file: `PascalCase` verb-noun (e.g., `ManageCredentials.java`); one public class per file
|
||||
- Variables: `camelCase` (e.g., `issuerAddress`, `subjectFuture`)
|
||||
- Constants: `UPPER_SNAKE_CASE` for `static final` (e.g., `NETWORK_URL`, `FAUCET_URL`, `EXPLORER_BASE`, `CREDENTIAL_TYPE`)
|
||||
- Package: `com.example.xrpl`
|
||||
- Imports: two blocks separated by a blank line — all non-`java.*` imports together (alphabetized: `com.*`, `okhttp3.*`, `org.*`, etc.), then `java.*` last. No wildcard imports.
|
||||
|
||||
## Structure
|
||||
|
||||
### Folder layout
|
||||
|
||||
Each code sample lives at `_code-samples/<topic>/java/` as a self-contained Maven project:
|
||||
|
||||
```
|
||||
_code-samples/<topic>/java/
|
||||
├── README.md
|
||||
├── pom.xml
|
||||
├── target/ # Maven build output; gitignored
|
||||
└── src/main/
|
||||
├── java/com/example/xrpl/
|
||||
│ └── <ClassName>.java # Tutorial samples (one class per user action)
|
||||
└── resources/
|
||||
└── logback.xml
|
||||
```
|
||||
|
||||
Run any sample with `mvn exec:java -Dexec.mainClass=com.example.xrpl.<ClassName>` from the language root directory.
|
||||
|
||||
### README
|
||||
|
||||
`README.md` is the entry point for a reader running the samples.
|
||||
|
||||
1. Title: `# <Topic> Example (Java)`
|
||||
2. One-sentence description listing what the directory demonstrates
|
||||
3. `## Setup` section with an `mvn install` fenced block
|
||||
4. One `##` section per tutorial sample, in the order a reader should run them:
|
||||
- Heading is a human-readable phrase for the action (e.g., `## Manage Credentials`, `## Issue a Token`) — not a code identifier like `## ManageCredentials`
|
||||
- Fenced ```sh``` block with `mvn exec:java -Dexec.mainClass=com.example.xrpl.<ClassName>`
|
||||
- One-sentence summary of what the script will output
|
||||
- Fenced ```sh``` block showing actual expected console output (real addresses, tx hashes, JSON dumps, explorer links — captured from a successful sample code run)
|
||||
5. `---` separator between tutorial sections
|
||||
|
||||
### pom.xml
|
||||
|
||||
Java 11, UTF-8, single xrpl4j dependency, exec plugin for `mvn exec:java`:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>{topic}-samples</artifactId> <!-- e.g., credential-samples; change per directory -->
|
||||
<version>1.0.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.release>11</maven.compiler.release>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.xrpl</groupId>
|
||||
<artifactId>xrpl4j-client</artifactId>
|
||||
<version>{latest-stable}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
```
|
||||
|
||||
### logback.xml
|
||||
|
||||
`src/main/resources/logback.xml` quiets xrpl4j's DEBUG chatter so tutorial output stays readable:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Quiets xrpl4j's DEBUG chatter so tutorial output stays readable.
|
||||
Raise xrpl4j to DEBUG to see wire-level transaction details. -->
|
||||
<configuration>
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="org.xrpl.xrpl4j" level="WARN"/>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</root>
|
||||
</configuration>
|
||||
```
|
||||
|
||||
## Tutorial files
|
||||
|
||||
**xrpl4j sync client** — `org.xrpl.xrpl4j.client.XrplClient`. Use `CompletableFuture.supplyAsync` + `allOf().join()` for parallel work (e.g., funding multiple accounts).
|
||||
|
||||
### Structure
|
||||
|
||||
1. Class-level Javadoc explaining what the sample demonstrates (and any preconditions, if applicable)
|
||||
2. `package com.example.xrpl;` + imports (alphabetical within groups, `java.*` last)
|
||||
3. Class declaration with `NETWORK_URL`, `FAUCET_URL`, `EXPLORER_BASE`, and tutorial-specific constants at top:
|
||||
```java
|
||||
private static final HttpUrl NETWORK_URL = HttpUrl.get("https://s.altnet.rippletest.net:51234/");
|
||||
private static final HttpUrl FAUCET_URL = HttpUrl.get("https://faucet.altnet.rippletest.net");
|
||||
private static final String EXPLORER_BASE = "https://testnet.xrpl.org/transactions/";
|
||||
```
|
||||
4. `main()` wraps `run()` and unwraps `CompletionException` so async failures print the same clean message as sync ones:
|
||||
```java
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
run();
|
||||
} catch (Exception e) {
|
||||
// Unwrap CompletionException so async failures print the same clean message
|
||||
// as sync failures. CompletableFuture.join() wraps exceptions in CompletionException
|
||||
Throwable cause = (e instanceof CompletionException && e.getCause() != null)
|
||||
? e.getCause() : e;
|
||||
System.err.println("Error: " + cause.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
```
|
||||
5. `private static void run()` holds the main flow.
|
||||
6. Connect to network and fund however many accounts the sample needs. Fund in parallel via `CompletableFuture.supplyAsync` + `allOf().join()` when there's more than one. Two-account example:
|
||||
```java
|
||||
// ----- Connect to Testnet and fund accounts -----
|
||||
XrplClient xrplClient = new XrplClient(NETWORK_URL);
|
||||
System.out.println("\n=== Funding issuer and subject accounts on Testnet ===\n");
|
||||
|
||||
CompletableFuture<KeyPair> issuerFuture = CompletableFuture.supplyAsync(
|
||||
() -> createAndFundWallet(xrplClient));
|
||||
CompletableFuture<KeyPair> subjectFuture = CompletableFuture.supplyAsync(
|
||||
() -> createAndFundWallet(xrplClient));
|
||||
CompletableFuture.allOf(issuerFuture, subjectFuture).join();
|
||||
|
||||
KeyPair issuer = issuerFuture.join();
|
||||
KeyPair subject = subjectFuture.join();
|
||||
Address issuerAddress = issuer.publicKey().deriveAddress();
|
||||
Address subjectAddress = subject.publicKey().deriveAddress();
|
||||
System.out.println("Issuer: " + issuerAddress);
|
||||
System.out.println("Subject: " + subjectAddress);
|
||||
```
|
||||
7. Tutorial code steps.
|
||||
8. Useful helpers below a `// ===== Helper functions =====` divider, each prefixed with a one-line comment. Copy any helpers the sample uses — the signatures and bodies below are canonical; only include the ones you call:
|
||||
```java
|
||||
// ===== Helper functions =====
|
||||
|
||||
// Generates a new Ed25519 keypair, funds it from the Testnet faucet, and
|
||||
// returns the keypair once the account is visible on a validated ledger.
|
||||
private static KeyPair createAndFundWallet(XrplClient xrplClient) {
|
||||
KeyPair keyPair = Seed.ed25519Seed().deriveKeyPair();
|
||||
Address address = keyPair.publicKey().deriveAddress();
|
||||
FaucetClient faucetClient = FaucetClient.construct(FAUCET_URL);
|
||||
faucetClient.fundAccount(FundAccountRequest.of(address));
|
||||
|
||||
for (int attempt = 0; attempt < 20; attempt++) {
|
||||
try {
|
||||
xrplClient.accountInfo(AccountInfoRequestParams.builder()
|
||||
.account(address)
|
||||
.ledgerSpecifier(LedgerSpecifier.VALIDATED)
|
||||
.build());
|
||||
return keyPair;
|
||||
} catch (JsonRpcClientErrorException notYetVisible) {
|
||||
try {
|
||||
Thread.sleep(1_000L);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Account polling interrupted for " + address + ". " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Faucet funding for " + address + " did not confirm in time.");
|
||||
}
|
||||
|
||||
// Fetches the next transaction sequence number of an address from
|
||||
// the latest validated ledger.
|
||||
private static UnsignedInteger accountSequence(XrplClient xrplClient, Address address) {
|
||||
try {
|
||||
AccountInfoResult info = xrplClient.accountInfo(AccountInfoRequestParams.builder()
|
||||
.account(address)
|
||||
.ledgerSpecifier(LedgerSpecifier.VALIDATED)
|
||||
.build());
|
||||
return info.accountData().sequence();
|
||||
} catch (JsonRpcClientErrorException e) {
|
||||
throw new RuntimeException("Failed to fetch account sequence for " + address + ". " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetches the current network fee and returns the recommended fee for
|
||||
// a standard (non-multisig, non-batch) transaction.
|
||||
private static XrpCurrencyAmount recommendedFee(XrplClient xrplClient) {
|
||||
try {
|
||||
return FeeUtils.computeNetworkFees(xrplClient.fee()).recommendedFee();
|
||||
} catch (JsonRpcClientErrorException e) {
|
||||
throw new RuntimeException("Failed to fetch network fee. " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// Computes a safe LastLedgerSequence for a new transaction. The
|
||||
// latest validated ledger index plus a small buffer (20 ledgers).
|
||||
private static UnsignedInteger lastLedgerSequence(XrplClient xrplClient) {
|
||||
try {
|
||||
UnsignedInteger validatedLedger = xrplClient.ledger(LedgerRequestParams.builder()
|
||||
.ledgerSpecifier(LedgerSpecifier.VALIDATED)
|
||||
.build())
|
||||
.ledgerIndexSafe()
|
||||
.unsignedIntegerValue();
|
||||
return validatedLedger.plus(UnsignedInteger.valueOf(20));
|
||||
} catch (JsonRpcClientErrorException e) {
|
||||
throw new RuntimeException("Failed to compute LastLedgerSequence. " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// Prints a transaction as a formatted JSON.
|
||||
private static void printTransactionJson(Transaction tx) {
|
||||
try {
|
||||
System.out.println(ObjectMapperFactory.create().writerWithDefaultPrettyPrinter().writeValueAsString(tx));
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException("Failed to serialize transaction JSON. " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// Signs and submits a transaction, then polls the network until
|
||||
// the transaction reaches a validated state.
|
||||
private static <T extends Transaction> TransactionResult<T> signSubmitAndWait(
|
||||
XrplClient xrplClient,
|
||||
KeyPair signer,
|
||||
T transaction,
|
||||
Class<T> transactionType
|
||||
) {
|
||||
SignatureService<PrivateKey> signatureService = new BcSignatureService();
|
||||
|
||||
UnsignedInteger lastLedgerSequence = transaction.lastLedgerSequence()
|
||||
.orElseThrow(() -> new IllegalArgumentException(
|
||||
"Must set LastLedgerSequence for polling expiration"));
|
||||
|
||||
try {
|
||||
SingleSignedTransaction<T> signed = signatureService.sign(signer.privateKey(), transaction);
|
||||
SubmitResult<T> submit = xrplClient.submit(signed);
|
||||
|
||||
if (!TransactionResultCodes.TES_SUCCESS.equals(submit.engineResult())) {
|
||||
throw new IllegalStateException(
|
||||
"Submission rejected. " + submit.engineResult() + " — " + submit.engineResultMessage());
|
||||
}
|
||||
|
||||
Finality finality;
|
||||
do {
|
||||
Thread.sleep(1_000L);
|
||||
finality = xrplClient.isFinal(
|
||||
signed.hash(),
|
||||
submit.validatedLedgerIndex(),
|
||||
lastLedgerSequence,
|
||||
transaction.sequence(),
|
||||
signer.publicKey().deriveAddress()
|
||||
);
|
||||
} while (finality.finalityStatus() == FinalityStatus.NOT_FINAL);
|
||||
|
||||
if (finality.finalityStatus() != FinalityStatus.VALIDATED_SUCCESS) {
|
||||
throw new IllegalStateException(
|
||||
"Transaction failed with status " + finality.finalityStatus()
|
||||
+ ". Result code: " + finality.resultCode().orElse("unknown"));
|
||||
}
|
||||
|
||||
// Retrieve the transaction result; isFinal() only returns finality status
|
||||
return xrplClient.transaction(
|
||||
TransactionRequestParams.of(signed.hash()), transactionType);
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Transaction polling interrupted. " + e.getMessage(), e);
|
||||
} catch (JsonRpcClientErrorException | JsonProcessingException e) {
|
||||
throw new RuntimeException("Transaction processing failed. " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// Checks for a tesSUCCESS result code. If true, prints an explorer
|
||||
// link. Otherwise, throws an error.
|
||||
private static void requireSuccess(TransactionResult<?> result) {
|
||||
String code = result.metadata().get().transactionResult();
|
||||
String txType = result.transaction().transactionType().value();
|
||||
if (!TransactionResultCodes.TES_SUCCESS.equals(code)) {
|
||||
throw new IllegalStateException(txType + " failed with error code " + code);
|
||||
}
|
||||
System.out.println(txType + " succeeded!");
|
||||
System.out.println("Explorer: " + EXPLORER_BASE + result.hash());
|
||||
}
|
||||
```
|
||||
|
||||
### Tutorial code step guide
|
||||
|
||||
- Before each major step, add a section comment and print a banner. Strict format: `// ----- Title -----` on its own line, then `System.out.println("\n=== Title ===\n");` immediately after. The title text should match between the two.
|
||||
- Build transactions with the builder pattern; always set `sequence`, `fee`, `lastLedgerSequence`, `signingPublicKey` from shared helpers, then print as pretty JSON:
|
||||
```java
|
||||
// ----- Prepare CredentialCreate transaction -----
|
||||
System.out.println("\n=== Preparing CredentialCreate transaction ===\n");
|
||||
|
||||
CredentialCreate createTx = CredentialCreate.builder()
|
||||
.account(issuerAddress)
|
||||
.subject(subjectAddress)
|
||||
.credentialType(CREDENTIAL_TYPE)
|
||||
.sequence(accountSequence(xrplClient, issuerAddress))
|
||||
.fee(recommendedFee(xrplClient))
|
||||
.lastLedgerSequence(lastLedgerSequence(xrplClient))
|
||||
.signingPublicKey(issuer.publicKey())
|
||||
.build();
|
||||
printTransactionJson(createTx);
|
||||
```
|
||||
- Sign, submit, and wait via the shared `signSubmitAndWait` helper, then verify success with `requireSuccess`:
|
||||
```java
|
||||
// ----- Sign, submit, and wait for CredentialCreate validation -----
|
||||
System.out.println("\n=== Submitting CredentialCreate transaction ===\n");
|
||||
|
||||
TransactionResult<CredentialCreate> createResult = signSubmitAndWait(
|
||||
xrplClient, issuer, createTx, CredentialCreate.class);
|
||||
|
||||
requireSuccess(createResult);
|
||||
```
|
||||
205
.claude/rules/javascript-code-samples.md
Normal file
205
.claude/rules/javascript-code-samples.md
Normal file
@@ -0,0 +1,205 @@
|
||||
---
|
||||
paths:
|
||||
- "_code-samples/**/*.js"
|
||||
---
|
||||
|
||||
# XRPL JavaScript 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>.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.
|
||||
|
||||
## 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)
|
||||
5. `---` 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:
|
||||
|
||||
```json
|
||||
{
|
||||
"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:
|
||||
```js
|
||||
// Connect to the network ----------------------
|
||||
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
|
||||
await client.connect()
|
||||
```
|
||||
4. (Optional) If the tutorial is using setup script data:
|
||||
```js
|
||||
// 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}`)
|
||||
```
|
||||
5. (Optional) If no setup data is used, fund new wallets for the tutorial.
|
||||
```js
|
||||
const { wallet } = await client.fundWallet()
|
||||
```
|
||||
If creating multiple wallets, parallelize the process:
|
||||
```js
|
||||
const [
|
||||
{ wallet: loanBroker },
|
||||
{ wallet: borrower },
|
||||
{ wallet: depositor },
|
||||
{ wallet: credentialIssuer }
|
||||
] = await Promise.all([
|
||||
client.fundWallet(),
|
||||
client.fundWallet(),
|
||||
client.fundWallet(),
|
||||
client.fundWallet()
|
||||
])
|
||||
```
|
||||
6. Tutorial code steps.
|
||||
7. Disconnect at the end of the code sample.
|
||||
```js
|
||||
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:
|
||||
```js
|
||||
// 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:
|
||||
```js
|
||||
// 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:
|
||||
```js
|
||||
// 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:
|
||||
```js
|
||||
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))
|
||||
```
|
||||
188
.claude/rules/python-code-samples.md
Normal file
188
.claude/rules/python-code-samples.md
Normal file
@@ -0,0 +1,188 @@
|
||||
---
|
||||
paths:
|
||||
- "_code-samples/**/*.py"
|
||||
---
|
||||
|
||||
# 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
|
||||
```
|
||||
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 `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)
|
||||
5. `---` 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")
|
||||
```
|
||||
4. (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}")
|
||||
```
|
||||
5. (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)
|
||||
```
|
||||
Reference in New Issue
Block a user