Pricing & assets
Each pay.charge() call produces independent middleware, so you can
price every route differently. Prices are authored in atomic units —
the smallest indivisible unit of the configured mint.
For USDC (6 decimals, mainnet default), "10000" is 0.01 USDC =
$0.01. For devnet WSOL (9 decimals, current devnet default), the same
"10000" is 0.00001 WSOL ≈ a small fraction of a cent — the units
example below assumes USDC math.
Per-route prices
const cheap = pay.charge({ amount: "1000" }); // $0.001
const standard = pay.charge({ amount: "10000" }); // $0.01
const premium = pay.charge({ amount: "100000" }); // $0.10
app.get("/preview/:id", cheap, previewHandler);
app.get("/article/:id", standard, articleHandler);
app.get("/analysis/:id", premium, analysisHandler);Why atomic-unit strings
- Exact. JS numbers lose precision above 2⁵³; atomic amounts go higher.
- Predictable. No rounding, no locale, no decimal-separator drift.
- Stable on the wire. The envelope is JSON, which can't carry bigint — strings are the lossless representation across both sides.
Dashboard catalog
Optionally register each route on the APIs page of your dashboard. The catalog is display-only — it doesn't change what the SDK charges. But registered routes show up with friendly names in your payment feed and per-API analytics.
Custom mint / decimals
Override at the SDK level for every route, or per-route via asset.
Any SPL token Obscura's backend supports as STABLECOIN_MINT works —
USDC is the default. The Umbra mixer is mint-agnostic, but both sides
(the agent's Obscura backend and your merchant SDK) must agree on the
mint, or the envelope's asset check will reject the payment.
// every route prices in a different stablecoin
const pay = obscura({
merchantEtaAddress: process.env.MERCHANT_ETA_ADDRESS!,
mint: "<BASE58_MINT_ADDRESS>",
decimals: 6,
});
// or override just one route
app.get(
"/premium-report",
pay.charge({
amount: "500000",
asset: {
address: "<BASE58_MINT_ADDRESS>",
decimals: 6,
},
}),
reportHandler,
);Mainnet
Flip one flag when you're ready for real money. The mint default switches to canonical USDC automatically.
const pay = obscura({
merchantEtaAddress: process.env.MERCHANT_ETA_ADDRESS!,
network: "solana", // mainnet-beta
// mint defaults to EPjFW…Dt1v (USDC on mainnet)
});The merchantEtaAddress itself is network-agnostic — the same Solana
pubkey is valid on devnet and mainnet — but balances are isolated per
network. See Devnet vs mainnet.
Custom RPC
The SDK hits Solana RPC only for chain reads during verification (two
getTransaction calls per paid request). Default is
api.devnet.solana.com / api.mainnet-beta.solana.com, both
rate-limited public RPCs. Point at Helius / Triton / QuickNode for
lower latency and higher throughput in production:
const pay = obscura({
merchantEtaAddress: process.env.MERCHANT_ETA_ADDRESS!,
rpcUrl: process.env.HELIUS_RPC_URL,
});Replay window
Each successful payment's queueSignature is cached in-memory and
rejected on duplicate within replayWindowMs (default 5 minutes).
Long enough to absorb a slow client retry, short enough that the map
doesn't grow unbounded on a busy merchant.
const pay = obscura({
merchantEtaAddress: process.env.MERCHANT_ETA_ADDRESS!,
replayWindowMs: 10 * 60_000, // 10 minutes
});For multi-process deployments you'll want to back this with Redis — the in-memory map only protects within a single process.
Next: Deploy — runtime notes for production.