Project Story — Tap-to-Tip (Starknet • Front-only PWA)
🚀 About the project
Tap-to-Tip lets anyone send a micro-tip in two taps using QR or NFC, with ultra-low fees on Starknet Sepolia. No backend, no accounts—just your wallet.
Why this matters: today’s payment UX still makes sending tiny amounts (a “thank you” of €0.05, €0.10) feel heavy. With L2 fees and a wallet-native flow, micro-gratitude becomes natural: tip a barista, a street artist, an open-source maintainer, or a streamer—instantly.
💡 Inspiration
- Sub-cent fees deserve sub-second UX. We wanted to prove that you don’t need servers, custodians, or KYC walls to express appreciation.
- QR/NFC is already mainstream (menus, transit, Apple/Google Pay). We reuse those habits to make crypto feel familiar.
- Hackathon constraint: ship fast, keep it pure front-end, secure by design.
🧰 How we built it
Stack: Vite 5, React 18, TypeScript, TailwindCSS, starknet.js, qrcode, React Router.
Deployment: Vercel (static hosting, SPA fallback).
Architecture (no backend)
- UI only renders three screens:
Create Tip→Pay→Receipt - Tip Link encodes all params directly in the URL (signed later by the wallet):
/pay?recipient=<addr>&token=<erc20>&decimals=<int>&amount=<str>&memo=<txt>&nonce=<8>&expiry=<unix>
- QR/NFC: the merchant page renders the link as a QR and (optionally) writes it to an NFC tag (Web NFC on Android/Chrome).
- Wallet: the payer confirms an ERC-20
transferfrom their wallet (Argent X / Braavos). No private keys in the app.
Core math (client-side)
We convert the human amount to token units:
[
\text{amount_units} = \left\lfloor \text{amount} \times 10^{\text{decimals}} \right\rfloor
]
For Cairo’s u256:
[
\text{u256} = (\text{low} = x \bmod 2^{128},\ \text{high} = \lfloor x / 2^{128} \rfloor)
]
Minimal on-chain interaction
- We call only
ERC20.transfer(recipient, u256_amount). - The wallet handles gas, nonce, chain selection, and signature.
Security model (front-only)
- Wallet confirmation is the hard gate: no tx can broadcast without explicit user approval.
expiry+noncein the link to reduce accidental reuse.- No secrets, no API keys, no servers → tiny attack surface.
🧪 What it does (demo flow)
- Merchant opens
/merchant, fills:
recipient(destination address)token(ERC-20 on Starknet Sepolia) +decimals(often 18)amount(e.g., 0.10), optionalmemo- The app generates a Tip Link, QR, and can write NFC.
- Payer scans the QR (or taps NFC), lands on
/pay?..., connects wallet, clicks Pay, signs. - The app shows the transaction hash and a link to the explorer.
- Payer scans the QR (or taps NFC), lands on
🧩 Key code snippets
ERC-20 transfer (starknet.js)
const erc20Abi = [{ name: "transfer", type: "function", state_mutability: "external",
inputs: [
{ name: "recipient", type: "core::starknet::contract_address::ContractAddress" },
{ name: "amount", type: "core::integer::u256" }
],
outputs: []
}];
const toU256 = (x: bigint) => ({
low: "0x" + (x & ((1n << 128n) - 1n)).toString(16),
high: "0x" + (x >> 128n).toString(16)
});
const amount = BigInt(Math.floor(Number(amountStr) * 10 ** decimals));
const erc20 = new Contract(erc20Abi as any, tokenAddress, account);
const tx = await erc20.transfer(recipient, toU256(amount));
await account.waitForTransaction(tx.transaction_hash);
QR build & render
const url = `/pay?${new URLSearchParams(params).toString()}`;
await QRCode.toCanvas(canvasEl, url, { margin: 1, scale: 5 });
NFC write (Android / Chrome)
const ndef = new (window as any).NDEFReader();
await ndef.write({ records: [{ recordType: "url", data: url }] });
🏆 What we’re proud of
- Truly serverless: nothing to host except static files.
- 2-tap UX that feels like Web2: scan → pay.
- Composable: works with any ERC-20 on Starknet testnet (ETH/STRK/USDC test).
- Production-friendly: simple code, clear README, fast deploys.
🧠 What we learned
- The wallet = account contract pattern on Starknet is powerful: deployment + signatures are ergonomic for end-users.
- Keeping it front-only forces crisp boundaries and reduces complexity.
- Mobile UX details matter (clipboard, share sheet, NFC quirks, route fallbacks on hosting).
🧗 Challenges
- Tooling vs Node versions: Vite 7 enforces Node ≥ 20; we stabilized on Vite 5 to support Node 18 for easy dev environments.
- Wallet variability: ensuring the app degrades gracefully when no extension is present or when network isn’t set to Sepolia.
- Token presets: different decimals and addresses need a simple, safe UX (we added a preset mechanism).
🔭 What’s next
- NFT Receipt (“Proof-of-Tip”): mint a minimalist ERC-721 (Cairo) with
data:URI metadata post-payment. - Official token presets (ETH/STRK/USDC test) shipped as a signed JSON.
- Fiat preview: lightweight, bundled price cache (no external calls).
- Offline queue: optimistic UI + retry when connectivity returns.
📐 Minimal spec (for reviewers)
- Tracks: Payments (Starknet Foundation / Chipi Pay), Mobile, Open.
- Contracts: none required for MVP (uses existing ERC-20). Optional ERC-721 receipt.
- Security: no keys, wallet-signed txs only, link expiry/nonce.
- Deploy: static SPA (Vercel), SPA fallback enabled.
📎 Appendix
Tip Link schema
/pay?recipient=<addr>&token=<erc20>&decimals=<int>&amount=<string>&memo=<txt>&nonce=<8>&expiry=<unix>
Amount conversion [ \text{units}=\left\lfloor \text{amount}\times 10^{\text{decimals}}\right\rfloor,\quad \text{u256}=(\text{low},\ \text{high}) ]
Explorer: configurable in src/lib/constants.ts
Wallets tested: Argent X, Braavos (Starknet Sepolia)
Thanks for reading! If you want to try it live, ping us for a test token preset and a demo QR.
Built With
- argentx
- autoprefixer
- braavos
- braavos-(wallet-injected-flow)-*-**qr/nfc:**-`qrcode`-(qr-generation)
- css
- css-*-**frontend:**-react-18
- erc-20-abi-(openzeppelin-patterns)-*-**wallets:**-argent-x
- html
- no-db
- openzeppelin
- qrcode
- react
- react-router
- spa-fallback)-*-**data/backend:**-**none**-(front-only;-no-servers
- starknet
- starknet.js`
- swc-(`@vitejs/plugin-react-swc`)
- tailwind-css-*-**blockchain-/-web3:**-starknet-sepolia
- typescript
- typescript-strict-mode-*-**hosting/ci:**-vercel-(static-spa
- vercel
- vite
- vite-5
- web-nfc-(android/chrome)-*-**tooling:**-postcss

Log in or sign up for Devpost to join the conversation.