PrivaRoll
Enterprise Web3 Payroll on Base
Created on 14th March 2026
•
PrivaRoll
Enterprise Web3 Payroll on Base
The problem PrivaRoll 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.
Challenges I ran into
Challenges I Ran Into
- 🧩 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.
- 🔐 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.
- 🎨 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).
Tracks Applied (3)
Best creative use of ENS
Ethereum Name Service
Best Privacy Application using BitGo
BitGo
Privacy
Base
Technologies used
Cheer Project
Cheering for a project means supporting a project you like with as little as 0.0025 ETH. Right now, you can Cheer using ETH on Arbitrum, Optimism and Base.
