UI-SDK is the go-to approach for implementing Account Abstraction (AA) capabilities in Incentiv applications by providing a streamlined interface for communicating with ERC-4337 bundlers. It supports both EOA (Externally Owned Account) and Passkey-based authentication methods for creating AA wallets.
Account Abstraction (ERC-4337) allows users to interact with smart contracts using smart contract wallets instead of Externally Owned Accounts (EOAs). This enables features like:
Multi-signature requirements
Account recovery mechanisms
Transaction batching
Gas abstraction (sponsored transactions)
Custom validation logic
Transaction authorization rules
Transaction whitelisting/blacklisting
Spending limits and quotas
Time-based transaction locks
Custom security policies
The Incentiv UI-SDK simplifies the implementation of these features by providing a high-level interface built on top of ERC-4337. It acts as a communication layer between your application and the bundler network, handling:
Creating an EOA-based Account Abstraction Provider
An EOA (Externally Owned Account) provider wraps existing wallet providers like MetaMask, WalletConnect, or any other Ethereum wallet to work with Account Abstraction. This allows users to leverage their existing wallets while gaining the benefits of smart contract accounts.The EOA provider:
Uses your existing wallet’s signing capabilities
Converts regular transactions into UserOperations
Handles gas estimation and payment through the smart contract account
Maintains compatibility with existing wallet interfaces
import { ethers } from 'ethers';import { getEoaProvider } from 'ui-sdk';const createEoaProvider = async () => { // You can use any base provider - MetaMask, WalletConnect, etc. const baseProvider = new ethers.providers.Web3Provider(window.ethereum); // Request account access if needed await window.ethereum.request({ method: 'eth_requestAccounts' }); const config = { chainId: await baseProvider.getNetwork().then((net) => net.chainId), entryPointAddress: process.env.REACT_APP_ENTRY_POINT_ADDRESS, bundlerUrl: process.env.REACT_APP_BUNDLER_URL, factoryAddress: process.env.REACT_APP_ACCOUNT_FACTORY_ADDRESS }; // Create the AA provider using the EOA provider const aaProvider = await getEoaProvider(baseProvider, config); return aaProvider;};// Example with WalletConnectconst createWalletConnectProvider = async () => { // Initialize WalletConnect provider const walletConnectProvider = new WalletConnectProvider({ infuraId: "your-infura-id" }); await walletConnectProvider.enable(); const baseProvider = new ethers.providers.Web3Provider(walletConnectProvider); // Use the same getEoaProvider function const aaProvider = await getEoaProvider(baseProvider, config); return aaProvider;};
Creating a Passkey-based Account Abstraction Provider
A passkey provider enables passwordless authentication using WebAuthn (passkeys). This approach eliminates the need for traditional wallets and provides a more secure, user-friendly experience.The passkey provider:
Uses biometric authentication (fingerprint, face recognition, or PIN)
Stores cryptographic keys securely in the device’s hardware
Provides cross-platform compatibility
Enables seamless user experience without browser extensions
import { ethers } from 'ethers';import { getPasskeyProvider, WebAuthnPublicKey } from 'ui-sdk';const createPasskeyProvider = async (credential) => { // Create a base provider using StaticJsonRpcProvider const baseProvider = new ethers.providers.StaticJsonRpcProvider( process.env.REACT_APP_RPC_URL ); const config = { chainId: 11690, // Your network chain ID entryPointAddress: process.env.REACT_APP_ENTRY_POINT_ADDRESS, bundlerUrl: process.env.REACT_APP_BUNDLER_URL, factoryAddress: process.env.REACT_APP_ACCOUNT_FACTORY_ADDRESS }; // Create the AA provider using the passkey provider const aaProvider = await getPasskeyProvider(baseProvider, credential, config); return aaProvider;};
Extract credentialId and create WebAuthnPublicKey from attestation object
Send attestation data to server for verification and storage
Login
Sign login challenge using signPasskeyLoginChallenge()
Send signature to server for verification
Retrieve public key and credential ID from server
Create WebAuthnCredential object and initialize provider
Usage
Use the created provider for all Account Abstraction operations
The provider handles WebAuthn signing automatically for transactions
This approach provides a seamless, secure authentication experience while maintaining the full capabilities of Account Abstraction.Let us code these steps:
1
Step 1: Register the Passkey
import { registerPasskey, WebAuthnPublicKey } from 'ui-sdk';const registerNewPasskey = async (passkeyName, challenge, userId) => { try { // Register the passkey with WebAuthn const response = await registerPasskey(passkeyName, challenge, userId); // Extract credential ID from the response const credentialId = response.credential.id; // Create WebAuthnPublicKey from the attestation object const publicKey = await WebAuthnPublicKey.fromAttetationObject( response.credential.response.attestationObject ); return { credentialId, publicKey, attestationObject: response.credential.response.attestationObject, signature: response.signature }; } catch (error) { console.error('Passkey registration failed:', error); throw error; }};
2
Step 2: Complete Registration Flow
The typical passkey registration and authentication flow works as follows:
The UI-SDK allows sending transactions through Account Abstraction, which means transactions are executed by a smart contract wallet rather than an EOA.Key features of AA transactions:
Gas is paid by the smart contract wallet
Multiple operations can be batched
Custom validation logic can be implemented
Transactions can be sponsored by paymasters
const sendTransaction = async (provider, to, value, data = '0x') => { try { const signer = provider.getSigner(); // Get current gas price for optimal transaction execution const feeData = await provider.getFeeData(); // Prepare transaction with gas optimization const tx = { to, value: ethers.utils.parseEther(value), data, maxFeePerGas: feeData.maxFeePerGas, maxPriorityFeePerGas: feeData.maxPriorityFeePerGas }; // Send transaction through AA const txResponse = await signer.sendTransaction(tx); // Wait for confirmation with 1 block and 60-second timeout await provider.waitForTransaction(txResponse.hash, 1, 60000); return txResponse; } catch (error) { console.error('Transaction error:', error); throw error; }};
Batch transactions are a powerful feature of Account Abstraction that allows multiple operations to be executed in a single transaction. This can significantly reduce gas costs and improve UX.Benefits of batch transactions:
Atomic execution (all operations succeed or all fail)
Reduced gas costs compared to individual transactions
Better UX with single signature for multiple operations
The UI-SDK provides a powerful contract deployment system that uses CREATE2 for deterministic addresses and supports both ContractFactory and raw bytecode deployments. All deployments are handled through Account Abstraction, ensuring consistent gas management and transaction handling.Key features:
Note: Replace environment variables and placeholder values with your actual configuration. Always ensure proper error handling and input validation in production code.