The state of each piece
| Piece | State |
|---|---|
| Chain probe - resolves FXRP, verifies the EIP-712 domain | live Coston2 |
ScripFacilitator.sol deployed + source-verified |
live Coston2 |
settle() end to end, real FXRP, real signatures |
verified on a fork |
| Attack coverage - payee substitution, replay, permit grief | verified on a fork |
| Gasless settlement against the deployed facilitator | live Coston2 |
| FTSO USD pricing - invoices priced at the live XRP/USD feed | live Coston2 |
| x402 facilitator service + Express middleware | hosted and public |
| An agent paying a 402 end to end, holding no gas token | pays the hosted facilitator |
| MCP server - an assistant prices, quotes and pays in conversation | runs locally, settles on live Coston2 |
| Payee allowlist - the relayer spends gas only for payees it was configured to serve | enforced on the hosted facilitator |
| A spent invoice is answered with a fresh quote, and the client re-signs | working, live Coston2 |
| Invoice ids namespaced per payer - a stranger cannot burn a quote | live Coston2 |
| Zero-amount settlements refused | live Coston2 |
Where it runs
The facilitator is public, at
scrip-production.up.railway.app.
Nothing needs installing to see it work:
curl https://scrip-production.up.railway.app/health
curl https://scrip-production.up.railway.app/price
curl -i https://scrip-production.up.railway.app/api/haiku
The last one answers 402 Payment Required with a price in
USD, the FXRP it converts to, the FTSO rate it was derived from, and a
single-use invoice id - priced at the moment you ask, and payable for
ten minutes.
Open that
endpoint in a browser and you get the raw JSON body, which looks
like an error page and is not one. A 402 is the server
quoting a price rather than refusing to answer: the terms are the
response. It reads better through curl -i, where the
status line is visible above the body:
HTTP/2 402
content-type: application/json
{
"x402Version": 1,
"error": "payment required: $0.25 in FXRP (0.247794 FTestXRP at $1.008904/XRP)",
"accepts": [{
"scheme": "exact-permit2612",
"maxAmountRequired": "247794",
"priceUsd": "0.25",
"rate": { "feedId": "0x0158...", "value": "1008904", "decimals": 6 },
"payTo": "0xaA34e14a0e0B2fdD8Ad10F06bC0907fA0b1D02Bd",
"asset": "0x0b6A3645c240605887a5532109323A3E12273dc7",
"invoiceId": "0x98913e13...",
"deadline": "1786655791"
}]
}
Everything needed to pay is in there, and the rate is
included so a client can recompute the FXRP amount from the USD price
and refuse if the two disagree. The agent in this repository does
exactly that before it signs anything.
To pay one, clone the repository and point the agent at the hosted endpoint:
npm run agent -- https://scrip-production.up.railway.app/api/haiku
The payments that actually happened
Transactions on live Coston2, all paid for by the relayer, all from a payer whose C2FLR balance was zero before and after. These are the receipts behind every claim on this site.
| What | Result |
|---|---|
| Direct settlement, 0.5 FXRP invoice |
0x4bea1e37
· block 33961376 · 222,790 gas
|
| Agent paying a $0.25 x402 invoice |
0x975a5ac6
· block 33961951 · 205,702 gas
|
| Direct settlement, 0.1 FXRP invoice |
0x1e512e96
· block 33962211 · 205,698 gas
|
An assistant paying $0.25 through the MCP pay tool |
0xeeef2e1d
· block 34018086 · 205,698 gas
|
| An agent paying the hosted facilitator |
0xe3cb0532
· block 34022538 · 205,722 gas
|
| A payment under the current contract, invoice namespaced per payer |
0x10ff52e5
· block 34035592 · 206,561 gas
|
The first payment costs about 17,000 more gas than the ones after it. That is not noise: it is the payer's EIP-2612 nonce slot going from zero to one, which is a cold storage write the first time and a warm one forever after. The steady-state figure is the one quoted on the home page.
402 payment required
price $0.25
amount 0.244356 FTestXRP
XRP/USD $1.023099
PASS quoted amount matches the quoted rate
PASS intent digest agrees with the facilitator
signed two messages, sent no transaction
200 OK
delivered 0.244356 FTestXRP
gas paid by 0xaA34e14a0e0B2fdD8Ad10F06bC0907fA0b1D02Bd
payer C2FLR 0 -> 0
PASS the agent paid for an API call holding no gas token at all
What has been verified, and how
npm run test:fork forks live Coston2, deploys the
facilitator onto the fork, and settles a real invoice against the
real FXRP contract - the deployed vendored FAsset,
resolved through AssetManagerFXRP.fAsset(), not a mock.
Each property below is asserted, and the attacks are run as attacks.
- The payment is gasless. The payer's native balance is forced to zero before signing and checked to still be zero after. The receipt shows the relayer's address paid.
-
Signatures verify against FXRP's vendored
permit- not stock OpenZeppelin, and not a mock. -
A relayer cannot redirect the payment. A third
account calls
settle()substituting itself as payee and is rejected withIntentNotSignedByPayer. -
An invoice settles once. Replaying the same
signatures reverts with
AlreadySettled. -
A front-runner cannot grief a payment. A third
account submits the permit straight to FXRP, burning the nonce.
settle()still delivers in full.
attack: relayer substitutes itself as payee
PASS payee substitution rejected with IntentNotSignedByPayer
settle
PASS delivered == requested (1500000 == 1500000)
PASS payer native balance still 0 - the payment was gasless
PASS relayer paid the gas
replay: same invoice again
PASS replay rejected with AlreadySettled
front-runner replays the permit directly to FXRP
PASS front-runner consumed the nonce (3 -> 4)
PASS settle() still succeeded after the permit was front-run
The token is the real deployed FAsset, and the signatures are real. The payer's balance is written directly into the token's balance mapping, because minting FAssets legitimately requires an XRP payment proof that no test can produce - so the fork exercises the contract logic and the signing path against the genuine token. The funding route itself is covered live instead: the payments above are on Coston2, from a payer holding FXRP it was actually sent.
What comes next
Each of these is a known piece of work with a known shape, listed in the order it would be done.
-
Concurrent payments from one payer
EIP-2612 nonces are sequential, so an agent paying two endpoints at once has the second permit fail on a consumed nonce. EIP-3009's random nonces avoid this and FXRP does not implement EIP-3009, so payments are serialised per payer today. The MCP server pays for one resource at a time for the same reason - paying for two means solving this, not calling the tool twice.
-
Mainnet FXRP
Everything here is Coston2. The signing path is unchanged on mainnet, but the facilitator would need redeploying and auditing against the real asset.