From 6c8b22ad9e105872c8383337249e386fb031e2b4 Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Tue, 27 Feb 2024 10:38:05 +0100 Subject: [PATCH] add support for entrypoint v0.7 in parallel --- CHANGELOG.md | 4 + README.md | 6 +- example/lib/main.dart | 4 +- lib/src/4337/chains.dart | 112 ++++++++------ lib/src/4337/paymaster.dart | 2 +- lib/src/4337/providers.dart | 4 +- lib/src/4337/userop.dart | 187 ++++++++++++++++------- lib/src/4337/wallet.dart | 78 +++++----- lib/src/interfaces/bundler_provider.dart | 8 +- lib/src/interfaces/interfaces.dart | 1 + lib/src/interfaces/smart_wallet.dart | 12 -- lib/src/interfaces/user_operations.dart | 8 + lib/src/utils/crypto.dart | 26 ++++ lib/utils.dart | 1 + pubspec.yaml | 2 +- 15 files changed, 292 insertions(+), 163 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbd26f3..4e66c3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.9 + +* Add support for entrypoint v0.7 in parrallel. + ## 0.0.8 * Add paymaster as a plugin diff --git a/README.md b/README.md index 51352e4..f833e26 100644 --- a/README.md +++ b/README.md @@ -50,16 +50,16 @@ const String rpcUrl = 'http://localhost:8545'; const String bundlerUrl = 'http://localhost:3000/rpc'; chain = Chain( - ethRpcUrl: rpcUrl, + jsonRpcUrl: rpcUrl, bundlerUrl: bundlerUrl, - entrypoint: Constants.entrypoint, + entrypoint: EntryPoint.v06, accountFactory: Constants.accountFactory, chainId: 1337, explorer: ""); // using pre configured chain chain = Chains.getChain(Network.localhost) - ..ethRpcUrl = rpcUrl + ..jsonRpcUrl = rpcUrl ..bundlerUrl = bundlerUrl; ``` diff --git a/example/lib/main.dart b/example/lib/main.dart index 8695d3d..a679b75 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -15,9 +15,9 @@ Future main() async { // configure your chain final Chain chain = Chain( - ethRpcUrl: rpcUrl, + jsonRpcUrl: rpcUrl, bundlerUrl: bundlerUrl, - entrypoint: Constants.entrypointv06, + entrypoint: EntryPoint.v06, accountFactory: EthereumAddress.fromHex("0xCCaE5F64307D86346B83E55e7865f77906F9c7b4"), chainId: 1337, diff --git a/lib/src/4337/chains.dart b/lib/src/4337/chains.dart index b855c2c..b42887a 100644 --- a/lib/src/4337/chains.dart +++ b/lib/src/4337/chains.dart @@ -3,9 +3,9 @@ part of '../../variance.dart'; class Chain { final int chainId; final String explorer; - final EthereumAddress entrypoint; + final EntryPoint entrypoint; EthereumAddress? accountFactory; - String? ethRpcUrl; + String? jsonRpcUrl; String? bundlerUrl; String? paymasterUrl; @@ -14,13 +14,13 @@ class Chain { required this.explorer, required this.entrypoint, this.accountFactory, - this.ethRpcUrl, + this.jsonRpcUrl, this.bundlerUrl, this.paymasterUrl}); - /// asserts that [ethRpcUrl] and [bundlerUrl] is provided + /// asserts that [jsonRpcUrl] and [bundlerUrl] is provided Chain validate() { - require(isURL(ethRpcUrl), + require(isURL(jsonRpcUrl), "Chain Config Error: please provide a valid eth rpc url"); require(isURL(bundlerUrl), "Chain Config Error: please provide a valid bundler url"); @@ -36,134 +36,134 @@ class Chains { Network.ethereum: Chain( chainId: 1, explorer: "https://etherscan.io/", - ethRpcUrl: "https://rpc.ankr.com/eth", - entrypoint: Constants.entrypointv06, + jsonRpcUrl: "https://rpc.ankr.com/eth", + entrypoint: EntryPoint.v06, ), Network.polygon: Chain( chainId: 137, explorer: "https://polygonscan.com/", - ethRpcUrl: "https://polygon-rpc.com/", - entrypoint: Constants.entrypointv06, + jsonRpcUrl: "https://polygon-rpc.com/", + entrypoint: EntryPoint.v06, ), Network.optimism: Chain( chainId: 10, explorer: "https://explorer.optimism.io", - ethRpcUrl: "https://mainnet.optimism.io", - entrypoint: Constants.entrypointv06, + jsonRpcUrl: "https://mainnet.optimism.io", + entrypoint: EntryPoint.v06, ), Network.base: Chain( chainId: 8453, explorer: "https://basescan.org", - ethRpcUrl: "https://mainnet.base.org", - entrypoint: Constants.entrypointv06, + jsonRpcUrl: "https://mainnet.base.org", + entrypoint: EntryPoint.v06, ), Network.arbitrumOne: Chain( chainId: 42161, explorer: "https://arbiscan.io/", - ethRpcUrl: "https://arb1.arbitrum.io/rpc", - entrypoint: Constants.entrypointv06, + jsonRpcUrl: "https://arb1.arbitrum.io/rpc", + entrypoint: EntryPoint.v06, ), Network.mantle: Chain( chainId: 5000, explorer: "https://explorer.mantle.xyz/", - ethRpcUrl: "https://rpc.mantle.xyz/", - entrypoint: Constants.entrypointv06, + jsonRpcUrl: "https://rpc.mantle.xyz/", + entrypoint: EntryPoint.v06, ), Network.linea: Chain( chainId: 59144, explorer: "https://lineascan.build/", - ethRpcUrl: "https://rpc.linea.build", - entrypoint: Constants.entrypointv06), + jsonRpcUrl: "https://rpc.linea.build", + entrypoint: EntryPoint.v06), Network.avalanche: Chain( chainId: 43114, explorer: "https://snowtrace.io/", - ethRpcUrl: "https://api.avax.network/ext/bc/C/rpc", - entrypoint: Constants.entrypointv06, + jsonRpcUrl: "https://api.avax.network/ext/bc/C/rpc", + entrypoint: EntryPoint.v06, ), Network.gnosis: Chain( chainId: 100, explorer: "https://gnosisscan.io/", - ethRpcUrl: "https://rpc.ankr.com/gnosis", - entrypoint: Constants.entrypointv06, + jsonRpcUrl: "https://rpc.ankr.com/gnosis", + entrypoint: EntryPoint.v06, ), Network.celo: Chain( chainId: 42220, explorer: "https://celoscan.io/", - ethRpcUrl: "https://forno.celo.org", - entrypoint: Constants.entrypointv06, + jsonRpcUrl: "https://forno.celo.org", + entrypoint: EntryPoint.v06, ), Network.fantom: Chain( chainId: 250, explorer: "https://ftmscan.com/", - ethRpcUrl: "https://rpc.fantom.network", - entrypoint: Constants.entrypointv06, + jsonRpcUrl: "https://rpc.fantom.network", + entrypoint: EntryPoint.v06, ), Network.opBnB: Chain( chainId: 204, explorer: "http://opbnbscan.com/", - ethRpcUrl: "https://opbnb-mainnet-rpc.bnbchain.org", - entrypoint: Constants.entrypointv06, + jsonRpcUrl: "https://opbnb-mainnet-rpc.bnbchain.org", + entrypoint: EntryPoint.v06, ), Network.arbitrumNova: Chain( chainId: 42170, explorer: "https://nova.arbiscan.io/", - ethRpcUrl: "https://nova.arbitrum.io/rpc", - entrypoint: Constants.entrypointv06, + jsonRpcUrl: "https://nova.arbitrum.io/rpc", + entrypoint: EntryPoint.v06, ), Network.polygonzkEvm: Chain( chainId: 1101, explorer: "https://zkevm.polygonscan.com/", - ethRpcUrl: "https://polygonzkevm-mainnet.g.alchemy.com/v2/demo", - entrypoint: Constants.entrypointv06, + jsonRpcUrl: "https://polygonzkevm-mainnet.g.alchemy.com/v2/demo", + entrypoint: EntryPoint.v06, ), Network.scroll: Chain( chainId: 534352, explorer: "https://scrollscan.com/", - ethRpcUrl: "https://rpc.scroll.io/", - entrypoint: Constants.entrypointv06, + jsonRpcUrl: "https://rpc.scroll.io/", + entrypoint: EntryPoint.v06, ), Network.mode: Chain( chainId: 34443, explorer: "https://explorer.mode.network/", - ethRpcUrl: "https://mainnet.mode.network/", - entrypoint: Constants.entrypointv06, + jsonRpcUrl: "https://mainnet.mode.network/", + entrypoint: EntryPoint.v06, ), Network.sepolia: Chain( chainId: 11155111, explorer: "https://sepolia.etherscan.io/", - ethRpcUrl: "https://rpc.sepolia.org", - entrypoint: Constants.entrypointv06, + jsonRpcUrl: "https://rpc.sepolia.org", + entrypoint: EntryPoint.v06, ), Network.mumbai: Chain( chainId: 80001, explorer: "https://mumbai.polygonscan.com/", - ethRpcUrl: "https://rpc-mumbai.maticvigil.com/", - entrypoint: Constants.entrypointv06, + jsonRpcUrl: "https://rpc-mumbai.maticvigil.com/", + entrypoint: EntryPoint.v06, ), Network.baseTestent: Chain( chainId: 84531, explorer: "https://sepolia.basescan.org", - ethRpcUrl: "https://api-sepolia.basescan.org/api", - entrypoint: Constants.entrypointv06, + jsonRpcUrl: "https://api-sepolia.basescan.org/api", + entrypoint: EntryPoint.v06, ), Network.fuji: Chain( chainId: 43113, explorer: "https://testnet.snowtrace.io/", - ethRpcUrl: "https://api.avax-test.network/ext/bc/C/rpc", - entrypoint: Constants.entrypointv06, + jsonRpcUrl: "https://api.avax-test.network/ext/bc/C/rpc", + entrypoint: EntryPoint.v06, ), Network.katla: Chain( chainId: 167008, explorer: "https://explorer.katla.taiko.xyz/", - ethRpcUrl: "https://rpc.katla.taiko.xyz", - entrypoint: Constants.entrypointv06, + jsonRpcUrl: "https://rpc.katla.taiko.xyz", + entrypoint: EntryPoint.v06, ), Network.localhost: Chain( chainId: 1337, explorer: "http://localhost:8545", - ethRpcUrl: "http://localhost:8545", + jsonRpcUrl: "http://localhost:8545", bundlerUrl: "http://localhost:3000/rpc", - entrypoint: Constants.entrypointv06, + entrypoint: EntryPoint.v06, ) }; @@ -177,7 +177,7 @@ class Chains { class Constants { static EthereumAddress entrypointv06 = EthereumAddress.fromHex("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"); - static EthereumAddress entrypoint = + static EthereumAddress entrypointv07 = EthereumAddress.fromHex("0x0000000071727De22E5E9d8BAf0edAc6f37da032"); static EthereumAddress zeroAddress = EthereumAddress.fromHex("0x0000000000000000000000000000000000000000"); @@ -185,6 +185,18 @@ class Constants { Constants._(); } +enum EntryPoint { + v06(0.6, "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"), + v07(0.7, "0x0000000071727De22E5E9d8BAf0edAc6f37da032"); + + final double version; + final String hex; + + const EntryPoint(this.version, this.hex); + + EthereumAddress get address => EthereumAddress.fromHex(hex); +} + enum Network { // mainnet ethereum, diff --git a/lib/src/4337/paymaster.dart b/lib/src/4337/paymaster.dart index eee49df..a96b65a 100644 --- a/lib/src/4337/paymaster.dart +++ b/lib/src/4337/paymaster.dart @@ -47,7 +47,7 @@ class Paymaster { } Future sponsorUserOperation(Map userOp, - EthereumAddress entrypoint, Map? context) async { + EntryPoint entrypoint, Map? context) async { final response = await _rpc.send>( 'pm_sponsorUserOperation', [userOp, entrypoint.hex, context]); return PaymasterResponse.fromMap(response); diff --git a/lib/src/4337/providers.dart b/lib/src/4337/providers.dart index 48f3e09..903b5e2 100644 --- a/lib/src/4337/providers.dart +++ b/lib/src/4337/providers.dart @@ -12,7 +12,7 @@ class BundlerProvider implements BundlerProviderBase { @override Future estimateUserOperationGas( - Map userOp, EthereumAddress entrypoint) async { + Map userOp, EntryPoint entrypoint) async { require(_initialized, "estimateUserOpGas: Wallet Provider not initialized"); final opGas = await _rpc.send>( 'eth_estimateUserOperationGas', [userOp, entrypoint.hex]); @@ -37,7 +37,7 @@ class BundlerProvider implements BundlerProviderBase { @override Future sendUserOperation( - Map userOp, EthereumAddress entrypoint) async { + Map userOp, EntryPoint entrypoint) async { require(_initialized, "sendUserOp: Wallet Provider not initialized"); final opHash = await _rpc .send('eth_sendUserOperation', [userOp, entrypoint.hex]); diff --git a/lib/src/4337/userop.dart b/lib/src/4337/userop.dart index 9e069f6..c7522bf 100644 --- a/lib/src/4337/userop.dart +++ b/lib/src/4337/userop.dart @@ -14,20 +14,30 @@ class UserOperation implements UserOperationBase { final Uint8List callData; @override + @Deprecated("now packed in accountGasLimits from v0.7") final BigInt callGasLimit; @override + @Deprecated("now packed in accountGasLimits from v0.7") final BigInt verificationGasLimit; + @override + final Uint8List accountGasLimits; + @override final BigInt preVerificationGas; @override + @Deprecated("now packed in gasFees from v0.7") BigInt maxFeePerGas; @override + @Deprecated("now packed in gasFees from v0.7") BigInt maxPriorityFeePerGas; + @override + final Uint8List gasFees; + @override String signature; @@ -42,8 +52,10 @@ class UserOperation implements UserOperationBase { required this.callGasLimit, required this.verificationGasLimit, required this.preVerificationGas, + required this.accountGasLimits, required this.maxFeePerGas, required this.maxPriorityFeePerGas, + required this.gasFees, required this.signature, required this.paymasterAndData, }); @@ -57,11 +69,25 @@ class UserOperation implements UserOperationBase { nonce: BigInt.parse(map['nonce'] as String), initCode: hexToBytes(map['initCode'] as String), callData: hexToBytes(map['callData'] as String), - callGasLimit: BigInt.parse(map['callGasLimit'] as String), - verificationGasLimit: BigInt.parse(map['verificationGasLimit'] as String), + callGasLimit: map['callGasLimit'] != null + ? BigInt.parse(map['callGasLimit'] as String) + : BigInt.zero, + verificationGasLimit: map['verificationGasLimit'] != null + ? BigInt.parse(map['verificationGasLimit'] as String) + : BigInt.zero, + accountGasLimits: map['accountGasLimits'] != null + ? hexToBytes(map['accountGasLimits'] as String) + : Uint8List(0), preVerificationGas: BigInt.parse(map['preVerificationGas'] as String), - maxFeePerGas: BigInt.parse(map['maxFeePerGas'] as String), - maxPriorityFeePerGas: BigInt.parse(map['maxPriorityFeePerGas'] as String), + maxFeePerGas: map['maxFeePerGas'] != null + ? BigInt.parse(map['maxFeePerGas'] as String) + : BigInt.zero, + maxPriorityFeePerGas: map['maxPriorityFeePerGas'] != null + ? BigInt.parse(map['maxPriorityFeePerGas'] as String) + : BigInt.zero, + gasFees: map['gasFees'] != null + ? hexToBytes(map['gasFees'] as String) + : Uint8List(0), signature: map['signature'] as String, paymasterAndData: hexToBytes(map['paymasterAndData'] as String), ); @@ -90,17 +116,18 @@ class UserOperation implements UserOperationBase { /// // Other parameters can be set as needed. /// ); /// ``` - factory UserOperation.partial({ - required Uint8List callData, - EthereumAddress? sender, - BigInt? nonce, - Uint8List? initCode, - BigInt? callGasLimit, - BigInt? verificationGasLimit, - BigInt? preVerificationGas, - BigInt? maxFeePerGas, - BigInt? maxPriorityFeePerGas, - }) => + factory UserOperation.partial( + {required Uint8List callData, + EthereumAddress? sender, + BigInt? nonce, + Uint8List? initCode, + BigInt? callGasLimit, + BigInt? verificationGasLimit, + Uint8List? accountGasLimits, + BigInt? preVerificationGas, + BigInt? maxFeePerGas, + BigInt? maxPriorityFeePerGas, + Uint8List? gasFees}) => UserOperation( sender: sender ?? Constants.zeroAddress, nonce: nonce ?? BigInt.zero, @@ -109,8 +136,10 @@ class UserOperation implements UserOperationBase { callGasLimit: callGasLimit ?? BigInt.from(250000), verificationGasLimit: verificationGasLimit ?? BigInt.from(750000), preVerificationGas: preVerificationGas ?? BigInt.from(51000), + accountGasLimits: accountGasLimits ?? Uint8List(0), maxFeePerGas: maxFeePerGas ?? BigInt.one, maxPriorityFeePerGas: maxPriorityFeePerGas ?? BigInt.one, + gasFees: gasFees ?? Uint8List(0), signature: "0x", paymasterAndData: Uint8List(0), ); @@ -122,9 +151,11 @@ class UserOperation implements UserOperationBase { Uint8List? callData, BigInt? callGasLimit, BigInt? verificationGasLimit, + Uint8List? accountGasLimits, BigInt? preVerificationGas, BigInt? maxFeePerGas, BigInt? maxPriorityFeePerGas, + Uint8List? gasFees, String? signature, Uint8List? paymasterAndData, }) { @@ -135,9 +166,11 @@ class UserOperation implements UserOperationBase { callData: callData ?? this.callData, callGasLimit: callGasLimit ?? this.callGasLimit, verificationGasLimit: verificationGasLimit ?? this.verificationGasLimit, + accountGasLimits: accountGasLimits ?? this.accountGasLimits, preVerificationGas: preVerificationGas ?? this.preVerificationGas, maxFeePerGas: maxFeePerGas ?? this.maxFeePerGas, maxPriorityFeePerGas: maxPriorityFeePerGas ?? this.maxPriorityFeePerGas, + gasFees: gasFees ?? this.gasFees, signature: signature ?? this.signature, paymasterAndData: paymasterAndData ?? this.paymasterAndData, ); @@ -145,31 +178,54 @@ class UserOperation implements UserOperationBase { @override Uint8List hash(Chain chain) { - final encoded = keccak256(abi.encode([ - 'address', - 'uint256', - 'bytes32', - 'bytes32', - 'uint256', - 'uint256', - 'uint256', - 'uint256', - 'uint256', - 'bytes32', - ], [ - sender, - nonce, - keccak256(initCode), - keccak256(callData), - callGasLimit, - verificationGasLimit, - preVerificationGas, - maxFeePerGas, - maxPriorityFeePerGas, - keccak256(paymasterAndData), - ])); + Uint8List encoded; + if (chain.entrypoint == EntryPoint.v07) { + encoded = keccak256(abi.encode([ + 'address', + 'uint256', + 'bytes32', + 'bytes32', + 'bytes32', + 'uint256', + 'bytes32', + 'bytes32', + ], [ + sender, + nonce, + keccak256(initCode), + keccak256(callData), + accountGasLimits, + preVerificationGas, + gasFees, + keccak256(paymasterAndData), + ])); + } else { + encoded = keccak256(abi.encode([ + 'address', + 'uint256', + 'bytes32', + 'bytes32', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'bytes32', + ], [ + sender, + nonce, + keccak256(initCode), + keccak256(callData), + callGasLimit, + verificationGasLimit, + preVerificationGas, + maxFeePerGas, + maxPriorityFeePerGas, + keccak256(paymasterAndData), + ])); + } return keccak256(abi.encode(['bytes32', 'address', 'uint256'], - [encoded, chain.entrypoint, BigInt.from(chain.chainId)])); + [encoded, chain.entrypoint.address, BigInt.from(chain.chainId)])); } @override @@ -179,30 +235,46 @@ class UserOperation implements UserOperationBase { @override Map toMap() { - return { + Map op = { 'sender': sender.hexEip55, 'nonce': '0x${nonce.toRadixString(16)}', 'initCode': hexlify(initCode), 'callData': hexlify(callData), 'callGasLimit': '0x${callGasLimit.toRadixString(16)}', - 'verificationGasLimit': '0x${verificationGasLimit.toRadixString(16)}', - 'preVerificationGas': '0x${preVerificationGas.toRadixString(16)}', - 'maxFeePerGas': '0x${maxFeePerGas.toRadixString(16)}', - 'maxPriorityFeePerGas': '0x${maxPriorityFeePerGas.toRadixString(16)}', 'signature': signature, 'paymasterAndData': hexlify(paymasterAndData), }; + if (accountGasLimits.isEmpty || accountGasLimits == Uint8List(0)) { + op['verificationGasLimit'] = + '0x${verificationGasLimit.toRadixString(16)}'; + op['preVerificationGas'] = '0x${preVerificationGas.toRadixString(16)}'; + } else { + op['accountGasLimits'] = hexlify(accountGasLimits); + } + + if (gasFees.isEmpty || gasFees == Uint8List(0)) { + op['maxFeePerGas'] = '0x${maxFeePerGas.toRadixString(16)}'; + op['maxPriorityFeePerGas'] = + '0x${maxPriorityFeePerGas.toRadixString(16)}'; + } else { + op['gasFees'] = hexlify(gasFees); + } + + return op; } @override UserOperation updateOpGas( - UserOperationGas? opGas, Map? feePerGas) { + UserOperationGas? opGas, Map? feePerGas) { return copyWith( callGasLimit: opGas?.callGasLimit, verificationGasLimit: opGas?.verificationGasLimit, + accountGasLimits: opGas?.accountGasLimits, preVerificationGas: opGas?.preVerificationGas, - maxFeePerGas: feePerGas?["maxFeePerGas"]!.getInWei, - maxPriorityFeePerGas: feePerGas?["maxPriorityFeePerGas"]!.getInWei); + maxFeePerGas: (feePerGas?["maxFeePerGas"] as EtherAmount?)?.getInWei, + maxPriorityFeePerGas: + (feePerGas?["maxPriorityFeePerGas"] as EtherAmount?)?.getInWei, + gasFees: feePerGas?["gasFees"] as Uint8List?); } Future validate(bool deployed, [String? initCode]) async { @@ -242,27 +314,36 @@ class UserOperationByHash { } class UserOperationGas { - final BigInt preVerificationGas; + final BigInt callGasLimit; final BigInt verificationGasLimit; + final Uint8List accountGasLimits; + final BigInt preVerificationGas; BigInt? validAfter; BigInt? validUntil; - final BigInt callGasLimit; UserOperationGas({ - required this.preVerificationGas, + required this.callGasLimit, required this.verificationGasLimit, + required this.accountGasLimits, + required this.preVerificationGas, this.validAfter, this.validUntil, - required this.callGasLimit, }); factory UserOperationGas.fromMap(Map map) { return UserOperationGas( + callGasLimit: map['callGasLimit'] != null + ? BigInt.parse(map['callGasLimit']) + : BigInt.zero, + verificationGasLimit: map['callGasLimit'] != null + ? BigInt.parse(map['verificationGasLimit']) + : BigInt.zero, + accountGasLimits: map['accountGasLimits'] != null + ? hexToBytes(map['accountGasLimits']) + : Uint8List(0), preVerificationGas: BigInt.parse(map['preVerificationGas']), - verificationGasLimit: BigInt.parse(map['verificationGasLimit']), validAfter: map['validAfter'] != null ? BigInt.parse(map['validAfter']) : null, validUntil: map['validUntil'] != null ? BigInt.parse(map['validUntil']) : null, - callGasLimit: BigInt.parse(map['callGasLimit']), ); } } diff --git a/lib/src/4337/wallet.dart b/lib/src/4337/wallet.dart index 518e90e..af2ec8c 100644 --- a/lib/src/4337/wallet.dart +++ b/lib/src/4337/wallet.dart @@ -20,17 +20,17 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { // since the wallet factory will use builder pattern to add plugins // the following can be moved into the factory. // which would allow the smartwallet to reamin testable. - final ethRpc = RPCProvider(chain.ethRpcUrl!); + final jsonRpc = RPCProvider(chain.jsonRpcUrl!); final bundlerRpc = RPCProvider(chain.bundlerUrl!); final bundler = BundlerProvider(chain, bundlerRpc); final fact = _AccountFactory( - address: chain.accountFactory!, chainId: chain.chainId, rpc: ethRpc); + address: chain.accountFactory!, chainId: chain.chainId, rpc: jsonRpc); addPlugin('signer', signer); addPlugin('bundler', bundler); - addPlugin('ethRpc', ethRpc); - addPlugin('contract', Contract(ethRpc)); + addPlugin('jsonRpc', jsonRpc); + addPlugin('contract', Contract(jsonRpc)); addPlugin('factory', fact); if (chain.paymasterUrl != null) { @@ -114,13 +114,13 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { return Uint8List.fromList(extended); } - Future get _initCodeGas => - plugin('ethRpc').estimateGas(_chain.entrypoint, _initCode); + Future get _initCodeGas => plugin('jsonRpc') + .estimateGas(_chain.entrypoint.address, _initCode); @override Future createSimpleAccount(Uint256 salt, {int? index}) async { - EthereumAddress signer = - EthereumAddress.fromHex(plugin('signer').getAddress(index: index ?? 0)); + EthereumAddress signer = EthereumAddress.fromHex( + plugin('signer').getAddress(index: index ?? 0)); _initCalldata = _getInitCallData('createAccount', [signer, salt.value]); await getSimpleAccountAddress(signer, salt) .then((addr) => {_walletAddress = addr}); @@ -150,35 +150,27 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { @override Future getSimpleAccountAddress( EthereumAddress signer, Uint256 salt) => - plugin('factory').getAddress(signer, salt.value); + plugin('factory').getAddress(signer, salt.value); @override Future getSimplePassKeyAccountAddress( PassKeyPair pkp, Uint256 salt) => - plugin('factory').getPasskeyAccountAddress(pkp.credentialHexBytes, - pkp.publicKey[0].value, pkp.publicKey[1].value, salt.value); + plugin('factory').getPasskeyAccountAddress( + pkp.credentialHexBytes, + pkp.publicKey[0].value, + pkp.publicKey[1].value, + salt.value); @override UserOperation buildUserOperation({ required Uint8List callData, BigInt? customNonce, - BigInt? callGasLimit, - BigInt? verificationGasLimit, - BigInt? preVerificationGas, - BigInt? maxFeePerGas, - BigInt? maxPriorityFeePerGas, }) => UserOperation.partial( - callData: callData, - initCode: _initCodeBytes, - sender: _walletAddress, - nonce: customNonce, - callGasLimit: callGasLimit, - verificationGasLimit: verificationGasLimit, - preVerificationGas: preVerificationGas, - maxFeePerGas: maxFeePerGas, - maxPriorityFeePerGas: maxPriorityFeePerGas, - ); + callData: callData, + initCode: _initCodeBytes, + sender: _walletAddress, + nonce: customNonce); @override Future send( @@ -208,7 +200,7 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { @override Future sendSignedUserOperation(UserOperation op) => - plugin('bundler') + plugin('bundler') .sendUserOperation(op.toMap(), _chain.entrypoint) .catchError( (e) => throw SmartWalletError.sendError(op, e.toString())); @@ -228,14 +220,14 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { final opHash = userOp.hash(_chain); if (hasPlugin('paymaster')) { - userOp = await plugin().intercept(userOp); + userOp = await plugin('paymaster').intercept(userOp); } - Uint8List signature = await plugin('signer').personalSign(opHash, + Uint8List signature = await plugin('signer').personalSign(opHash, index: index, id: id ?? - (plugin('signer') is PasskeyInterface - ? plugin('signer').defaultId + (plugin('signer') is PasskeyInterface + ? plugin('signer').defaultId : id)); userOp.signature = hexlify(signature); @@ -257,18 +249,32 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { throw SmartWalletError.nonceError(_walletAddress, e.toString()))); Future _updateUserOperation(UserOperation op) => - Future.wait([_getNonce(), plugin('ethRpc').getGasPrice()]) + Future.wait( + [_getNonce(), plugin('jsonRpc').getGasPrice()]) .then((responses) { op = op.copyWith( nonce: op.nonce > BigInt.zero ? op.nonce : responses[0].value, initCode: responses[0] > BigInt.zero ? Uint8List(0) : null, - signature: plugin('signer').dummySignature); - return _updateUserOperationGas(op, feePerGas: responses[1]); + signature: plugin('signer').dummySignature); + return _updateUserOperationGas(op, + feePerGas: _formatGasFees(responses[1])); }); + // supporting v07 migration + Map _formatGasFees(Map feePerGas) { + if (_chain.entrypoint == EntryPoint.v07) { + final gasFees = {}; + final high128 = feePerGas['maxPriorityFeePerGas']!.getInWei; + final low128 = feePerGas['maxFeePerGas']!.getInWei; + gasFees['gasFees'] = packUints(high128, low128) as T; + return gasFees; + } + return feePerGas as Map; + } + Future _updateUserOperationGas(UserOperation op, {Map? feePerGas}) => - plugin('bundler') + plugin('bundler') .estimateUserOperationGas(op.toMap(), _chain.entrypoint) .then((opGas) => op.updateOpGas(opGas, feePerGas)) .then((op) => multiply(op)) @@ -311,3 +317,5 @@ class SmartWalletError extends Error { return message; } } + +typedef MSI = MultiSignerInterface; diff --git a/lib/src/interfaces/bundler_provider.dart b/lib/src/interfaces/bundler_provider.dart index 52a4a84..9e27735 100644 --- a/lib/src/interfaces/bundler_provider.dart +++ b/lib/src/interfaces/bundler_provider.dart @@ -20,7 +20,7 @@ abstract class BundlerProviderBase { /// /// Parameters: /// - `userOp`: A map containing the user operation data. - /// - `entrypoint`: The [EthereumAddress] representing the entrypoint for the operation. + /// - `entrypoint`: The [EntryPoint] representing the entrypoint for the operation. /// /// Returns: /// A [Future] that completes with a [UserOperationGas] instance representing the estimated gas values. @@ -34,7 +34,7 @@ abstract class BundlerProviderBase { /// ``` /// This method uses the bundled RPC to estimate the gas cost for the provided user operation data. Future estimateUserOperationGas( - Map userOp, EthereumAddress entrypoint); + Map userOp, EntryPoint entrypoint); /// Asynchronously retrieves information about a user operation using its hash. /// @@ -70,7 +70,7 @@ abstract class BundlerProviderBase { /// /// Parameters: /// - `userOp`: A map containing the user operation data. - /// - `entrypoint`: The [EthereumAddress] representing the entrypoint for the operation. + /// - `entrypoint`: The [EntryPoint] representing the entrypoint for the operation. /// /// Returns: /// A [Future] that completes with a [UserOperationResponse] containing information about the executed operation. @@ -84,7 +84,7 @@ abstract class BundlerProviderBase { /// ``` /// This method uses the bundled RPC to send the specified user operation for execution and returns the response. Future sendUserOperation( - Map userOp, EthereumAddress entrypoint); + Map userOp, EntryPoint entrypoint); /// Asynchronously retrieves a list of supported entrypoints from the bundler. /// diff --git a/lib/src/interfaces/interfaces.dart b/lib/src/interfaces/interfaces.dart index f974d90..87acacc 100644 --- a/lib/src/interfaces/interfaces.dart +++ b/lib/src/interfaces/interfaces.dart @@ -18,6 +18,7 @@ import '../../utils.dart' import '../../variance.dart' show Chain, + EntryPoint, PassKeyPair, PassKeySignature, PassKeysOptions, diff --git a/lib/src/interfaces/smart_wallet.dart b/lib/src/interfaces/smart_wallet.dart index ecf10ed..f86775b 100644 --- a/lib/src/interfaces/smart_wallet.dart +++ b/lib/src/interfaces/smart_wallet.dart @@ -36,11 +36,6 @@ abstract class SmartWalletBase { /// Parameters: /// - `callData` (required): The call data as a [Uint8List]. /// - `customNonce`: An optional custom nonce value. - /// - `callGasLimit`: An optional custom call gas limit as a [BigInt]. - /// - `verificationGasLimit`: An optional custom verification gas limit as a [BigInt]. - /// - `preVerificationGas`: An optional custom pre-verification gas as a [BigInt]. - /// - `maxFeePerGas`: An optional custom maximum fee per gas as a [BigInt]. - /// - `maxPriorityFeePerGas`: An optional custom maximum priority fee per gas as a [BigInt]. /// /// Returns: /// A [UserOperation] instance with the specified parameters. @@ -50,18 +45,11 @@ abstract class SmartWalletBase { /// var userOperation = buildUserOperation( /// callData: Uint8List(0xabcdef), /// customNonce: BigInt.from(42), - /// callGasLimit: BigInt.from(20000000), - /// // Other optional parameters can be provided as needed. /// ); /// ``` UserOperation buildUserOperation({ required Uint8List callData, BigInt? customNonce, - BigInt? callGasLimit, - BigInt? verificationGasLimit, - BigInt? preVerificationGas, - BigInt? maxFeePerGas, - BigInt? maxPriorityFeePerGas, }); /// Sets the account initialization calldata for a [SmartWalletBase] in a potentially unsafe manner. diff --git a/lib/src/interfaces/user_operations.dart b/lib/src/interfaces/user_operations.dart index e6985b4..2a92e8e 100644 --- a/lib/src/interfaces/user_operations.dart +++ b/lib/src/interfaces/user_operations.dart @@ -13,16 +13,24 @@ abstract class UserOperationBase { Uint8List get callData; + @Deprecated("packed in accountGasLimits from v0.7") BigInt get callGasLimit; + @Deprecated("packed in accountGasLimits from v0.7") BigInt get verificationGasLimit; + Uint8List get accountGasLimits; + BigInt get preVerificationGas; + @Deprecated("packed in gasFees from v0.7") BigInt get maxFeePerGas; + @Deprecated("packed in gasFees from v0.7") BigInt get maxPriorityFeePerGas; + Uint8List get gasFees; + String get signature; Uint8List get paymasterAndData; diff --git a/lib/src/utils/crypto.dart b/lib/src/utils/crypto.dart index cc9b559..cf1c969 100644 --- a/lib/src/utils/crypto.dart +++ b/lib/src/utils/crypto.dart @@ -94,6 +94,32 @@ require(bool requirement, String exception) { } } +/// Packs two 128-bit unsigned integers into a 32-byte array. +/// +/// Parameters: +/// - [high128]: The high 128-bit unsigned integer. +/// - [low128]: The low 128-bit unsigned integer. +/// +/// Returns a Uint8List containing the packed bytes. +/// +/// Example: +/// ```dart +/// final high128 = BigInt.from(1); +/// final low128 = BigInt.from(2); +/// final packedBytes = packUints(high128, low128); +/// print(packedBytes); +/// ``` +Uint8List packUints(BigInt high128, BigInt low128) { + if (high128 >= BigInt.two.pow(128) || low128 >= BigInt.two.pow(128)) { + throw ArgumentError('Values exceed the range of 128-bit unsigned integers'); + } + + final high = high128.toRadixString(16).padLeft(32, '0'); + final low = low128.toRadixString(16).padLeft(32, '0'); + final packedBytes = hexToBytes('$high$low'); + return packedBytes; +} + /// Computes the SHA-256 hash of the specified input. /// /// Parameters: diff --git a/lib/utils.dart b/lib/utils.dart index 8e750ff..594853d 100644 --- a/lib/utils.dart +++ b/lib/utils.dart @@ -12,6 +12,7 @@ import 'package:local_auth/error_codes.dart' as auth_error; import 'package:local_auth/local_auth.dart'; import 'package:local_auth_android/local_auth_android.dart'; import 'package:local_auth_ios/types/auth_messages_ios.dart'; +import 'package:web3dart/crypto.dart'; import 'package:web3dart/web3dart.dart' show BlockNum, EthereumAddress; import 'package:webcrypto/webcrypto.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index fd77e39..44a1457 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: variance_dart description: An Account Abstraction (4337) Development kit, for quickly building mobile web3 apps and smart wallets. -version: 0.0.8 +version: 0.0.9 documentation: https://docs.variance.space homepage: https://variance.space repository: https://github.com/vaariance/variance-dart