Skip to content

Commit d0c9a6f

Browse files
fix(docs): apply additional suggested changes
1 parent dddcfaf commit d0c9a6f

7 files changed

Lines changed: 116 additions & 15 deletions

File tree

docs/smart-accounts/1-overview.mdx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ Flare Smart Accounts support two complementary flows for turning an XRPL `Paymen
5252
### Direct-minting (memo) flow
5353

5454
1. The XRPL user sends a `Payment` to an FAssets agent's XRPL address that mints FXRP directly to the smart account, with the [memo field](/smart-accounts/custom-instruction-comparison) carrying the instruction.
55-
2. The FAssets `AssetManager` mints FXRP to the `MasterAccountController` and calls back into [`handleMintedFAssets`](/smart-accounts/reference/IMasterAccountController#handlemintedfassets) on `MemoInstructionsFacet`.
56-
3. The facet routes the FAssets to the user's `PersonalAccount`, optionally pays an executor fee, and dispatches any memo instruction (`0xFF` execute UserOp, `0xE0` ignore memo, `0xE1` set nonce, `0xE2` replace fee, `0xD0`/`0xD1` set/remove executor).
55+
2. The FAssets `AssetManager` mints FXRP to the `MasterAccountController` and calls back into [`handleMintedFAssets`](/smart-accounts/reference/IMasterAccountController#handlemintedfassets).
56+
3. The `MasterAccountController` routes the FAssets to the user's `PersonalAccount`, optionally pays an executor fee, and dispatches any [memo instruction](#memo-opcodes-direct-minting-flow).
5757

5858
```mermaid
5959
graph TB
@@ -106,7 +106,7 @@ The first nibble is the instruction type.
106106
This is either `FXRP`, `Firelight`, or `Upshift` (with corresponding type IDS `0`, `1`, and `2`).
107107
The second nibble is the instruction command; the available commands are different for each instruction type.
108108

109-
For the direct-minting flow, the memo carries a different layout - see the [Custom Instruction guide](/smart-accounts/custom-instruction) for the 0xFE variant, the [Raw Custom Instruction guide](/smart-accounts/raw-custom-instruction) for the `PackedUserOperation` format, or the [comparison](/smart-accounts/custom-instruction-comparison) of `0xFE`/`0xFF` and the executor-management codes `0xE0`/`0xE1`/`0xE2`/`0xD0`/`0xD1`.
109+
For the direct-minting flow, the memo carries a different layout; the first byte selects one of the [memo opcodes](#memo-opcodes-direct-minting-flow) listed below.
110110

111111
<details>
112112
<summary>Table of instruction IDs and corresponding actions.</summary>
@@ -151,12 +151,28 @@ Instructions for interacting with an Upshift-type vault.
151151

152152
</details>
153153

154+
### Memo opcodes (direct-minting flow)
155+
156+
The XRPL memo for the direct-minting flow selects one of the following opcodes in its first byte:
157+
158+
| Memo opcode | Action | Description |
159+
| ----------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
160+
| `0xFE` | Custom Instruction | Carry `keccak256(PackedUserOperation)` in the memo; the bytes are delivered off-chain by an executor. See [Custom Instruction](/smart-accounts/custom-instruction). |
161+
| `0xFF` | Raw Custom Instruction | Carry the full ABI-encoded `PackedUserOperation` inline in the memo. See [Raw Custom Instruction](/smart-accounts/raw-custom-instruction). |
162+
| `0xE0` | Skip memo | Mark a target XRPL transaction's memo to be skipped on its next direct mint. |
163+
| `0xE1` | Fast-forward nonce | Advance the personal account's memo-instruction nonce. |
164+
| `0xE2` | Replace executor fee | Set a replacement executor fee for a stuck XRPL transaction. |
165+
| `0xD0` | Pin executor | Pin a specific executor address to the personal account. |
166+
| `0xD1` | Unpin executor | Unpin the executor from the personal account. |
167+
168+
The [Custom Instruction Comparison](/smart-accounts/custom-instruction-comparison) covers when to choose `0xFE` over `0xFF`.
169+
154170
## Dispatch on Flare
155171

156172
### Proof-based flow
157173

158174
The operator monitors incoming transactions to the specified XRPL address.
159-
Upon receiving a payment, it requests a [`Payment` attestation](/fdc/attestation-types/payment) from the FDC and submits the proof together with the user's XRPL address to the appropriate facet on the `MasterAccountController`:
175+
Upon receiving a payment, it requests a [`Payment` attestation](/fdc/attestation-types/payment) from the FDC and submits the proof together with the user's XRPL address to the appropriate function on the `MasterAccountController`:
160176

161177
- [`reserveCollateral`](/smart-accounts/reference/IMasterAccountController#reservecollateral) — for command `00` of any instruction type.
162178
Takes the payment reference and XRPL transaction ID (no FDC proof needed at this stage, the user has only committed to mint).
@@ -173,8 +189,8 @@ The contract then resolves the XRPL user's `PersonalAccount` from the address ma
173189

174190
### Direct-minting flow
175191

176-
When the user mints FXRP directly to their smart account via [FAssets direct minting](/fassets/direct-minting), the FAssets `AssetManager` calls back into [`handleMintedFAssets`](/smart-accounts/reference/IMasterAccountController#handlemintedfassets) on `MemoInstructionsFacet`.
177-
It enforces that the caller is the `AssetManager`, resolves (or deploys) the user's `PersonalAccount`, pays an executor fee out of the minted FAssets, forwards the remainder to the personal account, and dispatches any memo instruction (`0xFF`, `0xFE`, `0xE0`, `0xE1`, `0xE2`, `0xD0`, `0xD1`).
192+
When the user mints FXRP directly to their smart account via [FAssets direct minting](/fassets/direct-minting), the FAssets `AssetManager` calls back into [`handleMintedFAssets`](/smart-accounts/reference/IMasterAccountController#handlemintedfassets) on the `MasterAccountController`.
193+
It enforces that the caller is the `AssetManager`, resolves (or deploys) the user's `PersonalAccount`, pays an executor fee out of the minted FAssets, forwards the remainder to the personal account, and dispatches any [memo instruction](#memo-opcodes-direct-minting-flow).
178194

179195
## Actions on Flare
180196

docs/smart-accounts/3-custom-instruction.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ sidebar_position: 1
33
slug: custom-instruction
44
title: Custom Instruction
55
authors: [nikerzetic]
6-
description: Performing custom function calls in the Flare Smart Accounts by committing to a PackedUserOperation hash on XRPL and delivering the bytes via an executor (0xFE).
6+
description: Performing custom function calls in Flare Smart Accounts via XRPL payments with an off-chain executor.
77
tags: [intermediate, ethereum, flare-smart-accounts]
88
keywords:
99
[

docs/smart-accounts/4-raw-custom-instruction.mdx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,12 @@ A destination tag forces [FAssets direct minting](/fassets/direct-minting) to cr
3535

3636
The XRPL memo carries a 10-byte instruction header followed by the ABI-encoded `PackedUserOperation`:
3737

38-
| Bytes | Field | Meaning |
39-
| ----- | ---------------- | ---------------------------------------------------------------------- |
40-
| `0` | `instructionId` | `0xFF` - raw custom instruction |
41-
| `1` | `walletId` | One-byte wallet identifier assigned by Flare; `0` if not registered |
42-
| `2-9` | `executorFeeUBA` | Executor fee in the FAsset's smallest unit, big-endian `uint64` |
43-
| `10+` | `userOpData` | `abi.encode(PackedUserOperation)` - the call payload the facet decodes |
38+
| Bytes | Field | Meaning |
39+
| ----- | ---------------- | --------------------------------------------------------------------------- |
40+
| `0` | `instructionId` | `0xFF` - raw custom instruction |
41+
| `1` | `walletId` | One-byte wallet identifier assigned by Flare; `0` if not registered |
42+
| `2-9` | `executorFeeUBA` | Executor fee in the FAsset's smallest unit, big-endian `uint64` |
43+
| `10+` | `userOpData` | `abi.encode(PackedUserOperation)` - the call payload the controller decodes |
4444

4545
The total memo length is `10 + len(abi.encode(userOp))` bytes.
4646
The XRPL caps each memo at `1024` bytes, so large or repetitive call batches must be split across multiple XRPL payments - each of which pays its own FAssets minting fee and executor fee - and a single call whose ABI-encoded calldata alone exceeds the budget cannot be expressed at all without deploying a shim contract on Flare to compress it.

docs/smart-accounts/guides/typescript-viem/02-custom-instruction.mdx

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ sidebar_position: 2
33
slug: custom-instruction-ts
44
title: Custom Instruction
55
authors: [nikerzetic]
6-
description: Sending only keccak256(userOp) on XRPL and delivering the PackedUserOperation bytes via an executor (0xFE flow).
6+
description: Sending custom smart account instructions via XRPL using Viem and an off-chain executor.
77
tags: [quickstart, ethereum, flare-smart-accounts]
88
keywords:
99
[
@@ -20,13 +20,16 @@ unlisted: false
2020
import CodeBlock from "@theme/CodeBlock";
2121
import CustomInstructionsScript from "!!raw-loader!/examples/developer-hub-javascript/smart-accounts/custom-instructions.ts";
2222

23-
The [Custom Instruction overview](/smart-accounts/custom-instruction) introduces the 0xFE memo opcode, which keeps the XRPL memo at a fixed 42 bytes by carrying only `keccak256(PackedUserOperation)` and delivers the user operation bytes to Flare through an off-chain executor.
23+
The [Custom Instruction overview](/smart-accounts/custom-instruction) introduces the `0xFE` memo opcode, which keeps the XRPL memo at a fixed 42 bytes by carrying only `keccak256(PackedUserOperation)` and delivers the user operation bytes to Flare through an off-chain executor.
2424
This guide walks through a TypeScript script that drives all three steps of the protocol with Viem and the [Flare Data Connector (FDC)](/fdc/overview).
2525

2626
A prerequisite for the user operation to be executed is that the personal account holds enough native tokens to cover the call values.
2727
The script forwards a total of `2` C2FLR (Coston2 Flare native tokens), so fund the personal account from the [Flare faucet](https://faucet.flare.network/coston2) before running it.
2828
The [State Lookup guide](/smart-accounts/guides/typescript-viem/state-lookup-ts#personal-account-of-an-xrpl-address) shows how to derive the personal account address.
2929

30+
The XRPL wallet that signs the `Payment` also needs enough XRP to cover the payment amount (net mint plus fees, computed below).
31+
Before sending, the script reads the wallet's XRP balance with the `getXrpBalance` helper and aborts if it is below the required amount.
32+
3033
The full code is available on [GitHub](https://github.com/flare-foundation/flare-viem-starter/blob/main/src/custom-instructions.ts).
3134

3235
## Contracts
@@ -288,6 +291,52 @@ export async function computeDirectMintingPaymentAmountXrp({
288291

289292
The script also mints `10` FXRP as a side effect of the same payment, so the payment amount includes the net mint amount on top of the fees.
290293

294+
## Checking the XRPL Wallet Balance
295+
296+
The signer of the XRPL `Payment` must hold at least `paymentAmountXrp` XRP, otherwise the payment will fail when submitted to the network.
297+
The `getXrpBalance` helper issues an [`account_info`](https://xrpl.org/account_info.html) request against the validated ledger and returns the wallet's balance in XRP.
298+
It reuses the caller's `Client` when one is passed in, and otherwise opens and closes its own connection:
299+
300+
```typescript
301+
export async function getXrpBalance(
302+
xrplAddress: string,
303+
client?: Client,
304+
): Promise<number> {
305+
const ownsClient = client === undefined;
306+
const xrplClient = client ?? new Client(process.env.XRPL_TESTNET_RPC_URL!);
307+
if (!xrplClient.isConnected()) {
308+
await xrplClient.connect();
309+
}
310+
try {
311+
const response = await xrplClient.request({
312+
command: "account_info",
313+
account: xrplAddress,
314+
ledger_index: "validated",
315+
});
316+
return Number(dropsToXrp(response.result.account_data.Balance));
317+
} finally {
318+
if (ownsClient) {
319+
await xrplClient.disconnect();
320+
}
321+
}
322+
}
323+
```
324+
325+
The `main` function calls it right after computing `paymentAmountXrp` and aborts before touching XRPL if the wallet is short.
326+
Failing fast here avoids a half-committed flow where the XRPL submission errors out after the user has already paid gas on Flare for unrelated reads:
327+
328+
```typescript
329+
const xrpBalance = await getXrpBalance(xrplWallet.address, xrplClient);
330+
if (xrpBalance < paymentAmountXrp) {
331+
throw new Error(
332+
`Insufficient XRP balance on ${xrplWallet.address}: have ${xrpBalance} XRP, need ${paymentAmountXrp} XRP`,
333+
);
334+
}
335+
```
336+
337+
This check covers only the payment amount; the XRPL network also requires the account to keep its base reserve, which is not subtracted here.
338+
If the wallet's spendable balance is close to the reserve, the payment can still fail with `tecUNFUNDED_PAYMENT` even though this check passes.
339+
291340
## Step 1: Send the hash memo on XRPL
292341

293342
The user-side helper function computes the memo, sends the XRPL `Payment` to the FAssets direct-minting address, and returns everything the executor needs to drive Step 2:
@@ -503,6 +552,8 @@ Personal account address: 0xFd2f0eb6b9fA4FE5bb1F7B26fEE3c647ed103d9F
503552

504553
Payment amount (XRP, net mint + fees): 10.2
505554

555+
XRPL wallet XRP balance: 1000
556+
506557
[hash-instruction-batch] calls: [
507558
{
508559
target: '0xEE6D54382aA623f4D16e856193f5f8384E487002',

docs/smart-accounts/guides/typescript-viem/03-raw-custom-instruction.mdx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,19 @@ The same cap is also why some calls - those whose calldata alone overruns the me
3838
A prerequisite for the user operation to be executed is that the personal account holds enough native tokens to cover the call values.
3939
The script calls forward a total of `2` C2FLR (Coston2 Flare native tokens), so fund the personal account from the [Flare faucet](https://faucet.flare.network/coston2) before running it.
4040

41+
The XRPL wallet must also cover the **sum** of both payments, `paymentAmountXrp + memoOnlyAmountXrp`, since the raw flow splits the batch into two XRPL `Payment` transactions.
42+
The script reads the wallet's balance with the shared [`getXrpBalance`](/smart-accounts/guides/typescript-viem/custom-instruction-ts#checking-the-xrpl-wallet-balance) helper and aborts before sending either payment if the wallet is short, so neither half of the batch is submitted on partial funding:
43+
44+
```typescript
45+
const totalRequiredXrp = paymentAmountXrp + memoOnlyAmountXrp;
46+
const xrpBalance = await getXrpBalance(xrplWallet.address, xrplClient);
47+
if (xrpBalance < totalRequiredXrp) {
48+
throw new Error(
49+
`Insufficient XRP balance on ${xrplWallet.address}: have ${xrpBalance} XRP, need ${totalRequiredXrp} XRP (both payments)`,
50+
);
51+
}
52+
```
53+
4154
The full code is available on [GitHub](https://github.com/flare-foundation/flare-viem-starter/blob/main/src/custom-instructions-raw.ts).
4255

4356
## Splitting the Call Array Across Two Payments
@@ -261,6 +274,8 @@ Payment amount (XRP, net mint + fees): 10.0011
261274

262275
Memo-only amount (XRP, fees only): 0.0011
263276

277+
XRPL wallet XRP balance: 1000
278+
264279
[checkpoint-and-deposit] calls: [
265280
{
266281
target: '0xEE6D54382aA623f4D16e856193f5f8384E487002',

examples/developer-hub-javascript/smart-accounts/custom-instructions-raw.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
type Call,
1010
} from "./utils/smart-accounts";
1111
import { computeDirectMintingPaymentAmountXrp } from "./utils/fassets";
12+
import { getXrpBalance } from "./utils/xrpl";
1213

1314
// NOTE:(Nik) For this example to work, you first need to faucet C2FLR to your personal account address.
1415
async function main() {
@@ -73,6 +74,15 @@ async function main() {
7374
console.log("Payment amount (XRP, net mint + fees):", paymentAmountXrp, "\n");
7475
console.log("Memo-only amount (XRP, fees only):", memoOnlyAmountXrp, "\n");
7576

77+
const totalRequiredXrp = paymentAmountXrp + memoOnlyAmountXrp;
78+
const xrpBalance = await getXrpBalance(xrplWallet.address, xrplClient);
79+
console.log("XRPL wallet XRP balance:", xrpBalance, "\n");
80+
if (xrpBalance < totalRequiredXrp) {
81+
throw new Error(
82+
`Insufficient XRP balance on ${xrplWallet.address}: have ${xrpBalance} XRP, need ${totalRequiredXrp} XRP (both payments)`,
83+
);
84+
}
85+
7686
await sendMemoFieldInstruction({
7787
label: "checkpoint-and-deposit",
7888
calls: checkpointAndDepositCalls,

examples/developer-hub-javascript/smart-accounts/custom-instructions.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
type Call,
1212
} from "./utils/smart-accounts";
1313
import { computeDirectMintingPaymentAmountXrp } from "./utils/fassets";
14+
import { getXrpBalance } from "./utils/xrpl";
1415

1516
// NOTE:(Nik) For this example to work, you first need to faucet C2FLR to your personal account address.
1617
//
@@ -80,6 +81,14 @@ async function main() {
8081
console.log("Personal account address:", personalAccount, "\n");
8182
console.log("Payment amount (XRP, net mint + fees):", paymentAmountXrp, "\n");
8283

84+
const xrpBalance = await getXrpBalance(xrplWallet.address, xrplClient);
85+
console.log("XRPL wallet XRP balance:", xrpBalance, "\n");
86+
if (xrpBalance < paymentAmountXrp) {
87+
throw new Error(
88+
`Insufficient XRP balance on ${xrplWallet.address}: have ${xrpBalance} XRP, need ${paymentAmountXrp} XRP`,
89+
);
90+
}
91+
8392
// --- 1. USER SIDE -------------------------------------------------------
8493
// Send the XRPL Payment carrying the 32-byte UserOp hash in the memo. The
8594
// full PackedUserOperation bytes (returned as `data`) never go onto XRPL.

0 commit comments

Comments
 (0)