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.

Latest attested run · Phase 2 payoutCoston2 + XRPL Testnet
XRPLXRPL
XRPL
XRPL Payment
3CBD8EC9…48C1
one 0xFC memo, 42 bytes, no destination tag
FDCFLARE
FLARE
requestAttestation
0x220592a4…0102
FDC voting round 1,461,499
execMEMOKIT
MEMOKIT
execute
0x2f6faf66…64ad
five transfers and a 0.1 FTestXRP executor fee, one attempt
 s
end to end, 96 s of it waiting for the FDC voting round to close and the Data Availability Layer to serve the proof. That leg is FDC's cadence, not this code.

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

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

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.

01Sign on XRPL

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.

MEMOKIT XRPL Testnet · your wallet
ONE XRPL PAYMENT · ONE MEMO · 42 BYTES · NO DESTINATION TAGXRPL0xFC · 01 · 00…header · 10 bytesMEMOKITkeccak256(payload)commitment · 32 bytesMEMOKITabi.encode(sender, nonce, feeToken, feeAmount, Call[])the payload travels off-chain to whoever submits
02FDC proves the payment

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.

FLARE Flare Data Connector · Coston2
XRPLvalidated ledgerthe PaymentMEMOKITrequest + MICbuilt offline by the SDKFLAREFdcHubrequestAttestationFLAREvoting round · ~90 sFDC providersFLAREDA LayerMerkle proof · no API keyFlare's verifier server is not on the path
03The account executes

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.

MEMOKIT memokit diamond · Coston2
XRPLanyoneexecute(proof, payload)MEMOKITMemoControllerproof · source · hash · nonceFLAREFdcVerificationMerkle checkMEMOKITPersonalAccountCall[] in order · derived from your XRPL addressXRPLvault · DEXtargetsfee paid last, in the moved asset, only if every call succeeded
~150 s
XRPL payment → executed call

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
Checked: FTestXRP totalSupply is identical in the block before and the block containing execute. The share count is not 1:1 with the deposit because the vault has a real exchange rate.

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
Checked: Six Transfer events in the receipt, none from the zero address; totalSupply unchanged across the execute block. Re-read from the chain independently of the script that produced the trace.

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

Live

Phase 1. 5.0 FTestXRP into TESTearnXRP, 152 s.

Multi-recipient payout

Live

Phase 2. Five transfers from one payment, 118 s, 915,998 gas.

Executor fee in the moved asset

Live

The 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

Fork

FXRP is not a Kinetic market. The fork instruction supplies sFLR and borrows USDT0: approve → mint → enterMarkets → borrow → balance assertion.

Swap on SparkDEX V3

Fork

1,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

Tested

0xE1 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

Open

No mainnet deployment and no mainnet transaction.

SDK on npm

Open

Packed and verified from a tarball in an empty project. Not published; the name is provisional.

Self-hosted DA Layer

Open

The 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 →
TypeScript · @memokit/sdknot published · name provisional
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 });
StepCallNotes
buildprepareInstruction(instruction)Returns { payload, commitment, memo }. Keep payload; the memo carries only its hash.
sendsendMemoPayment from @memokit/sdk/xrplOne memo, no destination tag: both enforced.
attestrequestAttestation, waitForProofBuilds the request and its MIC from the ledger record. Never calls Flare's verifier. Polls the DA Layer; no API key.
executesubmit({ 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.

OpcodePayloadLength
0xFDabi.encode(sender, nonce, feeToken, feeAmount, Call[]) inline10 + N
0xFCkeccak256(payload), payload supplied out of band42
0xE0targetTxId: retire a stuck transaction id42
0xE1newNonce: advance past a stuck instruction42
0xE2targetTxId + newFee: override the executor fee amount50

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.md
Solidity tests
TypeScript tests
fork tests

Pause 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.

memokit

XRPL-originated calls on assets a Flare account already holds. A complement to Flare Smart Accounts.

Status