userop.dart
is a comprehensive library tailored for crafting ERC-4337 User Operations. While web3dart
equips developers to effortlessly generate standard EVM transactions, userop.dart
streamlines the creation and dispatch of User Operations to ERC-4337 Bundlers.
-
đź’Ş Versatile Implementation: Suitable for generating User Operations for any ERC-4337 Smart Account, Bundler platform, or Paymaster.
-
🏗️ User-friendly architecture: Adopts the builder design pattern, reflecting the real-world construction of User Operations.
dependencies:
userop: [latest-version]
Connecting to an ERC-4337 bundler is easy using userop.dart
userop.dart
allows you connect to a bundler RPC using the client interface.
An instance of a client is an abstraction for building and sending your User Operations to the eth_sendUserOperation
RPC method on a bundler.
import 'package:userop/userop.dart';
final String bundlerRPC = 'YOUR_BUNDLER_RPC_URL';
final iClientOpts = IClientOpts()
..overrideBundlerRpc = bundlerRPC
..entryPoint = EthereumAddress.fromHex('YOU_ENTRY_POINT' ?? ERC4337.ENTRY_POINT);
final client = await Client.init(
bundlerRPC,
opts: iClientOpts,
);
A method for directing a builder
instance to create a User Operation and send it to a bundler via eth_sendUserOperation
.
import 'package:userop/userop.dart';
final response = await client.sendUserOperation(
await simpleAccount.execute(
Call(
to: targetAddress,
value: amount,
data: Uint8List(0),
)
),
opts: sendOpts,
);
final filterEvent = await response.wait();
This method can be used to direct a builder using the client's entryPoint and chainID. However it will only return the UserOperation and not initiate a send request.
final userOp = await client.buildUserOperation(builder);
A instance of a client has several constants that can be set.
// The maximum amount of time to wait for the UserOperationEvent after calling response.wait()
client.waitTimeoutMs = 30000;
// The interval at which it will poll the node to look up UserOperationEvent.
client.waitIntervalMs = 5000;
userop.dart
provides a straightforward wrapper over web3dart
JsonRPC, offering the flexibility to re-route bundler methods. By default, it assumes that both bundler and node methods share the same RPC url. However, in instances where this isn't the case, this module offers the added capability to override the bundler RPC, allowing all bundler RPC methods to be redirected to a different endpoint.
import 'package:userop/userop.dart';
import 'package:http/http.dart' as http;
final String bundlerRPC = 'YOUR_BUNDLER_RPC_URL';
final provider = BundlerJsonRpcProvider(rpcUrl, http.Client());
userop.dart
comes bundled with common presets, facilitating a quicker setup for specific use cases.
Builder presets offer pre-configured builders for known contract account implementations. These presets can be utilized directly or can be customized using get and set functions.
The Kernel preset is an abstraction to build User Operations for an ERC-4337 account based on ZeroDev Kernel V2 - a modular contract account framework. It deploys with the ECDSA validator by default.
import 'package:userop/userop.dart';
final targetAddress = EthereumAddress.fromHex('YOUR_TARGET_ADDRESS');
final amount = BigInt.parse('AMOUNT_IN_WEI');
final signingKey = EthPrivateKey.fromHex('YOUR_PRIVATE_KEY');
final bundlerRPC = 'YOUR_BUNDLER_RPC_URL';
final opts = IPresetBuilderOpts()
..factoryAddress = EthereumAddress.fromHex(
'YOUR_FACTORY_ADDRESS',
);
final kernel = await Kernel.init(
signingKey,
bundlerRPC,
opts: opts,
);
final client = await Client.init(bundlerRPC);
final res = await client.sendUserOperation(
await kernel.execute(
Call(
to: targetAddress,
value: amount,
data: Uint8List(0),
),
),
);
print('UserOpHash: ${res.userOpHash}');
print('Waiting for transaction...');
final ev = await res.wait();
print('Transaction hash: ${ev?.transactionHash}');
The EtherspotWallet
preset provides an abstraction to construct User Operations for an ERC-4337 account. It's based on EtherspotWallet.sol.
import 'package:userop/userop.dart';
final targetAddress = EthereumAddress.fromHex('YOUR_TARGET_ADDRESS');
final amount = BigInt.parse('AMOUNT_IN_WEI');
final signingKey = EthPrivateKey.fromHex('YOUR_PRIVATE_KEY');
final bundlerRPC = 'YOUR_BUNDLER_RPC_URL';
final etherspotWallet = await EtherspotWallet.init(
signingKey,
bundlerRPC,
);
final client = await Client.init(bundlerRPC);
final res = await client.sendUserOperation(
await etherspotWallet.execute(
Call(
to: targetAddress,
value: amount,
data: Uint8List(0),
),
),
);
print('UserOpHash: ${res.userOpHash}');
print('Waiting for transaction...');
final ev = await res.wait();
print('Transaction hash: ${ev?.transactionHash}');
The SimpleAccount
preset provides an abstraction to construct User Operations for an ERC-4337 account. It's based on SimpleAccount.sol.
import 'package:userop/userop.dart';
final targetAddress = EthereumAddress.fromHex('YOUR_TARGET_ADDRESS');
final amount = BigInt.parse('AMOUNT_IN_WEI');
final signingKey = EthPrivateKey.fromHex('YOUR_PRIVATE_KEY');
final bundlerRPC = 'YOUR_BUNDLER_RPC_URL';
final simpleAccount = await SimpleAccount.init(
signingKey,
bundlerRPC,
);
final client = await Client.init(bundlerRPC);
final res = await client.sendUserOperation(
await simpleAccount.execute(
Call(
to: targetAddress,
value: amount,
data: Uint8List(0),
),
),
);
print('UserOpHash: ${res.userOpHash}');
print('Waiting for transaction...');
final ev = await res.wait();
print('Transaction hash: ${ev?.transactionHash}');
Middleware presets are reusable implementations of middleware functions tailored for different builder instances.
This middleware function is designed for sending UserOperations to the eth_estimateUserOperationGas
endpoint, ensuring accurate gas limit estimations for preVerificationGas
, verificationGasLimit
, and callGasLimit
.
import 'package:userop/userop.dart';
final builder = UserOperationBuilder();
builder = builder.useMiddleware(estimateUserOperationGas(
Web3Client('RPC_URL', http.Client()),
BundlerJsonRpcProvider('RPC_URL', http.Client()),
))
This middleware function retrieves the latest values for maxFeePerGas
and maxPriorityFeePerGas
.
import 'package:userop/userop.dart';
final builder = UserOperationBuilder();
builder = builder.useMiddleware(getGasPrice(
Web3Client('RPC_URL', http.Client()),
BundlerJsonRpcProvider('RPC_URL', http.Client()),
))
This middleware function requests gas sponsorship from a Paymaster service. It assumes the service adheres to the proposed JSON-RPC API for verifying paymasters.
final paymasterMiddleware = verifyingPaymaster(
'YOUR_PAYMASTER_SERVICE_URL',
{},
);
final IPresetBuilderOpts opts = IPresetBuilderOpts()
..paymasterMiddleware = paymasterMiddleware;
final simpleAccount = await SimpleAccount.init(
signingKey,
bundlerRPC,
opts: opts,
);
A middleware function designed to sign the User Operation using an EOA private key.
import 'package:userop/userop.dart';
final builder = UserOperationBuilder();
builder = builder.useMiddleware(eOASignature(signer))