Ethereum QR codes — EIP-681, chain IDs, stablecoins
An ethereum qr code encodes an address, an EIP-681 URI, or a stablecoin transfer. Here's what wallets read, why the chain ID matters, and what breaks.
An ethereum qr code looks like a bitcoin one at a glance and behaves nothing like it underneath. The pixels are the same standard — ISO 18004 — but what those pixels encode is different on every axis that matters: the address format, the URI scheme, the optional fields, the wallets that will accept what, and the one piece of information a Bitcoin QR has never needed and an Ethereum QR sometimes must carry to be safe at all. That last piece is the chain ID, and most of the failure modes in this post trace back to it.
This post walks through what an ethereum qr code actually encodes, how EIP-681 differs from BIP-21, why the same wallet QR can quietly mean different things on Mainnet, Optimism, Arbitrum, Polygon, and Base, what changes when the destination is a USDT or USDC transfer instead of plain ETH, and the placements where this all becomes load-bearing — conference tip jars, on-chain merch checkouts, DeFi event sponsorship cards. None of it is rocket science. All of it is the difference between a scan that lands and one that sends real money to a chain the recipient doesn't watch.
An ethereum qr code is one of three things
There's no fourth option. Every Ethereum payment QR you've ever scanned encodes one of these three formats, and the wallet on the scanning side reads them differently.
The bare-address form is the simplest. The encoded string is just the 20-byte address in its checksummed hex form: 0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B and nothing else. Every Ethereum wallet on earth reads this. There's no parser to fail, no fields to misinterpret. The sender's wallet asks them to choose the chain, type the amount, and pick whether they're sending ETH or a token. That's fine for "send me whatever, on whichever chain you have funds on" but it puts every decision back on the sender, which is the failure mode that costs the most money — sender picks the wrong chain, funds land in a contract address that exists on Mainnet but is unwatched on Polygon, and the recipient has no idea anything was sent.
The EIP-681 URI is the structured form. The encoded string is a URI with the scheme ethereum: followed by the target, an optional chain ID, an optional function selector, and a query string of typed parameters. The minimum looks like this:
ethereum:0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B
That's the address wrapped in the scheme — equivalent to the bare-address QR for any wallet that ignores the prefix. Add the chain ID and the value and it starts describing the payment:
ethereum:0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B@1?value=2.5e16
That single string pre-fills three things in the sender's wallet — the destination address, Mainnet (chain ID 1) as the network, and 0.025 ETH (2.5×10^16 wei) as the amount. The sender taps once to confirm. No typing, no manual chain-picking, no chance of a fat-fingered amount or a wrong-network slip.
The third form is the ERC-20 token transfer. Same scheme, same syntax, but the target is now the token contract address and the URI carries a function selector telling the wallet "this is a transfer call, not a value send." A USDC transfer on Mainnet looks like this:
ethereum:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48@1/transfer?address=0xRecipient...&uint256=10000000
The /transfer part is the function on the token contract. The address= is the recipient. The uint256= is the amount in the token's smallest unit — for USDC, six decimals, so 10000000 is 10.00 USDC. The structure is the same logic as the BIP-21 bitcoin URI and the SEPA EPC / UPI payment formats — one structured string the receiving app parses into a pre-filled transaction — except the rail is a smart-contract call on whichever chain the URI nominates.
EIP-681 was finalised in late 2017. The full grammar is published at eips.ethereum.org and supported across MetaMask, Trust Wallet, Rainbow, Coinbase Wallet, Frame, Rabby, and most embedded-WebView wallets in dapps. The two pieces most generators get wrong: the @chain_id segment is part of the path, not the query string (so ethereum:0x...@1?value=..., not ethereum:0x...?chain_id=1&value=...), and the value field uses base-10 wei with optional scientific notation — 2.5e16 parses; 0.025 ETH does not.
The chain ID is what makes Ethereum QRs different
A Bitcoin QR encodes a destination on one network. There's mainnet and testnet, and the address prefixes (bc1 versus tb1, 1/3 versus m/n/2) make it impossible to mix them up — every wallet refuses to send a mainnet transaction to a testnet address. The chain is encoded in the address itself.
Ethereum addresses don't work that way. The same 20-byte address exists on Mainnet, on Optimism, on Arbitrum, on Polygon, on Base, on Linea, on Scroll, on Avalanche C-Chain, and on every other EVM-compatible chain. Whether anyone controls that address on a given chain depends on whether the corresponding private key has been used on that chain. An address that holds 50 ETH on Mainnet might be entirely empty — and unwatched — on Polygon. A USDC transfer to that address on the wrong chain lands in the void as far as the recipient knows.
This is the load-bearing detail that BIP-21 doesn't have to worry about. EIP-681's @chain_id segment is the explicit fix: the URI nominates the chain, the wallet switches networks before signing, and the sender can't accidentally send on the wrong rail. The five chain IDs you'll encounter in 95% of payment QRs:
The relevant operational point: if your EIP-681 URI omits the @chain_id, EIP-681 says the wallet should default to Mainnet. In practice, wallets behave differently. MetaMask defaults to whatever network the wallet is currently switched to. Rainbow asks. Coinbase Wallet tries to detect from the address. Trust Wallet defaults to Mainnet for an unknown-token transfer but to the currently-selected chain for a plain ETH value send. The only reliable behaviour is the URI you control — set the @chain_id explicitly on every QR you print and the wallet has no room to guess.
Why a Bitcoin QR and an Ethereum QR aren't interchangeable
This is the question that keeps showing up in support inboxes for crypto-accepting merchants. The two QRs look identical to a phone camera. They scan in the same milliseconds. They both encode short strings. They are wholly incompatible in every way that matters.
Three differences worth carrying around in your head:
Schemes. A Bitcoin QR encodes a string starting with bitcoin:. An Ethereum QR encodes a string starting with ethereum:. Phone operating systems route the resolved string to the wallet registered for that scheme. iOS and Android each maintain a system-level mapping from scheme to installed app — bitcoin: opens the user's default Bitcoin wallet, ethereum: opens the user's default Ethereum wallet. If only one is installed and the user scans the other, the camera shows the raw text in a notification and the scan fails politely.
Address formats. Bitcoin addresses are base58 or bech32 — letters, numbers, no 0x prefix, length varies by type (26 to 62 characters). Ethereum addresses are hex with a mandatory 0x prefix, always 40 hex characters after the prefix. A wallet receiving a Bitcoin address through an ethereum: URI rejects it as not-a-valid-Ethereum-address before doing anything else. A wallet receiving an Ethereum address through a bitcoin: URI does the same in the other direction. There is no path where a single QR works for both.
Networks. A Bitcoin QR encodes a destination on one chain — the Bitcoin network. An Ethereum QR encodes a destination on whichever chain the URI nominates, and the address itself doesn't carry that information. The chain-ID problem the previous section spent 600 words on is uniquely an Ethereum-side concern.
A merchant accepting both Bitcoin and Ethereum needs two separate QRs. Some donation surfaces print them side by side with labels under each. Some print one and rely on a redirect page (the kind covered in the URL-vs-short-link discussion) that lets the scanner choose. The first pattern is simpler and works without an internet round-trip; the second is what big public donation surfaces tend to land on once they're accepting four or five chains.
Stablecoin QRs — USDT and USDC
Most of the Ethereum-QR traffic in the wild isn't ETH. It's USDT or USDC. A QR for a stablecoin transfer differs in three places from a plain ETH QR.
The target is the token contract, not the recipient. The ethereum: URI's address segment after the scheme is the token contract address — for USDC on Mainnet that's 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48. The recipient address moves into the address= parameter on the query string. Most generators get this wrong on the first try because the BIP-21 mental model puts the recipient right after the scheme.
The function selector is /transfer. That's the standard ERC-20 method for sending tokens. The wallet reads the selector, knows the URI is a token send rather than a value send, and constructs the appropriate contract call. Without the selector, the wallet treats the URI as a plain ETH send to the token contract — which would in most cases revert, but in some edge cases produces a stuck transaction.
The amount is in the token's smallest unit. USDC has six decimal places, so 10 USDC is uint256=10000000. USDT also has six on Mainnet. DAI has 18 — same as ETH itself. Getting this wrong by a factor of 1000 is the most common bug in hand-built EIP-681 URIs. Always look up the decimals for the specific token on the specific chain (a token's decimals can differ across chains for older bridged tokens; native USDC is six everywhere it's deployed natively).
A practical USDC QR for a 25-dollar conference tip jar on Optimism:
ethereum:0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85@10/transfer?address=0xRecipient...&uint256=25000000
The 0x0b2C... is the native-USDC contract on Optimism. @10 is Optimism's chain ID. /transfer is the ERC-20 method. address= is the recipient. uint256=25000000 is 25.00 USDC. The sender's wallet switches to Optimism, opens the USDC transfer flow with the recipient and amount pre-filled, and the user taps once to confirm. Fees are roughly five cents; settlement is a few seconds.
A common confusion: the same QR cannot also accept USDT. ERC-20 tokens are independent contracts. A transfer call to the USDC contract sends USDC; a transfer call to the USDT contract sends USDT. If you want to accept both, you print two QRs (one per token) or you print a QR that points at a redirect page where the sender picks. Wallets won't quietly substitute one stablecoin for another based on what the sender holds — that would be a security hole, not a convenience.
A QR for USDC and a QR for USDT are independent contracts under the same address syntax. There is no scan that means "any stablecoin." Pick one or print two.
Build an EIP-681 URI
A working preview. Pick the chain, type the recipient address, set the amount, and the encoded string updates in real time. Paste the result into any QR generator. State saves locally — refresh the page and your last build comes back.
The output is the literal text a wallet reads. Drop it into the Bitcoin QR code generator — the same tool handles ethereum: URIs as plain text through the broader chain field — or paste it into any generic QR library. EIP-681 is a text format and any QR encoder produces the correct pixels for that text.
Real placements where this stops being abstract
Three patterns that show up in the wild more often than the theory papers suggest:
Conference tip jars. A speaker or a booth puts a QR on the table that says "tip in USDC on Base — five cents in fees, instant settlement." The QR encodes the EIP-681 URI for a 5-USDC transfer with a pre-filled amount, on Base, to the speaker's address. Attendees with the Coinbase Wallet or any Base-enabled mobile wallet scan, confirm, and the speaker sees the deposit in twenty seconds. This is roughly the highest-velocity place ETH-style QRs live — sub-dollar tips, near-zero friction, and the chain choice (Base or Optimism) is what makes the gesture actually work versus a $40-fee Mainnet send that nobody completes.
On-chain merch payments. A small merch booth at a Web3 event takes USDC for hoodies. The booth has a printed QR for each chain they accept — Mainnet (for the customers who only have funds there and don't mind the fee), Optimism, Base, sometimes Polygon. Each QR encodes the EIP-681 transfer call with the price pre-filled in the token's smallest unit. The customer picks the chain that matches their wallet, scans, confirms, and the booth attendant sees the transaction land before handing over the merch. The same operational hygiene that applies to high-value QR scans where verification is the load-bearing step applies here in concentrated form — show the address in human-readable hex alongside the QR so the customer can sanity-check before confirming.
DeFi event sponsorship codes. Conference sponsors who want to enable on-chain donations or sign-ups during a session print QRs that point at a sponsor's address with a pre-filled symbolic amount (1 USDC, say) on a low-fee L2. Scans aggregate into a public count the sponsor shows on screen. This is closer to a marketing surface than a payment one — the QR is a participation token more than a transaction — but it's the same EIP-681 plumbing underneath.
In all three cases the design rule is the same: nominate the chain explicitly in the URI, use a low-fee chain for amounts under a few hundred dollars, print the receiving address in human-readable text alongside the QR for verification, and use error correction H if the print is going to live more than one event.
Hardware wallets and the verification step
Anyone who has set up a Ledger or a Trezor has watched the device walk through a hex address one chunk at a time, asking the user to verify each segment matches the host computer's screen. That exists because of clipboard-swap malware and the same threat extends to QR scanning. A QR encoding an Ethereum address that's been replaced by an attacker's sticker delivers the attacker's address to the wallet without the user noticing — the camera reads the pixels and shows the parsed address on screen, where a tired or hurried user might confirm without reading.
Hardware wallets close that gap by showing the receiving address on the device's own screen, character-by-character or in chunked groups, requiring user confirmation against whatever the companion app displays. EIP-191 and EIP-712 add the same protection for signed messages and structured-data signatures — the device shows what's actually being signed, not what the host computer claims is being signed. The verification step is the whole point of the device. The QR scan is just an input to the companion app; the device's own display is the only screen in the chain a compromised host can't substitute.
The consumer-facing version of that advice: for any Ethereum QR scan involving a non-trivial amount, verify at least the first four and last four characters of the address on the wallet's confirmation screen against the address printed in human-readable text alongside the QR. Wallets that display the parsed address in large text make this easier; some custodial mobile apps make it harder by hiding the address behind a "Send" button. Use a wallet that respects the verification step. MetaMask, Rabby, and Frame all do this well; some embedded WebView wallets inside dapps do it less obviously.
Error correction is still where most printed wallet QRs go wrong
EIP-681 URIs aren't short. A plain ETH send is about 70 characters; a USDC transfer call is closer to 130. That's a denser QR than a Bitcoin BIP-21 URI for the equivalent payment, which means smaller modules at the same printed size and less margin for print wear.
The rule is the same as for any printed payment QR: error correction level H (30%), full stop. The full trade-off lives in the QR error correction levels post, but for Ethereum-style URIs the calculus is brutally clean — a single-character flip in a hex address can produce a different valid-looking address (Ethereum addresses are checksummed with EIP-55, and any wallet worth using will reject an address that fails the checksum, but the failure mode of a successfully-flipped-and-still-valid address exists, especially in older non-EIP-55 lowercase form). Level H absorbs the most damage in three out of four positions and gives the wallet's checksum the best chance to catch what error correction couldn't.
Print straight from the SVG at the size you'll display it. Don't bitmap-scale a 200px PNG up to a 5cm-wide sticker — sub-pixel interpolation softens module boundaries enough to push borderline reads over the edge. The general visual rules — module-pitch, scan distance, contrast — that govern any printed code apply here exactly as they do for any other payment QR.
Generating Ethereum-style payment QRs? Linked.Codes handles both BIP-21 and EIP-681 payloads through the same generator, with error correction defaults set for printed-vs-on-screen contexts and chain-ID enforcement built into the URI builder.
Open the generatorSecurity context — what an Ethereum QR can and can't protect
An Ethereum QR in a public space carries the same risks as any other public QR plus one structural Ethereum-specific weakness: transactions on most chains are practically irreversible once finalised, and the "irreversibility window" varies — Mainnet finalises in about 13 seconds for practical purposes (12 epochs to actual finality), L2s reach soft confirmation in seconds but full L1 finality only after the rollup's challenge window (seven days for optimistic rollups like Optimism and Arbitrum). The defence is the same family covered in the QR-codes-and-phishing consumer guide: verify the address on the wallet confirmation screen, match the displayed address against the human-readable text printed alongside the QR, and reject the scan if the two don't match.
The infrastructure side — keeping a printed QR's link to your actual wallet safe across the lifetime of the print, separate from the address it carries — is its own discipline. The QR codes documentation covers the platform-level options for hosting receive pages, and the broader case for running printed QRs on a domain you control applies to Ethereum receive surfaces just as much as it does to marketing links — a printed QR pointing at your-domain.example/donate-eth (which displays the EIP-681 URI for one-tap scanning, after the page loads) lets you rotate the receiving address without reprinting, at the cost of one extra hop the sender's wallet has to follow. For most public-facing tip jars and merch booths that hop is acceptable; for cold-storage destinations it's the wrong trade-off and the QR should encode the address directly.
What we'd ship by default
For an on-screen receive code in a wallet app: EIP-681 URI with address, chain ID, and value. Error correction L or M. Regenerated per request. Standard, boring, and what every wallet already does.
For a printed conference tip jar in USDC on a low-fee chain: EIP-681 transfer URI with token contract, chain ID, recipient address, and a suggested amount (or omit the amount and let the sender choose). Error correction H. Printed at 4cm minimum so module size survives arm's-length scans. Receiving address printed in plain text alongside for verification. Refresh the print every 6-12 months as a hygiene measure (and because conference branding rotates anyway).
For a merch booth taking USDC across two or three chains: one EIP-681 QR per chain, each with the chain ID baked in, printed side by side with the chain name as the headline above each. Sender picks the chain that matches their funded wallet. Error correction H on every print.
For a sponsorship participation QR at a DeFi event: EIP-681 transfer URI for 1 USDC on Optimism or Base, pre-filled amount, error correction H, scan-count surfaced on a public display in the room. Treat the QR as a participation token first and a payment second; the dollar value is symbolic.
For cold-storage paper wallets: bare address (no EIP-681 wrapper), printed at error correction H, laminated, stored offline. The same conservative posture as a Bitcoin paper wallet and for the same reason — the paper might survive the next two decades and you want maximum wallet compatibility across whatever EVM clients exist by then.
Does a wallet ever auto-pick the chain when an EIP-681 URI omits the chain ID?
Mostly no, and the behaviour isn't standardised. EIP-681 specifies that an omitted chain_id should default to Mainnet, but real-world wallets disagree. MetaMask uses whatever chain the wallet is currently switched to. Rainbow prompts the user. Coinbase Wallet tries to infer from the address (and sometimes guesses wrong). The reliable behaviour is the URI you control — set the @chain_id explicitly on every payment QR you print and the wallet has no room to guess.
Can one QR work for both ETH and a stablecoin like USDT?
No. ERC-20 tokens are independent contracts, and an EIP-681 URI nominates exactly one — either ETH (no function selector, value in wei) or a specific token contract address with the /transfer selector. Wallets won't substitute one asset for another based on what the sender holds. Print two QRs if you want to accept both, or print one QR that points at a redirect page where the sender chooses the asset.
What happens if a sender accidentally sends to the wrong chain?
The transaction lands at the same 20-byte address on the wrong chain. If the recipient controls the corresponding private key on that chain (every EVM chain shares the same keypair-to-address derivation), the funds are technically recoverable — but only if the recipient knows to look. If the recipient never watches that chain, the funds sit in limbo until someone notices. For tokens that aren't deployed on the wrong chain at all (rare, but possible), the transaction can revert and the sender pays gas without losing principal. Bake the chain ID into the URI and this failure mode largely vanishes.
How do hardware wallets verify addresses scanned from a QR?
By showing the parsed address on the device's own screen and requiring the user to confirm it matches whatever the companion app displays. The QR scan is an input to the companion app; the device's display is the only screen in the chain that a compromised host computer can't substitute. EIP-191 and EIP-712 extend the same pattern to signed messages and structured-data signatures. The verification step is the whole point of the device and is non-skippable on every reasonable model.
Do Lightning-style Layer-2 ETH QRs exist?
Yes — Optimism, Arbitrum, Base, and the other L2 rollups are the Ethereum equivalent of "off-Mainnet rails for small fast payments." The QR format is the same EIP-681 URI with the L2's chain ID in the @chain_id slot. The user experience is similar to Lightning: cents per transaction, near-instant confirmation, full security inherited from L1 after the challenge window. The format isn't a separate protocol the way Lightning is — it's the same URI grammar pointing at a different chain ID.
What error correction level should an Ethereum payment QR use?
H for anything printed. EIP-681 URIs are longer than Bitcoin BIP-21 URIs for the equivalent payment, which produces a denser QR with smaller modules at the same physical size. Level H gives the print 30% redundancy against UV fade, smudging, sticker overlap, and fold-line damage. Level L or M is fine for an on-screen wallet QR that lives for thirty seconds; it isn't fine for a printed sticker that has to survive a conference.
Is encoding an ethereum: URI safe in any generic QR generator?
The encoding itself is — the QR encodes the literal text of the URI and any standards-compliant QR library produces the correct pixels. What matters is whether the URI inside is well-formed (chain ID in the path, value in wei, function selector for token transfers) and whether the address belongs to a wallet you control. The QR library has no way to validate either. Generate the address from the receiving wallet, double-check the chain ID, and send a small test transaction before publishing.
Sourcesshow citations
- EIP-681 — Transaction Request URL Format (canonical specification on eips.ethereum.org): https://eips.ethereum.org/EIPS/eip-681
- EIP-20 — Token Standard (ERC-20, defines the transfer function selector EIP-681 references): https://eips.ethereum.org/EIPS/eip-20
- EIP-55 — Mixed-case checksum address encoding: https://eips.ethereum.org/EIPS/eip-55
- ethereum.org developer documentation — Sending transactions and reading addresses: https://ethereum.org/en/developers/docs/transactions/
- MetaMask documentation — handling deep links and EIP-681 URIs: https://docs.metamask.io/wallet/concepts/wallet-api/
- Ledger Academy — Verify a receiving address on your Ledger device: https://www.ledger.com/academy/security/the-safest-way-to-use-bitcoin
- ISO/IEC 18004:2024 — QR code bar code symbology specification (error correction levels L/M/Q/H defined): https://www.iso.org/standard/83389.html
- Wikipedia — Ethereum (chain ID, EVM, L2 networks overview): https://en.wikipedia.org/wiki/Ethereum
Try it on your own domain
Branded short links and dynamic QR codes, on your subdomain or your own domain. One-time purchase, no per-click fees.