radius-sdk reference
radius-sdk accepts and makes x402 v2 payments on Radius. It defaults to SBC, the Radius facilitator, and mainnet. For task guides, see Accept payments and Make payments.
Source, changelog, and examples: radiustechsystems/radius-cli/packages/sdk. radius-cli wallet x402 uses the same client.
Entry points
Requires Node.js 20 or later, or a runtime with fetch such as Cloudflare Workers.
| Entry point | Exports | Peer dependency |
|---|---|---|
radius-sdk | Networks, SBC, price helpers, getPaymentReceipt, RadiusPaymentError, radiusEnv | — |
radius-sdk/hono | radiusPayments and its types | hono ^4 |
radius-sdk/client | createRadiusFetch, balance actions, getSettlement, and the receipt and error exports | viem ^2.48 |
The root and Hono entry points do not load viem at runtime.
radiusPayments
Hono middleware that answers unpaid requests to priced routes with 402 Payment Required, then verifies and settles paid requests through a facilitator. Requests that match no route pass through.
import { radiusPayments } from 'radius-sdk/hono';
app.use('/api/*', radiusPayments({ network: 'testnet', payTo: '0xYourWalletAddress', routes: { 'GET /api/lookup': '0.001 SBC' } }));| Option | Type | Default | Description |
|---|---|---|---|
payTo | address or (c) => address | Required | Recipient of every payment unless a route overrides it |
routes | Record<string, RouteSpec | Price> | Required | Priced routes keyed METHOD /path, with * wildcards; a bare price is shorthand for { price } |
network | 'mainnet', 'testnet', or a RadiusNetwork | 'mainnet' | See Networks |
settle | 'before' or 'after' | 'before' | 'before' settles, then runs the handler; 'after' runs the handler and settles only if it responds below 400 |
gasSponsoring | 'auto' or boolean | 'auto' | Declare eip2612GasSponsoring in 402s; 'auto' follows the facilitator's /supported |
facilitator | options object or FacilitatorClient | Radius facilitator | See Facilitator |
onSettled | (receipt, c) => void | — | Called once per settled payment |
It also accepts the network overrides. In handlers, c.get('radiusPayment') holds the receipt; add RadiusPaymentVariables to your Hono Variables type to type it.
RouteSpec
| Field | Type | Default | Description |
|---|---|---|---|
price | Price or (c) => Price | Required | See Prices |
payTo | address or (c) => address | The middleware's payTo | Recipient for this route |
description | string | — | Shown to buyers in the challenge's resource |
mimeType | string | — | Response type advertised in the challenge |
maxTimeoutSeconds | number | 300 | How long a signed payment stays valid |
Prices
A Price is a string or number in display units of the asset ('0.001 SBC', '0.001', 0.001), or { amount: '1000' } in base units. SBC has six decimals.
createRadiusFetch
Returns a fetch that pays x402 challenges and retries the request. Responses other than 402 are returned unchanged.
import { createRadiusFetch } from 'radius-sdk/client';
const payFetch = createRadiusFetch({ network: 'testnet', signer: process.env.RADIUS_PRIVATE_KEY as `0x${string}`, maxPerRequest: '0.01 SBC' });| Option | Type | Default | Description |
|---|---|---|---|
signer | private key, viem account, or WalletClient | Required | Signs payments; sending transactions needs a key, a local account, or a WalletClient |
maxPerRequest | Price | Required | Ceiling for each payment; not a cumulative budget |
network | 'mainnet', 'testnet', or a RadiusNetwork | 'mainnet' | Payments are made only on this network |
onPaymentRequired | (offer) => boolean | — | Approve or decline an offer before signing |
permit2Approval | 'auto' or 'never' | 'auto' | Without gas sponsoring, 'auto' sends one unlimited Permit2 approval; 'never' throws approval_required |
onApprovalRequired | (request) => boolean | — | Approve or decline sending that approval |
onPaid | (receipt, offer) => void | — | Called after each paid response |
fetch | typeof fetch | globalThis.fetch | Underlying fetch |
It also accepts the network overrides.
Payment rules:
- Pays only on the configured network and in the configured asset.
- Takes the first compatible offer within
maxPerRequest, in the server's order. - Schemes: x402 v2
exact(Permit2 or EIP-3009) andupto(Permit2), and x402 v1exact(EIP-3009). - The signing window is the server's
maxTimeoutSeconds, capped at 600 seconds. - The paid retry never follows a redirect to another origin (
redirect_refused).
Offers
onPaymentRequired receives a PaymentOffer:
| Field | Description |
|---|---|
amount | Base units as a string; for upto, the authorized maximum |
amountFormatted | Display amount, for example 0.01 SBC |
payTo, asset, network | Recipient, token, and CAIP-2 network |
resource | { url, description, mimeType } from the challenge |
scheme, x402Version | exact or upto; 1 or 2 |
transferMethod | permit2 or eip3009 |
gasSponsored | Whether the facilitator sponsors the Permit2 approval |
requirements | The raw requirement from the challenge |
Wallet helpers
The returned function also has these members:
| Member | Returns |
|---|---|
address, network | Signer address and resolved network |
maxPerRequest | The cap in base units |
balance() | { atomic, formatted }: the signer's SBC balance |
balances() | Native RUSD and SBC separately; see Balances |
send(to, amount) | Transfers SBC; needs a key or local account |
permit2Allowance() | The current Permit2 allowance |
approvePermit2() | Sends the unlimited Permit2 approval now |
getSettlement(txHash) | A settlement, or undefined while the node does not know the transaction |
fund() | Requests a faucet drip for this wallet |
client | The underlying @x402/core client |
Receipts
getPaymentReceipt(response, network) decodes a PAYMENT-RESPONSE header and returns undefined when there is none. radiusPayments exposes the same object as c.get('radiusPayment').
| Field | Description |
|---|---|
success | Whether settlement succeeded |
transaction | Settlement transaction hash |
payer, amount, network | Paying address, base units charged, CAIP-2 network |
explorerUrl | Explorer link for the transaction |
errorReason, errorMessage | Set when settlement failed |
Settlements
getSettlement(network, txHash) from radius-sdk/client, or payFetch.getSettlement(txHash), reads a settlement transaction from the chain. The result has status, blockNumber, transfers (each { from, to, amount } in the payment asset), paid(to?), paidFormatted(to?), and explorerUrl.
Balances
On Radius, eth_getBalance includes convertible SBC; see The Turnstile and balances. These viem actions from radius-sdk/client report each part separately:
| Action | Returns |
|---|---|
getBalances(client, { address }) | native (raw, aggregate, convertible), tokens (SBC by default), and total |
getNativeBalance(client, { address }) | Native RUSD only, in wei |
getAggregateBalance(client, { address }) | What eth_getBalance returns |
getTokenBalance(client, { address, token }) | An ERC-20 balanceOf |
Each takes an optional blockNumber or blockTag. radiusActions() adds them to a viem client:
import { createPublicClient, http } from 'viem';
import { radiusTestnet } from 'radius-sdk';
import { radiusActions } from 'radius-sdk/client';
const client = createPublicClient({ chain: radiusTestnet.chain, transport: http() }).extend(radiusActions());
const balances = await client.getBalances({ address: '0xYourWalletAddress' });Errors
Payment failures throw RadiusPaymentError with a code and, for some codes, details:
| Code | Cause |
|---|---|
price_above_limit | Every compatible offer is above maxPerRequest |
declined | onPaymentRequired returned false |
network_mismatch | The server's offers are on another network |
asset_mismatch | The server's offers are in another token |
no_compatible_offer | No offer uses a scheme this client pays |
unsupported_transfer_method | The server needs a transfer method other than permit2 or eip3009 |
invalid_challenge | The 402 could not be parsed; details.response holds the response |
payment_rejected | The paid retry got another 402; details.response holds the response |
invalid_receipt | An upto payment response is invalid, for example it charges more than the signed maximum |
redirect_refused | The paid retry redirected to another origin |
approval_required | A Permit2 approval is needed and permit2Approval is 'never' |
approval_failed | The approval transaction reverted |
faucet | fund() failed |
config | The options are invalid, for example a missing maxPerRequest |
Networks
| Network | Chain ID | Export |
|---|---|---|
'mainnet' | 723487 | radiusMainnet |
'testnet' | 72344 | radiusTestnet |
A RadiusNetwork carries a viem Chain (radiusTestnet.chain) plus facilitatorUrl, faucetUrl, and asset. defineRadiusNetwork builds one for another instance from a chain ID or a viem chain.
Both radiusPayments and createRadiusFetch accept these overrides alongside network:
| Override | Default |
|---|---|
rpcUrl | The network's public RPC endpoint |
facilitatorUrl | The Radius facilitator for the network |
asset | SBC (six decimals, permit domain Stable Coin version 1) |
explorerUrl, faucetUrl | The network's explorer and faucet |
Facilitator
For radiusPayments, facilitator takes:
{ url, apiKey }for another hosted facilitator;apiKeyis sent asx-api-key{ live: false }to use the built-in Radius/supportedanswer instead of fetching it on the first paid request after each cold start{ timeoutMs }to bound facilitator calls- a
FacilitatorClientfrom@x402/core/serverfor a self-hosted facilitator
Environment variables
radiusEnv(env) reads these variables, using the same names as radius-cli, and returns options to spread into either function. Explicit options take precedence. On Cloudflare Workers, pass c.env.
| Variable | Option |
|---|---|
RADIUS_NETWORK | network (mainnet or testnet) |
RADIUS_RPC_URL | rpcUrl |
RADIUS_FACILITATOR_URL | facilitatorUrl |
RADIUS_FACILITATOR_API_KEY | facilitator.apiKey |
RADIUS_ASSET_ADDRESS | asset.address (alias RADIUS_SBC_ADDRESS) |
RADIUS_PAY_TO | payTo |
RADIUS_PRIVATE_KEY | signer |
RADIUS_MAX_PER_REQUEST | maxPerRequest |
Examples
| Example | What it shows |
|---|---|
worker-seller | Hono Worker with a free route and two paid routes |
agent-buyer | Node scripts that pay a URL, including from a wallet holding only SBC |
demo-dapp | Browser page that exercises both sides with a burner wallet or MetaMask |