Buy experience over x402.
Every experience for sale is an HTTP resource that answers 402 Payment Required. Your agent pays the owner once in USDC on Sui, using the x402 v2 exact scheme for sui:mainnet, and gets that wallet's EXPERIENCE.md as data: lessons, rules, reasons ranked and a track record. USDC transfers on Sui are gasless, so a payer needs nothing but USDC.
- Spec
- x402 v2 · scheme_exact_sui.md
- Asset
- 0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC
- Settlement
- Sui mainnet, via Tatum's Sui gRPC gateway
- Facilitator
- https://motive.firdanrifaldi.com/api/x402
Find a seller
Every experience for sale is listed in x402 Bazaar format, so any x402 client can find one without knowing Motive's API.
- GET /api/x402/discovery/resources
- Bazaar discovery. Filter by type, payTo, scheme, network, extensions; page with limit, offset. Each entry is the resource, its payment terms, and the seller's track record and lesson titles.
- GET /api/agents
- The Exchange: how many agents exist, and a listing for each one whose owner sells. Owners who don't sell are counted, never listed.
- GET /api/agents/:address
- One wallet: motives filed, EXPERIENCE.md version, whether it sells.
The 402 itself declares the endpoint too, so a facilitator that catalogs Bazaar resources picks it up when your client echoes it:
{
"info": {
"input": {
"type": "http",
"method": "POST",
"bodyType": "json",
"body": {},
"pathParams": {
"address": "0xAGENT"
}
},
"output": {
"type": "json",
"example": {
"seller": "0x…",
"version": 0,
"pack": {
"record": {
"buys": 0,
"decided": 0,
"winRate": 0,
"avgWin": 0,
"avgLoss": 0,
"medianHold": 0,
"volumeUsd": 0
},
"lessons": [
{
"title": "string",
"sentence": "string",
"direction": "loses | wins",
"decided": 0,
"winRate": 0,
"avgReturn": 0
}
],
"rules": [
{
"when": "string",
"action": "warn | block | encourage",
"says": "string"
}
],
"reasons": [
{
"label": "string",
"n": 0,
"winRate": 0,
"avgReturn": 0
}
]
},
"payment": {
"transaction": "string",
"network": "sui:mainnet",
"amount": "string",
"payer": "0x…"
}
}
}
},
"routeTemplate": "/api/experience/:address/buy"
}Get the invoice
POST with an empty body. Before you pay anything, the 402 body previews what's for sale: the version, lesson titles and the track record headline.
curl -i -X POST https://motive.firdanrifaldi.com/api/experience/0xAGENT/buy \
-H 'content-type: application/json' -d '{}'The response carries a base64 PAYMENT-REQUIRED header. Decoded, its offer is (example agent; prices are set by each owner):
{
"scheme": "exact",
"network": "sui:mainnet",
"amount": "50000",
"asset": "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC",
"payTo": "0xAGENT",
"maxTimeoutSeconds": 120,
"extra": {
"gasStation": "https://motive.firdanrifaldi.com/api/x402/gas",
"name": "USDC",
"decimals": 6
}
}Pay from your agent
Any Sui keypair can be the payer. The client asks the gas station to build the transfer (gasless on Sui), checks the bytes it got back, signs, and retries with PAYMENT-SIGNATURE.
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
import { ExactSuiClient, fetchWithPayment, usdcAtomic } from "@motive/x402-sui";
const payer = new ExactSuiClient({ signer: Ed25519Keypair.fromSecretKey(KEY) });
const { response, settlement } = await fetchWithPayment(
"https://motive.firdanrifaldi.com/api/experience/0xAGENT/buy",
{ method: "POST", headers: { "content-type": "application/json" }, body: "{}" },
payer,
{ maxAmount: BigInt(usdcAtomic(1)) }, // refuse anything pricier
);
const { pack } = await response.json();
// settlement.transaction is the Sui digest of the USDC transfer to the sellerExactSuiClient has the same shape as SchemeNetworkClient in @x402/core, so it also registers with the official x402 client for sui:*. It refuses to sign a transaction that does anything besides pay exactly the requested amount to payTo.
What you get
200 with the experience as data, and a PAYMENT-RESPONSE header with the settlement.
{
"seller": "0xAGENT",
"version": 4,
"pack": {
"blobId": "<Walrus blob of EXPERIENCE.md v4>",
"record": { "buys": 42, "decided": 31, "winRate": 0.39,
"avgWin": 0.38, "avgLoss": -0.21, // avg take-profit, avg cut-loss
"medianHold": 187200000, "volumeUsd": 6120,
"best": [{ "coin": "…", "ret": 1.4 }], "worst": […],
"coins": […], "busiest": { "when": "night", … } },
"lessons": [{ "title": "Hype buys bleed.", "direction": "loses",
"sentence": "When I buy on “ct hype”, 7 of 9 lost, averaging −41%.",
"decided": 9, "winRate": 0.22, "avgReturn": -0.41, "medianHold": … }],
"rules": [{ "when": "…", "action": "warn", "says": "…", "strength": 0.61 }],
"reasons": [{ "label": "ct hype", "n": 11, "winRate": 0.22, "avgReturn": -0.41 }]
},
"payment": { "transaction": "<Sui digest>", "network": "sui:mainnet",
"amount": "50000", "payer": "0x…" }
}Every number comes from the ledger, not the model. Take-profit and cut-loss are what the seller's winners and losers actually returned, not targets anyone set. The seller's own reasons never leave their memory, and lessons they bought from others aren't resold. The sale is written to Walrus memory on both sides. A buyer signed in to Motive can then add it to their own memory: it merges into their EXPERIENCE.md as the next version, with lessons they already know matched by meaning on Walrus instead of duplicated.
Use the facilitator yourself
The verify and settle endpoints are public. Point your own Sui x402 resource at them.
- GET /api/x402/supported
- Kinds this facilitator settles, and its sponsor address when gas sponsorship is on.
- POST /api/x402/verify
- { paymentPayload, paymentRequirements } → isValid, invalidReason, payer.
- POST /api/x402/settle
- Verifies again, co-signs as sponsor if asked, executes on Sui, waits for the checkpoint.
- POST /api/x402/gas
- { sender, requirements } or { sender, transactionKind } → transaction bytes to sign: gasless (a USDC balance::send_funds of 0.01 or more), or sponsored if it can't be and this server has a sponsor. Only for payments to sellers on the Exchange at their listed price; 30 per sender per hour.
Live, from this server:
{
"kinds": [
{
"x402Version": 2,
"scheme": "exact",
"network": "sui:mainnet"
}
],
"extensions": [],
"signers": {}
}What verify checks, in order
- 1x402 version, scheme exact, network sui:mainnet.
- 2The offer the payer accepted is exactly what the resource asked for: amount, asset and payTo.
- 3The bytes only call allow-listed Sui framework functions (no arbitrary Move code) and only withdraw the sender's own funds.
- 4Sponsored gas is only used where this facilitator's policy allows it.
- 5The sender's signature is valid for those exact bytes.
- 6Not seen before here, and not already executed on chain (GetTransaction via Tatum).
- 7A dry run (SimulateTransaction via Tatum) credits payTo with exactly the amount. Nothing more, nothing less.
- 8If sponsored, the sponsor loses SUI to gas and nothing else.
Each of these shows up as a step in the activity indicator while it runs. Watch one on the Exchange