Skip to content
LogoLogo

Sell a data lookup

Charge 0.001 SBC per request on testnet

View as Markdown

In this tutorial you build a small API that sells one IP-reputation lookup for 0.001 SBC. You run it locally, see an unpaid request receive a payment challenge, and, after the buyer tutorial, see the payment arrive in your wallet. The data is synthetic; no external service is needed.

It takes about 10 minutes. For the same steps as a reference, see Accept payments.

Before you start

  • Node.js 20 or later and pnpm.
  • A wallet address to receive payments. It needs no funds and no private key on the server; see create and fund a wallet.

1. Create the project

mkdir lookup-seller && cd lookup-seller
pnpm init
pnpm add [email protected] [email protected]
pnpm add -D [email protected]

hono serves the API, radius-sdk adds payments, and wrangler runs the Worker locally. The versions are pinned to the ones this tutorial was verified with; radius-sdk is pre-1.0, so newer minor versions can change its API.

2. Configure the Worker

Create wrangler.toml:

name = "lookup-seller"
main = "src/index.ts"
compatibility_date = "2026-09-01"

Create .dev.vars with the address that receives payments, and keep it out of version control:

echo "PAY_TO=0xYourWalletAddress" > .dev.vars

3. Write the API

Create src/index.ts:

import { Hono } from 'hono';
import { radiusPayments, type RadiusPaymentVariables } from 'radius-sdk/hono';
 
type Env = {
  Bindings: { PAY_TO: `0x${string}` };
  Variables: RadiusPaymentVariables;
};
 
const app = new Hono<Env>();
 
app.get('/health', (c) => c.json({ ok: true }));
 
app.use(
  '/api/*',
  radiusPayments<Env>({
    network: 'testnet',
    payTo: (c) => c.env.PAY_TO,
    routes: {
      'GET /api/lookup': { price: '0.001 SBC', description: 'Reputation lookup for one IP address' },
    },
  }),
);
 
app.get('/api/lookup', (c) => {
  const ip = c.req.query('ip') ?? '0.0.0.0';
  const payment = c.get('radiusPayment');
  return c.json({ ip, reputation: 'clean', score: 7, paidBy: payment?.payer, transaction: payment?.transaction });
});
 
export default app;

radiusPayments protects GET /api/lookup. The handler runs only after the payment has settled, so c.get('radiusPayment') always holds a payment there. /health has no price, so it stays free.

4. Run it

pnpm wrangler dev

The Worker listens on http://localhost:8787. In another terminal, call the free route:

curl http://localhost:8787/health
{ "ok": true }

5. See the payment challenge

Request the lookup without paying:

curl -i 'http://localhost:8787/api/lookup?ip=9.9.9.9'

The response is HTTP/1.1 402 Payment Required, with the offer in the PAYMENT-REQUIRED header. Decode it:

curl -s -D - -o /dev/null 'http://localhost:8787/api/lookup?ip=9.9.9.9' \
  | grep -i '^payment-required:' | cut -d' ' -f2 | tr -d '\r' | base64 -d

The accepts entry says what a buyer must pay:

FieldValue
schemeexact
networkeip155:72344 (Radius testnet)
amount1000: 0.001 SBC in base units (6 decimals)
assetThe SBC contract
payToYour PAY_TO address

A request with an invalid payment gets the same 402, and the handler does not run:

curl -s -o /dev/null -w '%{http_code}\n' -H 'PAYMENT-SIGNATURE: eyJ4NDAyVmVyc2lvbiI6Mn0=' \
  'http://localhost:8787/api/lookup?ip=9.9.9.9'
402

6. Get paid

Leave the Worker running and follow buy a data lookup with a second wallet. Then check your balance:

RADIUS_NETWORK=testnet npx [email protected] wallet balance 0xYourWalletAddress --json

sbc increases by 0.001 for each paid lookup. The facilitator pays the gas, so you receive the full price.

What you built

  • A route priced in SBC, with payment checked and settled before the handler runs.
  • A free route next to it.
  • A recipient address in configuration, with no key on the server.

Next steps

  • Accept payments: per-request prices, reading the payment in handlers, settling after the handler, and moving to mainnet.
  • Deploy: set the recipient with pnpm wrangler secret put PAY_TO, then run pnpm wrangler deploy.
  • x402 payments: what happens between the challenge and the settlement.