TAB

Charge for it

A priced endpoint is two things: an answer that says 402 with your terms, and a check that the authorization you were handed is real. Everything else is the ordinary handler you already wrote.

Try it from a shell

# the terms, with no payment
curl -s https://this-site/api/paid/brief | jq .accepts[0]

# what this facilitator settles
curl -s https://this-site/api/x402/supported | jq .

# verify a signed payment without broadcasting it
curl -s -X POST https://this-site/api/x402/verify \
  -H 'content-type: application/json' \
  -d '{"paymentPayload": { ... }}' | jq .checks

Your server: answer 402, then serve

import { verifyTypedData, createPublicClient, http } from 'viem';

const USDG   = '0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168';  // 6 decimals
const DOMAIN = { name: 'Global Dollar', version: '1', chainId: 4663, verifyingContract: USDG };
const FIELDS = ['from', 'to', 'value', 'validAfter', 'validBefore', 'nonce']
  .map((n, i) => ({ name: n, type: i < 2 ? 'address' : i < 5 ? 'uint256' : 'bytes32' }));

app.get('/premium-data', async (req, res) => {
  const header = req.get('X-PAYMENT');
  if (!header) return res.status(402).json({ x402Version: 1, accepts: [terms()] });

  const p = JSON.parse(Buffer.from(header, 'base64').toString());
  const a = p.payload.authorization;

  // the signature, against the dollar's own domain
  const ok = await verifyTypedData({
    address: a.from, domain: DOMAIN, types: { ReceiveWithAuthorization: FIELDS },
    primaryType: 'ReceiveWithAuthorization', message: a, signature: p.payload.signature,
  });

  // and the two facts only the chain can tell you
  const [balance, used] = await Promise.all([
    chain.readContract({ address: USDG, abi: USDG_ABI, functionName: 'balanceOf', args: [a.from] }),
    chain.readContract({ address: USDG, abi: USDG_ABI, functionName: 'authorizationState', args: [a.from, a.nonce] }),
  ]);

  if (!ok || used || balance < BigInt(a.value)) {
    return res.status(402).json({ error: 'payment not valid', accepts: [terms()] });
  }
  res.json(await answer());   // serve first, settle in the background
});

The nonce in terms() is keccak256(abi.encode(merchant, resourceHash, salt)), which is what binds the signature to you and to this resource. Hand it to TabGate and the fee splits on-chain; anyone can submit that transaction, including the agent that signed it.


Your agent: pay without thinking about it

async function fetchPaid(url, account) {
  let r = await fetch(url);
  if (r.status !== 402) return r;                // it was free after all

  const t = (await r.json()).accepts[0];
  if (BigInt(t.maxAmountRequired) > budget) throw new Error('too dear');

  const message = {
    from: account.address, to: t.extra.to, value: BigInt(t.maxAmountRequired),
    validAfter: 0n, validBefore: BigInt(Math.floor(Date.now() / 1000) + 120),
    nonce: t.extra.nonce,
  };
  const signature = await account.signTypedData({
    domain: t.extra, types: { [t.extra.authorization]: FIELDS },
    primaryType: t.extra.authorization, message,
  });

  const payment = { x402Version: 1, scheme: 'exact', network: t.network,
    payload: { authorization: message, authorizationType: t.extra.authorization,
               salt: t.extra.salt, signature } };

  return fetch(url, { headers: { 'X-PAYMENT': btoa(JSON.stringify(payment)) } });
}

No wallet connection, no gas estimation, no nonce management, no receipt to wait for. The agent signs and asks again. That is the whole client.

Addresses to point at

USDG0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168
chainRobinhood Chain, id 4663
TabGate—
TabBook—
TabIndex—
facilitator/api/x402/verify · /api/x402/settle · /api/x402/supported

Publishing a price

// one transaction, and any agent can read your price before it calls you
index.register("Your service", "https://your.site", payTo);
index.listResource("GET", "/premium-data", 10000, "what it is");  // 0.01 USDG

// and any agent can list you back, without reading a single event log
index.merchants(0, 50);
index.getResources(merchant, 0, 50);