Protocol Architecture

Cart.fun Developer Documentation

cart.fun enables fully autonomous on-chain commerce for humans and AI agents. Merchants sell physical or digital goods for stablecoins, and every completed sale prints a permanent proof-of-purchase receipt NFT.

Key Principles:
  • Optional Escrow: By default funds go straight to the merchant's treasury at checkout. Stores can instead hold the payout in escrow (quotes carry a non-zero escrowHold), released on redemption or when the hold ends, and refunded to the buyer if the store refunds first.
  • Cryptographic Quotes: All checkouts require EIP-712 store-signed quotes valid for 10 minutes.
  • Proof of Purchase: Receipts are non-transferable NFTs containing verifiable hash commitments of the order. On CartCheckoutV4 the commitment also covers the order's line items (linesHash), which lead to a store-signed catalog snapshot.
  • Agent Friendly: Standardized ERC-8004 agent cards, OpenAPI 3.1, and autonomous tool calling skills.
Supported Networks
Base Sepolia, Robinhood Chain Testnet, Arc Testnet
API Rate Limits
120 requests / min per key
Getting Started

Quickstart: Read Stores & Check Quotes

Anyone can query active store agent cards and catalog listings without an API key:

# 1. Fetch live store ERC-8004 agent card
curl -s https://go.cart.fun/api/v1/stores/base-sepolia/3/agent | jq .

# 2. Query public catalog
curl -s https://go.cart.fun/api/v1/stores/base-sepolia/3/products | jq .
Buyer Agent Guide

Autonomous Checkout with Viem / Wagmi

AI Agents and client dApps execute purchases through four programmatic steps:

  1. Discover Store: Browse /api/v1/stores/directory or resolve a cart.fun/@handle with /api/v1/handles/{handle}, then inspect /api/v1/stores/{chain}/{storeId}/agent to retrieve accepted payment tokens and the active CartCheckout address.
  2. Request Signed Quote: POST /api/v1/orders with the buyer wallet address and SKU line items. The response contains an EIP-712 signature generated by the store signer, and version (3 or 4) says which CartCheckout it is signed for. Limited-stock products return 409 when too few units are left.
  3. ERC-20 Approval: Approve the checkout contract for order.amount + order.serviceFee + order.relayFee of order.paymentToken.
  4. Execute Checkout: Call checkout(order, items, recipient, signature) on-chain. V4 orders carry an extra trailing linesHash field in the Order struct; pass the quote's order through unchanged.
  5. Verify (V4): GET /api/v1/receipts/{chain}/{id}/order returns linesHash and, to the buyer, holder and store, the linesPreimage that must keccak256 to it and names the signed catalog snapshot the lines were priced from.
import { createWalletClient, http, parseAbi, erc20Abi } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { baseSepolia } from "viem/chains";

const account = privateKeyToAccount(process.env.BUYER_PRIVATE_KEY as `0x${string}`);
const wallet = createWalletClient({ account, chain: baseSepolia, transport: http() });

const API = "https://go.cart.fun";
// V3 orders (quote.version 3). V4 orders (quote.version 4) end with "bytes32 linesHash" in the struct.
const CHECKOUT_ABI = parseAbi([
  "struct Order { uint16 storeId; address buyer; address paymentToken; uint256 amount; uint256 serviceFee; bytes32 itemsHash; uint256 nonce; uint256 deadline; uint32 escrowHold; uint256 relayFee; }",
  "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)",
]);
const CHECKOUT_ABI_V4 = parseAbi([
  "struct Order { uint16 storeId; address buyer; address paymentToken; uint256 amount; uint256 serviceFee; bytes32 itemsHash; uint256 nonce; uint256 deadline; uint32 escrowHold; uint256 relayFee; bytes32 linesHash; }",
  "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)",
]);

// 1. Request signed price quote from cart.fun API
const quote = await fetch(`${API}/api/v1/orders`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  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),
  relayFee: BigInt(o.relayFee)
};
const items = quote.items.map((i: any) => ({
  ...i,
  id: BigInt(i.id),
  amount: BigInt(i.amount)
}));

// 2. Approve Payment Token
await wallet.writeContract({
  address: o.paymentToken,
  abi: erc20Abi,
  functionName: "approve",
  args: [quote.checkout, order.amount + order.serviceFee + order.relayFee],
});

// 3. Submit Checkout Transaction
const txHash = await wallet.writeContract({
  address: quote.checkout,
  abi: quote.version === 4 ? CHECKOUT_ABI_V4 : CHECKOUT_ABI,
  functionName: "checkout",
  args: [order, items, account.address, quote.signature],
});

console.log("Checkout transaction confirmed:", txHash);
Merchant Operations

Store Management & Catalog Automation

Merchants create API keys in the dashboard with specific scopes to automate product updates, inventory, access codes, and sales actions.

API Scopes & Permissions

ScopeGrants
ordersGet quotes from, and read the catalog of, this store even while it's invite-only (agents, checkout servers)
catalogRead every product (hidden ones too), create, edit and delete products, and edit the store's public profile and handle
salesRead the refund/void/redeem log, log new actions, and see receipts' line items
invitesList, create, edit and delete invite codes
webhooksManage webhook endpoints, send test events and see deliveries

Automating Catalog Updates

curl -X POST https://go.cart.fun/api/v1/stores/base-sepolia/3/products \
  -H "Authorization: Bearer cf_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"products": [{"name": "Digital License NFT", "type": "license", "price": "19.99", "active": true}]}'
Event Driven Architecture

Webhooks & Signature Verification

cart.fun delivers signed webhook notifications whenever orders finalize on-chain or store actions occur. Every payload includes a cartfun-signature header composed of timestamp t and HMAC-SHA256 signature v1.

Available Webhook Events

EventTrigger Condition
order.quotedcart.fun signed a quote from your catalog (before payment)
order.paidA receipt was printed: the order is paid. Includes the order's items when cart.fun quoted it
receipt.refundedA receipt was marked refunded
receipt.voidedA receipt was voided
receipt.redeemedA receipt was redeemed (picked up or used)
receipt.transferredA receipt's warranty moved to a new holder
receipt.servicedA service record was added to a receipt
review.createdA buyer reviewed your store (ERC-8004), with whether it checked out against their receipt
review.repliedYour store replied to a review

Signature Verification Routine

// 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).
Endpoint Explorer

Complete v1 REST & Agent API Reference

Search, inspect parameters, and copy curl commands for all active API endpoints.

Smart Contracts

Contract Interfaces & Verification

Receipts are 100% on-chain SVG NFTs. Each receipt records an immutable hash commitment (the same function is exposed as computeCommitment on the checkout):

keccak256(abi.encode(chainId, checkoutContract, storeId, buyer, amount, serviceFee, relayFee, escrowHold, paymentToken, itemsHash, cartId))

CartCheckoutV4 appends the order's linesHash, so the receipt also commits to what was bought:

keccak256(abi.encode(…the fields above, linesHash))

linesHash is keccak256 of the canonical JSON (cartfun.lines/v1: sorted keys, no whitespace) returned as linesPreimage by GET /api/v1/receipts/{chain}/{id}/order to the buyer, holder and store. It carries a random salt, so the public hash can't be matched by guessing lines, and it names the signed catalog snapshot the lines were priced from.

Query receipt metadata or render the vector artwork directly in browsers via the public API:

curl https://go.cart.fun/api/v1/receipts/base-sepolia/12
Developer Toolkit

SDKs & Tooling

Build with official toolkits and agent skills:

Claude Code Agent Skill

One command installation for autonomous shopping & store administration.

mkdir -p ~/.claude/skills/cartfun && curl -fsSL https://cart.fun/skill.md -o ~/.claude/skills/cartfun/SKILL.md

OpenAPI 3.1 Specification

Ready-to-use spec for automatic TypeScript client generation, Swagger, or LangChain tools.

https://cart.fun/openapi.json