We are killing stablecoin checkout addresses.
Addressless checkout: onchain, easy to add, and 3-D Secure-style USDT payments on Whitechain. Every payment is bound to the website, so a hacked page can't redirect the money.
Live app · Demo shop · Real site: 0xmujahid.xyz · First real payment · Docs
Built at ETHGlobal Mumbai 2026 on Whitechain Sepolia testnet, by mujahid002 and sowjanyalakshmi001.
A website registers its payout wallet once (one DNS TXT record proves it owns the domain). The buyer approves the
website and the amount with a passkey (Touch ID). The contract checks the browser-signed site and pays only that site's
registered wallet. pay() has no payee field, so compromised checkout JavaScript can change the page, not who gets
paid.
This defends against recipient substitution: hacked code on a real website swapping who gets paid. It is not phishing protection (a lookalike domain that registers its own wallet is a separate threat; the fake-site demo shows it). Plainly: a 3-D Secure-style confirmation, not hack-proof. Testnet only (testnet USDT has no value), not audited. Full trust model: docs/THREAT-MODEL.md.
| Link | |
|---|---|
| Live app: landing, onboarding, checkout frame | https://sitebound-pay.pages.dev |
| Demo shop | https://sitebound-demo.pages.dev |
| Real site using it ("Buy me a coffee") | https://0xmujahid.xyz |
| Attack demos | Address swap, fake site and overlay |
| Dashboard for 0xmujahid.xyz | sitebound-pay.pages.dev/dashboard |
| First real USDT payment (testnet transaction) | 0x65a00ae0…71d0 · receipt |
| Contract SiteboundV2, verified source | 0x6411…9596 |
| Relayer health | https://sitebound-relayer.sitebound-relayer.workers.dev/health |
Need testnet funds to pay? The landing page's Try it section lists the Whitechain faucet and the USDT swap steps.
- Register the site. Your browser creates a payout wallet. Add one DNS TXT record,
_sitebound.<your domain>=sitebound=<wallet>. The relayer checks DNS, not the page, and registers the site to that wallet on Whitechain. - Paste one tag.
<sitebound-pay amount="12.00" order="A123">. There is no address in it. - Buyers pay with Touch ID. A frame on another origin shows the site name the browser reports. The contract checks the passkey signature on chain and pays only the wallet registered for that site. The buyer pays no gas, only a 0.01 USDT fee; the relayer pays the gas.
Buy the builder a coffee for 3 test USDT at https://0xmujahid.xyz (or on the landing page's Try it section).
- Get test WBT from the Whitechain faucet (GitHub sign-in) and add Whitechain Testnet, chain 1874, to your wallet.
- Swap a little WBT for test USDT on YetiTrade.
- Pay 3 USDT, choose your wallet, then confirm with Touch ID. You pay no gas.
Test USDT has no real value.
- Hacked checkout code swaps addresses. The website's address lives in its checkout JavaScript. One compromised dependency rewrites it and the money goes to the attacker. The npm "qix" compromise in Sep 2025 rewrote crypto recipients this way. More in docs/WHY.md.
- Addresses are bad checkout UX. Buyers copy, paste or scan 40 hex characters they can't read.
- Crypto has no 3-D Secure. Card buyers get a confirmation that shows the merchant and the amount. Crypto buyers sign raw transactions.
- Buyers need gas. With Sitebound a relayer pays the gas and the buyer pays a 0.01 USDT fee.
- Hard for a web2 business to start (our view). Sitebound's onboarding is a domain, a wallet created in the browser and one DNS record.
sequenceDiagram
autonumber
participant S as Website
participant R as Relayer
participant C as SiteboundV2 (Whitechain)
participant B as Buyer (browser + passkey)
Note over S,C: Once per website
S->>R: DNS TXT _sitebound.host = sitebound=payout
R->>C: register(site, payout, attestation)
Note over B,C: Every payment
B->>B: Sitebound frame shows site + amount, Touch ID signs
B->>R: signed payment
R->>C: pay(...) and pays the gas
C->>C: verify passkey (P-256 at 0x100), site from clientDataJSON, caps, deadline
C-->>S: USDT to the wallet registered for that site
- Website, once. DNS TXT record
_sitebound.<host>=sitebound=<payout>. The attestor checks it and signs, and anyone can submitregister(). The website pastes one script tag and a<sitebound-pay amount="12.00" order="A123">element. There is no address in its code. The onboarding wizard creates the payout wallet in the browser. - Buyer, first time. In a popup on the Sitebound origin: connect a wallet, pick limits, create a passkey, sign a capped EIP-2612 permit and an EIP-712 binding (passkey key, per-payment cap, total cap, expiry).
- Buyer, every payment. The button opens the Sitebound frame in a cross-origin iframe, which shows the site (from
location.ancestorOrigins) and the amount, then calls WebAuthnget(). One Touch ID signs one exact payment. - Contract. Chrome writes the embedding page's origin into the signed
clientDataJSONastopOrigin, and page code can't change it.pay()checks the passkey signature with the P-256 precompile, checks the signature came from the Sitebound frame, and moves USDT to the split registered forkeccak256(topOrigin).
Why on-chain settlement: the registry binds each site to a payout wallet and pay() takes no payee argument, so
no server or page can redirect the money, and anyone can read and verify the rules. An off-chain processor can't give
the buyer that guarantee. All diagrams: docs/ARCHITECTURE.md.
Sitebound runs on Whitechain Sepolia (chain 1874) because the chain has the pieces the design needs, checked on 4 to 6 Nov 2026. Details, and what was hard, are in WHITECHAIN_FEEDBACK.md.
- P-256 precompile at
0x100(RIP-7212). The contract verifies every passkey (Touch ID) signature on chain. Without it, each payment would need a Solidity P-256 verifier. Gas ofpay()on Whitechain: 305,060 for the one real payment below (a single sample, testnet). - USDT with EIP-2612
permit. The buyer grants the capped allowance with a signature, so the relayer can pay all the gas and the buyer never needs WBT. - A public RPC with no key (
https://rpc.testnet.whitechain.io), so the frame, the dashboard and "Verify us" read the chain straight from the browser. - Verified source. SiteboundV2 is verified on the Whitechain explorer (Blockscout) and on Sourcify.
Diagrams of each flow: docs/ARCHITECTURE.md.
| Part | Path | What it does |
|---|---|---|
| Contract | src/SiteboundV2.sol |
Site registry with splits and site roles, k-of-n attestors, passkey bindings with caps, pay(), refunds. Deprecated v1: src/Sitebound.sol. |
| Frame | web/frame/ |
Checkout iframe (get() only) and the first-time setup popup. Vanilla JS and vendored ethers 6.17.0. |
| Button | web/button/sitebound.js |
The <sitebound-pay> element. It opens the frame from its own host and fires sitebound:paid. |
| Merchant helper | web/merchant/sitebound-merchant.mjs |
verifyPayment and signQuote, for the website's server. |
| Relayer (optional) | worker/ |
Cloudflare Worker. It pays gas for register, permit, bind and pay, and is also the DNS attestor. See worker/README.md. |
| Onboarding | web/onboard/ |
Website setup: payout wallet (created in the browser or your own), DNS TXT check, register, snippet. |
| Dashboard | web/dashboard/ |
Payments to one site from chain events, refunds, CSV. |
| Receipt | web/receipt/ |
One payment's receipt, read from the chain: /receipt/?id=<paymentId> or ?tx=<hash>. |
| Pay link | web/pay/ |
Builds a standalone pay page for the owner's own origin. |
| Landing | web/landing/ |
The public page, served at the site root. |
| Portfolio | web/portfolio/ |
The real-domain demo at https://0xmujahid.xyz (its own Pages project; deploy/pages.sh does not deploy it). |
| Demo site | web/demo-shop/ |
A site whose checkout is only the <sitebound-pay> element. |
| Attack demos | web/attack/ |
An address swap on a classic checkout, the same script against Sitebound, and a fake site embedding the frame. |
The relayer only pays gas. pay() is permissionless and the payee comes from the registry, so a relayer can refuse
a payment but can't redirect one. The frame also offers "Pay from my wallet": the buyer submits pay() with
relayerFee = 0 and pays gas in WBT.
Requires Foundry, Node, Python 3 and Chrome (the only browser measured so far).
forge install foundry-rs/forge-std # lib/ is not committed
forge test # v1, SiteboundV2 and the V2 invariant suites
node web/dev/check-lib.mjs # the frame's encoders and the merchant helper, on the real Chrome fixture
(cd worker && npm install && node test/local.mjs) # relayer against anvil, then under wrangler dev
(cd e2e && npm install && npm test) # headless Chromium: setup, pay, refund, demo site, quotes
python3 web/dev/serve.py # site http://127.0.0.1:8001, frame http://localhost:8002More (local pages, deploying the contract, adding a site without DNS): docs/DEVELOPMENT.md.
Three independent checks that the deployed contract is this code: sh scripts/verify.sh (rebuilds and compares the
runtime code), the Sourcify and Whitechain explorer verification, and "Verify us" in the browser at
https://sitebound-pay.pages.dev/#verify. Details and addresses: docs/DEPLOYMENTS.md.
The full version, with line references, is docs/THREAT-MODEL.md.
- Stops: hacked checkout JavaScript on the real website redirecting a payment. A fake site embedding the frame can only pay its own registry entry.
- Does not stop:
- a lookalike domain that registered its own wallet;
- a page drawn over the frame (UI redressing);
- a hacked page asking for a different amount, unless the site uses quotes.
- Trusted:
- the attestors' DNS check. 1-of-1 is one trusted authority, not decentralized; rotation at k = 1 adds no safety;
- the frame host's code;
- the buyer's browser;
- the guardian, for liveness only;
- the USDT issuer.
- Relayer only: the daily caps and the minimum fee are enforced by the relayer, not on-chain.
- Owner key: a stolen owner key can freeze a site permanently.
- Browsers: Safari may omit
topOrigin(not measured), and a missingtopOriginfails closed. Firefox: not checked.
No dates and no promises. Live means running on Whitechain Sepolia testnet today.
Now (live on testnet)
- Addressless payment: the contract pays the wallet registered for the site.
- Touch ID confirmation: a passkey signs the site and the amount.
- Gasless for buyers: a relayer pays gas; buyers pay USDT and a 0.01 USDT fee.
- DNS TXT onboarding, with the payout wallet created in the browser.
- Refunds, and payouts split across up to 5 wallets.
- Optional quotes: your server signs the amount, so a page can't change it.
- Verified source on Sourcify and the Whitechain explorer, plus "Verify us" in the browser.
Next (planned, not built)
- Bring web2 businesses on chain: their domain, one DNS record, no crypto team.
- Pay from any chain, settle on Whitechain, through Relay. Waits on Relay adding Whitechain (not listed, checked 7 Nov).
- A public API for adding sites (today only our admin script does it).
- A team payout wallet with several passkey-bound owners (needs a new contract).
- More than one DNS checker (the contract supports it; today there is one).
- Safari and Firefox testing (only Chrome is measured).
Later (planned, not built)
- Audit, then mainnet. More chains. Shop plugins. Overlay detection. An on-chain DNSSEC proof instead of the trusted DNS checker.
Gasless withdrawal for websites is not built: today a payout wallet needs a little WBT to refund, freeze or move its USDT.
- What is Sitebound? Checkout for USDT with no payout address on the page. The buyer confirms the site and the amount with Touch ID, like 3-D Secure for cards, and a contract pays the wallet registered for that site.
- Do I need a crypto wallet, or to know crypto? Site owners: no. Setup creates the payout wallet in your browser, so back it up, because it lives only there. Buyers link a wallet that holds USDT once, then pay with Touch ID.
- Do buyers need gas? No. Buyers pay in USDT only, never WBT. A relayer pays the gas and takes a 0.01 USDT fee.
- Who gets the money? Only the wallet, or split of wallets, registered on chain for that site's domain.
pay()has no payee field, so a page can't name another wallet. - Can a hacked page steal the payment? It can't redirect the money. It can still show a wrong amount, and a fake lookalike site can fool a buyer, so check the site and the amount in the confirmation. A site that signs quotes closes the amount gap.
- What stops someone registering my domain? A DNS TXT record that only the domain's owner can add. One trusted checker, our relayer, reads it today, and that is a real trust assumption. A change to an existing site's payout waits 1 hour, and its owner can freeze it.
- Is it real money? No. Whitechain Sepolia testnet with test USDT that has no value. Not audited; don't use real funds.
- Why Whitechain? Stablecoin payments settle in one transaction, its USDT supports
permit(a relayer can pay the gas), and its P-256 precompile lets the contract verify Touch ID signatures itself. - Is it open source? Yes, MIT. The contract source is verified on Sourcify and the Whitechain explorer, and "Verify us" checks the live code in your browser.
| Doc | What is in it |
|---|---|
| Why we are building it | The attacks on web checkout code, and the name |
| Architecture | Diagrams of the parts, onboarding, setup, a payment and the attacker |
| Threat model | What it stops, what it trusts, known limits |
| Whitechain prize checklist | Each requirement with its evidence |
| Whitechain feedback | What worked and what broke on Whitechain |
| Deployments and verification | Addresses, transactions, three ways to verify |
| For websites | Settings, server-side payment check, merchant quotes |
| Development | Local run, deploy, add a site without DNS |
| Prior art, CROPS, multi-chain | What exists, what is new, why single-chain today |
| Demo video script · Relayer |
Claude Code wrote much of the code, tests, pages and these docs under the author's direction. The author made the design calls and reviewed the changes, and signs every transaction; the AI never held a key. Claims in this repo that were not measured are marked "not checked".
MIT, see LICENSE.



