Filament Web3 Airdrop Platform
Token Distribution & Delegate Voting on an L2 Rollup
Role: Senior full-stack engineer in a team of 3–5: the Node server routes, the integration with the team's L2 rollup API, MetaMask signing of rollup transactions through a WASM serializer, the Ethereum wallet flows, and the campaign and voting web client. The rollup itself was built by teammates.
Node server routes that aggregate an external blockchain-analytics API for the campaign dashboard with per-widget graceful degradation, and the integration with the team's L2 rollup REST API — queries, sequencer submission and ledger status. Rollup transactions are signed in MetaMask through the rollup's own serializer compiled to WebAssembly, with BigInt-safe payloads and a nonce-0 fix for wallets the rollup has not seen yet.
Key Features
- Node server routes aggregating a blockchain-analytics API with per-widget graceful degradation (Promise.allSettled)
- Integration with the team's L2 rollup REST API: campaign, vote and account queries, sequencer submission, ledger status
- MetaMask signing of rollup transactions via the rollup's serializer compiled to WASM (EIP-191), lazy-loaded on first sign
- BigInt-safe payloads (json-bigint) for 64-bit ids, nonces and vote weights; nonce 0 for wallets new to the rollup
- Ethereum (Sepolia) wallet flows with ethers.js: connect, account and chain changes, network switching, delegate-registry reads
- Campaign wizard, delegate voting and campaign pages; Clerk auth with PostgreSQL (Supabase)
Tech Stack
Backend & APIs
Blockchain
Frontend & Tooling
Challenges & Solutions
Signing Rollup Transactions With MetaMask
The rollup encodes transactions in its own binary format, but users only have MetaMask. Re-implementing that serialization in TypeScript would drift from the rollup's types with every protocol change.
Used the rollup's own serializer compiled to WebAssembly and published as an npm package with generated types. The client fetches the nonce, serializes the call in WASM, signs it with EIP-191 personal_sign and submits the signed transaction to the sequencer. The WASM module is lazy-loaded only when a user signs.
Silent Precision Loss Between the Rollup and JavaScript
Campaign ids, nonces and vote weights are 64-bit integers on the rollup side. JSON.stringify and JavaScript numbers round anything above 2^53 without an error.
Serialized call payloads with json-bigint and passed ids and chain ids as BigInt end to end, so large values reach the signer exactly as the rollup expects them.
The First Transaction From a New Wallet
A brand-new Ethereum address has no account on the rollup yet, so the account lookup returned 404 and the client could not build the user's first transaction.
Added a per-call error hook to the shared fetch wrapper that maps that 404 to nonce 0. Nonce plus chain id in the signed payload keep replay protection intact.
Dashboard Aggregation That Degrades Gracefully
The analytics dashboard needed several upstream calls per campaign to an external blockchain-analytics API; one slow or failing call used to break the whole page.
A Node route loads the organisation's campaigns, then fetches aggregations, interactions and daily change for each in parallel with Promise.allSettled at both levels. A failure nulls only that widget, and the route also returns status counts for the header.