Raw Custom Instruction
The raw custom instruction (memo opcode 0xFF) is a single-actor variant of the Custom Instruction: instead of committing to a keccak256(userOp) hash and relying on an executor to deliver the bytes, the XRPL Payment memo carries the entire ABI-encoded PackedUserOperation inline immediately after a 10-byte header.
The user signs the XRPL Payment, ships the payload on XRPL, and the smart account replays the user operation on Flare with no further off-chain data.
The recommended path is the Custom Instruction - it lifts the XRPL memo size cap and is what the rest of the smart-accounts tooling defaults to. Reach for the raw variant when you do not want to operate or coordinate with an executor and your call batch fits inside the XRPL memo cap. The comparison guide breaks down when each is appropriate.
This page covers only what differs from the Custom Instruction.
For the PackedUserOperation construction (sender, nonce, callData), the Call struct, and how to build callData in TypeScript, refer to the Custom Instruction page.
XRPL transactions targeting smart accounts must not use a destination tag. A destination tag forces FAssets direct minting to credit the tag-holder, which would let an unrelated party front-run the user operation.
Memo Layout
The XRPL memo carries a 10-byte instruction header followed by the ABI-encoded PackedUserOperation:
| Bytes | Field | Meaning |
|---|---|---|
0 | instructionId | 0xFF - raw custom instruction |
1 | walletId | One-byte wallet identifier assigned by Flare; 0 if not registered |
2-9 | executorFeeUBA | Executor fee in the FAsset's smallest unit, big-endian uint64 |
10+ | userOpData | abi.encode(PackedUserOperation) - the call payload the controller decodes |
The total memo length is 10 + len(abi.encode(userOp)) bytes.
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.
Dispatch Flow
A single XRPL payment drives the whole flow: the FAssets AssetManager mints FXRP into the smart account, calls MasterAccountController.handleMintedFAssets, and the controller decodes the PackedUserOperation directly from the memo bytes (no _data parameter, no hash check) before dispatching executeUserOp on the personal account.
There is no executor split: any indexer that observes the XRPL payment can submit the FDC proof through executeDirectMinting(proof), and the MasterAccountController validates the same sender, nonce, and callData fields as for the custom instruction.
Replay Protection
Each personal account maintains a monotonically increasing nonce, accessible via getNonce.
A successful executeUserOp increments the nonce and emits UserOperationExecuted, so the same PackedUserOperation cannot be re-executed.
The XRPL transaction ID is also recorded in the controller to prevent the same payment from being submitted twice.
Failure Handling
The whole pipeline is atomic with respect to the user operation:
- If
senderdoes not match the personal account, the call reverts withInvalidSender. - If
nonceis not the expected value, it reverts withInvalidNonce. - If the memo body has the wrong length for its instruction ID, it reverts with
InvalidMemoData; an unrecognized instruction byte reverts withInvalidInstructionId. - If any inner call reverts, the personal account surfaces it as
CallFailedand the entire user operation reverts.
Because the FXRP transfer is performed before the memo is decoded, the mint succeeds even if the user operation reverts - see DirectMintingExecuted.
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.
Next Steps
- Walk through a Viem implementation in the Raw Custom Instruction TypeScript guide.
- Read the Custom Instruction for the recommended hash-commitment variant.
- Compare the two flows in the Custom Instruction Comparison.
- Dig into
IMasterAccountControllerin the reference.