Search projects, hackathons, sponsors and tech

    ← All projects

    PrivaRoll

    Enterprise Web3 Payroll on Base

    Demo

    About

    The problem it solves The Problem PrivaRoll Solves 💸 On-chain payroll is a privacy catastrophe When companies pay employees on Ethereum or any EVM chain, every transaction is permanently public. Anyone — competitors, colleagues, recruiters, or attackers — can: Reverse-engineer your company's burn rate by watching outflows from a known payroll wallet Compare individual salaries by tracing which addresses receive how much and how often Target high-earners for phishing, social engineering, or physical threats Link employee identities to their on-chain financial activity forever Traditional payroll on blockchain fundamentally breaks salary confidentiality.

    How it's made

    Challenges we ran into Challenges I Ran Into 1. 🧩 BitGo SDK vs Next.js Webpack — The Bundle Breaker The bug: The BitGo SDK (bitgo npm package) pulled in eccrypto, casper-js-sdk, and @wasmer/wasi — all of which contain Node.js-only syntax (like return at the top level of eccrypto/index.js) and native WASM modules that webpack cannot parse. This caused a hard Syntax Error during next build: Error: Return statement is not allowed here ,-[node_modules/eccrypto/index.js:21:1] 24 | return (module.exports = require("./browser")); : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ How I fixed it: The initial attempt used serverExternalPackages in next.config.js, but that's a Next.js 15+ option. Since the project runs Next.js 14.1.0, I switched to the correct experimental.serverComponentsExternalPackages and added every problematic transitive dependency (bitgo, eccrypto, @wasmer/wasi, casper-js-sdk, @bitgo/sdk-core, @bitgo/sdk-coin-cspr, @bitgo/sdk-lib-mpc) to ensure webpack skips bundling them entirely and treats them as runtime require() calls. 2. 🔐 Stealth Address Math — Getting ECDH Right The hurdle: Implementing the Diffie-Hellman shared secret derivation for stealth addresses required careful handling of compressed vs. uncompressed public key formats. The @noble/secp256k1 library expects specific hex formats, and any mismatch (missing 0x prefix, wrong key length, compressed when uncompressed was needed) produced cryptic "invalid point" errors with no helpful stack trace. How I got over it: Built a dedicated generateStealthPayment() function that: Always generates ephemeral keys in compressed format (getPublicKey(privKey, true)) Derives the shared secret with compressed output Hashes the secret with keccak256 to produce the stealth modifier Uses ProjectivePoint.fromHex() + .add() for point arithmetic instead of manual bigint math Extracts the Ethereum address from the last 20 bytes of the uncompressed stealth public key hash 3. 🌐 ENS Text Record Integration The hurdle: Storing stealth meta-public-keys in ENS text records seemed straightforward, but ENS resolver calls via viem/wagmi had subtle issues with Base L2 vs. Ethereum mainnet resolution. The ENS names resolve on L1 (mainnet), but our app operates on Base Sepolia (L2). How I got over it: Used cross-chain ENS resolution — the frontend reads ENS text records from Ethereum mainnet (where ENS lives) but generates stealth addresses and sends transactions on Base Sepolia. This required configuring separate RPC providers for ENS lookups vs. transaction execution. 4. 🎨 Cyberpunk Design System — CSS Animation Performance The hurdle: The neon glow effects, animated gradient borders, and scan-line animations initially caused layout thrashing and janky scrolling on lower-end devices. Stacking multiple box-shadow and text-shadow values with CSS animations triggered expensive repaints. How I got over it: Moved ambient glow animations to opacity and transform only (GPU-composited properties) Used will-change: transform sparingly on animated elements Replaced CSS-only particle animations with Framer Motion for better frame scheduling Added backdrop-filter: blur() with hardware-accelerated compositing Created animation delay utilities (.animation-delay-100 through .animation-delay-1000) to stagger animations and avoid simultaneous repaints 5. 🏦 Dual Payroll Mode (BitGo vs Direct Signing) The hurdle: Supporting both BitGo enterprise multi-sig AND direct ethers.js signing in the same API route created complex branching logic. The BitGo SDK is huge (~50MB of deps) and we didn't want to force-install it for developers who just want to test with a local private key. How I got over it: Implemented a handleBitGoMode() → handleDirectSigning() fallback chain with environment variable detection. BitGo mode first tries proxying to the Express backend (which has the SDK installed), and only falls back to a dynamic import("bitgo") if the backend is unreachable. Direct mode uses lightweight ethers.js with zero additional deps. The frontend auto-detects the mode and adjusts the UI accordingly (showing/hiding the passphrase field).