Interacting with a Bundler
The BundlerProvider provides a convenient way to interact with a bundler for sending and tracking user operations on any supported EVM network.
Setting up the BundlerProvider
import 'package:variance_dart/variance_dart.dart';
final chain = Chains.getChain(Network.ethereum);
final bundlerProvider = BundlerProvider(chain); Properties
The BundlerProvider has the following properties:
rpc: The remote procedure call (RPC) client used to communicate with the bundler. It is an instance of theRPCBaseclass.
Methods
estimateUserOperationGas
The estimateUserOperationGas method allows you to estimate the gas cost for a user operation before sending it to the bundler. Here's an example:
import 'package:variance_dart/variance_dart.dart';
import 'package:web3dart/web3dart.dart';
// example transfering Eth to another recipient
final entrypoint = EntryPointAddress.v06;
final recipient = '0x1234567890123456789012345678901234567890';
final amount = EtherAmount.fromUnitAndValue(EtherUnit.wei, 1);
final userOp = UserOperation.partial(callData: Contract.execute(smartAccountAddress,
to: recipient, amount: amount))
try {
final gasEstimate = await bundlerProvider.estimateUserOperationGas(userOp, entrypoint);
print('Estimated gas cost: ${gasEstimate.callGasLimit}');
} catch (e) {
print('Error estimating gas cost: $e');
}getUserOperationByHash
The getUserOperationByHash method allows you to retrieve a user operation by its hash. Here's an example:
import 'package:variance_dart/variance_dart.dart';
import 'package:web3dart/web3dart.dart';
// example transfering Eth to another recipient
final entrypoint = EntryPointAddress.v06;
final recipient = '0x1234567890123456789012345678901234567890';
final amount = EtherAmount.fromUnitAndValue(EtherUnit.wei, 1);
final userOp = UserOperation.partial(callData: Contract.execute(smartAccountAddress,
to: recipient, amount: amount))
final userOpHash = userOp.hash(chain); // chain is an instance of the Chain class
// the hash operation requires some chain parameters to calculate the hash
try {
final userOp = await bundlerProvider.getUserOperationByHash(userOpHash);
print('User operation: $userOp');
} catch (e) {
print('Error retrieving user operation: $e');
}getUserOpReceipt
The getUserOpReceipt method allows you to retrieve the receipt of a user operation by its hash. Here's an example:
import 'package:variance_dart/variance_dart.dart';
import 'package:web3dart/web3dart.dart';
// example transfering Eth to another recipient
final entrypoint = EntryPointAddress.v06;
final recipient = '0x1234567890123456789012345678901234567890';
final amount = EtherAmount.fromUnitAndValue(EtherUnit.wei, 1);
final userOp = UserOperation.partial(callData: Contract.execute(smartAccountAddress,
to: recipient, amount: amount))
final userOpHash = userOp.hash(chain);
try {
final receipt = await bundlerProvider.getUserOpReceipt(userOpHash);
print('User operation receipt: $receipt');
} catch (e) {
print('Error retrieving user operation receipt: $e');
}sendUserOperation
The sendUserOperation method allows you to send a user operation to the bundler. Here's an example:
// after estimating UserOperation gas and updating the userOp object
try {
final response = await bundlerProvider.sendUserOperation(userOp, entrypoint);
print('User operation hash: ${response.userOpHash}');
// Wait for the user operation receipt
final receipt = await response.wait();
print('User operation receipt: $receipt');
} catch (e) {
print('Error sending user operation: $e');
}supportedEntryPoints
The supportedEntryPoints method allows you to retrieve the list of supported entry points from the bundler. Here's an example:
try {
final entryPoints = await bundlerProvider.supportedEntryPoints();
print('Supported entry points: $entryPoints');
} catch (e) {
print('Error retrieving supported entry points: $e');
}These are high level abstraction over these methods: eth_estimateUserOperationGas, eth_getUserOperationByHash, eth_getUserOperationReceipt, eth_sendUserOperation, eth_supportedEntryPoints and uses an instance of the RPCBase class to communicate with the bundler.
The RPCBase class provides a simple and consistent interface for making RPC requests and handling responses allowing you to implement custom methods like bundler specific methods. e.g pimlico_getUserOperationGas
final RPCBase rpc = RPCBase(bundlerUrl);
Future<Map<String, dynamic>> getPimlicoBundlerOpGas(
Map<String, dynamic> userOp, EntryPointAddress entrypoint) async {
final res = await rpc.send<Map<String, dynamic>>(
'pimlico_getUserOperationGas', [userOp, entrypoint.address.hex]);
return res;
}