Accept BB84 Payments On Your Site
Drop a QR code onto a checkout page, invoice, or donation button that already carries the amount and currency — no backend, no wallet connection, no API key. The customer scans it with the BB84 app, and the recipient, amount, and asset (ETH, USDC, or EURC) are filled in automatically.
This is the same payload shape bb84.com/receive has always generated for a plain ETH request — bb84-pay.js just makes it embeddable on your page, and extends it to USDC and EURC.
ethereum: URI a BB84 wallet reads amount and asset from.data-bb84-pay div on the page — no JS required.BB84Pay.render() for checkout flows that build the amount dynamically.Quick Start
Add this to any page. Replace shop.bb84.eth with your own address or bb84.eth name.
<!-- 1. A placeholder where the QR should appear -->
<div data-bb84-pay
data-to="shop.bb84.eth"
data-amount="20"
data-asset="usdc"></div>
<!-- 2. The widget script — finds it and renders the code -->
<script src="https://bb84.com/bb84-pay.js" defer></script>
bb84-pay.js scans the page for data-bb84-pay elements on load, lazy-loads the (tiny) QR-rendering library from cdnjs the first time it's needed, and draws a self-contained QR + amount caption in place.The Payload
A BB84 payment-request QR encodes an ethereum: URI — the recipient, followed by the amount in the asset's smallest unit:
ethereum:<recipient>?value=<raw-units>&asset=<usdc|eurc>
<recipient> is a 0x… address, a *.bb84.eth name, or a real Mainnet *.eth name. Two concrete examples:
// Request 20 USDC
ethereum:shop.bb84.eth?value=20000000&asset=usdc
// Request 0.05 ETH — omit "asset" for plain ETH
ethereum:shop.bb84.eth?value=50000000000000000
You never need to hand-build this string — BB84Pay.buildValue(to, amount, asset) does the decimal-to-raw-units conversion for you (see Programmatic API).
Amount & Asset
value is always an integer in the asset's smallest unit, matching how BB84 stores balances on-chain:
| Asset | data-asset | Decimals | "1" means |
|---|---|---|---|
| Ethereum | eth (default — can be omitted) | 18 | 1 wei |
| Circle USDC | usdc | 6 | 0.000001 USDC |
| Circle EURC | eurc | 6 | 0.000001 EURC |
data-amount renders an address-only QR (a generic "pay me" code, no fixed amount) — the customer types their own amount in the app. This is intentional: it's how the widget stays useful for tip jars and donation pages, not just fixed-price checkouts.What Happens on Scan
Customer opens the BB84 app and taps the QR icon on Send
The camera reads the ethereum: URI straight off your page — no app switch, no copy-pasting an address.
Recipient, amount, and currency are filled in automatically
If the code specifies asset=usdc, the app switches its active currency to USDC so the right balance and OTA are shown — since this is an explicit request from you, not something the customer typed themselves.
Customer reviews and sends
Nothing leaves their device until they confirm — the widget only ever displays a request, it can't move funds on its own.
Under the hood this is the exact same shorthand the BB84 app also accepts when typed directly into the recipient field, e.g. 20usd.shop.bb84.eth — handy for sharing a request over chat instead of a QR image.
Declarative Embed
The simplest integration needs no JavaScript of your own. Add one <div data-bb84-pay> per QR code you want on the page:
<div
data-bb84-pay
data-to="shop.bb84.eth" <!-- required -->
data-amount="49.90" <!-- optional -->
data-asset="eurc" <!-- optional, default "eth" -->
data-size="220" <!-- optional, pixels, default 200 -->
data-label="true" <!-- optional, shows "Requesting 49.90 EURC" -->
></div>
Elements are matched once on DOMContentLoaded. Adding a widget div later (e.g. after fetching a cart total) — call BB84Pay.init() to pick up any new ones, or just call BB84Pay.render() directly.
Programmatic API
For a checkout flow where the total isn't known until runtime:
BB84Pay.render("#checkout-qr", {
to: "shop.bb84.eth",
amount: cartTotal.toFixed(2), // decimal string, e.g. "49.90"
asset: "usdc", // "eth" | "usdc" | "eurc" — default "eth"
size: 220,
});
BB84Pay.render(target, options)
Draws the QR into target (a CSS selector string or a DOM element). Returns a Promise that resolves once drawn, and rejects with a descriptive error if to/amount don't parse — the widget also shows that error inline so a bad integration fails loudly, not silently.
| Option | Type | Default | Notes |
|---|---|---|---|
to | string | — | Required. Address, bb84.eth, or Mainnet .eth name. |
amount | string | number | none | Omit for an address-only, any-amount QR. |
asset | "eth"|"usdc"|"eurc" | "eth" | Ignored (no asset= param emitted) when "eth". |
size | number (px) | 200 | Outer widget width/height. |
label | boolean | true when amount set | Shows a "Requesting X ASSET" caption under the code. |
BB84Pay.buildValue(to, amount, asset)
Returns the raw ethereum:… string without rendering anything — useful if you're feeding your own QR library (a native app, a PDF invoice generator, etc.) instead of the DOM widget. Returns null if the inputs don't parse.
const value = BB84Pay.buildValue("shop.bb84.eth", "20", "usdc");
// "ethereum:shop.bb84.eth?value=20000000&asset=usdc"
Live Demo
This calls the real bb84-pay.js hosted at /bb84-pay.js — the exact widget your page would load.
No-JS Alternative
If your site can't load third-party scripts at all, generate the QR once ahead of time at bb84.com/receive (ETH only), download the PNG, and embed the image directly — a static <img> tag works everywhere. For USDC/EURC or a dynamically-computed amount, build the ethereum:… string yourself using the format in The Payload and feed it to any QR-code image generator.
Security Notes
- The widget only displays a request — it never has access to funds, private keys, or the customer's device beyond rendering a QR image.
bb84-pay.jsis static and unauthenticated by design, same as any QR-image generator — nothing about it needs an API key, and nothing you embed can be revoked or rate-limited server-side.- Double-check
data-tois your own address or bb84.eth name before shipping — the widget validates the shape of the recipient (a valid address or ENS name) but has no way to know whose it is. - The QR-rendering library (
qrcodejs) is lazy-loaded fromcdnjs.cloudflare.comonly once, and only ifwindow.QRCodeisn't already present — check your CSP allows that origin if you enforce one.