![]() |
VOOZH | about |
dotnet add package Nethereum.AccountAbstraction --version 6.1.0
NuGet\Install-Package Nethereum.AccountAbstraction -Version 6.1.0
<PackageReference Include="Nethereum.AccountAbstraction" Version="6.1.0" />
<PackageVersion Include="Nethereum.AccountAbstraction" Version="6.1.0" />Directory.Packages.props
<PackageReference Include="Nethereum.AccountAbstraction" />Project file
paket add Nethereum.AccountAbstraction --version 6.1.0
#r "nuget: Nethereum.AccountAbstraction, 6.1.0"
#:package Nethereum.AccountAbstraction@6.1.0
#addin nuget:?package=Nethereum.AccountAbstraction&version=6.1.0Install as a Cake Addin
#tool nuget:?package=Nethereum.AccountAbstraction&version=6.1.0Install as a Cake Tool
This guide shows you how to use Account Abstraction with Nethereum, enabling smart contract wallets to execute transactions through UserOperations instead of traditional EOA transactions.
Account Abstraction (ERC-4337) allows smart contracts to act as user accounts. Instead of sending transactions directly, you create UserOperations that are:
handleOpsBenefits include:
using Nethereum.AccountAbstraction;
using Nethereum.Signer;
// Your smart account details
var accountAddress = "0x..."; // Your smart account address
var ownerKey = new EthECKey("your-private-key");
// Bundler service (handles UserOperation submission)
var bundlerService = new AccountAbstractionBundlerService(
new RpcClient(new Uri("https://your-bundler-url")));
// EntryPoint v0.9 (recommended)
var entryPointAddress = "0x433709009B8330FDa32311DF1C2AFA402eD8D009";
// Or use constants
var entryPointAddress = EntryPointAddresses.V09; // 0x433709009B8330FDa32311DF1C2AFA402eD8D009
// var entryPointAddress = EntryPointAddresses.V08; // 0x4337084d9e255ff0702461cf8895ce9e3b5ff108
// var entryPointAddress = EntryPointAddresses.V07; // 0x0000000071727De22E5E9d8BAf0edAc6f37da032
The simplest way to use Account Abstraction is to switch an existing contract service to use AA:
// Deploy or get your contract service as usual
var myToken = await StandardTokenService.DeployContractAndGetServiceAsync(
web3, new EIP20Deployment { ... });
// Switch to Account Abstraction - one line!
myToken.ChangeContractHandlerToAA(
accountAddress,
ownerKey,
bundlerService,
entryPointAddress);
// Now all transactions go through UserOperations
var receipt = await myToken.TransferRequestAndWaitForReceiptAsync(recipient, amount);
// The receipt includes AA-specific information
var aaReceipt = (AATransactionReceipt)receipt;
Console.WriteLine($"UserOp Hash: {aaReceipt.UserOpHash}");
Console.WriteLine($"Success: {aaReceipt.UserOpSuccess}");
Nethereum provides built-in services for common token standards. All of these now support Account Abstraction.
// Get the ERC20 service from web3
var erc20 = web3.Eth.ERC20.GetContractService("0xTokenAddress");
// Switch to Account Abstraction
erc20.SwitchToAccountAbstraction(
accountAddress,
ownerKey,
bundlerService,
entryPointAddress);
// All ERC20 operations now use UserOperations
await erc20.TransferRequestAndWaitForReceiptAsync(recipient, amount);
await erc20.ApproveRequestAndWaitForReceiptAsync(spender, amount);
// Query functions still use normal eth_call (no gas needed)
var balance = await erc20.BalanceOfQueryAsync(accountAddress);
var erc721 = web3.Eth.ERC721.GetContractService("0xNFTAddress");
erc721.SwitchToAccountAbstraction(
accountAddress,
ownerKey,
bundlerService,
entryPointAddress);
// Transfer NFTs via UserOperation
await erc721.SafeTransferFromRequestAndWaitForReceiptAsync(
from: accountAddress,
to: recipient,
tokenId: 123);
var erc1155 = web3.Eth.ERC1155.GetContractService("0xMultiTokenAddress");
erc1155.SwitchToAccountAbstraction(
accountAddress,
ownerKey,
bundlerService,
entryPointAddress);
await erc1155.SafeTransferFromRequestAndWaitForReceiptAsync(
from: accountAddress,
to: recipient,
id: tokenId,
amount: quantity,
data: Array.Empty<byte>());
var ensRegistry = new ENSRegistryService(web3.Eth, ensRegistryAddress);
ensRegistry.SwitchToAccountAbstraction(
accountAddress,
ownerKey,
bundlerService,
entryPointAddress);
// ENS operations via UserOperation
await ensRegistry.SetOwnerRequestAndWaitForReceiptAsync(node, newOwner);
If your smart account doesn't exist yet, you can have it deployed automatically on the first transaction using a FactoryConfig:
// Calculate the account address (it doesn't exist yet)
var factory = new SimpleAccountFactoryService(web3, factoryAddress);
var accountAddress = await factory.GetAddressQueryAsync(ownerKey.GetPublicAddress(), salt: 0);
// Fund the address so it can pay for deployment + first transaction
await web3.Eth.GetEtherTransferService()
.TransferEtherAndWaitForReceiptAsync(accountAddress, 0.1m);
// Configure the factory for auto-deployment
var factoryConfig = new FactoryConfig(
factoryAddress: factoryAddress,
owner: ownerKey.GetPublicAddress(),
salt: 0);
// Switch to AA with factory config
myContract.ChangeContractHandlerToAA(
accountAddress,
ownerKey,
bundlerService,
entryPointAddress,
factory: factoryConfig); // <-- Include factory config
// First transaction will:
// 1. Deploy the smart account (via initCode)
// 2. Execute your contract call
var receipt = await myContract.SomeRequestAndWaitForReceiptAsync();
The handler automatically checks if the account exists. If not, it includes the initCode to deploy it. On subsequent calls, initCode is omitted.
Execute multiple contract calls in a single UserOperation. All calls target the handler's contract:
var handler = (AAContractHandler)erc20.ContractHandler;
// Use ToBatchCall() extension method for clean, type-safe batching
var receipt = await handler.BatchExecuteAsync(
new TransferFunction { To = addr1, Value = 100 }.ToBatchCall(),
new TransferFunction { To = addr2, Value = 200 }.ToBatchCall(),
new TransferFunction { To = addr3, Value = 300 }.ToBatchCall());
// All three operations succeed or fail atomically
if (receipt.UserOpSuccess)
{
Console.WriteLine("All transfers completed!");
}
// Recommended: Use ToBatchCall() for type safety
await handler.BatchExecuteAsync(
new TransferFunction { To = addr1, Value = 100 }.ToBatchCall(),
new ApproveFunction { Spender = spender, Value = 500 }.ToBatchCall());
// With ETH value: ToBatchCall(ethValue)
await handler.BatchExecuteAsync(
new DepositFunction().ToBatchCall(ethValue: Web3.Convert.ToWei(1)));
// Simple: Just raw call data bytes
await handler.BatchExecuteAsync(callData1, callData2, callData3);
// Generic: Pass FunctionMessage objects directly (all same type)
await handler.BatchExecuteAsync(
new CountFunction(),
new CountFunction(),
new CountFunction());
Paymasters can sponsor gas costs for your users:
myContract.ChangeContractHandlerToAA(
accountAddress,
ownerKey,
bundlerService,
entryPointAddress)
.WithPaymaster(paymasterAddress);
// Or with custom paymaster data (e.g., for verifying paymasters)
.WithPaymaster(paymasterAddress, paymasterData);
// Or with dynamic paymaster data
.WithPaymaster(new PaymasterConfig(
paymasterAddress,
dataProvider: async (userOp) => {
// Generate signed paymaster data based on the UserOperation
return await GetSignedPaymasterData(userOp);
}));
myContract.ChangeContractHandlerToAA(...)
.WithGasConfig(new AAGasConfig
{
ReceiptPollIntervalMs = 1000, // How often to check for receipt
ReceiptTimeoutMs = 60000, // Max wait time for mining
VerificationGasBuffer = 5000, // Extra gas buffer for verification
CallGasBuffer = 10000, // Extra gas buffer for call execution
PreVerificationGasBuffer = 2000, // Extra gas buffer for pre-verification
CallGasMultiplier = 1.2, // Multiplier for call gas estimation
VerificationGasMultiplier = 1.5 // Multiplier for verification gas estimation
});
All configuration methods return the handler, allowing chaining:
var handler = myContract.ChangeContractHandlerToAA(
accountAddress, ownerKey, bundlerService, entryPointAddress)
.WithFactory(factoryConfig)
.WithPaymaster(paymasterAddress)
.WithGasConfig(gasConfig);
The AATransactionReceipt extends the standard TransactionReceipt with AA-specific fields:
var receipt = await myContract.SomeRequestAndWaitForReceiptAsync();
var aaReceipt = (AATransactionReceipt)receipt;
// Standard transaction fields (from the bundle transaction)
Console.WriteLine($"Block: {aaReceipt.BlockNumber}");
Console.WriteLine($"Tx Hash: {aaReceipt.TransactionHash}");
// AA-specific fields
Console.WriteLine($"UserOp Hash: {aaReceipt.UserOpHash}");
Console.WriteLine($"Success: {aaReceipt.UserOpSuccess}");
Console.WriteLine($"Revert Reason: {aaReceipt.RevertReason}");
Console.WriteLine($"Actual Gas Used: {aaReceipt.ActualGasUsed}");
Console.WriteLine($"Actual Gas Cost: {aaReceipt.ActualGasCost}");
Console.WriteLine($"Sender: {aaReceipt.Sender}");
Console.WriteLine($"Paymaster: {aaReceipt.Paymaster}");
You can inspect a UserOperation before sending it:
var handler = (AAContractHandler)myContract.ContractHandler;
// Create but don't send
var packedOp = await handler.CreateUserOperationAsync(
new TransferFunction { To = recipient, Value = amount });
Console.WriteLine($"Sender: {packedOp.Sender}");
Console.WriteLine($"Nonce: {packedOp.Nonce}");
Console.WriteLine($"InitCode length: {packedOp.InitCode?.Length ?? 0}");
Console.WriteLine($"CallData: {packedOp.CallData.ToHex()}");
// Estimate total gas for a UserOperation
var gas = await myContract.ContractHandler.EstimateGasAsync<TransferFunction>(
new TransferFunction { To = recipient, Value = amount });
Console.WriteLine($"Estimated gas: {gas.Value}");
// This includes: verificationGasLimit + callGasLimit + preVerificationGas
try
{
var receipt = await myContract.TransferRequestAndWaitForReceiptAsync(to, amount);
var aaReceipt = (AATransactionReceipt)receipt;
if (!aaReceipt.UserOpSuccess)
{
// UserOp was included but inner execution failed
Console.WriteLine($"Execution failed: {aaReceipt.RevertReason}");
}
}
catch (TimeoutException ex)
{
// UserOp wasn't mined within the timeout period
Console.WriteLine($"Timeout waiting for UserOp: {ex.Message}");
}
catch (RpcClientUnknownException ex)
{
// Bundler rejected the UserOp or connection failed
Console.WriteLine($"Bundler error: {ex.Message}");
}
The following services implement IContractHandlerService and support SwitchToAccountAbstraction():
| Service | Namespace |
|---|---|
ERC20ContractService |
Nethereum.Contracts.Standards.ERC20 |
ERC721ContractService |
Nethereum.Contracts.Standards.ERC721 |
ERC1155ContractService |
Nethereum.Contracts.Standards.ERC1155 |
ERC1271ContractService |
Nethereum.Contracts.Standards.ERC1271 |
ERC165SupportsInterfaceContractService |
Nethereum.Contracts.Standards.ERC165 |
EIP3009ContractService |
Nethereum.Contracts.Standards.EIP3009 |
ENSRegistryService |
Nethereum.Contracts.Standards.ENS |
ETHRegistrarControllerService |
Nethereum.Contracts.Standards.ENS |
PublicResolverService |
Nethereum.Contracts.Standards.ENS |
OffchainResolverService |
Nethereum.Contracts.Standards.ENS |
RegistrarService |
Nethereum.Contracts.Standards.ENS |
All generated contract services (extending ContractWeb3ServiceBase) support ChangeContractHandlerToAA().
Your Application
β
βΌ
βββββββββββββββββββββββββββ
β Contract Service β (ERC20Service, your generated services, etc.)
β with AAContractHandlerβ
βββββββββββββ¬ββββββββββββββ
β Creates UserOperation
βΌ
βββββββββββββββββββββββββββ
β Bundler Service β (IAccountAbstractionBundlerService)
β eth_sendUserOperation β
βββββββββββββ¬ββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββ
β Bundler β (Collects UserOps, creates bundle)
βββββββββββββ¬ββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββ
β EntryPoint Contract β (handleOps)
β 0x4337090...eD8D009 β (v0.9)
βββββββββββββ¬ββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββ
β Your Smart Account β (SimpleAccount, etc.)
β execute(target, data) β
βββββββββββββ¬ββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββ
β Target Contract β (ERC20, your contract, etc.)
βββββββββββββββββββββββββββ
using Nethereum.AccountAbstraction;
using Nethereum.AccountAbstraction.SimpleAccount;
using Nethereum.Signer;
using Nethereum.Web3;
// Setup
var web3 = new Web3("https://your-rpc-url");
var ownerKey = new EthECKey("your-private-key");
var bundlerService = new AccountAbstractionBundlerService(
new RpcClient(new Uri("https://your-bundler-url")));
// Deploy factory and get account address
var factory = await SimpleAccountFactoryService.DeployContractAndGetServiceAsync(
web3, new SimpleAccountFactoryDeployment { EntryPoint = entryPointAddress });
var accountAddress = await factory.GetAddressQueryAsync(
ownerKey.GetPublicAddress(), salt: 0);
// Fund the account
await web3.Eth.GetEtherTransferService()
.TransferEtherAndWaitForReceiptAsync(accountAddress, 0.5m);
// Get an ERC20 service
var usdc = web3.Eth.ERC20.GetContractService("0xUSDCAddress");
// Switch to Account Abstraction with auto-deployment
usdc.SwitchToAccountAbstraction(
accountAddress,
ownerKey,
bundlerService,
entryPointAddress,
factory: new FactoryConfig(factory.ContractAddress, ownerKey.GetPublicAddress(), 0));
// Transfer USDC via UserOperation
// First call will deploy the account, subsequent calls won't
var receipt = await usdc.TransferRequestAndWaitForReceiptAsync(
recipient,
Web3.Convert.ToWei(100, 6)); // 100 USDC (6 decimals)
var aaReceipt = (AATransactionReceipt)receipt;
Console.WriteLine($"Transfer {(aaReceipt.UserOpSuccess ? "succeeded" : "failed")}");
Console.WriteLine($"UserOp Hash: {aaReceipt.UserOpHash}");
Console.WriteLine($"Gas used: {aaReceipt.ActualGasUsed}");
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 was computed. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
Showing the top 1 NuGet packages that depend on Nethereum.AccountAbstraction:
| Package | Downloads |
|---|---|
|
Nethereum.AccountAbstraction.Bundler
Nethereum AccountAbstraction Bundler - ERC-4337 bundler with UserOperation validation, mempool, gas estimation, and bundle submission |
This package is not used by any popular GitHub repositories.