---
name: fast-shop
version: 2.0.1
description: >
  Buy real physical products and have them delivered to your door, paid from
  the user's Fast wallet. Use for: "buy X", "order X", "purchase X", "shop for
  X", "find product", "search for X", "compare prices", "track my order",
  "cancel order", "how much does X cost". Triggers on product shopping,
  purchasing, price lookup, order tracking, and cancellation requests.
  NOT for: digital products, gift cards, or FAST SDK and wallet setup in code
  (use the fast skill).
---

# Fast Shop

Fast Shop (shop.fast.xyz) finds products across the merchants listed on its marketplace and buys them with fastUSD. Everything runs through the **Fast Shop MCP server**, `@fastxyz/mcp`: it searches, quotes, pays (x402 on Fast) and tracks orders. Don't hand-roll HTTP calls or payments.

## Setup

Add the MCP server to the agent's client:

```json
{
  "mcpServers": {
    "fast-shop": { "command": "npx", "args": ["-y", "@fastxyz/mcp@latest"] }
  }
}
```

Node.js 22+. Payments are real: fastUSD on Fast mainnet by default. `FAST_NETWORK=testnet` only switches the wallet tools (balance, transfers). A merchant's quote can still ask for a `fast-mainnet` payment, so when testing, use a merchant that quotes on testnet and check the quote's `payment_network` before confirming.

## The wallet

Call `fast_onboard` before saying anything about wallets. It reports the wallet in use, its balance, and where it came from (`source`):

- **`fast_cli`**: the **Fast CLI's** account on this machine, the same wallet the `fast` CLI skill uses. `cliAccount` names it. This is how a wallet gets set up (see the rules below). Its settings, in the server's environment:
  - `FAST_CLI_ACCOUNT` to use another CLI account.
  - `FAST_PASSWORD` if that account is password-protected (`WALLET_LOCKED` otherwise).
  - `FAST_CLI_BIN` if `fast` isn't on the server's `PATH`.
  - `FAST_MCP_CLI_WALLET=0` turns the CLI source off; then the wallet is set up as a keyfile instead (see the rules below).
- **`keyfile`**: the JSON keyfile at `details.keyfilePath`, either written there by the human (the setup when the CLI source is off) or saved by `fast_wallet_import`. When it exists, it takes precedence over the CLI account.

Rules:

- **Never ask for or invite a private key, seed phrase or password in the chat.** Anything typed into the conversation passes through the model provider and the client's transcript and logs before the server sees it.
- When `fast_onboard` returns `WALLET_NOT_FOUND`, the human sets the wallet up **outside the chat, in their own terminal**: `fast account create` for a new wallet (then fund it), or `fast account import --key-file <path>` to use an existing key, such as the Agent Wallet key from https://app.fast.xyz/ saved as a JSON file with a `privateKey` field. Then call `fast_onboard` again: it reports `source: fast_cli`.
- If `WALLET_NOT_FOUND` doesn't have `details.fast_cli_checked: true`, this server ignores the Fast CLI (`FAST_MCP_CLI_WALLET=0`). Then the human writes the key themselves, outside the chat, as the JSON file at `details.keyfilePath` (`{"privateKey": "<64 hex>"}`), creating its folder and the file owner-only first (for example `install -d -m 700 "$(dirname <path>)" && install -m 600 /dev/null <path>`, then writing the key into it); the server reads it on the next call (`source: keyfile`).
- `fast_wallet_import` is only for a key the human has already pasted on their own. Never suggest it; if it happens, tell them not to paste keys into a chat again.
- This server never generates keys, and won't replace a wallet in use: importing a different key while one is loaded returns `WALLET_ALREADY_EXISTS`.
- Funding: when the balance is short, the response carries a `funding_url` (`https://app.fast.xyz/fund?to=<address>[&amount=<USD>]`). Give it to the human as is; they pick card, crypto or an existing balance there.

## Flow

```
merchants → search → details → quote → confirm → pay → track
```

1. **Which stores.** The merchant set changes over time. Name stores only from `fast_shop_list_merchants`, never from memory. Copy each merchant's `shopId` from its result verbatim into the `shop_id` argument of the other tools.
2. **Search** with `fast_shop_search`, then `fast_shop_get_product` for details, variants and availability.
3. **Pick the checkout path** from the merchant's `checkoutModes`:
   - includes `fast`: by default `fast_shop_quote` then `fast_shop_create_order` (paid from the Fast wallet);
   - `fast_shop_handoff_checkout` returns the merchant's own checkout link, where the buyer pays by card. Use it when `checkoutModes` is only `self`, and also when the merchant supports `self` and the buyer opts out of paying from the wallet ("I'll use my own card", "send me the link", or rejecting the quoted total). Ask first ("Shall I open the merchant's checkout page?") and call it with `confirmation: true` only after the buyer's explicit yes in a separate turn. After giving the link, stop: don't quote or pay, and don't claim anything about the order, since Fast takes no payment there and doesn't track it.
4. **Quote** with a full shipping address: first and last name, street, city, 2-letter state, postal code, the 2-letter country code (`US`, `BR`…; the server assumes `US` when it's left out, so always pass it) and a phone number (10+ digits; country code for non-US). Some non-US merchants also need the street number as its own field and the neighborhood (Brazil needs both). Some non-US merchants also need identity fields (`BUYER_IDENTITY_REQUIRED` says which); ask the buyer for exactly those.
5. **Confirm.** Show the quote's `note` breakdown and any `delivery_options`, then wait for an explicit "yes", "confirm" or "pay" in a separate turn. The total includes a budget for shipping and tax; unused budget is refunded.
6. **Pay** with `fast_shop_create_order`, passing `quote_id` exactly as returned and `confirmation: true` (only after that explicit yes), **once**. Never pay with `fast_wallet_send`: that is for plain transfers.
7. **Track** with `fast_shop_get_order` / `fast_shop_list_orders`. To cancel, name the order and wait for the human's explicit "yes, cancel it" in a separate turn, then call `fast_shop_cancel_order` with `confirmation: true`.

## Order states

The same states as the marketplace's status contract (`/llms.txt` on shop.fast.xyz):

- **Confirmed:** only `placed` means the order is confirmed; `shipped` and `delivered` come later and are confirmed too. `placed` can move on to `shipped` or `delivered`.
- **Still in progress:** `pending`, `submitted`, `processing` and `submission_unknown`. Say so, and check again in about a minute. Don't say "ordered" or "shipped" before `placed`.
- **Not confirmations, and they don't go back to in progress:** `paid_unconfirmed`, `support_needed` and `self_checkout_opened` are not confirmations: report them as they are.
  - `paid_unconfirmed`: the payment went through but the merchant hasn't confirmed the order; check again later.
  - `support_needed`: point the human to the merchant's support with the order ID.
  - `self_checkout_opened`: the buyer was sent to the merchant's own checkout; Fast can't see whether they finished.
- **Final:** `failed` (the order didn't go through), `cancelled` (it was cancelled), `refunded` (the payment was returned), and `delivered`. They don't regress on stale updates, but an authoritative refund or correction can still update them (`delivered` can become `refunded`).
- Report `refund_status` separately from the order status, and normalize the aliases a merchant may still send: `in_progress` means `processing`, `canceled` means `cancelled`, and an ambiguous `unknown` or `waiting` means `submission_unknown`.

## If paying fails

The Fast transfer can settle before the merchant answers. **Any** error from `fast_shop_create_order` is therefore an unresolved payment, not a refusal: `SHOP_AUTH_FAILED`, `X402_PAYMENT_FAILED`, `SHOP_QUOTE_EXPIRED`, `SHOP_RATE_LIMITED`, a network error, even one that comes with a `funding_url`. The one exception is `CONFIRMATION_REQUIRED`: it comes back before anything is paid, because `confirmation: true` was missing; get the buyer's explicit yes and call it again with `confirmation: true`. A funding link only says the balance is low *now*, which is also what a payment that went through looks like. Calling `fast_shop_create_order` again can pay twice. So never call it again on your own; reconcile instead:

1. Check `fast_shop_list_orders` / `fast_shop_get_order` for an order on that quote. If there is one, the payment went through: report its status and track it.
2. If there is none, the outcome is **unknown**. No balance reading settles it: a lower balance can be other spending, and an unchanged one can hide a payment behind a deposit. Tell the human exactly that, with the quote ID, and don't claim the order went through or that it didn't. Check the order status again in a minute; if it stays unclear, point them to the merchant's support with the quote ID.
3. Don't pay again on your own. Pay again only if the human, told that this could pay twice, explicitly chooses to, and then with a fresh quote and their confirmation (after a top-up if the balance is short).

`fast_onboard`'s balance is not evidence either way: it reports `0` when the lookup fails.

## Pitfalls

- **Automatic retries are only for calls that don't pay:** search, product details, quotes, order status. `SHOP_AUTH_FAILED` on those is not a missing key: search again for fresh merchant hints and retry once.
- On `SHOP_QUOTE_EXPIRED` from `fast_shop_quote` or before you paid, quote again and re-confirm. From `fast_shop_create_order`, follow "If paying fails".
- Never print or log private keys, or the contents of `~/.fast/keys/` or `~/.fast/fast.db`.

## HTTP, for reference

`https://shop.fast.xyz` serves discovery only: `GET /commerce/shops`, `GET /commerce/search?q=…`, `GET /commerce/products/:productId` and `/llms.txt`. The marketplace places no orders and holds no funds. Orders go to each merchant's own adapter, using signed marketplace hints, an address-signed bearer token and an x402 payment, which is what the MCP server does. Use the server rather than reimplementing that.
