# 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'}
{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
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 (
)
}
```
### 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
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
}
```