---
name: cartfun
description: Buy from and run stores on cart.fun, where every sale prints a receipt NFT. Use when asked to shop a cart.fun store, quote or pay an order on-chain, check a receipt or review, or manage a cart.fun store's products, invites, sales log or webhooks with an API key.
---

# cart.fun

cart.fun is on-chain commerce: stores sell for stablecoins through a CartCheckout contract, and every sale prints a receipt NFT (proof of purchase, warranty, refunds) plus a cart NFT holding any on-chain goods. Buyer agents discover stores, get signed quotes and pay on-chain. Merchant agents run a store's catalog, invites, sales log and webhooks with API keys.

API base: `https://go.cart.fun`. When unsure about an endpoint, fetch `https://go.cart.fun/openapi.json`; for the full guide, `https://go.cart.fun/llms-full.txt`.

| Network | Chain id | Slug for URLs |
| --- | --- | --- |
| Base Sepolia | 84532 | `base-sepolia` |
| Robinhood Chain Testnet | 46630 | `robinhood-testnet` |
| Arc Testnet | 5042002 | `arc-testnet` |

## Rules
- Never put an API key (`cf_sk_…`) in client-side code, logs or chat output. Read it from the environment (e.g. `CARTFUN_API_KEY`).
- Quotes are signed for one buyer and expire; get a fresh one rather than editing it. Pass `order`, `items` and `signature` to the contract unchanged.
- Confirm with the user before any on-chain transaction (approve, checkout) and before deleting products, invites or webhooks.
- On 429, wait `Retry-After` seconds. Each key gets 120 requests a minute.
- Verify webhook signatures and dedupe on the event id before acting on one.

### Buy something
1. **Discover the store.** `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/agent` returns its agent card. Read `cartfun.commerce`: `orderable`, `payment` (token, symbol, decimals), `checkout` (contract, version), and for public stores `catalog.products` with `price` (decimal) and `priceUnits` (base units). Invite-only stores list no products; ask the merchant for an API key with the `orders` scope.
2. **Quote.** `POST https://go.cart.fun/api/v1/orders` with `{ chainId, storeId, buyer, lines: [{ id: sku, quantity }] }`. The server prices the lines from the catalog and returns an order signed by the store, valid for 10 minutes and fillable only by `buyer`.
3. **Approve.** From the buyer wallet, approve the checkout contract (`quote.checkout`) to spend `order.amount + order.serviceFee` of `order.paymentToken`. Tokens with EIP-2612 permit can use `checkoutWithPermit` instead.
4. **Pay.** Call `checkout(order, items, cartRecipient, signature)` on `quote.checkout` with the quote's `order`, `items` and `signature` exactly as returned (numbers as uint256). `cartRecipient` receives the cart NFT, usually the buyer. On a version 1 checkout, drop `serviceFee` from the order struct.
5. **Confirm.** The `CheckoutCompleted` event carries the `receiptId`. `GET https://go.cart.fun/api/v1/receipts/{chain}/{receiptId}` shows the receipt; `GET …/order` with header `x-order-token: <quote.viewToken>` shows its line items.

```ts
import { createWalletClient, http, parseAbi, erc20Abi } from "viem";

// wallet: a viem WalletClient for the buyer's account on the store's chain
const API = "https://go.cart.fun";
const CHECKOUT_ABI = parseAbi([
  "struct Order { uint16 storeId; address buyer; address paymentToken; uint256 amount; uint256 serviceFee; bytes32 itemsHash; uint256 nonce; uint256 deadline; }",
  "struct Item { uint8 kind; address token; uint256 id; uint256 amount; }",
  "function checkout(Order order, Item[] items, address cartRecipient, bytes signature) returns (uint256 receiptId, uint256 cartId)",
]); // version 1 checkouts: the same without serviceFee

const quote = await fetch(`${API}/api/v1/orders`, {
  method: "POST",
  headers: { "Content-Type": "application/json" }, // + Authorization: Bearer cf_sk_… for invite-only stores
  body: JSON.stringify({ chainId: 84532, storeId: 3, buyer: account.address, lines: [{ id: 1, quantity: 1 }] }),
}).then((r) => r.json());

const o = quote.order;
const order = { ...o, amount: BigInt(o.amount), serviceFee: BigInt(o.serviceFee), nonce: BigInt(o.nonce), deadline: BigInt(o.deadline) };
const items = quote.items.map((i) => ({ ...i, id: BigInt(i.id), amount: BigInt(i.amount) }));

await wallet.writeContract({ address: o.paymentToken, abi: erc20Abi, functionName: "approve", args: [quote.checkout, order.amount + order.serviceFee] });
await wallet.writeContract({ address: quote.checkout, abi: CHECKOUT_ABI, functionName: "checkout", args: [order, items, account.address, quote.signature] });
```

### Run a store
1. **Get a key.** The store owner creates one in the cart.fun dashboard (Developers tab), choosing its scopes. Send it as `Authorization: Bearer cf_sk_…` from a server, never a browser. Keys are per store and stop working when revoked or when the store changes owner.
2. **Catalog.** `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/products` with `{ products: [{ name, type, price, active, onchain? }] }`; `PATCH …/products/{sku}`; `DELETE …/products/{sku}`. Prices are decimal strings in the store's payment token.
3. **Webhooks.** `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/webhooks` with `{ url, events? }` returns a signing secret once. Verify every delivery (below), dedupe on the event `id`, answer 2xx quickly.
4. **Sales.** `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/sales` lists receipts. After sending a refund, void or redeem on-chain, log it with `POST …/actions` so the reason shows in the dashboard.
5. **Access.** `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/invites` makes the store invite-only (6-digit codes); deleting every code opens it again. Agents with an `orders` key get through regardless.

```bash
curl https://go.cart.fun/api/v1/stores/base-sepolia/3/products \
  -H "Authorization: Bearer cf_sk_…"
```

## Auth
Public reads need nothing. Store APIs take an API key (`Authorization: Bearer cf_sk_…`) with the right scope. Creating and revoking keys takes the store owner's wallet session (`Authorization: CartSig <base64 {message, signature}>`), which agents rarely need.

| Scope | Grants |
| --- | --- |
| `orders` | Get quotes from, and read the catalog of, this store even while it's invite-only (agents, checkout servers) |
| `catalog` | Read every product (hidden ones too) and create, edit and delete products |
| `sales` | Read the refund/void/redeem log, log new actions, and see receipts' line items |
| `invites` | List, create, edit and delete invite codes |
| `webhooks` | Manage webhook endpoints, send test events and see deliveries |

Errors are JSON `{ "error": "…" }` with a meaningful status: 400 bad input, 401 no or bad credential, 402 store not activated, 403 wrong store or missing scope, 404 not found, 409 conflict, 429 rate limited (honour `Retry-After`). Each key gets 120 requests a minute.

## Webhooks
cart.fun POSTs a JSON envelope `{ id, type, created, chainId, storeId, data }` to your endpoint. On-chain events fire once their block has a few confirmations; failed deliveries retry with backoff for about two days. The `cartfun-signature` header is `t=<unix>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>`.

| Event | When |
| --- | --- |
| `order.quoted` | cart.fun signed a quote from your catalog (before payment) |
| `order.paid` | A receipt was printed: the order is paid. Includes the order's items when cart.fun quoted it |
| `receipt.refunded` | A receipt was marked refunded |
| `receipt.voided` | A receipt was voided |
| `receipt.redeemed` | A receipt was redeemed (picked up or used) |
| `receipt.transferred` | A receipt's warranty moved to a new holder |
| `receipt.serviced` | A service record was added to a receipt |
| `review.created` | A buyer reviewed your store (ERC-8004), with whether it checked out against their receipt |
| `review.replied` | Your store replied to a review |

```js
// Node: verify a cart.fun webhook before trusting it
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyCartfun(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false; // stale: possible replay
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const given = Buffer.from(parts.v1 ?? "", "hex");
  return given.length === 32 && timingSafeEqual(given, Buffer.from(expected, "hex"));
}

// verifyCartfun(await req.text(), req.headers.get("cartfun-signature"), process.env.CARTFUN_WEBHOOK_SECRET)
// Events can arrive more than once: dedupe on the body's "id" (also in the cartfun-event-id header).
```

## Endpoints

### Discover
- `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/agent` (No auth): The store's ERC-8004 agent card. cartfun.commerce says whether it takes orders, what it's paid in, how to quote and pay, and (public stores) every orderable product with prices in base units.
- `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/products` (Optional API key (orders)): The store's catalog. Invite-only stores need an API key with the orders scope (or a browser pass). A key with the catalog scope also sees hidden products.
- `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/reviews` (No auth): ERC-8004 reviews, each checked against the receipt it names, and the verified score.
- `GET https://go.cart.fun/api/v1/signer` (No auth): The platform order signer. A store must register this address as its signer to get quotes.
- `GET https://go.cart.fun/api/v1/service-fee` (No auth): The buyer service-fee policy on a network, to preview fees before quoting. Null where the checkout can't charge one.
- `GET https://go.cart.fun/api/v1/stores/{chain}/summary` (No auth): Sales totals per store on a network, from the public receipt index.

### Buy
- `POST https://go.cart.fun/api/v1/orders` (Optional API key (orders)): Prices the lines from the catalog and returns an order signed by the store. The buyer then pays it on-chain (see the checkout flow). Quotes expire after 10 minutes and only `buyer` can fill them.

### Receipts & reviews
- `GET https://go.cart.fun/api/v1/receipts/{chain}/{id}` (No auth): A receipt NFT's on-chain record: store, purchaser, holder, amount paid and lifecycle state.
- `GET https://go.cart.fun/api/v1/receipts/{chain}/{id}/order` (Optional API key (sales)): The order behind a receipt, proven by its on-chain commitment. Everyone gets the money breakdown; line items need the quote's x-order-token, the buyer's or holder's session, or an API key with the sales scope.
- `GET https://go.cart.fun/api/v1/receipts/{chain}/{id}/svg` (No auth): The receipt's rendered image (SVG).
- `GET https://go.cart.fun/api/v1/reviews/comment` (No auth): How long a review comment may be, and whether long ones can be pinned to IPFS here.
- `POST https://go.cart.fun/api/v1/reviews/comment` (No auth): Pins a long review comment to IPFS and returns its ipfs:// URI and keccak256 hash for the review's feedback file.

### Catalog
- `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/products` (API key or owner session (catalog), activated store): Creates products. Rejects the whole batch if any sku is taken.
- `PATCH https://go.cart.fun/api/v1/stores/{chain}/{storeId}/products/{sku}` (API key or owner session (catalog), activated store): Replaces a product's fields; the sku stays.
- `DELETE https://go.cart.fun/api/v1/stores/{chain}/{storeId}/products/{sku}` (API key or owner session (catalog), activated store): Deletes a product.

### Sales
- `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/sales` (No auth): Every receipt the store has printed, newest first. Public: receipts are on-chain.
- `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/sales/refresh` (No auth): Re-reads up to 50 of the store's receipts from chain now, after a redeem, refund or void lands.
- `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/actions` (API key or owner session (sales)): The store's refund, void and redeem log, with reasons and notes.
- `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/actions` (API key or owner session (sales), activated store): Logs refunds, voids or redemptions you just sent on-chain, attributed to your key.

### Invites
- `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/invites` (API key or owner session (invites)): The store's invite codes. While a store has any, it's invite-only.
- `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/invites` (API key or owner session (invites), activated store): Creates an invite code (generated when `code` is omitted).
- `PATCH https://go.cart.fun/api/v1/stores/{chain}/{storeId}/invites/{code}` (API key or owner session (invites), activated store): Changes an invite's label, use limit, expiry or active flag.
- `DELETE https://go.cart.fun/api/v1/stores/{chain}/{storeId}/invites/{code}` (API key or owner session (invites), activated store): Deletes an invite code. Deleting every code makes the store public.
- `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/access` (No auth): Checks an invite code for a browser and sets a 30-day pass cookie. Agents should use an API key with the orders scope instead.

### Webhooks
- `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/webhooks` (API key or owner session (webhooks)): The store's webhook endpoints and its 50 latest deliveries.
- `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/webhooks` (API key or owner session (webhooks), activated store): Adds an endpoint. The response carries its signing secret, shown this once.
- `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/webhooks/{id}` (API key or owner session (webhooks)): One endpoint's 50 latest deliveries.
- `PATCH https://go.cart.fun/api/v1/stores/{chain}/{storeId}/webhooks/{id}` (API key or owner session (webhooks)): Changes an endpoint's URL, description, events or active flag, or rotates its secret (the new one comes back once).
- `DELETE https://go.cart.fun/api/v1/stores/{chain}/{storeId}/webhooks/{id}` (API key or owner session (webhooks)): Deletes an endpoint and its delivery history.
- `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/webhooks/{id}/test` (API key or owner session (webhooks)): Sends a `ping` event to the endpoint now.
- `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/webhooks/deliveries/{deliveryId}/retry` (API key or owner session (webhooks)): Sends a delivery again now, whatever happened to it before.

### Keys & activation
- `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/keys` (Owner wallet session): The store's API keys (never their secrets). Key management always takes the owner's wallet session.
- `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/keys` (Owner wallet session, activated store): Creates a key. The response carries the secret, the only time it's returned.
- `DELETE https://go.cart.fun/api/v1/stores/{chain}/{storeId}/keys/{id}` (Owner wallet session): Revokes a key at once.
- `GET https://go.cart.fun/api/v1/stores/{chain}/{storeId}/activation` (No auth): Whether the store is activated for cart.fun orders, and the fee to activate it.
- `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/activation` (No auth): Activates the store from its fee payment. The payment must come from the store's owner or treasury.
- `POST https://go.cart.fun/api/v1/stores/{chain}/{storeId}/agent` (No auth): Links a registered ERC-8004 agent to the store. Checked on-chain: the agent's cartfun.store metadata must name the store, and the store's owner must own the agent.
