Crypto Onboarding: Passwordless Wallets in Practice

By Joan Alavedra, Co-Founder at Openfort19 min read
TL;DR

Crypto onboarding is the single biggest reason most web3 apps lose users before they ever transact. Seed phrases, gas tokens, and 'install this wallet' detours kill conversion. Passwordless wallets — biometric / passkey sign-in, sponsored gas, an account created silently inside your app — fix the funnel. This guide explains what good crypto onboarding looks like, then walks through how to build a passwordless wallet on EIP-7702 and ERC-4337 with WebAuthn-backed signing, session keys for friction-free use, and recovery that does not depend on the user remembering twelve words.

Crypto Onboarding: Passwordless Wallets in Practice

Crypto onboarding is the part of every web3 product where most of the conversion goes to die. The user lands, hits a "connect wallet" button, gets told to install a browser extension, write down twelve words, buy some ETH, and come back later. By the time they could have transacted, they are doing something else.

This is the single biggest opportunity in consumer crypto: not new chains, not new DEXs, not new yield protocols — getting more of the people who clicked "sign up" to actually use the product. The fix has a name. It is passwordless wallets: accounts created silently inside your app, signed with Face ID or Touch ID, with gas sponsored on the user's behalf. A seed phrase never appears. The user moves through onboarding the way they move through Notion or Stripe.

This guide explains crypto onboarding the way a PM, growth marketer, or technical founder needs it explained: what the term means, where the conventional onboarding funnel leaks, what a passwordless flow looks like end-to-end, and then — for the engineers — a complete walkthrough of how to build one on EIP-7702 and ERC-4337 with WebAuthn-backed signing.

You can ship Openfort embedded wallets and skip most of the implementation entirely. If you want to understand the parts before you call the SDK, read on.

What "crypto onboarding" actually means

Crypto onboarding is the full path from "user arrives in your app" to "user has done their first on-chain action successfully". It is not a single step — it is a small product flow with at least five distinct moments:

  1. Sign-up / sign-in — the user proves who they are (email, social, OTP, passkey, sometimes SIWE).
  2. Wallet provisioning — an on-chain account is created and bound to that user.
  3. Funding — the user has the assets they need (sometimes pre-funded by you, sometimes by fiat ramp, sometimes by airdrop / claim).
  4. Gas — there is a way to pay for the first few transactions without the user holding native tokens.
  5. First transaction — the user signs and confirms an action that does something real.

A "good" onboarding flow gets the median user through all five in under a minute. A "bad" one stops at step 1.5 ("install MetaMask first"). The arithmetic of conversion makes this brutal: if any single step has a 50% drop, the funnel halves. Five 50% steps and you are at 3%.

Crypto onboarding's reputation problem is that the default has eight such steps, several of them outside your app entirely. That is the problem passwordless wallets solve.

Why the traditional crypto onboarding funnel leaks

If you have ever shipped a web3 product and watched the analytics, the pattern is depressingly consistent. Take a typical "connect wallet → mint NFT" flow and the leaks are:

  • "Install MetaMask" — you redirect to an extension store. The user is now on a different domain, on a different app, looking at developer screenshots. Drop-off: 40–60%.
  • "Write down your seed phrase" — the user is asked to take responsibility for 12 random words, forever, on a piece of paper they will not have when they need it. Drop-off: 10–30%.
  • "Send some ETH to your new wallet" — the user is told they need to buy a token on a centralized exchange (with KYC, with a bank transfer) and send it to their address. Drop-off: 60–80%.
  • "Approve this contract" — a popup with technical-looking calldata appears. Drop-off: 10–20%.
  • "Sign this message" — every action gets a confirmation prompt. Drop-off compounds.

Every single one of these can be removed by a well-designed crypto onboarding flow. The user no longer leaves your app, never writes anything down, never funds with native tokens, and only confirms the actions that actually matter.

The piece that makes that possible is the passwordless wallet.

What a good crypto onboarding flow looks like

The shape of a modern web3 onboarding flow is closer to a SaaS onboarding flow than a 2017 dApp. The user-facing version:

  1. Sign up with what you already know. Email, Google, Apple, GitHub, a phone OTP, or a passkey. Pick whichever fits your audience — see OTP authentication for the email-OTP variant, and guest accounts for the no-sign-up "try it first" variant.
  2. A wallet appears silently. Behind the scenes, your app provisions a smart-account-backed wallet for the user. They never see the words "wallet", "key", or "address" unless they ask.
  3. The first transaction is paid for. A paymaster sponsors gas. The user does not need ETH, MATIC, or anything else in their wallet to take the first action.
  4. Biometrics confirm anything that matters. Spending? Buying? Transferring? A Face ID or Touch ID prompt — the same gesture the user does to unlock their phone.
  5. Routine actions sign themselves. Session keys, scoped to the actions the app needs, sign in the background. The user is not asked to approve a message every fifteen seconds.

The whole thing fits inside the user's existing mental model of "signing up for an app". The blockchain is implementation detail, not a customer-facing concept.

This is the design Privy, Coinbase Smart Wallet, and Openfort all converged on independently. The label varies (passkey wallet, smart wallet, embedded wallet); the underlying construction is the same: an account created silently, biometric or passkey sign-in, gas sponsorship by default.

The patterns that make crypto onboarding feel like SaaS

Three building blocks do most of the work.

1. Passkeys and WebAuthn

A passkey is a public/private key pair created by the user's device and protected by biometrics. The user "signs in" by touching a fingerprint sensor or looking at the front camera; the device produces a cryptographic signature in response. There is no password, no phrase to remember, and no key for the user to leak.

For crypto onboarding, passkeys are a near-perfect signer. The private key is non-extractable (it lives in the Secure Enclave on iOS, in Strongbox on Android, in the TPM on Windows). It is bound to the device, so phishing a key over the wire is impossible — you cannot steal what cannot leave. And the UX is something every user already does daily.

WebAuthn is the web standard that lets browsers create and use passkeys. RIP-7212 (a precompile on Ethereum) makes P256 signatures — the curve passkeys use — verifiable on-chain.

2. Smart accounts (ERC-4337 + EIP-7702)

A passkey alone is just a signer; you also need an account that knows how to verify a passkey signature, sponsor its own gas, and enforce rules. That is what a smart account is — a contract on-chain that acts as the user's account.

ERC-4337 is the standard for smart accounts. EIP-7702 (live on Ethereum since Pectra in May 2025) lets an existing EOA temporarily act as a smart contract — which means a user can get smart-account behaviour without migrating to a new address. Embedded wallets typically use both: the account is created as a smart-account-shaped wallet, and 7702 delegation makes it interoperable with the rest of the EVM.

For the full background, the embedded wallet explained post is the right starter.

3. Sponsored gas and session keys

The two UX killers in classical crypto onboarding are "you need gas tokens" and "you need to approve every action". Sponsored gas and session keys close both.

  • Sponsored gas = a paymaster contract pays the gas for the user's transaction, on behalf of your app. The user signs, the user does not pay. You bake the cost into your business model.
  • Session keys = a short-lived, scoped signer registered on the smart account, that can sign certain actions within tight limits (which contracts, which functions, how much, for how long). Routine actions go through the session key automatically; the user only sees the biometric prompt when something genuinely needs it.

For the full pattern set, see programmable wallets and the deep dive on how to build wallet permissions with session keys.

A two-minute onboarding flow you can ship

Putting it together, here is what a passwordless onboarding flow looks like from the user's seat:

  1. User lands on your app and clicks "Sign up".
  2. They tap "Sign in with Google" (or "Use a passkey", or "Email me a code").
  3. They get an authentication prompt — biometric or one-time code — and they are in.
  4. Behind the scenes, your app provisions a smart-account wallet, registers their passkey as the primary signer, and issues a session key.
  5. They click "Claim your starter NFT". A paymaster covers the gas. Their session key signs. The mint succeeds.
  6. They see a confirmation: "Your starter NFT is live. Welcome."

Five steps. About 60 seconds. No seed phrase. No installation. No funding. That is the standard the rest of the consumer software world set, and that crypto onboarding is finally able to match.

Now we can get into how the technology works.

How Openfort handles crypto onboarding

If you want to ship this flow without writing the smart account contract or the WebAuthn signer yourself, Openfort embedded wallets handle the end-to-end:

  • Login methods out of the box: email + OTP, social (Google / Apple / Discord / GitHub), passkey, SIWE, guest accounts for try-before-sign-up.
  • Smart-account-backed wallets created silently for every user.
  • Paymaster integration so first-transaction gas is sponsored by default.
  • Session keys that you can scope to the actions your app actually needs.
  • Recovery via backup passkeys and guardian sets — set up at sign-up, before anything has gone wrong.
  • Drop-in UI: Openfort Kit ships pre-built React components for the sign-in flow, wallet display, and connect button. The companion guide is Openfort Kit Authentication in React.

A basic Openfort integration takes a few hours. The result is the two-minute onboarding flow above, with the security model of a self-custodial wallet.

Try Openfort

Start here. Drop the SDK into your app, configure your login methods, and ship a passwordless wallet that turns crypto onboarding into a SaaS sign-up. New users hit their first transaction in under a minute, and you keep the self-custody story.


Developer appendix: build a passwordless wallet from scratch

Everything below is the implementation reference for engineers who want to know what the SDK does under the hood, or who want to build a custom version. This is the original technical content of the post, kept intact so the deeper guide does not get lost.

This guide, inspired by Openfort's slick demonstration of passkey wallet, will walk you through creating a wallet experience where users interact with the blockchain using familiar WebAuthn credentials (like Passkeys). Think of it: no more "write this down and keep it safe" lectures. But before you jump into the code, it's essential to grasp the core components. Getting these concepts clear from the outset will make your development journey smoother and your final product much more robust.

If you want to check the code directly, here is the repo.

Key concepts and glossary: the tech you need to know

Before you start building, let's break down the essential tech powering this passwordless revolution. Understanding these terms isn't just academic; it's about knowing the tools that will let you deliver a top-tier user experience.

  • Externally Owned Account (EOA): This is your starting block – the standard Ethereum account controlled by a private key. While it's the most common account type, we're about to give it some serious upgrades.
  • EIP-7702: Think of this as the EOA "empowerment" proposal. It allows an EOA to temporarily grant specific permissions to a smart contract. This is key because it lets your EOA gain smart contract-like abilities without the immediate overhead of deploying a full smart contract wallet for every user.
  • Delegation Contract: This is the trusted smart contract your EOA (via EIP-7702) authorizes to act on its behalf. It's the intermediary that will execute actions based on the EOA's approved instructions, enabling more complex operations.
  • ERC-4337 (Account Abstraction): This is the standard that brings true "Account Abstraction" to Ethereum without needing deep protocol changes. For you, this means unlocking powerful features like gas sponsorship and improved transaction flows, making your dApp far more accessible.
  • UserOperation (UserOp): With ERC-4337, users (or their accounts) don't just send plain old transactions. Instead, they submit UserOperation objects to a special, alternative mempool. This is how we tap into the advanced features of Account Abstraction.
  • Bundler: These are the workhorses of the ERC-4337 ecosystem. Bundlers pick up UserOps, package them efficiently into a single on-chain transaction, and get them executed. They handle the complexity so your users don't have to.
  • WebAuthn / Passkeys: This is the heart of the "passwordless" experience. A web standard that lets users authenticate using biometrics (Face ID, Touch ID) or physical security keys. For your users, this means maximum security with minimal friction – a huge win.
  • RIP-7212 P256 Precompile: For WebAuthn to be useful on-chain, signatures need to be verifiable. This precompile makes verifying the P256 signatures (common in WebAuthn) efficient and standard on Ethereum, securely linking Passkeys to blockchain accounts.
  • Paymaster: An ERC-4337 smart contract that can pay gas fees on behalf of your users. This enables Sponsored Transactions, a game-changer for onboarding.
  • Session Keys: To avoid "signature fatigue" (users constantly having to approve transactions), you can implement session keys. These are temporary, limited-permission keys that can be authorized for a certain number of actions or time, allowing for smoother, uninterrupted interactions within your dApp.
  • Batch Transactions: Another win for user experience, enabled by Account Abstraction. This allows multiple distinct operations (e.g., approve a token and then swap it) to be combined and executed as a single, atomic transaction.
  • IndexedDB: A browser-based database. In our context, it's a practical example of where you might securely store non-extractable session keys locally in the user's browser, keeping them handy but safe.

How it all clicks: your passwordless architecture blueprint

So, how do these pieces – EOAs, EIP-7702, ERC-4337, WebAuthn – actually combine to deliver that seamless passwordless flow? It's not just about individual components; it's their smart integration.

Traditional wallets expose private keys to the application layer, creating multiple attack vectors. Using EIP-7702, you empower this EOA by allowing it to designate a Delegation Contract. This contract becomes a trusted agent, ready to execute operations based on your EOA's authority.

Next, you bring in WebAuthn (Passkeys) for authentication. Users use their familiar device biometrics or security keys, and thanks to the RIP-7212 P256 Precompile, these credentials can securely control the EOA via the Delegation Contract.

The ERC-4337 standard then kicks things into high gear. User intentions become UserOperations, processed by Bundlers. This is your gateway to features that truly differentiate your dApp:

  • Sponsored Transactions (via Paymasters): Onboard users without them ever touching gas.
  • Session Keys: Let users perform multiple actions within a session without constant signature prompts.
  • Batch Transactions: Simplify complex multi-step processes into single clicks.

By thoughtfully combining these technologies, you're not just building a wallet; you're crafting a user experience that's years ahead of the curve. You're setting up a secure, trustworthy, and incredibly intuitive way for users to engage with the decentralized web.

With these foundational concepts in place, you're well-prepared to tackle the code and bring your passwordless wallet to life. Let's get building.

Setting up WebAuthn authentication

Creating your first passkey

The journey begins by creating a WebAuthn credential that will serve as the primary authentication method for your smart account. This credential is tied to your device and protected by biometric authentication or a PIN.


_17
import { Bytes, WebAuthnP256 } from "ox";
_17
_17
// Create a WebAuthn credential using the account address as the user ID
_17
const credential = await WebAuthnP256.createCredential({
_17
authenticatorSelection: {
_17
requireResidentKey: false,
_17
residentKey: "preferred", // Store credential on device when possible
_17
userVerification: "required", // Require biometric/PIN verification
_17
},
_17
user: {
_17
id: Bytes.from(account.address),
_17
name: `${account.address.slice(0, 6)}...${account.address.slice(-4)}`,
_17
},
_17
});
_17
_17
// Store the credential ID for future authentication
_17
const credentialId = credential.id;

This process prompts the user to authenticate (via Face ID, Touch ID, Windows Hello, etc.) and creates a unique key pair bound to their device. The private key remains in secure hardware, while the public key is returned for smart account initialization.

Initializing the smart account

With the WebAuthn credential created, we can now initialize a smart account that recognizes this passkey as its primary signer:


_30
// Prepare the initialization call data
_30
const callData = encodeFunctionData({
_30
abi: accountABI,
_30
functionName: "initialize",
_30
args: [
_30
{
_30
pubKey: {
_30
x: toHex(x), // The WebAuthn public key x coordinate
_30
y: toHex(y), // The WebAuthn public key y coordinate
_30
},
_30
eoaAddress: zeroAddress, // address(0)
_30
keyType: KEY_TYPE_WEBAUTHN, // WEBAUTHN key type identifier
_30
},
_30
spendTokenInfo, // Token spending configuration
_30
[ // Function selectors this key can call
_30
'0xa9059cbb', // transfer(address,uint256)
_30
'0x40c10f19' // mint(address,uint256)
_30
],
_30
messageHash, // Verification message hash
_30
signatureInit, // Initial signature for verification
_30
validUntil, // Key expiration timestamp
_30
nonce + 1n, // Account nonce
_30
],
_30
});
_30
_30
// Create the user operation with EIP-7702 authorization
_30
const userOperation = await smartAccountClient.prepareUserOperation({
_30
callData,
_30
authorization: signedAuthorization, // EIP-7702 authorization signature
_30
});

The EIP-7702 authorization allows an EOA to temporarily act as a smart contract, enabling the initialization process. Once this UserOperation is executed, your smart account is ready for WebAuthn-based transactions.

Executing transactions with WebAuthn

Preparing the transaction

Let's demonstrate by minting some ERC-20 tokens. The process involves preparing a UserOperation with a placeholder signature, then replacing it with the actual WebAuthn signature:


_20
// Encode the mint function call
_20
const data = encodeFunctionData({
_20
abi: erc20ABI,
_20
functionName: "mint",
_20
args: [
_20
smartAccountClient.account.address,
_20
parseEther("10"), // Mint 10 tokens
_20
],
_20
});
_20
_20
// Prepare UserOperation with stub signature
_20
const userOperation = await smartAccountClient.prepareUserOperation({
_20
calls: [
_20
{
_20
to: erc20Address,
_20
data,
_20
},
_20
],
_20
signature: webAuthnStubSignature, // Placeholder signature
_20
});

Signing with WebAuthn

Now we generate the actual signature using the WebAuthn credential:


_16
// Get the UserOperation hash that needs to be signed
_16
const userOperationHash = getUserOperationHash(userOperation);
_16
_16
// Sign with WebAuthn - this triggers biometric authentication
_16
const webauthnData = await WebAuthnP256.sign({
_16
challenge: userOperationHash,
_16
credentialId, // The credential we created earlier
_16
rpId: window.location.hostname, // Relying party identifier
_16
userVerification: "required", // Require user verification
_16
});
_16
_16
// Replace stub signature with the actual WebAuthn signature
_16
userOperation.signature = encodeWebAuthnSignature(webauthnData);
_16
_16
// Send to bundler for execution
_16
await bundlerClient.sendUserOperation(userOperation);

The user experiences a familiar biometric prompt (Face ID, fingerprint, etc.), and once authenticated, the transaction is signed and submitted.

Hint: The stub signature has to be different for P256 and WebAuthn. The actual webauthn signature is significantly different from the P256 signature, so the stub signature has to be different as well.

Implementing session keys for seamless UX

While WebAuthn provides excellent security, requiring biometric authentication for every transaction can impact user experience. Session keys solve this by creating temporary, limited-privilege keys for routine operations.

Creating a session key

Session keys are also non-extractable P256 keys, but they're created using the WebCrypto API and stored locally:


_10
import { WebCryptoP256 } from "ox";
_10
_10
// Generate a new P256 key pair for the session
_10
const keyPair = await WebCryptoP256.createKeyPair();
_10
const publicKey = await WebCryptoP256.getPublicKey({
_10
publicKey: keyPair.publicKey
_10
});
_10
_10
// Store the key pair securely (e.g., in IndexedDB)
_10
await storeSessionKey(keyPair);

Registering the session key

The session key must be registered with the smart account, defining its permissions and validity:


_39
const callData = encodeFunctionData({
_39
abi: accountABI,
_39
functionName: 'registerSessionKey',
_39
args: [
_39
{
_39
pubKey: {
_39
x: toHex(publicKey.x), // The P256 public key x coordinate
_39
y: toHex(publicKey.y), // The P256 public key y coordinate
_39
},
_39
eoaAddress: zeroAddress, // address(0)
_39
keyType: KEY_TYPE_P256, // P256 key type identifier
_39
},
_39
validUntil, // Session expiration
_39
0, // Nonce
_39
limits, // Spending/call limits
_39
true, // Authorized flag
_39
erc20Address, // Contract this key can interact with
_39
spendTokenInfo, // Token spending configuration
_39
[ // Function selectors this key can call
_39
'0xa9059cbb', // transfer(address,uint256)
_39
'0x40c10f19' // mint(address,uint256)
_39
],
_39
ethLimit // ETH spending limit
_39
]
_39
});
_39
_39
// This registration requires WebAuthn approval
_39
const userOperation = await smartAccountClient.prepareUserOperation({
_39
callData,
_39
signature: webAuthnStubSignature,
_39
});
_39
_39
// Sign with WebAuthn (one-time approval for session key)
_39
const webauthnData = await WebAuthnP256.sign({
_39
challenge: getUserOperationHash(userOperation),
_39
credentialId,
_39
rpId: window.location.hostname,
_39
userVerification: "required",
_39
});

Hint: Webauthn rpId is crucial. It binds credentials to a specific domain for strong phishing protection. If you don't provide it, the browser will throw an error.

Using session keys for transactions

Once registered, session keys can sign transactions without requiring biometric authentication:


_26
// Prepare the transaction as before
_26
const userOperation = await smartAccountClient.prepareUserOperation({
_26
calls: [
_26
{
_26
to: erc20Address,
_26
data: encodeFunctionData({
_26
abi: erc20ABI,
_26
functionName: "mint",
_26
args: [smartAccountClient.account.address, parseEther("10")],
_26
}),
_26
},
_26
],
_26
signature: P256StubSignature, // Different stub for P256 keys
_26
});
_26
_26
// Sign with the session key (no user interaction required)
_26
const { r, s } = await WebCryptoP256.sign({
_26
privateKey: sessionKey.privateKey,
_26
payload: getUserOperationHash(userOperation),
_26
});
_26
_26
// Format and attach the signature
_26
userOperation.signature = encodeP256Signature({ r, s });
_26
_26
// Submit the transaction
_26
await bundlerClient.sendUserOperation(userOperation);

Conclusion

WebAuthn with P256 signatures represents a significant leap forward in Web3 user experience and security. By eliminating private key exposure and leveraging familiar authentication methods, we can build smart accounts that are both more secure and more user-friendly than traditional wallets.

The combination of EIP-7702 and ERC-4337 unlocks the rest of the experience: gas sponsorship so users do not need to hold ETH, session keys so they do not need to approve every action, and batch transactions so multi-step actions feel like a single click.

That is the full shape of modern crypto onboarding — a sign-up flow that feels like a SaaS product, a wallet that does not require a seed phrase, and a security model that is still self-custodial when you look under the hood.

For a turnkey version of all of the above, Openfort embedded wallets ship every piece described in this post: passkey sign-in, smart-account provisioning, paymaster sponsorship, session keys, and recovery. If you would rather call an SDK than maintain your own delegation contract, that is the path.

Share this article

Related Articles

  1. The GENIUS Act for Fintechs: What Changes by July 2028

    The GENIUS Act regulates payment-stablecoin issuers — but every app that embeds USDC, PYUSD, or USD1 inherits the cascade. The integrator's map: what changes, when, and which 11 things builders ship before the July 2028 cliff.

  2. Stablecoin KYC for Fintechs: Who Owns the Compliance Perimeter

    Stablecoin KYC sits at the wallet perimeter, not the SDK install. A 2026 map of who owns KYC for builders embedding USDC, PYUSD, and USD1 — the FATF / MiCA / GENIUS triggers, the role split between issuer / wallet provider / app, and where the SDK hooks plug in.

  3. Polygon Wallet: A Developer's Guide to Embedding One

    How developers embed a Polygon wallet with account abstraction, gas sponsorship, and HTTP 402 payments — and how Openfort compares to Crossmint.

Ship your first wallet in minutes