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.
- 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.
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 .Autonomous Checkout with Viem / Wagmi
AI Agents and client dApps execute purchases through four programmatic steps:
- Discover Store: Browse
/api/v1/stores/directoryor resolve acart.fun/@handlewith/api/v1/handles/{handle}, then inspect/api/v1/stores/{chain}/{storeId}/agentto retrieve accepted payment tokens and the activeCartCheckoutaddress. - Request Signed Quote:
POST /api/v1/orderswith the buyer wallet address and SKU line items. The response contains an EIP-712 signature generated by the store signer, andversion(3or4) says which CartCheckout it is signed for. Limited-stock products return 409 when too few units are left. - ERC-20 Approval: Approve the checkout contract for
order.amount + order.serviceFee + order.relayFeeoforder.paymentToken. - Execute Checkout: Call
checkout(order, items, recipient, signature)on-chain. V4 orders carry an extra trailinglinesHashfield in theOrderstruct; pass the quote'sorderthrough unchanged. - Verify (V4):
GET /api/v1/receipts/{chain}/{id}/orderreturnslinesHashand, to the buyer, holder and store, thelinesPreimagethat mustkeccak256to 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);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
| 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), create, edit and delete products, and edit the store's public profile and handle |
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 |
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}]}'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
| Event | Trigger Condition |
|---|---|
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 |
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).Complete v1 REST & Agent API Reference
Search, inspect parameters, and copy curl commands for all active API endpoints.
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/12SDKs & 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.mdOpenAPI 3.1 Specification
Ready-to-use spec for automatic TypeScript client generation, Swagger, or LangChain tools.
https://cart.fun/openapi.json