From 5da67fcf5c1b32c50996ce6893af4ad070f6f1c1 Mon Sep 17 00:00:00 2001 From: Steve Date: Fri, 10 Oct 2025 12:07:00 -0400 Subject: [PATCH 1/6] feat: added initial webauthn tutorial --- .../5.x/learn/webauthn-smart-accounts.mdx | 1967 +++++++++++++++++ public/webauthn-demo.png | Bin 0 -> 150737 bytes src/navigation/ethereum-evm.json | 5 + 3 files changed, 1972 insertions(+) create mode 100644 content/contracts/5.x/learn/webauthn-smart-accounts.mdx create mode 100644 public/webauthn-demo.png diff --git a/content/contracts/5.x/learn/webauthn-smart-accounts.mdx b/content/contracts/5.x/learn/webauthn-smart-accounts.mdx new file mode 100644 index 00000000..806f28a1 --- /dev/null +++ b/content/contracts/5.x/learn/webauthn-smart-accounts.mdx @@ -0,0 +1,1967 @@ +--- +title: WebAuthn Smart Accounts +--- + +import { Step, Steps } from 'fumadocs-ui/components/steps'; + +Account abstraction is becoming a vital tool to advance onchain technology for everyday users, making it easier to create and use a wallet without needing to handle a private key. One of the most popular forms of this is through [Passkeys](https://www.webauthn.me/passkeys). One particular schema of passkey signers is WebAuthn, a standard that is used across multiple devices and ecosystems. OpenZeppelin has created a `WebAuthn.sol` contract that can be used to create smart accounts using this signature standard. The result is a powerful and secure experience using biometrics and industry standards of cryptography. + +In this tutorial we'll show you how you can build fullstack application that allows users to create smart accounts with WebAuthn passkeys and conduct an example user operation like minting an NFT. + +## Prequisites + +Before we get started make sure you have the following installed + +- [`Node.js`](https://nodejs.org/en/download) +- [`pmpm`](https://pnpm.io/installation) +- [`Foundry`](https://getfoundry.sh/introduction/installation) + +Once you have confirmed those are all installed, let's make sure we have a wallet setup with Foundry. If you already have one setup and funded with testnet eth, you can skip this part. + +### Wallet Setup + +To make a new wallet run the following command: + +```bash title="Shell" +cast wallet new -p ~/.foundry/keystores sepolia +``` + +This will prompt you for a password and then create a new keypair and encrypt the private key locally, which is much safer than working with plain text private keys. The public address should be printed when you create the wallet but you can retrieve it at any time with the following command: + +```bash title="Shell" +cast wallet address --account sepolia +``` + + + Make sure to only use this wallet for testnet funds! + + + +### Project Structure + +For context our final project will look something like this + +``` +. +└── contracts // Smart contracts +└── server // Secure server enviornment +└── shared // Shared addresses and ABIs +└── client // Web UI +``` + +Let's make an empty directory that will store all of this and then `cd` into it. + +```bash title="Shell" +mkdir webauthn-tutorial +cd webauthn-tutorial +``` + +With the initial structure setup we can move on to intializing the different projects. + +## Contracts + +While inside `webauthn-tutorial` run the command below to setup a new foundry project for our contracts, then move into it. + +```bash title="Shell" +forge init contracts +cd contracts +``` + +Inside the `contracts` project go ahead and delete the default `Counter` files like so: + +```bash title="Shell" +rm src/* test/* script/* +``` + +### Setup + +Next we'll install the OpenZeppelin contracts library which will include everything else we need to setup a WebAuthn account and factory. + +```bash title="Shell" +foundry install OpenZeppelin/openzeppelin-contracts@v5.5.0-rc.0 +``` + +For our contracts we need to make three files inside of `src`: +- `AccountWebAuthn.sol` - Account implementation using WebAuthn signatures +- `AccountFactory.sol` - Account factory +- `MyNFT.sol` - Example NFT contract for testing User Operations + +Inside `AccountWebAuthn.sol` paste in the following code: + +```solidity title="AccountWebAuthn.sol" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {Account} from "@openzeppelin/contracts/account/Account.sol"; +import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; +import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; +import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; +import {ERC7739} from "@openzeppelin/contracts/utils/cryptography/signers/draft-ERC7739.sol"; +import {ERC7821} from "@openzeppelin/contracts/account/extensions/draft-ERC7821.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {SignerWebAuthn} from "@openzeppelin/contracts/utils/cryptography/signers/SignerWebAuthn.sol"; +import {SignerP256} from "@openzeppelin/contracts/utils/cryptography/signers/SignerP256.sol"; + + +contract AccountWebAuthn is + Initializable, + Account, + EIP712, + ERC7739, + ERC7821, + SignerWebAuthn, + ERC721Holder, + ERC1155Holder +{ + constructor() + EIP712("AccountWebAuthn", "1") + SignerP256( + 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296, + 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5 + ) + {} + + function initializeWebAuthn(bytes32 qx, bytes32 qy) public initializer { + _setSigner(qx, qy); // Set the P256 public key + } + + /** + * @dev Override to allow EntryPoint to execute transactions + */ + function _erc7821AuthorizedExecutor( + address caller, + bytes32 mode, + bytes calldata executionData + ) internal view override returns (bool) { + return + caller == address(entryPoint()) || + super._erc7821AuthorizedExecutor(caller, mode, executionData); + } +} +``` + +Let's go over some of the details we have going on. First we've imported some of the Account modules and extensions we need for things like ERC721 or ERC1155 support, as well as using ERC7821 for authorized execution. Since we'll be using a factory to make accounts we don't want to use the constructor, however the `P256` signer does require a valid key when being setup. To bypass this we'll simply pass in a dummy public key and then setup an initializer function to set the signer after the fact. Once it's initialized it won't be updated again. + +Now paste the following code into `AccountFactory.sol`: + +```solidity title="AccountFactory.sol" +// contracts/AccountFactory.sol +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.27; + +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + +/** + * @dev A factory contract to create accounts on demand. + */ +contract AccountFactory { + using Clones for address; + using Address for address; + + address private immutable _impl; + + constructor(address impl_) { + _impl = impl_; + } + + /// @dev Predict the address of the account + function predictAddress( + bytes32 salt, + bytes calldata callData + ) public view returns (address, bytes32) { + bytes32 calldataSalt = _saltedCallData(salt, callData); + return ( + _impl.predictDeterministicAddress(calldataSalt, address(this)), + calldataSalt + ); + } + + /// @dev Create clone accounts on demand + function cloneAndInitialize( + bytes32 salt, + bytes calldata callData + ) public returns (address) { + return _cloneAndInitialize(salt, callData); + } + + /// @dev Create clone accounts on demand and return the address. Uses `callData` to initialize the clone. + function _cloneAndInitialize( + bytes32 salt, + bytes calldata callData + ) internal returns (address) { + (address predicted, bytes32 _calldataSalt) = predictAddress(salt, callData); + if (predicted.code.length == 0) { + _impl.cloneDeterministic(_calldataSalt); + predicted.functionCall(callData); + } + return predicted; + } + + function _saltedCallData( + bytes32 salt, + bytes calldata callData + ) internal pure returns (bytes32) { + // Scope salt to the callData to avoid front-running the salt with a different callData + return keccak256(abi.encodePacked(salt, callData)); + } +} +``` + +The factory will take an implementation address which it will use for creating new accounts. There are two public functions; one is to `predictAddress` so we could fund the account before creating if we wanted to, and the second is `cloneAndInitialize` which will create the new account from our implementation address. + +Finally we'll add the code for `MyNFT.sol`: + +```solidity title="MyNFT.sol" +// SPDX-License-Identifier: MIT +// Compatible with OpenZeppelin Contracts ^5.4.0 +pragma solidity ^0.8.27; + +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +contract MyNFT is ERC721, Ownable { + uint256 private _nextTokenId; + + constructor(address initialOwner) + ERC721("MyNFT", "MYNFT") + Ownable(initialOwner) + {} + + function safeMint(address to) public returns (uint256) { + uint256 tokenId = _nextTokenId++; + _safeMint(to, tokenId); + return tokenId; + } +} +``` + +This is a really simple NFT contract that has minting enabled, with the small exception that we've removed the `onlyOwner` modifier from the `safeMint` function to make it simpler for our smart account to interact with it. + +### Deployment + +With all of our contracts put together we can make a new file under the `script` directory called `Deploy.s.sol` and put the following code inside: + +```solidity title="Deploy.s.sol" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import "forge-std/Script.sol"; +import "../src/AccountWebAuthn.sol"; +import "../src/AccountFactory.sol"; +import "../src/MyNFT.sol"; + +contract Deploy is Script { + function run() external { + uint256 deployerPrivateKey; + + // Use Anvil's first default account if no private key is provided + // Anvil account #0: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + if (vm.envOr("PRIVATE_KEY", uint256(0)) == 0) { + deployerPrivateKey = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80; + console.log("Using Anvil default account"); + } else { + deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + console.log("Using provided private key"); + } + + vm.startBroadcast(deployerPrivateKey); + + // Deploy AccountWebAuthn implementation + AccountWebAuthn accountImpl = new AccountWebAuthn(); + console.log("AccountWebAuthn implementation deployed at:", address(accountImpl)); + + // Deploy AccountFactory with the implementation address + AccountFactory accountFactory = new AccountFactory(address(accountImpl)); + console.log("AccountFactory deployed at:", address(accountFactory)); + + // Deploy test NFT + MyNFT nftContract = new MyNFT(vm.addr(deployerPrivateKey)); + console.log("AccountWebAuthn implementation deployed at:", address(nftContract)); + + console.log("Deployed by:", vm.addr(deployerPrivateKey)); + + vm.stopBroadcast(); + } +} +``` + +This deployment script will do the following: +- Setup the broadcast with our `PRIVATE_KEY` +- Deploy the `AccountWebAuthn` implementation contract +- Deploy the `AccountFactory` and pass in the recently deployed `AccountWebAuthn` implementation address +- Deploy the `MyNFT` contract with our deployer address as the owner + +Before we can run this script we need to create a `.env` file with the following content: + +```bash +export RPC_URL=https://sepolia.drpc.org +export PRIVATE_KEY=$(cast wallet private-key --account sepolia) +``` + +Thanks to `cast` we can use our wallet private key without keeping it in plain text and instead make it a shell environment variable that can be accessed by Foundry. We're using a public RPC url here but you may want to use one from Alchemy or your DRPC account that won't have rate limits. With that we can go ahead and run the deployment: + +```bash title="Shell" +source .env +forge script script/Deploy.s.sol:Deploy --rpc-url $RPC_URL --broadcast --verify +``` + +This should deploy all three contracts and print the addresses for each in the terminal, as well as save them to `broadcast`. + +### Setup Shared Directory + +By compiling and deploying these contracts we've created the ABI's and Addresses we need across the other pieces of our app. To make it easier to access these constants, let's make make a new directory called `shared`. + +```bash title="Shell" +cd .. # Move out of contracts +mkdir shared +cd shared +``` + +Inside the folder create two files and put in the following content: + +```bash title="Shell" +touch index.ts +``` + +```typescript title="index.ts" +export * from "./EntrypointV08"; +export const FACTORY_ADDRESS = "0xf403e5e9230a233dde99d1c6adffa9d1e81dbd98"; +export const NFT_ADDRESS = "0x1936494b8444aF8585873F478dc826C6Ab76582e"; +export const ENTRYPOINT_ADDRESS = "0x4337084d9e255ff0702461cf8895ce9e3b5ff108"; +export { abi as accountWebAuthnAbi } from "../contracts/out/AccountWebAuthn.sol/AccountWebAuthn.json"; +export { abi as accountFactoryAbi } from "../contracts/out/AccountFactory.sol/AccountFactory.json"; +export { abi as myNftAbi } from "../contracts/out/MyNFT.sol/MyNFT.json"; +``` + +One of the pieces we need to use Account Abstraction is the Entrypoint contract. This is a contract that has the same address across every chain and allows us to submit user operations and have them conducted to our smart accounts. You can create a new file inside `shared` called `EntrypointV08.ts` and paste in the contents below: + +```typescript title="EntrypointV08.ts" +export const entryPointAbi = [ + { inputs: [], stateMutability: "nonpayable", type: "constructor" }, + { + inputs: [ + { internalType: "bool", name: "success", type: "bool" }, + { internalType: "bytes", name: "ret", type: "bytes" }, + ], + name: "DelegateAndRevert", + type: "error", + }, + { + inputs: [ + { internalType: "uint256", name: "opIndex", type: "uint256" }, + { internalType: "string", name: "reason", type: "string" }, + ], + name: "FailedOp", + type: "error", + }, + { + inputs: [ + { internalType: "uint256", name: "opIndex", type: "uint256" }, + { internalType: "string", name: "reason", type: "string" }, + { internalType: "bytes", name: "inner", type: "bytes" }, + ], + name: "FailedOpWithRevert", + type: "error", + }, + { inputs: [], name: "InvalidShortString", type: "error" }, + { + inputs: [{ internalType: "bytes", name: "returnData", type: "bytes" }], + name: "PostOpReverted", + type: "error", + }, + { inputs: [], name: "ReentrancyGuardReentrantCall", type: "error" }, + { + inputs: [{ internalType: "address", name: "sender", type: "address" }], + name: "SenderAddressResult", + type: "error", + }, + { + inputs: [{ internalType: "address", name: "aggregator", type: "address" }], + name: "SignatureValidationFailed", + type: "error", + }, + { + inputs: [{ internalType: "string", name: "str", type: "string" }], + name: "StringTooLong", + type: "error", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "userOpHash", + type: "bytes32", + }, + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "factory", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "paymaster", + type: "address", + }, + ], + name: "AccountDeployed", + type: "event", + }, + { anonymous: false, inputs: [], name: "BeforeExecution", type: "event" }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "account", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "totalDeposit", + type: "uint256", + }, + ], + name: "Deposited", + type: "event", + }, + { anonymous: false, inputs: [], name: "EIP712DomainChanged", type: "event" }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "userOpHash", + type: "bytes32", + }, + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "nonce", + type: "uint256", + }, + { + indexed: false, + internalType: "bytes", + name: "revertReason", + type: "bytes", + }, + ], + name: "PostOpRevertReason", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "aggregator", + type: "address", + }, + ], + name: "SignatureAggregatorChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "account", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "totalStaked", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "unstakeDelaySec", + type: "uint256", + }, + ], + name: "StakeLocked", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "account", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "withdrawTime", + type: "uint256", + }, + ], + name: "StakeUnlocked", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "account", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "withdrawAddress", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "StakeWithdrawn", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "userOpHash", + type: "bytes32", + }, + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "paymaster", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "nonce", + type: "uint256", + }, + { indexed: false, internalType: "bool", name: "success", type: "bool" }, + { + indexed: false, + internalType: "uint256", + name: "actualGasCost", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "actualGasUsed", + type: "uint256", + }, + ], + name: "UserOperationEvent", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "userOpHash", + type: "bytes32", + }, + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "nonce", + type: "uint256", + }, + ], + name: "UserOperationPrefundTooLow", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "userOpHash", + type: "bytes32", + }, + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "nonce", + type: "uint256", + }, + { + indexed: false, + internalType: "bytes", + name: "revertReason", + type: "bytes", + }, + ], + name: "UserOperationRevertReason", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "account", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "withdrawAddress", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "Withdrawn", + type: "event", + }, + { + inputs: [ + { internalType: "uint32", name: "unstakeDelaySec", type: "uint32" }, + ], + name: "addStake", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "target", type: "address" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + name: "delegateAndRevert", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "depositTo", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "eip712Domain", + outputs: [ + { internalType: "bytes1", name: "fields", type: "bytes1" }, + { internalType: "string", name: "name", type: "string" }, + { internalType: "string", name: "version", type: "string" }, + { internalType: "uint256", name: "chainId", type: "uint256" }, + { internalType: "address", name: "verifyingContract", type: "address" }, + { internalType: "bytes32", name: "salt", type: "bytes32" }, + { internalType: "uint256[]", name: "extensions", type: "uint256[]" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "getDepositInfo", + outputs: [ + { + components: [ + { internalType: "uint256", name: "deposit", type: "uint256" }, + { internalType: "bool", name: "staked", type: "bool" }, + { internalType: "uint112", name: "stake", type: "uint112" }, + { internalType: "uint32", name: "unstakeDelaySec", type: "uint32" }, + { internalType: "uint48", name: "withdrawTime", type: "uint48" }, + ], + internalType: "struct IStakeManager.DepositInfo", + name: "info", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getDomainSeparatorV4", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "sender", type: "address" }, + { internalType: "uint192", name: "key", type: "uint192" }, + ], + name: "getNonce", + outputs: [{ internalType: "uint256", name: "nonce", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getPackedUserOpTypeHash", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [{ internalType: "bytes", name: "initCode", type: "bytes" }], + name: "getSenderAddress", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "address", name: "sender", type: "address" }, + { internalType: "uint256", name: "nonce", type: "uint256" }, + { internalType: "bytes", name: "initCode", type: "bytes" }, + { internalType: "bytes", name: "callData", type: "bytes" }, + { + internalType: "bytes32", + name: "accountGasLimits", + type: "bytes32", + }, + { + internalType: "uint256", + name: "preVerificationGas", + type: "uint256", + }, + { internalType: "bytes32", name: "gasFees", type: "bytes32" }, + { internalType: "bytes", name: "paymasterAndData", type: "bytes" }, + { internalType: "bytes", name: "signature", type: "bytes" }, + ], + internalType: "struct PackedUserOperation", + name: "userOp", + type: "tuple", + }, + ], + name: "getUserOpHash", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + components: [ + { + components: [ + { internalType: "address", name: "sender", type: "address" }, + { internalType: "uint256", name: "nonce", type: "uint256" }, + { internalType: "bytes", name: "initCode", type: "bytes" }, + { internalType: "bytes", name: "callData", type: "bytes" }, + { + internalType: "bytes32", + name: "accountGasLimits", + type: "bytes32", + }, + { + internalType: "uint256", + name: "preVerificationGas", + type: "uint256", + }, + { internalType: "bytes32", name: "gasFees", type: "bytes32" }, + { + internalType: "bytes", + name: "paymasterAndData", + type: "bytes", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + ], + internalType: "struct PackedUserOperation[]", + name: "userOps", + type: "tuple[]", + }, + { + internalType: "contract IAggregator", + name: "aggregator", + type: "address", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + ], + internalType: "struct IEntryPoint.UserOpsPerAggregator[]", + name: "opsPerAggregator", + type: "tuple[]", + }, + { internalType: "address payable", name: "beneficiary", type: "address" }, + ], + name: "handleAggregatedOps", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "address", name: "sender", type: "address" }, + { internalType: "uint256", name: "nonce", type: "uint256" }, + { internalType: "bytes", name: "initCode", type: "bytes" }, + { internalType: "bytes", name: "callData", type: "bytes" }, + { + internalType: "bytes32", + name: "accountGasLimits", + type: "bytes32", + }, + { + internalType: "uint256", + name: "preVerificationGas", + type: "uint256", + }, + { internalType: "bytes32", name: "gasFees", type: "bytes32" }, + { internalType: "bytes", name: "paymasterAndData", type: "bytes" }, + { internalType: "bytes", name: "signature", type: "bytes" }, + ], + internalType: "struct PackedUserOperation[]", + name: "ops", + type: "tuple[]", + }, + { internalType: "address payable", name: "beneficiary", type: "address" }, + ], + name: "handleOps", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint192", name: "key", type: "uint192" }], + name: "incrementNonce", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes", name: "callData", type: "bytes" }, + { + components: [ + { + components: [ + { internalType: "address", name: "sender", type: "address" }, + { internalType: "uint256", name: "nonce", type: "uint256" }, + { + internalType: "uint256", + name: "verificationGasLimit", + type: "uint256", + }, + { + internalType: "uint256", + name: "callGasLimit", + type: "uint256", + }, + { + internalType: "uint256", + name: "paymasterVerificationGasLimit", + type: "uint256", + }, + { + internalType: "uint256", + name: "paymasterPostOpGasLimit", + type: "uint256", + }, + { + internalType: "uint256", + name: "preVerificationGas", + type: "uint256", + }, + { internalType: "address", name: "paymaster", type: "address" }, + { + internalType: "uint256", + name: "maxFeePerGas", + type: "uint256", + }, + { + internalType: "uint256", + name: "maxPriorityFeePerGas", + type: "uint256", + }, + ], + internalType: "struct EntryPoint.MemoryUserOp", + name: "mUserOp", + type: "tuple", + }, + { internalType: "bytes32", name: "userOpHash", type: "bytes32" }, + { internalType: "uint256", name: "prefund", type: "uint256" }, + { internalType: "uint256", name: "contextOffset", type: "uint256" }, + { internalType: "uint256", name: "preOpGas", type: "uint256" }, + ], + internalType: "struct EntryPoint.UserOpInfo", + name: "opInfo", + type: "tuple", + }, + { internalType: "bytes", name: "context", type: "bytes" }, + ], + name: "innerHandleOp", + outputs: [ + { internalType: "uint256", name: "actualGasCost", type: "uint256" }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "uint192", name: "", type: "uint192" }, + ], + name: "nonceSequenceNumber", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "senderCreator", + outputs: [ + { internalType: "contract ISenderCreator", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes4", name: "interfaceId", type: "bytes4" }], + name: "supportsInterface", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "unlockStake", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address payable", + name: "withdrawAddress", + type: "address", + }, + ], + name: "withdrawStake", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address payable", + name: "withdrawAddress", + type: "address", + }, + { internalType: "uint256", name: "withdrawAmount", type: "uint256" }, + ], + name: "withdrawTo", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { stateMutability: "payable", type: "receive" }, +] as const; +``` + +With that our contracts are all set to go! + +## Client and Server + +In our smart account app we want to have the following flow: +- User clicks on UI button to create an account +- User is prompted to create a passkey +- Passkey is used to create and fund smart account on on our server by our server wallet interacting with the account factory +- Client prepares operation to mint an NFT from the NFT contract, then prompts the user to sign the transaction with their passkey +- Signature and operation info is sent to the server to be processed through the Entrypoint contract by our server wallet +- Server sends a response back to the client with the transaction info + +In a real world application you might use a bundler instead of a server like we are to process the transactions, but it's helpful to see how it all works end-to-end. With that said we need to setup the client and server repos inside our main project directory. + +### Setup Client + +Make sure you are in the root directory and run the following command + +```npm +pnpm create vite@latest client +``` + +Go ahead and select the `React` and `Typescript` options, and the defaults that follow. Then move into that client directory and install our other dependencies. + +```bash title="Shell" +cd client +pnpm install viem ox +``` + +While we are here go ahead and create a new file called `utils.ts` inside the `src` directory and put in the following code: + +```typescript title="utils.ts" +export function serializeBigInts(obj: any): any { + if (typeof obj === "bigint") { + return obj.toString(); + } + if (Array.isArray(obj)) { + return obj.map(serializeBigInts); + } + if (obj !== null && typeof obj === "object") { + return Object.fromEntries( + Object.entries(obj).map(([key, value]) => [key, serializeBigInts(value)]), + ); + } + return obj; +} +``` + +This will be a helper function to help process `BigInt` types that can't be serialized by JSON when we send data to our server. + +### Setup Server + +Move back out into the main root directory of the tutorial and then run the following command to create a [Hono]() app: + +```npm +pnpm create hono@latest server +``` + +Select the `cloudflare-worker` option from the templates, then move into the project and install the other dependencies. + +```bash title="Shell" +pnpm install viem +pnpm install -D @types/node +``` + +This server is going to use our same foundry wallet from before to handle transactions that need to be processed on the backend. Let's make another `.env` file with the following contents: + +```bash +export CLOUDFLARE_INCLUDE_PROCESS_ENV=true +export RPC_URL=https://sepolia.drpc.org +export PRIVATE_KEY=$(cast wallet private-key --account sepolia) +``` + + + It is highly recommend to use an RPC URL that will be performant and not rate limited. Make a free one at DRPC.org or Alchemy! + + +One last thing we need to do is edit the `server/wrangler.jsonc` file by uncommenting the ` "compatibility_flags"` field like so: + +```jsonc +{ + "$schema": "node_modules/wrangler/config-schema.json", + "name": "server", + "main": "src/index.ts", + "compatibility_date": "2025-10-10", + "compatibility_flags": ["nodejs_compat"] +} +``` + +### Client & Server Code + +Now it's time to start putting code into our client app and server to start the flow we want to achieve. Go ahead and open the `client/src/App.tsx` file and put in the following code: + +```tsx title="App.tsx" +import { useState } from "react"; +import "./App.css"; +import { WebAuthnP256 } from "ox"; +import { + encodeAbiParameters, + createPublicClient, + http, + encodeFunctionData, + encodePacked, + type Hex, +} from "viem"; +import { sepolia } from "viem/chains"; +import { + ENTRYPOINT_ADDRESS, + NFT_ADDRESS, + entryPointAbi, + myNftAbi, + accountWebAuthnAbi, +} from "../../shared"; +import type { PackedUserOperation } from "viem/account-abstraction"; +import { serializeBigInts } from "./utils"; + +const SERVER_URL = "http://localhost:8787"; + +const publicClient = createPublicClient({ + transport: http(), + chain: sepolia, +}); + +function App() { + const [isLoading, setIsLoading] = useState(false); + const [statusMessage, setStatusMessage] = useState(""); + const [accountAddress, setAccountAddress] = useState(null); + const [mintTxHash, setMintTxHash] = useState(null); + + async function createAccount() { + try { + setIsLoading(true); + setStatusMessage("Creating WebAuthn credential..."); + + // Create WebAuthn credential + const credential = await WebAuthnP256.createCredential({ + name: "wallet-user", + }); + + // Convert BigInt values to hex strings for serialization (with proper padding) + const publicKey = { + prefix: credential.publicKey.prefix, + x: `0x${credential.publicKey.x.toString(16).padStart(64, "0")}`, + y: `0x${credential.publicKey.y.toString(16).padStart(64, "0")}`, + }; + + setStatusMessage("Deploying WebAuthn account..."); + + // Send credential to server for account deployment + const response = await fetch(`${SERVER_URL}/account/create`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + credentialId: credential.id, + publicKey, + }), + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || "Failed to create account"); + } + + const result = await response.json(); + + const deployedAddress = result.accountAddress; + setAccountAddress(deployedAddress); + + setStatusMessage("Account deployed! Preparing NFT mint transaction..."); + + const nonce = await publicClient.readContract({ + address: ENTRYPOINT_ADDRESS, + abi: entryPointAbi, + functionName: "getNonce", + args: [deployedAddress, 0n], + }); + + const incrementCallData = encodeFunctionData({ + abi: myNftAbi, + functionName: "safeMint", + args: [deployedAddress], + }); + + const mode = encodePacked( + ["bytes1", "bytes1", "bytes4", "bytes4", "bytes22"], + [ + "0x01", + "0x00", + "0x00000000", + "0x00000000", + "0x00000000000000000000000000000000000000000000", + ], + ); + + // Encode execution data as array of (address, uint256, bytes)[] + const executionData = encodeAbiParameters( + [ + { + type: "tuple[]", + components: [ + { type: "address" }, + { type: "uint256" }, + { type: "bytes" }, + ], + }, + ], + [[[NFT_ADDRESS, 0n, incrementCallData]]], + ); + + // Encode the execute call on the account using ERC7821 format + const callData = encodeFunctionData({ + abi: accountWebAuthnAbi, + functionName: "execute", + args: [mode, executionData], + }); + + const feeData = await publicClient.estimateFeesPerGas(); + + const userOp: PackedUserOperation = { + sender: deployedAddress, + nonce, // Already a BigInt from readContract + initCode: "0x", + callData, + accountGasLimits: encodePacked( + ["uint128", "uint128"], + [ + 1_000_000n, // verificationGasLimit (high for P256 verification) + 300_000n, // callGasLimit + ], + ), + preVerificationGas: 100_000n, + gasFees: encodePacked( + ["uint128", "uint128"], + [ + feeData.maxPriorityFeePerGas, // maxPriorityFeePerGas (1 gwei) + feeData.maxFeePerGas, // maxFeePerGas (2 gwei) + ], + ), + paymasterAndData: "0x", + signature: "0x" as Hex, // Placeholder, will be replaced + }; + + const userOpHash = await publicClient.readContract({ + address: ENTRYPOINT_ADDRESS, + abi: entryPointAbi, + functionName: "getUserOpHash", + args: [userOp], + }); + + setStatusMessage("Signing transaction with WebAuthn..."); + + const { signature, metadata } = await WebAuthnP256.sign({ + challenge: userOpHash, + credentialId: credential.id, + }); + + // Encode the signature in the format expected by OpenZeppelin SignerWebAuthn + // The contract expects an ABI-encoded WebAuthnAuth struct: + // struct WebAuthnAuth { + // bytes32 r; + // bytes32 s; + // uint256 challengeIndex; + // uint256 typeIndex; + // bytes authenticatorData; + // string clientDataJSON; + // } + + // Prepare signature components + const rHex = `0x${signature.r.toString(16).padStart(64, "0")}` as Hex; + const sHex = `0x${signature.s.toString(16).padStart(64, "0")}` as Hex; + + setStatusMessage("Submitting UserOperation to mint NFT..."); + + const mintRequest = await fetch(`${SERVER_URL}/account/mint`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + rHex, + sHex, + metadata, + userOp: serializeBigInts(userOp), + nonce: nonce.toString(), + }), + }); + + const mintResponse = await mintRequest.json(); + console.log(mintResponse); + + if (mintResponse.hash) { + setMintTxHash(mintResponse.hash); + } + + setStatusMessage("Success! NFT minted to your account."); + setIsLoading(false); + } catch (err) { + console.error("Error creating account:", err); + setStatusMessage( + `Error: ${err instanceof Error ? err.message : "Unknown error occurred"}`, + ); + setIsLoading(false); + } + } + + return ( + <> +

WebAuthn Account Abstraction

+
+ + {statusMessage && ( +
+ {isLoading &&
} +

{statusMessage}

+
+ )} + {accountAddress && ( +
+

Account Details

+
+ Address: + {accountAddress} +
+ {mintTxHash && ( +
+ NFT Mint Transaction: + + View on Etherscan ↗ + +
+ )} +
+ )} +
+ + ); +} +export default App; +``` + +Now inside `server/src/index.ts` paste in the code below: + +```ts title="index.ts" +import { Hono } from "hono"; +import { cors } from "hono/cors"; +import { logger } from "hono/logger"; +import { + type Hex, + encodeFunctionData, + createPublicClient, + createWalletClient, + http, + encodeAbiParameters, +} from "viem"; +import { + accountWebAuthnAbi, + FACTORY_ADDRESS, + accountFactoryAbi, + ENTRYPOINT_ADDRESS, + entryPointAbi, +} from "../../shared"; +import { privateKeyToAccount } from "viem/accounts"; +import { sepolia } from "viem/chains"; + +type Bindings = { + RPC_URL: string; + PRIVATE_KEY: string; +}; + +const app = new Hono<{ Bindings: Bindings }>(); + +app.use(cors()); +app.use(logger()); + +app.get("/", (c) => { + return c.text("Hello Hono!"); +}); + +app.post("/account/create", async (c) => { + try { + const publicClient = createPublicClient({ + chain: sepolia, + transport: http(c.env.RPC_URL), + }); + + const account = privateKeyToAccount(c.env.PRIVATE_KEY as `0x${string}`); + + const walletClient = createWalletClient({ + chain: sepolia, + transport: http(c.env.RPC_URL), + account, + }); + + const { + credentialId, // Can be used to store accounts for future logins + publicKey, + } = await c.req.json(); + + // Extract qx and qy from the public key (ensure proper padding) + const qx = publicKey.x as Hex; + const qy = publicKey.y as Hex; + + // Encode the initialization call + const initCallData = encodeFunctionData({ + abi: accountWebAuthnAbi, + functionName: "initializeWebAuthn", + args: [qx, qy], + }); + + // Generate random salt + const accountSalt = + `0x${Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString("hex")}` as Hex; + + // Predict account address + const [predictedAddress] = await publicClient.readContract({ + address: FACTORY_ADDRESS, + abi: accountFactoryAbi, + functionName: "predictAddress", + args: [accountSalt, initCallData], + }); + + // Deploy the account + const hash = await walletClient.writeContract({ + address: FACTORY_ADDRESS, + abi: accountFactoryAbi, + functionName: "cloneAndInitialize", + args: [accountSalt, initCallData], + }); + + // Wait for transaction + await publicClient.waitForTransactionReceipt({ hash }); + + // Fund the account with 0.005 ETH from deployer wallet + const fundHash = await walletClient.sendTransaction({ + to: predictedAddress, + value: 5000000000000000n, // 0.005 ETH in wei + }); + + // Wait for funding transaction + await publicClient.waitForTransactionReceipt({ hash: fundHash }); + + return c.json({ + success: true, + accountAddress: predictedAddress, + transactionHash: hash, + fundingTransactionHash: fundHash, + publicKey: { qx, qy }, + }); + } catch (error) { + return c.json({ error: ` Failed to create account: ${error} ` }, 500); + } +}); + +app.post("/account/mint", async (c) => { + const publicClient = createPublicClient({ + chain: sepolia, + transport: http(c.env.RPC_URL), + }); + + const account = privateKeyToAccount(c.env.PRIVATE_KEY as `0x${string}`); + + const walletClient = createWalletClient({ + chain: sepolia, + transport: http(c.env.RPC_URL), + account, + }); + + try { + const { + metadata, + rHex, + sHex, + userOp, + nonce: serializedNonce, + } = await c.req.json(); + + const challengeIndex = BigInt(metadata.challengeIndex); + const typeIndex = BigInt(metadata.typeIndex); + const authenticatorDataHex = metadata.authenticatorData; + const clientDataJSON = metadata.clientDataJSON; + const nonce = BigInt(serializedNonce); + + const encodedSignature = encodeAbiParameters( + [ + { name: "r", type: "bytes32" }, + { name: "s", type: "bytes32" }, + { name: "challengeIndex", type: "uint256" }, + { name: "typeIndex", type: "uint256" }, + { name: "authenticatorData", type: "bytes" }, + { name: "clientDataJSON", type: "string" }, + ], + [ + rHex, + sHex, + challengeIndex, + typeIndex, + authenticatorDataHex, + clientDataJSON, + ], + ); + + const fullUserOp = { + ...userOp, + nonce, + preVerificationGas: BigInt(userOp.preVerificationGas), + signature: encodedSignature, + }; + + const { request } = await publicClient.simulateContract({ + address: ENTRYPOINT_ADDRESS, + abi: entryPointAbi, + functionName: "handleOps", + args: [[fullUserOp], walletClient.account.address], + account: walletClient.account, + }); + + const hash = await walletClient.writeContract(request); + + const receipt = await publicClient.waitForTransactionReceipt({ + hash: hash, + }); + + if (receipt.status === "reverted") { + return c.json({ error: ` Failed to Mint: Reverted ` }, 500); + } + + return c.json({ + status: "success", + hash, + }); + } catch (error) { + return c.json({ error: ` Failed to Mint: ${error} ` }, 500); + } +}); + +export default app; +``` + +Now that's a fair bit of code, so let's do a breakdown of each step and what's happening. + +### Breakdown + + + + +#### Create and Fund Account + +The flow starts in the client code where the user clicks on the button and it kicks off `createAccount()`. + +```tsx +async function createAccount() { + try { + setIsLoading(true); + setStatusMessage("Creating WebAuthn credential..."); + + // Create WebAuthn credential + const credential = await WebAuthnP256.createCredential({ + name: "wallet-user", + }); + + // Convert BigInt values to hex strings for serialization (with proper padding) + const publicKey = { + prefix: credential.publicKey.prefix, + x: `0x${credential.publicKey.x.toString(16).padStart(64, "0")}`, + y: `0x${credential.publicKey.y.toString(16).padStart(64, "0")}`, + }; + + setStatusMessage("Deploying WebAuthn account..."); + + // Send credential to server for account deployment + const response = await fetch(`${SERVER_URL}/account/create`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + credentialId: credential.id, + publicKey, + }), + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || "Failed to create account"); + } + + const result = await response.json(); + + // Rest of function +``` + +The first thing we need is a WebAuthn credential, and thankfully `ox` makes this really easy to do with `WebAuthnP256.createCredential()`. Once we have the credential we need to take the public key coordinates and serlialize the BigInt values into hex strings. Then we send a request to our server to `/account/create` with a JSON body of that `publicKey` as well as a `credentialId`. + +On the server the incoming requst is handled with this endpoint: + +```ts +app.post("/account/create", async (c) => { + try { + const publicClient = createPublicClient({ + chain: sepolia, + transport: http(c.env.RPC_URL), + }); + + const account = privateKeyToAccount(c.env.PRIVATE_KEY as `0x${string}`); + + const walletClient = createWalletClient({ + chain: sepolia, + transport: http(c.env.RPC_URL), + account, + }); + + const { + credentialId, // Can be used to store accounts for future logins + publicKey, + } = await c.req.json(); + + // Extract qx and qy from the public key (ensure proper padding) + const qx = publicKey.x as Hex; + const qy = publicKey.y as Hex; + + // Encode the initialization call + const initCallData = encodeFunctionData({ + abi: accountWebAuthnAbi, + functionName: "initializeWebAuthn", + args: [qx, qy], + }); + + // Generate random salt + const accountSalt = + `0x${Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString("hex")}` as Hex; + + // Predict account address + const [predictedAddress] = await publicClient.readContract({ + address: FACTORY_ADDRESS, + abi: accountFactoryAbi, + functionName: "predictAddress", + args: [accountSalt, initCallData], + }); + + // Deploy the account + const hash = await walletClient.writeContract({ + address: FACTORY_ADDRESS, + abi: accountFactoryAbi, + functionName: "cloneAndInitialize", + args: [accountSalt, initCallData], + }); + + // Wait for transaction + await publicClient.waitForTransactionReceipt({ hash }); + + // Fund the account with 0.005 ETH from deployer wallet + const fundHash = await walletClient.sendTransaction({ + to: predictedAddress, + value: 5000000000000000n, // 0.005 ETH in wei + }); + + // Wait for funding transaction + await publicClient.waitForTransactionReceipt({ hash: fundHash }); + + return c.json({ + success: true, + accountAddress: predictedAddress, + transactionHash: hash, + fundingTransactionHash: fundHash, + publicKey: { qx, qy }, + }); + } catch (error) { + return c.json({ error: ` Failed to create account: ${error} ` }, 500); + } +}); +``` + +In this endpoint the server parses the JSON body to grab the serialized coordinates of the public key, then we put together what we need to do in order to predict our address. We don't necessarily need this in our flow, but it's good to know how it works. The predicted address is calculated by creating `initCallData` from `initializeWebAuthn` function and the coordiantes we got from the client. We combine that with a random salt that's generated and then we can read the `predictAddress` from the factory. + +To actually deploy our smart account we'll take the same `initCallData` and `randomSalt` as arguments, then call `cloneAndInitialize` from the factory. This will return the smart account address, which we can then fund with our server wallet. Finally we can return the result of our process to the client. + + + + +#### Prepare and Sign User Operation + +Back in our client we now can start prepping a User Operation. + +```tsx + // createAccount() continuted + + const nonce = await publicClient.readContract({ + address: ENTRYPOINT_ADDRESS, + abi: entryPointAbi, + functionName: "getNonce", + args: [deployedAddress, 0n], + }); + + const mintCallData = encodeFunctionData({ + abi: myNftAbi, + functionName: "safeMint", + args: [deployedAddress], + }); + + const mode = encodePacked( + ["bytes1", "bytes1", "bytes4", "bytes4", "bytes22"], + [ + "0x01", + "0x00", + "0x00000000", + "0x00000000", + "0x00000000000000000000000000000000000000000000", + ], + ); + + // Encode execution data as array of (address, uint256, bytes)[] + const executionData = encodeAbiParameters( + [ + { + type: "tuple[]", + components: [ + { type: "address" }, + { type: "uint256" }, + { type: "bytes" }, + ], + }, + ], + [[[NFT_ADDRESS, 0n, mintCallData]]], + ); + + // Encode the execute call on the account using ERC7821 format + const callData = encodeFunctionData({ + abi: accountWebAuthnAbi, + functionName: "execute", + args: [mode, executionData], + }); + + const feeData = await publicClient.estimateFeesPerGas(); + + const userOp: PackedUserOperation = { + sender: deployedAddress, + nonce, // Already a BigInt from readContract + initCode: "0x", + callData, + accountGasLimits: encodePacked( + ["uint128", "uint128"], + [ + 1_000_000n, // verificationGasLimit (high for P256 verification) + 300_000n, // callGasLimit + ], + ), + preVerificationGas: 100_000n, + gasFees: encodePacked( + ["uint128", "uint128"], + [ + feeData.maxPriorityFeePerGas, // maxPriorityFeePerGas (1 gwei) + feeData.maxFeePerGas, // maxFeePerGas (2 gwei) + ], + ), + paymasterAndData: "0x", + signature: "0x" as Hex, // Placeholder, will be replaced + }; + + const userOpHash = await publicClient.readContract({ + address: ENTRYPOINT_ADDRESS, + abi: entryPointAbi, + functionName: "getUserOpHash", + args: [userOp], + }); + + setStatusMessage("Signing transaction with WebAuthn..."); + + const { signature, metadata } = await WebAuthnP256.sign({ + challenge: userOpHash, + credentialId: credential.id, + }); + + // Prepare signature components + const rHex = `0x${signature.r.toString(16).padStart(64, "0")}` as Hex; + const sHex = `0x${signature.s.toString(16).padStart(64, "0")}` as Hex; + + setStatusMessage("Submitting UserOperation to mint NFT..."); + + const mintRequest = await fetch(`${SERVER_URL}/account/mint`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + rHex, + sHex, + metadata, + userOp: serializeBigInts(userOp), + nonce: nonce.toString(), + }), + }); +``` + +Now this starts to get a little confusing as we have a lot of encoding and nesting happening, so let's break it down piece by piece. First we have the `nonce` which we fetch from the Entrypoint contract. Next we need to encode the function data that our smart account wants to perform, which in our case is minting the NFT. + +Then we have the `mode` and `executionData` which includes the address of our NFT contract and the `mintCallData` we just made. With that `executionData` we can do one final encoding of the `callData` that will be passed to the `execute` function of our smart account. + +In order to actually run this transaction through `execute` we need to create what's called an User Operation that has all of the details of how it should be performed. We build the `userOp` object to gather the data that will be passed, and then we send that object to the entrypoint contract to get a `userOpHash`. This is what the user's passkey will sign; it's the approval of everything that's been encoded earlier. + +Once it's signed by the passkey through `WebAuthnP256.sign()` we get the `r` and `s` values from the signature and serialize them as hex strings, and we finally send all of that data to our server to execute the transaction. It's at this point if you wanted to you could send it to a bundler instead which might include a paymaster to handle gas fees. In our case the server wallet will pay gas initially, but then it will be refunded by the entrypoint once the smart account pays the gas fees for the transaciton. + + + + +#### Process User Operation on Server + +To handle processing the user operation we have an endpoint on the server called `/account/mint`: + +```ts +app.post("/account/mint", async (c) => { + const publicClient = createPublicClient({ + chain: sepolia, + transport: http(c.env.RPC_URL), + }); + + const account = privateKeyToAccount(c.env.PRIVATE_KEY as `0x${string}`); + + const walletClient = createWalletClient({ + chain: sepolia, + transport: http(c.env.RPC_URL), + account, + }); + + try { + const { + metadata, + rHex, + sHex, + userOp, + nonce: serializedNonce, + } = await c.req.json(); + + const challengeIndex = BigInt(metadata.challengeIndex); + const typeIndex = BigInt(metadata.typeIndex); + const authenticatorDataHex = metadata.authenticatorData; + const clientDataJSON = metadata.clientDataJSON; + const nonce = BigInt(serializedNonce); + + const encodedSignature = encodeAbiParameters( + [ + { name: "r", type: "bytes32" }, + { name: "s", type: "bytes32" }, + { name: "challengeIndex", type: "uint256" }, + { name: "typeIndex", type: "uint256" }, + { name: "authenticatorData", type: "bytes" }, + { name: "clientDataJSON", type: "string" }, + ], + [ + rHex, + sHex, + challengeIndex, + typeIndex, + authenticatorDataHex, + clientDataJSON, + ], + ); + + const fullUserOp = { + ...userOp, + nonce, + preVerificationGas: BigInt(userOp.preVerificationGas), + signature: encodedSignature, + }; + + const { request } = await publicClient.simulateContract({ + address: ENTRYPOINT_ADDRESS, + abi: entryPointAbi, + functionName: "handleOps", + args: [[fullUserOp], walletClient.account.address], + account: walletClient.account, + }); + + const hash = await walletClient.writeContract(request); + + const receipt = await publicClient.waitForTransactionReceipt({ + hash: hash, + }); + + if (receipt.status === "reverted") { + return c.json({ error: ` Failed to Mint: Reverted ` }, 500); + } + + return c.json({ + status: "success", + hash, + }); + } catch (error) { + return c.json({ error: ` Failed to Mint: ${error} ` }, 500); + } +}); +``` + +In order for the OpenZeppelin `WebAuthn.sol` to verify our signature and prove that the user authorized the operation, we have to prepare and encode the signature data correctly. Since we had to serialize some of the BigInt values on the client we need to reinstate them. Then we can put together the `encodedSignature` with all our values, many of them being the `metadata` that was produced in the signing process by `WebAuthnP256` from `ox`. + +Then we need to construct the `fullUserOp` which includes our updated `encodedSignature`, and we can finally submit it to the Entrypoint Contract. Once successful we can return the transaction hash to the user. + + + + + +## Try it! + +With an overview of how it all works you can test it yourself! In a terminal window run the client dev server: + +```bash title="Shell" +cd client +pnpm dev +``` + +Then in a separate window run the server: + +```bash title="Shell" +cd server +source .env +pnpm dev +``` + +You should be able to visit `http://localhost:5173` and click on the `Create Account` button to experience the full flow! + + + Make sure you are on a browser and device that supports passkeys + + +![image of demo](/webauthn-demo.png) + +## Next Steps + +This tutorial is just scraping the surface of what is possible with OpenZeppelin account abstraction. With `AccountWebAuthn.sol` we could customize the logic and build custom use cases such as multifactor authentication, social recovery, time based controls, and more! We would highly encourage you to check out what other pieces you can add into Accounts with the [Wizard](https://wizard.openzeppelin.com) under the `Accounts` tab. diff --git a/public/webauthn-demo.png b/public/webauthn-demo.png new file mode 100644 index 0000000000000000000000000000000000000000..4fbe2a3cf6ea5f66ae1e7c3a126f848b3f985791 GIT binary patch literal 150737 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 cTKeToSj~Phw%3HXG6wm;(V;I8X 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¥NzxFdCSH# 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 Date: Fri, 10 Oct 2025 12:22:00 -0400 Subject: [PATCH 2/6] chore: small edits --- .../contracts/5.x/learn/webauthn-smart-accounts.mdx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/content/contracts/5.x/learn/webauthn-smart-accounts.mdx b/content/contracts/5.x/learn/webauthn-smart-accounts.mdx index 806f28a1..4d3dccb4 100644 --- a/content/contracts/5.x/learn/webauthn-smart-accounts.mdx +++ b/content/contracts/5.x/learn/webauthn-smart-accounts.mdx @@ -8,12 +8,12 @@ Account abstraction is becoming a vital tool to advance onchain technology for e In this tutorial we'll show you how you can build fullstack application that allows users to create smart accounts with WebAuthn passkeys and conduct an example user operation like minting an NFT. -## Prequisites +## Prerequisites Before we get started make sure you have the following installed - [`Node.js`](https://nodejs.org/en/download) -- [`pmpm`](https://pnpm.io/installation) +- [`pnpm`](https://pnpm.io/installation) - [`Foundry`](https://getfoundry.sh/introduction/installation) Once you have confirmed those are all installed, let's make sure we have a wallet setup with Foundry. If you already have one setup and funded with testnet eth, you can skip this part. @@ -44,7 +44,7 @@ For context our final project will look something like this ``` . └── contracts // Smart contracts -└── server // Secure server enviornment +└── server // Secure server environment └── shared // Shared addresses and ABIs └── client // Web UI ``` @@ -56,7 +56,7 @@ mkdir webauthn-tutorial cd webauthn-tutorial ``` -With the initial structure setup we can move on to intializing the different projects. +With the initial structure setup we can move on to initializing the different projects. ## Contracts @@ -1104,7 +1104,7 @@ export PRIVATE_KEY=$(cast wallet private-key --account sepolia) ``` - It is highly recommend to use an RPC URL that will be performant and not rate limited. Make a free one at DRPC.org or Alchemy! + It is highly recommended to use an RPC URL that will be performant and not rate limited. Make a free one at DRPC.org or Alchemy! One last thing we need to do is edit the `server/wrangler.jsonc` file by uncommenting the ` "compatibility_flags"` field like so: @@ -1964,4 +1964,4 @@ You should be able to visit `http://localhost:5173` and click on the `Create Acc ## Next Steps -This tutorial is just scraping the surface of what is possible with OpenZeppelin account abstraction. With `AccountWebAuthn.sol` we could customize the logic and build custom use cases such as multifactor authentication, social recovery, time based controls, and more! We would highly encourage you to check out what other pieces you can add into Accounts with the [Wizard](https://wizard.openzeppelin.com) under the `Accounts` tab. +This tutorial is just scratching the surface of what is possible with OpenZeppelin account abstraction. With `AccountWebAuthn.sol` we could customize the logic and build custom use cases such as multifactor authentication, social recovery, time based controls, and more! We would highly encourage you to check out what other pieces you can add into Accounts with the [Wizard](https://wizard.openzeppelin.com) under the `Accounts` tab. From 2e0c2510443d95238393e19f1df6a212213a908a Mon Sep 17 00:00:00 2001 From: Steve Simkins <73185218+stevedylandev@users.noreply.github.com> Date: Tue, 14 Oct 2025 10:04:22 -0400 Subject: [PATCH 3/6] chore: Update content/contracts/5.x/learn/webauthn-smart-accounts.mdx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- content/contracts/5.x/learn/webauthn-smart-accounts.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/content/contracts/5.x/learn/webauthn-smart-accounts.mdx b/content/contracts/5.x/learn/webauthn-smart-accounts.mdx index 4d3dccb4..2ef3d95a 100644 --- a/content/contracts/5.x/learn/webauthn-smart-accounts.mdx +++ b/content/contracts/5.x/learn/webauthn-smart-accounts.mdx @@ -4,7 +4,9 @@ title: WebAuthn Smart Accounts import { Step, Steps } from 'fumadocs-ui/components/steps'; -Account abstraction is becoming a vital tool to advance onchain technology for everyday users, making it easier to create and use a wallet without needing to handle a private key. One of the most popular forms of this is through [Passkeys](https://www.webauthn.me/passkeys). One particular schema of passkey signers is WebAuthn, a standard that is used across multiple devices and ecosystems. OpenZeppelin has created a `WebAuthn.sol` contract that can be used to create smart accounts using this signature standard. The result is a powerful and secure experience using biometrics and industry standards of cryptography. +Account abstraction is becoming a vital tool to advance onchain technology for everyday users, making it easier to create and use a wallet without needing to handle a private key. One of the most popular forms of this is through [Passkeys](https://www.webauthn.me/passkeys), which use the WebAuthn standard to create cryptographic [Authentication Assertions](https://www.w3.org/TR/webauthn-2/#sctn-verifying-assertion) across multiple devices and ecosystems. + +OpenZeppelin's `WebAuthn.sol` contract enables smart accounts to verify these WebAuthn assertions onchain. This creates a powerful and secure user experience that leverages biometrics and industry-standard cryptography for wallet interactions. In this tutorial we'll show you how you can build fullstack application that allows users to create smart accounts with WebAuthn passkeys and conduct an example user operation like minting an NFT. From 0063efb510c0f6b23e097195832c15a29de1ea57 Mon Sep 17 00:00:00 2001 From: Steve Simkins <73185218+stevedylandev@users.noreply.github.com> Date: Tue, 14 Oct 2025 10:04:31 -0400 Subject: [PATCH 4/6] chore: Update content/contracts/5.x/learn/webauthn-smart-accounts.mdx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- content/contracts/5.x/learn/webauthn-smart-accounts.mdx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/content/contracts/5.x/learn/webauthn-smart-accounts.mdx b/content/contracts/5.x/learn/webauthn-smart-accounts.mdx index 2ef3d95a..ea40526f 100644 --- a/content/contracts/5.x/learn/webauthn-smart-accounts.mdx +++ b/content/contracts/5.x/learn/webauthn-smart-accounts.mdx @@ -142,7 +142,13 @@ contract AccountWebAuthn is } ``` -Let's go over some of the details we have going on. First we've imported some of the Account modules and extensions we need for things like ERC721 or ERC1155 support, as well as using ERC7821 for authorized execution. Since we'll be using a factory to make accounts we don't want to use the constructor, however the `P256` signer does require a valid key when being setup. To bypass this we'll simply pass in a dummy public key and then setup an initializer function to set the signer after the fact. Once it's initialized it won't be updated again. +Let's break down the key components of our WebAuthn account implementation: + +Our contract inherits from `Account.sol`, which provides the core ERC-4337 functionality, along with `EIP712.sol` for typed data signatures. We also include `ERC721Holder.sol` and `ERC1155Holder.sol` to enable the account to receive NFTs and multi-tokens. The `ERC7739.sol` extension enables readable typed signatures that prevent replay attacks, while `ERC7821.sol` provides the minimal batch executor interface for transaction batching. + +The account uses a factory pattern with minimal proxies (like `Clones.sol`) for gas-efficient deployment. Since each account is deployed as a proxy, we use an initializer function instead of a constructor to set up the account's state after deployment. + +WebAuthn verification relies on `P256.sol` elliptic curve operations, which require a valid public key during contract construction. To work around this factory pattern constraint, we provide a dummy public key in the constructor and set the real WebAuthn public key through the `initializeWebAuthn` function. Once initialized, the signer cannot be changed. Now paste the following code into `AccountFactory.sol`: From 4eaec096f54eaca90012863f1428a195addc1cc7 Mon Sep 17 00:00:00 2001 From: Steve Simkins <73185218+stevedylandev@users.noreply.github.com> Date: Tue, 14 Oct 2025 10:05:01 -0400 Subject: [PATCH 5/6] chore: Update content/contracts/5.x/learn/webauthn-smart-accounts.mdx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- content/contracts/5.x/learn/webauthn-smart-accounts.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/content/contracts/5.x/learn/webauthn-smart-accounts.mdx b/content/contracts/5.x/learn/webauthn-smart-accounts.mdx index ea40526f..1c2ab0e8 100644 --- a/content/contracts/5.x/learn/webauthn-smart-accounts.mdx +++ b/content/contracts/5.x/learn/webauthn-smart-accounts.mdx @@ -171,6 +171,7 @@ contract AccountFactory { address private immutable _impl; constructor(address impl_) { + require(impl_.code.length > 0); _impl = impl_; } From 9708e90ecd43c6e8261e7a78fc968d18d3930f46 Mon Sep 17 00:00:00 2001 From: Steve Date: Tue, 14 Oct 2025 10:16:06 -0400 Subject: [PATCH 6/6] chore: updated factory contract, removed salts from functions --- .../5.x/learn/webauthn-smart-accounts.mdx | 44 +++---------------- 1 file changed, 7 insertions(+), 37 deletions(-) diff --git a/content/contracts/5.x/learn/webauthn-smart-accounts.mdx b/content/contracts/5.x/learn/webauthn-smart-accounts.mdx index 1c2ab0e8..528984bf 100644 --- a/content/contracts/5.x/learn/webauthn-smart-accounts.mdx +++ b/content/contracts/5.x/learn/webauthn-smart-accounts.mdx @@ -176,45 +176,19 @@ contract AccountFactory { } /// @dev Predict the address of the account - function predictAddress( - bytes32 salt, - bytes calldata callData - ) public view returns (address, bytes32) { - bytes32 calldataSalt = _saltedCallData(salt, callData); - return ( - _impl.predictDeterministicAddress(calldataSalt, address(this)), - calldataSalt - ); + function predictAddress(bytes calldata callData) public view returns (address) { + return _impl.predictDeterministicAddress(keccak256(callData), address(this)); } /// @dev Create clone accounts on demand - function cloneAndInitialize( - bytes32 salt, - bytes calldata callData - ) public returns (address) { - return _cloneAndInitialize(salt, callData); - } - - /// @dev Create clone accounts on demand and return the address. Uses `callData` to initialize the clone. - function _cloneAndInitialize( - bytes32 salt, - bytes calldata callData - ) internal returns (address) { - (address predicted, bytes32 _calldataSalt) = predictAddress(salt, callData); + function cloneAndInitialize(bytes calldata callData) public returns (address) { + address predicted = predictAddress(callData); if (predicted.code.length == 0) { - _impl.cloneDeterministic(_calldataSalt); + _impl.cloneDeterministic(keccak256(callData)); predicted.functionCall(callData); } return predicted; } - - function _saltedCallData( - bytes32 salt, - bytes calldata callData - ) internal pure returns (bytes32) { - // Scope salt to the callData to avoid front-running the salt with a different callData - return keccak256(abi.encodePacked(salt, callData)); - } } ``` @@ -1457,16 +1431,12 @@ app.post("/account/create", async (c) => { args: [qx, qy], }); - // Generate random salt - const accountSalt = - `0x${Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString("hex")}` as Hex; - // Predict account address const [predictedAddress] = await publicClient.readContract({ address: FACTORY_ADDRESS, abi: accountFactoryAbi, functionName: "predictAddress", - args: [accountSalt, initCallData], + args: [initCallData], }); // Deploy the account @@ -1474,7 +1444,7 @@ app.post("/account/create", async (c) => { address: FACTORY_ADDRESS, abi: accountFactoryAbi, functionName: "cloneAndInitialize", - args: [accountSalt, initCallData], + args: [initCallData], }); // Wait for transaction