Docs/Merchants

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

ts
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.

ts
// 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.

ts
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:

ts
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.

ts
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.