
Splithub
Splitwise for web3 , but with tap to pay.
Demo
About
The problem it solves # 🌐 SplitHub **Bridging On-Chain Assets with Real-World Use** ## The Problem People hold stablecoins like USDC, but using them for everyday actions splitting a bill, paying at venues, arcade games comes with too much friction. ## The Solution SplitHub turns crypto into a **tap-to-pay settlement layer**, delivering the ease of Apple Pay with the safety of a hardware wallet. --- ## 🎯 What People Use It For ### 🚀 Social Settlements "Web3 Splitwise" Apps track who owes what. Wallets settle. **SplitHub does both.** - **One person pays** → SplitHub auto-calculates each share - **Friends tap phones** → instant USDC settlement - **No off-ramping**, no bank rails, no friction ### 🎟️ Venues & Event Credits Arcades, festivals, and pop-ups lose margins to card fees. SplitHub enables a **closed-loop, on-chain economy**. - Users tap to load USDC → credits mint gaslessly - Tap to play games or buy items - Sub-second finality and **0% payment processor fees** --- ## ⚡ How It Makes Tasks Easier SplitHub removes transaction fatigue from Web3. **Paying a Friend** - Current UX: Wallet unlock → network switch → approvals → gas - SplitHub UX: **Tap phone** **Gas Requirements** - Current UX: Must hold ETH/MATIC - SplitHub UX: **Gasless, relayer-powered** **Speed** - Current UX: 10–30s waits - SplitHub UX: **Instant optimistic settlement** --- ## 🛡️ How It Makes Tasks Safer Most users expose main wallets to unknown dApps. SplitHub uses a **hardware-isolated signing chip** so daily spending is separated from savings. ### Device-Bound Security with Arx HaLo - ✅ Private key **generated inside the chip** - ✅ It **never leaves the hardware** - ✅ Signing occurs **on-chip**, requiring physical tap - ✅ Even a compromised phone **cannot extract or misuse the key** > **Result:** A secure "cash wallet" for daily use, while your primary wallet stays untouched. --- ## 🚪 Seamless Onboarding with Privy Web3 onboarding usually requires installing wallets, storing seed phrases, acquiring gas, and understanding networks **most users drop off**. SplitHub integrates **Privy** tsplitting a bill, paying at venues, arcade games comes with too much friction. ## The Solution SplitHub turns crypto into a **tap-to-pay settlement layer**, delivering the ease of Apple Pay with the safety of a hardware wallet. --- ## 🎯 What People Use It For ### 🚀 Social Settlements "Web3 Splitwise" Apps track who owes what. Wallets settle. **SplitHub does both.** - **One person pays** → SplitHub auto-calculates each share - **Friends tap phones** → instant USDC settlement - **No off-ramping**, no bank rails, no friction ### 🎟️ Venues & Event Credits Arcades, festivals, and pop-ups lose margins to card fees. SplitHub enables a **closed-loop, on-chain economy**. - Users tap to load USDC → credits mint gaslessly - Tap to play games or buy items - Sub-second finality and **0% payment processor fees** --- ## ⚡ How It Makes Tasks Easier SplitHub removes transaction fatigue from Web3. **Paying a Friend** - Current UX: Wallet unlock → network switch → approvals → gas - SplitHub UX: **Tap phone** **Gas Requirements** - Current UX: Must hold ETH/MATIC - SplitHub UX: **Gasless, relayer-powered** **Speed** - Current UX: 10–30s waits - SplitHub UX: **Instant optimistic settlement** --- ## 🛡️ How It Makes Tasks Safer Most users expose main wallets to unknown dApps. SplitHub uses a **hardware-isolated signing chip** so daily spending is separated from savings. ### Device-Bound Security with Arx HaLo - ✅ Private key **generated inside the chip** - ✅ It **never leaves the hardware** - ✅ Signing occurs **on-chip**, requiring physical tap - ✅ Even a compromised phone **cannot extract or misuse the key** > **Result:** A secure "cash wallet" for daily use, while your primary wallet stays untouched. --- ## 🚪 Seamless Onboarding with Privy Web3 onboarding usually requires installing wallets, storing seed phrases, acquiring gas, and understanding networks **most users drop off**. SplitHub integrates **Privy** to remove all that: - ✨ Log in with familiar Web2 auth - ✨ Wallets + identity created behind the scenes - ✨ No seed phrases or setup - ✨ No gas management ### 🌍 Every Twitter User Becomes a Web3 User > If you can log in with Twitter, you can use SplitHub. > **That's 500M+ people onboarded instantly.** --- ## 🎯 The Outcome SplitHub combines: - 💸 **Instant social settlements** Split bills in seconds - ⛽ **Gasless on-chain payments** No ETH needed - 🔐 **Tap-to-pay hardware security** Hardware-isolated keys - 🚀 **Frictionless onboarding** Web2 login → Web3 ready --- ## 💡 The Vision It makes crypto **useful, safe, and invisible** powering real-world interactions without the complexity of Web3. --- **SplitHub: Where On-Chain Meets Real-World**
How it's made
Challenges we ran into # 🚧 Challenges & Solutions ## The Journey to Production-Ready NFC Payments Building a tap-to-pay system that bridges physical hardware with blockchain infrastructure presented unique cryptographic and architectural challenges. Here's how we solved them. --- ## 🔄 The Double-Tap Paradox ### The Problem We hit a fundamental **chicken-and-egg problem** with NFC authentication. Our EIP-712 `PaymentAuth` signature requires the `payer` address embedded in the signed message—but we don't know who the payer is until they tap their chip! **The circular dependency:** 1. 🔷 **Tap the chip** → Get chip address 2. 🔍 **Look up chip in registry** → Find the owner (payer) 3. ⚠️ **But wait...** The signature already needed the payer address *before we knew it* ### The Solution: Two-Tap Flow We implemented a clever two-stage authentication pattern: ```typescript // 🎯 TAP 1: Sign with placeholder to discover chip address const placeholderAuth = { payer: "0x0000000000000000000000000000000000000000", // ...other fields }; const { address: chipAddress } = await signTypedData({ message: placeholderAuth }); // 🔍 Lookup real owner from on-chain registry const owner = await registry.ownerOf(chipAddress); const nonce = await payments.nonces(owner); // ✅ TAP 2: Sign the REAL message with correct payer + nonce const realAuth = { payer: owner, nonce, // ...other fields }; const signature = await signTypedData({ message: realAuth }); ``` > **Implementation Locations:** > This pattern appears throughout the codebase in: > - `useSettleFlow.ts` > - `useMultiSettleFlow.ts` > - `useCreditPurchase.ts` > - `useCreditSpend.ts` **The Challenge:** Getting state management right across asynchronous taps while handling errors gracefully required careful orchestration. --- ## 💥 BigInt Serialization Nightmares ### The Issue JavaScript's `JSON.stringify()` **silently breaks** on BigInt values. Our NFC signing library needed JSON-serializable data, but Ethereum amounts are *all* BigInts. ```javascript // ❌ This fails silently JSON.stringify({ amount: 1000000000000000000n }); // TypeError: Do not know how to serialize a BigInt ``` Signatures would **randomly fail verification** until we traced it to corrupted message hashes. ### The Fix We built a recursive BigInt serializer in `useHaloChip.ts`: ```typescript const serializeBigInt = (obj: any): any => { // Handle BigInt primitives if (typeof obj === "bigint") return obj.toString(); // Handle arrays recursively if (Array.isArray(obj)) return obj.map(serializeBigInt); // Handle objects recursively if (obj && typeof obj === "object") { return Object.fromEntries( Object.entries(obj).map(([k, v]) => [k, serializeBigInt(v)]) ); } return obj; }; ``` > **Result:** All BigInt values are recursively converted to strings before JSON serialization, preventing silent data corruption. --- ## 🎯 Gasless Relayer: Struct Encoding Hell ### The Challenge Building a relayer to submit transactions on behalf of users required **perfect encoding** of nested Solidity structs. One misaligned tuple encoding = `InvalidSignature` errors with zero helpful debug info. ### Batch Payment Architecture The batch payment endpoint uses `Multicall3` to atomically execute multiple payments: ```typescript const calls = payments.map(payment => ({ target: paymentsAddress, allowFailure: false, // ⚛️ Atomic: all succeed or all fail callData: encodeFunctionData({ abi: SPLIT_HUB_PAYMENTS_ABI, functionName: "executePayment", args: [authTuple, payment.signature], }), })); ``` **The Debugging Nightmare:** We spent hours debugging why signatures that were **valid in unit tests** failed through the relayer. The culprit? Subtle differences in how `viem` vs. direct contract calls encode struct arrays. > **Lesson Learned:** Always test signature encoding through the exact same path that production will use. --- --- ## ✨ How We Got Over It Our systematic approach to solving these challenges: ### 1. **Extensive Logging** 📝 Detailed logging during the double-tap flow to trace exactly where state diverged And A lot of AI Agents and Vibe Coding.