Skip to content

Commit e96d049

Browse files
fix(docs): smart accounts updates (#1367)
Co-authored-by: Nik Erzetič | AFLabs | Flare <Nik.Erzetic@flare.network>
1 parent 3e7ff6b commit e96d049

11 files changed

Lines changed: 1191 additions & 838 deletions

File tree

docs/smart-accounts/1-overview.mdx

Lines changed: 102 additions & 165 deletions
Large diffs are not rendered by default.

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

Lines changed: 78 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -19,126 +19,95 @@ keywords:
1919
import ThemedImage from "@theme/ThemedImage";
2020
import useBaseUrl from "@docusaurus/useBaseUrl";
2121

22-
Flare Smart Accounts allow users to execute custom function calls on the Flare chain through instructions on XRPL.
23-
The process expands on the workflow for other actions by including an additional step at the beginning.
24-
25-
In order for the `MasterAccountController` contract to be able to give a custom instruction to a personal account, the custom action must first be registered with the said contract.
26-
The custom instruction is stored in a mapper, with its 30-byte hash as a key.
27-
That hash is then sent as the payment reference, along with the byte representation of the hexadecimal number `ff` (decimal `255`) in the first byte and the `walletId` in the second byte.
28-
The `walletId` is a Flare-designated value, used by the operator for wallet identification.
29-
30-
<div
31-
style={{
32-
width: "90%",
33-
margin: "0 auto",
34-
display: "flex",
35-
justifyContent: "center",
36-
}}
37-
>
38-
<ThemedImage
39-
alt="Breakdown of bytes in payment reference for the custom action"
40-
style={{ width: "100%" }}
41-
sources={{
42-
light: useBaseUrl(
43-
"/img/docs/smart-accounts/bytes-custom-instruction-light.png",
44-
),
45-
dark: useBaseUrl(
46-
"/img/docs/smart-accounts/bytes-custom-instruction-dark.png",
47-
),
48-
}}
49-
/>
50-
</div>
51-
52-
## The expanded workflow
53-
54-
We expand the workflow described in the [Flare Smart Accounts overview](/smart-accounts/overview) with an additional step before the first.
55-
56-
0. A custom instruction is registered with the `MasterAccountController` contract.
57-
1. The XRPL user sends instructions as a `Payment` transaction to a specific XRPL address, with instructions encoded as the payment reference in the memo field.
58-
2. The operator interacts with the [Flare Data Connector](/fdc/overview) to procure a `Payment` proof.
59-
It then calls the `executeTransaction` function on the `MasterAccountController` contract, with the `Payment` proof and the XRPL address that made the transaction.
60-
3. The XRPL user's smart account performs the actions given in the instructions.
61-
62-
## Custom Instructions
63-
64-
Custom instructions are an array of the `CustomInstructions.CustomCall` Solidity struct.
65-
The struct contains three fields:
66-
67-
- `targetContract`: the address of the smart contract that will execute the custom function
68-
- `value`: the amount of FLR paid to the contract
69-
- `data`: transaction calldata, which includes a function selector and values of the function's arguments
70-
71-
Each of the custom instructions in the array will be performed in order.
72-
A call to the `targetContract` address is made, with the specified `value` and the calldata `data`.
73-
74-
In Solidity, we can obtain the calldata by doing the following:
75-
76-
```Solidity
77-
abi.encodeWithSignature("<functionName>(<type1>,<type2>,...,<typeN>)", [<value1>, <value2>, ..., <valueN>]);
78-
```
79-
80-
where `<functionName>` is the name of the function that we want to call, `<type1>`, `<type2>`, . . . , `<typeN>` are its argument types, and `<value1>`, `<value2>`, . . . , `<valueN>` their values.
22+
Flare Smart Accounts let an XRPL user execute arbitrary contract calls on Flare trough the [`Payment`](https://xrpl.org/docs/references/protocol/transactions/types/payment) transaction on the XRPL blockchain.
23+
Each personal account exposes an [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) style `executeUserOp` entry point that the [`MasterAccountController`](/smart-accounts/reference/IMasterAccountController) invokes when it processes a custom instruction memo.
8124

82-
Only function calls with specific parameter values included can be registered.
83-
That means that a new custom instruction needs to be registered for each unique action (though this can be done just seconds in advance).
84-
It is also the reason why special FAsset actions have their own IDs, instead of defaulting to the custom call - it allows us to also specify certain parameters within the instructions on XRPL.
25+
Custom instructions carry the full call payload in the XRPL **memo** field.
26+
The user signs a `PackedUserOperation`, ships it on XRPL, and the smart account replays it on the Flare blockchain.
8527

86-
:::warning
87-
Encoding calldata by hand is error prone.
88-
It is recommended to use established libraries, or an [online tool](https://abi.hashex.org/) (if you want to quickly check something).
28+
:::warning No destination tags
29+
XRPL transactions targeting smart accounts must not use a destination tag.
30+
A destination tag forces [FAssets direct minting](/fassets/direct-minting) to credit the tag-holder, which would let an unrelated party front-run the user operation.
8931
:::
9032

91-
## Call hash
33+
## User operation payload
9234

93-
To produce the custom instructions calldata, we first ABI encode the array of the `CustomInstructions.CustomCall` struct.
94-
We then take the `keccak256` hash of that value, and drop the first two bytes (`(1 << 240) - 1` shifts the number binary number `1` left `30*8` times, and replaces it with `0` and all the `0`s that follow it with `1`; essentially, we create a mask of length `30*8` of only `1`s).
95-
That is the call hash that is provided as the payment reference for the custom action, and the ID under which the custom instructions are stored in the `MasterAccountController` contract.
35+
The custom instruction payload has two layers: the outer EIP-4337 [`PackedUserOperation`](https://eips.ethereum.org/EIPS/eip-4337#useroperation) carried in the XRPL memo, and the inner [`executeUserOp(Call[])`](/smart-accounts/reference/IPersonalAccount#executeuserop) that the personal account runs once the controller dispatches it.
9636

97-
```Solidity
98-
return bytes32(uint256(keccak256(abi.encode(_customInstruction))) & ((1 << 240) - 1));
99-
```
37+
Only three fields from the [`PackedUserOperation`](https://eips.ethereum.org/EIPS/eip-4337#useroperation) struct are required for Flare Smart Accounts:
38+
39+
- `sender` **must** equal the address of the personal account derived from the XRPL sender.
40+
Use [`getPersonalAccount`](/smart-accounts/reference/IMasterAccountController#getpersonalaccount) to look it up — the address is deterministic, so you can fetch it before the account is even deployed.
41+
- `nonce` **must** equal the personal account's current nonce returned by [`getNonce`](/smart-accounts/reference/IMasterAccountController#getnonce).
42+
The nonce auto-increments on every successful execution to prevent replay.
43+
- `callData` is the calldata that the controller invokes on the personal account.
44+
In practice, this is `abi.encodeCall(IPersonalAccount.executeUserOp, (calls))` — anything else either reverts or is rejected by the personal account's `onlyController` modifier.
45+
46+
The remaining fields are not validated on-chain and can be left empty.
47+
Authorization comes from the XRPL signature on the `Payment` XRPL payment transaction itself: only the XRPL key for `sender`'s `xrplOwner` can deliver the memo.
48+
If the personal account has pinned an executor via [`getExecutor`](/smart-accounts/reference/IMasterAccountController#getexecutor), only that executor is permitted to relay the mint.
10049

101-
The call hash can also be obtained through the `encodeCustomInstruction` helper function of the `MasterAccountController` contract.
50+
### `executeUserOp` and the `Call` struct
10251

103-
```Solidity
104-
function encodeCustomInstruction(
105-
CustomInstructions.CustomCall[] calldata _customInstruction
106-
) public pure returns (bytes32) {
107-
return CustomInstructions.encodeCustomInstruction(_customInstruction);
108-
}
52+
The personal account's [`executeUserOp(Call[])`](/smart-accounts/reference/IPersonalAccount#executeuserop) runs each entry in the `Call[]` in order, forwarding it with its supplied `value` and `data`:
53+
54+
```solidity
55+
struct Call {
56+
address target;
57+
uint256 value;
58+
bytes data;
59+
}
60+
61+
function executeUserOp(Call[] calldata _calls) external payable;
10962
```
11063

111-
Behind the scenes, the `MasterAccountController` contract calls the `encodeCustomInstruction` function of the `CustomInstructions` library.
64+
Each call is dispatched with the personal account as `msg.sender`.
65+
If any call reverts, the whole user operation reverts with [`CallFailed`](/smart-accounts/reference/IPersonalAccount#callfailed) — partial execution is not possible.
66+
67+
The `executeUserOp` function is `payable`, so the user operation can forward native tokens (e.g. FLR) alongside the calls.
68+
To fund the personal account, send FLR to the address using the [Flare faucet](https://faucet.flare.network/).
69+
70+
### Building `callData` in TypeScript
11271

113-
```Solidity
114-
function encodeCustomInstruction(
115-
CustomCall[] calldata _customInstruction
116-
)
117-
internal pure
118-
returns (bytes32)
119-
{
120-
return bytes32(uint256(keccak256(abi.encode(_customInstruction))) & ((1 << 240) - 1));
121-
}
72+
You can build the `callData` in TypeScript using the [`encodeFunctionData`](https://viem.sh/docs/contract/encodeFunctionData#encodefunctiondata) function from the `viem` library:
12273

74+
```typescript
75+
import { encodeFunctionData } from "viem";
76+
77+
const calls = [
78+
{
79+
target: counterAddress,
80+
value: 0n,
81+
data: encodeFunctionData({
82+
abi: counterAbi,
83+
functionName: "increment",
84+
args: [],
85+
}),
86+
},
87+
];
88+
89+
const callData = encodeFunctionData({
90+
abi: personalAccountAbi,
91+
functionName: "executeUserOp",
92+
args: [calls],
93+
});
12394
```
12495

125-
## 0. Register custom instructions
126-
127-
We register a custom instruction by calling the `registerCustomInstruction` function on the `MasterAccountController` contract.
128-
The `CustomInstructions.CustomCall` array is provided as an argument.
129-
It is encoded as described above and stored in a `CustomInstructions` mapping.
130-
131-
To obtain the instruction that can be sent as the memo of an XRPL Payment transaction, we take the call hash produced by the `encodeCustomInstruction` function and modify it the following way.
132-
First, we remove the initial two bytes from this hash.
133-
Next, we prepend the hexadecimal value `ff` followed by the `walletId`.
134-
This is the encoded custom instruction.
135-
136-
<ThemedImage
137-
alt="Flare smart accounts custom instruction workflow"
138-
sources={{
139-
light: useBaseUrl(
140-
"img/docs/smart-accounts/fsa-developer-workflow-light.png",
141-
),
142-
dark: useBaseUrl("img/docs/smart-accounts/fsa-developer-workflow-dark.png"),
143-
}}
144-
/>
96+
The encoded `callData` becomes the `callData` field of the `PackedUserOperation` struct placed in the XRPL payment memo field.
97+
98+
## Replay protection
99+
100+
Each personal account maintains a monotonically increasing nonce, accessible via [`getNonce`](/smart-accounts/reference/IMasterAccountController#getnonce).
101+
A successful `executeUserOp` increments the nonce and emits [`UserOperationExecuted`](/smart-accounts/reference/IMasterAccountController#useroperationexecuted), so the same `PackedUserOperation` cannot be re-executed.
102+
103+
## Failure Handling
104+
105+
The whole pipeline is atomic with respect to the user operation:
106+
107+
- If `sender` does not match the personal account, the call reverts with [`InvalidSender`](/smart-accounts/reference/IMasterAccountController#invalidsender).
108+
- If `nonce` is not the expected value, it reverts with [`InvalidNonce`](/smart-accounts/reference/IMasterAccountController#invalidnonce).
109+
- If the memo body has the wrong length for its instruction ID, it reverts with [`InvalidMemoData`](/smart-accounts/reference/IMasterAccountController#invalidmemodata); an unrecognized instruction byte reverts with [`InvalidInstructionId`](/smart-accounts/reference/IMasterAccountController#invalidinstructionid).
110+
- If any inner call reverts, the personal account surfaces it as [`CallFailed`](/smart-accounts/reference/IPersonalAccount#callfailed) and the entire user operation reverts.
111+
112+
Because the FXRP transfer is performed before the memo is decoded, **the mint succeeds even if the user operation reverts** — see [`DirectMintingExecuted`](/smart-accounts/reference/IMasterAccountController#directmintingexecuted).
113+
The freshly minted FXRP remains in the personal account, and the user can either re-submit a fixed user operation or transfer the FXRP to another address via standard [FAssets instructions](/smart-accounts/fasset-instructions).

docs/smart-accounts/guides/cli/01-introduction.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import ThemedImage from "@theme/ThemedImage";
2121
import useBaseUrl from "@docusaurus/useBaseUrl";
2222

2323
The [Flare Smart Accounts CLI](https://github.com/flare-foundation/smart-accounts-cli) is a tool written in Python that streamlines the Flare Smart Accounts process by properly structuring and sending XRPL transactions/instructions.
24-
It has commands corresponding to each Flare Smart Accounts action ([table of instructions](/smart-accounts/overview#1-instructions-on-xrpl)), as well as other useful debugging features.
24+
It has commands corresponding to each Flare Smart Accounts action ([table of instructions](/smart-accounts/overview#instructions-on-xrpl)), as well as other useful debugging features.
2525

2626
:::note
2727
The CLI executes **only** the transaction on XRPL.

0 commit comments

Comments
 (0)