A complement to Flare Smart Accounts · Live on Coston2 against XRPL Testnet
Sign once on XRPL.
Your Flare account executes.
memokit executes XRPL-originated calls on assets a Flare account already holds. One XRPL Payment carries a 42-byte commitment to what the account should do; the Flare Data Connector attests the payment, and the account runs exactly the calls you committed to. No FAssets mint in the path, no Flare-assigned destination tag, no wallet registration.
The gap
FXRP already on Flare cannot be managed from XRPL without minting more.
Flare Smart Accounts reach arbitrary calls through memo opcodes 0xFF and 0xFE, but only as a side effect of executeDirectMintingWithData. Every instruction mints FXRP and inherits FAssets' direct-minting limits. That is the right rail for bringing XRP onto Flare. It leaves one balance unreachable: the one that is already there.
Flare Smart Accounts · the direct-mint rail
- The instruction rides a direct mint. The account receives fresh FXRP, then acts.
- Routed by an XRPL destination tag that Flare assigns.
- The executor is paid out of the freshly minted fAsset.
memokit · the existing-balance rail
- The instruction acts on what the account already holds. FTestXRP totalSupply is identical in the block before and the block containing execute.
- Routed by the receiving XRPL address. The account address is derived from your XRPL address, so there is nothing to register.
- The executor is paid from what the account holds, in the asset being moved.
Same 10-byte memo header, one byte different. The controller and accounts facets are built to be cut into Flare's own diamond, and every external selector is diffed against Flare's live MasterAccountController on Coston2 and mainnet.
How it works
Sign on XRPL. FDC proves the payment. The account executes.
About 150 s from XRPL payment to executed call. Around 90% of it is one leg: waiting for the FDC voting round to close and the DA Layer to serve the proof.
Your wallet sends one XRPL Payment to a memokit receiving address. Its memo is 42 bytes: Flare's 10-byte header with opcode 0xFC, then keccak256 of the instruction. The instruction itself (sender, nonce, fee token, fee amount and the list of calls) travels off-chain to whoever submits.
The SDK builds the attestation request and its message integrity code from the XRPL ledger record alone, submits it to FdcHub, and polls the public Data Availability Layer for the Merkle proof. Flare's verifier server is not on the path and no API key is needed. This is the latency: waiting for a 90 s voting round to close.
Anyone can call execute with the proof and the preimage. The controller checks the proof, the source address, that the memo equals the hash of the payload, and the nonce. Your PersonalAccount, a beacon proxy at an address derived from your XRPL address, runs the calls in order. The executor's fee is paid last, in the asset the instruction moved, only if every call succeeded.
Measured end to end: 152 s and 162 s in Phase 1, 118 s in Phase 2. About 90% of it is one leg, waiting for the FDC voting round to close and the DA Layer to serve the proof. Nothing here can shorten that leg, so an instruction that depends on a price commits to a deadline and a minimum output inside its own hashed calldata. The SDK's default deadline is 900 s.
Live proof · Coston2 + XRPL Testnet
Two attested traces. Every link resolves.
Both runs were executed against live infrastructure and recorded in the repository under fixtures/measurements/. The explorer links are the primary evidence; the JSON traces carry the balances and the latency breakdown.
Phase 1
A vault deposit, no mint
5.0 FTestXRP into a live Coston2 ERC-4626 vault, out of a balance the account already held.
- XRPL Payment
- 11A56DB8…53A6
- requestAttestation
- 0xded36a56…272e
- execute
- 0x69f5259f…1978
- result
- 10.0 → 5.0 FTestXRP · 0 → 4.994505 TESTearnXRP shares
- latency
- 152 s
Phase 2
One payment, five transfers
Five FTestXRP transfers to five Flare addresses, and 0.1 FTestXRP to the executor, from one XRPL Payment.
- memokit diamond
- 0x98882776…9E36
- account
- 0x8F1eD3f5…43b1
- XRPL Payment
- 3CBD8EC9…48C1
- requestAttestation
- 0x220592a4…0102
- execute
- 0x2f6faf66…64ad
- result
- +1.2, +1.1, +1.0, +0.9, +0.7 to five recipients · +0.1 to the executor · account 5.0 → 0
- latency
- 118 s, one execute attempt
Phase 1 ran on the Phase 1 diamond. Phase 2 changed the payload format, so it is a new deployment.
Status
What works today, and how each result was obtained.
- Liveattested by FDC, executed on Coston2 against XRPL Testnet
- ForkFoundry fork of Flare mainnet at block 70,267,728; FDC verification simulated; real target contracts
- Testedunit and fork tests, no live trace
- Opennot done
ERC-4626 vault deposit
LivePhase 1. 5.0 FTestXRP into TESTearnXRP, 152 s.
Multi-recipient payout
LivePhase 2. Five transfers from one payment, 118 s, 915,998 gas.
Executor fee in the moved asset
LiveThe payout paid its executor 0.1 FTestXRP out of the 5.0 it was paying out. Fee token and amount are inside the committed payload.
Lending on Kinetic
ForkFXRP is not a Kinetic market. The fork instruction supplies sFLR and borrows USDT0: approve → mint → enterMarkets → borrow → balance assertion.
Swap on SparkDEX V3
Fork1,000 FXRP → 1,408.788917 USDT0 through the 0.05% pool. When the price moves past the committed minimum the whole instruction reverts and nothing is consumed.
Recovery opcodes
Tested0xE1 and a same-nonce re-issue unstick the queue; 0xE0 retires a memo that cannot execute or parse. Exercised on the fork after a real price-driven failure.
Mainnet deployment
OpenNo mainnet deployment and no mainnet transaction.
SDK on npm
OpenPacked and verified from a tarball in an empty project. Not published; the name is provisional.
Self-hosted DA Layer
OpenThe public endpoint allows about 20 requests a minute, enough for one executor and not for many.
SDK
Five calls from XRPL payment to executed instruction.
sdk/ is a TypeScript library over ethers v6. Build the instruction and its memo, send the payment, request attestation, wait for the proof, submit. A full vault deposit is 29 lines.
Read the full quickstart in the memokit README →1const { payload, memo } = prepareInstruction({ sender: account, nonce, feeToken, feeAmount, calls });
2const sent = await sendMemoPayment({ network: COSTON2, wallet, destination, drops: "1000000", memo });
3const request = await requestAttestation({ signer, xrplHash: sent.hash, network: COSTON2 });
4const { proof } = await waitForProof({ request, network: COSTON2 });
5await submit({ signer, controller, proof, payload });| Step | Call | Notes |
|---|---|---|
| build | prepareInstruction(instruction) | Returns { payload, commitment, memo }. Keep payload; the memo carries only its hash. |
| send | sendMemoPayment from @memokit/sdk/xrpl | One memo, no destination tag: both enforced. |
| attest | requestAttestation, waitForProof | Builds the request and its MIC from the ledger record. Never calls Flare's verifier. Polls the DA Layer; no API key. |
| execute | submit({ signer, controller, proof, payload }) | Callable by anyone. The caller earns the instruction's fee and cannot change what runs. |
Wire format
Header byte-identical to Flare's, so an integrated wallet changes one byte. 0xE0–0xE2 reuse Flare's opcodes at their original values and semantics; the receiving XRPL address disambiguates which protocol a memo is addressed to.
| Opcode | Payload | Length |
|---|---|---|
| 0xFD | abi.encode(sender, nonce, feeToken, feeAmount, Call[]) inline | 10 + N |
| 0xFC | keccak256(payload), payload supplied out of band | 42 |
| 0xE0 | targetTxId: retire a stuck transaction id | 42 |
| 0xE1 | newNonce: advance past a stuck instruction | 42 |
| 0xE2 | targetTxId + newFee: override the executor fee amount | 50 |
Safety machinery, kept from Flare
Flare's safety machinery, kept.
memokit keeps Flare's controls rather than shedding them, and adds the post-conditions that permissionless execution needs. Every external selector is diffed against Flare's live controller on Coston2 and mainnet.
Read PHASE2.mdPause and registry
A pause facet and an owner-managed registry of receiving XRPL addresses, as in Flare's controller.
Timelock
Economic parameters change behind a timelock, with the same timelocked/immediate split Flare uses.
Post-conditions
A swap's minimum output and deadline are call arguments, so they sit inside the hash and an executor cannot loosen them. A Compound-style market reports some failures as return values; end such an instruction with a balance assertion and the whole execution unwinds.
Recovery opcodes
0xE0 retires a transaction id. 0xE1 advances past a stuck nonce. 0xE2 overrides the executor fee amount. Flare's opcodes, at their original values. A failed instruction is never consumed; its proof works again once the cause clears.
Fee only on success
The executor fee is inside the committed payload and paid after every call succeeds, in the same transaction. A failing call unwinds it. Execution is permissionless, so a zero fee is the lockout escape hatch: an owner whose account cannot pay submits it themselves.
Collision-checked against Flare
Every external selector is diffed against Flare's live MasterAccountController on Coston2 and mainnet, so the facets can be cut into Flare's own diamond without a silent wrong answer.
XRPL-originated calls on assets a Flare account already holds. A complement to Flare Smart Accounts.
Source
GitHubREADMEPHASE1.md · walking skeletonPHASE2.md · integrations and the SDKphase0-report.md · the investigationStatus