coal
coal

React SDK

The coal-react package provides everything you need to integrate Coal payments and agent-discoverable commerce into a React / Next.js application.

Version 0.4.1 — three sub-exports:

Import fromWhat it does
coal-reactClient components: checkout buttons, product grids, catalog publisher, Schema.org SEO, receipt polling
coal-react/serverServer-only: publish your product catalog to Coal's 0G-backed index
coal-react/nextNext.js App Router: serve agent-discoverable manifests (A2A Agent Card, llms.txt, x402)

Installation

bash
1npm install coal-react

Peer dependencies: React 18+ and React DOM 18+ (optional for server-only usage).


Quick start

1. Wrap your app

tsx
1// app/layout.tsx
2import { CoalProvider } from 'coal-react';
3
4export default function RootLayout({ children }) {
5 return (
6 <CoalProvider merchantId="your-coal-merchant-id">
7 {children}
8 </CoalProvider>
9 );
10}

2. Add a buy button

tsx
1import { CoalBuyButton } from 'coal-react';
2
3<CoalBuyButton
4 createSession={async () => {
5 const res = await fetch('/api/create-checkout', { method: 'POST' });
6 return res.json(); // must return { url } or { sessionId }
7 }}
8>
9 Buy for $9.99
10</CoalBuyButton>

3. Make your site agent-discoverable (optional)

ts
1// app/.well-known/agent-card.json/route.ts
2import { createAgentCardRoute } from 'coal-react/next';
3export const GET = createAgentCardRoute({ merchantId: 'your-merchant-id' });

AI agents using A2A (Google), x402 (Coinbase), MCP, or LLM crawlers now find your products automatically.


Client components

All client components require <CoalProvider> as an ancestor.

<CoalProvider>

Context wrapper. Safe to render on the server. Provides merchantId, apiUrl, and baseUrl to all nested Coal components.

PropTypeDefaultDescription
merchantIdstringrequiredYour Coal merchant ID
apiUrlstring'https://api.usecoal.xyz'Coal API base URL
baseUrlstring'https://usecoal.xyz'Coal frontend base URL (for checkout redirects)

<CoalCheckoutButton>

Redirects to Coal's hosted checkout page. Use when you already have a checkout URL or session ID from your server.

PropTypeDefaultDescription
checkoutUrlstringFull checkout URL. Preferred.
sessionIdstringAlternative — URL is constructed as {baseUrl}/pay/checkout/{id}
baseUrlstringProvider's baseUrlOverride the Coal base URL
target'_self' | '_blank''_self'Redirect vs new tab
onBeforeRedirect() => voidCallback for analytics
childrenReactNode"Pay with Coal"Button content
classNamestringCSS class
styleCSSPropertiesInline styles
tsx
1<CoalCheckoutButton
2 checkoutUrl={url}
3 onBeforeRedirect={() => analytics.track('checkout')}
4/>

<CoalBuyButton>

Higher-level button that calls your server to create a session, then redirects. The Coal API key stays on your server.

PropTypeDefaultDescription
createSession() => Promise<{ url?, sessionId?, id? }>requiredServer call that returns a checkout URL
target'_self' | '_blank''_self'Redirect vs new tab
onError(error: Error) => voidError handler
childrenReactNode"Pay with Coal"Button content
tsx
1<CoalBuyButton
2 createSession={async () => {
3 const res = await fetch('/api/checkout', { method: 'POST' });
4 return res.json();
5 }}
6 onError={(err) => toast.error(err.message)}
7>
8 Buy Now — $29.99
9</CoalBuyButton>

<CoalProducts>

Renders a grid or list of products from your Coal merchant profile. Data is fetched from Coal's agent API automatically.

PropTypeDefaultDescription
merchantIdstringProvider's merchantIdOverride merchant
layout'grid' | 'list''grid'Layout mode
columnsnumber3Grid columns
renderProduct(product) => ReactNodeCustom render function
loadingContentReactNode"Loading…"Loading state
emptyContentReactNode"No products yet"Empty state
tsx
1<CoalProducts layout="grid" columns={2} />

<CoalProduct>

Single product card by ID.

tsx
1<CoalProduct id="product-id" />

<CoalAgentPublisher>

Publishes your product catalog to Coal for 0G-backed agent discovery. Posts to a merchant-owned proxy route — your server handles the Coal API key so it never reaches the browser.

PropTypeDefaultDescription
productsCoalAgentPublisherProduct[]requiredYour product catalog
proxyUrlstring'/api/coal/publish-catalog'Your server-side proxy route
mode'upsert' | 'replace''upsert'Upsert: only touch listed products. Replace: also archive unlisted external products.
headersRecord<string, string>Custom headers (CSRF tokens, shared secrets)
debounceMsnumber30000Minimum ms between publishes
showStatusbooleanfalseShow a "Catalog indexed on 0G" badge
onPublish(result) => voidSuccess callback
onError(error: Error) => voidError callback
tsx
1<CoalAgentPublisher
2 products={[
3 { externalId: 'sku-1', name: 'Pro Plan', price: 29.99, description: '...' },
4 { externalId: 'sku-2', name: 'Enterprise', price: 99, image: 'https://...' },
5 ]}
6 showStatus
7 onPublish={(result) => console.log('Indexed:', result.zeroG.storageUri)}
8/>

Each product needs an externalId — your own stable product ID. Coal upserts by (merchantId, externalId) so republishing is idempotent.

What happens under the hood:

  1. Component POSTs to your proxy route (server-side)
  2. Your proxy calls publishCoalCatalog() with your Coal API key
  3. Coal upserts products into its DB
  4. Coal publishes the merchant profile to 0G Storage (immutable log) + 0G KV (mutable mirror)
  5. Agents hitting /api/agent/discover see your products

<CoalSchemaOrg>

Injects Schema.org JSON-LD Product / Offer markup into the page. ChatGPT, Perplexity, and Google AI Overviews cite pages with this markup. XSS-safe — uses safeJsonForScriptTag() internally.

tsx
1<CoalSchemaOrg />

useCoalReceipt(sessionId, options?)

Polls Coal for a payment receipt until the 3-step proof trail is fully verified: Base TX → 0G Storage → 0G Chain.

tsx
1const {
2 receipt, // CoalReceipt object
3 loading, // true while polling
4 error, // string | null
5 fullyVerified, // true when all 3 steps are green
6 verifiedSteps, // 0..3
7} = useCoalReceipt(sessionId, {
8 pollIntervalMs: 2000, // default
9 timeoutMs: 60000, // default
10});

Use on your success page to show the customer their verifiable receipt in real time.

useCoalCheckout(options)

Low-level hook for custom checkout flows (same as v0.3).

tsx
1const { checkoutUrl, redirectToCheckout, openCheckout } = useCoalCheckout({
2 sessionId: 'abc123',
3});

Server helper

publishCoalCatalog(options)

Import from coal-react/server. This module is server-only — it throws at load time if imported in a browser context to prevent API key leaks.

ts
1import { publishCoalCatalog, CoalPublishError } from 'coal-react/server';
2
3try {
4 const result = await publishCoalCatalog({
5 merchantId: 'your-merchant-id',
6 apiKey: process.env.COAL_API_KEY!,
7 products: [
8 { externalId: 'sku-1', name: 'Pro Plan', price: 29.99 },
9 ],
10 mode: 'upsert',
11 });
12
13 console.log(result.zeroG.storageUri); // "0g://log/0x..."
14} catch (err) {
15 if (err instanceof CoalPublishError) {
16 console.error(err.status, err.code, err.message);
17 // 401 UNAUTHORIZED, 429 RATE_LIMITED, 400 VALIDATION_ERROR
18 }
19}

Price validation: The helper validates each product's price before the network call — rejects NaN, Infinity, negative, zero, >1,000,000, and >6 decimal places with clear error messages.

Proxy route pattern

Your <CoalAgentPublisher> component posts to YOUR server. Your server calls publishCoalCatalog() with the API key.

ts
1// app/api/coal/publish-catalog/route.ts
2import { publishCoalCatalog } from 'coal-react/server';
3
4export async function POST(request: Request) {
5 // ⚠️ Add your own auth here (session check, CSRF token, etc.)
6 const body = await request.json();
7
8 const result = await publishCoalCatalog({
9 merchantId: process.env.NEXT_PUBLIC_COAL_MERCHANT_ID!,
10 apiKey: process.env.COAL_API_KEY!,
11 products: body.products,
12 mode: body.mode ?? 'upsert',
13 });
14
15 return Response.json(result);
16}

Next.js route helpers

Import from coal-react/next. Drop these into your Next.js App Router and your site becomes findable by AI agents across multiple discovery protocols.

createAgentCardRoute(options)

Serves an A2A Agent Card at /.well-known/agent-card.json (Google + Linux Foundation spec).

ts
1// app/.well-known/agent-card.json/route.ts
2import { createAgentCardRoute } from 'coal-react/next';
3
4export const GET = createAgentCardRoute({
5 merchantId: 'your-merchant-id',
6 name: 'My Store', // optional
7 description: 'We sell...', // optional
8 cacheMaxAge: 300, // Cache-Control max-age in seconds (default: 300)
9});

The handler fetches your merchant profile from Coal's public agent API (backed by 0G Storage + KV), transforms it into the A2A Agent Card format, and adds capabilities, payment info, catalog preview, and 0G proof metadata.

createLlmsTxtRoute(options)

Serves /llms.txt (Answer.AI convention). ChatGPT, Perplexity, and Google AI Overviews read this when citing your site.

ts
1// app/llms.txt/route.ts
2import { createLlmsTxtRoute } from 'coal-react/next';
3
4export const GET = createLlmsTxtRoute({
5 merchantId: 'your-merchant-id',
6 title: 'My Store',
7 summary: 'We sell digital goods payable in USDC on Base via Coal.',
8});

createX402ManifestRoute(options)

Serves /.well-known/x402.json for x402 Bazaar (Coinbase).

ts
1// app/.well-known/x402.json/route.ts
2import { createX402ManifestRoute } from 'coal-react/next';
3
4export const GET = createX402ManifestRoute({
5 merchantId: 'your-merchant-id',
6});

Lists all of your x402 paywall endpoints with verify URLs so agent crawlers and the Coinbase Bazaar indexer can discover and pay for gated content.


TypeScript types

All types are exported from their respective sub-paths:

typescript
1// Client components
2import type {
3 CoalCheckoutButtonProps,
4 CoalBuyButtonProps,
5 CoalProductsProps,
6 CoalAgentPublisherProps,
7 CoalSchemaOrgProps,
8 CoalProductSummary,
9 CoalPaywallSummary,
10 CoalMerchantProfile,
11 CoalReceipt,
12 UseCoalCheckoutOptions,
13 UseCoalCheckoutReturn,
14 UseCoalReceiptOptions,
15 UseCoalReceiptResult,
16} from 'coal-react';
17
18// Server helper
19import type {
20 CoalCatalogProduct,
21 PublishCoalCatalogOptions,
22 PublishCoalCatalogResult,
23 CoalPublishError,
24} from 'coal-react/server';
25
26// Next.js route helpers
27import type {
28 AgentCardRouteOptions,
29 LlmsTxtRouteOptions,
30 X402ManifestRouteOptions,
31} from 'coal-react/next';

Backwards-compatible aliases

CoalWidget and CoalCheckout are exported as aliases for CoalCheckoutButton. They are deprecated — use CoalCheckoutButton in new code.


Examples


Next steps