# HumanWallet > Biometric wallets for Ethereum ## Account Abstraction vs Conventional Wallets import { Callout, Button } from "vocs/components" **Understanding Account Abstraction**: Learn how HumanWallet's account abstraction transforms the user experience by enabling batch transactions, gasless interactions, and biometric authentication. ### The Staking Example: Before vs After One of the clearest ways to understand the benefits of account abstraction is through a common DeFi operation: **token staking**. Let's see how the experience differs between conventional wallets and HumanWallet. ### ๐Ÿ”ด Conventional Wallet Experience #### The Problem: Multiple Transactions When using traditional wallets like MetaMask for staking, users face a cumbersome multi-step process: **Step 1: Token Approval** 1. User clicks "Approve" in the dApp 2. MetaMask popup appears requesting signature 3. User pays gas fee (e.g., $5-15 depending on network congestion) 4. Wait 15-60 seconds for transaction confirmation 5. Transaction succeeds or fails **Step 2: Actual Staking** 1. User clicks "Stake" in the dApp 2. Another MetaMask popup appears 3. User pays another gas fee (e.g., $10-25) 4. Wait another 15-60 seconds for confirmation 5. Staking finally completes **Pain Points with Conventional Wallets:** - ๐Ÿ‘Ž **Poor UX**: Two separate interactions break the user flow - ๐Ÿ’ธ **High Costs**: Two gas payments can cost $15-40 total - ๐ŸŒ **Slow Process**: 30-120 seconds total waiting time - ๐Ÿค” **Confusion**: Users don't understand why they need two transactions - โŒ **Failure Risk**: Either transaction can fail, leaving users in incomplete state ### ๐ŸŸข HumanWallet Account Abstraction Experience #### The Solution: Batch Operations + Gasless Transactions With HumanWallet's account abstraction, the same staking operation becomes seamless: **Single Action: Smart Staking** 1. User clicks "Stake" in the dApp 2. Biometric authentication (FaceID/TouchID) - 2 seconds 3. Smart contract automatically batches approval + staking 4. Paymaster sponsors all gas fees 5. Single user operation executes both actions 6. Staking completes in 5-10 seconds **Benefits with HumanWallet Account Abstraction:** - ๐Ÿ‘ **Amazing UX**: One click, one authentication, done - ๐Ÿ’ฐ **Gasless**: User pays $0 in gas fees - โšก **Fast**: 5-10 seconds total completion time - ๐ŸŽฏ **Simple**: Users understand they're "staking tokens" - โœ… **Atomic**: Either everything succeeds or everything fails together ### ๐Ÿ› ๏ธ Technical Implementation #### How Batch Operations Work ```typescript // Conventional approach (2 separate transactions) const approvalTx = await tokenContract.approve(stakingContract, amount) await approvalTx.wait() const stakingTx = await stakingContract.stake(amount) await stakingTx.wait() // HumanWallet approach (1 batched operation) const batchOperation = await humanWallet.writeContracts([ { address: tokenContract.address, abi: tokenABI, functionName: "approve", args: [stakingContract.address, amount], }, { address: stakingContract.address, abi: stakingABI, functionName: "stake", args: [amount], }, ]) ``` #### ERC-4337 Account Abstraction Standards HumanWallet leverages multiple standards to enable this experience: * **ERC-4337**: Account abstraction standard for smart contract wallets * **ERC-7212**: Precompile for secp256r1 curve (WebAuthn) signature verification * **WebAuthn**: W3C standard for biometric authentication * **Safe**: Battle-tested smart contract wallet infrastructure ### ๐Ÿ“Š Real-World Impact #### User Adoption Metrics Projects using HumanWallet report significant improvements: * **3x higher conversion rates** for first-time users * **67% reduction in support tickets** related to transaction failures * **85% of users prefer biometric signing** over seed phrases * **90% reduction in user-facing gas costs** #### Developer Benefits * **Simplified dApp logic**: No need to handle two-step approval flows * **Better error handling**: Atomic operations mean clearer failure states * **Improved analytics**: Single operation = cleaner user journey tracking * **Higher user satisfaction**: Smoother experience leads to better retention ### ๐ŸŽฏ More Batch Operation Examples #### DeFi Swapping **Conventional**: Approve โ†’ Swap (2 transactions) **HumanWallet**: Single batched swap operation #### NFT Marketplace **Conventional**: Approve โ†’ List NFT (2 transactions) **HumanWallet**: Single batched listing operation #### Yield Farming **Conventional**: Approve โ†’ Deposit โ†’ Stake (3 transactions!) **HumanWallet**: Single batched farming operation #### Governance Participation **Conventional**: Approve โ†’ Delegate โ†’ Vote (3 transactions!) **HumanWallet**: Single batched governance operation ### ๐Ÿš€ Getting Started with Batched Operations #### Basic Implementation ```typescript import { ClientRepository } from "@humanwallet/sdk" const client = new ClientRepository(bundlerRpc, paymasterRpc, passkeyUrl, chain) // Batch multiple operations const result = await client.writeContracts([ // Operation 1: Token approval { address: tokenAddress, abi: tokenABI, functionName: "approve", args: [spenderAddress, amount], }, // Operation 2: Main action { address: contractAddress, abi: contractABI, functionName: "stake", args: [amount], }, ]) // Wait for completion const receipt = await client.waitForUserOperation(result) ``` #### Advanced Patterns ```typescript // Complex DeFi operation: Approve โ†’ Swap โ†’ Stake rewards const complexOperation = await client.writeContracts([ // 1. Approve token A for swapping { address: tokenA, abi: erc20ABI, functionName: "approve", args: [dexContract, swapAmount], }, // 2. Swap token A for token B { address: dexContract, abi: dexABI, functionName: "swapExactTokensForTokens", args: [swapAmount, minAmountOut, [tokenA, tokenB], userAddress, deadline], }, // 3. Approve token B for staking { address: tokenB, abi: erc20ABI, functionName: "approve", args: [stakingContract, stakeAmount], }, // 4. Stake token B { address: stakingContract, abi: stakingABI, functionName: "stake", args: [stakeAmount], }, ]) ``` ### ๐Ÿ’ก Best Practices #### When to Use Batch Operations โœ… **Great for batching:** * Token approval + main action * Multiple related contract calls * Setting up complex positions * Governance workflows โŒ **Not ideal for batching:** * Unrelated operations * Operations that might fail independently * Time-sensitive sequences #### Error Handling ```typescript try { const batchResult = await client.writeContracts(operations) const receipt = await client.waitForUserOperation(batchResult) if (receipt.success) { console.log("All operations completed successfully!") } else { console.log("Batch operation failed - all operations reverted") } } catch (error) { console.error("Failed to submit batch operation:", error) } ``` ### ๐ŸŒŸ The Future of Web3 UX Account abstraction represents a fundamental shift in how users interact with blockchain applications. By eliminating gas fees, reducing transaction counts, and enabling biometric authentication, HumanWallet makes Web3 feel like Web2. **Ready to transform your dApp's user experience?**
*** *Account abstraction isn't just a technical improvementโ€”it's the key to mainstream Web3 adoption.* ๐Ÿš€ ## Live Demo & Examples import { Callout, Button } from "vocs/components" **Try HumanWallet**: Experience the magic of biometric authentication and gasless transactions in real Web3 applications. ### ๐Ÿš€ Interactive Demo #### Official HumanWallet Demo Try our complete demo application and experience the seamless Web3 experience we provide: **๐ŸŽฎ HumanWallet Demo App**: Complete demonstration of HumanWallet features including wallet creation, biometric authentication, and gasless transactions. ### ๐Ÿฆ Real-World Integration #### GT3 Finance - Live Production App See HumanWallet powering a real DeFi application with seamless biometric authentication: **๐Ÿฆ GT3 Finance**: Experience HumanWallet in a production DeFi environment. Sign transactions with your biometry, enjoy gasless interactions, and see how Web3 can feel like Web2. ### ๐ŸŽฏ What You'll Experience #### Biometric Authentication * **No Seed Phrases**: Create a wallet using only your biometrics * **Device Security**: Your private keys stay secure in your device's hardware * **Instant Access**: Login with Face ID, Touch ID, or device PIN #### Gasless Transactions * **No Gas Fees**: Transactions are sponsored - users never see gas * **Batch Operations**: Multiple actions in a single transaction * **Instant Confirmation**: Fast transaction processing #### Web2-Like Experience * **Familiar UX**: Interface feels like traditional mobile banking * **No Wallet Extensions**: Works directly in any browser * **Mobile First**: Optimized for mobile devices ### ๐Ÿ› ๏ธ Developer Playground #### Code Examples Want to see the integration code? Check out our live examples: **๐Ÿ”ง React Integration** Complete React example with Wagmi integration [View React Guide โ†’](/react/getting-started) **๐Ÿ“ฆ SDK Examples**\ High-level SDK with simple client interface [View SDK Docs โ†’](/sdk) ### ๐Ÿข Client Success Stories #### TurinPool Integration *"HumanWallet is what we needed for our users & investors, making the process easy."* โ€” **Miguel Caballero**, CEO TurinLabs #### Nash21 Platform *"With HumanWallet we complete our 'Web2.5 platform': the feature that lets us to scale"* โ€” **Nico Barilari**, CEO Nash21 ### ๐ŸŽฌ Demo Walkthrough #### Step-by-Step Experience 1. **Visit the Demo**: Click on any demo link above 2. **Create Wallet**: Tap "Create Wallet" - no downloads needed 3. **Biometric Setup**: Use your device's biometrics to secure your wallet 4. **Try Transactions**: Send tokens, interact with contracts - all gasless 5. **See the Magic**: Experience Web3 that feels like Web2 #### What Makes It Special * **๐Ÿ”’ Invisible Security**: Enterprise-grade security that users never see * **๐Ÿ‘† Biometric Signing**: Sign like you do with banking apps * **๐Ÿ†“ Zero Gas Fees**: Users never worry about gas costs * **๐Ÿ“ฑ Mobile Native**: Perfect mobile experience *** *Ready to give your users a magical Web3 experience? Start with our demo and see the difference HumanWallet makes.* ## Frequently Asked Questions import { Callout, Button } from "vocs/components" **Frequently Asked Questions**: Get answers to the most common questions about HumanWallet integration, pricing, and functionality. ### ๐Ÿค” General Questions #### What is HumanWallet? HumanWallet is a SDK that can be integrated in any dApp. It lets users interact with the blockchain without the need to have a browser Web3 wallet: it combines ERC-4337 and ERC-7212 standards to create wallets that are very easy to use. **Key Benefits:** * Biometric authentication (Face ID, Touch ID, device PIN) * Gasless transactions through account abstraction * No seed phrases or browser extensions required * Mobile-first design for any device #### What makes HumanWallet different from other Web3 wallets? HumanWallet is based on **ERC-4337 + ERC-7212** real standards. It also incorporates 2FA to make some transactions safer. Besides, it's the only solution that combines both standards, letting users: * **Sign with biometry** like banking apps * **Custody private keys** in a 100% decentralized way * **No gas fees** for end users * **Web2-like UX** with Web3 power #### Can I install HumanWallet as a user? No. HumanWallet has no interface - it needs to be integrated into your preferred dApp. You'll use HumanWallet through third-party applications. **For Users:** Ask your favorite Web3 projects to integrate HumanWallet! **For Developers:** Check our [integration guides](/react/getting-started) to add HumanWallet to your dApp. #### Why does any Web3 project need HumanWallet? **Because it helps you grow.** The main pain point in any Web3 project is the complexity of attracting new users: โŒ **Traditional Web3:** * Managing MetaMask complexity * Explaining gas fees to users * Handling seed phrase security * Browser extension dependencies โœ… **With HumanWallet:** * Users feel like they're using a Web2 application * No Web3 complexity barriers * Biometric authentication like banking apps * Gasless, seamless experience **Result:** Higher user adoption and retention rates. ### ๐Ÿ”ง Technical Questions #### How do users configure HumanWallet and their biometry? When users use HumanWallet for the first time, they decide their preferred authentication methods: 1. **Device Selection**: Face ID, Touch ID, fingerprint, or device PIN 2. **Security Level**: Configure transaction-specific security (from simple click to 2FA) 3. **Native Integration**: HumanWallet integrates with the device's OS natively The setup is **completely automatic** - users just follow their device's standard biometric enrollment. #### Do I have a HumanWallet interface? No, HumanWallet is integrated through an SDK, so **you control your own UX** and design. This gives you complete flexibility over: * User interface design * Authentication flows * Transaction confirmations * Branding and styling You can test our [demo application](https://demo.humanwallet.io/) to see a reference implementation. #### What blockchains does HumanWallet support? HumanWallet is compatible with **any EVM-compatible blockchain**: * **Ethereum** - Mainnet and testnets * **Polygon** - MATIC network * **Base** - Coinbase's L2 * **Optimism** - Optimistic rollup * **Arbitrum** - Arbitrum One and Nova * **BNB Chain** - Binance Smart Chain * **zkSync** - Zero-knowledge rollup * **Avalanche** - C-Chain * **And any other EVM chain** Our JavaScript library is ready for any EVM project. ### ๐Ÿ›ก๏ธ Security & Compliance #### Is HumanWallet compliant with regulations? **Yes.** HumanWallet is a completely decentralized solution with **no custody**. **Key Compliance Features:** * Users keep their own private keys * Decentralized key management through Safe smart contracts * Guardian-based recovery system * No central authority holding funds * Full audit trail for transactions This architecture ensures compliance with most financial regulations worldwide. #### How secure is biometric authentication? HumanWallet leverages **device-level security**: * **Hardware Security Modules (HSM)**: Private keys stored in secure device enclaves * **WebAuthn Standard**: Uses W3C WebAuthn specification for secure authentication * **No Cloud Storage**: Biometric data never leaves your device * **Local Processing**: All authentication happens locally on the user's device This provides **bank-level security** while maintaining user control. #### What happens if I lose my device? HumanWallet includes **robust recovery mechanisms**: 1. **Guardian System**: Set up trusted contacts for account recovery 2. **Multi-Device Support**: Register multiple devices for the same account 3. **Social Recovery**: Community-based recovery options 4. **Backup Options**: Secure backup methods for enterprise users Recovery is secure, decentralized, and doesn't require seed phrases. ### ๐Ÿš€ Integration & Development #### How quickly can I integrate HumanWallet? **Integration time depends on your approach:** * **Basic Integration**: 1-2 hours with our React SDK * **Custom Implementation**: 1-2 days with core packages * **Full Customization**: 1-2 weeks for complex enterprise features Check our [integration guides](/react/getting-started) for step-by-step instructions. #### Do you provide integration support? **Yes!** We offer multiple support levels: * **Documentation**: Comprehensive guides and API references * **Community Support**: Discord community for developers * **Priority Support**: Available for Growth and Corporate plans * **Dedicated Support**: Enterprise customers get dedicated account managers #### Can I customize the user experience? **Absolutely!** HumanWallet gives you complete control over: * User interface and design * Authentication flow customization * Security level configuration (click to 2FA) * Transaction confirmation UX * Error handling and messaging * Branding and styling ### ๐Ÿ’ฌ Support & Community #### How can I get help or solve doubts? Multiple ways to get support: 1. **๐Ÿ“š Documentation**: Check our comprehensive guides first 2. **๐Ÿ’ฌ Discord Community**: [Join our Discord](https://discord.gg/j66zdmmxWq) for real-time help 3. **๐Ÿ“ง Contact Form**: Use the [contact form](https://www.humanwallet.io/#contact) for business inquiries 4. **๐Ÿ› GitHub Issues**: Report technical issues on our [GitHub repository](https://github.com/HumanWallet) #### Is there a community for developers? Yes! Join our growing community: * **Discord**: Active developer community with real-time support * **GitHub**: Open source contributions and issue tracking * **Twitter**: Latest updates and announcements * **Documentation**: Community-contributed examples and guides #### Can I contribute to HumanWallet? We welcome contributions! Ways to get involved: * **Code Contributions**: Submit PRs to our GitHub repositories * **Documentation**: Improve guides and add examples * **Community Support**: Help other developers in Discord * **Bug Reports**: Help us improve by reporting issues * **Feature Requests**: Suggest new features and improvements ### ๐ŸŽฏ Getting Started #### I'm convinced! How do I start? Great! Here's your path to HumanWallet integration: 1. **๐ŸŽฎ Try Demo**: Experience HumanWallet at [demo.humanwallet.io](https://demo.humanwallet.io/) 2. **๐Ÿ“š Read Docs**: Start with our [React integration guide](/react/getting-started) 3. **๐Ÿ’ฐ Choose Plan**: Review our [pricing plans](https://www.humanwallet.io/#pricing) 4. **๐Ÿš€ Integrate**: Follow step-by-step integration guides 5. **๐Ÿ’ฌ Get Support**: Join our [Discord community](https://discord.gg/j66zdmmxWq) #### Still have questions? **Can't find what you're looking for?** Our team is here to help with any questions about HumanWallet integration, pricing, or technical implementation.
## Authentication Methods The SDK provides simplified authentication methods that handle user registration, connection, session management, and logout through the `HumanWallet` class. ### Methods Overview * [`connect()`](#connect) - Connect with existing passkey * [`register()`](#register) - Create new passkey-based accounts * [`reconnect()`](#reconnect) - Restore previous sessions * [`disconnect()`](#disconnect) - End sessions and clear data * [`hasAccount()`](#hasaccount) - Check if user has existing account ### connect() Connects with an existing passkey and automatically sets up the session. #### Method Signature ```typescript async connect(): Promise
``` #### Return Value Returns the authenticated wallet address as a promise. #### Usage Example ```typescript import { HumanWallet } from "@humanwallet/sdk" import { sepolia } from "viem/chains" const client = new HumanWallet("your-project-id", sepolia) try { const address = await client.connect() console.log("Connected:", address) console.log("User is now authenticated:", !!client.address) // true console.log("Current address:", client.address) // same as returned address } catch (error) { if (error.message.includes("User rejected")) { console.log("User cancelled passkey authentication") } else if (error.message.includes("not found")) { console.log("No passkey found - user needs to register first") } else { console.error("Connection failed:", error) } } ``` #### What Happens During Connection 1. **Passkey Authentication**: Browser prompts user to authenticate with their passkey 2. **Account Recovery**: The smart contract wallet is recovered using the passkey 3. **Session Setup**: The client automatically configures the session for immediate use 4. **State Update**: `client.address` is set and user is ready to interact ### register() Creates a new passkey-based account for a user and automatically sets up the session. #### Method Signature ```typescript async register(username: string): Promise
``` #### Parameters * **`username`**: Unique identifier for the user (used for passkey creation) #### Return Value Returns the newly created wallet address as a promise. #### Usage Example ```typescript try { const address = await client.register("alice") console.log("Account created:", address) console.log("User is now authenticated:", !!client.address) // true console.log("Current address:", client.address) // same as returned address } catch (error) { if (error.message.includes("User rejected")) { console.log("User cancelled passkey creation") } else if (error.message.includes("already exists")) { console.log("Username already taken") } else { console.error("Registration failed:", error) } } ``` #### What Happens During Registration 1. **Passkey Creation**: Browser prompts user to create a passkey using biometrics/PIN 2. **Account Generation**: A smart contract wallet is created using the passkey 3. **Session Setup**: The client automatically configures the session for immediate use 4. **State Update**: `client.address` is set and user is ready to interact ### reconnect() Restores a previous session using stored authentication data, avoiding the need for passkey authentication. #### Method Signature ```typescript async reconnect(): Promise
``` #### Return Value Returns the restored wallet address if successful, or `null` if no session could be restored. #### Usage Example ```typescript // Try to restore previous session on app startup const restoredAddress = await client.reconnect() if (restoredAddress) { console.log("Session restored for:", restoredAddress) console.log("User is authenticated:", client.isConnected) // true // User can immediately perform operations await client.writeContract(contractParams) } else { console.log("No previous session found") console.log("User needs to login or register") // Show authentication UI await showAuthenticationDialog() } ``` #### When to Use Reconnect * **App Startup**: Check if user has an active session * **Page Refresh**: Restore authentication state * **Tab Switching**: Maintain authentication across browser tabs * **Background Tasks**: Keep authentication for scheduled operations ### disconnect() Clears stored authentication data and ends the current session. #### Method Signature ```typescript async disconnect(): Promise ``` #### Usage Example ```typescript await client.disconnect() console.log("User logged out") console.log("Is connected:", client.isConnected) // false console.log("Address:", client.address) // undefined // All stored authentication data is cleared // User will need to login/register again to perform operations ``` #### What Gets Cleared * Session keys and authentication tokens * Locally stored account data * Client state (address, connection status) * Any cached user information ### hasAccount() Checks if the user has an existing account stored locally without affecting the client's connection state. #### Method Signature ```typescript async hasAccount(): Promise ``` #### Usage Example ```typescript const userHasAccount = await client.hasAccount() if (userHasAccount) { // Try to reconnect or show login UI const address = await client.reconnect() if (!address) { // Session expired, show login form showLoginForm() } } else { // New user, show registration form showRegistrationForm() } ``` ### Complete Authentication Flows #### Registration Flow ```typescript class AuthenticationService { constructor(private client: ClientRepository) {} async registerNewUser(username: string): Promise<{ success: boolean; address?: string; error?: string }> { try { // Check if user already has an account const hasExisting = await this.client.hasAccount() if (hasExisting) { return { success: false, error: "User already has an account" } } // Register new user const address = await this.client.register(username) return { success: true, address } } catch (error) { return { success: false, error: this.handleAuthError(error as Error), } } } private handleAuthError(error: Error): string { if (error.message.includes("User rejected")) { return "Authentication cancelled by user" } else if (error.message.includes("not supported")) { return "Passkeys not supported in this browser" } else if (error.message.includes("already exists")) { return "Username already taken" } else { return "Authentication failed" } } } ``` #### Login Flow ```typescript class LoginService { constructor(private client: ClientRepository) {} async authenticateUser(username: string): Promise<{ success: boolean; address?: string; error?: string }> { try { // First check if user has an account const hasAccount = await this.client.hasAccount() if (!hasAccount) { return { success: false, error: "No account found. Please register first." } } // Try to reconnect first (faster than full login) let address = await this.client.reconnect() if (!address) { // Session expired, perform full login address = await this.client.login(username) } return { success: true, address } } catch (error) { return { success: false, error: this.handleLoginError(error as Error), } } } private handleLoginError(error: Error): string { if (error.message.includes("User rejected")) { return "Authentication cancelled" } else if (error.message.includes("not found")) { return "Account not found" } else { return "Login failed" } } } ``` #### Session Management ```typescript class SessionManager { constructor(private client: ClientRepository) {} async initializeSession(): Promise<{ authenticated: boolean; address?: string }> { try { // Try to restore existing session const address = await this.client.reconnect() if (address) { return { authenticated: true, address } } return { authenticated: false } } catch (error) { console.error("Session restoration failed:", error) return { authenticated: false } } } async logout(): Promise { try { await this.client.disconnect() } catch (error) { console.error("Logout failed:", error) } } get isAuthenticated(): boolean { return this.client.isConnected } get userAddress(): string | undefined { return this.client.address } } ``` ### React Hook Examples #### Basic Authentication Hook ```typescript import { useState, useEffect } from "react" import { ClientRepository } from "@humanwallet/sdk" export function useAuth(client: ClientRepository) { const [address, setAddress] = useState() const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState() // Initialize session on mount useEffect(() => { async function initSession() { try { const restoredAddress = await client.reconnect() if (restoredAddress) { setAddress(restoredAddress) } } catch (err) { console.error("Session restoration failed:", err) } finally { setIsLoading(false) } } initSession() }, [client]) const register = async (username: string) => { setIsLoading(true) setError(undefined) try { const newAddress = await client.register(username) setAddress(newAddress) return newAddress } catch (err) { const errorMessage = (err as Error).message setError(errorMessage) throw err } finally { setIsLoading(false) } } const login = async (username: string) => { setIsLoading(true) setError(undefined) try { const userAddress = await client.login(username) setAddress(userAddress) return userAddress } catch (err) { const errorMessage = (err as Error).message setError(errorMessage) throw err } finally { setIsLoading(false) } } const logout = async () => { setIsLoading(true) try { await client.disconnect() setAddress(undefined) } catch (err) { console.error("Logout failed:", err) } finally { setIsLoading(false) } } return { address, isAuthenticated: !!address, isLoading, error, register, login, logout, } } ``` #### Complete Authentication Component ```typescript import React, { useState } from 'react' import { useAuth } from './useAuth' interface AuthComponentProps { client: ClientRepository } export function AuthComponent({ client }: AuthComponentProps) { const { address, isAuthenticated, isLoading, error, register, login, logout } = useAuth(client) const [username, setUsername] = useState('') const [mode, setMode] = useState<'login' | 'register'>('login') const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!username.trim()) { alert('Please enter a username') return } try { if (mode === 'register') { await register(username) } else { await login(username) } } catch (err) { // Error is handled by the hook console.error('Authentication failed:', err) } } if (isLoading) { return
Loading...
} if (isAuthenticated) { return (

Welcome!

Address: {address}

) } return (

{mode === 'login' ? 'Login' : 'Register'}

setUsername(e.target.value)} />
{error &&

{error}

}
) } ``` ### Error Handling Best Practices #### Comprehensive Error Handler ```typescript export class AuthErrorHandler { static getErrorMessage(error: Error): string { const message = error.message.toLowerCase() if (message.includes("user rejected")) { return "Authentication was cancelled. Please try again." } if (message.includes("not supported")) { return "Passkeys are not supported in this browser. Please use a modern browser." } if (message.includes("already exists")) { return "This username is already taken. Please choose a different one." } if (message.includes("not found")) { return "Account not found. Please check your username or register a new account." } if (message.includes("account not created")) { return "Failed to create account. Please try again." } if (message.includes("network")) { return "Network error. Please check your connection and try again." } return "Authentication failed. Please try again." } static isRetryableError(error: Error): boolean { const message = error.message.toLowerCase() return message.includes("network") || message.includes("timeout") } static isCancelledError(error: Error): boolean { return error.message.toLowerCase().includes("user rejected") } } ``` ### Security Considerations * **Username Privacy**: Usernames are used for passkey creation but aren't stored on-chain * **Session Security**: Session keys are temporary and automatically expire * **Local Storage**: Authentication data is stored securely in browser storage * **Network Security**: All communications use HTTPS and secure protocols * **Passkey Security**: Private keys are stored in secure device enclaves and never exposed ## Contract Interactions The SDK provides simplified methods for interacting with smart contracts, including reading contract state and executing transactions. All write operations are gasless through account abstraction. ### Methods Overview * [`writeContract()`](#writecontract) - Execute single contract transactions * [`writeContracts()`](#writecontracts) - Execute multiple transactions in batch * [`readContract()`](#readcontract) - Read contract state * [`signTypedData()`](#signtypeddata) - Sign structured data ### writeContract() Executes a single contract function call as a gasless transaction. #### Method Signature ```typescript async writeContract(args: WriteContractParameters): Promise ``` #### Parameters ##### `args: WriteContractParameters` * **`address`**: Contract address (`0x${string}`) * **`abi`**: Contract ABI array * **`functionName`**: Function name to call * **`args`**: Array of function arguments (optional) * **`value`**: ETH value to send (optional, defaults to `0n`) #### Return Value Returns the user operation hash as a string. #### Usage Examples ##### Token Transfer ```typescript import { ClientRepository } from "@humanwallet/sdk" import { parseEther } from "viem" // Ensure user is authenticated if (!client.isConnected) { await client.login("alice") } // Execute token transfer const hash = await client.writeContract({ address: "0x...", // Token contract address abi: [ { name: "transfer", type: "function", inputs: [ { name: "to", type: "address" }, { name: "amount", type: "uint256" }, ], outputs: [{ name: "", type: "bool" }], stateMutability: "nonpayable", }, ], functionName: "transfer", args: ["0x742d35Cc6634C0532925a3b8D52ECC5c5e9fA008", parseEther("1")], // 1 token }) console.log("Transaction hash:", hash) // Wait for transaction to complete const receipt = await client.waitForUserOperation(hash) console.log("Transaction completed:", receipt.success) ``` ##### NFT Minting ```typescript const mintHash = await client.writeContract({ address: "0x...", // NFT contract address abi: nftAbi, functionName: "mint", args: [client.address, 1], // mint to self, token ID 1 }) console.log("NFT mint transaction:", mintHash) ``` ##### Contract with ETH Value ```typescript const donationHash = await client.writeContract({ address: "0x...", // Donation contract abi: donationAbi, functionName: "donate", value: parseEther("0.1"), // Send 0.1 ETH }) console.log("Donation transaction:", donationHash) ``` ### writeContracts() Executes multiple contract function calls in a single batch transaction. #### Method Signature ```typescript async writeContracts(contracts: WriteContractParameters[]): Promise ``` #### Parameters * **`contracts`**: Array of contract call parameters #### Return Value Returns the batch user operation hash as a string. #### Usage Examples ##### Token Approval and Swap ```typescript // Batch approve and swap operations const batchHash = await client.writeContracts([ { address: TOKEN_CONTRACT, // Token contract abi: erc20Abi, functionName: "approve", args: [DEX_CONTRACT, parseEther("1")], }, { address: DEX_CONTRACT, // DEX contract abi: dexAbi, functionName: "swapExactTokensForTokens", args: [ parseEther("1"), // amountIn parseEther("0.95"), // amountOutMin [TOKEN_A, TOKEN_B], // path client.address, // to Math.floor(Date.now() / 1000) + 600, // deadline (10 minutes) ], }, ]) console.log("Batch transaction hash:", batchHash) // Wait for batch completion const receipt = await client.waitForUserOperation(batchHash) if (receipt.success) { console.log("Token swap completed successfully!") } ``` ##### Multi-Contract Setup ```typescript // Setup multiple contracts in one transaction const setupHash = await client.writeContracts([ { address: REGISTRY_CONTRACT, abi: registryAbi, functionName: "register", args: ["alice"], }, { address: SETTINGS_CONTRACT, abi: settingsAbi, functionName: "setPreferences", args: [true, 100, "dark"], }, { address: TOKEN_CONTRACT, abi: erc20Abi, functionName: "approve", args: [SPENDING_CONTRACT, parseEther("1000")], }, ]) console.log("Setup transaction hash:", setupHash) ``` #### Benefits of Batching * **Atomic Operations**: All transactions succeed or fail together * **Gas Efficiency**: Single user operation instead of multiple * **Better UX**: One signature for multiple actions * **Reduced Latency**: Faster than sequential transactions ### readContract() Reads data from a smart contract without executing a transaction. #### Method Signature ```typescript async readContract(args: ReadContractParameters): Promise ``` #### Parameters ##### `args: ReadContractParameters` * **`address`**: Contract address * **`abi`**: Contract ABI array * **`functionName`**: Function name to call * **`args`**: Array of function arguments (optional) #### Usage Examples ##### Token Balance ```typescript const balance = await client.readContract({ address: "0x...", // Token contract abi: [ { name: "balanceOf", type: "function", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "", type: "uint256" }], stateMutability: "view", }, ], functionName: "balanceOf", args: [client.address], }) console.log("Token balance:", balance.toString()) ``` ##### Contract State ```typescript // Read multiple contract properties const [totalSupply, name, symbol, decimals] = await Promise.all([ client.readContract({ address: TOKEN_CONTRACT, abi: erc20Abi, functionName: "totalSupply", }), client.readContract({ address: TOKEN_CONTRACT, abi: erc20Abi, functionName: "name", }), client.readContract({ address: TOKEN_CONTRACT, abi: erc20Abi, functionName: "symbol", }), client.readContract({ address: TOKEN_CONTRACT, abi: erc20Abi, functionName: "decimals", }), ]) console.log(`${name} (${symbol})`) console.log(`Total Supply: ${totalSupply.toString()}`) console.log(`Decimals: ${decimals}`) ``` ##### Complex Data Structures ```typescript // Reading a struct from a staking contract const userInfo = await client.readContract({ address: STAKING_CONTRACT, abi: stakingAbi, functionName: "getUserInfo", args: [client.address], }) // userInfo is typically returned as an array const [stakedAmount, rewardDebt, lastStakeTime] = userInfo console.log("Staking info:", { stakedAmount: stakedAmount.toString(), rewardDebt: rewardDebt.toString(), lastStakeTime: new Date(Number(lastStakeTime) * 1000), }) ``` ### signTypedData() Signs structured data according to EIP-712 standard. #### Method Signature ```typescript async signTypedData(args: SignTypedDataParameters): Promise ``` #### Parameters ##### `args: SignTypedDataParameters` * **`domain`**: EIP-712 domain separator * **`types`**: Type definitions for the data * **`primaryType`**: The primary type being signed * **`message`**: The actual data to sign #### Usage Example ```typescript // Sign a permit message for gasless token approval const signature = await client.signTypedData({ domain: { name: "MyToken", version: "1", chainId: 11155111, verifyingContract: TOKEN_CONTRACT, }, types: { Permit: [ { name: "owner", type: "address" }, { name: "spender", type: "address" }, { name: "value", type: "uint256" }, { name: "nonce", type: "uint256" }, { name: "deadline", type: "uint256" }, ], }, primaryType: "Permit", message: { owner: client.address!, spender: SPENDER_CONTRACT, value: parseEther("1000"), nonce: 0n, deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour }, }) console.log("Signature:", signature) // Use signature for gasless permit transaction ``` ### High-Level Service Classes #### Token Service ```typescript class TokenService { constructor( private client: ClientRepository, private tokenAddress: `0x${string}`, private tokenAbi: any[], ) {} async getBalance(address?: `0x${string}`): Promise { const account = address || this.client.address if (!account) throw new Error("No address provided") return await this.client.readContract({ address: this.tokenAddress, abi: this.tokenAbi, functionName: "balanceOf", args: [account], }) } async getTokenInfo() { const [name, symbol, decimals, totalSupply] = await Promise.all([ this.client.readContract({ address: this.tokenAddress, abi: this.tokenAbi, functionName: "name", }), this.client.readContract({ address: this.tokenAddress, abi: this.tokenAbi, functionName: "symbol", }), this.client.readContract({ address: this.tokenAddress, abi: this.tokenAbi, functionName: "decimals", }), this.client.readContract({ address: this.tokenAddress, abi: this.tokenAbi, functionName: "totalSupply", }), ]) return { name, symbol, decimals, totalSupply } } async transfer(to: `0x${string}`, amount: bigint): Promise { if (!this.client.isConnected) { throw new Error("User not authenticated") } return await this.client.writeContract({ address: this.tokenAddress, abi: this.tokenAbi, functionName: "transfer", args: [to, amount], }) } async approve(spender: `0x${string}`, amount: bigint): Promise { if (!this.client.isConnected) { throw new Error("User not authenticated") } return await this.client.writeContract({ address: this.tokenAddress, abi: this.tokenAbi, functionName: "approve", args: [spender, amount], }) } async getAllowance(spender: `0x${string}`): Promise { if (!this.client.address) { throw new Error("User not authenticated") } return await this.client.readContract({ address: this.tokenAddress, abi: this.tokenAbi, functionName: "allowance", args: [this.client.address, spender], }) } } ``` #### DeFi Service ```typescript class DeFiService { constructor( private client: ClientRepository, private dexAddress: `0x${string}`, private dexAbi: any[], ) {} async swapTokens( tokenA: `0x${string}`, tokenB: `0x${string}`, amountIn: bigint, minAmountOut: bigint, ): Promise { if (!this.client.isConnected) { throw new Error("User not authenticated") } // Check current allowance const tokenService = new TokenService(this.client, tokenA, erc20Abi) const currentAllowance = await tokenService.getAllowance(this.dexAddress) const operations: WriteContractParameters[] = [] // Add approval if needed if (currentAllowance < amountIn) { operations.push({ address: tokenA, abi: erc20Abi, functionName: "approve", args: [this.dexAddress, amountIn], }) } // Add swap operation operations.push({ address: this.dexAddress, abi: this.dexAbi, functionName: "swapExactTokensForTokens", args: [ amountIn, minAmountOut, [tokenA, tokenB], this.client.address, BigInt(Math.floor(Date.now() / 1000) + 600), // 10 minute deadline ], }) // Execute as batch if approval needed, otherwise single transaction if (operations.length > 1) { return await this.client.writeContracts(operations) } else { return await this.client.writeContract(operations[0]) } } async addLiquidity( tokenA: `0x${string}`, tokenB: `0x${string}`, amountA: bigint, amountB: bigint, minAmountA: bigint, minAmountB: bigint, ): Promise { // Approve both tokens and add liquidity in batch return await this.client.writeContracts([ { address: tokenA, abi: erc20Abi, functionName: "approve", args: [this.dexAddress, amountA], }, { address: tokenB, abi: erc20Abi, functionName: "approve", args: [this.dexAddress, amountB], }, { address: this.dexAddress, abi: this.dexAbi, functionName: "addLiquidity", args: [ tokenA, tokenB, amountA, amountB, minAmountA, minAmountB, this.client.address, BigInt(Math.floor(Date.now() / 1000) + 600), ], }, ]) } } ``` ### Error Handling #### Transaction Error Handler ```typescript export class ContractErrorHandler { static getErrorMessage(error: Error): string { const message = error.message.toLowerCase() if (message.includes("user not authenticated")) { return "Please login to perform transactions" } if (message.includes("insufficient funds")) { return "Insufficient balance for transaction" } if (message.includes("execution reverted")) { return "Contract execution failed" } if (message.includes("user rejected")) { return "Transaction cancelled by user" } if (message.includes("network")) { return "Network error - please try again" } return "Transaction failed" } static isRetryableError(error: Error): boolean { const message = error.message.toLowerCase() return message.includes("network") || message.includes("timeout") || message.includes("connection") } } ``` #### Transaction with Error Handling ```typescript async function executeTransactionWithRetry( client: ClientRepository, contractParams: WriteContractParameters, maxRetries: number = 3, ): Promise { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { if (!client.isConnected) { throw new Error("User not authenticated") } const hash = await client.writeContract(contractParams) console.log(`Transaction successful on attempt ${attempt}:`, hash) return hash } catch (error) { const errorMessage = ContractErrorHandler.getErrorMessage(error as Error) console.error(`Attempt ${attempt} failed:`, errorMessage) if (attempt === maxRetries || !ContractErrorHandler.isRetryableError(error as Error)) { throw new Error(`Transaction failed after ${attempt} attempts: ${errorMessage}`) } // Wait before retry (exponential backoff) await new Promise((resolve) => setTimeout(resolve, 1000 * attempt)) } } throw new Error("Transaction failed after all retry attempts") } ``` ### React Integration #### Contract Hook ```typescript import { useState, useCallback } from "react" import { ClientRepository, WriteContractParameters } from "@humanwallet/sdk" export function useContract(client: ClientRepository) { const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState() const writeContract = useCallback( async (params: WriteContractParameters) => { if (!client.isConnected) { throw new Error("User not authenticated") } setIsLoading(true) setError(undefined) try { const hash = await client.writeContract(params) return hash } catch (err) { const errorMessage = ContractErrorHandler.getErrorMessage(err as Error) setError(errorMessage) throw err } finally { setIsLoading(false) } }, [client], ) const readContract = useCallback( async (params: any) => { setError(undefined) try { return await client.readContract(params) } catch (err) { const errorMessage = (err as Error).message setError(errorMessage) throw err } }, [client], ) return { writeContract, readContract, isLoading, error, } } ``` ### Best Practices #### 1. Always Check Authentication ```typescript // Good: Check authentication before contract interactions if (!client.isConnected) { await client.login("username") } const hash = await client.writeContract(params) ``` #### 2. Use Batch Transactions When Possible ```typescript // Good: Batch related operations const batchHash = await client.writeContracts([approveOperation, swapOperation]) // Avoid: Sequential transactions // const approveHash = await client.writeContract(approveOperation) // const swapHash = await client.writeContract(swapOperation) ``` #### 3. Handle Errors Gracefully ```typescript try { const hash = await client.writeContract(params) await client.waitForUserOperation(hash) showSuccessMessage("Transaction completed!") } catch (error) { const userFriendlyMessage = ContractErrorHandler.getErrorMessage(error) showErrorMessage(userFriendlyMessage) } ``` #### 4. Use Type-Safe Contract Interfaces ```typescript interface ERC20Contract { address: `0x${string}` abi: typeof ERC20_ABI } class TypedTokenService { constructor( private client: ClientRepository, private contract: ERC20Contract, ) {} async transfer(to: `0x${string}`, amount: bigint): Promise { return this.client.writeContract({ address: this.contract.address, abi: this.contract.abi, functionName: "transfer", args: [to, amount], }) } } ``` ## SDK Package (@humanwallet/sdk)
HumanWallet SDK @humanwallet/sdk
The `@humanwallet/sdk` package provides a high-level, class-based client interface for HumanWallet. Built on top of `@humanwallet/core`, it offers a simplified API that makes it easy to integrate passkey-based authentication and account abstraction into your Web3 applications. ### Overview The SDK abstracts away the complexity of the core functions by providing: * **Simple Client Interface**: Easy-to-use class-based API for all wallet operations * **Automatic State Management**: Built-in session handling and account state tracking * **Streamlined Configuration**: Minimal setup required compared to core functions * **Comprehensive Operations**: Support for authentication, contract interactions, and transaction management * **TypeScript First**: Excellent developer experience with full type safety ### When to Use SDK vs Core #### Use SDK When: * You want a simple, high-level interface * You're building a new application from scratch * You prefer class-based APIs * You want automatic state management * You need quick prototyping capabilities #### Use Core When: * You need fine-grained control over the authentication flow * You're integrating into existing state management systems * You want to implement custom session handling * You're building a framework or library on top of HumanWallet ### Installation ```bash npm install @humanwallet/sdk ``` #### Browser Compatibility When using the SDK in browser environments, you need to configure Buffer: ```bash npm install buffer ``` Create a polyfills file: ```typescript // src/polyfills.ts import { Buffer } from "buffer" // Make Buffer available globally window.Buffer = Buffer ``` Import it at the beginning of your main entry file: ```typescript import "./polyfills" // ... rest of your imports ``` And add to your `index.html` ``: ```html ``` For more details, see the [React Integration Guide](/react/getting-started). ### Quick Start ```typescript import { HumanWallet } from "@humanwallet/sdk" import { sepolia } from "viem/chains" // Initialize the client const client = new HumanWallet( "your-project-id", // projectId sepolia, // chain ) // Connect with existing passkey const address = await client.connect() console.log("Connected:", address) // Or register a new user // const address = await client.register("alice") // Check connection status if (client.address) { console.log("User address:", client.address) // Execute a contract transaction const hash = await client.writeContract({ address: "0x...", abi: contractAbi, functionName: "transfer", args: ["0x742d35Cc6634C0532925a3b8D52ECC5c5e9fA008", BigInt("1000000000000000000")], }) console.log("Transaction hash:", hash) } // Disconnect when done await client.disconnect() ``` ### HumanWallet Class The main SDK interface is the `HumanWallet` class that encapsulates all HumanWallet functionality. #### Constructor ```typescript new HumanWallet( projectId: string, chain: Chain ) ``` ##### Parameters * **`projectId`**: Your ZeroDev project ID for accessing bundler and paymaster services * **`chain`**: Viem chain configuration object (e.g., `sepolia`, `mainnet`) ##### Example ```typescript import { sepolia, polygon, mainnet } from "viem/chains" // Sepolia testnet client const sepoliaClient = new HumanWallet("your-project-id", sepolia) // Mainnet client const mainnetClient = new HumanWallet("your-project-id", mainnet) ``` #### Properties ##### `address: Address | undefined` The current user's wallet address. Returns `undefined` if no user is authenticated. ```typescript if (client.address) { console.log("Current address:", client.address) } else { console.log("No user authenticated") } ``` ##### `chain: Chain` The current blockchain network the client is connected to. ```typescript console.log("Current chain:", client.chain.name) // "Sepolia" console.log("Chain ID:", client.chain.id) // 11155111 ``` ### API Reference #### [Authentication Methods](/sdk/authentication) * `connect()` - Connect with existing passkey * `register()` - Create new passkey-based accounts * `reconnect()` - Restore previous sessions * `disconnect()` - End sessions and clear data * `hasAccount()` - Check if user has existing account #### [Contract Interaction Methods](/sdk/contracts) * `writeContract()` - Execute contract transactions * `writeContracts()` - Execute batch transactions * `readContract()` - Read contract state * `signTypedData()` - Sign structured data #### [Transaction Management Methods](/sdk/transactions) * `waitForTransaction()` - Wait for transaction confirmation * `waitForUserOperation()` - Wait for user operation completion ### Usage Patterns #### Single Page Application (SPA) ```typescript class WalletService { private client: HumanWallet constructor() { this.client = new HumanWallet(process.env.VITE_PROJECT_ID!, sepolia) } async initializeSession() { // Try to restore previous session const address = await this.client.reconnect() if (address) { console.log("Session restored for:", address) return true } return false } async authenticateUser(username: string, isNewUser: boolean = false) { if (isNewUser) { return await this.client.register(username) } else { return await this.client.connect() } } async transferTokens(to: string, amount: bigint) { if (!this.client.address) { throw new Error("User not authenticated") } return await this.client.writeContract({ address: TOKEN_CONTRACT_ADDRESS, abi: ERC20_ABI, functionName: "transfer", args: [to, amount], }) } async signOut() { await this.client.disconnect() } get userAddress() { return this.client.address } get isAuthenticated() { return !!this.client.address } } ``` #### React Integration ```typescript import { createContext, useContext, useState, useEffect } from 'react' import { HumanWallet } from '@humanwallet/sdk' const WalletContext = createContext<{ client: HumanWallet address?: string isConnected: boolean connect: () => Promise register: (username: string) => Promise disconnect: () => Promise }>() export function WalletProvider({ children }: { children: React.ReactNode }) { const [client] = useState(() => new HumanWallet( process.env.VITE_PROJECT_ID!, sepolia )) const [address, setAddress] = useState() const [isConnected, setIsConnected] = useState(false) useEffect(() => { // Try to restore session on mount client.reconnect().then(addr => { if (addr) { setAddress(addr) setIsConnected(true) } }) }, [client]) const connect = async () => { const addr = await client.connect() setAddress(addr) setIsConnected(true) return addr } const register = async (username: string) => { const addr = await client.register(username) setAddress(addr) setIsConnected(true) return addr } const disconnect = async () => { await client.disconnect() setAddress(undefined) setIsConnected(false) } return ( {children} ) } export const useWallet = () => { const context = useContext(WalletContext) if (!context) { throw new Error('useWallet must be used within WalletProvider') } return context } ``` ### Error Handling The SDK methods can throw various errors that should be handled appropriately: ```typescript try { const address = await client.register("username") console.log("Registration successful:", address) } catch (error) { if (error.message.includes("User rejected")) { console.log("User cancelled passkey creation") } else if (error.message.includes("not supported")) { console.log("Passkeys not supported in this browser") } else if (error.message.includes("already exists")) { console.log("Username already taken") } else { console.error("Registration failed:", error) } } ``` #### Common Error Patterns ```typescript class ErrorHandler { static handleAuthError(error: Error) { if (error.message.includes("User rejected")) { return "Authentication cancelled by user" } else if (error.message.includes("not supported")) { return "Passkeys not supported in this browser" } else if (error.message.includes("Account not created")) { return "Failed to create account" } else { return "Authentication failed" } } static handleTransactionError(error: Error) { if (error.message.includes("User not authenticated")) { return "Please login to perform transactions" } else if (error.message.includes("insufficient funds")) { return "Insufficient balance for transaction" } else if (error.message.includes("execution reverted")) { return "Contract execution failed" } else { return "Transaction failed" } } } ``` ### Best Practices #### 1. Environment Configuration ```typescript // config.ts const getSDKConfig = () => { const requiredEnvVars = ["VITE_PROJECT_ID"] for (const envVar of requiredEnvVars) { if (!process.env[envVar]) { throw new Error(`Missing required environment variable: ${envVar}`) } } return { projectId: process.env.VITE_PROJECT_ID!, } } export const createWalletClient = (chain: Chain) => { const config = getSDKConfig() return new HumanWallet(config.projectId, chain) } ``` #### 2. Session Management ```typescript // Always try to reconnect on app startup async function initializeApp() { const client = createWalletClient(sepolia) try { const address = await client.reconnect() if (address) { console.log("User session restored:", address) return { client, address } } } catch (error) { console.log("No previous session found") } return { client, address: null } } ``` #### 3. Type Safety ```typescript // Define your contract interfaces interface TokenContract { address: `0x${string}` abi: typeof ERC20_ABI } class TokenService { constructor( private client: ClientRepository, private tokenContract: TokenContract, ) {} async getBalance(): Promise { return await this.client.readContract({ address: this.tokenContract.address, abi: this.tokenContract.abi, functionName: "balanceOf", args: [this.client.address!], }) } async transfer(to: `0x${string}`, amount: bigint): Promise { return await this.client.writeContract({ address: this.tokenContract.address, abi: this.tokenContract.abi, functionName: "transfer", args: [to, amount], }) } } ``` ### Migration from Core If you're currently using `@humanwallet/core`, migrating to the SDK is straightforward: #### Before (Core) ```typescript import { createConfig, register, login, writeContract } from "@humanwallet/core" const config = createConfig({ passkeyUrl: "https://passkeys.example.com/api/v3/your-project-id", bundlerRpc: "https://rpc.example.com/api/v3/your-project-id/chain/11155111", paymasterRpc: "https://rpc.example.com/api/v3/your-project-id/chain/11155111", chain: sepolia, }) const { sessionKeyAccount, kernelClient } = await register("username", config) // Update config with authenticated clients const authenticatedConfig = { ...config, sessionKeyAccount, kernelClient } const hash = await writeContract(authenticatedConfig, contractParams) ``` #### After (SDK) ```typescript import { HumanWallet } from "@humanwallet/sdk" const client = new HumanWallet("your-project-id", sepolia) await client.register("username") const hash = await client.writeContract(contractParams) ``` ### Next Steps * [Authentication Methods](/sdk/authentication) - Learn about user authentication flows * [Contract Interactions](/sdk/contracts) - Execute and read smart contract operations * [Transaction Management](/sdk/transactions) - Monitor and manage blockchain transactions * [React Integration Examples](/react/getting-started) - See SDK in action with React ## Transaction Management The SDK provides simplified methods for monitoring and managing blockchain transactions and user operations. These methods help you track transaction status and wait for confirmations with built-in error handling. ### Methods Overview * [`waitForTransaction()`](#waitfortransaction) - Wait for regular transaction confirmation * [`waitForUserOperation()`](#waitforuseroperation) - Wait for user operation completion ### waitForTransaction() Waits for a regular blockchain transaction to be confirmed and included in a block. #### Method Signature ```typescript async waitForTransaction(transactionHash: Hash): Promise ``` #### Parameters * **`transactionHash`**: Transaction hash to wait for (as returned by external transactions) #### Return Value Returns a `TransactionReceipt` object containing transaction details. #### Usage Example ```typescript import { ClientRepository } from "@humanwallet/sdk" // For external transactions (not from HumanWallet) const externalTxHash = "0x..." // From external source try { const receipt = await client.waitForTransaction(externalTxHash) console.log("Transaction confirmed!") console.log("Block number:", receipt.blockNumber) console.log("Gas used:", receipt.gasUsed.toString()) console.log("Status:", receipt.status) // 'success' or 'reverted' } catch (error) { console.error("Transaction failed or timed out:", error) } ``` #### When to Use This method is primarily for monitoring external transactions that weren't created through HumanWallet's `writeContract` or `writeContracts` methods. For HumanWallet transactions, use `waitForUserOperation()` instead. ### waitForUserOperation() Waits for a HumanWallet user operation (gasless transaction) to be completed. This is the primary method for monitoring transactions created through the SDK. #### Method Signature ```typescript async waitForUserOperation(userOperationHash: UserOperationHash): Promise ``` #### Parameters * **`userOperationHash`**: User operation hash returned by `writeContract()` or `writeContracts()` #### Return Value Returns a `UserOperationReceipt` object containing: * `transactionHash`: The actual on-chain transaction hash * `blockNumber`: Block number where it was included * `success`: Whether the operation succeeded * `gasUsed`: Gas consumed by the operation * `logs`: Event logs emitted #### Usage Example ```typescript // Execute a transaction and wait for completion try { // Execute transaction const userOpHash = await client.writeContract({ address: "0x...", abi: contractAbi, functionName: "mint", args: [client.address, 1], }) console.log("Transaction submitted:", userOpHash) // Wait for completion const receipt = await client.waitForUserOperation(userOpHash) if (receipt.success) { console.log("โœ… Transaction successful!") console.log("On-chain hash:", receipt.transactionHash) console.log("Block number:", receipt.blockNumber) console.log("Gas used:", receipt.gasUsed.toString()) } else { console.log("โŒ Transaction failed") } } catch (error) { console.error("Transaction error:", error) } ``` ### Complete Transaction Workflows #### Simple Transaction Flow ```typescript class TransactionService { constructor(private client: ClientRepository) {} async executeTransaction(contractParams: WriteContractParameters): Promise<{ success: boolean transactionHash?: string error?: string }> { try { // Ensure user is authenticated if (!this.client.isConnected) { throw new Error("User not authenticated") } console.log("Submitting transaction...") const userOpHash = await this.client.writeContract(contractParams) console.log("Waiting for confirmation...") const receipt = await this.client.waitForUserOperation(userOpHash) if (receipt.success) { return { success: true, transactionHash: receipt.transactionHash, } } else { return { success: false, error: "Transaction reverted", } } } catch (error) { return { success: false, error: (error as Error).message, } } } } ``` #### Batch Transaction Monitoring ```typescript class BatchTransactionService { constructor(private client: ClientRepository) {} async executeBatch(operations: WriteContractParameters[]): Promise<{ success: boolean transactionHash?: string individualResults?: any[] error?: string }> { try { console.log(`Executing batch of ${operations.length} operations...`) const batchHash = await this.client.writeContracts(operations) console.log("Batch submitted:", batchHash) const receipt = await this.client.waitForUserOperation(batchHash) if (receipt.success) { // Parse logs to get individual operation results const individualResults = this.parseOperationLogs(receipt.logs) return { success: true, transactionHash: receipt.transactionHash, individualResults, } } else { return { success: false, error: "One or more operations in the batch failed", } } } catch (error) { return { success: false, error: (error as Error).message, } } } private parseOperationLogs(logs: any[]): any[] { // Parse logs to extract results from individual operations return logs.map((log, index) => ({ operationIndex: index, eventData: log, success: true, // Determine based on log data })) } } ``` #### Transaction with Progress Tracking ```typescript interface TransactionProgress { status: "pending" | "confirmed" | "failed" userOpHash?: string transactionHash?: string receipt?: any error?: string } class ProgressTrackingService { constructor(private client: ClientRepository) {} async executeWithProgress( contractParams: WriteContractParameters, onProgress?: (progress: TransactionProgress) => void, ): Promise { try { // Initial state onProgress?.({ status: "pending" }) // Submit transaction const userOpHash = await this.client.writeContract(contractParams) onProgress?.({ status: "pending", userOpHash, }) // Wait for completion const receipt = await this.client.waitForUserOperation(userOpHash) const finalProgress: TransactionProgress = { status: receipt.success ? "confirmed" : "failed", userOpHash, transactionHash: receipt.transactionHash, receipt, } onProgress?.(finalProgress) return finalProgress } catch (error) { const errorProgress: TransactionProgress = { status: "failed", error: (error as Error).message, } onProgress?.(errorProgress) return errorProgress } } } ``` ### React Integration #### Transaction Hook ```typescript import { useState, useCallback } from "react" import { ClientRepository, WriteContractParameters } from "@humanwallet/sdk" interface TransactionState { isLoading: boolean userOpHash?: string transactionHash?: string success?: boolean error?: string } export function useTransaction(client: ClientRepository) { const [state, setState] = useState({ isLoading: false }) const executeTransaction = useCallback( async (params: WriteContractParameters) => { setState({ isLoading: true }) try { // Submit transaction const userOpHash = await client.writeContract(params) setState((prev) => ({ ...prev, userOpHash })) // Wait for completion const receipt = await client.waitForUserOperation(userOpHash) setState({ isLoading: false, userOpHash, transactionHash: receipt.transactionHash, success: receipt.success, error: receipt.success ? undefined : "Transaction reverted", }) return receipt } catch (error) { setState({ isLoading: false, error: (error as Error).message, success: false, }) throw error } }, [client], ) const executeBatch = useCallback( async (operations: WriteContractParameters[]) => { setState({ isLoading: true }) try { const batchHash = await client.writeContracts(operations) setState((prev) => ({ ...prev, userOpHash: batchHash })) const receipt = await client.waitForUserOperation(batchHash) setState({ isLoading: false, userOpHash: batchHash, transactionHash: receipt.transactionHash, success: receipt.success, error: receipt.success ? undefined : "Batch transaction failed", }) return receipt } catch (error) { setState({ isLoading: false, error: (error as Error).message, success: false, }) throw error } }, [client], ) const reset = useCallback(() => { setState({ isLoading: false }) }, []) return { ...state, executeTransaction, executeBatch, reset, } } ``` #### Transaction Status Component ```typescript import React from 'react' interface TransactionStatusProps { isLoading: boolean userOpHash?: string transactionHash?: string success?: boolean error?: string } export function TransactionStatus({ isLoading, userOpHash, transactionHash, success, error }: TransactionStatusProps) { if (isLoading) { return (

Transaction pending...

{userOpHash && (

User Op: {userOpHash.slice(0, 10)}...

)}
) } if (success === true) { return (
โœ…

Transaction confirmed!

{transactionHash && (

Hash: {transactionHash.slice(0, 10)}... View on Etherscan

)}
) } if (success === false || error) { return (
โŒ

Transaction failed

{error &&

{error}

}
) } return null } ``` #### Complete Transaction Flow Component ```typescript import React, { useState } from 'react' import { useTransaction } from './useTransaction' import { TransactionStatus } from './TransactionStatus' import { ClientRepository } from '@humanwallet/sdk' interface TokenTransferProps { client: ClientRepository tokenAddress: string tokenAbi: any[] } export function TokenTransfer({ client, tokenAddress, tokenAbi }: TokenTransferProps) { const [recipient, setRecipient] = useState('') const [amount, setAmount] = useState('') const transaction = useTransaction(client) const handleTransfer = async (e: React.FormEvent) => { e.preventDefault() if (!recipient || !amount) { alert('Please fill all fields') return } try { await transaction.executeTransaction({ address: tokenAddress, abi: tokenAbi, functionName: 'transfer', args: [recipient, BigInt(amount)] }) } catch (error) { console.error('Transfer failed:', error) } } return (

Token Transfer

setRecipient(e.target.value)} placeholder="0x..." disabled={transaction.isLoading} />
setAmount(e.target.value)} placeholder="1000000000000000000" disabled={transaction.isLoading} />
) } ``` ### Error Handling and Retries #### Advanced Error Handler ```typescript export class TransactionErrorHandler { static getErrorMessage(error: Error): string { const message = error.message.toLowerCase() if (message.includes("user operation failed")) { return "Transaction execution failed on the blockchain" } if (message.includes("timeout")) { return "Transaction timed out - it may still be processing" } if (message.includes("insufficient funds")) { return "Insufficient balance for transaction" } if (message.includes("user rejected")) { return "Transaction cancelled by user" } if (message.includes("network")) { return "Network error - please try again" } return "Transaction failed" } static isRetryableError(error: Error): boolean { const message = error.message.toLowerCase() return message.includes("network") || message.includes("timeout") || message.includes("connection failed") } } ``` #### Transaction with Retry Logic ```typescript class RobustTransactionService { constructor(private client: ClientRepository) {} async executeWithRetry( contractParams: WriteContractParameters, maxRetries: number = 3, timeout: number = 60000, ): Promise { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { console.log(`Transaction attempt ${attempt}/${maxRetries}`) const userOpHash = await this.client.writeContract(contractParams) // Wait with custom timeout const receipt = await Promise.race([ this.client.waitForUserOperation(userOpHash), new Promise((_, reject) => setTimeout(() => reject(new Error("Transaction timeout")), timeout)), ]) console.log(`Transaction successful on attempt ${attempt}`) return receipt } catch (error) { console.error(`Attempt ${attempt} failed:`, error) if (attempt === maxRetries || !TransactionErrorHandler.isRetryableError(error as Error)) { throw error } // Exponential backoff const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000) await new Promise((resolve) => setTimeout(resolve, delay)) } } } } ``` ### Monitoring and Analytics #### Transaction Analytics ```typescript class TransactionAnalytics { private metrics: { totalTransactions: number successfulTransactions: number failedTransactions: number averageConfirmationTime: number gasUsed: bigint } = { totalTransactions: 0, successfulTransactions: 0, failedTransactions: 0, averageConfirmationTime: 0, gasUsed: 0n, } async trackTransaction(contractParams: WriteContractParameters, client: ClientRepository): Promise { const startTime = Date.now() this.metrics.totalTransactions++ try { const userOpHash = await client.writeContract(contractParams) const receipt = await client.waitForUserOperation(userOpHash) const confirmationTime = Date.now() - startTime if (receipt.success) { this.metrics.successfulTransactions++ this.metrics.gasUsed += receipt.gasUsed // Update average confirmation time this.metrics.averageConfirmationTime = (this.metrics.averageConfirmationTime * (this.metrics.successfulTransactions - 1) + confirmationTime) / this.metrics.successfulTransactions } else { this.metrics.failedTransactions++ } console.log("Transaction metrics:", this.getMetrics()) return receipt } catch (error) { this.metrics.failedTransactions++ throw error } } getMetrics() { return { ...this.metrics, successRate: this.metrics.successfulTransactions / this.metrics.totalTransactions, averageGasUsed: this.metrics.successfulTransactions > 0 ? this.metrics.gasUsed / BigInt(this.metrics.successfulTransactions) : 0n, } } } ``` ### Best Practices #### 1. Always Wait for User Operations ```typescript // Good: Wait for completion const userOpHash = await client.writeContract(params) const receipt = await client.waitForUserOperation(userOpHash) if (receipt.success) { showSuccessMessage("Transaction completed!") } ``` #### 2. Provide User Feedback ```typescript // Good: Show progress to user console.log("Submitting transaction...") const userOpHash = await client.writeContract(params) console.log("Waiting for confirmation...") const receipt = await client.waitForUserOperation(userOpHash) console.log("Transaction completed!") ``` #### 3. Handle Errors Gracefully ```typescript try { const receipt = await executeTransaction() } catch (error) { const userMessage = TransactionErrorHandler.getErrorMessage(error) showUserError(userMessage) // Log technical details for debugging console.error("Transaction failed:", error) } ``` #### 4. Use Appropriate Timeouts ```typescript // For simple transactions const receipt = await client.waitForUserOperation(hash) // Default timeout // For complex batch operations, consider longer timeouts if available // (Note: SDK may not expose timeout options, but this is the pattern) ``` import { Button, Callout } from "vocs/components" ## Connect Wallet Guide
Enable users to connect their existing HumanWallet using secure passkey authentication
๐Ÿ” Passkey Auth โšก No Extensions ๐Ÿš€ One-Click Connect
This guide demonstrates how to implement wallet connection functionality in your React application using HumanWallet's passkey-based authentication. **Already Using Wagmi?** Great news! HumanWallet works seamlessly with your existing `useConnect`, `useAccount`, and all other Wagmi hooks. No changes to your existing code needed! ๐Ÿš€ ### Overview HumanWallet connection allows users to securely access their existing wallets using WebAuthn passkeys. This approach eliminates the need for browser extensions or seed phrases while maintaining the highest security standards. #### Drop-in Compatibility with Wagmi If you're already using Wagmi in your application, HumanWallet integrates perfectly: * โœ… **Same hooks**: Use `useConnect`, `useAccount`, `useBalance` exactly as before * โœ… **Same patterns**: All your existing Wagmi patterns work unchanged * โœ… **Same TypeScript**: Full type safety with your existing setup * โœ… **Same performance**: Built on the same Wagmi/Viem foundation you trust #### Key Features * **Passkey Authentication**: Uses WebAuthn for secure, passwordless login * **No Browser Extensions**: Works directly in the browser without additional software * **Automatic Detection**: Distinguishes between existing and new wallet scenarios * **Error Handling**: Comprehensive error states and user feedback **Prerequisites**: Make sure you've completed the [Getting Started](/react/getting-started) guide before implementing wallet connection. ### Core Hook Usage #### Basic Connection Hook The foundation of wallet connection is the `useConnect` hook from Wagmi: ```tsx [hooks/useWalletConnection.ts] import { useConnect } from "wagmi" import type { Connector } from "wagmi" export function useWalletConnection() { const { connectors, connect, isPending, error } = useConnect() const connectWallet = (connector: Connector) => { connect({ connector }) } const getHumanWalletConnector = () => { return connectors.find((connector) => connector.name === "HumanWallet") } return { connectors, connectWallet, getHumanWalletConnector, isPending, error, } } ``` #### Continue Learning
**Next Steps**: Use these hooks and utilities to build your own UI components that fit your application's design system and user experience requirements. import { Button, Callout } from "vocs/components" ## Create Wallet Guide
Enable new users to create secure HumanWallet accounts with passkey authentication
๐Ÿ‘ค New Users ๐Ÿ” Passkey Creation โšก Instant Setup
This guide shows how to implement wallet creation functionality for new users who want to create a HumanWallet account using secure passkey authentication. **Wagmi Users**: Perfect! Wallet creation works with your existing Wagmi hooks. Just use the same `useConnect` hook with an additional parameter - your existing code stays the same! ๐Ÿ’ช ### Overview HumanWallet creation allows new users to generate a secure wallet account using WebAuthn passkeys. This process eliminates traditional password requirements while providing enterprise-grade security through biometric authentication. #### Seamless Integration with Existing Wagmi Code If you already have wallet connection implemented with Wagmi, adding wallet creation is trivial: * โœ… **Same `useConnect` hook**: Just add a `forceCreate` parameter * โœ… **Same error handling**: All your existing error states work * โœ… **Same UI patterns**: Reuse your connection components * โœ… **Same state management**: Works with your current loading states #### Key Features * **Passkey Registration**: Creates secure WebAuthn credentials during wallet setup * **Instant Access**: Immediate wallet availability after creation * **No Seed Phrases**: Eliminates the need for users to manage complex seed phrases * **Biometric Security**: Uses device biometrics or security keys for authentication **Prerequisites**: Ensure you have completed the [Getting Started](/react/getting-started) guide and understand [wallet connection](/react/connect-wallet) concepts. ### Core Hook Usage #### Basic Wallet Creation Hook The foundation of wallet creation uses the extended `useConnect` hook with force creation: ```tsx [hooks/useWalletCreation.ts] import { useConnect } from "wagmi" import type { Connector } from "wagmi" export function useWalletCreation() { const { connectors, connect, isPending, error } = useConnect() const createNewWallet = (connector: Connector) => { // Extended connect function with forceCreate parameter ;(connect as (args: { connector: Connector; forceCreate?: boolean }) => void)({ connector, forceCreate: true, }) } const getHumanWalletConnector = () => { return connectors.find((connector) => connector.name === "HumanWallet") } return { connectors, createNewWallet, getHumanWalletConnector, isPending, error, } } ``` #### Continue Learning
**Pro Tip**: Combine these creation hooks with the [connection hooks](/react/connect-wallet) to provide users with seamless options based on whether they have an existing wallet. import { Button, Callout } from "vocs/components" ## React Integration Guide
Connect your React application to the blockchain in minutes
โšก Quick Setup ๐Ÿ”’ Secure ๐Ÿš€ Production Ready
This comprehensive guide will walk you through integrating HumanWallet with your React application using Wagmi and TypeScript. ### โšก Already Using Wagmi? Integration Takes 2 Minutes! **Super Easy Integration**: If you already have Wagmi infrastructure in your app, adding HumanWallet is incredibly simple! Just install our connector and add it to your existing Wagmi config - that's it! ๐Ÿš€
โšก Quick Add

Existing Wagmi Setup

Just add HumanWallet connector to your current Wagmi configuration. No other changes needed!

npm install @humanwallet/connector
// Add to your connectors array
๐Ÿ—๏ธ Full Setup

New Wagmi Project

Setting up from scratch? Follow our complete guide below for the full Wagmi + HumanWallet setup.

Complete installation
+ configuration guide
#### Quick Integration for Existing Wagmi Users If you already have Wagmi set up, just follow these 3 steps: 1. **Install the connector**: ```bash npm install @humanwallet/connector ``` 2. **Add to your connectors**: ```ts import { humanWalletConnector } from "@humanwallet/connector" const config = createConfig({ // your existing config... connectors: [ ...yourExistingConnectors, humanWalletConnector({ projectId: "your-project-id", }), ], }) ``` 3. **Use with your existing hooks**: ```tsx // Works with all your existing Wagmi hooks! const { connect, connectors } = useConnect() const humanWallet = connectors.find((c) => c.id === "humanWallet") ``` That's it! HumanWallet now works seamlessly with your existing Wagmi infrastructure. ๐ŸŽ‰ *** ### Full Setup Guide For new projects or those wanting the complete setup process, follow the detailed guide below. ### Overview HumanWallet provides a seamless way to connect your React application to Ethereum and EVM-compatible blockchains. This integration uses: * **HumanWallet Connector**: Our custom connector for blockchain interactions * **[Wagmi](https://wagmi.sh/)**: React hooks for Ethereum * **[Viem](https://viem.sh/)**: TypeScript interface for Ethereum operations * **[TanStack Query](https://tanstack.com/query/v5)**: State management for async operations **Quick Start**: This integration typically takes 10-15 minutes to complete and provides full blockchain connectivity for your React application. ### Prerequisites Before starting, ensure you have: - A React application set up - Node.js and npm/yarn installed - A HumanWallet project ID (contact support if needed) - GitHub access token for private package installation ### Installation #### Step 1: Configure Package Registry First, create an **.npmrc** file in your project root to access the HumanWallet private package: ```fold title:.npmrc @humanwallet:registry=https://npm.pkg.github.com //npm.pkg.github.com/:_authToken=${HW_CONNECTOR_TOKEN} ``` #### Step 2: Set Environment Variables **Important**: Keep your tokens secure and never commit them to version control. Configure your environment with the HumanWallet package token: **macOS/Linux (Bash/Zsh):** ```bash export GITHUB_TOKEN=tu_token_aqui ``` **Windows (PowerShell):** ```bash setx GITHUB_TOKEN "tu_token_aqui" ``` #### Step 3: Install Core Packages Install the HumanWallet connector: ```bash npm install @humanwallet/connector ``` Install the required Wagmi ecosystem packages: ```bash npm install wagmi viem@2.x @tanstack/react-query ``` Install Buffer for browser compatibility: ```bash npm install buffer ``` ### Configuration #### Step 1: Create Environment Configuration Create a `.env` file in your project root with your HumanWallet Project ID: ```bash [.env] VITE_HW_PROJECT_ID=your_project_id ``` **Token Distinction**: Don't confuse `VITE_HW_PROJECT_ID` with `HW_CONNECTOR_TOKEN`. The Project ID is used for application configuration, while the connector token is for package access. #### Step 2: Set Up Wagmi Configuration Create a basic Wagmi configuration file: ```ts [config.ts] import { createConfig, http } from "wagmi" import { mainnet, sepolia } from "wagmi/chains" export const config = createConfig({ chains: [mainnet, sepolia], transports: { [mainnet.id]: http(), [sepolia.id]: http(), }, }) ``` #### Step 3: Add Environment Variables Update your config to use the Project ID from environment variables: ```ts [config.ts] import { createConfig, http } from "wagmi" import { mainnet, sepolia } from "wagmi/chains" const PROJECT_ID = import.meta.env.VITE_HW_PROJECT_ID // [!code focus] export const config = createConfig({ chains: [mainnet, sepolia], transports: { [mainnet.id]: http(), [sepolia.id]: http(), }, }) ``` #### Step 4: Initialize HumanWallet Connector Add the HumanWallet connector to your Wagmi configuration: ```ts title=config.ts hl:13-17,1 import { createConfig, http } from "wagmi" import { mainnet, sepolia } from "wagmi/chains" import { humanWalletConnector } from "@humanwallet/connector" // [!code focus] const PROJECT_ID = import.meta.env.VITE_HW_PROJECT_ID export const config = createConfig({ chains: [mainnet, sepolia], transports: { [mainnet.id]: http(), [sepolia.id]: http(), }, connectors: [ // [!code focus] humanWalletConnector({ // [!code focus] projectId: PROJECT_ID, // [!code focus] appName: "My App Name", // [!code focus] }), // [!code focus] ], // [!code focus] }) ``` #### Step 5: Configure Buffer for Browser Compatibility **Important**: Blockchain libraries require Buffer to work in the browser. This step is essential for proper functionality. Create a polyfills file in your `src` directory: ```ts title=src/polyfills.ts import { Buffer } from "buffer" // Make Buffer available globally for libraries that need it // Reference: https://developers.binance.com/docs/binance-w3w/evm-compatible-provider#buffer-is-not-defined window.Buffer = Buffer ``` Update your main entry file (typically `src/main.tsx`) to import the polyfills first: ```tsx title=main.tsx hl:1 import "./polyfills" // [!code focus] import { StrictMode } from "react" import { createRoot } from "react-dom/client" import { Providers } from "./providers.tsx" import App from "./App.tsx" createRoot(document.getElementById("root")!).render( , ) ``` Then add this script to your `index.html` in the `` section: ```html title=index.html My App ``` **Note**: This implementation follows the [Binance Web3 Wallet integration pattern](https://developers.binance.com/docs/binance-w3w/evm-compatible-provider#buffer-is-not-defined) for maximum compatibility. ### Application Setup #### Step 6: Create Provider Components **Architecture Note**: You'll need both the Wagmi provider and TanStack Query provider for proper functionality. This setup ensures optimal performance and state management. Create the following files to structure your application: :::code-group ```ts [config.ts] import { createConfig, http } from "wagmi" import { mainnet, sepolia } from "wagmi/chains" import { humanWalletConnector } from "@humanwallet/connector" const PROJECT_ID = import.meta.env.VITE_HW_PROJECT_ID export const config = createConfig({ chains: [mainnet, sepolia], transports: { [mainnet.id]: http(), [sepolia.id]: http(), }, connectors: [ humanWalletConnector({ projectId: PROJECT_ID, appName: "My App Name", }), ], }) ``` ```tsx [providers.tsx] import { QueryClient, QueryClientProvider } from "@tanstack/react-query" import { WagmiProvider } from "wagmi" import { config } from "./config" const queryClient = new QueryClient() export function Providers({ children }: { children: React.ReactNode }) { return ( {children} ) } ``` ```tsx [main.tsx] import "./polyfills" // [!code focus] import { StrictMode } from "react" import { createRoot } from "react-dom/client" import { Providers } from "./providers.tsx" import App from "./App.tsx" createRoot(document.getElementById("root")!).render( , ) ``` ::: ### Next Steps **๐ŸŽ‰ Congratulations!** Your React application is now ready to use HumanWallet's blockchain connectivity features. You can now: * โœ… Use Wagmi hooks to interact with the blockchain * โœ… Connect and disconnect wallets through the HumanWallet interface * โœ… Access account information and perform transactions * โœ… Leverage TanStack Query for efficient data management #### Continue Learning
**Pro Tip**: Start by implementing wallet connection in your app, then gradually add transaction functionality and state management as needed. ## Authentication HumanWallet's authentication system is built on WebAuthn (passkeys), providing secure, passwordless authentication. The core package includes several functions for managing user authentication lifecycle. ### Functions Overview * [`register()`](#register) - Create new passkey-based accounts * [`login()`](#login) - Authenticate existing users * [`reconnect()`](#reconnect) - Restore previous sessions * [`disconnect()`](#disconnect) - End sessions and clear data * [`hasAccount()`](#hasaccount) - Check if user has existing account ### register() Creates a new passkey-based account for a user. #### Function Signature ```typescript function register( username: string, config: Config, ): Promise<{ sessionKeyAccount: SessionKeyAccount; kernelClient: KernelClient }> ``` #### Parameters * **`username`**: Unique identifier for the user (used for passkey creation) * **`config`**: Configuration object from `createConfig()` #### Usage Example ```typescript import { createConfig, register } from "@humanwallet/core" import { sepolia } from "viem/chains" const config = createConfig({ projectId: "your-project-id", chain: sepolia, }) try { const { sessionKeyAccount, kernelClient } = await register("alice", config) console.log("Account created:", sessionKeyAccount.address) // User is now authenticated and ready to interact } catch (error) { if (error.message.includes("User rejected")) { console.log("User cancelled passkey creation") } else { console.error("Registration failed:", error) } } ``` #### What Happens During Registration 1. **Passkey Creation**: Browser prompts user to create a passkey using biometrics/PIN 2. **Account Generation**: A smart contract wallet is created using the passkey 3. **Session Key**: A temporary session key is generated for seamless interactions 4. **Storage**: Authentication data is securely stored locally ### login() Authenticates an existing user with their passkey. #### Function Signature ```typescript function login(config: Config): Promise<{ sessionKeyAccount: SessionKeyAccount; kernelClient: KernelClient }> ``` #### Parameters * **`config`**: Configuration object from `createConfig()` #### Usage Example ```typescript try { const { sessionKeyAccount, kernelClient } = await login(config) console.log("Logged in as:", sessionKeyAccount.address) // User is authenticated and ready to interact } catch (error) { if (error.message.includes("User rejected")) { console.log("User cancelled authentication") } else if (error.message.includes("not found")) { console.log("Account not found - user needs to register first") } else { console.error("Login failed:", error) } } ``` #### What Happens During Login 1. **Passkey Authentication**: Browser prompts user to authenticate with their passkey 2. **Account Recovery**: The smart contract wallet is recovered using the passkey 3. **Session Restoration**: A new session key is generated for interactions 4. **Storage Update**: Fresh authentication data is stored locally ### reconnect() Restores a previous session using stored authentication data, avoiding the need for passkey authentication. #### Function Signature ```typescript function reconnect(config: Config): Promise<{ sessionKeyAccount: SessionKeyAccount; kernelClient: KernelClient } | null> ``` #### Parameters * **`config`**: Configuration object from `createConfig()` #### Usage Example ```typescript // Try to restore previous session const result = await reconnect(config) if (result) { const { sessionKeyAccount, kernelClient } = result console.log("Session restored for:", sessionKeyAccount.address) // User is automatically authenticated } else { console.log("No previous session found") // User needs to login or register } ``` #### When to Use Reconnect * **App Startup**: Check if user has an active session * **Page Refresh**: Restore authentication state * **Background Tasks**: Maintain authentication for scheduled operations ### disconnect() Clears stored authentication data and ends the current session. #### Function Signature ```typescript function disconnect(): Promise ``` #### Usage Example ```typescript await disconnect() console.log("User logged out") // All stored authentication data is cleared // User will need to login again to interact ``` #### What Gets Cleared * Session keys and authentication tokens * Locally stored account data * Any cached user information ### hasAccount() Checks if the user has an existing account stored locally. #### Function Signature ```typescript function hasAccount(): Promise ``` #### Usage Example ```typescript const userHasAccount = await hasAccount() if (userHasAccount) { // Try to reconnect or show login UI const result = await reconnect(config) if (!result) { // Show login form } } else { // Show registration form } ``` ### Authentication Flow Examples #### Complete Authentication Flow ```typescript import { createConfig, hasAccount, reconnect, login, register } from "@humanwallet/core" const config = createConfig({ projectId: "your-project-id", chain: sepolia, }) async function authenticateUser(username?: string) { // Check if user has existing account const hasExistingAccount = await hasAccount() if (hasExistingAccount) { // Try to restore session const session = await reconnect(config) if (session) { return session // User is authenticated } // Session expired, need to login return await login(config) } else { // New user, need to register if (username) { return await register(username, config) } else { throw new Error("Username required for registration") } } } ``` #### React Hook Example ```typescript import { useState, useEffect } from "react" import { hasAccount, reconnect, login, register, disconnect } from "@humanwallet/core" export function useAuth(config) { const [account, setAccount] = useState(null) const [isLoading, setIsLoading] = useState(true) // Check for existing session on mount useEffect(() => { async function checkAuth() { try { const session = await reconnect(config) if (session) { setAccount(session.sessionKeyAccount) } } catch (error) { console.error("Reconnect failed:", error) } finally { setIsLoading(false) } } checkAuth() }, [config]) const loginUser = async () => { setIsLoading(true) try { const { sessionKeyAccount } = await login(config) setAccount(sessionKeyAccount) return sessionKeyAccount } finally { setIsLoading(false) } } const registerUser = async (username: string) => { setIsLoading(true) try { const { sessionKeyAccount } = await register(username, config) setAccount(sessionKeyAccount) return sessionKeyAccount } finally { setIsLoading(false) } } const logoutUser = async () => { await disconnect() setAccount(null) } return { account, isLoading, isAuthenticated: !!account, login: loginUser, register: registerUser, logout: logoutUser, } } ``` ### Error Handling #### Common Error Types ```typescript try { await register("username", config) } catch (error) { if (error.message.includes("User rejected")) { // User cancelled passkey creation/authentication console.log("Authentication cancelled by user") } else if (error.message.includes("not supported")) { // Browser doesn't support WebAuthn console.log("Passkeys not supported in this browser") } else if (error.message.includes("network")) { // Network connectivity issues console.log("Network error - please try again") } else if (error.message.includes("already exists")) { // Username already taken (for register) console.log("Username already exists") } else { // Other errors console.error("Authentication error:", error) } } ``` ### Browser Compatibility Passkey authentication requires modern browser support: * **Chrome**: 67+ * **Firefox**: 60+ * **Safari**: 14+ * **Edge**: 18+ For unsupported browsers, consider showing a fallback message or alternative authentication method. ### Security Considerations * **Username Privacy**: Usernames are used for passkey creation but aren't stored on-chain * **Session Keys**: Temporary keys are used for transactions to avoid repeated passkey prompts * **Local Storage**: Authentication data is stored securely in browser storage * **Network Security**: All communications use HTTPS and secure protocols ## Configuration The `createConfig` function initializes a HumanWallet configuration object that's used by all other core functions. ### Function Signature ```typescript function createConfig(params: CreateConfigParams): Config interface CreateConfigParams { projectId: string chain: Chain } ``` ### Parameters #### `projectId` * **Type**: `string` * **Description**: Your ZeroDev project ID for accessing bundler and paymaster services * **Example**: `"your-project-id"` #### `chain` * **Type**: `Chain` (from viem) * **Description**: The blockchain network to connect to * **Example**: `sepolia`, `mainnet`, `polygon`, etc. ### Usage Example ```typescript import { createConfig } from "@humanwallet/core" import { sepolia, mainnet, polygon } from "viem/chains" // Sepolia testnet configuration const sepoliaConfig = createConfig({ projectId: "your-project-id", chain: sepolia, }) // Mainnet configuration const mainnetConfig = createConfig({ projectId: "your-project-id", chain: mainnet, }) // Polygon configuration const polygonConfig = createConfig({ projectId: "your-project-id", chain: polygon, }) ``` ### Return Value The function returns a configuration object with the following properties: ```typescript interface Config { bundlerTransport: Transport // HTTP transport for bundler paymasterTransport: Transport // HTTP transport for paymaster publicClient: PublicClient // Viem public client for reading chain: Chain // The specified blockchain passkeyUrl: string // Passkey service URL } ``` ### Getting Your Project Configuration To use HumanWallet, you'll need: 1. **Project ID**: Get your project ID from your ZeroDev account 2. **Choose Network**: Decide which blockchain network you want to support #### Supported Networks HumanWallet supports all major EVM-compatible networks: * **Ethereum Mainnet**: `mainnet` * **Ethereum Sepolia**: `sepolia` * **Polygon**: `polygon` * **Polygon Mumbai**: `mumbai` * **Arbitrum**: `arbitrum` * **Optimism**: `optimism` ### Configuration Best Practices #### Environment Variables Store sensitive configuration in environment variables: ```typescript // .env VITE_PROJECT_ID = your_project_id // config.ts import { sepolia } from "viem/chains" const projectId = import.meta.env.VITE_PROJECT_ID const config = createConfig({ projectId, chain: sepolia, }) ``` #### Multiple Environment Support ```typescript const getConfig = (environment: "development" | "production") => { const projectId = environment === "development" ? process.env.VITE_DEV_PROJECT_ID : process.env.VITE_PROD_PROJECT_ID const chain = environment === "development" ? sepolia : mainnet return createConfig({ projectId, chain, }) } ``` ### Troubleshooting #### Common Issues **Invalid Project ID** ``` Error: Request failed with status 401 ``` * Verify your project ID is correct * Ensure you're using the right environment (development/production) **Network Mismatch** ``` Error: Chain ID mismatch ``` * Ensure the chain ID in your URLs matches the `chain` parameter * Double-check the chain ID reference above **URL Format Issues** ``` Error: Invalid URL ``` * Ensure URLs follow the exact format: `https://[service].example.com/api/v3/[project-id]/chain/[chain-id]` * Remove any trailing slashes from URLs ## Contract Interactions HumanWallet provides functions for interacting with smart contracts, including reading contract state and executing transactions. All write operations are gasless through account abstraction. ### Functions Overview * [`writeContract()`](#writecontract) - Execute single contract transactions * [`writeContracts()`](#writecontracts) - Execute multiple transactions in batch * [`readContract()`](#readcontract) - Read contract state * [`signTypedData()`](#signtypeddata) - Sign structured data ### writeContract() Executes a single contract function call as a gasless transaction. #### Function Signature ```typescript function writeContract(config: Config, args: WriteContractParameters): Promise ``` #### Parameters ##### `config` * **Type**: `Config` * **Description**: Configuration object from `createConfig()` with authenticated client ##### `args` * **Type**: `WriteContractParameters` * **Properties**: * `address`: Contract address (`0x${string}`) * `abi`: Contract ABI array * `functionName`: Function name to call * `args`: Array of function arguments (optional) * `value`: ETH value to send (optional, defaults to `0n`) #### Usage Example ```typescript import { writeContract } from "@humanwallet/core" // First authenticate user const { sessionKeyAccount, kernelClient } = await login("alice", config) // Update config with authenticated client const authenticatedConfig = { ...config, kernelClient, sessionKeyAccount, } // Execute a token transfer const hash = await writeContract(authenticatedConfig, { address: "0x...", // Token contract address abi: [ { name: "transfer", type: "function", inputs: [ { name: "to", type: "address" }, { name: "amount", type: "uint256" }, ], outputs: [{ name: "", type: "bool" }], stateMutability: "nonpayable", }, ], functionName: "transfer", args: ["0x742d35Cc6634C0532925a3b8D52ECC5c5e9fA008", BigInt("1000000000000000000")], // 1 token }) console.log("Transaction hash:", hash) ``` #### Advanced Examples ##### NFT Minting ```typescript const mintHash = await writeContract(authenticatedConfig, { address: "0x...", // NFT contract abi: nftAbi, functionName: "mint", args: [sessionKeyAccount.address, 1], // mint to self, token ID 1 }) ``` ##### Contract with ETH Value ```typescript const donationHash = await writeContract(authenticatedConfig, { address: "0x...", // Donation contract abi: donationAbi, functionName: "donate", value: BigInt("1000000000000000000"), // 1 ETH }) ``` ### writeContracts() Executes multiple contract function calls in a single batch transaction. #### Function Signature ```typescript function writeContracts(config: Config, calls: WriteContractParameters[]): Promise ``` #### Parameters * **`config`**: Configuration object with authenticated client * **`calls`**: Array of contract call parameters #### Usage Example ```typescript // Batch multiple operations const batchHash = await writeContracts(authenticatedConfig, [ { address: "0x...", // Token contract abi: tokenAbi, functionName: "approve", args: ["0x...", BigInt("1000000000000000000")], }, { address: "0x...", // DEX contract abi: dexAbi, functionName: "swap", args: [BigInt("1000000000000000000"), BigInt("950000000000000000")], }, ]) console.log("Batch transaction hash:", batchHash) ``` #### Benefits of Batching * **Atomic Operations**: All transactions succeed or fail together * **Gas Efficiency**: Single user operation instead of multiple * **Better UX**: One signature for multiple actions * **Reduced Latency**: Faster than sequential transactions ### readContract() Reads data from a smart contract without executing a transaction. #### Function Signature ```typescript function readContract(config: Config, args: ReadContractParameters): Promise ``` #### Parameters ##### `args` * **Type**: `ReadContractParameters` * **Properties**: * `address`: Contract address * `abi`: Contract ABI array * `functionName`: Function name to call * `args`: Array of function arguments (optional) #### Usage Examples ##### Token Balance ```typescript const balance = await readContract(config, { address: "0x...", // Token contract abi: [ { name: "balanceOf", type: "function", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "", type: "uint256" }], stateMutability: "view", }, ], functionName: "balanceOf", args: [sessionKeyAccount.address], }) console.log("Token balance:", balance.toString()) ``` ##### Contract State ```typescript const totalSupply = await readContract(config, { address: "0x...", abi: tokenAbi, functionName: "totalSupply", }) const name = await readContract(config, { address: "0x...", abi: tokenAbi, functionName: "name", }) console.log(`${name} total supply:`, totalSupply.toString()) ``` ##### Complex Data Structures ```typescript // Reading a struct const userInfo = await readContract(config, { address: "0x...", abi: stakingAbi, functionName: "getUserInfo", args: [sessionKeyAccount.address], }) console.log("User info:", { stakedAmount: userInfo[0].toString(), rewardDebt: userInfo[1].toString(), lastStakeTime: new Date(Number(userInfo[2]) * 1000), }) ``` ### signTypedData() Signs structured data according to EIP-712 standard. #### Function Signature ```typescript function signTypedData(config: Config, args: SignTypedDataParameters): Promise ``` #### Parameters ##### `args` * **Type**: `SignTypedDataParameters` * **Properties**: * `domain`: EIP-712 domain separator * `types`: Type definitions for the data * `primaryType`: The primary type being signed * `message`: The actual data to sign #### Usage Example ```typescript // Sign a permit message for gasless token approval const signature = await signTypedData(authenticatedConfig, { domain: { name: "MyToken", version: "1", chainId: 11155111, verifyingContract: "0x...", }, types: { Permit: [ { name: "owner", type: "address" }, { name: "spender", type: "address" }, { name: "value", type: "uint256" }, { name: "nonce", type: "uint256" }, { name: "deadline", type: "uint256" }, ], }, primaryType: "Permit", message: { owner: sessionKeyAccount.address, spender: "0x...", value: BigInt("1000000000000000000"), nonce: BigInt(0), deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour }, }) console.log("Signature:", signature) ``` #### Signature Object The function returns a `Signature` object with: ```typescript interface Signature { r: string // First 32 bytes of signature s: string // Second 32 bytes of signature v: number // Recovery parameter } ``` ### Error Handling #### Authentication Errors ```typescript try { await writeContract(config, contractParams) } catch (error) { if (error.message.includes("Kernel client not initialized")) { console.log("User not authenticated - please login first") // Redirect to authentication flow } } ``` #### Contract Execution Errors ```typescript try { await writeContract(authenticatedConfig, contractParams) } catch (error) { if (error.message.includes("execution reverted")) { console.log("Contract execution failed:", error.message) // Handle specific contract errors } else if (error.message.includes("insufficient funds")) { console.log("Insufficient balance for transaction") } else if (error.message.includes("user rejected")) { console.log("User cancelled transaction") } } ``` #### Read Contract Errors ```typescript try { const result = await readContract(config, contractParams) } catch (error) { if (error.message.includes("call reverted")) { console.log("Contract call reverted - check parameters") } else if (error.message.includes("network")) { console.log("Network error - please try again") } } ``` ### Best Practices #### Gas Optimization ```typescript // Batch related operations const operations = [approveTokenOperation, swapTokenOperation, stakeLPTokenOperation] // Execute as single batch instead of 3 separate transactions await writeContracts(authenticatedConfig, operations) ``` #### Error Recovery ```typescript async function executeWithRetry(operation: () => Promise, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await operation() } catch (error) { if (i === maxRetries - 1) throw error // Wait before retry await new Promise((resolve) => setTimeout(resolve, 1000 * (i + 1))) } } } const hash = await executeWithRetry(() => writeContract(authenticatedConfig, contractParams)) ``` #### Type Safety ```typescript // Define contract types for better TypeScript support interface TokenContract { address: `0x${string}` abi: typeof tokenAbi } async function transferToken(contract: TokenContract, to: `0x${string}`, amount: bigint) { return writeContract(authenticatedConfig, { address: contract.address, abi: contract.abi, functionName: "transfer", args: [to, amount], }) } ``` ### Integration Examples #### DeFi Application ```typescript class DeFiService { constructor(private config: Config) {} async swapTokens(tokenA: string, tokenB: string, amount: bigint) { // 1. Check allowance const allowance = await readContract(this.config, { address: tokenA, abi: erc20Abi, functionName: "allowance", args: [this.config.sessionKeyAccount.address, DEX_CONTRACT], }) const operations = [] // 2. Approve if needed if (allowance < amount) { operations.push({ address: tokenA, abi: erc20Abi, functionName: "approve", args: [DEX_CONTRACT, amount], }) } // 3. Execute swap operations.push({ address: DEX_CONTRACT, abi: dexAbi, functionName: "swapExactTokensForTokens", args: [amount, 0n, [tokenA, tokenB], this.config.sessionKeyAccount.address], }) // 4. Execute as batch return writeContracts(this.config, operations) } } ``` ## Core Package (@humanwallet/core)
HumanWallet Core @humanwallet/core
The `@humanwallet/core` package provides the foundational functionality for HumanWallet, including authentication, account management, and blockchain interactions using passkey-based authentication and account abstraction. ### Overview HumanWallet Core enables: * **Passkey Authentication**: Secure biometric and hardware-based authentication using WebAuthn * **Account Abstraction**: Gasless transactions with smart contract wallets * **Session Management**: Persistent authentication sessions with secure key storage * **Ethereum Integration**: Full blockchain interaction capabilities * **TypeScript Support**: Complete type safety with comprehensive TypeScript definitions ### Installation ```bash npm install @humanwallet/core ``` #### Browser Compatibility When using HumanWallet in browser environments, you need to configure Buffer: ```bash npm install buffer ``` Create a polyfills file: ```typescript // src/polyfills.ts import { Buffer } from "buffer" // Make Buffer available globally window.Buffer = Buffer ``` Import it at the beginning of your main entry file: ```typescript import "./polyfills" // ... rest of your imports ``` And add to your `index.html` ``: ```html ``` For more details, see the [React Integration Guide](/react/getting-started). ### Quick Start ```typescript import { createConfig, register, login, writeContract } from "@humanwallet/core" import { sepolia } from "viem/chains" // Create configuration const config = createConfig({ passkeyUrl: "https://passkeys.example.com/api/v3/your-project-id", bundlerRpc: "https://rpc.example.com/api/v3/your-project-id/chain/11155111", paymasterRpc: "https://rpc.example.com/api/v3/your-project-id/chain/11155111", chain: sepolia, }) // Register a new user const { sessionKeyAccount, kernelClient } = await register("alice", config) // Execute a transaction const hash = await writeContract(config, { address: "0x...", abi: contractAbi, functionName: "transfer", args: ["0x...", BigInt(1000)], }) ``` ### Core Functions The core package is organized into several categories of functionality: #### [Configuration](/core/configuration) * `createConfig()` - Initialize HumanWallet configuration #### [Authentication](/core/authentication) * `register()` - Create new passkey-based accounts * `login()` - Authenticate existing users * `reconnect()` - Restore previous sessions * `disconnect()` - End sessions and clear data * `hasAccount()` - Check if user has existing account #### [Contract Interactions](/core/contracts) * `writeContract()` - Execute contract transactions * `writeContracts()` - Execute multiple transactions in batch * `readContract()` - Read contract state * `signTypedData()` - Sign structured data #### [Transaction Management](/core/transactions) * `waitForTransaction()` - Wait for transaction confirmation * `waitForUserOperation()` - Wait for user operation completion ### Architecture HumanWallet Core is built on several key technologies: * **WebAuthn**: For secure, passwordless authentication using passkeys * **Account Abstraction**: Smart contract wallets that enable gasless transactions * **Account Abstraction Infrastructure**: Bundler and paymaster services for AA transactions * **Viem**: Low-level Ethereum interactions and type safety * **Session Keys**: Temporary keys for seamless user experience ### Error Handling All core functions can throw errors that should be handled appropriately: ```typescript try { const result = await login("username", config) // Handle success } catch (error) { if (error.message.includes("User rejected")) { // Handle user rejection } else if (error.message.includes("not found")) { // Handle account not found } else { // Handle other errors } } ``` ### Next Steps * [Configuration Setup](/core/configuration) - Learn how to configure HumanWallet * [Authentication Guide](/core/authentication) - Understand the authentication flow * [Contract Interactions](/core/contracts) - Execute transactions and read contract state * [Transaction Management](/core/transactions) - Monitor and manage transactions ## Transaction Management HumanWallet provides functions for monitoring and managing blockchain transactions and user operations. These functions help you track transaction status and wait for confirmations. ### Functions Overview * [`waitForTransaction()`](#waitfortransaction) - Wait for regular transaction confirmation * [`waitForUserOperation()`](#waitforuseroperation) - Wait for user operation completion ### waitForTransaction() Waits for a regular blockchain transaction to be confirmed and included in a block. #### Function Signature ```typescript function waitForTransaction( config: Config, hash: string, options?: WaitForTransactionOptions, ): Promise ``` #### Parameters ##### `config` * **Type**: `Config` * **Description**: Configuration object from `createConfig()` ##### `hash` * **Type**: `string` * **Description**: Transaction hash to wait for ##### `options` (optional) * **Type**: `WaitForTransactionOptions` * **Properties**: * `timeout`: Maximum time to wait in milliseconds (default: 60000) * `confirmations`: Number of confirmations to wait for (default: 1) #### Usage Example ```typescript import { writeContract, waitForTransaction } from "@humanwallet/core" // Execute a transaction const hash = await writeContract(authenticatedConfig, { address: "0x...", abi: tokenAbi, functionName: "transfer", args: ["0x742d35Cc6634C0532925a3b8D52ECC5c5e9fA008", BigInt("1000000000000000000")], }) console.log("Transaction submitted:", hash) try { // Wait for confirmation const receipt = await waitForTransaction(config, hash) console.log("Transaction confirmed!") console.log("Block number:", receipt.blockNumber) console.log("Gas used:", receipt.gasUsed.toString()) console.log("Status:", receipt.status) // 'success' or 'reverted' } catch (error) { console.error("Transaction failed or timed out:", error) } ``` #### Advanced Usage ##### With Custom Timeout and Confirmations ```typescript // Wait for 3 confirmations with 2 minute timeout const receipt = await waitForTransaction(config, hash, { timeout: 120000, // 2 minutes confirmations: 3, }) console.log("Transaction confirmed with 3 confirmations") ``` ##### Error Handling ```typescript try { const receipt = await waitForTransaction(config, hash) if (receipt.status === "reverted") { console.log("Transaction was reverted") // Handle revert case } else { console.log("Transaction successful") } } catch (error) { if (error.message.includes("timeout")) { console.log("Transaction timed out - may still be pending") } else if (error.message.includes("not found")) { console.log("Transaction not found") } else { console.error("Unexpected error:", error) } } ``` ### waitForUserOperation() Waits for an account abstraction user operation to be completed. This is used for gasless transactions executed through HumanWallet. #### Function Signature ```typescript function waitForUserOperation( config: Config, hash: string, options?: WaitForUserOperationOptions, ): Promise ``` #### Parameters ##### `config` * **Type**: `Config` * **Description**: Configuration object from `createConfig()` ##### `hash` * **Type**: `string` * **Description**: User operation hash to wait for ##### `options` (optional) * **Type**: `WaitForUserOperationOptions` * **Properties**: * `timeout`: Maximum time to wait in milliseconds (default: 60000) #### Usage Example ```typescript import { writeContract, waitForUserOperation } from "@humanwallet/core" // Execute a gasless transaction (returns user operation hash) const userOpHash = await writeContract(authenticatedConfig, { address: "0x...", abi: contractAbi, functionName: "mint", args: [sessionKeyAccount.address, 1], }) console.log("User operation submitted:", userOpHash) try { // Wait for user operation completion const receipt = await waitForUserOperation(config, userOpHash) console.log("User operation completed!") console.log("Actual transaction hash:", receipt.transactionHash) console.log("Block number:", receipt.blockNumber) console.log("Success:", receipt.success) } catch (error) { console.error("User operation failed:", error) } ``` #### User Operation Receipt The `UserOperationReceipt` contains: ```typescript interface UserOperationReceipt { transactionHash: string // The actual on-chain transaction hash blockNumber: bigint // Block number where it was included success: boolean // Whether the operation succeeded gasUsed: bigint // Gas consumed by the operation logs: Log[] // Event logs emitted } ``` ### Transaction Flow Examples #### Complete Transaction Flow ```typescript async function executeAndWaitForTransaction(contractParams: WriteContractParameters) { try { // 1. Submit transaction console.log("Submitting transaction...") const hash = await writeContract(authenticatedConfig, contractParams) // 2. Show pending state to user console.log("Transaction pending:", hash) // 3. Wait for confirmation console.log("Waiting for confirmation...") const receipt = await waitForUserOperation(config, hash) // 4. Handle result if (receipt.success) { console.log("โœ… Transaction successful!") console.log("Transaction hash:", receipt.transactionHash) return receipt } else { console.log("โŒ Transaction failed") throw new Error("Transaction reverted") } } catch (error) { console.error("Transaction error:", error) throw error } } ``` #### Batch Transaction Monitoring ```typescript async function executeBatchAndMonitor(operations: WriteContractParameters[]) { try { // Execute batch const batchHash = await writeContracts(authenticatedConfig, operations) console.log("Batch submitted:", batchHash) // Monitor progress const receipt = await waitForUserOperation(config, batchHash, { timeout: 120000, // 2 minutes for batch operations }) if (receipt.success) { console.log("All operations in batch succeeded") // Parse logs to see individual operation results receipt.logs.forEach((log, index) => { console.log(`Operation ${index + 1} log:`, log) }) } else { console.log("Batch failed - some operations reverted") } return receipt } catch (error) { console.error("Batch execution failed:", error) throw error } } ``` ### React Integration Examples #### Transaction Status Hook ```typescript import { useState, useEffect } from "react" import { waitForUserOperation } from "@humanwallet/core" export function useTransactionStatus(config: Config, hash: string | null) { const [status, setStatus] = useState<"pending" | "confirmed" | "failed" | null>(null) const [receipt, setReceipt] = useState(null) useEffect(() => { if (!hash) return setStatus("pending") waitForUserOperation(config, hash) .then((receipt) => { setReceipt(receipt) setStatus(receipt.success ? "confirmed" : "failed") }) .catch((error) => { console.error("Transaction monitoring failed:", error) setStatus("failed") }) }, [config, hash]) return { status, receipt } } ``` #### Transaction Component ```typescript import React from 'react' import { useTransactionStatus } from './useTransactionStatus' interface TransactionStatusProps { config: Config hash: string onComplete?: (receipt: any) => void } export function TransactionStatus({ config, hash, onComplete }: TransactionStatusProps) { const { status, receipt } = useTransactionStatus(config, hash) useEffect(() => { if (status === 'confirmed' && receipt && onComplete) { onComplete(receipt) } }, [status, receipt, onComplete]) return (
{status === 'pending' && (
โณ Transaction pending...
Hash: {hash}
)} {status === 'confirmed' && (
โœ… Transaction confirmed!
Block: {receipt?.blockNumber.toString()}
)} {status === 'failed' && (
โŒ Transaction failed
Please try again
)}
) } ``` ### Error Handling #### Timeout Handling ```typescript async function waitWithTimeout(hash: string, timeoutMs: number = 60000) { try { const receipt = await waitForUserOperation(config, hash, { timeout: timeoutMs, }) return receipt } catch (error) { if (error.message.includes("timeout")) { // Transaction might still be pending console.log("Transaction timed out but may still complete") // Optionally, continue monitoring with longer timeout return waitForUserOperation(config, hash, { timeout: timeoutMs * 2, }) } throw error } } ``` #### Network Error Recovery ```typescript async function waitWithRetry(hash: string, maxRetries: number = 3) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await waitForUserOperation(config, hash) } catch (error) { console.log(`Attempt ${attempt} failed:`, error.message) if (attempt === maxRetries) { throw new Error(`Failed to confirm transaction after ${maxRetries} attempts`) } // Wait before retry await new Promise((resolve) => setTimeout(resolve, 5000 * attempt)) } } } ``` ### Best Practices #### Progress Feedback ```typescript async function executeWithProgress(contractParams: WriteContractParameters) { // 1. Show submission state console.log("๐Ÿš€ Submitting transaction...") const hash = await writeContract(authenticatedConfig, contractParams) // 2. Show pending state console.log("โณ Transaction submitted, waiting for confirmation...") console.log("Hash:", hash) // 3. Monitor with timeout const receipt = await waitForUserOperation(config, hash, { timeout: 90000, // 1.5 minutes }) // 4. Show final result if (receipt.success) { console.log("โœ… Transaction confirmed!") } else { console.log("โŒ Transaction failed") } return receipt } ``` #### Batch Monitoring ```typescript async function monitorBatchExecution(batchHash: string) { console.log("Monitoring batch execution...") const startTime = Date.now() const receipt = await waitForUserOperation(config, batchHash, { timeout: 180000, // 3 minutes for complex batches }) const duration = Date.now() - startTime console.log(`Batch completed in ${duration}ms`) if (receipt.success) { console.log(`โœ… All ${receipt.logs.length} operations succeeded`) } else { console.log("โŒ Batch failed - check individual operations") } return receipt } ``` #### Gas Usage Tracking ```typescript async function trackGasUsage(operations: WriteContractParameters[]) { const startTime = Date.now() // Execute operations const hashes = await Promise.all(operations.map((op) => writeContract(authenticatedConfig, op))) // Wait for all confirmations const receipts = await Promise.all(hashes.map((hash) => waitForUserOperation(config, hash))) // Calculate metrics const totalGasUsed = receipts.reduce((sum, receipt) => sum + receipt.gasUsed, 0n) const successfulOps = receipts.filter((r) => r.success).length const duration = Date.now() - startTime console.log("Gas usage summary:", { totalOperations: operations.length, successfulOperations: successfulOps, totalGasUsed: totalGasUsed.toString(), averageGasPerOp: (totalGasUsed / BigInt(receipts.length)).toString(), executionTime: `${duration}ms`, }) return receipts } ```