Skip to content
PrivaRoll

PrivaRoll

Enterprise Web3 Payroll on Base

Created on 14th March 2026

•

PrivaRoll

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

  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.

  1. 🔐 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.

  1. 🎨 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

Best Creative Use of ENS The Insight: ENS Text Records as a Stealth Key Distribution Layer ENS is typically used for hum...Read More
Ethereum Name Service

Ethereum Name Service

Best Privacy Application using BitGo

How PrivaRoll Uses BitGo Why BitGo? A company running payroll on-chain can't rely on a single private key sitting on a s...Read More
BitGo

BitGo

Privacy

How PrivaRoll Uses Privacy The Core Problem On any EVM chain, every ETH transfer is permanently public. If a company sen...Read More
Base

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.

Discussion

Builders also viewed

See more projects on Devfolio