Handle Pay
A seedless, gasless, multi-chain smart wallet powered by Biometric Passkeys and Account Abstraction.
Built at BlockApex. Handle Pay has not launched: this case study covers the backend I designed and built, not a shipped product.
Architecture Overview
A modular NestJS monolith that acts as a 'Smart Account Factory,' deriving deterministic multi-chain addresses from a single WebAuthn credential and orchestrating gasless transactions via Paymaster relays.
The Challenges
Problem
We needed a single Passkey (FaceID) to control wallets on completely different cryptographic curves: EVM (secp256k1) and Solana (ed25519). Standard WebAuthn only gives one public key.
Solution
Engineered a deterministic derivation pipeline. For EVM, we used ZeroDev to create a Kernel Smart Account controlled by the WebAuthn key. For Solana, we hashed the Credential ID to derive a PDA via LazorKit. One 'Login with FaceID' therefore yields synchronized addresses on Ethereum and Solana from a single credential.
wallet.service.ts
Problem
Passkeys are typically used for *login*, not *authorizing specific financial transactions*. We needed to prevent replay attacks where a valid login signature is re-used to drain funds.
Solution
Customized the WebAuthn challenge flow. Instead of a random nonce, we inject the `keccak256` hash of the transaction data (calldata) into the WebAuthn challenge. The user's biometric signature thus cryptographically commits to *that specific transaction*, making replay attacks impossible.
verifyTransactionSignature()
Problem
We allowed users to claim unique `@usernames` (like Venmo). High concurrency during onboarding meant two users could pass validation simultaneously, leading to database collisions.
Solution
Implemented a 'Two-Phase Commit' reservation system using Redis and MongoDB TTL indexes. The username is atomically 'locked' for 30 minutes upon the first check. The WebAuthn registration options are only generated *after* a successful lock is acquired. If the passkey creation fails, the lock auto-expires.
onboarding.service.ts
Problem
Users shouldn't need to hold ETH or SOL just to pay for a coffee. Managing gas fees for them is complex.
Solution
Integrated ERC-4337 Paymasters. The backend calculates the gas cost, communicates with the Paymaster service to sponsor the transaction, and executes the UserOp. The intended result is an app that behaves like a standard banking app, with no 'Insufficient ETH for Gas' errors.
paymaster.service.ts