The complete source for a payment-link service that takes USDT and USDC on six EVM chains. Buyers pay from whatever chain they already hold; the money goes straight to your wallet. No signup, no KYC, no custody, no platform fee.
Six chains, two stablecoins — BSC, Ethereum, Polygon, Arbitrum, Base, Optimism. 11 payment rails from one wallet address.
The amount is the identity. Each invoice gets a unique micro-amount, so a transfer on any chain matches the right order. No per-chain order numbering, no collisions.
Zero dependencies. Plain Node. No database, no ORM, no build step. Two files and a folder of HTML.
You are not a custodian. The code reads public Transfer events. It never signs, never holds a key, never touches the money.
Multi-seller by design — each payout address gets its own chain cursors, so one instance can serve many wallets without cross-matching.
49 USDT
Pay in USDT or USDC — BSC, Ethereum, Polygon, Arbitrum, Base or Optimism. The download link unlocks the moment the transfer confirms — no account needed.
README.md — deploy notes, the six measured RPC quirks, and the failure modes worth knowing
The numbers that matter (measured, not claimed)
chain
blocks per getLogs call
round trip
note
BSC
1000
375 ms
18 decimals on USDT/USDC
Ethereum
10000
578 ms
6 decimals
Base
3000
369 ms
USDC only — no official USDT
Polygon
5000
~540 ms
6 decimals
Optimism
5000
~522 ms
6 decimals
Arbitrum
3000
~530 ms
publicnode returns 403 on eth_getLogs — must use arb1.arbitrum.io
Every row above was measured from a real machine, not copied from docs. The Arbitrum row alone saves an afternoon.
Run it
# no install step — plain node
node paylink-server.js
# it prints:
# PayLink on http://127.0.0.1:8901
# rails: 11 channels across 6 chains
# create an invoice
curl -s localhost:8901/api/invoice -H 'content-type: application/json' \
-d '{"payTo":"0xYourWallet","price":49,"title":"Invoice #1"}'
Put it behind any reverse proxy. If you already run a Cloudflare tunnel, add a path rule — the kit ships the exact ingress snippet.
The failure modes it already handles
Exchange withdrawal fees. A buyer paying 19.27 from Binance may land 18.27. Exact matching alone would lose that sale — tolerance matching catches it, and the order is flagged underpaid.
RPC failure must not advance the cursor. If a block range fails, the cursor stays put and the range is retried. Slow is fine; silent loss is not.
Amount-pool saturation. Only 98 cent-values exist per price. Past that, invoices collide and two buyers fight over one transfer. The pool tiers up to 998 and then refuses to issue rather than hand out a doomed invoice.
Unmatched deposits are logged, never swallowed. Someone sends the wrong amount and it does not vanish — it lands in unmatched-deposits.log with the txid.
Decimals are read from the contract, not hardcoded. BSC is 18, everyone else is mostly 6, and getting this wrong is a 1012 error.
What this does not do
It does not convert between tokens — 1 USDC counts as 1 USDT, which is fine for stablecoin checkout and wrong for anything else.
It does not handle BTC, Tron, or Solana. EVM only. Adding Tron is a separate watcher (the original store had one).
It is not a hosted service — you run it. That is the point: nobody can freeze your checkout.
It is not audited. Read the code; it is under 1500 lines total.