Pricing & gates
Declare what a route costs — inline prices, named gate catalogues, fees and splits, and dynamic per-request pricing.
A gate is a protected unit: an amount, an ordered list of accepted protocols, and zero or more named fees. You declare gates inline in createPayKit's pricing option; gate names are inferred, so referencing them is typed.
Prices
A price is a value object: a USD amount and the stablecoins it may settle in. Build one with usd.
import { usd } from '@solana/pay-kit';
usd('0.10'); // $0.10, default settlement stablecoins
usd('0.10', 'USDC', 'PYUSD'); // restrict/order settlement to USDC then PYUSDThe customer is quoted in USD; the settlement stablecoin (USDC, USDT, PYUSD, USDG, CASH) is what actually transfers.
Inline price vs. catalogue
For a single route, pass a price straight to the middleware — an inline gate:
app.get('/report', pay.express(usd('0.25')), (_req, res) => res.json({ ok: true }));When more than one route is paid, lift the prices into the pricing catalogue and reference gates by name:
import { createPayKit, usd } from '@solana/pay-kit';
const pay = await createPayKit({
rpcUrl: 'https://402.surfnet.dev:8899',
pricing: {
report: { amount: usd('0.10'), description: 'Premium report' },
apiCall: { amount: usd('0.001') },
},
});
app.get('/report', pay.express('report'), (_req, res) => res.json({ ok: true }));
app.get('/api/data', pay.express('apiCall'), (_req, res) => res.json({ data: [] }));Fees and splits
Two fee shapes route part of a payment to another address. feeWithin carves the fee out of the amount (the recipient nets less); feeOnTop adds it on top (the customer pays more, the recipient nets full).
const SELLER = 'Ay…';
const PLATFORM = 'CX…';
const pay = await createPayKit({
rpcUrl: 'https://402.surfnet.dev:8899',
pricing: {
// Stripe-Connect "application fee": customer pays $10.00,
// SELLER nets $9.70, PLATFORM nets $0.30.
marketplaceSale: {
amount: usd('10.00'),
payTo: SELLER,
feeWithin: { [PLATFORM]: usd('0.30') },
},
// Surcharge: customer pays $10.50, SELLER nets $10.00, PLATFORM nets $0.50.
ticket: {
amount: usd('10.00'),
payTo: SELLER,
feeOnTop: { [PLATFORM]: usd('0.50') },
},
},
});Dynamic pricing
A gate can be a function of the request — return a price per call:
const pay = await createPayKit({
rpcUrl: 'https://402.surfnet.dev:8899',
pricing: {
tiered: (request) => usd(new URL(request.url).searchParams.get('tier') === 'premium' ? '5.00' : '0.10'),
},
});Boot-time validation
Gates are validated when createPayKit runs, so configuration mistakes throw at boot — before any traffic — as a ConfigurationError or subtype:
payToresolves from the gate oroperator.recipient.- A fee recipient must differ from
payTo— fold the fee into the amount instead. - All fee prices share one currency with the amount.
sum(feeWithin)must be less than the amount.accept: ['x402']on a fee-bearing gate throws (ProtocolIncompatibleError) — fees need MPP.