---
name: x402-celo-facilitator
description: Integrate x402 stablecoin payments into any web service using the hosted x402 facilitator on Celo. Use this when adding pay-per-request billing to an API or when building a client that pays for x402-protected endpoints. Covers the seller flow (charge for your API with the @x402/hono or @x402/express v2 middleware + @x402/core + @x402/evm) and the buyer flow (pay automatically with @x402/fetch + @x402/evm), pointed at the Celo facilitator at api.x402.celo.org.
license: MIT
---

# x402 payments on Celo — integration skill

This skill lets an agent add x402 stablecoin payments to a project using a hosted
facilitator on the Celo network. x402 is an open standard: a server answers a
request with HTTP `402 Payment Required` and a machine-readable price; the client
signs a stablecoin payment; a **facilitator** verifies the signature and settles it
on-chain. The end user pays no gas — settlement gas is paid by the facilitator.

You are integrating against the hosted facilitator at:

- Frontend / dashboard: `https://x402.celo.org`
- Mainnet facilitator API: `https://api.x402.celo.org`
- Testnet facilitator API: `https://api.x402.sepolia.celo.org`

There are two roles. **Seller** (accept payments for your API) is the primary case;
**buyer** (pay for someone else's x402 API) is covered second.

---

## Prerequisites (the human does this once)

The facilitator is metered: each on-chain settlement spends one prepaid credit. To
get a key, the human — not the agent — must:

1. Open `https://x402.celo.org` and connect an EVM wallet (MetaMask, etc.).
2. Click **Create API key** and sign the message (no gas, no transaction).
3. Copy the key (shown once) — it looks like `x402_...`.
4. Provide it to the project as the `X402_API_KEY` environment variable.

New accounts start with free credits (mainnet + testnet). More credits are bought by
depositing USDC on the dashboard. The key is shown only once; if it is lost, click
**Lost your key? Regenerate it** on the dashboard to issue a new one (this invalidates
the old key; credits are preserved). Do **not** try to mint keys programmatically or
handle the user's wallet — read the key from the environment.

> The agent's job begins after `X402_API_KEY` is set. If it is missing, stop and ask
> the human to complete the steps above.

---

## Facts you must not get wrong

- **Networks:** Celo mainnet is CAIP-2 `eip155:42220`; Celo Sepolia testnet is
  `eip155:11142220`. Develop on testnet first.
- **Scheme:** `exact` (a fixed price per request), the standard x402 EVM scheme.
- **Asset:** USDC (6 decimals), EIP-3009 `transferWithAuthorization`.
  - Celo **mainnet** USDC (`eip155:42220`): `0xcebA9300f2b948710d2653dD7B07f33A8B32118C`
  - Celo **Sepolia testnet** USDC (`eip155:11142220`): `0x01C5C0122039549AD1493B8220cABEdD739BC44E`
  - Celo is not in the x402 packages' default-asset table, so you must always name the
    asset explicitly in the route price (see the seller flow). Its EIP-712 domain is
    `name: "USDC", version: "2"` — pass it in `extra`.
- **Amounts are in the token's base units** (USDC has 6 decimals): `$0.01 = "10000"`.
  Always pass amounts as strings.
- **`payTo`** is the seller's own wallet address — the address that receives the USDC.
  It is **not** the facilitator and not the buyer. Funds move buyer → `payTo` directly
  inside the token contract; the facilitator never holds funds.
- **The API key goes only to the facilitator**, as `X-API-Key`, and only on requests
  the middleware sends to the facilitator. Never expose it to the browser or to buyers.

Confirm the live facilitator capabilities any time with:

```bash
curl https://api.x402.sepolia.celo.org/supported
# -> { "kinds": [ { "x402Version": 2, "scheme": "exact", "network": "eip155:11142220" }, ... ] }
```

---

## Seller flow — charge for your API (primary)

Use the official x402 **v2** framework packages and point the facilitator client at
this facilitator. Do **not** hand-assemble payment payloads — the middleware builds
the correct on-the-wire shape for you.

> **Use the v2 scoped packages (`@x402/*`), not the legacy `x402-express` /
> `x402-fetch`.** The legacy packages have no Celo entry in their network enum, so they
> will not work here. Everything below is verified against `@x402/*@2.17` and settles
> real payments against this facilitator.
>
> The snippets are **TypeScript** — run them through a TS toolchain (`tsx`, `ts-node`,
> or your framework's build), not raw `node file.js`.

### Install

```bash
# Seller (Hono shown; swap @x402/hono → @x402/express for Express)
npm i @x402/hono @x402/core @x402/evm viem
```

### Configure the facilitator client

Construct an `HTTPFacilitatorClient` that adds the `X-API-Key` header on every call the
middleware makes to the facilitator. This is the one seller-side custom piece.

```ts
// x402-facilitator.ts
import { HTTPFacilitatorClient } from "@x402/core/server";

const FACILITATOR_URL =
  process.env.X402_NETWORK === "mainnet"
    ? "https://api.x402.celo.org"
    : "https://api.x402.sepolia.celo.org";

export const facilitator = new HTTPFacilitatorClient({
  url: FACILITATOR_URL,
  // Attach the metering key to every facilitator request. Returns per-endpoint
  // header maps: verify / settle / supported (bazaar is optional).
  createAuthHeaders: async () => {
    const h = { "X-API-Key": process.env.X402_API_KEY! };
    return { verify: h, settle: h, supported: h };
  },
});
```

### Seller server (Hono)

The v2 API is: build an `x402ResourceServer` from the facilitator client, register the
`exact` EVM scheme, and pass **`(routes, server)`** to `paymentMiddleware` — routes
first, then the server. `payTo` and `price` live **inside** each route's `accepts`.

```ts
import { Hono } from "hono";
import { serve } from "@hono/node-server";
import { paymentMiddleware, x402ResourceServer } from "@x402/hono";
import { ExactEvmScheme } from "@x402/evm/exact/server";
import { getAddress } from "viem";
import { facilitator } from "./x402-facilitator";

const NETWORK = "eip155:11142220"; // Celo Sepolia (mainnet: eip155:42220)
const USDC = getAddress("0x01C5C0122039549AD1493B8220cABEdD739BC44E"); // Sepolia USDC

const server = new x402ResourceServer(facilitator);
server.register("eip155:*", new ExactEvmScheme());

const routes = {
  "GET /premium": {
    accepts: [
      {
        scheme: "exact",
        network: NETWORK,
        payTo: getAddress(process.env.SELLER_PAY_TO!), // your wallet — receives the USDC
        price: {
          amount: "10000", // $0.01 in USDC base units (6 decimals)
          asset: USDC,
          extra: { name: "USDC", version: "2" }, // EIP-712 domain for EIP-3009
        },
      },
    ],
    description: "Premium content",
  },
};

const app = new Hono();
app.use(paymentMiddleware(routes, server)); // routes FIRST, then server
app.get("/premium", (c) => c.json({ data: "this response cost $0.01" }));

serve({ fetch: app.fetch, port: 3000 });
```

> **Do not use a bare dollar price like `price: "$0.01"` on Celo.** It type-checks,
> but at request time the middleware calls `getDefaultAsset(network)` to resolve the
> token — and Celo is **not** in the packages' default-asset table, so it throws
> `No default asset configured for network eip155:…` and the route 500s. Always pass
> the explicit `{ amount, asset, extra: { name, version } }` price object above.

> **Express:** identical, but import `paymentMiddleware`, `x402ResourceServer` from
> `@x402/express` and mount with `app.use(paymentMiddleware(routes, server))`.

### Environment

```bash
X402_API_KEY=x402_...                 # from the dashboard (facilitator credits)
X402_NETWORK=testnet                  # testnet | mainnet
SELLER_PAY_TO=0xYourWalletThatGetsPaid
```

### What happens at runtime

1. A client requests `GET /premium` with no payment → middleware returns `402` with
   the price and payment requirements.
2. The client (a wallet or the buyer flow below) signs an EIP-3009 authorization and
   retries with an `X-PAYMENT` header.
3. The middleware calls the facilitator's `/verify` then `/settle` (with your
   `X-API-Key`). The facilitator submits the on-chain transfer and pays gas.
4. On success the middleware runs your handler and returns the response, plus an
   `X-PAYMENT-RESPONSE` header with the settlement details. USDC has moved buyer → `payTo`.
5. Your facilitator credit balance drops by one. At zero credits, settlement returns
   `402` until you top up on the dashboard.

---

## Buyer flow — pay for an x402 API (for testing or agents that consume paid APIs)

Use the official `@x402/fetch` (v2) client to wrap `fetch`. It handles the whole
402 → sign → retry loop automatically. The buyer needs a funded wallet (USDC on the
target network); the buyer does **not** need a facilitator API key, and does **not**
need native gas — the facilitator sponsors settlement gas.

```bash
npm i @x402/fetch @x402/evm viem
```

Build an `x402Client`, register the `exact` EVM scheme with the buyer account, then
wrap `fetch`:

```ts
import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount(process.env.BUYER_PRIVATE_KEY as `0x${string}`);

const client = new x402Client();
client.register("eip155:*", new ExactEvmScheme(account)); // note: account passed here
const payFetch = wrapFetchWithPayment(fetch, client);

// Transparently pays if the endpoint returns 402, then returns the paid response.
const res = await payFetch("https://your-server.example/premium");
console.log(res.status, await res.json());
```

Equivalent one-call form (no separate client object):

```ts
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm/exact/client";

const payFetch = wrapFetchWithPaymentFromConfig(fetch, {
  schemes: [{ network: "eip155:11142220", client: new ExactEvmScheme(account) }],
});
```

To test end-to-end: run your seller server on testnet, fund a buyer wallet with a
little testnet USDC (see "Get testnet funds" below), and call it with the buyer client
above. A successful call moves USDC on-chain and returns your protected content, with a
`payment-response` header carrying the settlement tx hash.

### Get testnet funds (Celo Sepolia)

- **Testnet USDC** (what the buyer pays with): https://faucet.circle.com/ — select
  Celo Sepolia, paste the buyer address.
- **Testnet CELO** (optional; the facilitator sponsors settlement gas, so the buyer
  needs none for the transfer itself): https://faucet.celo.org

---

## Verify the integration

- Seller up: `curl -i https://your-server/premium` → `402`. The payment requirements
  come back in the **`payment-required` response header** (base64-encoded JSON with
  scheme `exact`, network, `payTo`, `asset`, atomic `amount`, and the EIP-712 domain);
  the response body is an empty `{}`. The stock client reads the header for you — you
  only need to decode it if inspecting the 402 by hand.
- Facilitator reachable: `curl https://api.x402.sepolia.celo.org/supported`
  → `200` listing the `exact` scheme for `eip155:11142220`.
- Full loop: the buyer client above returns `200` and the response contains a
  `payment-response` header (base64 JSON with the settlement tx hash). Check the
  dashboard — your credit count dropped by one.

## Common mistakes

- Using a bare `price: "$0.01"` on Celo. It compiles but 500s at request time (no
  default asset for Celo). Pass the explicit `{ amount, asset, extra: { name, version } }`
  price object. Amounts are **strings** in 6-decimal base units (`$0.01 = "10000"`).
- Using the legacy `x402-express` / `x402-fetch` packages, or the v1 call shape
  `paymentMiddleware(payTo, routes, facilitator)`. Use the v2 scoped `@x402/*` packages
  and `paymentMiddleware(routes, server)` with `payTo` inside each route's `accepts`.
- Pointing `payTo` at the facilitator or leaving it unset. `payTo` is the seller's own
  receiving wallet.
- Forgetting `createAuthHeaders`. Without the `X-API-Key` the facilitator rejects
  settlement (the metering gate returns `401`).
- Calling the facilitator's `/verify` or `/settle` directly and hand-building the
  payment body. Use the middleware; it produces the correct wire format. Only bypass it
  if you know the exact on-the-wire schema.
- Running on mainnet before testing on testnet, or before the mainnet facilitator has
  funded settlement gas.

## References

- x402 protocol site: https://x402.org
- x402 documentation: https://docs.cdp.coinbase.com/x402
- x402 GitHub (spec, packages, examples): https://github.com/coinbase/x402
- The `exact` EVM scheme spec: https://github.com/coinbase/x402/blob/main/specs/schemes/exact/scheme_exact_evm.md
- EIP-3009 (transferWithAuthorization): https://eips.ethereum.org/EIPS/eip-3009
- Celo network docs: https://docs.celo.org
- This facilitator's dashboard (get an API key, buy credits): https://x402.celo.org

---

Built by Celo Core Co.
