Install
openclaw skills install @agora0x/mentatMENTAT (formerly FLOOR OTC), the agent-native OTC venue. Escrow settlement on Base (atomic, no admin keys), CoW Protocol relay (agent-signed, MEV-protected), and an XMR route via Wagyu (XMR/USDC both directions, three legs, one proof per leg). Every quote and order response carries settlement_path, route_legs, and proofs[].
openclaw skills install @agora0x/mentatDisciplined intelligence. MENTAT settles token swaps for agents three ways, and every fill carries whatever its chain can prove.
| settlement_path | What settles | Trust model | Proofs |
|---|---|---|---|
escrow | 1:1 swap through the FloorEscrow contract on Base. Both sides deposit, one atomic settlement, 25 bps fee. | Trustless: no owner, no pause, no upgrade. | escrow_create_tx, then deposits and settlement on the contract page. |
cow_relay | The calling agent signs an EIP-712 order; MENTAT relays it to CoW Protocol's batch auction on Base. 25 bps partner fee inside the signed appData. | Non-custodial: MENTAT never holds funds. | cow_order_uid, then one settlement_tx per batch that filled it. |
wagyu_route | XMR in or out. XMR is routed through XMR1 on Hyperliquid by Wagyu, the settlement counterparty. MENTAT never holds XMR. 25 bps integrator fee applied inside Wagyu's quoted amount. | Not trustless. You rely on Wagyu to deliver; the route gives you a proof per leg instead. | deposit_tx (Monero hash on XMR legs), one fill_tx per Hyperliquid fill hash, payout_tx. |
ERC-8004 Agent #31596 on Base Mainnet. Contact: desk@mentat.market.
Every quote and order response carries:
settlement_path: escrow, cow_relay, wagyu_route, or quote_only (a price with no executable path).route_legs[]: the legs of that path, each { leg, name, chain, chain_id, proof_kind, proofs } where proofs says how many proofs the leg can carry (one, one or many, zero, one, or many).proofs[]: { chain, kind, hash, explorer_url }, one entry per hash the chain has exposed so far. Empty on a quote. Nothing is inferred: a leg without a hash yet has no entry.Amounts on the XMR route are decimal strings. Never send a float.
curl -s "https://floor-a2a-production.up.railway.app/api/quote?from=USDC&to=WETH&amount=1000" | jq
curl -s -X POST https://floor-a2a-production.up.railway.app/a2a \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tasks/send","params":{"skill_id":"get_quote","arguments":{"from_token":"USDC","to_token":"WETH","amount":1000}},"id":1}'
curl -s "https://floor-a2a-production.up.railway.app/api/prices" | jq
wagyu_route)Pairs: XMR ↔ USDC on Base, both directions. Minimums are direction-aware: sell XMR from $25, buy XMR from $500 (below $500 a buy loses most of its value to Hyperliquid size rounding). Maximum $5,000 per order. MENTAT earns a 25 bps integrator fee on every order, applied inside Wagyu's quoted amount: it is already included in to_amount, reported by Wagyu as integrator_fee, and counted in effective_cost_bps, the all-in number.
curl -s -X POST https://floor-a2a-production.up.railway.app/a2a \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tasks/send","params":{"skill_id":"get_quote","arguments":{"from_token":"XMR","to_token":"USDC","amount":"0.1","to_address":"0xYourBaseAddress"}},"id":1}'
Response (abridged):
{
"quote": {
"venue": "wagyu",
"settlement_path": "wagyu_route",
"counterparty": "Wagyu (wagyu.xyz)",
"custody": "MENTAT never holds XMR. Wagyu is the settlement counterparty; XMR in and out is routed through XMR1 on Hyperliquid. Not trustless.",
"from_token": "XMR", "to_token": "USDC",
"from_chain": "monero", "to_chain": "base",
"from_amount": "0.1", "to_amount": "50.409916",
"from_amount_atomic": "100000000000", "to_amount_atomic": "50409916",
"from_amount_usd": "51.45", "effective_cost_bps": 169,
"mentat_fee_bps": 25, "mentat_fee_note": "Included in to_amount; reported by Wagyu as integrator_fee; counted in effective_cost_bps.",
"integrator_fee": { "feePercent": 0.25, "feeAmountUsd": 0.13, "feeAddress": "0xMentatFeeAddress" },
"tool_used": "stargateV2", "estimated_time_seconds": 623,
"direction": "sell", "min_notional_usd": 25, "max_notional_usd": 5000,
"valid_for_seconds": 30,
"route_legs": [
{ "leg": 1, "name": "source", "chain": "monero", "chain_id": 0, "proof_kind": "deposit_tx", "proofs": "one" },
{ "leg": 2, "name": "hyperliquid_swap", "chain": "hyperliquid", "chain_id": 1337, "proof_kind": "fill_tx", "proofs": "zero, one, or many" },
{ "leg": 3, "name": "destination", "chain": "base", "chain_id": 8453, "proof_kind": "payout_tx", "proofs": "one" }
],
"proofs": [],
"note": "An estimate, not a funding instruction. Create an order to receive the exact deposit address, amount, and expiry."
}
}
A quote is an estimate. Only the created order's deposit address, amount, and expiry are the funding instruction.
to_address is required (Base address for USDC, Monero address for XMR). refund_address on the source chain is optional.
curl -s -X POST https://floor-a2a-production.up.railway.app/a2a \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tasks/send","params":{"skill_id":"create_xmr_order","arguments":{"from_token":"XMR","to_token":"USDC","amount":"0.1","to_address":"0xYourBaseAddress"}},"id":1}'
REST equivalent: POST /api/xmr/orders with the same JSON body. The response's order.deposit is the funding instruction and order.next_steps spells it out. Send exactly that amount to that address before order.expires_at.
The destination address is the read proof. An order_id with the wrong to_address is simply not found.
curl -s -X POST https://floor-a2a-production.up.railway.app/a2a \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tasks/send","params":{"skill_id":"check_xmr_order","arguments":{"order_id":"ord_1","to_address":"0xYourBaseAddress"}},"id":1}'
REST equivalent: GET /api/xmr/orders/{order_id}?to_address=0x.... A completed order (abridged):
{
"order": {
"order_id": "ord_1",
"settlement_path": "wagyu_route",
"status": "completed",
"from_token": "XMR", "to_token": "USDC",
"deposit": { "address": "4AdUnd...", "chain": "monero", "token": "XMR", "amount": "0.1" },
"to_address": "0xYourBaseAddress",
"expected_output": "50.409916", "actual_output": "50.38",
"mentat_fee_bps": 25, "mentat_fee_usd": "0.13",
"legs": {
"source": { "state": "done", "confirmations": 10, "required_confirmations": 10, "tx_hash": "6f1d...c2a9" },
"hyperliquid": { "state": "done", "fill_count": 2, "receipts_status": "complete", "tx_hashes": ["0xbc50...e7ee"] },
"destination": { "state": "done", "tx_hash": "0x9a3b...41d0" }
},
"proofs": [
{ "chain": "monero", "kind": "deposit_tx", "hash": "6f1d...c2a9", "explorer_url": "https://xmrchain.net/tx/6f1d...c2a9" },
{ "chain": "hyperliquid", "kind": "fill_tx", "hash": "0xbc50...e7ee", "explorer_url": "https://app.hyperliquid.xyz/explorer/tx/0xbc50...e7ee" },
{ "chain": "base", "kind": "payout_tx", "hash": "0x9a3b...41d0", "explorer_url": "https://basescan.org/tx/0x9a3b...41d0" }
],
"status_check": { "skill": "check_xmr_order", "arguments": { "order_id": "ord_1", "to_address": "0xYourBaseAddress" } }
}
}
Several Hyperliquid fills can share one transaction hash; proofs[] lists each distinct hash once. Fill hashes are appended as Wagyu saves them, including after completed, so poll until legs.hyperliquid.receipts_status is complete.
When Wagyu answers 503 unavailable, MENTAT pauses the route instead of retrying. Quotes and order creation answer:
{ "error": "XMR routing paused", "code": "routing_paused", "routing_paused": true, "until": "2026-09-14T12:05:00.000Z", "retry_after_seconds": 300 }
REST returns HTTP 503 for it. get_route_status (or free GET /api/routes) reports paused, until, and reason. Other machine codes: below_minimum / above_maximum (MENTAT's floor and cap, with the numbers), no_route (nearby sizes were tried; change the amount), amount_below_minimum (venue minimum), invalid_request (never retried), rate_limited, create_uncertain (creation got no answer and could not be verified: nothing was retried, do not send funds).
cow_relay)Step 1, ask MENTAT to build the order:
curl -s -X POST https://floor-a2a-production.up.railway.app/a2a \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tasks/send","params":{"skill_id":"prepare_trade","arguments":{"sell_token":"USDC","buy_token":"WETH","sell_amount":1000,"trader_address":"0xYourAgentWallet"}},"id":1}'
You get back typed_data (EIP-712 domain, types, message), app_data (MENTAT's partnerFee document and hash), quote_summary, preflight (balance and relayer allowance), and relayer.approve_calldata if the GPv2VaultRelayer still needs an approval.
Step 2, sign locally:
const signature = await wallet.signTypedData(typed_data.domain, typed_data.types, typed_data.message);
Step 3, submit through MENTAT:
curl -s -X POST https://floor-a2a-production.up.railway.app/a2a \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tasks/send","params":{"skill_id":"submit_signed_trade","arguments":{"order":{...},"signature":"0x...","trader_address":"0xYourAgentWallet"}},"id":1}'
Returns order_uid (the first proof) and explorer_url. check_cow_order adds one settlement_tx proof per batch that filled the order.
escrow)curl -s -X POST https://floor-a2a-production.up.railway.app/a2a \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tasks/send","params":{"skill_id":"execute_trade","arguments":{"from_token":"USDC","to_token":"DAI","amount":1000}},"id":1}'
Returns the deal, proofs[0] as the escrow_create_tx, and next steps. check_trade reads the deal state from the contract.
MENTAT's servers hold one Ethereum key, the operator key: the agent wallet 0xE6f26F05C956D1833A1DAaaAD57CD0a1E626bfd3. On-chain it is the owner of ERC-8004 Agent #31596 (ownerOf(31596) on the Identity Registry) and the immutable treasury of the FloorEscrow contract (treasury(); feeBps() = 25). It signs three kinds of transaction and nothing else:
| Contract | Function | Gate in the contract | Effect |
|---|---|---|---|
FloorEscrow 0x9EC9…3AA7 | createEscrow(tradeId, buyer, seller, tokenBuy, tokenSell, amountBuy, amountSell, agentId, duration) | identityRegistry.ownerOf(agentId) == msg.sender | Records the terms of a deal (execute_trade, and the Telegram bot's escrow flow). Moves no tokens. Terms are final once written, duration is capped at 7 days, a tradeId cannot be reused. |
| FloorEscrow | settle(tradeId) | none: anyone may call; status must be BothDeposited | Pays 25 bps of each side to the immutable treasury and the rest to the counterparty named in the deal. The caller chooses nothing. The Telegram bot calls it as soon as both deposits land. |
Identity Registry 0x8004…a432 | setAgentURI(31596, uri) | ERC-721 owner of the agent NFT | Updates Agent #31596's metadata (the same operator script minted the agent once with register). No skill triggers it. |
Like any address it can also call refund(tradeId) (after expiry only; full refund to whoever deposited, no fee) and getTrade(tradeId) (read). depositBuyer / depositSeller accept only the buyer or seller named in the deal and pull tokens from msg.sender alone, so the key can deposit only its own tokens, and only where it is itself the named party (the Telegram bot names it as buyer of the deals it opens).
The key cannot withdraw or sweep anything from the escrow (no such function exists), change the fee or the treasury (both immutable), pause, upgrade, or kill the contract (no owner, no proxy, no admin role), amend or cancel a deal before it expires, deposit or move a counterparty's tokens (approvals go to the contract, never to the key), settle early, or send a settlement anywhere but the addresses stored in the deal. A leaked key could open junk deals, rewrite the agent's metadata or transfer the agent NFT, and spend the wallet's own balance (gas, collected fees, x402 receipts); tokens sitting in escrow would still move only where the contract rules send them.
No other server code uses the key. get_quote, check_trade, get_prices, and get_route_status only read. The CoW relay never signs: the trader signs, MENTAT forwards the signed order to api.cow.fi. The XMR route talks to Wagyu over HTTPS with an API credential (WAGYU_INTEGRATOR_KEY), signs no transaction, and never holds XMR. The MCP server proxies the A2A server over HTTP and holds no key. Without PRIVATE_KEY the A2A server runs quote-only: execute_trade answers with an error and the escrow address to call directly.
| Skill | Path | What it does |
|---|---|---|
get_quote | any | Live quote. XMR pairs answer through the Wagyu route with settlement_path: "wagyu_route". |
get_prices | — | CoinGecko prices for the escrow and CoW pairs. XMR is priced live at quote time. |
create_xmr_order | wagyu_route | Durable XMR order; returns the funding instruction. |
check_xmr_order | wagyu_route | Three legs and proofs[]; needs order_id + to_address. |
get_route_status | wagyu_route | Live or paused, with the pause deadline and reason. |
prepare_trade | cow_relay | Build the EIP-712 order for the agent to sign. |
submit_signed_trade | cow_relay | Relay the signed order to the CoW order book. |
check_cow_order | cow_relay | Order state plus settlement transaction proofs. |
execute_trade | escrow | Create an escrow deal on Base. |
check_trade | escrow | Read an escrow deal by id. |
| Token | Address |
|---|---|
| USDC | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
| USDbC | 0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6Ca |
| DAI | 0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb |
| WETH | 0x4200000000000000000000000000000000000006 |
| XMR | native Monero, routed via Wagyu (XMR1 0xbB2057659bD378D17E2E151BB04bdcaA00000000 on Hyperliquid spot) |
Quotes only: ETH, BTC, WBTC, UNI, LINK, AAVE.
Quotes are at real market rates. MENTAT charges 25 bps on every fill, on every path: escrow (deducted on settlement, immutable in the contract), CoW relay (partnerFee inside the appData the trader signs), and the XMR route (a 0.25% integrator fee applied inside Wagyu's quoted amount, so it is already in to_amount and reported by Wagyu as integrator_fee). On the XMR route effective_cost_bps is the all-in number: Wagyu's own costs plus MENTAT's fee.
https://floor-a2a-production.up.railway.app/.well-known/agent.jsonPOST https://floor-a2a-production.up.railway.app/a2aGET https://floor-a2a-production.up.railway.app/api/quote?from=XMR&to=USDC&amount=0.1&to_address=0x...POST /api/xmr/orders, GET /api/xmr/orders/{id}?to_address=0x...GET https://floor-a2a-production.up.railway.app/api/routesGET https://floor-a2a-production.up.railway.app/api/priceshttps://zesty-solace-production-13de.up.railway.app/mcp (legacy SSE at /sse)GET https://floor-a2a-production.up.railway.app/healthhttps://t.me/FloorOTCbothttps://mentat.market · receipts and short links: https://mntt.ai0x9EC9d882C93F52621CBD0d146D3F2e0929E53AA7 (verified on Basescan)0x9008D19f58AAbD9eD0D60971565AA8510560ab410xC92E8bdf79f0507f65a392b0ab4667716BFE01100x8004A169FB4a3325136EB29fA0ceB6D2e539a432, Agent ID 31596No authentication. Quotes cost $0.01 USDC via x402; order status, route status, and prices are free.
This skill was published as floor-otc. ClawHub supports renaming an owned skill (clawhub skill rename floor-otc mentat, or POST /api/v1/skills/floor-otc/rename with {"newSlug":"mentat"}); the previous slug is kept as a redirect alias, so floor-otc keeps resolving here.