mechawallet_
Sell

Build a marketplace#

You have sellers. They have things agents will pay for. You want to run the storefront, take a cut, and never touch anyone's money.

That is a marketplace on mechawallet — the API calls it platform mode (platform_account, platform_fee_*), and this guide uses both words for the same thing: one account (yours), one API key, and one checkout per thing your sellers sell — with their wallet as the payout and your wallet on the fee. Settlement pays everyone in the same transaction, on-chain. There are no sub-accounts to create, no balances to hold, no payouts to schedule, and nothing for your sellers to sign up for here — a seller is a wallet address on a checkout, full stop.

buyer ──pays $50.26──▶ settlement ──▶ $49.00 seller's wallet
                            ├──────▶  $1.00 your wallet (2% platform fee)
                            └──────▶  $0.26 protocol fee

The legs always sum to what the buyer paid — settlement is disbursement, so there is no rounding gap for money to hide in. The buyer pays exactly what a $50 checkout without a platform costs: $50 plus the protocol fee ($0.01 + 0.5%, charged on top). Your cut comes out of the seller's side — platform prices never drift from non-platform prices — and every receipt itemizes it, so your sellers always see the deal they took.

gitbuyer is this guide, running: repos for sale, sellers paid per clone, one mechawallet account underneath. When a step below feels abstract, go click around it.


Set up, from zero#

The whole onboarding is six calls, in this order, and every one is safe to re-run. Nothing below assumes an existing account — this is the sequence a brand-new marketplace (or a deploy script rebuilding one) follows.

0 — An account. Two doors, both end holding a credential that can do everything below:

  • Agent-native: POST /auth/wallet/challenge → sign it → POST /auth/wallet/verify → your first full-scope key, minted on the spot (the signing wallet is registered as your verified payout wallet, so step 1 is already done).
  • Email: request a magic link, POST /auth/verify → the response carries a bearer session token that works in Authorization: Bearer … on every endpoint below — the dashboard is optional, not required.

1 — Your payout wallets: this is where your cut lands. One wallet per chain, and this is the account's ONLY wallet store — an ordinary seller's sales land here; a marketplace's cut lands here. Set one for EACH chain your sellers sell on: a chain without a wallet is dropped from your fee-carrying checkouts, and its buyers with it. Also required before any key can be minted.

curl -s -X PUT https://api.mechawallet.com/v1/account/wallets \
  -H "Authorization: Bearer $SESSION_OR_KEY" \
  -H "content-type: application/json" \
  -d '{"wallets": {"evm": "0xYourBase…", "svm": "YourSolana…"}}'

2 — Marketplace mode + your standing fee. One call, the whole B2B2X switch:

curl -s -X PUT https://api.mechawallet.com/v1/account/platform \
  -H "Authorization: Bearer $SESSION_OR_KEY" \
  -H "content-type: application/json" \
  -d '{"platform_account": true, "platform_fee_bps": 200}'

The mode and the RATE — there are no wallet fields here, on purpose. Your cut lands in the payout wallets from step 1, per chain; the response's fee_wallets shows the resolved view so a missing chain is visible now, not discovered in lost buyers. Marketplace is a strong either: with the mode on, every checkout you create is for a sub-seller — creating for the account itself is refused (sell your own products from a separate seller account).

(Or the dashboard: Payout wallets → Marketplace.) From this moment: creates default to inherit_account_payouts: false (your wallets can never leak onto a sub-seller's checkout), the standing fee rides on every sub-seller create that doesn't name its own, and whoami / /auth/me report the configuration so your service can assert it at boot.

3 — Your key(s). POST /v1/keys — one is enough; several with their own fee tiers if you run several storefronts (next section).

4 — Sale events. Tell mechawallet where to deliver — re-runnable by design (the same POST is setup, recovery and revocation in one):

curl -s -X POST https://api.mechawallet.com/v1/webhooks \
  -H "Authorization: Bearer $FULL_KEY_OR_SESSION" \
  -H "content-type: application/json" \
  -d '{"url": "https://yourservice.example/internal/mw-events"}'

Your receiver needs no configured secret. Verify each delivery's X-Mechawallet-Ed25519 header against the public key at GET /v1/webhooks/verification-key (no auth — fetch it at boot). The API key is the only credential your deploy requires. The response's secret exists for receivers that prefer symmetric HMAC verification instead (X-Mechawallet-Signature) — re-POSTing the same URL re-keys it ("rekeyed": true), and DELETE /v1/webhooks/{id} removes a receiver. See Webhooks for both verification paths.

5 — Sell. Create one checkout per thing your sellers sell (next sections). whoami at boot confirms steps 1–4 took.

One rate per key (several storefronts, one account)#

Running more than one storefront or seller tier? Give each its own key with its own RATE — the credential is the tier. A rate, never a wallet: keys name how much, the account's payout wallets name where.

curl -s -X POST https://api.mechawallet.com/v1/keys \
  -H "Authorization: Bearer $YOUR_KEY" \
  -d '{"scope": "full", "mode": "live", "name": "partners",
       "platform_fee_bps": 100}'

Checkouts that key creates carry its rate; keys without one fall through to the account's standing rate. Precedence, always: explicit per-create → the key's rate → the standing rate. The rate is echoed in the mint response, the key list and whoami (key_platform_fee_bps), so a rotation that lost it is visible immediately — not in next month's margin.

The five primitives#

Everything a platform does is these five. There is deliberately no sixth.

  1. Your account in platform mode, and its key(s). You authenticate; your sellers never do.
  2. The checkout is the sub-merchant. Each one carries the seller's wallet(s), the price, your fee — and platform_checkout: true, frozen at create, so "yours" and "your sellers'" stay separable everywhere (?platform=true|false on lists and receipts).
  3. The platform fee — a RATE (per create, per key, or the account's standing one; most specific wins), landing in your payout wallets per chain.
  4. The per-seller ledgerGET /v1/receipts?checkout_id=….
  5. The event feed — one checkout.paid webhook; route by checkout_id.

Create a checkout for a seller#

curl -s -X POST https://api.mechawallet.com/v1/checkouts \
  -H "Authorization: Bearer $YOUR_KEY" \
  -H "content-type: application/json" \
  -d '{
    "title": "Clone acme/private-sdk",
    "price_usd": "50.00",
    "pay_to": "0xSELLER…",
    "payouts": {"SVM": "SellerSolanaAddr…"},
    "fulfillment": "relay",
    "origin_url": "https://…"
  }'

One line in that body is the platform discipline:

  • pay_to (+ payouts) is the seller's wallet. Money never routes through you. Every rail the seller can receive on is offered to the buyer — at the SAME dollar price, with the checkout itself guiding the buyer to the right one ("Pay with USDC on Base/Solana", installed wallets detected and named, the payable rail leading). Two chains cost you and your sellers zero explanation; they only widen who can pay.

Everything else the mode already did: inherit_account_payouts defaulted to false (your wallets stay off the seller's rails), and your standing fee rode on. Your cut needs no line at all. To state it per checkout anyway, send platform_fee_bps — explicit beats the key's rate and the standing rate, and "platform_fee_bps": 0 makes that one checkout free of your fee. There is no selling your own things from a marketplace account: the strong either refuses it. Use a separate seller account for your own products.

Know your numbers before you set them#

curl -s "https://api.mechawallet.com/v1/platform/quote?price_usd=50.00&platform_fee_bps=250"
{
  "buyer_pays": "50.26",
  "seller_receives": "48.75",
  "platform_cut": "1.25",
  "protocol_fee": "0.26"
}

Public, no account needed — price your platform before you build it. The dashboard's platform section has the same calculator live, and both are computed by the very modules that settle real payments, so the preview and the payment cannot disagree. It exists to make two rules unmissable: your cut plus the seller's take equals the price to the cent, and the protocol fee is paid by the buyer on top — never out of your cut.

The fee rules#

  • Capped at 2000 bps (20%). Past that, a "fee" is a revenue share wearing the wrong name, and the API refuses it.
  • Taken from the seller's side, floored in the seller's favor. platform = price × bps ÷ 10000, rounded down. The buyer's total and the protocol fee (0.5% + $0.01, on top, yours to pay never) are untouched.
  • The cut lands in your payout wallets, per chain — intersected with each seller's chains. Each rail pays your wallet on its own chain. A chain where YOU have no wallet is omitted from that checkout's live face (its buyers included); a chain the SELLER doesn't sell on simply carries no leg — a Base-only seller lists fine on a two-chain marketplace. Only zero overlap refuses (the cut would have nowhere to land), and the error names both fixes. One wallet on each chain and nothing is ever dropped. (EVM fee legs settle through splitter v2; create refuses the leg with the exact fix when v2 is not configured.)
  • Itemized everywhere money is shown — receipts, exports, webhooks, the buyer's receipt, the pay page. Your sellers see the cut; that is a feature, not a leak.
  • Frozen at create, like everything else on a checkout. Changing the standing fee changes what you create next, never a deal a buyer can already see. Every change to it is written to your account timeline, old and new, attributed.

Receipts: minted by settlement, distributed by you#

You never create a receipt — settlement mints it. Every sale on your marketplace automatically produces an unforgeable receipt carrying the on-chain tx_hash, your cut itemized in its disbursements, and a permanent public page at /r/{payment_id} (plus a printable download and by-tx recovery). A receipt nobody can POST into existence is a receipt everyone can trust — that is the design, not a missing endpoint.

What you get per sale, with nothing to derive:

  • the checkout.paid webhook arrives carrying receipt_url (relay it straight to your buyer) and sold_by (route the sale to the right sub-seller, no lookup);
  • GET /v1/receipts?sold_by=<seller> is one sub-seller's statement in one call — rows carry sold_by, and the CSV export (/v1/receipts/export) has it as a column for the accountant's subtotals;
  • receipt pages embed in your own site with the same two-line script as checkouts — <div data-mw-receipt="pay_…"></div> on your order page shows the buyer their receipt (and their escrow controls) without leaving your site, under the same embed_origins allowlist. Receipts also name the parties: your account as seller of record (linked profile, verified badge) and sold_by — "on behalf of " — on every platform sale.

Name the parties, and the receipt speaks your language. The wallets prove what happened; parties declares who it happened between — in your namespace, not the chain's:

"parties": {
  "buyer":  {"name": "Klaus K.", "email": "klaus@acme.io", "ref": "cus_4821"},
  "seller": {"name": "Acme Parser Co.", "ref": "sel_77"}
}

Pass it on any create (everything optional, graceful when absent). The receipt page and printable download then read "Billed to Klaus K." and "Sold by Acme Parser Co. — on behalf of acme/parser"; the checkout.paid webhook and the ledger rows echo the whole object (so your refs come back with the money event); the CSV export carries buyer_name/buyer_ref for the accountant. mechawallet verifies none of it — you assert, we carry, the chain proves — and what you put here appears on the receipt page, so share its URL accordingly. Escrow twins inherit it with every other frozen term.

A decision worth knowing: your sub-sellers do not pull receipts from mechawallet directly — under the one-account B2B2X model there is nothing for them to log into, and you are the distributor of statements. The public capability URL is what you hand onward; the sold_by filter is what you build their dashboards from. (Scoped per-sub-seller read keys would be a later product, if ever needed.)

Escrow on your marketplace: you are the judge#

If you offer escrow on sub-seller checkouts, understand one rule first: your marketplace's disputes are yours to decide — mechawallet never arbitrates a checkout you created for one of your sellers. That is what makes B2B2X real: your marketplace, your dispute policy, your ruling.

Concretely:

  • Name your arbiter wallets before offering escrow — PUT /v1/account/escrow with escrow_arbiter_evm and/or escrow_arbiter_svm (dashboard: Who settles disputes). An escrow create for a sub-seller with no arbiter of yours is refused with a 400; a chain you name no arbiter for simply offers no escrow on your checkouts. The one-word setup: {"arbiter": "payouts"} copies your payout wallets as arbiters — values frozen at that moment, never a live link, so rotating payouts later moves nothing. Still an explicit act, because becoming the judge of your marketplace's disputes should be said, not inherited.
  • Choose a wallet that can actually rule. Three requirements, learned the hard way: it must be a key you can sign with (an exchange deposit address can hold funds and still never rule); on Base it needs a little ETH, because a ruling is a transaction (Solana rulings ride our relayer's gas); and once set, keep the key signable until the last escrow that froze it resolves. GET /v1/account/escrow/readiness — the dashboard's Check balance button — answers "can I rule today, and roughly how many times" per chain, and the same answer arrives inside every escrow.escalated webhook as arbiter_readiness, so the moment you learn about a dispute you also learn whether you can sign its ruling.
  • Your arbiter can only ever pick between the two exits the buyer's deposit committed to — release to the seller or refund to the buyer. No third route exists, for you, for us, for anyone.
  • Disputes reach you as escrow.escalated webhooks and sit in GET /v1/escrows?status=escalated. Rule from your dashboard's Live escrows panel (both chains — Base signs with your browser wallet, Solana with any Wallet-Standard wallet, gasless), from the API (POST /v1/escrows/{id}/resolve), or let an agent work the queue via MCP (resolve_escrow).
  • If you never rule, the buyer is refunded automatically at the backstop deadline. Nobody's money locks — but your seller loses by default, so an unworked dispute queue is a policy, whether you meant it or not.

Timing is yours per checkout (presets instant / standard / shipping, or explicit windows). Details: Escrow.

When a seller changes something#

A checkout's deal — price, wallets, fee — is frozen at creation, which is what makes a signed payment trustworthy. To change anything: create a new checkout with the new terms, point your store page at it, and void the old one (DELETE /v1/checkouts/{id}) so a cached link cannot keep selling at yesterday's price to yesterday's wallet. Do both, always, in that order.

Per-seller sales, without a ledger of your own#

curl -s "https://api.mechawallet.com/v1/receipts?checkout_id=chk_…" \
  -H "Authorization: Bearer $YOUR_KEY"

Rows carry amounts, the payer, the tx hash, platform_amount (your cut) and test_mode — and every row carries disbursements, the leg-by-leg record a reconciler wants:

"disbursements": [
  {"role": "seller",   "wallet": "0xSELLER…", "amount": 4875000},
  {"role": "platform", "wallet": "YourAddr…", "amount":  125000},
  {"role": "protocol", "wallet": null,        "amount":   35000}
]

All legs land in the same transaction (tx_hash): settlement is disbursement, so there is no later payout run whose status could diverge from the sale's, and the legs sum to what the buyer paid. Filter one checkout to answer "what did this seller earn"; subscribe one webhook and route checkout.paid events by checkout_id to tell them the moment it happens. Keep rehearsal money out of revenue numbers — rows say test_mode for exactly that reason.

Attach a payment to YOUR buyer: the handoff nonce#

An x402 buyer is a wallet. Your platform's buyer is an account — and the gap between those two is the last thing standing between "an agent paid a URL" and "my user bought this". The handoff nonce closes it.

The mechanics, end to end:

  1. Your signed-in buyer asks to buy (or their agent does, holding their session). You mint a nonce — any [A-Za-z0-9_-]{16,64} string — and store (nonce, account, checkout) on your side.
  2. You hand out the pay URL with ?h=<nonce> appended. Any face works: plain, escrow, /test. Whoever ends up paying — an agent, a phone, a curl — pays the URL exactly as given.
  3. Settlement stores the nonce on the payment, and you hear it back twice: - handoff_id on the checkout.paid webhook (and every escrow event). Match it against your table and the order is attached the moment the event arrives. - GET /c/{id}/handoff/{nonce} (also /e/), a public poll that answers 404 until a settled — or escrow-held — payment carries the nonce, then {payment_id, receipt_url, escrow_state, test_mode}. This is what a live browser page uses to react in real time; a wrong guess is indistinguishable from nonexistence.

The hosted pay page cooperates without any work from you: a wallet payment forwards the page's ?h= into settlement, and the cross-device QR threads an existing tag into the code it shows, so a buyer who opens your tagged link on a desktop and pays with their phone keeps the attribution.

Rules worth copying from the platforms already running this:

  • The tag is a bearer capability. Whoever pays through it buys for that account. That is the feature — handing the link away only donates a purchase to the minter. Money routing is untouched; the nonce decides which account the goods-side lands on, nothing else.
  • Scope it to one checkout and consume it on first settlement. A reusable checkout's second payer is a stranger with a forwarded link.
  • Give it an expiry (days, not minutes — agents shop slowly).
  • Escrow attaches at held, revenue waits for release. The nonce tells you whose hold it is; it never makes a hold a sale.
  • Keep a fallback. A payer that strips the query string settles fine and attributes to nobody; let buyers claim by payment_id after the fact.

Test first, live when you mean it#

A mw_test_ key makes your whole platform a rehearsal: every flow works, every split is simulated and recorded, no money exists. Swap to a mw_live_ key and listings sell for real the moment they are created — so decide first how you prove a seller controls what they are selling (gitbuyer's answer: a fine-grained GitHub token that can push to the repo). mechawallet enforces your protocol; vouching for your sellers is your half of the deal.


The pitch, compressed: your sellers get paid on-chain per sale with no account here; you get your cut in the same transaction with no money ever in your custody; buyers — human or agent — pay one URL. One account, one mode (PUT /v1/account/platform), one standing fee, and the contract underneath serves every platform at once: nothing about yours is deployed anywhere.