DEV Community

Cover image for Building a Biometric Crypto Wallet (That Actually Works!)
Ola Adesoye
Ola Adesoye

Posted on

Building a Biometric Crypto Wallet (That Actually Works!)

Or: How I Learned to Stop Worrying and Love WebAuthn Passkeys


The "Why Did I Do This Again?" Moment

So, I built a mobile crypto wallet. But not just any wallet – one where you create your wallet using your face (or fingerprint), and send money without paying gas fees. Sounds cool, right? Well, it kind of is, and I'm here to tell you it's also kind of not.

Let me back up.

A few weeks ago, I stumbled upon this thing called Lazorkit. They had this wild idea: what if instead of making people memorize 12-word seed phrases (which, let's be honest, most people just screenshot and pray their phones don't get stolen), we used biometric authentication? You know, the Face ID thing you use to unlock your phone 50 times a day anyway?

Challenge accepted.

What I Built

The app is pretty straightforward:

  1. You tap "Create Wallet."
  2. Your phone asks for Face ID or fingerprint
  3. Boom. You have a Solana wallet.
  4. You can send USDC tokens without having SOL for gas fees (gasless transactions via a paymaster)

That's it. No seed phrases. No "write this down on a piece of paper and hide it in your mattress." Just your face.

Tech Stack:

  • React Native (with Expo SDK 54)
  • TypeScript (because I like knowing what's breaking before it breaks)
  • Solana blockchain (Devnet for testing)
  • Lazorkit SDK (the magic sauce)
  • WebAuthn passkeys (the real magic sauce)

The "Oh Crap" Moments (aka What I Learned)

1. Polyfills Are Your Secret Enemies

First lesson: React Native doesn't have all the JavaScript globals that web browsers have. Solana's SDK expects things like Buffer and crypto.getRandomValues() to just... exist. They don't.

I spent 2 hours debugging a cryptic error about randomness before realizing I needed to import polyfills. And not just import them – import them in the EXACT RIGHT ORDER at the very top of my app.

// This order matters. Don't ask me why. I've been hurt before.
import 'react-native-url-polyfill/auto';
import { Buffer } from 'buffer';
global.Buffer = Buffer;
import 'react-native-get-random-values';
Enter fullscreen mode Exit fullscreen mode

Move these around? Your app explodes. Fun times.

2. Simulators Are Liars

You can't test biometric authentication properly on a simulator. I mean, you can, but it's like testing a swimming pool by looking at a picture of water. The iOS simulator has a "fake Face ID" feature that sort of works, but deep linking to the Lazorkit Portal and back? Yeah, that's where things get weird.

Hot tip: Get a physical device. Test on real hardware. Trust me on this one.

3. WebAuthn Is Actually Pretty Cool

Here's the thing nobody told me: WebAuthn passkeys are just public-key cryptography with better UX. When you create a wallet:

  1. Your phone generates a key pair
  2. The private key stays in your device's secure enclave (the same place your fingerprint data lives)
  3. The public key goes to Lazorkit's Portal service
  4. When you need to sign something, your phone uses Face ID to unlock the private key

It's like having a hardware wallet, but your phone IS the hardware wallet. Mind. Blown.

4. Gasless Transactions Are Secretly Not Gasless

Plot twist: "gasless" transactions still have gas fees. You just don't pay them. A paymaster does.

Here's how it works:

  1. You create a transaction (send 1 USDC to your friend)
  2. The paymaster service looks at it and says "cool, I'll pay the SOL fee"
  3. The paymaster signs the transaction with their wallet
  4. You sign it with your biometric wallet
  5. Transaction goes through, you paid $0

Catch: The paymaster won't create token accounts for you. If your recipient doesn't have a USDC account already, the transaction fails. This burned me during testing until I realized I could just send USDC to myself.

5. Deep Linking Is Dark Magic

To make the biometric authentication work, the app needs to:

  • Open Safari/Chrome
  • Load the Lazorkit Portal page
  • Trigger Face ID
  • Redirect back to your app

This requires deep linking configuration in app.json:

{
  "scheme": "lazorkitstarter"
}
Enter fullscreen mode Exit fullscreen mode

Getting this right took me 3 tries because I kept using weird characters. Keep it simple: lowercase, no spaces, no special characters. Future you will thank present you.

The "Wait, This Actually Works?" Moment

After wrestling with polyfills, deep links, and trying to understand why my transaction kept failing (spoiler: I had no test USDC), I finally got it working.

I opened the app, tapped "Create Wallet," looked at my phone for Face ID, and BAM – I had a Solana wallet.

No seed phrases. No friction.

It felt like science fiction, but it's just good API design.

The Tutorials (Because I Like You)

I wrote two full tutorials for this project so you can follow along:

Tutorial 1: Wallet Creation with Biometric Authentication (~30 min)

  • Setting up polyfills (the right way)
  • Configuring LazorKitProvider
  • Deep linking between your app and the Portal
  • Handling biometric authentication callbacks

Tutorial 2: Gasless USDC Transactions (~40 min)

  • Building Solana transactions with web3.js
  • Integrating paymaster for gasless fees
  • Proper blockhash management (because they expire after 60 seconds and will ruin your day)
  • Error handling and validation

Who Is This For?

You should build this if:

  • You're learning React Native and want a real-world project
  • You're curious about Solana mobile development
  • You want to understand how WebAuthn passkeys work
  • You like the idea of crypto wallets without seed phrases

You should NOT build this if:

  • You need production-ready code (this is Devnet only, folks)
  • You hate TypeScript
  • You don't have a physical iPhone or Android device for testing
  • You're allergic to polyfills

The TL;DR

I built a mobile crypto wallet with Face ID authentication and gasless transactions. It uses WebAuthn passkeys (no seed phrases!), runs on Solana Devnet, and actually works. I learned way too much about polyfills, deep linking, and why simulators are not your friends.

If you're learning mobile dev or blockchain stuff, give it a shot. If nothing else, you'll have a very strong opinion about import order.


Built this? Got stuck? Found a bug? Hit me up – I'm always down to talk about why something broke.

P.S. If you try this and your app crashes because of polyfill order, just know I told you so.😅

Top comments (0)