skills/fd-agentic-commerce/SKILL.md
Complete a shopping checkout at any agentic-commerce merchant that accepts the Finance District Prism payment handler (xyz.fd.prism_payment). Works with both UCP (Universal Commerce Protocol) and ACP (Agentic Checkout Protocol) merchants — the skill auto-detects which protocol the store speaks. Use when the user asks to "buy", "order", "purchase", "shop", "checkout", or "get me something" from a storefront. Covers the full flow — merchant discovery, catalog/product browsing, checkout session create/update/complete, shipping-address collection, x402 payment authorization via the FD Agent Wallet MCP, and order confirmation. Single-merchant purchases only (no cross-merchant carts). Do NOT use for general crypto wallet operations or non-checkout wallet flows.
npx skillsauth add financedistrict-platform/fd-agent-wallet-skills fd-agentic-commerceInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Shop on behalf of the user at any agentic-commerce merchant that accepts
Finance District payment. Auto-detects the store's protocol (UCP or ACP) and
handles everything behind the scenes — the user never needs to hear about
protocols. You drive HTTP with curl; the FD Agent Wallet signs the payment.
First rule: see §0 on how to talk to the user.
The user is shopping. They do not care about UCP, ACP, x402, EIP-3009, handlers, or any other protocol/technical name. Keep that vocabulary out of user-visible messages entirely.
| Instead of saying… | Say this | | --- | --- | | "Detecting which protocol the store speaks" | "Checking the store" | | "Both UCP and ACP available, using UCP" | (say nothing — just proceed) | | "Prism payment handler confirmed" | (say nothing — verified internally) | | "Authorizing x402 payment via EIP-3009" | "Paying now" | | "On-chain tx hash: 0x…" | "Here's your payment confirmation: 0x…" (or just "Transaction: 0x…") | | "Checkout session ready_for_complete" | (say nothing — move to the confirmation step) | | "Wallet signing via MCP authorizePayment" | "Paying with your wallet" | | "Insufficient balance on all supported assets" | "Looks like you're short for this cart — you have X; the cart is Y." | | "Checking wallet balances via getWalletOverview" | (silent if sufficient; only speak up if you need to surface a shortfall) |
Internally, still do all the protocol-level work (that's what this skill is for). But to the user, narrate only: looking up products, presenting recommendations, asking for shipping details, confirming price, paying, showing proof of purchase.
Rule of thumb: if a word sounds like it belongs in an RFC, don't say it to the user.
Second rule: stay autonomous. A real personal shopper doesn't poll their client for every decision. If the user's opening gave you enough to infer — budget, gift intent, recipient, quantity — infer silently and proceed. Ask only when:
Every avoidable question is friction. "Want to trim the cart?", "Which asset should I pay with?", "Should I use your saved address?" — skip them unless the default answer is genuinely uncertain.
authorizePayment, getMyInfo, and getWalletOverview. If they are not attached, stop immediately and tell the user to add the MCP server at https://mcp.fd.xyz to their MCP client (e.g. Claude Desktop config → mcpServers, Claude Code → claude mcp add fd-wallet https://mcp.fd.xyz) and sign in through the OAuth flow the server triggers. Do not attempt to proceed without it.getMyInfo MCP tool once. If it returns an auth error, tell the user to complete the OAuth flow in their MCP client, then retry. If it returns the wallet identity, proceed.https://medusa.test.1stdigital.tech). Do not invent one.Request-Id and (for POST/PUT) an Idempotency-Key. uuidgen does not exist on Windows. Detect once at session start, use for all calls. See references/unique-ids.md for the detection order and one-liners (Python is the most portable default).Before touching the store, scan the user's first message for things they already told you. Store them as session context and reuse silently:
{ amount, currency }. If not given, no ceiling is implied — don't invent one.These feed §4.2 (catalog picks) and §4.3 (balance check). Don't re-ask for anything you can read from the opening message.
These protect the user's money. Follow them every time, regardless of protocol.
xyz.fd.prism_payment. If it doesn't, refuse to pay.complete, show the user: line items, shipping, grand total with currency, shipping destination, and the payment network + asset + human-readable amount. Require explicit "yes, charge $X" confirmation.First action of any flow: detect which protocol the merchant speaks.
# Try ACP first (handlers populate at discovery, cheap signal)
curl -s -w "\n%{http_code}" "$MERCHANT/.well-known/acp.json"
# If that 404s or doesn't return a valid body, try UCP
curl -s -w "\n%{http_code}" "$MERCHANT/.well-known/ucp"
ACP signature — response shape:
{ "protocol": { "name": "acp", "version": "2026-01-30" },
"api_base_url": "<base>/acp",
"capabilities": { "payment": { "handlers": [ { "id": "xyz.fd.prism_payment", ... } ] } } }
UCP signature — response shape:
{ "ucp": { "version": "2026-01-11",
"services": { "dev.ucp.shopping": [{ "endpoint": "<base>/ucp" }] },
"payment_handlers": { ... } } }
If the merchant publishes both (our demo store medusa.test.1stdigital.tech does), prefer UCP — it requires no API key and has richer browse endpoints (JSON search + lookup vs ACP's RSS product feed).
Set $PROTOCOL to ucp or acp and branch. For the wire-level detail of each protocol, read the matching reference file only:
The steps below are the same regardless of protocol; only the request/response shapes change (see the wire docs).
Fetch the discovery doc. Confirm xyz.fd.prism_payment is advertised. If not, refuse.
POST $UCP/catalog/search and POST $UCP/catalog/lookup (both JSON). See ucp-wire.md.GET $ACP/../product-feed returns a Google Shopping–style RSS/XML feed. Parse it to get product ids, titles, prices, and links. There is no JSON search endpoint. See acp-wire.md.Present 2–3 thoughtful picks (title, price, why-this-one). Ask which to buy.
Respect the budget ceiling silently. If the user told you a ceiling in §1, filter picks so the proposed combined total stays under it (convert if currencies differ — use a ~5% buffer for EUR↔USD etc.). Don't narrate "I'm filtering for budget"; just propose things that fit. If nothing reasonable fits, surface the conflict — not as a question, but as a clear statement: "Everything here starts at €80 — over your $50 ceiling. Want to raise the ceiling or look elsewhere?"
Once the user has picked their items, estimate the subtotal (items only — shipping is typically small, and we'll get an exact figure later). Then read the wallet's balances and sanity-check the cart is payable, the way a real shopper would before walking to the counter.
Steps:
Estimate the subtotal. Sum unit_price × quantity from the items you'll add to the cart. Use the cart's currency (e.g. EUR). Don't worry about shipping/tax yet — add a small buffer (~10%) to cover them.
Read the wallet. Call the getWalletOverview MCP tool (no chainKey to get all chains, or loop over the chains the merchant likely supports — Base, Arbitrum, Ethereum + their sepolias for testnet merchants). For each held stablecoin, you have { asset, network, balance_atomic, decimals } — convert to major units.
Pick the preferred asset by currency match. Reduce FX slippage by picking a stablecoin pegged to the cart's currency when the wallet has enough of it:
Walk the preference list in order. Take the first asset/chain combination where the wallet balance covers the subtotal + 10% buffer. Remember this pick — it flows into §4.7 (confirm) and §4.8 (authorize).
Compare against budget. If the subtotal (converted to the user's ceiling currency) exceeds the user's stated budget ceiling from §1, treat it as a conflict — surface it and offer to trim, same as an insufficient balance case.
Decide.
Surfacing an insufficient-balance situation (§0-friendly):
I checked what your wallet has — looks like you're short for this cart. You have 20 EURC (on Base Sepolia) and 20 USDC (on Base Sepolia), but the cart is €42.
Want to trim it down (e.g. mug only — €15), or top up first? Your wallet address is
0x318c…C806.
Do not say "insufficient balance", "chain support", "atomic units", or "stablecoin decimals" — the §0 rule applies here too.
Caveats:
authorizePayment picks an entry. If the pre-flight passes but authorizePayment fails (shipping pushed the total over, rate moved, etc.), fall through to the same surfacing pattern above.getWalletOverview itself errors (auth issue, wallet not set up), tell the user to re-auth via their MCP client — don't silently skip the check.Only proceed to §4.4 once the cart looks payable and the chosen asset/chain is noted for §4.7 and §4.8.
Don't just ask. A good personal shopper remembers the customer. Before asking the user to type anything, check available memory sources for their details and propose them for confirmation.
Prefer the agent's native memory. Fall back to a skill-local file only if none is available.
Most modern agent runtimes (Claude.ai, Claude Code, Cursor, OpenClaw, etc.) provide a memory mechanism — a native Memory tool, hierarchical CLAUDE.md, a user-profile KB, or similar. That memory is shared across skills and sessions, is the user's one source of truth, and is how they already manage their own info. The skill should use it, not duplicate it.
Check, in order:
CLAUDE.md (~/.claude/CLAUDE.md, project CLAUDE.md); native Memory tool if exposed.~/.claude/skills/fd-agentic-commerce/profile.md. Fallback only. Use this when the harness has no memory system, or when the user explicitly asked to keep shopping details separate from their general agent memory. See references/user-profile.md for the format.git config user.email, git config user.name, $USER. Useful for first-time users; weak signal, always confirm.Present, don't ask — once you have candidates, propose them:
I have your details on file:
- Name: Janno Jarv (from your profile)
- Email: [email protected]
- Ship to: Järveoja tee 8, Veibi, 52220, Estonia
- Phone: +372 5803 3175
Use these, or give me new ones?
If you have nothing on file, then ask — but ask compactly in one message, not a bulleted list of six questions. Give context: "I'll need shipping details. Share a name + address block (one line is fine) and I'll parse it."
After checkout succeeds, offer to save or update if info was new:
Want me to remember this address as "home" for next time? (yes/no)
If yes, write to the same place you read from:
profile.md when no harness memory is available.Never save silently. Never save without explicit user consent. See references/user-profile.md for the fallback file format.
POST $UCP/checkout-sessions with line_items[].item.id + quantity, optional buyer and shipping_address.POST $ACP/checkout_sessions with line_items[].id + quantity, optional buyer and fulfillment_details.address. Requires Authorization: Bearer <key> and Api-Version: 2026-01-30.Capture the session id.
PUT $UCP/checkout-sessions/{id}POST $ACP/checkout_sessions/{id} (yes, POST — ACP uses POST for updates)Wait for status: "ready_for_complete".
Plain-English summary. Include the payment asset/chain picked in §4.3, and — if the user gave a budget ceiling in §1 — a one-line budget status.
Order from <merchant>:
• <qty>× <title> — <price>
Shipping to <city, country>: <shipping>
Total: <currency> <grand_total>
Paying <human_amount> <asset_name> from your <network> balance.
<(optional)> That's ~<converted> <ceiling_currency>, <under|at|over> your <ceiling> ceiling.
Confirm? (yes/no)
Examples:
Paying 42 EURC from your Base Sepolia balance.Paying 45 USDC from your Base Sepolia balance. That's ~$45, under your $100 ceiling.Do not turn the asset choice into a separate question — it was already decided in §4.3 and this is informational only. The "yes/no" is on the purchase, not on the asset.
If total > $500 USD-equivalent, require the user to type back the exact amount (safety rule §2.5).
Call the authorizePayment MCP tool. It expects a full x402 PaymentRequirementsResponse envelope as a JSON-serialized string — not a single accepts[] entry. Given the envelope, the wallet picks an entry to sign against when autoApprove: true.
The merchant gives you the full envelope at ucp.payment_handlers["xyz.fd.prism_payment"][i].config (top-level x402Version, resource, and accepts[]).
Narrow the envelope to match the asset you chose in §4.3 so the wallet's autoApprove is deterministic.
Critical: do NOT rebuild the envelope from scratch. Clone the merchant's envelope, then replace only the accepts array. The wallet's parser has resource as a required top-level field — if you reconstruct the envelope and forget it, the tool errors before it ever reads your accepts[].
Correct pattern (pseudocode):
envelope = deepClone(prism_checkout_config.config) // copies x402Version, resource, error, accepts, ...
envelope.accepts = envelope.accepts.filter(e =>
e.extra.name === chosenAssetName && // e.g. "EURC"
e.network === chosenNetwork // e.g. "eip155:84532"
)
// envelope.x402Version, envelope.resource, envelope.error — unchanged
authorizePayment({ paymentRequirementsResponseJson: JSON.stringify(envelope), autoApprove: true })
Incorrect (what the wallet rejects with missing required property 'resource'):
{ x402Version: 2, accepts: [...] } // ← rebuilt from scratch, resource dropped
If the filter leaves accepts[] empty (shouldn't happen if §4.3 did its job), fall back to the unmodified envelope and let autoApprove pick.
Arguments:
paymentRequirementsResponseJson — string-encoded JSON of the full response envelope (top-level x402Version, resource: { url, description }, accepts: [...])autoApprove — true to let the wallet pick the best accepts[] entry from what you can actually payThe tool returns an object containing a signed paymentPayload and the specific paymentRequirements entry the wallet chose. Use BOTH in the UCP complete call, or (for ACP) pass the whole returned object as the base64-encoded credential.authorization.
If authorizePayment errors with an insufficient-balance or no-matching-option reason, call getMyInfo to get the wallet address, call getWalletOverview to confirm what's held, and relay to the user: "to pay, you need <asset> on <network>; your wallet address is 0x…".
For envelope shape, network/asset tables, and atomic-unit conversion, see references/payment-payload.md.
POST $UCP/checkout-sessions/{id}/complete with payment.instruments[] carrying both paymentPayload and paymentRequirements.POST $ACP/checkout_sessions/{id}/complete with payment_data.instrument.credential.authorization (the EIP-3009 auth string) and x402_version.Exact bodies in the wire docs.
Show: order.id, tx_hash, block-explorer link if derivable from CAIP-2 network id, and any receipt URL. Tell the user delivery is on its way.
Both protocols return structured errors with severity levels:
| Severity | Action |
| --- | --- |
| recoverable | Fix and retry (usually PUT/POST a missing field) |
| requires_buyer_input | Ask user, then retry |
| requires_buyer_review | Re-show summary, re-confirm |
| requires_escalation | Stop; surface the full message |
| unrecoverable | Session is dead; offer to start over |
Retry budget: 3 recoverable retries per session, then ask the user.
For protocol-specific error shapes and common codes, see references/error-recovery.md.
In scope: single merchant, single checkout, full browse-to-pay on either UCP or ACP.
Out of scope (v1):
claude-code/1.0 — set to whatever identifies your harness.Load only the one you need for the current step.
accepts[] anatomy, CAIP-2 networks, token decimals, selection heuristicsuuidgen)~/.claude/skills/fd-agentic-commerce/profile.md is documented there. Read before asking for shipping; write only with user consent.tools
Manage agent crypto wallets and merchant payments via the Finance District platform (fdx CLI). Use when the user mentions wallets, tokens, crypto, DeFi, or merchant payments. Wallet operations — create/setup wallet, send/receive/transfer tokens, swap/trade/exchange crypto, check balance/portfolio, token prices, earn yield, stake, deposit/withdraw from DeFi vaults, x402 payments, pay for API services, fund wallet, bridge tokens, supported chains. Prism merchant operations — Points of Service (POS), create/list/manage API keys, settlement wallets, payment history, list payments, earnings, revenue, staff management (invite staff, revoke staff access, grant/revoke POS access for staff, list staff members). Trigger phrases include "set up wallet", "send ETH", "swap tokens", "check my balance", "show portfolio", "how much do I have", "earn yield", "buy/sell crypto", "use fdx", "create API key", "point of service", "payment history", "Prism", "invite staff", "revoke staff", "staff access", "manage staff", "list staff". Covers EVM chains (Ethereum, Base, Polygon, Arbitrum, Optimism), Solana, and Bitcoin. Do NOT use for general payment processing unrelated to Finance District or fdx.
tools
Use when work should span one or more detached tasks but still behave like one job with a single owner context. TaskFlow is the durable flow substrate under authoring layers like Lobster, ACPX, plugins, or plain code. Keep conditional logic in the caller; use TaskFlow for flow identity, child-task linkage, waiting state, revision-checked mutations, and user-facing emergence.
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------