proxy-shopping
Allows interaction with the Bitcoin network for proxy-shopping escrow flows: building and signing BTC funding transactions, creating P2WSH escrow scripts and addresses, constructing multisig/T1/T2 spends, monitoring chain state via bitcoind/Esplora, and checking timelocks and payout problems.
Provides Chainlink oracle exchange-rate data for FX computation and quote-rate checking, enabling agents to validate the conversion rates used in proxy-shopping quotes and orders.
proxy-shopping-web
Buy from cash-only shops with crypto, or earn as a proxy shopper: the browser app, guided demo, TypeScript protocol library and MCP server of proxy-shopping, a P2P network where a local shopper buys for remote users who pay through a 2-of-3 escrow with timelocks. Agents: MCP server io.github.pad01g/proxy-shopping (packages/mcp), skills npx skills add pad01g/proxy-shopping-go, AGENTS.md; overview for machines: https://pad01g.github.io/proxy-shopping-docs/llms.txt.
Documentation in 14 languages: https://pad01g.github.io/proxy-shopping-docs/
Browser side of proxy-shopping (spec: ../proxy-shopping-go/docs/spec.md, lab: docs/lab.md there).
packages/core @proxy-shopping/core — protocol library (TypeScript, ESM, browsers and Node 22)
apps/web React + Vite single-page app for every role (Japanese UI)
apps/demo the lab's integrated demo page: every role with its own key in one browser, a guide per scenario
scripts/ docker helpers for integration and browser testsHost tools are not required; everything runs in docker.
# install, build, unit tests
docker run --rm -v "$PWD":/src -v ps-npm:/root/.npm -w /src node:22-bookworm npm ci
docker run --rm -v "$PWD":/src -v ps-npm:/root/.npm -w /src node:22-bookworm npm run build
docker run --rm -v "$PWD":/src -v "$PWD/../proxy-shopping-go":/proxy-shopping-go:ro -v ps-npm:/root/.npm -w /src node:22-bookworm npm test
# integration: bitcoind signet + anvil (lab contracts) + nostr-rs-relay
scripts/integration.sh
# the built web app in Chromium against a mini lab (TS shopper, faucet, escrow …)
scripts/e2e-web.sh
# the demo in mock mode (GitHub Pages build) in Chromium: every scenario, nothing leaves the page
scripts/e2e-demo-mock.sh
# web image (config.json is mounted at runtime)
docker build -f apps/web/Dockerfile -t proxy-shopping-web .
docker run -p 8080:80 -v $PWD/config.json:/usr/share/nginx/html/config.json:ro proxy-shopping-webMounting ../proxy-shopping-go read-only lets the unit tests check docs/test-vectors.json
and lab/keys/public.json. Without the vectors file npm test fails, so CI cannot pass silently;
set SKIP_VECTORS=1 to run the other tests without it.
apps/demo
Try it in your browser: https://pad01g.github.io/proxy-shopping-web/ (everything simulated in the page). That build
runs in mock mode (?mock=1, header switch モック / Mock): the relays, the BTC chain (with script checks), a real EVM
with the lab's Safe and escrow-module bytecode, the faucet, the rates, the shops and the shopper-1 node run inside the
browser (a SharedWorker shared by all windows), while the four roles use core's real clients. Details, what is simulated
and its limits: apps/demo/README.md. The mock e2e (all scenarios, English, separate windows, no
request leaving the page): scripts/e2e-demo-mock.sh; Pages is deployed by .github/workflows/pages-demo.yaml.
The integrated demo for the docker compose lab (../proxy-shopping-go, compose service demo, http://localhost:8888/;
usage in ../proxy-shopping-go/docs/lab.md「デモ画面」). User, escrow, operator and coordinator each have their own key
(localStorage, lab only) and their own Session + client in the page; the shopper is the lab's Go node. A guide walks
through seven scenarios (normal BTC / USDC, dispute refund, sold out, risky shop, fraudulent escrow, T2 refund);
?role=user / ?role=escrow,operator,coordinator runs only those roles in a window. The page is in Japanese or English
(header switch 日本語 / English, kept in localStorage; ?lang=en|ja overrides it): one message catalog per language in
apps/demo/src/i18n (ja.ts defines the keys, en.ts must match them), core's timeline lines are rendered from their kind.
Protocol logic is core's
(UserClient, EscrowClient, OperatorClient, CoordinatorClient); the page wires them over core's MappedTransport (logical relay
URLs such as wss://relay-1.test stay protocol-visible, the connection goes to the demo server's /relay-1).
docker build -f apps/demo/Dockerfile -t proxy-shopping-demo . # static files; the lab mounts its nginx.conf + demo-config.json
DEMO_SERVER=http://localhost:8888 npm run dev -w @proxy-shopping/demo # dev server proxying to a running lab demo serverScenario steps are data (apps/demo/src/scenarios); test ids in apps/demo/TESTIDS.md; the e2e that follows the guide is
../proxy-shopping-go/e2e/src/demo (docker compose run --rm runner demo).
Related MCP server: arbitova-mcp-server
@proxy-shopping/core
Entry points: @proxy-shopping/core (portable), /node (+ FileStorage), /browser
(+ IndexedDBStorage), /testing (in-memory relays/chain, scripted shopper, createWorld),
/testing/node (bitcoind RPC + Esplora shim). Build output with declarations is in dist/.
area | main exports |
keys |
|
nostr |
|
trust |
|
fx |
|
btc |
|
evm |
|
delivery |
|
flows |
|
checks |
|
Notes
The mnemonic is stored in the origin's IndexedDB encrypted with a passphrase (PBKDF2-SHA256, 600k iterations, AES-GCM via WebCrypto); plaintext only if the user explicitly opts out at onboarding. WebCrypto needs a secure context (https or localhost) —
scripts/e2e-web.shforwards localhost:8080 in the browser container for that.Funds never move without a click and an in-page confirmation (fund, resume funding, release, countersign, refunds). The shopper's cooperative refund is shown for review, never auto-signed. Terminal states (
completed,settled,refunded) wait for the chain, also after our own broadcast (BTC/tx/{txid}/outspend/{vout}; USDC: Safe balance below lock_amount and a receipt with a Transfer out of the Safe — anyone can send dust to a Safe, §4.8).Funding persists every transaction before it is sent (BTC txid + raw tx; EVM: signed locally, hash + raw tx stored, then sent), so an interrupted funding is resumed with the same transactions, never paid twice.
The escrow opens a case only when a notice / dispute names a request whose request + quote recompute to the funded output (P2WSH, or the Safe with its owners / module configuration) on chain (§4.7); its ruling fee is exactly
dispute_fee_bpsof what is split, and it owes rulings only for upfront fees ≥ its own published minimum.config.jsonfields beyond the endpoints:timelock_policy(§4.5.1; names as inproxy-shopping-go/lab/web-config.json, spec defaults when absent),max_clock_skew_seconds(§4.5.1: largest accepted difference between the chain's clock — BTC tip header time, EVM latest block time — and ours, default 7200; the lab sets 315360000 because anvil's time is warped),allow_private_endpoints(lab only: http/ws and private hosts),max_fee_rate(sat/vB, default 50). Endpoints must otherwise be https / wss; relays from peers' kind 10050 are limited to 8 public wss relays.Messaging (§4.10): 120 messages / min per sender after EOSE, 60 / min for all non-counterparty senders together, higher per-sender limits for the stored backlog (older pages are read when the first 1000 wraps are full); messages no attached role accepts are neither stored nor acked. Sends go to k (2) inbox relays, the next ones only when one fails; resends 1, 2, 4, 8, … get a fresh wrap. A signed inner is at most 28000 bytes (§4.9); dispute evidence is split over several messages.
The Safe address is predicted with the factory's
proxyCreationCodeonly if its keccak256 is a known Safe v1.4.1 build (the lab's and the canonical one,KNOWN_PROXY_CREATION_CODE_HASHES).Only one tab runs the protocol at a time (Web Locks, BroadcastChannel fallback).
apps/web/nginx.confsets CSP,X-Content-Type-Options,Referrer-Policy,Permissions-Policy,X-Frame-Options; HSTS belongs to the TLS terminator. Production builds have no source maps.The browser talks to Esplora, the EVM RPC, rate sources and the faucet directly, so those endpoints must send CORS headers.
UI test ids:
apps/web/TESTIDS.md.
Contributing — pull requests welcome
Pull requests are welcome: shop drivers for shopper-bot, new payment methods and chains, translations, protocol and security reviews, bug fixes. Want to earn in your own town? You need nobody's permission: make a coordinator key, delegate to your operator key and list yourself as a shopper for your region (guide: https://pad01g.github.io/proxy-shopping-docs/en/quickstart/, section 3); a home machine reached over Tailscale or any VPN is enough. To be found by everyone, open a pull request to https://github.com/pad01g/proxy-shopping-registry.
License
MIT (LICENSE).
Available Tools
19 toolsaccept_quoteAccept a quoteA
Step 3: accept a quoted order (sends a signed acceptance to the shopper; nothing is paid yet — fund_order pays). Only for status quoted. A quote that failed validation is refused. A quote whose rate deviates strongly (> 10 %) from our sources, or could not be checked, is not accepted until you call again with acknowledge_rate_deviation: true — ask your human first.
Inputs: order_id (required), acknowledge_rate_deviation.
accept_refund_offerAccept the shopper's refundADestructive
Moves money: co-sign and broadcast a cooperative refund the shopper offered (e.g. the item was sold out or delivery failed), returning the funds to your wallet. Refused if the offer does not pay you as the refund template requires. Without confirm: true it shows the offer and its check. Errors if there is no offer (get_order shows refund_offer).
Inputs: order_id (required), confirm.
become_shopperEarn as a proxy shopperARead-onlyIdempotent
Return a step-by-step plan to earn fees as a proxy shopper (buying at local shops for remote users who pay into the escrow): requirements (always-online host, Docker, the Go node and shopper-bot images, card or cash regions), fees and risks, an honest status of the network, a ps-main node config and compose file, and how to get listed through the registry (then registry_entry). Local, no network, nothing is started.
Inputs: name, fee_bps, regions, cash_regions.
cancel_orderCancel an order before fundingA
Cancel an order that is not funded yet (requested, quoted or accepted) and tell the shopper (best effort). Refused once funding has started — then use open_dispute, accept_refund_offer or refund_after_timelock. Nothing is paid or refunded.
Inputs: order_id (required), reason.
confirm_receiptConfirm receipt and pay the shopperADestructive
Step 5, moves money: the items arrived and are right, so sign the escrow payout to the shopper (release); the shopper co-signs and broadcasts it. Irreversible. Without confirm: true it only returns the payout (amount, recipient) and a warning if the shopper has not reported delivery. With confirm: true it signs and waits up to wait_seconds for the payout on chain. If the items did not arrive or are wrong, use open_dispute instead.
Inputs: order_id (required), confirm, wait_seconds.
countersign_rulingCountersign the rulingADestructive
Moves money: add your signature to the escrow's ruling transaction (2 of 3) and broadcast it, settling the dispute. Refused if the transaction does not pay the ruled split. Without confirm: true it returns the same review as review_ruling. Ask your human before confirming.
Inputs: order_id (required), confirm.
export_backupExport the recovery wordsARead-onlyIdempotent
Show this agent's 12-word BIP39 mnemonic, which controls the identity and every order's escrow key, with restore instructions. Only with confirm: true — the words then stay in the conversation, and anyone who sees them can take the funds; use only when your human asks for a backup. Local, no network.
Inputs: confirm.
find_offersFind proxy shoppers for a shopARead-onlyIdempotent
Step 1 of buying from a cash-only or crypto-unsupported shop: list the trusted proxy shopper × escrow combinations that serve this shop, region and payment method. Each offer has an index, the shopper's fee, delivery days, cash regions and order limit, the escrow's fees and dispute SLA, and which operator list (under which coordinator) vouches for it. Pass the index to request_quote with the same shop_url, region and payment. Read-only; an empty list means nobody serves that shop/region yet (network_info shows what exists).
Inputs: region (required), shop_url (required), payment.
fund_orderPay into the escrowADestructive
Step 4, moves money: pay the quoted lock amount into the per-order 2-of-3 escrow (BTC P2WSH address or USDC Safe) plus the escrow upfront fee, from this wallet. Only for status accepted/funding. Without confirm: true it only returns the recipients, amounts and network fee (a preview; call it first). With confirm: true it signs and broadcasts. The funds can then leave only with 2 of 3 signatures, by the shopper alone after T1, or by you alone after T2. Ask your human before confirming.
Inputs: order_id (required), confirm.
get_orderOrder statusARead-onlyIdempotent
Show one order: status, quote and its validation, funding, purchase, tracking, dispute, ruling, refund offer and settlement, the recent timeline, and next_steps telling which tool to call next. Optionally waits (bounded) until the order reaches one of wait_for, e.g. ["quoted","rejected"] after request_quote. Read-only (local order state, kept up to date by the running session).
Inputs: order_id (required), wait_for, wait_seconds.
list_ordersList ordersARead-onlyIdempotent
List this agent's orders (from its data dir), newest first, with status, shop, items, lock amount and next steps. Use it to find an order id or to see what needs attention; get_order shows one order in full. Read-only.
Inputs: limit, status.
network_infoNetwork statusARead-onlyIdempotent
Show which proxy-shopping network this server is on (ps-main = public Nostr relays + BTC signet, or the local lab), its relays, trusted coordinators, chains, timelock policy, and how many shopper × escrow combinations are trusted right now (with their regions and operator lists). Call it first in a session: on a new network there may be no shoppers yet, and then find_offers will return nothing. Read-only; re-reads the trust lists from the relays and the registry unless refresh is false. Returns a summary and the details as JSON.
Inputs: refresh.
open_disputeOpen a disputeA
Ask the escrow to decide a funded order: items not delivered, wrong item, or the shopper not releasing. Sends the claim with all signed order messages as evidence (copy to the shopper) and the key that lets the escrow decrypt the delivery address. No funds move now; the escrow later rules a split, which you check with review_ruling and execute with countersign_ruling. Without confirm: true it only shows what would be sent.
Inputs: text (required), claim (required), order_id (required), confirm, +1 more.
refund_after_timelockTake the funds back after T2ADestructive
Moves money, last resort: after the T2 timelock you alone can take the locked funds back to your wallet (e.g. the shopper disappeared). Without confirm: true it shows T2, the current block height or chain time, whether T2 is reached, and the amount. Before T2 the chain rejects it; prefer accept_refund_offer or open_dispute while the shopper responds.
Inputs: order_id (required), confirm.
registry_entryRegistry entry for getting listedARead-onlyIdempotent
Return the exact JSON file and path to add in a pull request to github.com/pad01g/proxy-shopping-registry, so a shopper, escrow, operator or coordinator gets listed once the maintainer merges it: shoppers/.json {pk, contact, description, regions (cash regions), payments, escrows}; escrows/.json {pk, contact, description, sla_days}; operators/.json {pk, contact, description, regions}; coordinators/.json {pk, contact, description, url?, bundle?}. Uses this data dir's identity pubkey unless pubkey is given (a shopper entry must carry the shopper node's key). Local; opens no pull request itself.
Inputs: name (required), role (required), contact (required), description (required), +7 more.
reportReport a shopper or escrowA
Report the order's shopper or escrow to the operator that listed them, attaching the order's signed messages as evidence (e.g. a dishonest ruling, a quote that failed validation, no delivery). Sends one signed message; no funds move and the order is not changed otherwise. The operator may remove them from its list.
Inputs: text (required), subject (required), order_id (required).
request_quoteRequest a quote from a proxy shopperA
Step 2: create an order with one shopper × escrow combination (offer_index from the last find_offers with the same shop_url, region and payment, or both shopper and escrow pubkeys) and wait up to wait_seconds for the quote. Sends a signed, encrypted order request over Nostr; the delivery address is encrypted for the shopper (the escrow can read it only in a dispute). Nothing is paid. Returns the order id, the price breakdown, and our validation of the quote: rate deviation from our own rate sources, the recomputed 2-of-3 escrow address, the timelocks T1/T2. If no quote arrives in time the order stays open; follow it with get_order. Next: accept_quote or cancel_order.
Inputs: items (required), region (required), address (required), shop_url (required), +5 more.
review_rulingReview the escrow's rulingARead-onlyIdempotent
Show the escrow's ruling for a disputed order — the split between you, the shopper and the escrow fee, and its reason — and whether the escrow's transaction really pays exactly that split. Read-only. If it matches, countersign_ruling executes it; if not, do not countersign and consider report.
Inputs: order_id (required).
walletWallet and identityARead-onlyIdempotent
Show this agent's identity (Nostr pubkey), its BTC signet address and balance, and where the network has USDC its EVM address with USDC and ETH balances. Use it before fund_order to check that the wallet can pay the quote, and to get the address to fund (signet faucet; on the lab: lab_faucet). Read-only: queries the chain, never signs. The key is created on first run in the data dir; export_backup shows it.
Inputs: no named parameters.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 19 tools
Tools are nicely differentiated by explicit multi-step labeling (find_offers → request_quote → accept_quote → fund_order → confirm_receipt) and money-moving vs read-only distinctions. Some overlap clusters exist (report vs open_dispute, and the four refund/dispute settlement tools), but descriptions consistently clarify when to use each.
Dominated by clear verb_noun snake_case (find_offers, request_quote, accept_quote, fund_order, confirm_receipt, open_dispute, review_ruling, cancel_order). A few deviations break the pattern: bare noun phrases network_info, wallet, registry_entry, and a lone bare verb report.
19 tools is on the heavier side but each maps to a distinct step in a genuinely multi-step, money-handling escrow workflow where separation aids safety. Some consolidation (info/registry helpers) might be possible, but nothing feels gratuitous.
The buy → fund → receive → dispute/refund/report → settle lifecycle is thoroughly covered, including edge cases (timelock refund, cooperative refund offer, ruling verification) and seller-side onboarding. Minor gap: no shop-discovery tool, so agents must already know shop_url.
Maintenance
Related MCP Connectors
Buy products and services with escrow, or trade crypto P2P. Money moves only with human 2FA.
Agent registry with Nostr identity, reputation, escrow, observability, and Lightning payments.
Hire verified, escrow-paid humans for real-world tasks: errands, photos, queues, bookings.
Agents ask other agents for help: post requests, take work, rooms, signed receipts. Test USDC beta.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceTrust-aware Nostr MCP server. 236 tools for identity, social, DMs, trust scoring, AI-to-AI dispatch, Lightning payments, privacy proofs, and encrypted vaults. NIP-46 bunker auth; keys never leave the signing device.1,549 npm1MIT
- AlicenseAqualityDmaintenanceNon-custodial on-chain escrow + AI arbitration for agent-to-agent USDC payments on Base. Seven tools wrap a verified EscrowV1 contract — create, deliver, confirm, dispute, resolve — with read-only introspection if no wallet key is provided.72MIT
- AlicenseAqualityBmaintenanceGive your agent an address: private agent-to-agent messaging, free encrypted file handoffs, and Lightning commerce. Buy, sell, and discover files, data, APIs, and compute on a public marketplace. Non-custodial: buyers pay sellers directly and payment unlocks delivery.27132 npmMIT No Attribution
- AlicenseAqualityCmaintenanceEnables AI agents to register a bonded Ed25519 identity, open private end-to-end-encrypted negotiation rooms, exchange messages, and settle deals with dual-signed receipts, trust badges, and Lightning hold-invoice escrow. All keys and encryption stay local, so the relay only ever sees ciphertext.12MIT