Docs/Merchants

Deploy

The merchant SDK is a regular Node package — no native deps, no persistent state, no webhooks. Anywhere Node runs, this runs.

Recommended platforms

  • Railway, Fly, Render. Drop in with a Dockerfile or a buildpack. Set MERCHANT_ETA_ADDRESS + HELIUS_RPC_URL as env vars. Best fit for Express-style servers.
  • Vercel / Cloudflare Workers. Use the SDK inside a Next.js route handler or a Workers fetch handler — the charge() middleware has no Express runtime dep, just express-shaped req/res. Express adapter helpers are on the roadmap.
  • AWS Lambda / GCF. Wrap with serverless-http. Cold-start cost is ~300ms for the first request's RPC handshake; warm requests are ~150–400ms (two getTransaction reads).

Environment variables

VariableRequiredNotes
MERCHANT_ETA_ADDRESSYesCopy from your merchant dashboard. The Solana pubkey of your Umbra encrypted token account.
HELIUS_RPC_URLRecommendedProduction-grade RPC endpoint. Default is the public Solana RPC, which is rate-limited. helius.dev is generous on the free tier.

There is no facilitator API key — verification is direct chain reads.

Scaling notes

  • Stateless on the request path. No DB, no caches besides the in-memory replay map (queueSignature → seen-at).
  • One RPC dependency. Two getTransaction calls per paid request. At 100 RPS that's 200 RPC reads/sec — well within Helius free-tier limits, comfortable on any paid tier.
  • Replay map is per-process. Horizontal scale across multiple Node processes means each holds its own map. For correctness this is fine — the on-chain queue tx is the source of truth, and a duplicate retry that happens to hit a different process still fails the on-chain check (queue tx already consumed). For tightness against short-window replays across processes, back the map with Redis.

Health checks

The middleware doesn't interfere with /healthz, /_ping, or whatever pattern your platform uses — it only runs on routes you explicitly wrap with pay.charge().

ts
app.get("/healthz", (_req, res) => res.json({ ok: true }));        // free
app.get("/article/:id", pay.charge({ amount: "10000" }), handler); // paid

Next: Error handling.