Skip to content
Draft
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions _code-samples/simplexrpl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# simpleXRPL Examples

This directory contains runnable TypeScript examples that demonstrate `simpleXRPL` business operations.
109 changes: 109 additions & 0 deletions _code-samples/simplexrpl/getStarted.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* simpleXRPL — Get Started walkthrough. Config is read from the environment
* (never hard-code keys); endpoints target the XRPL Testnet.
*/
import {
dispatch,
isNativePath,
LocalSigner,
PalisadeCustody,
RippleCustody,
SignerCapabilityError,
SimpleXRPL,
} from 'simplexrpl'
import type { Custodian, SubmissionPath, TransactorType } from 'simplexrpl'

// --- Initialize the client ---
// `SimpleXRPL.init(...)` is the only entry point — it binds already-authenticated
// connectors to a network and builds the account index. A single local signer
// (seeds from `XRPL_*_SEED`) is enough to run against the Testnet today.
const client = await SimpleXRPL.init({
// Point at a rippled endpoint. `faucetUrl` is only used on test networks
// (by `client.account.fund`).
rippledUrl: 'wss://s.altnet.rippletest.net:51233',
faucetUrl: 'https://faucet.altnet.rippletest.net/accounts',
signers: [LocalSigner.fromEnv()],
// `primarySigner` is the default backend for verbs called without an explicit
// account; it defaults to `signers[0]`, so it's optional with a single signer.
})

Comment thread
oeggert marked this conversation as resolved.
// --- Discover your accounts ---
// Connectors discover their accounts at init; the client merges them into one
// index keyed by r-address.
for (const [address, account] of client.accounts) {
console.log(address, '→', account.signer.kind)
}

// Resolve the account a verb would act on (no argument → the primary account).
const primary = client.resolveAccount()
console.log('primary:', primary.address)

// Read an account's on-chain state — a read, so no signer is required.
const state = await client.account.retrieve()
console.log('balance (XRP):', state.data.xrpBalance, '| sequence:', state.data.sequence)

// --- Check how operations route ---
// Before submitting, ask how each transactor would route for an account: signed
// locally, a custodian's native operation, the raw sign-only fallback, or rejected.
const TRANSACTORS: TransactorType[] = [
'Payment',
'TrustSet',
'OfferCreate',
'MPTokenIssuanceCreate',
'CredentialCreate',
'PermissionedDomainSet',
]
for (const transactor of TRANSACTORS) {
let path: SubmissionPath | 'rejected' = 'rejected'
try {
path = dispatch(primary, transactor)
} catch (error) {
// `dispatch` throws when the connector can neither natively nor raw-sign it.
if (!(error instanceof SignerCapabilityError)) throw error
}
const via =
path === 'rejected'
? '(unsupported)'
: isNativePath(path)
? '(custodian network)'
: '(shared ledger)'
console.log(`${transactor.padEnd(24)} → ${path} ${via}`)
}

// --- Send a payment ---
// Verbs use the primary account by default; target another bound account with `from`.
const result = await client.xrp.transfer({
to: 'rDestination00000000000000000000000',
amount: '10',
})
console.log('submitted:', result.txHash)

await client.disconnect()

// --- Connect a custodian (production) ---
// For production, construct each custodian connector on its own and pass them to
// `SimpleXRPL.init`'s `signers` in place of (or alongside) the local signer above,
// e.g. `signers: await connectCustodians(), primarySigner: /* your custodian */`.
export async function connectCustodians(): Promise<Custodian[]> {
// Palisade — OAuth client credentials, acting on a specific vault/wallet.
const palisade = await PalisadeCustody.create({
baseUrl: 'https://api.sandbox.palisade.co',
clientId: process.env.PALISADE_CLIENT_ID ?? '',
clientSecret: process.env.PALISADE_CLIENT_SECRET ?? '',
primary: {
vaultId: process.env.PALISADE_VAULT_ID ?? '',
walletId: process.env.PALISADE_WALLET_ID ?? '',
},
// Enable the raw sign-only fallback for transactors Palisade has no native
// operation for. Off by default.
allowRawSigning: false,
})

// Ripple Custody — an intent-author key exchanged for a token; one Custody
// domain. `fromEnv` reads the `RIPPLE_CUSTODY_*` variables.
const rippleCustody = await RippleCustody.fromEnv({
primary: process.env.RIPPLE_CUSTODY_PRIMARY ?? '',
})

return [palisade, rippleCustody, LocalSigner.fromEnv()]
}
10 changes: 10 additions & 0 deletions docs/_snippets/simplexrpl-response-fields.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Every simpleXRPL write resolves to a `SubmissionResult<T>` — a union tagged by `source`, with the backend's raw response preserved verbatim. Its common fields are:

| Field | Type | Description |
| --- | --- | --- |
| `intent` | `T` | The method-specific output. See the method's return fields below. |
| `source` | `'rippled' \| 'custody' \| 'palisade'` | Which backend produced the result; discriminates `response`. |
| `response` | `TxResponse` \| custody record \| Palisade record | The backend's raw response, preserved verbatim. |
| `txHash` | `string` _(optional)_ | The XRPL transaction hash, once the transaction is on-ledger. |
| `intentId` | `string` _(optional)_ | The custodian intent id, when the path produced one. |
| `idempotencyKey` | `string` _(optional)_ | The UUIDv7 this submission carried. Pass it back as a later call's `idempotencyKey` to retry to the same intent. |
7 changes: 7 additions & 0 deletions docs/_snippets/simplexrpl-write-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
`options` is an optional second argument that sets the source account and overrides the fee.

| Option | Type | Required | Description |
| --- | --- | --- | --- |
| `from` | `AccountSelector` | No | The account to act as. Defaults to the primary signer's primary account. (For IOU verbs, this is the issuer.) |
| `fee` | `FeeIntent` | No | Fee override. |
| `idempotencyKey` | `string` | No | A prior submission's `idempotencyKey`, to retry to the same intent instead of creating a duplicate. Auto-generated when omitted. |
98 changes: 98 additions & 0 deletions docs/simpleXRPL/get-started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
seo:
description: Install simpleXRPL, initialize the client, connect a custodian, discover your accounts, check routing, and send your first XRP Ledger payment.
labels:
- simpleXRPL
- SDK
---

# Get Started

This tutorial takes you through the basics of sending your first operation on the XRP Ledger with simpleXRPL.
Comment thread
oeggert marked this conversation as resolved.
Outdated


## Goals

By the end of this tutorial, you will be able to:

- Initialize a client.
- Connect a custodian.
- Discover your accounts.
- Check how an operation will route before you submit it.
- Transfer XRP between accounts.


## Prerequisites

To complete this tutorial, you should:

- Have some familiarity with writing code in TypeScript.
- Have **Node.js version 20.19** or later.


## Source Code

You can find the complete source code for this tutorial's examples in the [code samples section of this website's repository](https://github.com/ripple/opensource.ripple.com/tree/main/_code-samples/simplexrpl/getStarted.ts)


## Steps

### 1. Install dependencies

```sh
npm install simplexrpl
```

### 2. Initialize the client

`SimpleXRPL.init(...)` is the single entry point — you never construct the client with `new`. It binds one or more already-authenticated signing backends (the **connectors**) to a network and builds the account index.

{% code-snippet file="/_code-samples/simplexrpl/getStarted.ts" language="ts" before="// --- Discover your accounts ---" /%}

- **`primarySigner`** is the default backend for verbs called without an explicit account. It defaults to `signers[0]`, so you only set it when you bind more than one connector.
- With **no `signers`**, the client is read-only: reads work, but write verbs throw `NoSignerError` until a signer is added.
- Bind an account at runtime (for example, a freshly created wallet) with `client.registerLocalAccount(seed)`.

See [Client and initialization](references/index.md#client-and-initialization) for the full configuration reference.

### 3. Connect a custodian

The local signer above is enough for development. For production, construct a custodian connector and pass it to `init`'s `signers` — in place of, or alongside, the local one. simpleXRPL ships **Ripple Custody** and **Palisade**, each constructed and authenticated on its own:
Comment thread
oeggert marked this conversation as resolved.
Outdated

{% code-snippet file="/_code-samples/simplexrpl/getStarted.ts" language="ts" from="// --- Connect a custodian (production) ---" /%}

Read credentials from your environment or secrets manager — never hard-code keys. Once bound, every vertical verb works the same regardless of which connector owns the account: the SDK routes each write to the custodian that holds it.
Comment thread
oeggert marked this conversation as resolved.
Outdated

{% admonition type="info" name="Note" %}
Whether an operation runs through a custodian's **native** path or the **raw-signing fallback** is decided per operation; the fallback is off by default and enabled per connector via `allowRawSigning`. See [Operation Execution](index.md#operation-execution) and the [Connector Routing](references/connectors/connector-routing.md) table.
Comment thread
oeggert marked this conversation as resolved.
Outdated
{% /admonition %}

### 4. Discover your accounts

Connectors discover their accounts at init; the client merges them into a single index keyed by r-address. List them, resolve the primary, and read on-chain state — a read needs no signer:
Comment thread
oeggert marked this conversation as resolved.
Outdated

{% code-snippet file="/_code-samples/simplexrpl/getStarted.ts" language="ts" from="// --- Discover your accounts ---" before="// --- Check how operations route ---" /%}

### 5. Check how operations route

Before you submit, ask how a given transactor would route for an account — signed locally, through a custodian's native operation, via the raw sign-only fallback, or rejected:

{% code-snippet file="/_code-samples/simplexrpl/getStarted.ts" language="ts" from="// --- Check how operations route ---" before="// --- Send a payment ---" /%}

### 6. Send a payment

Operations are grouped into domain-specific **verticals** — `xrp`, `token`, `iou`, `credential`, `domain`, and `account` — reached off the client. Each verb uses the primary account by default; target a different bound account with `from`. Here's a native XRP payment:
Comment thread
oeggert marked this conversation as resolved.
Outdated

{% code-snippet file="/_code-samples/simplexrpl/getStarted.ts" language="ts" from="// --- Send a payment ---" before="// --- Connect a custodian (production) ---" /%}

Every write resolves to a `SubmissionResult` carrying the transaction hash, the backend's raw response, and a typed `intent` output. See [Results and handles](references/index.md#results-and-handles).

{% admonition type="success" name="Tip" %}
On a test network, create and fund an account first with [account.create()](references/verticals/account/create.md) and [account.fund()](references/verticals/account/fund.md), then use its address as the source or destination.
{% /admonition %}

## Next steps

- **Tutorials** — end-to-end workflows: [Issue an RWA as an MPT](tutorials/issue-rwa-as-mpt.md), [Issue and distribute an IOU](tutorials/issue-and-distribute-iou.md), [Place a DEX order](tutorials/place-dex-order.md), and more.
- **Reference** — every vertical, method, connector, and type: [Reference](references/index.md).
- **Concepts** — what simpleXRPL is and why: [What is simpleXRPL](index.md).
47 changes: 47 additions & 0 deletions docs/simpleXRPL/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
seo:
description: simpleXRPL is an opinionated TypeScript SDK that lets institutional developers express XRP Ledger operations as business intent and route them through the custodians they already use.
labels:
- simpleXRPL
- SDK
---

# simpleXRPL
[Source](https://github.com/ripple/simpleXRPL)

The `simpleXRPL` is an opinionated TypeScript SDK for the XRP Ledger, built for institutional developers who interact with the ledger through a custodian. It raises the level of your code from XRPL protocol mechanics to business operations that the SDK routes through your institutional custodians. Concretely, this means:
Comment thread
oeggert marked this conversation as resolved.
Outdated

- `simpleXRPL` defines the shape of business operations, handling the underlying XRPL transactions and custodian API calls.
- Your code doesn't change even if you switch custodians or operate across several at once.

{% admonition type="warning" name="Caution" %}
`simpleXRPL` is pre-1.0. The public API may change between releases and no sandbox exists yet to test native custodian operations.
{% /admonition %}


## How It Works

`simpleXRPL` is built around four concepts:

- **Clients**: Establishes the network connection and the connector configuration. Both are immutable for the client's lifetime; to change either, you create a new client.
- **Connectors**: The execution model that determines *how* operations run and *who* holds account keys. Each connector exposes a uniform interface to the rest of the SDK, so the same code runs across all of them.
- **Accounts**: An XRPL [account](https://xrpl.org/docs/concepts/accounts) paired with the connector that signs for it.
Comment thread
oeggert marked this conversation as resolved.
Outdated
- **Verticals**: Domain-specific classes and methods that group related business operations.


## Operation Execution

For every operation, on every connector, `simpleXRPL` has a statically defined routing decision that it reports at initialization:

- **Native**: Maps onto an endpoint a custodian exposes and natively handles.
- **Raw Signing**: For an operation the custodian exposes no native support for, the SDK builds the underlying XRPL transactions, the custodian signs the raw bytes, then the SDK submits them to the XRPL directly. Raw signing is off by default and enabled per connector; once on, it covers every operation that connector can't handle natively.
- **Unavailable**: The custodian doesn't expose a native endpoint and raw signing isn't enabled on the connector, so this operation is rejected.

{% admonition type="info" name="Note" %}
For a complete list of supported operations by connector, see [Connector Routing](./references/connectors/connector-routing.md)
{% /admonition %}


## See Also

- [Get Started](./get-started.md)
61 changes: 61 additions & 0 deletions docs/simpleXRPL/references/connectors/connector-routing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
seo:
description: The connector routing table — per XRPL transactor and per custodian, whether an operation routes native, requires the raw-signing fallback, or is unavailable.
labels:
- simpleXRPL
- SDK
---

# Connector Routing

[Source](https://github.com/ripple/simpleXRPL/blob/50619258cf753008e8a185eaeb3ceca489e5998a/docs/connector-routing.md)
Comment thread
oeggert marked this conversation as resolved.
Outdated

How simpleXRPL dispatches each XRPL transactor per connector. Derived directly from each custodian's native-operation set and the transactors the verticals build, so it always matches the code.

{% admonition type="info" name="Note" %}
This page is generated from the SDK source by `scripts/gen-connector-routing.mjs` — do not edit by hand. Regenerate with `npm run docgen:routing`.
{% /admonition %}

## Transactor → connector path

The pipeline routes by transactor type: **Local** signs everything in-process; a custodian uses its **native** operation when the transactor is in its capability set, otherwise the **raw** sign-only fallback, otherwise the write is rejected.

| Transactor | Local | Ripple Custody | Palisade |
| --- | --- | --- | --- |
| `AccountSet` | signs locally | **native** | **native** |
| `Clawback` | signs locally | **native** | **native** |
| `CredentialAccept` | signs locally | raw fallback¹ | raw fallback¹ |
| `CredentialCreate` | signs locally | raw fallback¹ | raw fallback¹ |
| `CredentialDelete` | signs locally | raw fallback¹ | raw fallback¹ |
| `DepositPreauth` | signs locally | **native** | raw fallback¹ |
| `EscrowFinish` | signs locally | **native** | raw fallback¹ |
| `MPTokenAuthorize` | signs locally | **native** | raw fallback¹ |
| `MPTokenIssuanceCreate` | signs locally | **native** | raw fallback¹ |
| `MPTokenIssuanceDestroy` | signs locally | **native** | raw fallback¹ |
| `MPTokenIssuanceSet` | signs locally | **native** | raw fallback¹ |
| `OfferCancel` | signs locally | raw fallback¹ | **native** |
| `OfferCreate` | signs locally | **native** | **native** |
| `Payment` | signs locally | **native** | **native** |
| `PermissionedDomainDelete` | signs locally | raw fallback¹ | raw fallback¹ |
| `PermissionedDomainSet` | signs locally | raw fallback¹ | raw fallback¹ |
| `SetRegularKey` | signs locally | raw fallback¹ | raw fallback¹ |
| `TrustSet` | signs locally | **native** | **native** |

¹ **raw fallback** applies only when raw signing is enabled on that custodian (`allowRawSigning`). With raw signing disabled, a non-native transactor is rejected with `SignerCapabilityError` — use a Local account or a custodian that natively supports it. The raw path signs the encoded transaction and submits it through the shared XRPL connection.

## Vertical → transactors

Which XRPL transactors each vertical builds. Cross-reference with the table above to see how a given method routes on each connector.

| Vertical | Transactors emitted |
| --- | --- |
| `account` | `AccountSet`, `DepositPreauth`, `Payment`, `SetRegularKey` |
| `credential` | `CredentialAccept`, `CredentialCreate`, `CredentialDelete` |
| `domain` | `PermissionedDomainDelete`, `PermissionedDomainSet` |
| `iou` | `AccountSet`, `Clawback`, `OfferCancel`, `OfferCreate`, `Payment`, `TrustSet` |
| `token` | `MPTokenAuthorize`, `MPTokenIssuanceCreate`, `MPTokenIssuanceDestroy`, `MPTokenIssuanceSet`, `OfferCancel`, `OfferCreate`, `Payment` |
| `xrp` | `Payment` |

---

_Native-ops sets: `NATIVE_XRPL_TRANSACTORS` (Ripple Custody), `PALISADE_NATIVE_TRANSACTORS` (Palisade). Local signs all transactors._
15 changes: 15 additions & 0 deletions docs/simpleXRPL/references/connectors/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
seo:
description: A connector is a signing backend in simpleXRPL — LocalSigner, RippleCustody, or PalisadeCustody — constructed on its own and bound to the client at initialization.
labels:
- simpleXRPL
- SDK
---

# Connectors

A connector is a signing backend: it determines how an operation runs and which custodian holds your account keys. Each is constructed and authenticated on its own, then passed to the client constructed by `simpleXRPL` in a `signers` array. `simpleXRPL` supports these connectors:

- [Local](./local.md)
- [Ripple Custody](./ripple-custody.md)
- [Palisade](./palisade.md)
Loading