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


1. `---` 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)
}
```

1. (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)
```

1. (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)
}
```

1. 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)
}
```