From 6c3c27eb81cfd32a4033427f9ae8710b634e3eef Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Tue, 16 Jan 2024 11:14:45 +0100 Subject: [PATCH 01/31] fix: gas estimations and redundat calls --- CHANGELOG.md | 10 ++++++ lib/src/4337/wallet.dart | 71 +++++++++++++++++++++++++++++++--------- pubspec.yaml | 2 +- 3 files changed, 66 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 394ebdc..27bff91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## 0.0.5 + +* Added missing required blockParam to `eth_call` + +* Reduced default callgasLimit and verificationGasLimit to 250K and 750k respectively in `eth_estimateUserOperationGas` + +* Prevent redundant eth_call's in fetching nonce/deployment status + +* Reduced strict internal op callGasLimit validation from 21k gas 12k gas requirement + ## 0.0.4 * Added a Secure Storage Repository for saving credentials to encrypted android shared-preference and iOS keychain diff --git a/lib/src/4337/wallet.dart b/lib/src/4337/wallet.dart index 1dd8052..5af58ca 100644 --- a/lib/src/4337/wallet.dart +++ b/lib/src/4337/wallet.dart @@ -206,7 +206,9 @@ class SmartWallet with _PluginManager implements SmartWalletBase { @override Future sendSignedUserOperation(UserOperation op) => plugin('bundler') - .sendUserOperation(op.toMap(), _chain.entrypoint); + .sendUserOperation(op.toMap(), _chain.entrypoint) + .catchError( + (e) => throw SmartWalletError.sendError(op, e.toString())); @override Future sendUserOperation(UserOperation op, @@ -241,34 +243,40 @@ class SmartWallet with _PluginManager implements SmartWalletBase { .function(functionName) .encodeCall(params); - Future _getNonce() => plugin("contract") - .call(_chain.entrypoint, ContractAbis.get('getNonce'), "getNonce", - params: [_walletAddress, BigInt.zero]) - .then((value) => Uint256(value[0])) - .catchError((e) => throw SmartWalletError( - "Error getting nonce for address: $_walletAddress. ${e.toString()}")); + Future _getNonce() => _deployed == false + ? Future.value(Uint256.zero) + : plugin("contract") + .call(_chain.entrypoint, ContractAbis.get('getNonce'), "getNonce", + params: [_walletAddress, BigInt.zero]) + .then((value) => Uint256(value[0])) + .catchError((e) => + throw SmartWalletError.nonceError(_walletAddress, e.toString())); Future _updateUserOperation(UserOperation op) async { List responses = await Future.wait([ + Future.microtask(() async => _deployed == null || _deployed == false + ? _deployed = await deployed + : _deployed), plugin('ethRpc').getGasPrice(), - _getNonce(), - Future.microtask(() async => _deployed = await deployed) + _getNonce() ]); op = UserOperation.update(op.toMap(), sender: _walletAddress, - nonce: responses[1].value, - initCode: responses[2] ? "0x" : null); + nonce: responses[2].value, + initCode: responses[0] ? "0x" : null); op.maxFeePerGas = - (responses[0] as Map)["maxFeePerGas"]!.getInWei; + (responses[1] as Map)["maxFeePerGas"]!.getInWei; op.maxPriorityFeePerGas = - (responses[0] as Map)["maxPriorityFeePerGas"]! + (responses[1] as Map)["maxPriorityFeePerGas"]! .getInWei; op.signature = plugin('signer').dummySignature; return plugin('bundler') .estimateUserOperationGas(op.toMap(), _chain.entrypoint) - .then((opGas) => UserOperation.update(op.toMap(), opGas: opGas)); + .then((opGas) => UserOperation.update(op.toMap(), opGas: opGas)) + .catchError( + (e) => throw SmartWalletError.estimateError(op, e.toString())); } Future _validateUserOperation(UserOperation op) async { @@ -285,8 +293,8 @@ class SmartWallet with _PluginManager implements SmartWalletBase { ? hexlify(op.initCode).toLowerCase() == "0x" : hexlify(op.initCode).toLowerCase() == initCode.toLowerCase(), "Init code mismatch"); - require(op.callGasLimit >= BigInt.from(21000), - "Call gas limit too small expected value greater than 21000"); + require(op.callGasLimit >= BigInt.from(12000), + "Call gas limit too small expected value greater than 12000"); require(op.verificationGasLimit >= BigInt.from(39000), "Verification gas limit too small expected value greater than 39000"); require(op.preVerificationGas >= BigInt.from(5000), @@ -301,6 +309,37 @@ class SmartWalletError extends Error { SmartWalletError(this.message); + factory SmartWalletError.sendError(UserOperation op, String message) { + return SmartWalletError(''' + \x1B[31m ************************************************** + Error sending user operation! Failed with error: $message + -------------------------------------------------- + \x1B[33m User operation: ${op.toJson()}. \x1B[31m + ************************************************** \x1B[0m + '''); + } + + factory SmartWalletError.estimateError(UserOperation op, String message) { + return SmartWalletError(''' + \x1B[31m ************************************************** + Error estimating user operation gas! Failed with error: $message + -------------------------------------------------- + \x1B[33m User operation: ${op.toJson()}. \x1B[31m + ************************************************** \x1B[0m + '''); + } + + factory SmartWalletError.nonceError( + EthereumAddress? address, String message) { + return SmartWalletError(''' + \x1B[31m ************************************************** + Error fetching user account nonce for address \x1B[33m ${address?.hex}! \x1B[31m + -------------------------------------------------- + Failed with error: $message + ************************************************** \x1B[0m + '''); + } + @override String toString() { return message; diff --git a/pubspec.yaml b/pubspec.yaml index 9d97ea7..0632ba5 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.4 +version: 0.0.5 documentation: https://docs.variance.space homepage: https://variance.space repository: https://github.com/vaariance/variance-dart From d091a5cbff0be3a3b84ffa91f380ccd13c2b39f2 Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Thu, 22 Feb 2024 14:22:56 +0100 Subject: [PATCH 02/31] sunset all goerli chains --- CHANGELOG.md | 4 + example/lib/main.dart | 3 +- lib/src/4337/chains.dart | 264 +++++++++++++++++++++++---------------- lib/src/4337/wallet.dart | 6 +- pubspec.lock | 14 +-- pubspec.yaml | 2 +- 6 files changed, 175 insertions(+), 118 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27bff91..55ca50c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.6 + +* Sunset all goerli chains + ## 0.0.5 * Added missing required blockParam to `eth_call` diff --git a/example/lib/main.dart b/example/lib/main.dart index c43724e..d50f80a 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -18,7 +18,8 @@ Future main() async { ethRpcUrl: rpcUrl, bundlerUrl: bundlerUrl, entrypoint: Constants.entrypoint, - accountFactory: Constants.accountFactory, + accountFactory: + EthereumAddress.fromHex("0xCCaE5F64307D86346B83E55e7865f77906F9c7b4"), chainId: 1337, explorer: ""); diff --git a/lib/src/4337/chains.dart b/lib/src/4337/chains.dart index d1d3649..d997f68 100644 --- a/lib/src/4337/chains.dart +++ b/lib/src/4337/chains.dart @@ -4,22 +4,27 @@ class Chain { final int chainId; final String explorer; final EthereumAddress entrypoint; - final EthereumAddress accountFactory; + EthereumAddress? accountFactory; String? ethRpcUrl; String? bundlerUrl; - Chain( - {required this.chainId, - required this.explorer, - this.ethRpcUrl, - this.bundlerUrl, - required this.entrypoint, - required this.accountFactory}); + Chain({ + required this.chainId, + required this.explorer, + required this.entrypoint, + this.accountFactory, + this.ethRpcUrl, + this.bundlerUrl, + }); /// asserts that [ethRpcUrl] and [bundlerUrl] is provided Chain validate() { - require(isURL(ethRpcUrl), "Chain: please provide a valid eth rpc url"); - require(isURL(bundlerUrl), "Chain: please provide a valid bundler url"); + require(isURL(ethRpcUrl), + "Chain Congig Error: please provide a valid eth rpc url"); + require(isURL(bundlerUrl), + "Chain Config Error: please provide a valid bundler url"); + require(accountFactory != null, + "Chain Config Error: please provide account factory address"); return this; } } @@ -27,98 +32,138 @@ class Chain { //predefined Chains you can use class Chains { static Map chains = { - // Ethereum Mainnet - Network.mainnet: Chain( - chainId: 1, - explorer: "https://etherscan.io/", - ethRpcUrl: "https://rpc.ankr.com/eth", - entrypoint: Constants.entrypoint, - accountFactory: Constants.accountFactory), - // Optimism Mainnet + Network.ethereum: Chain( + chainId: 1, + explorer: "https://etherscan.io/", + ethRpcUrl: "https://rpc.ankr.com/eth", + entrypoint: Constants.entrypoint, + ), + Network.polygon: Chain( + chainId: 137, + explorer: "https://polygonscan.com/", + ethRpcUrl: "https://polygon-rpc.com/", + entrypoint: Constants.entrypoint, + ), Network.optimism: Chain( - chainId: 10, - explorer: "https://explorer.optimism.io", - ethRpcUrl: "https://mainnet.optimism.io", - entrypoint: Constants.entrypoint, - accountFactory: Constants.accountFactory), - // Base Mainnet + chainId: 10, + explorer: "https://explorer.optimism.io", + ethRpcUrl: "https://mainnet.optimism.io", + entrypoint: Constants.entrypoint, + ), Network.base: Chain( - chainId: 8453, - explorer: "https://basescan.org", - ethRpcUrl: "https://mainnet.base.org", - entrypoint: Constants.entrypoint, - accountFactory: Constants.accountFactory), - // Arbitrum (one) Mainnet - Network.arbitrum: Chain( - chainId: 42161, - explorer: "https://arbiscan.io/", - ethRpcUrl: "https://arb1.arbitrum.io/rpc", - entrypoint: Constants.entrypoint, - accountFactory: Constants.accountFactory), - // Polygon Mainnet - Network.polygon: Chain( - chainId: 137, - explorer: "https://polygonscan.com/", - ethRpcUrl: "https://polygon-rpc.com/", - entrypoint: Constants.entrypoint, - accountFactory: Constants.accountFactory), - // Mantle Mainnet + chainId: 8453, + explorer: "https://basescan.org", + ethRpcUrl: "https://mainnet.base.org", + entrypoint: Constants.entrypoint, + ), + Network.arbitrumOne: Chain( + chainId: 42161, + explorer: "https://arbiscan.io/", + ethRpcUrl: "https://arb1.arbitrum.io/rpc", + entrypoint: Constants.entrypoint, + ), Network.mantle: Chain( - chainId: 5000, - explorer: "https://explorer.mantle.xyz/", - ethRpcUrl: "https://rpc.mantle.xyz/", - entrypoint: Constants.entrypoint, - accountFactory: Constants.accountFactory), - // Sepolia Testnet + chainId: 5000, + explorer: "https://explorer.mantle.xyz/", + ethRpcUrl: "https://rpc.mantle.xyz/", + entrypoint: Constants.entrypoint, + ), + Network.linea: Chain( + chainId: 59144, + explorer: "https://lineascan.build/", + ethRpcUrl: "https://rpc.linea.build", + entrypoint: Constants.entrypoint), + Network.avalanche: Chain( + chainId: 43114, + explorer: "https://snowtrace.io/", + ethRpcUrl: "https://api.avax.network/ext/bc/C/rpc", + entrypoint: Constants.entrypoint, + ), + Network.gnosis: Chain( + chainId: 100, + explorer: "https://gnosisscan.io/", + ethRpcUrl: "https://rpc.ankr.com/gnosis", + entrypoint: Constants.entrypoint, + ), + Network.celo: Chain( + chainId: 42220, + explorer: "https://celoscan.io/", + ethRpcUrl: "https://forno.celo.org", + entrypoint: Constants.entrypoint, + ), + Network.fantom: Chain( + chainId: 250, + explorer: "https://ftmscan.com/", + ethRpcUrl: "https://rpc.fantom.network", + entrypoint: Constants.entrypoint, + ), + Network.opBnB: Chain( + chainId: 204, + explorer: "http://opbnbscan.com/", + ethRpcUrl: "https://opbnb-mainnet-rpc.bnbchain.org", + entrypoint: Constants.entrypoint, + ), + Network.arbitrumNova: Chain( + chainId: 42170, + explorer: "https://nova.arbiscan.io/", + ethRpcUrl: "https://nova.arbitrum.io/rpc", + entrypoint: Constants.entrypoint, + ), + Network.polygonzkEvm: Chain( + chainId: 1101, + explorer: "https://zkevm.polygonscan.com/", + ethRpcUrl: "https://polygonzkevm-mainnet.g.alchemy.com/v2/demo", + entrypoint: Constants.entrypoint, + ), + Network.scroll: Chain( + chainId: 534352, + explorer: "https://scrollscan.com/", + ethRpcUrl: "https://rpc.scroll.io/", + entrypoint: Constants.entrypoint, + ), + Network.mode: Chain( + chainId: 34443, + explorer: "https://explorer.mode.network/", + ethRpcUrl: "https://mainnet.mode.network/", + entrypoint: Constants.entrypoint, + ), Network.sepolia: Chain( - chainId: 11155111, - explorer: "https://sepolia.etherscan.io/", - ethRpcUrl: "https://rpc.sepolia.org", - entrypoint: Constants.entrypoint, - accountFactory: Constants.accountFactory), - // Optimism Goerli Testnet - Network.opGoerli: Chain( - chainId: 420, - explorer: "https://goerli-explorer.optimism.io", - ethRpcUrl: "https://goerli.optimism.io", - entrypoint: Constants.entrypoint, - accountFactory: Constants.accountFactory), - // Base Goerli testnet - Network.baseGoerli: Chain( - chainId: 84531, - explorer: "https://goerli.basescan.org", - ethRpcUrl: "https://goerli.base.org", - entrypoint: Constants.entrypoint, - accountFactory: Constants.accountFactory), - // Mumbai Testnet + chainId: 11155111, + explorer: "https://sepolia.etherscan.io/", + ethRpcUrl: "https://rpc.sepolia.org", + entrypoint: Constants.entrypoint, + ), Network.mumbai: Chain( - chainId: 80001, - explorer: "https://mumbai.polygonscan.com/", - ethRpcUrl: "https://rpc-mumbai.maticvigil.com/", - entrypoint: Constants.entrypoint, - accountFactory: Constants.accountFactory), - // Mantle Testnet - Network.mantleTestnet: Chain( - chainId: 50001, - explorer: "https://explorer.testnet.mantle.xyz/", - ethRpcUrl: "https://rpc.testnet.mantle.xyz/", - entrypoint: Constants.entrypoint, - accountFactory: Constants.accountFactory), - // Scroll Sepolia Testnet - Network.scrollSepolia: Chain( - chainId: 534351, - explorer: "https://sepolia-blockscout.scroll.io/", - ethRpcUrl: "https://sepolia-rpc.scroll.io/", - entrypoint: Constants.entrypoint, - accountFactory: Constants.accountFactory), - // Localhost + chainId: 80001, + explorer: "https://mumbai.polygonscan.com/", + ethRpcUrl: "https://rpc-mumbai.maticvigil.com/", + entrypoint: Constants.entrypoint, + ), + Network.baseTestent: Chain( + chainId: 84531, + explorer: "https://sepolia.basescan.org", + ethRpcUrl: "https://api-sepolia.basescan.org/api", + entrypoint: Constants.entrypoint, + ), + Network.fuji: Chain( + chainId: 43113, + explorer: "https://testnet.snowtrace.io/", + ethRpcUrl: "https://api.avax-test.network/ext/bc/C/rpc", + entrypoint: Constants.entrypoint, + ), + Network.katla: Chain( + chainId: 167008, + explorer: "https://explorer.katla.taiko.xyz/", + ethRpcUrl: "https://rpc.katla.taiko.xyz", + entrypoint: Constants.entrypoint, + ), Network.localhost: Chain( - chainId: 1337, - explorer: "http://localhost:8545", - ethRpcUrl: "http://localhost:8545", - bundlerUrl: "http://localhost:3000/rpc", - entrypoint: Constants.entrypoint, - accountFactory: Constants.accountFactory) + chainId: 1337, + explorer: "http://localhost:8545", + ethRpcUrl: "http://localhost:8545", + bundlerUrl: "http://localhost:3000/rpc", + entrypoint: Constants.entrypoint, + ) }; const Chains._(); @@ -134,28 +179,35 @@ class Constants { enforceEip55: true); static EthereumAddress zeroAddress = EthereumAddress.fromHex("0x0000000000000000000000000000000000000000"); - static EthereumAddress accountFactory = EthereumAddress.fromHex( - "0xCCaE5F64307D86346B83E55e7865f77906F9c7b4", - enforceEip55: true); + Constants._(); } enum Network { // mainnet - mainnet, + ethereum, + polygon, optimism, base, - arbitrum, - polygon, + arbitrumOne, mantle, + linea, + avalanche, + gnosis, + celo, + fantom, + opBnB, + arbitrumNova, + polygonzkEvm, + scroll, + mode, // testnet sepolia, - opGoerli, - baseGoerli, mumbai, - mantleTestnet, - scrollSepolia, + baseTestent, + fuji, + katla, // localhost localhost diff --git a/lib/src/4337/wallet.dart b/lib/src/4337/wallet.dart index 5af58ca..1bec7b9 100644 --- a/lib/src/4337/wallet.dart +++ b/lib/src/4337/wallet.dart @@ -18,7 +18,7 @@ class SmartWallet with _PluginManager implements SmartWalletBase { _walletAddress = address { final rpc = RPCProvider(chain.ethRpcUrl!); final fact = _AccountFactory( - address: chain.accountFactory, chainId: chain.chainId, rpc: rpc); + address: chain.accountFactory!, chainId: chain.chainId, rpc: rpc); addPlugin('signer', signer); addPlugin('bundler', bundler); @@ -98,12 +98,12 @@ class SmartWallet with _PluginManager implements SmartWalletBase { String? get toHex => _walletAddress?.hexEip55; String get _initCode => _initCalldata != null - ? _chain.accountFactory.hexEip55 + hexlify(_initCalldata!).substring(2) + ? _chain.accountFactory!.hexEip55 + hexlify(_initCalldata!).substring(2) : "0x"; Uint8List get _initCodeBytes { if (_initCalldata == null) return Uint8List(0); - List extended = _chain.accountFactory.addressBytes.toList(); + List extended = _chain.accountFactory!.addressBytes.toList(); extended.addAll(_initCalldata!); return Uint8List.fromList(extended); } diff --git a/pubspec.lock b/pubspec.lock index 05d0ed0..c8c040c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -189,10 +189,10 @@ packages: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" convert: dependency: transitive description: @@ -575,10 +575,10 @@ packages: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" mime: dependency: transitive description: @@ -924,10 +924,10 @@ packages: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "0.3.0" web3dart: dependency: "direct main" description: @@ -993,5 +993,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.0 <4.0.0" + dart: ">=3.2.0-194.0.dev <4.0.0" flutter: ">=3.13.0" diff --git a/pubspec.yaml b/pubspec.yaml index 0632ba5..b060d6a 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.5 +version: 0.0.6 documentation: https://docs.variance.space homepage: https://variance.space repository: https://github.com/vaariance/variance-dart From 2e511bc58587b6354211497e7cd1ebe79ace0ec3 Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Thu, 22 Feb 2024 19:07:48 +0100 Subject: [PATCH 03/31] add global gas settings. --- lib/src/4337/userop.dart | 67 ++++++++++++++++++------------- lib/src/4337/wallet.dart | 62 +++++++++++----------------- lib/src/common/plugins.dart | 80 ++++++++++++++++++++++++++++++------- 3 files changed, 129 insertions(+), 80 deletions(-) diff --git a/lib/src/4337/userop.dart b/lib/src/4337/userop.dart index 8c5a7c7..2725aef 100644 --- a/lib/src/4337/userop.dart +++ b/lib/src/4337/userop.dart @@ -115,14 +115,11 @@ class UserOperation implements UserOperationBase { paymasterAndData: Uint8List(0), ); - /// Creates a [UserOperation] by updating an existing operation using a map. + /// Creates a [UserOperation] by updating an existing operation gas params. /// /// Parameters: - /// - `map`: A map containing key-value pairs representing the user operation data. /// - `opGas`: Optional parameter of type [UserOperationGas] for specifying gas-related information. - /// - `sender`: Optional Ethereum address of the sender. - /// - `nonce`: Optional nonce value. - /// - `initCode`: Optional initialization code. + /// - `feePerGas`: Optional parameter of type [Map] for specifying maxFeePerGas and maxPriorityFeePerGas. /// /// Returns: /// A [UserOperation] instance created from the provided map. @@ -136,26 +133,42 @@ class UserOperation implements UserOperationBase { /// // Other parameters can be updated as needed. /// ); /// ``` - factory UserOperation.update( - Map map, { - UserOperationGas? opGas, + UserOperation updateOpGas( + UserOperationGas? opGas, Map? feePerGas) { + return copyWith( + callGasLimit: opGas?.callGasLimit, + verificationGasLimit: opGas?.verificationGasLimit, + preVerificationGas: opGas?.preVerificationGas, + maxFeePerGas: feePerGas?["maxFeePerGas"]!.getInWei, + maxPriorityFeePerGas: feePerGas?["maxPriorityFeePerGas"]!.getInWei); + } + + UserOperation copyWith({ EthereumAddress? sender, BigInt? nonce, - String? initCode, + Uint8List? initCode, + Uint8List? callData, + BigInt? callGasLimit, + BigInt? verificationGasLimit, + BigInt? preVerificationGas, + BigInt? maxFeePerGas, + BigInt? maxPriorityFeePerGas, + String? signature, + Uint8List? paymasterAndData, }) { - if (opGas != null) { - map['callGasLimit'] = '0x${opGas.callGasLimit.toRadixString(16)}'; - map['verificationGasLimit'] = - '0x${opGas.verificationGasLimit.toRadixString(16)}'; - map['preVerificationGas'] = - '0x${BigInt.from(opGas.preVerificationGas.toDouble() * 1.2).toRadixString(16)}'; - } - - if (sender != null) map['sender'] = sender.hex; - if (nonce != null) map['nonce'] = '0x${nonce.toRadixString(16)}'; - if (initCode != null) map['initCode'] = initCode; - - return UserOperation.fromMap(map); + return UserOperation( + sender: sender ?? this.sender, + nonce: nonce ?? this.nonce, + initCode: initCode ?? this.initCode, + callData: callData ?? this.callData, + callGasLimit: callGasLimit ?? this.callGasLimit, + verificationGasLimit: verificationGasLimit ?? this.verificationGasLimit, + preVerificationGas: preVerificationGas ?? this.preVerificationGas, + maxFeePerGas: maxFeePerGas ?? this.maxFeePerGas, + maxPriorityFeePerGas: maxPriorityFeePerGas ?? this.maxPriorityFeePerGas, + signature: signature ?? this.signature, + paymasterAndData: paymasterAndData ?? this.paymasterAndData, + ); } @override @@ -187,6 +200,11 @@ class UserOperation implements UserOperationBase { [encoded, chain.entrypoint, BigInt.from(chain.chainId)])); } + @override + String toJson() { + return jsonEncode(toMap()); + } + @override Map toMap() { return { @@ -203,11 +221,6 @@ class UserOperation implements UserOperationBase { 'paymasterAndData': hexlify(paymasterAndData), }; } - - @override - String toJson() { - return jsonEncode(toMap()); - } } class UserOperationByHash { diff --git a/lib/src/4337/wallet.dart b/lib/src/4337/wallet.dart index 1bec7b9..c3dc20d 100644 --- a/lib/src/4337/wallet.dart +++ b/lib/src/4337/wallet.dart @@ -1,6 +1,6 @@ part of '../../variance.dart'; -class SmartWallet with _PluginManager implements SmartWalletBase { +class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { final Chain _chain; EthereumAddress? _walletAddress; @@ -213,7 +213,9 @@ class SmartWallet with _PluginManager implements SmartWalletBase { @override Future sendUserOperation(UserOperation op, {String? id}) => - signUserOperation(op, id: id).then(sendSignedUserOperation); + signUserOperation(op, id: id) + .then(sendSignedUserOperation) + .catchError((e) => retryOp(() => sendSignedUserOperation(op), e)); @override Future signUserOperation(UserOperation userOp, @@ -261,20 +263,16 @@ class SmartWallet with _PluginManager implements SmartWalletBase { _getNonce() ]); - op = UserOperation.update(op.toMap(), + op = op.copyWith( sender: _walletAddress, nonce: responses[2].value, - initCode: responses[0] ? "0x" : null); - op.maxFeePerGas = - (responses[1] as Map)["maxFeePerGas"]!.getInWei; - op.maxPriorityFeePerGas = - (responses[1] as Map)["maxPriorityFeePerGas"]! - .getInWei; + initCode: responses[0] ? Uint8List(0) : null); op.signature = plugin('signer').dummySignature; return plugin('bundler') .estimateUserOperationGas(op.toMap(), _chain.entrypoint) - .then((opGas) => UserOperation.update(op.toMap(), opGas: opGas)) + .then((opGas) => op.updateOpGas(opGas, responses[1])) + .then((op) => multiply(op)) .catchError( (e) => throw SmartWalletError.estimateError(op, e.toString())); } @@ -282,25 +280,17 @@ class SmartWallet with _PluginManager implements SmartWalletBase { Future _validateUserOperation(UserOperation op) async { if (_walletAddress == null) { throw SmartWalletError( - 'Wallet address must be set: Did you call create?', + 'Wallet address not set', ); } - - require(op.sender.hex == _walletAddress?.hex, - "Operation sender error. ${op.sender} provided."); + require(op.sender.hex == _walletAddress?.hex, "invalid sender address."); require( (_deployed ?? (await deployed)) ? hexlify(op.initCode).toLowerCase() == "0x" : hexlify(op.initCode).toLowerCase() == initCode.toLowerCase(), - "Init code mismatch"); - require(op.callGasLimit >= BigInt.from(12000), - "Call gas limit too small expected value greater than 12000"); - require(op.verificationGasLimit >= BigInt.from(39000), - "Verification gas limit too small expected value greater than 39000"); - require(op.preVerificationGas >= BigInt.from(5000), - "Pre verification gas too small expected value greater than 5000"); - require(op.callData.length >= 4, "Call data too short, min is 4 bytes"); - require(op.signature.length >= 64, "Signature too short, min is 32 bytes"); + "InitCode mismatch"); + require(op.callData.length >= 4, "Calldata too short"); + require(op.signature.length >= 64, "Signature too short"); } } @@ -309,37 +299,31 @@ class SmartWalletError extends Error { SmartWalletError(this.message); - factory SmartWalletError.sendError(UserOperation op, String message) { - return SmartWalletError(''' - \x1B[31m ************************************************** - Error sending user operation! Failed with error: $message - -------------------------------------------------- - \x1B[33m User operation: ${op.toJson()}. \x1B[31m - ************************************************** \x1B[0m - '''); - } - factory SmartWalletError.estimateError(UserOperation op, String message) { return SmartWalletError(''' - \x1B[31m ************************************************** Error estimating user operation gas! Failed with error: $message -------------------------------------------------- - \x1B[33m User operation: ${op.toJson()}. \x1B[31m - ************************************************** \x1B[0m + User operation: ${op.toJson()}. '''); } factory SmartWalletError.nonceError( EthereumAddress? address, String message) { return SmartWalletError(''' - \x1B[31m ************************************************** - Error fetching user account nonce for address \x1B[33m ${address?.hex}! \x1B[31m + Error fetching user account nonce for address ${address?.hex}! -------------------------------------------------- Failed with error: $message - ************************************************** \x1B[0m '''); } + factory SmartWalletError.sendError(UserOperation op, String message) { + return SmartWalletError(''' + Error sending user operation! Failed with error: $message + -------------------------------------------------- + User operation: ${op.toJson()}. + '''); + } + @override String toString() { return message; diff --git a/lib/src/common/plugins.dart b/lib/src/common/plugins.dart index c184bb7..95c175c 100644 --- a/lib/src/common/plugins.dart +++ b/lib/src/common/plugins.dart @@ -1,5 +1,57 @@ part of 'package:variance_dart/variance.dart'; +typedef Percent = double; + +class GasSettings { + Percent? gasMultiplierPercentage; + Percent? pvgx; + BigInt? userDefinedMaxFeePerGas; + BigInt? userDefinedMaxPriorityFeePerGas; + bool retryFailedSendUserOp; + + GasSettings({ + this.gasMultiplierPercentage = 0, + this.userDefinedMaxFeePerGas, + this.userDefinedMaxPriorityFeePerGas, + this.retryFailedSendUserOp = false, + this.pvgx = 0.2, + }) : assert(gasMultiplierPercentage! >= 0 && pvgx! > 0 && pvgx <= 0.2, + 'Gas multiplier percentage or pvgx is out of permissible bounds.'); +} + +mixin _GasSettings { + GasSettings _gasParams = GasSettings(); + + set setGasParams(GasSettings gasParams) => _gasParams = gasParams; + + UserOperation multiply(UserOperation op) { + op = op.copyWith( + preVerificationGas: + op.preVerificationGas * BigInt.from(_gasParams.pvgx! + 1), + maxFeePerGas: _gasParams.userDefinedMaxFeePerGas, + maxPriorityFeePerGas: _gasParams.userDefinedMaxPriorityFeePerGas); + + final multiplier = _gasParams.gasMultiplierPercentage! > 0 + ? BigInt.from(_gasParams.gasMultiplierPercentage! + 1) + : BigInt.one; + + op = op.copyWith( + callGasLimit: op.callGasLimit * multiplier, + verificationGasLimit: op.verificationGasLimit * multiplier, + preVerificationGas: op.preVerificationGas * multiplier, + ); + + return op; + } + + dynamic retryOp(Function callback, dynamic err) { + if (_gasParams.retryFailedSendUserOp) { + return callback(); + } + throw err; + } +} + mixin _PluginManager { final Map _plugins = {}; @@ -8,6 +60,20 @@ mixin _PluginManager { return _plugins.keys.toList(growable: false); } + /// Adds a plugin by name. + /// + /// Parameters: + /// - `name`: The name of the plugin to add. + /// - `module`: The instance of the plugin. + /// + /// Example: + /// ```dart + /// addPlugin('logger', Logger()); + /// ``` + void addPlugin(String name, T module) { + _plugins[name] = module; + } + /// Gets a plugin by name. /// /// Parameters: @@ -31,18 +97,4 @@ mixin _PluginManager { void removePlugin(String name) { _plugins.remove(name); } - - /// Adds a plugin by name. - /// - /// Parameters: - /// - `name`: The name of the plugin to add. - /// - `module`: The instance of the plugin. - /// - /// Example: - /// ```dart - /// addPlugin('logger', Logger()); - /// ``` - void addPlugin(String name, T module) { - _plugins[name] = module; - } } From 5acac5f52f1be59675d138e7b64da20c0b5867c1 Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Thu, 22 Feb 2024 19:14:26 +0100 Subject: [PATCH 04/31] remove address and initdata field in constructor --- lib/src/4337/wallet.dart | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/lib/src/4337/wallet.dart b/lib/src/4337/wallet.dart index c3dc20d..ccaffc6 100644 --- a/lib/src/4337/wallet.dart +++ b/lib/src/4337/wallet.dart @@ -12,10 +12,8 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { SmartWallet( {required Chain chain, required MultiSignerInterface signer, - required BundlerProviderBase bundler, - EthereumAddress? address}) - : _chain = chain.validate(), - _walletAddress = address { + required BundlerProviderBase bundler}) + : _chain = chain.validate() { final rpc = RPCProvider(chain.ethRpcUrl!); final fact = _AccountFactory( address: chain.accountFactory!, chainId: chain.chainId, rpc: rpc); @@ -45,26 +43,20 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { /// chain: Chain.ethereum, /// signer: myMultiSigner, /// bundler: myBundler, - /// address: myWalletAddress, - /// initCallData: Uint8List.fromList([0x01, 0x02, 0x03]), /// ); /// ``` /// additionally initializes the associated Entrypoint contract for `tx.wait(userOpHash)` calls factory SmartWallet.init( {required Chain chain, required MultiSignerInterface signer, - required BundlerProviderBase bundler, - EthereumAddress? address, - Uint8List? initCallData}) { - final instance = SmartWallet( - chain: chain, signer: signer, bundler: bundler, address: address); - - instance - ..dangerouslySetInitCallData(initCallData) - ..plugin('bundler').initializeWithEntrypoint(Entrypoint( - address: chain.entrypoint, - client: instance.plugin('factory').client, - )); + required BundlerProviderBase bundler}) { + final instance = + SmartWallet(chain: chain, signer: signer, bundler: bundler); + + instance.plugin('bundler').initializeWithEntrypoint(Entrypoint( + address: chain.entrypoint, + client: instance.plugin('factory').client, + )); return instance; } From f4fa0da878d9c54163af8bb676592bfb57573d71 Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Thu, 22 Feb 2024 21:01:28 +0100 Subject: [PATCH 05/31] deprecate tbd issues --- CHANGELOG.md | 6 ++++++ lib/src/4337/wallet.dart | 22 +++++++++++++++------- pubspec.yaml | 2 +- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55ca50c..18f3d6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.0.7 + +* Deprecate passing wallet address via constructor as fields will be made final +* Enable global gas settings and fee % multiplier +* Introduce userOp retry mechanism + ## 0.0.6 * Sunset all goerli chains diff --git a/lib/src/4337/wallet.dart b/lib/src/4337/wallet.dart index ccaffc6..52af463 100644 --- a/lib/src/4337/wallet.dart +++ b/lib/src/4337/wallet.dart @@ -12,8 +12,11 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { SmartWallet( {required Chain chain, required MultiSignerInterface signer, - required BundlerProviderBase bundler}) - : _chain = chain.validate() { + required BundlerProviderBase bundler, + @Deprecated("address will be made final in the future") + EthereumAddress? address}) + : _chain = chain.validate(), + _walletAddress = address { final rpc = RPCProvider(chain.ethRpcUrl!); final fact = _AccountFactory( address: chain.accountFactory!, chainId: chain.chainId, rpc: rpc); @@ -43,15 +46,21 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { /// chain: Chain.ethereum, /// signer: myMultiSigner, /// bundler: myBundler, + /// address: myWalletAddress, + /// initCallData: Uint8List.fromList([0x01, 0x02, 0x03]), /// ); /// ``` /// additionally initializes the associated Entrypoint contract for `tx.wait(userOpHash)` calls factory SmartWallet.init( {required Chain chain, required MultiSignerInterface signer, - required BundlerProviderBase bundler}) { - final instance = - SmartWallet(chain: chain, signer: signer, bundler: bundler); + required BundlerProviderBase bundler, + @Deprecated("address will be made final in the future") + EthereumAddress? address, + @Deprecated("seperation of factory from wallet soon will be enforced") + Uint8List? initCallData}) { + final instance = SmartWallet( + chain: chain, signer: signer, bundler: bundler, address: address); instance.plugin('bundler').initializeWithEntrypoint(Entrypoint( address: chain.entrypoint, @@ -82,8 +91,7 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { Future get nonce => _getNonce(); @override - @Deprecated( - "pass the wallet address alongside the constructor if known beforehand") + @Deprecated("wallet address will be made final in the future") set setWalletAddress(EthereumAddress address) => _walletAddress = address; @override diff --git a/pubspec.yaml b/pubspec.yaml index b060d6a..3415b31 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.6 +version: 0.0.7 documentation: https://docs.variance.space homepage: https://variance.space repository: https://github.com/vaariance/variance-dart From ad157be0bc64de2b49a4cf86aed09f60647842f0 Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Sat, 24 Feb 2024 19:49:45 +0100 Subject: [PATCH 06/31] paymaster support --- CHANGELOG.md | 6 + example/lib/main.dart | 4 +- lib/src/4337/chains.dart | 70 ++++----- lib/src/4337/paymaster.dart | 54 ++++++- lib/src/4337/providers.dart | 92 ++---------- lib/src/4337/userop.dart | 52 +++---- lib/src/4337/wallet.dart | 148 ++++++++----------- lib/src/common/contract.dart | 8 +- lib/src/common/{plugins.dart => mixins.dart} | 53 ++++--- lib/src/interfaces/bundler_provider.dart | 34 +++-- lib/src/interfaces/interfaces.dart | 1 - lib/src/interfaces/smart_wallet.dart | 4 +- lib/src/interfaces/user_operations.dart | 21 +++ lib/variance.dart | 3 +- pubspec.yaml | 2 +- 15 files changed, 272 insertions(+), 280 deletions(-) rename lib/src/common/{plugins.dart => mixins.dart} (63%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18f3d6f..bbd26f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.0.8 + +* Add paymaster as a plugin +* Rename plugins.dart to mixins +* Improve gas and nonce calculation process. + ## 0.0.7 * Deprecate passing wallet address via constructor as fields will be made final diff --git a/example/lib/main.dart b/example/lib/main.dart index d50f80a..8695d3d 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -17,7 +17,7 @@ Future main() async { final Chain chain = Chain( ethRpcUrl: rpcUrl, bundlerUrl: bundlerUrl, - entrypoint: Constants.entrypoint, + entrypoint: Constants.entrypointv06, accountFactory: EthereumAddress.fromHex("0xCCaE5F64307D86346B83E55e7865f77906F9c7b4"), chainId: 1337, @@ -81,7 +81,7 @@ Future main() async { print("account nonce: ${nonce.toInt()}"); // check if a smart wallet has been deployed - final bool deployed = await simpleSmartAccount.deployed; + final bool deployed = await simpleSmartAccount.isDeployed; print("account deployed: $deployed"); // get the init code of the smart wallet diff --git a/lib/src/4337/chains.dart b/lib/src/4337/chains.dart index d997f68..b855c2c 100644 --- a/lib/src/4337/chains.dart +++ b/lib/src/4337/chains.dart @@ -7,20 +7,21 @@ class Chain { EthereumAddress? accountFactory; String? ethRpcUrl; String? bundlerUrl; + String? paymasterUrl; - Chain({ - required this.chainId, - required this.explorer, - required this.entrypoint, - this.accountFactory, - this.ethRpcUrl, - this.bundlerUrl, - }); + Chain( + {required this.chainId, + required this.explorer, + required this.entrypoint, + this.accountFactory, + this.ethRpcUrl, + this.bundlerUrl, + this.paymasterUrl}); /// asserts that [ethRpcUrl] and [bundlerUrl] is provided Chain validate() { require(isURL(ethRpcUrl), - "Chain Congig Error: please provide a valid eth rpc url"); + "Chain Config Error: please provide a valid eth rpc url"); require(isURL(bundlerUrl), "Chain Config Error: please provide a valid bundler url"); require(accountFactory != null, @@ -36,133 +37,133 @@ class Chains { chainId: 1, explorer: "https://etherscan.io/", ethRpcUrl: "https://rpc.ankr.com/eth", - entrypoint: Constants.entrypoint, + entrypoint: Constants.entrypointv06, ), Network.polygon: Chain( chainId: 137, explorer: "https://polygonscan.com/", ethRpcUrl: "https://polygon-rpc.com/", - entrypoint: Constants.entrypoint, + entrypoint: Constants.entrypointv06, ), Network.optimism: Chain( chainId: 10, explorer: "https://explorer.optimism.io", ethRpcUrl: "https://mainnet.optimism.io", - entrypoint: Constants.entrypoint, + entrypoint: Constants.entrypointv06, ), Network.base: Chain( chainId: 8453, explorer: "https://basescan.org", ethRpcUrl: "https://mainnet.base.org", - entrypoint: Constants.entrypoint, + entrypoint: Constants.entrypointv06, ), Network.arbitrumOne: Chain( chainId: 42161, explorer: "https://arbiscan.io/", ethRpcUrl: "https://arb1.arbitrum.io/rpc", - entrypoint: Constants.entrypoint, + entrypoint: Constants.entrypointv06, ), Network.mantle: Chain( chainId: 5000, explorer: "https://explorer.mantle.xyz/", ethRpcUrl: "https://rpc.mantle.xyz/", - entrypoint: Constants.entrypoint, + entrypoint: Constants.entrypointv06, ), Network.linea: Chain( chainId: 59144, explorer: "https://lineascan.build/", ethRpcUrl: "https://rpc.linea.build", - entrypoint: Constants.entrypoint), + entrypoint: Constants.entrypointv06), Network.avalanche: Chain( chainId: 43114, explorer: "https://snowtrace.io/", ethRpcUrl: "https://api.avax.network/ext/bc/C/rpc", - entrypoint: Constants.entrypoint, + entrypoint: Constants.entrypointv06, ), Network.gnosis: Chain( chainId: 100, explorer: "https://gnosisscan.io/", ethRpcUrl: "https://rpc.ankr.com/gnosis", - entrypoint: Constants.entrypoint, + entrypoint: Constants.entrypointv06, ), Network.celo: Chain( chainId: 42220, explorer: "https://celoscan.io/", ethRpcUrl: "https://forno.celo.org", - entrypoint: Constants.entrypoint, + entrypoint: Constants.entrypointv06, ), Network.fantom: Chain( chainId: 250, explorer: "https://ftmscan.com/", ethRpcUrl: "https://rpc.fantom.network", - entrypoint: Constants.entrypoint, + entrypoint: Constants.entrypointv06, ), Network.opBnB: Chain( chainId: 204, explorer: "http://opbnbscan.com/", ethRpcUrl: "https://opbnb-mainnet-rpc.bnbchain.org", - entrypoint: Constants.entrypoint, + entrypoint: Constants.entrypointv06, ), Network.arbitrumNova: Chain( chainId: 42170, explorer: "https://nova.arbiscan.io/", ethRpcUrl: "https://nova.arbitrum.io/rpc", - entrypoint: Constants.entrypoint, + entrypoint: Constants.entrypointv06, ), Network.polygonzkEvm: Chain( chainId: 1101, explorer: "https://zkevm.polygonscan.com/", ethRpcUrl: "https://polygonzkevm-mainnet.g.alchemy.com/v2/demo", - entrypoint: Constants.entrypoint, + entrypoint: Constants.entrypointv06, ), Network.scroll: Chain( chainId: 534352, explorer: "https://scrollscan.com/", ethRpcUrl: "https://rpc.scroll.io/", - entrypoint: Constants.entrypoint, + entrypoint: Constants.entrypointv06, ), Network.mode: Chain( chainId: 34443, explorer: "https://explorer.mode.network/", ethRpcUrl: "https://mainnet.mode.network/", - entrypoint: Constants.entrypoint, + entrypoint: Constants.entrypointv06, ), Network.sepolia: Chain( chainId: 11155111, explorer: "https://sepolia.etherscan.io/", ethRpcUrl: "https://rpc.sepolia.org", - entrypoint: Constants.entrypoint, + entrypoint: Constants.entrypointv06, ), Network.mumbai: Chain( chainId: 80001, explorer: "https://mumbai.polygonscan.com/", ethRpcUrl: "https://rpc-mumbai.maticvigil.com/", - entrypoint: Constants.entrypoint, + entrypoint: Constants.entrypointv06, ), Network.baseTestent: Chain( chainId: 84531, explorer: "https://sepolia.basescan.org", ethRpcUrl: "https://api-sepolia.basescan.org/api", - entrypoint: Constants.entrypoint, + entrypoint: Constants.entrypointv06, ), Network.fuji: Chain( chainId: 43113, explorer: "https://testnet.snowtrace.io/", ethRpcUrl: "https://api.avax-test.network/ext/bc/C/rpc", - entrypoint: Constants.entrypoint, + entrypoint: Constants.entrypointv06, ), Network.katla: Chain( chainId: 167008, explorer: "https://explorer.katla.taiko.xyz/", ethRpcUrl: "https://rpc.katla.taiko.xyz", - entrypoint: Constants.entrypoint, + entrypoint: Constants.entrypointv06, ), Network.localhost: Chain( chainId: 1337, explorer: "http://localhost:8545", ethRpcUrl: "http://localhost:8545", bundlerUrl: "http://localhost:3000/rpc", - entrypoint: Constants.entrypoint, + entrypoint: Constants.entrypointv06, ) }; @@ -174,9 +175,10 @@ class Chains { } class Constants { - static EthereumAddress entrypoint = EthereumAddress.fromHex( - "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", - enforceEip55: true); + static EthereumAddress entrypointv06 = + EthereumAddress.fromHex("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"); + static EthereumAddress entrypoint = + EthereumAddress.fromHex("0x0000000071727De22E5E9d8BAf0edAc6f37da032"); static EthereumAddress zeroAddress = EthereumAddress.fromHex("0x0000000000000000000000000000000000000000"); diff --git a/lib/src/4337/paymaster.dart b/lib/src/4337/paymaster.dart index bbf17e6..eee49df 100644 --- a/lib/src/4337/paymaster.dart +++ b/lib/src/4337/paymaster.dart @@ -1,5 +1,55 @@ part of '../../variance.dart'; -abstract class PaymasterBase {} +class PaymasterResponse { + final Uint8List paymasterAndData; + final BigInt preVerificationGas; + final BigInt verificationGasLimit; + final BigInt callGasLimit; -class Paymaster {} + PaymasterResponse({ + required this.paymasterAndData, + required this.preVerificationGas, + required this.verificationGasLimit, + required this.callGasLimit, + }); + + factory PaymasterResponse.fromMap(Map map) { + return PaymasterResponse( + paymasterAndData: hexToBytes(map['paymasterAndData']), + preVerificationGas: BigInt.parse(map['preVerificationGas']), + verificationGasLimit: BigInt.parse(map['verificationGasLimit']), + callGasLimit: BigInt.parse(map['callGasLimit']), + ); + } +} + +class Paymaster { + final RPCProviderBase _rpc; + final Chain _chain; + Map? _context; + + set context(Map? context) { + _context = context; + } + + Paymaster(this._chain, this._rpc, [this._context]); + + Future intercept(UserOperation operation) async { + final paymasterResponse = await sponsorUserOperation( + operation.toMap(), _chain.entrypoint, _context); + + return operation.copyWith( + paymasterAndData: paymasterResponse.paymasterAndData, + preVerificationGas: paymasterResponse.preVerificationGas, + verificationGasLimit: paymasterResponse.verificationGasLimit, + callGasLimit: paymasterResponse.callGasLimit, + ); + } + + Future sponsorUserOperation(Map userOp, + EthereumAddress 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 fa8e9bc..48f3e09 100644 --- a/lib/src/4337/providers.dart +++ b/lib/src/4337/providers.dart @@ -1,28 +1,12 @@ part of '../../variance.dart'; class BundlerProvider implements BundlerProviderBase { - /// Set of Ethereum RPC methods supported by the SmartWallet SDK. - static final Set methods = { - 'eth_chainId', - 'eth_sendUserOperation', - 'eth_estimateUserOperationGas', - 'eth_getUserOperationByHash', - 'eth_getUserOperationReceipt', - 'eth_supportedEntryPoints', - }; - - final int _chainId; - final String _bundlerUrl; - final RPCProviderBase _bundlerRpc; + final Chain _chain; + final RPCProviderBase _rpc; late final bool _initialized; - Entrypoint? entrypoint; - - BundlerProvider(Chain chain, RPCProviderBase bundlerRpc) - : _chainId = chain.chainId, - _bundlerUrl = chain.bundlerUrl!, - _bundlerRpc = bundlerRpc { + BundlerProvider(this._chain, this._rpc) { _initializeBundlerProvider(); } @@ -30,7 +14,7 @@ class BundlerProvider implements BundlerProviderBase { Future estimateUserOperationGas( Map userOp, EthereumAddress entrypoint) async { require(_initialized, "estimateUserOpGas: Wallet Provider not initialized"); - final opGas = await _bundlerRpc.send>( + final opGas = await _rpc.send>( 'eth_estimateUserOperationGas', [userOp, entrypoint.hex]); return UserOperationGas.fromMap(opGas); } @@ -38,7 +22,7 @@ class BundlerProvider implements BundlerProviderBase { @override Future getUserOperationByHash(String userOpHash) async { require(_initialized, "getUserOpByHash: Wallet Provider not initialized"); - final opExtended = await _bundlerRpc + final opExtended = await _rpc .send>('eth_getUserOperationByHash', [userOpHash]); return UserOperationByHash.fromMap(opExtended); } @@ -46,88 +30,36 @@ class BundlerProvider implements BundlerProviderBase { @override Future getUserOpReceipt(String userOpHash) async { require(_initialized, "getUserOpReceipt: Wallet Provider not initialized"); - final opReceipt = await _bundlerRpc.send>( + final opReceipt = await _rpc.send>( 'eth_getUserOperationReceipt', [userOpHash]); return UserOperationReceipt.fromMap(opReceipt); } - @override - void initializeWithEntrypoint(Entrypoint ep) { - entrypoint = ep; - } - @override Future sendUserOperation( Map userOp, EthereumAddress entrypoint) async { require(_initialized, "sendUserOp: Wallet Provider not initialized"); - final opHash = await _bundlerRpc + final opHash = await _rpc .send('eth_sendUserOperation', [userOp, entrypoint.hex]); - return UserOperationResponse(opHash, wait); + return UserOperationResponse(opHash); } @override Future> supportedEntryPoints() async { final entrypointList = - await _bundlerRpc.send>('eth_supportedEntryPoints'); + await _rpc.send>('eth_supportedEntryPoints'); return List.castFrom(entrypointList); } - @override - Future wait({int millisecond = 0}) async { - if (millisecond == 0) { - return null; - } - require(entrypoint != null, "Entrypoint required! use Wallet.init"); - final block = await entrypoint!.client.getBlockNumber(); - final end = DateTime.now().millisecondsSinceEpoch + millisecond; - - return await Isolate.run(() async { - while (DateTime.now().millisecondsSinceEpoch < end) { - final filterEvent = await entrypoint!.client - .events( - FilterOptions.events( - contract: entrypoint!.self, - event: entrypoint!.self.event('UserOperationEvent'), - fromBlock: BlockNum.exact(block - 100), - ), - ) - .take(1) - .first; - if (filterEvent.transactionHash != null) { - return filterEvent; - } - await Future.delayed(Duration(milliseconds: millisecond)); - } - return null; - }); - } - Future _initializeBundlerProvider() async { - final chainId = await _bundlerRpc + final chainId = await _rpc .send('eth_chainId') .then(BigInt.parse) .then((value) => value.toInt()); - require(chainId == _chainId, - "bundler $_bundlerUrl is on chainId $chainId, but provider is on chainId $_chainId"); + require(chainId == _chain.chainId, + "bundler ${_rpc.url} is on chainId $chainId, but provider is on chainId ${_chain.chainId}"); _initialized = true; } - - /// Validates if the provided method is a supported RPC method. - /// - /// Parameters: - /// - `method`: The Ethereum RPC method to validate. - /// - /// Throws: - /// - A [Exception] if the method is not a valid supported method. - /// - /// Example: - /// ```dart - /// validateBundlerMethod('eth_sendUserOperation'); - /// ``` - static validateBundlerMethod(String method) { - require(methods.contains(method), - "validateMethod: method ::'$method':: is not a valid method"); - } } class RPCProvider extends JsonRPC implements RPCProviderBase { diff --git a/lib/src/4337/userop.dart b/lib/src/4337/userop.dart index 2725aef..9e069f6 100644 --- a/lib/src/4337/userop.dart +++ b/lib/src/4337/userop.dart @@ -115,34 +115,6 @@ class UserOperation implements UserOperationBase { paymasterAndData: Uint8List(0), ); - /// Creates a [UserOperation] by updating an existing operation gas params. - /// - /// Parameters: - /// - `opGas`: Optional parameter of type [UserOperationGas] for specifying gas-related information. - /// - `feePerGas`: Optional parameter of type [Map] for specifying maxFeePerGas and maxPriorityFeePerGas. - /// - /// Returns: - /// A [UserOperation] instance created from the provided map. - /// - /// Example: - /// ```dart - /// var map = UserOperation.partial(callData: Uint8List(0xabcdef)).toMap(); - /// var updatedUserOperation = UserOperation.update( - /// map, - /// opGas: UserOperationGas(callGasLimit: BigInt.from(20000000), ...), - /// // Other parameters can be updated as needed. - /// ); - /// ``` - UserOperation updateOpGas( - UserOperationGas? opGas, Map? feePerGas) { - return copyWith( - callGasLimit: opGas?.callGasLimit, - verificationGasLimit: opGas?.verificationGasLimit, - preVerificationGas: opGas?.preVerificationGas, - maxFeePerGas: feePerGas?["maxFeePerGas"]!.getInWei, - maxPriorityFeePerGas: feePerGas?["maxPriorityFeePerGas"]!.getInWei); - } - UserOperation copyWith({ EthereumAddress? sender, BigInt? nonce, @@ -221,6 +193,27 @@ class UserOperation implements UserOperationBase { 'paymasterAndData': hexlify(paymasterAndData), }; } + + @override + UserOperation updateOpGas( + UserOperationGas? opGas, Map? feePerGas) { + return copyWith( + callGasLimit: opGas?.callGasLimit, + verificationGasLimit: opGas?.verificationGasLimit, + preVerificationGas: opGas?.preVerificationGas, + maxFeePerGas: feePerGas?["maxFeePerGas"]!.getInWei, + maxPriorityFeePerGas: feePerGas?["maxPriorityFeePerGas"]!.getInWei); + } + + Future validate(bool deployed, [String? initCode]) async { + require( + deployed + ? hexlify(this.initCode).toLowerCase() == "0x" + : hexlify(this.initCode).toLowerCase() == initCode?.toLowerCase(), + "InitCode mismatch"); + require(callData.length >= 4, "Calldata too short"); + require(signature.length >= 64, "Signature too short"); + } } class UserOperationByHash { @@ -317,7 +310,6 @@ class UserOperationReceipt { class UserOperationResponse { final String userOpHash; - final Future Function({int millisecond}) wait; - UserOperationResponse(this.userOpHash, this.wait); + UserOperationResponse(this.userOpHash); } diff --git a/lib/src/4337/wallet.dart b/lib/src/4337/wallet.dart index 52af463..518e90e 100644 --- a/lib/src/4337/wallet.dart +++ b/lib/src/4337/wallet.dart @@ -7,25 +7,37 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { Uint8List? _initCalldata; - bool? _deployed; - SmartWallet( {required Chain chain, required MultiSignerInterface signer, + @Deprecated( + "Bundler instance will be constructed by by factory from chain params") required BundlerProviderBase bundler, - @Deprecated("address will be made final in the future") + @Deprecated("to be removed: address will be made final in the future") EthereumAddress? address}) : _chain = chain.validate(), _walletAddress = address { - final rpc = RPCProvider(chain.ethRpcUrl!); + // 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 bundlerRpc = RPCProvider(chain.bundlerUrl!); + + final bundler = BundlerProvider(chain, bundlerRpc); final fact = _AccountFactory( - address: chain.accountFactory!, chainId: chain.chainId, rpc: rpc); + address: chain.accountFactory!, chainId: chain.chainId, rpc: ethRpc); addPlugin('signer', signer); addPlugin('bundler', bundler); - addPlugin('ethRpc', rpc); - addPlugin('contract', Contract(rpc)); + addPlugin('ethRpc', ethRpc); + addPlugin('contract', Contract(ethRpc)); addPlugin('factory', fact); + + if (chain.paymasterUrl != null) { + final paymasterRpc = RPCProvider(chain.paymasterUrl!); + final paymaster = Paymaster(chain, paymasterRpc); + addPlugin('paymaster', paymaster); + } } /// Initializes a [SmartWallet] instance for a specific chain with the provided parameters. @@ -50,10 +62,11 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { /// initCallData: Uint8List.fromList([0x01, 0x02, 0x03]), /// ); /// ``` - /// additionally initializes the associated Entrypoint contract for `tx.wait(userOpHash)` calls factory SmartWallet.init( {required Chain chain, required MultiSignerInterface signer, + @Deprecated( + "Bundler instance will be constructed by by factory from chain params") required BundlerProviderBase bundler, @Deprecated("address will be made final in the future") EthereumAddress? address, @@ -61,12 +74,6 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { Uint8List? initCallData}) { final instance = SmartWallet( chain: chain, signer: signer, bundler: bundler, address: address); - - instance.plugin('bundler').initializeWithEntrypoint(Entrypoint( - address: chain.entrypoint, - client: instance.plugin('factory').client, - )); - return instance; } @@ -75,11 +82,10 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { @override Future get balance => - plugin("contract").getBalance(_walletAddress); + plugin("contract").getBalance(_walletAddress); @override - Future get deployed => - plugin("contract").deployed(_walletAddress); + Future get isDeployed => plugin("contract").deployed(_walletAddress); @override String get initCode => _initCode; @@ -108,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('ethRpc').estimateGas(_chain.entrypoint, _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}); @@ -144,16 +150,13 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { @override Future getSimpleAccountAddress( EthereumAddress signer, Uint256 salt) => - plugin<_AccountFactory>('factory').getAddress(signer, salt.value); + plugin('factory').getAddress(signer, salt.value); @override Future getSimplePassKeyAccountAddress( PassKeyPair pkp, Uint256 salt) => - plugin<_AccountFactory>('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({ @@ -205,7 +208,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())); @@ -224,74 +227,53 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { final opHash = userOp.hash(_chain); - Uint8List signature = await plugin('signer') - .personalSign( - opHash, - index: index, - id: id ?? - (plugin('signer') is PasskeyInterface - ? plugin('signer').defaultId - : id)); + if (hasPlugin('paymaster')) { + userOp = await plugin().intercept(userOp); + } + + Uint8List signature = await plugin('signer').personalSign(opHash, + index: index, + id: id ?? + (plugin('signer') is PasskeyInterface + ? plugin('signer').defaultId + : id)); userOp.signature = hexlify(signature); + userOp.validate(userOp.nonce > BigInt.zero, initCode); - await _validateUserOperation(userOp); return userOp; } Uint8List _getInitCallData(String functionName, List params) => - plugin<_AccountFactory>('factory') - .self - .function(functionName) - .encodeCall(params); + plugin('factory').self.function(functionName).encodeCall(params); - Future _getNonce() => _deployed == false + Future _getNonce() => isDeployed.then((deployed) => !deployed ? Future.value(Uint256.zero) - : plugin("contract") + : plugin("contract") .call(_chain.entrypoint, ContractAbis.get('getNonce'), "getNonce", params: [_walletAddress, BigInt.zero]) .then((value) => Uint256(value[0])) .catchError((e) => - throw SmartWalletError.nonceError(_walletAddress, e.toString())); - - Future _updateUserOperation(UserOperation op) async { - List responses = await Future.wait([ - Future.microtask(() async => _deployed == null || _deployed == false - ? _deployed = await deployed - : _deployed), - plugin('ethRpc').getGasPrice(), - _getNonce() - ]); - - op = op.copyWith( - sender: _walletAddress, - nonce: responses[2].value, - initCode: responses[0] ? Uint8List(0) : null); - op.signature = plugin('signer').dummySignature; - - return plugin('bundler') - .estimateUserOperationGas(op.toMap(), _chain.entrypoint) - .then((opGas) => op.updateOpGas(opGas, responses[1])) - .then((op) => multiply(op)) - .catchError( - (e) => throw SmartWalletError.estimateError(op, e.toString())); - } - - Future _validateUserOperation(UserOperation op) async { - if (_walletAddress == null) { - throw SmartWalletError( - 'Wallet address not set', - ); - } - require(op.sender.hex == _walletAddress?.hex, "invalid sender address."); - require( - (_deployed ?? (await deployed)) - ? hexlify(op.initCode).toLowerCase() == "0x" - : hexlify(op.initCode).toLowerCase() == initCode.toLowerCase(), - "InitCode mismatch"); - require(op.callData.length >= 4, "Calldata too short"); - require(op.signature.length >= 64, "Signature too short"); - } + throw SmartWalletError.nonceError(_walletAddress, e.toString()))); + + Future _updateUserOperation(UserOperation op) => + Future.wait([_getNonce(), plugin('ethRpc').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]); + }); + + Future _updateUserOperationGas(UserOperation op, + {Map? feePerGas}) => + plugin('bundler') + .estimateUserOperationGas(op.toMap(), _chain.entrypoint) + .then((opGas) => op.updateOpGas(opGas, feePerGas)) + .then((op) => multiply(op)) + .catchError( + (e) => throw SmartWalletError.estimateError(op, e.toString())); } class SmartWalletError extends Error { diff --git a/lib/src/common/contract.dart b/lib/src/common/contract.dart index f4eec72..31f3798 100644 --- a/lib/src/common/contract.dart +++ b/lib/src/common/contract.dart @@ -2,18 +2,12 @@ part of 'common.dart'; /// A wrapper for interacting with deployed Ethereum contracts through [RPCProvider]. class Contract { - RPCProviderBase _provider; + final RPCProviderBase _provider; Contract( this._provider, ); - RPCProviderBase get provider => _provider; - - set setProvider(RPCProviderBase provider) { - _provider = provider; - } - /// Asynchronously calls a function on a smart contract with the provided parameters. /// /// Parameters: diff --git a/lib/src/common/plugins.dart b/lib/src/common/mixins.dart similarity index 63% rename from lib/src/common/plugins.dart rename to lib/src/common/mixins.dart index 95c175c..e3ac920 100644 --- a/lib/src/common/plugins.dart +++ b/lib/src/common/mixins.dart @@ -4,7 +4,6 @@ typedef Percent = double; class GasSettings { Percent? gasMultiplierPercentage; - Percent? pvgx; BigInt? userDefinedMaxFeePerGas; BigInt? userDefinedMaxPriorityFeePerGas; bool retryFailedSendUserOp; @@ -14,9 +13,8 @@ class GasSettings { this.userDefinedMaxFeePerGas, this.userDefinedMaxPriorityFeePerGas, this.retryFailedSendUserOp = false, - this.pvgx = 0.2, - }) : assert(gasMultiplierPercentage! >= 0 && pvgx! > 0 && pvgx <= 0.2, - 'Gas multiplier percentage or pvgx is out of permissible bounds.'); + }) : assert(gasMultiplierPercentage! >= 0, + 'Gas multiplier percentage should be 0 to 100'); } mixin _GasSettings { @@ -25,26 +23,19 @@ mixin _GasSettings { set setGasParams(GasSettings gasParams) => _gasParams = gasParams; UserOperation multiply(UserOperation op) { - op = op.copyWith( - preVerificationGas: - op.preVerificationGas * BigInt.from(_gasParams.pvgx! + 1), + final multiplier = + BigInt.from(_gasParams.gasMultiplierPercentage! / 100 + 1); + + return op.copyWith( + callGasLimit: op.callGasLimit * multiplier, + verificationGasLimit: op.verificationGasLimit * multiplier, + preVerificationGas: op.preVerificationGas * multiplier, maxFeePerGas: _gasParams.userDefinedMaxFeePerGas, maxPriorityFeePerGas: _gasParams.userDefinedMaxPriorityFeePerGas); - - final multiplier = _gasParams.gasMultiplierPercentage! > 0 - ? BigInt.from(_gasParams.gasMultiplierPercentage! + 1) - : BigInt.one; - - op = op.copyWith( - callGasLimit: op.callGasLimit * multiplier, - verificationGasLimit: op.verificationGasLimit * multiplier, - preVerificationGas: op.preVerificationGas * multiplier, - ); - - return op; } - dynamic retryOp(Function callback, dynamic err) { + Future retryOp( + Future Function() callback, dynamic err) { if (_gasParams.retryFailedSendUserOp) { return callback(); } @@ -77,14 +68,32 @@ mixin _PluginManager { /// Gets a plugin by name. /// /// Parameters: - /// - `name`: The name of the plugin to retrieve. + /// - `name`: Optional. The name of the plugin to retrieve. /// /// Returns: /// The plugin with the specified name. - T plugin(String name) { + T plugin([String? name]) { + if (name == null) { + for (var plugin in _plugins.values) { + if (plugin is T) { + return plugin; + } + } + } return _plugins[name] as T; } + /// checks if a plugin exists + /// + /// Parameters: + /// - `name`: The name of the plugin to check + /// + /// Returns: + /// true if the plugin exists + bool hasPlugin(String name) { + return _plugins.containsKey(name); + } + /// Removes an unwanted plugin by name. /// /// Parameters: diff --git a/lib/src/interfaces/bundler_provider.dart b/lib/src/interfaces/bundler_provider.dart index c691401..52a4a84 100644 --- a/lib/src/interfaces/bundler_provider.dart +++ b/lib/src/interfaces/bundler_provider.dart @@ -5,6 +5,17 @@ part of 'interfaces.dart'; /// Implementations of this class are expected to provide functionality for interacting specifically /// with bundlers and provides methods for sending user operations to an entrypoint. abstract class BundlerProviderBase { + /// Set of Ethereum RPC methods supported by a 4337 bundler. + static final Set methods = { + 'eth_chainId', + 'eth_sendUserOperation', + 'eth_estimateUserOperationGas', + 'eth_getUserOperationByHash', + 'eth_getUserOperationReceipt', + 'eth_supportedEntryPoints', + 'pm_sponsorUserOperation' + }; + /// Asynchronously estimates the gas cost for a user operation using the provided data and entrypoint. /// /// Parameters: @@ -55,11 +66,6 @@ abstract class BundlerProviderBase { /// This method uses the bundled RPC to fetch the receipt of the specified user operation using its hash. Future getUserOpReceipt(String userOpHash); - /// Initializes the provider with an entrypoint. - /// - /// - [ep]: The entrypoint to initialize with. - void initializeWithEntrypoint(Entrypoint ep); - /// Asynchronously sends a user operation to the bundler for execution. /// /// Parameters: @@ -91,20 +97,20 @@ abstract class BundlerProviderBase { /// ``` Future> supportedEntryPoints(); - /// Asynchronously waits for a FilterEvent within a specified time duration based on an event emmitted by entrypoint. - /// Used to wait for [UserOperation] to complete. + /// Validates if the provided method is a supported RPC method. /// /// Parameters: - /// - `millisecond`: The time duration, in milliseconds, to wait for a FilterEvent. Defaults to `0`. + /// - `method`: The Ethereum RPC method to validate. /// - /// Returns: - /// A [Future] that completes with a [FilterEvent] if one is found within the specified duration, otherwise, returns `null`. + /// Throws: + /// - A [Exception] if the method is not a valid supported method. /// /// Example: /// ```dart - /// var filterEvent = await wait(millisecond: 5000); + /// validateBundlerMethod('eth_sendUserOperation'); /// ``` - /// This method waits for a FilterEvent related to the 'UserOperationEvent' within the given time duration. - - Future wait({int millisecond}); + static validateBundlerMethod(String method) { + assert(methods.contains(method), + "validateMethod: method ::'$method':: is not a valid method"); + } } diff --git a/lib/src/interfaces/interfaces.dart b/lib/src/interfaces/interfaces.dart index 2e4fb50..f974d90 100644 --- a/lib/src/interfaces/interfaces.dart +++ b/lib/src/interfaces/interfaces.dart @@ -27,7 +27,6 @@ import '../../variance.dart' UserOperationGas, UserOperationReceipt, UserOperationResponse; -import '../abis/abis.dart' show Entrypoint; part 'account_factory.dart'; part 'bundler_provider.dart'; diff --git a/lib/src/interfaces/smart_wallet.dart b/lib/src/interfaces/smart_wallet.dart index cd96b91..ecf10ed 100644 --- a/lib/src/interfaces/smart_wallet.dart +++ b/lib/src/interfaces/smart_wallet.dart @@ -14,7 +14,7 @@ abstract class SmartWalletBase { Future get balance; /// Checks if the Smart Wallet has been deployed on the blockchain. - Future get deployed; + Future get isDeployed; /// Retrieves the init code of the Smart Wallet. String? get initCode; @@ -80,7 +80,7 @@ abstract class SmartWalletBase { void dangerouslySetInitCallData(Uint8List? code); /// Asynchronously creates a simple Ethereum smart account using the provided salt value. - /// Uses counterfactactual deployment to create the account and [deployed] should be used to check deployment status. + /// Uses counterfactactual deployment to create the account and [isDeployed] should be used to check deployment status. /// An `initCode` will be attached on the first transaction. /// /// Parameters: diff --git a/lib/src/interfaces/user_operations.dart b/lib/src/interfaces/user_operations.dart index 10564d3..e6985b4 100644 --- a/lib/src/interfaces/user_operations.dart +++ b/lib/src/interfaces/user_operations.dart @@ -41,4 +41,25 @@ abstract class UserOperationBase { /// /// Returns a [Map] representing the user operation. Map toMap(); + + /// Creates a [UserOperation] by updating an existing operation gas params. + /// + /// Parameters: + /// - `opGas`: Optional parameter of type [UserOperationGas] for specifying gas-related information. + /// - `feePerGas`: Optional parameter of type [Map] for specifying maxFeePerGas and maxPriorityFeePerGas. + /// + /// Returns: + /// A [UserOperation] instance created from the provided map. + /// + /// Example: + /// ```dart + /// var map = UserOperation.partial(callData: Uint8List(0xabcdef)).toMap(); + /// var updatedUserOperation = UserOperation.update( + /// map, + /// opGas: UserOperationGas(callGasLimit: BigInt.from(20000000), ...), + /// // Other parameters can be updated as needed. + /// ); + /// ``` + UserOperation updateOpGas( + UserOperationGas? opGas, Map? feePerGas); } diff --git a/lib/variance.dart b/lib/variance.dart index ad77e2c..cb402ac 100644 --- a/lib/variance.dart +++ b/lib/variance.dart @@ -2,7 +2,6 @@ library variance; import 'dart:async'; import 'dart:convert'; -import 'dart:isolate'; import 'dart:math'; import 'dart:typed_data'; @@ -34,7 +33,7 @@ part 'src/4337/providers.dart'; part 'src/4337/userop.dart'; part 'src/4337/wallet.dart'; part 'src/common/factory.dart'; -part 'src/common/plugins.dart'; +part 'src/common/mixins.dart'; part 'src/signers/private_key_signer.dart'; part 'src/signers/hd_wallet_signer.dart'; part 'src/signers/passkey_signer.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 3415b31..fd77e39 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.7 +version: 0.0.8 documentation: https://docs.variance.space homepage: https://variance.space repository: https://github.com/vaariance/variance-dart From 6c8b22ad9e105872c8383337249e386fb031e2b4 Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Tue, 27 Feb 2024 10:38:05 +0100 Subject: [PATCH 07/31] 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 From 1296689b2f3bd354424675d998bbb75bbe4a4412 Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Sat, 23 Mar 2024 14:24:46 +0100 Subject: [PATCH 08/31] feat: add web3 signers --- lib/interfaces.dart | 4 - lib/src/4337/chains.dart | 2 +- lib/src/4337/factory.dart | 123 +++ lib/src/4337/userop.dart | 4 +- lib/src/4337/wallet.dart | 201 +---- lib/src/abis/abis.dart | 2 - lib/src/abis/nft.abi.json | 270 ------- lib/src/abis/nft.g.dart | 546 ------------- lib/src/abis/simpleAccount.abi.json | 327 -------- lib/src/abis/simpleAccount.g.dart | 510 ------------- lib/src/abis/simplePasskeyAccount.abi.json | 334 -------- lib/src/abis/simplePasskeyAccount.g.dart | 549 ------------- lib/src/abis/token.abi.json | 201 ----- lib/src/abis/token.g.dart | 471 ------------ lib/src/common/abi_coder.dart | 59 -- lib/src/common/address.dart | 71 -- lib/src/common/common.dart | 17 - lib/src/common/contract.dart | 16 +- lib/src/common/factory.dart | 2 +- lib/src/common/mixins.dart | 2 +- lib/src/common/pack.dart | 27 + lib/src/common/uint256.dart | 114 --- lib/src/errors/wallet_errors.dart | 50 ++ lib/src/interfaces/ens_resolver.dart | 24 - lib/src/interfaces/hd_interface.dart | 47 -- lib/src/interfaces/interfaces.dart | 26 +- lib/src/interfaces/local_authentication.dart | 45 -- .../interfaces/multi_signer_interface.dart | 74 -- lib/src/interfaces/passkey_interface.dart | 101 --- .../interfaces/secure_storage_repository.dart | 149 ---- lib/src/interfaces/smart_wallet.dart | 81 +- lib/src/interfaces/smart_wallet_factory.dart | 79 ++ lib/src/interfaces/uint256_interface.dart | 138 ---- lib/src/signers/hd_wallet_signer.dart | 158 ---- lib/src/signers/passkey_signer.dart | 420 ---------- lib/src/signers/private_key_signer.dart | 128 ---- lib/src/utils/chainbase_api.dart | 416 ---------- lib/src/utils/crypto.dart | 173 ----- lib/src/utils/dio_client.dart | 90 --- lib/src/utils/local_authentication.dart | 83 -- lib/src/utils/models/ens.dart | 21 - lib/src/utils/models/ens.freezed.dart | 335 -------- lib/src/utils/models/ens.g.dart | 31 - lib/src/utils/models/metadata.dart | 26 - lib/src/utils/models/metadata.freezed.dart | 343 --------- lib/src/utils/models/metadata.g.dart | 35 - lib/src/utils/models/nft.dart | 138 ---- lib/src/utils/models/nft.freezed.dart | 548 ------------- lib/src/utils/models/nft.g.dart | 53 -- lib/src/utils/models/price.dart | 17 - lib/src/utils/models/price.freezed.dart | 222 ------ lib/src/utils/models/price.g.dart | 25 - lib/src/utils/models/token.dart | 98 --- lib/src/utils/models/token.freezed.dart | 361 --------- lib/src/utils/models/token.g.dart | 43 -- lib/src/utils/models/transaction.dart | 45 -- lib/src/utils/models/transaction.freezed.dart | 722 ------------------ lib/src/utils/models/transaction.g.dart | 72 -- lib/src/utils/models/transfer.dart | 30 - lib/src/utils/models/transfer.freezed.dart | 430 ----------- lib/src/utils/models/transfer.g.dart | 44 -- lib/src/utils/secure_storage_repository.dart | 136 ---- lib/utils.dart | 43 -- lib/variance.dart | 24 +- pubspec.lock | 386 ++-------- pubspec.yaml | 17 +- 66 files changed, 385 insertions(+), 9994 deletions(-) delete mode 100644 lib/interfaces.dart create mode 100644 lib/src/4337/factory.dart delete mode 100644 lib/src/abis/nft.abi.json delete mode 100644 lib/src/abis/nft.g.dart delete mode 100644 lib/src/abis/simpleAccount.abi.json delete mode 100644 lib/src/abis/simpleAccount.g.dart delete mode 100644 lib/src/abis/simplePasskeyAccount.abi.json delete mode 100644 lib/src/abis/simplePasskeyAccount.g.dart delete mode 100644 lib/src/abis/token.abi.json delete mode 100644 lib/src/abis/token.g.dart delete mode 100644 lib/src/common/abi_coder.dart delete mode 100644 lib/src/common/address.dart delete mode 100644 lib/src/common/common.dart create mode 100644 lib/src/common/pack.dart delete mode 100644 lib/src/common/uint256.dart create mode 100644 lib/src/errors/wallet_errors.dart delete mode 100644 lib/src/interfaces/ens_resolver.dart delete mode 100644 lib/src/interfaces/hd_interface.dart delete mode 100644 lib/src/interfaces/local_authentication.dart delete mode 100644 lib/src/interfaces/multi_signer_interface.dart delete mode 100644 lib/src/interfaces/passkey_interface.dart delete mode 100644 lib/src/interfaces/secure_storage_repository.dart create mode 100644 lib/src/interfaces/smart_wallet_factory.dart delete mode 100644 lib/src/interfaces/uint256_interface.dart delete mode 100644 lib/src/signers/hd_wallet_signer.dart delete mode 100644 lib/src/signers/passkey_signer.dart delete mode 100644 lib/src/signers/private_key_signer.dart delete mode 100644 lib/src/utils/chainbase_api.dart delete mode 100644 lib/src/utils/crypto.dart delete mode 100644 lib/src/utils/dio_client.dart delete mode 100644 lib/src/utils/local_authentication.dart delete mode 100644 lib/src/utils/models/ens.dart delete mode 100644 lib/src/utils/models/ens.freezed.dart delete mode 100644 lib/src/utils/models/ens.g.dart delete mode 100644 lib/src/utils/models/metadata.dart delete mode 100644 lib/src/utils/models/metadata.freezed.dart delete mode 100644 lib/src/utils/models/metadata.g.dart delete mode 100644 lib/src/utils/models/nft.dart delete mode 100644 lib/src/utils/models/nft.freezed.dart delete mode 100644 lib/src/utils/models/nft.g.dart delete mode 100644 lib/src/utils/models/price.dart delete mode 100644 lib/src/utils/models/price.freezed.dart delete mode 100644 lib/src/utils/models/price.g.dart delete mode 100644 lib/src/utils/models/token.dart delete mode 100644 lib/src/utils/models/token.freezed.dart delete mode 100644 lib/src/utils/models/token.g.dart delete mode 100644 lib/src/utils/models/transaction.dart delete mode 100644 lib/src/utils/models/transaction.freezed.dart delete mode 100644 lib/src/utils/models/transaction.g.dart delete mode 100644 lib/src/utils/models/transfer.dart delete mode 100644 lib/src/utils/models/transfer.freezed.dart delete mode 100644 lib/src/utils/models/transfer.g.dart delete mode 100644 lib/src/utils/secure_storage_repository.dart delete mode 100644 lib/utils.dart diff --git a/lib/interfaces.dart b/lib/interfaces.dart deleted file mode 100644 index b804c71..0000000 --- a/lib/interfaces.dart +++ /dev/null @@ -1,4 +0,0 @@ -library interfaces; - -export 'src/interfaces/interfaces.dart' - show MultiSignerInterface, HDInterface, PasskeyInterface; diff --git a/lib/src/4337/chains.dart b/lib/src/4337/chains.dart index b42887a..3464bc9 100644 --- a/lib/src/4337/chains.dart +++ b/lib/src/4337/chains.dart @@ -21,7 +21,7 @@ class Chain { /// asserts that [jsonRpcUrl] and [bundlerUrl] is provided Chain validate() { require(isURL(jsonRpcUrl), - "Chain Config Error: please provide a valid eth rpc url"); + "Chain Config Error: please provide a valid eth json rpc url"); require(isURL(bundlerUrl), "Chain Config Error: please provide a valid bundler url"); require(accountFactory != null, diff --git a/lib/src/4337/factory.dart b/lib/src/4337/factory.dart new file mode 100644 index 0000000..1543b72 --- /dev/null +++ b/lib/src/4337/factory.dart @@ -0,0 +1,123 @@ + part of '../../variance.dart'; + + class SmartWalletFactory implements SmartWalletFactoryBase { + + // createSimpleAccount + // createP256Account + // createVendorAccount + // createSafeAccount + // _createAccount + + } + + SmartWallet( + {required Chain chain, + required MultiSignerInterface signer, + @Deprecated( + "Bundler instance will be constructed by by factory from chain params") + required BundlerProviderBase bundler, + @Deprecated("to be removed: address will be made final in the future") + EthereumAddress? address}) + : _chain = chain.validate(), + _walletAddress = address { + // 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 jsonRpc = RPCProvider(chain.jsonRpcUrl!); + final bundlerRpc = RPCProvider(chain.bundlerUrl!); + + final bundler = BundlerProvider(chain, bundlerRpc); + final fact = _AccountFactory( + address: chain.accountFactory!, chainId: chain.chainId, rpc: jsonRpc); + + addPlugin('signer', signer); + addPlugin('bundler', bundler); + addPlugin('jsonRpc', jsonRpc); + addPlugin('contract', Contract(jsonRpc)); + addPlugin('factory', fact); + + if (chain.paymasterUrl != null) { + final paymasterRpc = RPCProvider(chain.paymasterUrl!); + final paymaster = Paymaster(chain, paymasterRpc); + addPlugin('paymaster', paymaster); + } + } + + /// Initializes a [SmartWallet] instance for a specific chain with the provided parameters. + /// + /// Parameters: + /// - `chain`: The blockchain [Chain] associated with the smart wallet. + /// - `signer`: The [MultiSignerInterface] responsible for signing transactions. + /// - `bundler`: The [BundlerProviderBase] that provides bundling services. + /// - `address`: Optional Ethereum address associated with the smart wallet. + /// - `initCallData`: Optional initialization calldata of the factory create method as a [Uint8List]. + /// + /// Returns: + /// A fully initialized [SmartWallet] instance. + /// + /// Example: + /// ```dart + /// var smartWallet = SmartWallet.init( + /// chain: Chain.ethereum, + /// signer: myMultiSigner, + /// bundler: myBundler, + /// address: myWalletAddress, + /// initCallData: Uint8List.fromList([0x01, 0x02, 0x03]), + /// ); + /// ``` + factory SmartWallet.init( + {required Chain chain, + required MultiSignerInterface signer, + @Deprecated( + "Bundler instance will be constructed by by factory from chain params") + required BundlerProviderBase bundler, + @Deprecated("address will be made final in the future") + EthereumAddress? address, + @Deprecated("seperation of factory from wallet soon will be enforced") + Uint8List? initCallData}) { + final instance = SmartWallet( + chain: chain, signer: signer, bundler: bundler, address: address); + return instance; + } + +@override +Future createSimplePasskeyAccount( + PassKeyPair pkp, Uint256 salt) async { + _initCalldata = _getInitCallData('createPasskeyAccount', [ + pkp.credentialHexBytes, + pkp.publicKey[0].value, + pkp.publicKey[1].value, + salt.value + ]); + + await getSimplePassKeyAccountAddress(pkp, salt) + .then((addr) => {_walletAddress = addr}); + return this; +} + +@override +Future createSimpleAccount(Uint256 salt, {int? index}) async { + EthereumAddress signer = EthereumAddress.fromHex( + plugin('signer').getAddress(index: index ?? 0)); + _initCalldata = _getInitCallData('createAccount', [signer, salt.value]); + await getSimpleAccountAddress(signer, salt) + .then((addr) => {_walletAddress = addr}); + return this; +} + +@override +Future getSimpleAccountAddress( + EthereumAddress signer, Uint256 salt) => + 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); + +Uint8List _getInitCallData(String functionName, List params) => + plugin('factory').self.function(functionName).encodeCall(params); diff --git a/lib/src/4337/userop.dart b/lib/src/4337/userop.dart index c7522bf..1ea3706 100644 --- a/lib/src/4337/userop.dart +++ b/lib/src/4337/userop.dart @@ -240,14 +240,14 @@ class UserOperation implements UserOperationBase { 'nonce': '0x${nonce.toRadixString(16)}', 'initCode': hexlify(initCode), 'callData': hexlify(callData), - 'callGasLimit': '0x${callGasLimit.toRadixString(16)}', + 'preVerificationGas': '0x${preVerificationGas.toRadixString(16)}', 'signature': signature, 'paymasterAndData': hexlify(paymasterAndData), }; if (accountGasLimits.isEmpty || accountGasLimits == Uint8List(0)) { + op['callGasLimit'] = '0x${callGasLimit.toRadixString(16)}'; op['verificationGasLimit'] = '0x${verificationGasLimit.toRadixString(16)}'; - op['preVerificationGas'] = '0x${preVerificationGas.toRadixString(16)}'; } else { op['accountGasLimits'] = hexlify(accountGasLimits); } diff --git a/lib/src/4337/wallet.dart b/lib/src/4337/wallet.dart index af2ec8c..3461be1 100644 --- a/lib/src/4337/wallet.dart +++ b/lib/src/4337/wallet.dart @@ -3,92 +3,26 @@ part of '../../variance.dart'; class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { final Chain _chain; - EthereumAddress? _walletAddress; + final EthereumAddress _walletAddress; - Uint8List? _initCalldata; + Uint8List _initCalldata; - SmartWallet( - {required Chain chain, - required MultiSignerInterface signer, - @Deprecated( - "Bundler instance will be constructed by by factory from chain params") - required BundlerProviderBase bundler, - @Deprecated("to be removed: address will be made final in the future") - EthereumAddress? address}) - : _chain = chain.validate(), - _walletAddress = address { - // 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 jsonRpc = RPCProvider(chain.jsonRpcUrl!); - final bundlerRpc = RPCProvider(chain.bundlerUrl!); - - final bundler = BundlerProvider(chain, bundlerRpc); - final fact = _AccountFactory( - address: chain.accountFactory!, chainId: chain.chainId, rpc: jsonRpc); - - addPlugin('signer', signer); - addPlugin('bundler', bundler); - addPlugin('jsonRpc', jsonRpc); - addPlugin('contract', Contract(jsonRpc)); - addPlugin('factory', fact); - - if (chain.paymasterUrl != null) { - final paymasterRpc = RPCProvider(chain.paymasterUrl!); - final paymaster = Paymaster(chain, paymasterRpc); - addPlugin('paymaster', paymaster); - } - } - - /// Initializes a [SmartWallet] instance for a specific chain with the provided parameters. - /// - /// Parameters: - /// - `chain`: The blockchain [Chain] associated with the smart wallet. - /// - `signer`: The [MultiSignerInterface] responsible for signing transactions. - /// - `bundler`: The [BundlerProviderBase] that provides bundling services. - /// - `address`: Optional Ethereum address associated with the smart wallet. - /// - `initCallData`: Optional initialization calldata of the factory create method as a [Uint8List]. - /// - /// Returns: - /// A fully initialized [SmartWallet] instance. - /// - /// Example: - /// ```dart - /// var smartWallet = SmartWallet.init( - /// chain: Chain.ethereum, - /// signer: myMultiSigner, - /// bundler: myBundler, - /// address: myWalletAddress, - /// initCallData: Uint8List.fromList([0x01, 0x02, 0x03]), - /// ); - /// ``` - factory SmartWallet.init( - {required Chain chain, - required MultiSignerInterface signer, - @Deprecated( - "Bundler instance will be constructed by by factory from chain params") - required BundlerProviderBase bundler, - @Deprecated("address will be made final in the future") - EthereumAddress? address, - @Deprecated("seperation of factory from wallet soon will be enforced") - Uint8List? initCallData}) { - final instance = SmartWallet( - chain: chain, signer: signer, bundler: bundler, address: address); - return instance; - } + SmartWallet(this._chain, this._walletAddress, this._initCalldata); @override - EthereumAddress? get address => _walletAddress; + EthereumAddress get address => _walletAddress; @override Future get balance => - plugin("contract").getBalance(_walletAddress); + plugin("contract").getBalance(_walletAddress); @override - Future get isDeployed => plugin("contract").deployed(_walletAddress); + Future get isDeployed => + plugin("contract").deployed(_walletAddress); @override - String get initCode => _initCode; + String get initCode => + _chain.accountFactory!.hexEip55 + hexlify(_initCalldata).substring(2); @override Future get initCodeGas => _initCodeGas; @@ -97,70 +31,22 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { Future get nonce => _getNonce(); @override - @Deprecated("wallet address will be made final in the future") - set setWalletAddress(EthereumAddress address) => _walletAddress = address; - - @override - String? get toHex => _walletAddress?.hexEip55; - - String get _initCode => _initCalldata != null - ? _chain.accountFactory!.hexEip55 + hexlify(_initCalldata!).substring(2) - : "0x"; + String? get toHex => _walletAddress.hexEip55; Uint8List get _initCodeBytes { - if (_initCalldata == null) return Uint8List(0); List extended = _chain.accountFactory!.addressBytes.toList(); - extended.addAll(_initCalldata!); + extended.addAll(_initCalldata); return Uint8List.fromList(extended); } 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)); - _initCalldata = _getInitCallData('createAccount', [signer, salt.value]); - await getSimpleAccountAddress(signer, salt) - .then((addr) => {_walletAddress = addr}); - return this; - } - - @override - Future createSimplePasskeyAccount( - PassKeyPair pkp, Uint256 salt) async { - _initCalldata = _getInitCallData('createPasskeyAccount', [ - pkp.credentialHexBytes, - pkp.publicKey[0].value, - pkp.publicKey[1].value, - salt.value - ]); - - await getSimplePassKeyAccountAddress(pkp, salt) - .then((addr) => {_walletAddress = addr}); - return this; - } + .estimateGas(_chain.entrypoint.address, initCode); @override - void dangerouslySetInitCallData(Uint8List? code) { + void dangerouslySetInitCallData(Uint8List code) { _initCalldata = code; } - @override - Future getSimpleAccountAddress( - EthereumAddress signer, Uint256 salt) => - 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); - @override UserOperation buildUserOperation({ required Uint8List callData, @@ -202,8 +88,7 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { Future sendSignedUserOperation(UserOperation op) => plugin('bundler') .sendUserOperation(op.toMap(), _chain.entrypoint) - .catchError( - (e) => throw SmartWalletError.sendError(op, e.toString())); + .catchError((e) => throw SendError(e.toString(), op)); @override Future sendUserOperation(UserOperation op, @@ -223,12 +108,8 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { userOp = await plugin('paymaster').intercept(userOp); } - Uint8List signature = await plugin('signer').personalSign(opHash, - index: index, - id: id ?? - (plugin('signer') is PasskeyInterface - ? plugin('signer').defaultId - : id)); + Uint8List signature = + await plugin('signer').personalSign(opHash, index: index); userOp.signature = hexlify(signature); userOp.validate(userOp.nonce > BigInt.zero, initCode); @@ -236,17 +117,14 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { return userOp; } - Uint8List _getInitCallData(String functionName, List params) => - plugin('factory').self.function(functionName).encodeCall(params); - Future _getNonce() => isDeployed.then((deployed) => !deployed ? Future.value(Uint256.zero) - : plugin("contract") - .call(_chain.entrypoint, ContractAbis.get('getNonce'), "getNonce", + : plugin("contract") + .call(_chain.entrypoint.address, ContractAbis.get('getNonce'), + "getNonce", params: [_walletAddress, BigInt.zero]) .then((value) => Uint256(value[0])) - .catchError((e) => - throw SmartWalletError.nonceError(_walletAddress, e.toString()))); + .catchError((e) => throw NonceError(e.toString(), _walletAddress))); Future _updateUserOperation(UserOperation op) => Future.wait( @@ -278,44 +156,7 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { .estimateUserOperationGas(op.toMap(), _chain.entrypoint) .then((opGas) => op.updateOpGas(opGas, feePerGas)) .then((op) => multiply(op)) - .catchError( - (e) => throw SmartWalletError.estimateError(op, e.toString())); -} - -class SmartWalletError extends Error { - final String message; - - SmartWalletError(this.message); - - factory SmartWalletError.estimateError(UserOperation op, String message) { - return SmartWalletError(''' - Error estimating user operation gas! Failed with error: $message - -------------------------------------------------- - User operation: ${op.toJson()}. - '''); - } - - factory SmartWalletError.nonceError( - EthereumAddress? address, String message) { - return SmartWalletError(''' - Error fetching user account nonce for address ${address?.hex}! - -------------------------------------------------- - Failed with error: $message - '''); - } - - factory SmartWalletError.sendError(UserOperation op, String message) { - return SmartWalletError(''' - Error sending user operation! Failed with error: $message - -------------------------------------------------- - User operation: ${op.toJson()}. - '''); - } - - @override - String toString() { - return message; - } + .catchError((e) => throw EstimateError(e.toString(), op)); } typedef MSI = MultiSignerInterface; diff --git a/lib/src/abis/abis.dart b/lib/src/abis/abis.dart index 2e8d394..bbaf6ab 100644 --- a/lib/src/abis/abis.dart +++ b/lib/src/abis/abis.dart @@ -1,5 +1,3 @@ export 'accountFactory.g.dart'; export 'contract_abis.dart'; export 'entrypoint.g.dart'; -export 'token.g.dart'; -export 'simplePasskeyAccount.g.dart'; diff --git a/lib/src/abis/nft.abi.json b/lib/src/abis/nft.abi.json deleted file mode 100644 index ccbf5ee..0000000 --- a/lib/src/abis/nft.abi.json +++ /dev/null @@ -1,270 +0,0 @@ -[ - { - "inputs": [ - { "internalType": "string", "name": "name", "type": "string" }, - { "internalType": "string", "name": "symbol", "type": "string" } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "approved", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "approved", - "type": "bool" - } - ], - "name": "ApprovalForAll", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [ - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "approve", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" } - ], - "name": "balanceOf", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "baseURI", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "burn", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "getApproved", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" }, - { "internalType": "address", "name": "operator", "type": "address" } - ], - "name": "isApprovedForAll", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, - { "internalType": "string", "name": "_tokenURI", "type": "string" } - ], - "name": "mint", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "ownerOf", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, - { "internalType": "bytes", "name": "_data", "type": "bytes" } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "operator", "type": "address" }, - { "internalType": "bool", "name": "approved", "type": "bool" } - ], - "name": "setApprovalForAll", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "string", "name": "baseURI", "type": "string" } - ], - "name": "setBaseURI", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" } - ], - "name": "supportsInterface", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "index", "type": "uint256" } - ], - "name": "tokenByIndex", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" }, - { "internalType": "uint256", "name": "index", "type": "uint256" } - ], - "name": "tokenOfOwnerByIndex", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "tokenURI", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "transferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/lib/src/abis/nft.g.dart b/lib/src/abis/nft.g.dart deleted file mode 100644 index e7aab7a..0000000 --- a/lib/src/abis/nft.g.dart +++ /dev/null @@ -1,546 +0,0 @@ -// Generated code, do not modify. Run `build_runner build` to re-generate! -// @dart=2.12 -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:web3dart/web3dart.dart' as _i1; -import 'dart:typed_data' as _i2; - -final _contractAbi = _i1.ContractAbi.fromJson( - '[{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"string","name":"_tokenURI","type":"string"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"baseURI","type":"string"}],"name":"setBaseURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"}]', - 'Nft', -); - -class Nft extends _i1.GeneratedContract { - Nft({ - required _i1.EthereumAddress address, - required _i1.Web3Client client, - int? chainId, - }) : super( - _i1.DeployedContract( - _contractAbi, - address, - ), - client, - chainId, - ); - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future approve( - _i1.EthereumAddress to, - BigInt tokenId, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[1]; - assert(checkSignature(function, '095ea7b3')); - final params = [ - to, - tokenId, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future balanceOf( - _i1.EthereumAddress owner, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[2]; - assert(checkSignature(function, '70a08231')); - final params = [owner]; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as BigInt); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future baseURI({_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[3]; - assert(checkSignature(function, '6c0360eb')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as String); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future burn( - BigInt tokenId, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[4]; - assert(checkSignature(function, '42966c68')); - final params = [tokenId]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future<_i1.EthereumAddress> getApproved( - BigInt tokenId, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[5]; - assert(checkSignature(function, '081812fc')); - final params = [tokenId]; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as _i1.EthereumAddress); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future isApprovedForAll( - _i1.EthereumAddress owner, - _i1.EthereumAddress operator, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[6]; - assert(checkSignature(function, 'e985e9c5')); - final params = [ - owner, - operator, - ]; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as bool); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future mint( - _i1.EthereumAddress to, - BigInt tokenId, - String _tokenURI, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[7]; - assert(checkSignature(function, 'd3fc9864')); - final params = [ - to, - tokenId, - _tokenURI, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future name({_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[8]; - assert(checkSignature(function, '06fdde03')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as String); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future<_i1.EthereumAddress> ownerOf( - BigInt tokenId, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[9]; - assert(checkSignature(function, '6352211e')); - final params = [tokenId]; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as _i1.EthereumAddress); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future safeTransferFrom( - _i1.EthereumAddress from, - _i1.EthereumAddress to, - BigInt tokenId, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[10]; - assert(checkSignature(function, '42842e0e')); - final params = [ - from, - to, - tokenId, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future safeTransferFrom$2( - _i1.EthereumAddress from, - _i1.EthereumAddress to, - BigInt tokenId, - _i2.Uint8List _data, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[11]; - assert(checkSignature(function, 'b88d4fde')); - final params = [ - from, - to, - tokenId, - _data, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future setApprovalForAll( - _i1.EthereumAddress operator, - bool approved, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[12]; - assert(checkSignature(function, 'a22cb465')); - final params = [ - operator, - approved, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future setBaseURI( - String baseURI, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[13]; - assert(checkSignature(function, '55f804b3')); - final params = [baseURI]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future supportsInterface( - _i2.Uint8List interfaceId, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[14]; - assert(checkSignature(function, '01ffc9a7')); - final params = [interfaceId]; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as bool); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future symbol({_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[15]; - assert(checkSignature(function, '95d89b41')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as String); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future tokenByIndex( - BigInt index, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[16]; - assert(checkSignature(function, '4f6ccce7')); - final params = [index]; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as BigInt); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future tokenOfOwnerByIndex( - _i1.EthereumAddress owner, - BigInt index, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[17]; - assert(checkSignature(function, '2f745c59')); - final params = [ - owner, - index, - ]; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as BigInt); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future tokenURI( - BigInt tokenId, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[18]; - assert(checkSignature(function, 'c87b56dd')); - final params = [tokenId]; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as String); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future totalSupply({_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[19]; - assert(checkSignature(function, '18160ddd')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as BigInt); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future transferFrom( - _i1.EthereumAddress from, - _i1.EthereumAddress to, - BigInt tokenId, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[20]; - assert(checkSignature(function, '23b872dd')); - final params = [ - from, - to, - tokenId, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// Returns a live stream of all Approval events emitted by this contract. - Stream approvalEvents({ - _i1.BlockNum? fromBlock, - _i1.BlockNum? toBlock, - }) { - final event = self.event('Approval'); - final filter = _i1.FilterOptions.events( - contract: self, - event: event, - fromBlock: fromBlock, - toBlock: toBlock, - ); - return client.events(filter).map((_i1.FilterEvent result) { - final decoded = event.decodeResults( - result.topics!, - result.data!, - ); - return Approval( - decoded, - result, - ); - }); - } - - /// Returns a live stream of all ApprovalForAll events emitted by this contract. - Stream approvalForAllEvents({ - _i1.BlockNum? fromBlock, - _i1.BlockNum? toBlock, - }) { - final event = self.event('ApprovalForAll'); - final filter = _i1.FilterOptions.events( - contract: self, - event: event, - fromBlock: fromBlock, - toBlock: toBlock, - ); - return client.events(filter).map((_i1.FilterEvent result) { - final decoded = event.decodeResults( - result.topics!, - result.data!, - ); - return ApprovalForAll( - decoded, - result, - ); - }); - } - - /// Returns a live stream of all Transfer events emitted by this contract. - Stream transferEvents({ - _i1.BlockNum? fromBlock, - _i1.BlockNum? toBlock, - }) { - final event = self.event('Transfer'); - final filter = _i1.FilterOptions.events( - contract: self, - event: event, - fromBlock: fromBlock, - toBlock: toBlock, - ); - return client.events(filter).map((_i1.FilterEvent result) { - final decoded = event.decodeResults( - result.topics!, - result.data!, - ); - return Transfer( - decoded, - result, - ); - }); - } -} - -class Approval { - Approval( - List response, - this.event, - ) : owner = (response[0] as _i1.EthereumAddress), - approved = (response[1] as _i1.EthereumAddress), - tokenId = (response[2] as BigInt); - - final _i1.EthereumAddress owner; - - final _i1.EthereumAddress approved; - - final BigInt tokenId; - - final _i1.FilterEvent event; -} - -class ApprovalForAll { - ApprovalForAll( - List response, - this.event, - ) : owner = (response[0] as _i1.EthereumAddress), - operator = (response[1] as _i1.EthereumAddress), - approved = (response[2] as bool); - - final _i1.EthereumAddress owner; - - final _i1.EthereumAddress operator; - - final bool approved; - - final _i1.FilterEvent event; -} - -class Transfer { - Transfer( - List response, - this.event, - ) : from = (response[0] as _i1.EthereumAddress), - to = (response[1] as _i1.EthereumAddress), - tokenId = (response[2] as BigInt); - - final _i1.EthereumAddress from; - - final _i1.EthereumAddress to; - - final BigInt tokenId; - - final _i1.FilterEvent event; -} diff --git a/lib/src/abis/simpleAccount.abi.json b/lib/src/abis/simpleAccount.abi.json deleted file mode 100644 index 2d6cc5f..0000000 --- a/lib/src/abis/simpleAccount.abi.json +++ /dev/null @@ -1,327 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "contract IEntryPoint", - "name": "anEntryPoint", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [ - { "internalType": "address", "name": "target", "type": "address" } - ], - "name": "AddressEmptyCode", - "type": "error" - }, - { "inputs": [], "name": "ECDSAInvalidSignature", "type": "error" }, - { - "inputs": [ - { "internalType": "uint256", "name": "length", "type": "uint256" } - ], - "name": "ECDSAInvalidSignatureLength", - "type": "error" - }, - { - "inputs": [{ "internalType": "bytes32", "name": "s", "type": "bytes32" }], - "name": "ECDSAInvalidSignatureS", - "type": "error" - }, - { - "inputs": [ - { "internalType": "address", "name": "implementation", "type": "address" } - ], - "name": "ERC1967InvalidImplementation", - "type": "error" - }, - { "inputs": [], "name": "ERC1967NonPayable", "type": "error" }, - { "inputs": [], "name": "FailedInnerCall", "type": "error" }, - { "inputs": [], "name": "InvalidInitialization", "type": "error" }, - { "inputs": [], "name": "NotInitializing", "type": "error" }, - { "inputs": [], "name": "UUPSUnauthorizedCallContext", "type": "error" }, - { - "inputs": [ - { "internalType": "bytes32", "name": "slot", "type": "bytes32" } - ], - "name": "UUPSUnsupportedProxiableUUID", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint64", - "name": "version", - "type": "uint64" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "contract IEntryPoint", - "name": "entryPoint", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "name": "SimpleAccountInitialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "implementation", - "type": "address" - } - ], - "name": "Upgraded", - "type": "event" - }, - { - "inputs": [], - "name": "UPGRADE_INTERFACE_VERSION", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "addDeposit", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "entryPoint", - "outputs": [ - { "internalType": "contract IEntryPoint", "name": "", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "dest", "type": "address" }, - { "internalType": "uint256", "name": "value", "type": "uint256" }, - { "internalType": "bytes", "name": "func", "type": "bytes" } - ], - "name": "execute", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address[]", "name": "dest", "type": "address[]" }, - { "internalType": "uint256[]", "name": "value", "type": "uint256[]" }, - { "internalType": "bytes[]", "name": "func", "type": "bytes[]" } - ], - "name": "executeBatch", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "getDeposit", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getNonce", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "anOwner", "type": "address" } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "uint256[]", "name": "", "type": "uint256[]" }, - { "internalType": "uint256[]", "name": "", "type": "uint256[]" }, - { "internalType": "bytes", "name": "", "type": "bytes" } - ], - "name": "onERC1155BatchReceived", - "outputs": [{ "internalType": "bytes4", "name": "", "type": "bytes4" }], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "uint256", "name": "", "type": "uint256" }, - { "internalType": "uint256", "name": "", "type": "uint256" }, - { "internalType": "bytes", "name": "", "type": "bytes" } - ], - "name": "onERC1155Received", - "outputs": [{ "internalType": "bytes4", "name": "", "type": "bytes4" }], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "uint256", "name": "", "type": "uint256" }, - { "internalType": "bytes", "name": "", "type": "bytes" } - ], - "name": "onERC721Received", - "outputs": [{ "internalType": "bytes4", "name": "", "type": "bytes4" }], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "proxiableUUID", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" } - ], - "name": "supportsInterface", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "uint256", "name": "", "type": "uint256" }, - { "internalType": "bytes", "name": "", "type": "bytes" }, - { "internalType": "bytes", "name": "", "type": "bytes" } - ], - "name": "tokensReceived", - "outputs": [], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newImplementation", - "type": "address" - }, - { "internalType": "bytes", "name": "data", "type": "bytes" } - ], - "name": "upgradeToAndCall", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { "internalType": "address", "name": "sender", "type": "address" }, - { "internalType": "uint256", "name": "nonce", "type": "uint256" }, - { "internalType": "bytes", "name": "initCode", "type": "bytes" }, - { "internalType": "bytes", "name": "callData", "type": "bytes" }, - { - "internalType": "uint256", - "name": "callGasLimit", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "verificationGasLimit", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "preVerificationGas", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "maxFeePerGas", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "maxPriorityFeePerGas", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "paymasterAndData", - "type": "bytes" - }, - { "internalType": "bytes", "name": "signature", "type": "bytes" } - ], - "internalType": "struct UserOperation", - "name": "userOp", - "type": "tuple" - }, - { "internalType": "bytes32", "name": "userOpHash", "type": "bytes32" }, - { - "internalType": "uint256", - "name": "missingAccountFunds", - "type": "uint256" - } - ], - "name": "validateUserOp", - "outputs": [ - { "internalType": "uint256", "name": "validationData", "type": "uint256" } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address payable", - "name": "withdrawAddress", - "type": "address" - }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "withdrawDepositTo", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { "stateMutability": "payable", "type": "receive" } -] diff --git a/lib/src/abis/simpleAccount.g.dart b/lib/src/abis/simpleAccount.g.dart deleted file mode 100644 index 05fab4b..0000000 --- a/lib/src/abis/simpleAccount.g.dart +++ /dev/null @@ -1,510 +0,0 @@ -// Generated code, do not modify. Run `build_runner build` to re-generate! -// @dart=2.12 -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:web3dart/web3dart.dart' as _i1; -import 'dart:typed_data' as _i2; - -final _contractAbi = _i1.ContractAbi.fromJson( - '[{"inputs":[{"internalType":"contract IEntryPoint","name":"anEntryPoint","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[],"name":"ECDSAInvalidSignature","type":"error"},{"inputs":[{"internalType":"uint256","name":"length","type":"uint256"}],"name":"ECDSAInvalidSignatureLength","type":"error"},{"inputs":[{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"ECDSAInvalidSignatureS","type":"error"},{"inputs":[{"internalType":"address","name":"implementation","type":"address"}],"name":"ERC1967InvalidImplementation","type":"error"},{"inputs":[],"name":"ERC1967NonPayable","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[],"name":"UUPSUnauthorizedCallContext","type":"error"},{"inputs":[{"internalType":"bytes32","name":"slot","type":"bytes32"}],"name":"UUPSUnsupportedProxiableUUID","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IEntryPoint","name":"entryPoint","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"}],"name":"SimpleAccountInitialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"inputs":[],"name":"UPGRADE_INTERFACE_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"addDeposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"entryPoint","outputs":[{"internalType":"contract IEntryPoint","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"dest","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"func","type":"bytes"}],"name":"execute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"dest","type":"address[]"},{"internalType":"uint256[]","name":"value","type":"uint256[]"},{"internalType":"bytes[]","name":"func","type":"bytes[]"}],"name":"executeBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"anOwner","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155BatchReceived","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxiableUUID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"tokensReceived","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"upgradeToAndCall","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"uint256","name":"callGasLimit","type":"uint256"},{"internalType":"uint256","name":"verificationGasLimit","type":"uint256"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"uint256","name":"maxFeePerGas","type":"uint256"},{"internalType":"uint256","name":"maxPriorityFeePerGas","type":"uint256"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct UserOperation","name":"userOp","type":"tuple"},{"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"internalType":"uint256","name":"missingAccountFunds","type":"uint256"}],"name":"validateUserOp","outputs":[{"internalType":"uint256","name":"validationData","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"withdrawAddress","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawDepositTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]', - 'SimpleAccount', -); - -class SimpleAccount extends _i1.GeneratedContract { - SimpleAccount({ - required _i1.EthereumAddress address, - required _i1.Web3Client client, - int? chainId, - }) : super( - _i1.DeployedContract( - _contractAbi, - address, - ), - client, - chainId, - ); - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future UPGRADE_INTERFACE_VERSION({_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[1]; - assert(checkSignature(function, 'ad3cb1cc')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as String); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future addDeposit({ - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[2]; - assert(checkSignature(function, '4a58db19')); - final params = []; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future<_i1.EthereumAddress> entryPoint({_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[3]; - assert(checkSignature(function, 'b0d691fe')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as _i1.EthereumAddress); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future execute( - _i1.EthereumAddress dest, - BigInt value, - _i2.Uint8List func, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[4]; - assert(checkSignature(function, 'b61d27f6')); - final params = [ - dest, - value, - func, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future executeBatch( - List<_i1.EthereumAddress> dest, - List value, - List<_i2.Uint8List> func, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[5]; - assert(checkSignature(function, '47e1da2a')); - final params = [ - dest, - value, - func, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future getDeposit({_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[6]; - assert(checkSignature(function, 'c399ec88')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as BigInt); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future getNonce({_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[7]; - assert(checkSignature(function, 'd087d288')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as BigInt); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future initialize( - _i1.EthereumAddress anOwner, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[8]; - assert(checkSignature(function, 'c4d66de8')); - final params = [anOwner]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future<_i2.Uint8List> onERC1155BatchReceived( - _i1.EthereumAddress $param7, - _i1.EthereumAddress $param8, - List $param9, - List $param10, - _i2.Uint8List $param11, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[9]; - assert(checkSignature(function, 'bc197c81')); - final params = [ - $param7, - $param8, - $param9, - $param10, - $param11, - ]; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as _i2.Uint8List); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future<_i2.Uint8List> onERC1155Received( - _i1.EthereumAddress $param12, - _i1.EthereumAddress $param13, - BigInt $param14, - BigInt $param15, - _i2.Uint8List $param16, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[10]; - assert(checkSignature(function, 'f23a6e61')); - final params = [ - $param12, - $param13, - $param14, - $param15, - $param16, - ]; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as _i2.Uint8List); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future<_i2.Uint8List> onERC721Received( - _i1.EthereumAddress $param17, - _i1.EthereumAddress $param18, - BigInt $param19, - _i2.Uint8List $param20, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[11]; - assert(checkSignature(function, '150b7a02')); - final params = [ - $param17, - $param18, - $param19, - $param20, - ]; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as _i2.Uint8List); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future<_i1.EthereumAddress> owner({_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[12]; - assert(checkSignature(function, '8da5cb5b')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as _i1.EthereumAddress); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future<_i2.Uint8List> proxiableUUID({_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[13]; - assert(checkSignature(function, '52d1902d')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as _i2.Uint8List); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future supportsInterface( - _i2.Uint8List interfaceId, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[14]; - assert(checkSignature(function, '01ffc9a7')); - final params = [interfaceId]; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as bool); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future tokensReceived( - _i1.EthereumAddress $param22, - _i1.EthereumAddress $param23, - _i1.EthereumAddress $param24, - BigInt $param25, - _i2.Uint8List $param26, - _i2.Uint8List $param27, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[15]; - assert(checkSignature(function, '0023de29')); - final params = [ - $param22, - $param23, - $param24, - $param25, - $param26, - $param27, - ]; - final response = await read( - function, - params, - atBlock, - ); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future upgradeToAndCall( - _i1.EthereumAddress newImplementation, - _i2.Uint8List data, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[16]; - assert(checkSignature(function, '4f1ef286')); - final params = [ - newImplementation, - data, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future validateUserOp( - dynamic userOp, - _i2.Uint8List userOpHash, - BigInt missingAccountFunds, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[17]; - assert(checkSignature(function, '3a871cdd')); - final params = [ - userOp, - userOpHash, - missingAccountFunds, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future withdrawDepositTo( - _i1.EthereumAddress withdrawAddress, - BigInt amount, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[18]; - assert(checkSignature(function, '4d44560d')); - final params = [ - withdrawAddress, - amount, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// Returns a live stream of all Initialized events emitted by this contract. - Stream initializedEvents({ - _i1.BlockNum? fromBlock, - _i1.BlockNum? toBlock, - }) { - final event = self.event('Initialized'); - final filter = _i1.FilterOptions.events( - contract: self, - event: event, - fromBlock: fromBlock, - toBlock: toBlock, - ); - return client.events(filter).map((_i1.FilterEvent result) { - final decoded = event.decodeResults( - result.topics!, - result.data!, - ); - return Initialized( - decoded, - result, - ); - }); - } - - /// Returns a live stream of all SimpleAccountInitialized events emitted by this contract. - Stream simpleAccountInitializedEvents({ - _i1.BlockNum? fromBlock, - _i1.BlockNum? toBlock, - }) { - final event = self.event('SimpleAccountInitialized'); - final filter = _i1.FilterOptions.events( - contract: self, - event: event, - fromBlock: fromBlock, - toBlock: toBlock, - ); - return client.events(filter).map((_i1.FilterEvent result) { - final decoded = event.decodeResults( - result.topics!, - result.data!, - ); - return SimpleAccountInitialized( - decoded, - result, - ); - }); - } - - /// Returns a live stream of all Upgraded events emitted by this contract. - Stream upgradedEvents({ - _i1.BlockNum? fromBlock, - _i1.BlockNum? toBlock, - }) { - final event = self.event('Upgraded'); - final filter = _i1.FilterOptions.events( - contract: self, - event: event, - fromBlock: fromBlock, - toBlock: toBlock, - ); - return client.events(filter).map((_i1.FilterEvent result) { - final decoded = event.decodeResults( - result.topics!, - result.data!, - ); - return Upgraded( - decoded, - result, - ); - }); - } -} - -class Initialized { - Initialized( - List response, - this.event, - ) : version = (response[0] as BigInt); - - final BigInt version; - - final _i1.FilterEvent event; -} - -class SimpleAccountInitialized { - SimpleAccountInitialized( - List response, - this.event, - ) : entryPoint = (response[0] as _i1.EthereumAddress), - owner = (response[1] as _i1.EthereumAddress); - - final _i1.EthereumAddress entryPoint; - - final _i1.EthereumAddress owner; - - final _i1.FilterEvent event; -} - -class Upgraded { - Upgraded( - List response, - this.event, - ) : implementation = (response[0] as _i1.EthereumAddress); - - final _i1.EthereumAddress implementation; - - final _i1.FilterEvent event; -} diff --git a/lib/src/abis/simplePasskeyAccount.abi.json b/lib/src/abis/simplePasskeyAccount.abi.json deleted file mode 100644 index 15ffa4e..0000000 --- a/lib/src/abis/simplePasskeyAccount.abi.json +++ /dev/null @@ -1,334 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "contract IEntryPoint", - "name": "anEntryPoint", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [ - { "internalType": "address", "name": "target", "type": "address" } - ], - "name": "AddressEmptyCode", - "type": "error" - }, - { - "inputs": [ - { "internalType": "address", "name": "implementation", "type": "address" } - ], - "name": "ERC1967InvalidImplementation", - "type": "error" - }, - { "inputs": [], "name": "ERC1967NonPayable", "type": "error" }, - { "inputs": [], "name": "FailedInnerCall", "type": "error" }, - { "inputs": [], "name": "InvalidInitialization", "type": "error" }, - { "inputs": [], "name": "NotInitializing", "type": "error" }, - { "inputs": [], "name": "UUPSUnauthorizedCallContext", "type": "error" }, - { - "inputs": [ - { "internalType": "bytes32", "name": "slot", "type": "bytes32" } - ], - "name": "UUPSUnsupportedProxiableUUID", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint64", - "name": "version", - "type": "uint64" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "contract IEntryPoint", - "name": "entryPoint", - "type": "address" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "credentialHex", - "type": "bytes32" - } - ], - "name": "SimpleAccountInitialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "implementation", - "type": "address" - } - ], - "name": "Upgraded", - "type": "event" - }, - { - "inputs": [], - "name": "UPGRADE_INTERFACE_VERSION", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "addDeposit", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "credentialHex", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "entryPoint", - "outputs": [ - { "internalType": "contract IEntryPoint", "name": "", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "dest", "type": "address" }, - { "internalType": "uint256", "name": "value", "type": "uint256" }, - { "internalType": "bytes", "name": "func", "type": "bytes" } - ], - "name": "execute", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address[]", "name": "dest", "type": "address[]" }, - { "internalType": "uint256[]", "name": "value", "type": "uint256[]" }, - { "internalType": "bytes[]", "name": "func", "type": "bytes[]" } - ], - "name": "executeBatch", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "getCredentialIdBase64", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getDeposit", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getNonce", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "aCredentialHex", - "type": "bytes32" - }, - { "internalType": "uint256", "name": "x", "type": "uint256" }, - { "internalType": "uint256", "name": "y", "type": "uint256" } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "uint256[]", "name": "", "type": "uint256[]" }, - { "internalType": "uint256[]", "name": "", "type": "uint256[]" }, - { "internalType": "bytes", "name": "", "type": "bytes" } - ], - "name": "onERC1155BatchReceived", - "outputs": [{ "internalType": "bytes4", "name": "", "type": "bytes4" }], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "uint256", "name": "", "type": "uint256" }, - { "internalType": "uint256", "name": "", "type": "uint256" }, - { "internalType": "bytes", "name": "", "type": "bytes" } - ], - "name": "onERC1155Received", - "outputs": [{ "internalType": "bytes4", "name": "", "type": "bytes4" }], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "uint256", "name": "", "type": "uint256" }, - { "internalType": "bytes", "name": "", "type": "bytes" } - ], - "name": "onERC721Received", - "outputs": [{ "internalType": "bytes4", "name": "", "type": "bytes4" }], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "proxiableUUID", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "name": "publicKey", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" } - ], - "name": "supportsInterface", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "uint256", "name": "", "type": "uint256" }, - { "internalType": "bytes", "name": "", "type": "bytes" }, - { "internalType": "bytes", "name": "", "type": "bytes" } - ], - "name": "tokensReceived", - "outputs": [], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newImplementation", - "type": "address" - }, - { "internalType": "bytes", "name": "data", "type": "bytes" } - ], - "name": "upgradeToAndCall", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { "internalType": "address", "name": "sender", "type": "address" }, - { "internalType": "uint256", "name": "nonce", "type": "uint256" }, - { "internalType": "bytes", "name": "initCode", "type": "bytes" }, - { "internalType": "bytes", "name": "callData", "type": "bytes" }, - { - "internalType": "uint256", - "name": "callGasLimit", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "verificationGasLimit", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "preVerificationGas", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "maxFeePerGas", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "maxPriorityFeePerGas", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "paymasterAndData", - "type": "bytes" - }, - { "internalType": "bytes", "name": "signature", "type": "bytes" } - ], - "internalType": "struct UserOperation", - "name": "userOp", - "type": "tuple" - }, - { "internalType": "bytes32", "name": "userOpHash", "type": "bytes32" }, - { - "internalType": "uint256", - "name": "missingAccountFunds", - "type": "uint256" - } - ], - "name": "validateUserOp", - "outputs": [ - { "internalType": "uint256", "name": "validationData", "type": "uint256" } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address payable", - "name": "withdrawAddress", - "type": "address" - }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "withdrawDepositTo", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { "stateMutability": "payable", "type": "receive" } -] diff --git a/lib/src/abis/simplePasskeyAccount.g.dart b/lib/src/abis/simplePasskeyAccount.g.dart deleted file mode 100644 index dc42741..0000000 --- a/lib/src/abis/simplePasskeyAccount.g.dart +++ /dev/null @@ -1,549 +0,0 @@ -// Generated code, do not modify. Run `build_runner build` to re-generate! -// @dart=2.12 -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:web3dart/web3dart.dart' as _i1; -import 'dart:typed_data' as _i2; - -final _contractAbi = _i1.ContractAbi.fromJson( - '[{"inputs":[{"internalType":"contract IEntryPoint","name":"anEntryPoint","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"implementation","type":"address"}],"name":"ERC1967InvalidImplementation","type":"error"},{"inputs":[],"name":"ERC1967NonPayable","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[],"name":"UUPSUnauthorizedCallContext","type":"error"},{"inputs":[{"internalType":"bytes32","name":"slot","type":"bytes32"}],"name":"UUPSUnsupportedProxiableUUID","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IEntryPoint","name":"entryPoint","type":"address"},{"indexed":true,"internalType":"bytes32","name":"credentialHex","type":"bytes32"}],"name":"SimpleAccountInitialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"inputs":[],"name":"UPGRADE_INTERFACE_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"addDeposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"credentialHex","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"entryPoint","outputs":[{"internalType":"contract IEntryPoint","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"dest","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"func","type":"bytes"}],"name":"execute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"dest","type":"address[]"},{"internalType":"uint256[]","name":"value","type":"uint256[]"},{"internalType":"bytes[]","name":"func","type":"bytes[]"}],"name":"executeBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getCredentialIdBase64","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"aCredentialHex","type":"bytes32"},{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155BatchReceived","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"proxiableUUID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"publicKey","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"tokensReceived","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"upgradeToAndCall","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"uint256","name":"callGasLimit","type":"uint256"},{"internalType":"uint256","name":"verificationGasLimit","type":"uint256"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"uint256","name":"maxFeePerGas","type":"uint256"},{"internalType":"uint256","name":"maxPriorityFeePerGas","type":"uint256"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct UserOperation","name":"userOp","type":"tuple"},{"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"internalType":"uint256","name":"missingAccountFunds","type":"uint256"}],"name":"validateUserOp","outputs":[{"internalType":"uint256","name":"validationData","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"withdrawAddress","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawDepositTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]', - 'SimplePasskeyAccount', -); - -class SimplePasskeyAccount extends _i1.GeneratedContract { - SimplePasskeyAccount({ - required _i1.EthereumAddress address, - required _i1.Web3Client client, - int? chainId, - }) : super( - _i1.DeployedContract( - _contractAbi, - address, - ), - client, - chainId, - ); - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future UPGRADE_INTERFACE_VERSION({_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[1]; - assert(checkSignature(function, 'ad3cb1cc')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as String); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future addDeposit({ - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[2]; - assert(checkSignature(function, '4a58db19')); - final params = []; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future<_i2.Uint8List> credentialHex({_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[3]; - assert(checkSignature(function, 'f2de8bc6')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as _i2.Uint8List); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future<_i1.EthereumAddress> entryPoint({_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[4]; - assert(checkSignature(function, 'b0d691fe')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as _i1.EthereumAddress); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future execute( - _i1.EthereumAddress dest, - BigInt value, - _i2.Uint8List func, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[5]; - assert(checkSignature(function, 'b61d27f6')); - final params = [ - dest, - value, - func, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future executeBatch( - List<_i1.EthereumAddress> dest, - List value, - List<_i2.Uint8List> func, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[6]; - assert(checkSignature(function, '47e1da2a')); - final params = [ - dest, - value, - func, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future getCredentialIdBase64({_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[7]; - assert(checkSignature(function, '3a941022')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as String); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future getDeposit({_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[8]; - assert(checkSignature(function, 'c399ec88')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as BigInt); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future getNonce({_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[9]; - assert(checkSignature(function, 'd087d288')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as BigInt); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future initialize( - _i2.Uint8List aCredentialHex, - BigInt x, - BigInt y, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[10]; - assert(checkSignature(function, 'f0fd7f64')); - final params = [ - aCredentialHex, - x, - y, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future<_i2.Uint8List> onERC1155BatchReceived( - _i1.EthereumAddress $param9, - _i1.EthereumAddress $param10, - List $param11, - List $param12, - _i2.Uint8List $param13, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[11]; - assert(checkSignature(function, 'bc197c81')); - final params = [ - $param9, - $param10, - $param11, - $param12, - $param13, - ]; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as _i2.Uint8List); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future<_i2.Uint8List> onERC1155Received( - _i1.EthereumAddress $param14, - _i1.EthereumAddress $param15, - BigInt $param16, - BigInt $param17, - _i2.Uint8List $param18, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[12]; - assert(checkSignature(function, 'f23a6e61')); - final params = [ - $param14, - $param15, - $param16, - $param17, - $param18, - ]; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as _i2.Uint8List); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future<_i2.Uint8List> onERC721Received( - _i1.EthereumAddress $param19, - _i1.EthereumAddress $param20, - BigInt $param21, - _i2.Uint8List $param22, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[13]; - assert(checkSignature(function, '150b7a02')); - final params = [ - $param19, - $param20, - $param21, - $param22, - ]; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as _i2.Uint8List); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future<_i2.Uint8List> proxiableUUID({_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[14]; - assert(checkSignature(function, '52d1902d')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as _i2.Uint8List); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future publicKey( - BigInt $param23, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[15]; - assert(checkSignature(function, '8940aebe')); - final params = [$param23]; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as BigInt); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future supportsInterface( - _i2.Uint8List interfaceId, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[16]; - assert(checkSignature(function, '01ffc9a7')); - final params = [interfaceId]; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as bool); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future tokensReceived( - _i1.EthereumAddress $param25, - _i1.EthereumAddress $param26, - _i1.EthereumAddress $param27, - BigInt $param28, - _i2.Uint8List $param29, - _i2.Uint8List $param30, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[17]; - assert(checkSignature(function, '0023de29')); - final params = [ - $param25, - $param26, - $param27, - $param28, - $param29, - $param30, - ]; - final response = await read( - function, - params, - atBlock, - ); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future upgradeToAndCall( - _i1.EthereumAddress newImplementation, - _i2.Uint8List data, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[18]; - assert(checkSignature(function, '4f1ef286')); - final params = [ - newImplementation, - data, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future validateUserOp( - dynamic userOp, - _i2.Uint8List userOpHash, - BigInt missingAccountFunds, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[19]; - assert(checkSignature(function, '3a871cdd')); - final params = [ - userOp, - userOpHash, - missingAccountFunds, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future withdrawDepositTo( - _i1.EthereumAddress withdrawAddress, - BigInt amount, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[20]; - assert(checkSignature(function, '4d44560d')); - final params = [ - withdrawAddress, - amount, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// Returns a live stream of all Initialized events emitted by this contract. - Stream initializedEvents({ - _i1.BlockNum? fromBlock, - _i1.BlockNum? toBlock, - }) { - final event = self.event('Initialized'); - final filter = _i1.FilterOptions.events( - contract: self, - event: event, - fromBlock: fromBlock, - toBlock: toBlock, - ); - return client.events(filter).map((_i1.FilterEvent result) { - final decoded = event.decodeResults( - result.topics!, - result.data!, - ); - return Initialized( - decoded, - result, - ); - }); - } - - /// Returns a live stream of all SimpleAccountInitialized events emitted by this contract. - Stream simpleAccountInitializedEvents({ - _i1.BlockNum? fromBlock, - _i1.BlockNum? toBlock, - }) { - final event = self.event('SimpleAccountInitialized'); - final filter = _i1.FilterOptions.events( - contract: self, - event: event, - fromBlock: fromBlock, - toBlock: toBlock, - ); - return client.events(filter).map((_i1.FilterEvent result) { - final decoded = event.decodeResults( - result.topics!, - result.data!, - ); - return SimpleAccountInitialized( - decoded, - result, - ); - }); - } - - /// Returns a live stream of all Upgraded events emitted by this contract. - Stream upgradedEvents({ - _i1.BlockNum? fromBlock, - _i1.BlockNum? toBlock, - }) { - final event = self.event('Upgraded'); - final filter = _i1.FilterOptions.events( - contract: self, - event: event, - fromBlock: fromBlock, - toBlock: toBlock, - ); - return client.events(filter).map((_i1.FilterEvent result) { - final decoded = event.decodeResults( - result.topics!, - result.data!, - ); - return Upgraded( - decoded, - result, - ); - }); - } -} - -class Initialized { - Initialized( - List response, - this.event, - ) : version = (response[0] as BigInt); - - final BigInt version; - - final _i1.FilterEvent event; -} - -class SimpleAccountInitialized { - SimpleAccountInitialized( - List response, - this.event, - ) : entryPoint = (response[0] as _i1.EthereumAddress), - credentialHex = (response[1] as _i2.Uint8List); - - final _i1.EthereumAddress entryPoint; - - final _i2.Uint8List credentialHex; - - final _i1.FilterEvent event; -} - -class Upgraded { - Upgraded( - List response, - this.event, - ) : implementation = (response[0] as _i1.EthereumAddress); - - final _i1.EthereumAddress implementation; - - final _i1.FilterEvent event; -} diff --git a/lib/src/abis/token.abi.json b/lib/src/abis/token.abi.json deleted file mode 100644 index 7f9f9a7..0000000 --- a/lib/src/abis/token.abi.json +++ /dev/null @@ -1,201 +0,0 @@ -[ - { - "constant": true, - "inputs": [], - "name": "name", - "outputs": [{ "name": "", "type": "string" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "name": "spender", "type": "address" }, - { "name": "value", "type": "uint256" } - ], - "name": "approve", - "outputs": [{ "name": "", "type": "bool" }], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalSupply", - "outputs": [{ "name": "", "type": "uint256" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "name": "from", "type": "address" }, - { "name": "to", "type": "address" }, - { "name": "value", "type": "uint256" } - ], - "name": "transferFrom", - "outputs": [{ "name": "", "type": "bool" }], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "decimals", - "outputs": [{ "name": "", "type": "uint8" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "name": "spender", "type": "address" }, - { "name": "addedValue", "type": "uint256" } - ], - "name": "increaseAllowance", - "outputs": [{ "name": "", "type": "bool" }], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "name": "to", "type": "address" }, - { "name": "value", "type": "uint256" } - ], - "name": "mint", - "outputs": [{ "name": "", "type": "bool" }], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [{ "name": "owner", "type": "address" }], - "name": "balanceOf", - "outputs": [{ "name": "", "type": "uint256" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "symbol", - "outputs": [{ "name": "", "type": "string" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [{ "name": "account", "type": "address" }], - "name": "addMinter", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "renounceMinter", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "name": "spender", "type": "address" }, - { "name": "subtractedValue", "type": "uint256" } - ], - "name": "decreaseAllowance", - "outputs": [{ "name": "", "type": "bool" }], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "name": "to", "type": "address" }, - { "name": "value", "type": "uint256" } - ], - "name": "transfer", - "outputs": [{ "name": "", "type": "bool" }], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [{ "name": "account", "type": "address" }], - "name": "isMinter", - "outputs": [{ "name": "", "type": "bool" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { "name": "owner", "type": "address" }, - { "name": "spender", "type": "address" } - ], - "name": "allowance", - "outputs": [{ "name": "", "type": "uint256" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "name": "name", "type": "string" }, - { "name": "symbol", "type": "string" }, - { "name": "decimals", "type": "uint8" } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [{ "indexed": true, "name": "account", "type": "address" }], - "name": "MinterAdded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [{ "indexed": true, "name": "account", "type": "address" }], - "name": "MinterRemoved", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "name": "from", "type": "address" }, - { "indexed": true, "name": "to", "type": "address" }, - { "indexed": false, "name": "value", "type": "uint256" } - ], - "name": "Transfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "name": "owner", "type": "address" }, - { "indexed": true, "name": "spender", "type": "address" }, - { "indexed": false, "name": "value", "type": "uint256" } - ], - "name": "Approval", - "type": "event" - } -] diff --git a/lib/src/abis/token.g.dart b/lib/src/abis/token.g.dart deleted file mode 100644 index ae78e35..0000000 --- a/lib/src/abis/token.g.dart +++ /dev/null @@ -1,471 +0,0 @@ -// Generated code, do not modify. Run `build_runner build` to re-generate! -// @dart=2.12 -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:web3dart/web3dart.dart' as _i1; - -final _contractAbi = _i1.ContractAbi.fromJson( - '[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"mint","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"account","type":"address"}],"name":"addMinter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"renounceMinter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"isMinter","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"name","type":"string"},{"name":"symbol","type":"string"},{"name":"decimals","type":"uint8"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"}],"name":"MinterAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"}],"name":"MinterRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"}]', - 'Token', -); - -class Token extends _i1.GeneratedContract { - Token({ - required _i1.EthereumAddress address, - required _i1.Web3Client client, - int? chainId, - }) : super( - _i1.DeployedContract( - _contractAbi, - address, - ), - client, - chainId, - ); - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future name({_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[0]; - assert(checkSignature(function, '06fdde03')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as String); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future approve( - _i1.EthereumAddress spender, - BigInt value, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[1]; - assert(checkSignature(function, '095ea7b3')); - final params = [ - spender, - value, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future totalSupply({_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[2]; - assert(checkSignature(function, '18160ddd')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as BigInt); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future transferFrom( - _i1.EthereumAddress from, - _i1.EthereumAddress to, - BigInt value, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[3]; - assert(checkSignature(function, '23b872dd')); - final params = [ - from, - to, - value, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future decimals({_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[4]; - assert(checkSignature(function, '313ce567')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as BigInt); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future increaseAllowance( - _i1.EthereumAddress spender, - BigInt addedValue, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[5]; - assert(checkSignature(function, '39509351')); - final params = [ - spender, - addedValue, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future mint( - _i1.EthereumAddress to, - BigInt value, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[6]; - assert(checkSignature(function, '40c10f19')); - final params = [ - to, - value, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future balanceOf( - _i1.EthereumAddress owner, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[7]; - assert(checkSignature(function, '70a08231')); - final params = [owner]; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as BigInt); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future symbol({_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[8]; - assert(checkSignature(function, '95d89b41')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as String); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future addMinter( - _i1.EthereumAddress account, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[9]; - assert(checkSignature(function, '983b2d56')); - final params = [account]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future renounceMinter({ - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[10]; - assert(checkSignature(function, '98650275')); - final params = []; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future decreaseAllowance( - _i1.EthereumAddress spender, - BigInt subtractedValue, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[11]; - assert(checkSignature(function, 'a457c2d7')); - final params = [ - spender, - subtractedValue, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future transfer( - _i1.EthereumAddress to, - BigInt value, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[12]; - assert(checkSignature(function, 'a9059cbb')); - final params = [ - to, - value, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future isMinter( - _i1.EthereumAddress account, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[13]; - assert(checkSignature(function, 'aa271e1a')); - final params = [account]; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as bool); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future allowance( - _i1.EthereumAddress owner, - _i1.EthereumAddress spender, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[14]; - assert(checkSignature(function, 'dd62ed3e')); - final params = [ - owner, - spender, - ]; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as BigInt); - } - - /// Returns a live stream of all MinterAdded events emitted by this contract. - Stream minterAddedEvents({ - _i1.BlockNum? fromBlock, - _i1.BlockNum? toBlock, - }) { - final event = self.event('MinterAdded'); - final filter = _i1.FilterOptions.events( - contract: self, - event: event, - fromBlock: fromBlock, - toBlock: toBlock, - ); - return client.events(filter).map((_i1.FilterEvent result) { - final decoded = event.decodeResults( - result.topics!, - result.data!, - ); - return MinterAdded( - decoded, - result, - ); - }); - } - - /// Returns a live stream of all MinterRemoved events emitted by this contract. - Stream minterRemovedEvents({ - _i1.BlockNum? fromBlock, - _i1.BlockNum? toBlock, - }) { - final event = self.event('MinterRemoved'); - final filter = _i1.FilterOptions.events( - contract: self, - event: event, - fromBlock: fromBlock, - toBlock: toBlock, - ); - return client.events(filter).map((_i1.FilterEvent result) { - final decoded = event.decodeResults( - result.topics!, - result.data!, - ); - return MinterRemoved( - decoded, - result, - ); - }); - } - - /// Returns a live stream of all Transfer events emitted by this contract. - Stream transferEvents({ - _i1.BlockNum? fromBlock, - _i1.BlockNum? toBlock, - }) { - final event = self.event('Transfer'); - final filter = _i1.FilterOptions.events( - contract: self, - event: event, - fromBlock: fromBlock, - toBlock: toBlock, - ); - return client.events(filter).map((_i1.FilterEvent result) { - final decoded = event.decodeResults( - result.topics!, - result.data!, - ); - return Transfer( - decoded, - result, - ); - }); - } - - /// Returns a live stream of all Approval events emitted by this contract. - Stream approvalEvents({ - _i1.BlockNum? fromBlock, - _i1.BlockNum? toBlock, - }) { - final event = self.event('Approval'); - final filter = _i1.FilterOptions.events( - contract: self, - event: event, - fromBlock: fromBlock, - toBlock: toBlock, - ); - return client.events(filter).map((_i1.FilterEvent result) { - final decoded = event.decodeResults( - result.topics!, - result.data!, - ); - return Approval( - decoded, - result, - ); - }); - } -} - -class MinterAdded { - MinterAdded( - List response, - this.event, - ) : account = (response[0] as _i1.EthereumAddress); - - final _i1.EthereumAddress account; - - final _i1.FilterEvent event; -} - -class MinterRemoved { - MinterRemoved( - List response, - this.event, - ) : account = (response[0] as _i1.EthereumAddress); - - final _i1.EthereumAddress account; - - final _i1.FilterEvent event; -} - -class Transfer { - Transfer( - List response, - this.event, - ) : from = (response[0] as _i1.EthereumAddress), - to = (response[1] as _i1.EthereumAddress), - value = (response[2] as BigInt); - - final _i1.EthereumAddress from; - - final _i1.EthereumAddress to; - - final BigInt value; - - final _i1.FilterEvent event; -} - -class Approval { - Approval( - List response, - this.event, - ) : owner = (response[0] as _i1.EthereumAddress), - spender = (response[1] as _i1.EthereumAddress), - value = (response[2] as BigInt); - - final _i1.EthereumAddress owner; - - final _i1.EthereumAddress spender; - - final BigInt value; - - final _i1.FilterEvent event; -} diff --git a/lib/src/common/abi_coder.dart b/lib/src/common/abi_coder.dart deleted file mode 100644 index 408da54..0000000 --- a/lib/src/common/abi_coder.dart +++ /dev/null @@ -1,59 +0,0 @@ -part of 'common.dart'; - -/// Abstract base class for handling Ethereum's Application Binary Interface (ABI). -/// -/// The ABI is a data encoding scheme used in Ethereum for ABI encoding -/// and interaction with contracts within Ethereum. -// ignore: camel_case_types -class abi { - abi._(); - - /// Decodes a list of ABI-encoded types and values. - /// - /// Parameters: - /// - `types`: A list of string types describing the ABI types to decode. - /// - `value`: A [Uint8List] containing the ABI-encoded data to be decoded. - /// - /// Returns: - /// A list of decoded values with the specified type. - /// - /// Example: - /// ```dart - /// var decodedValues = abi.decode(['uint256', 'string'], encodedData); - /// ``` - static List decode(List types, Uint8List value) { - List abiTypes = []; - for (String type in types) { - var abiType = parseAbiType(type); - abiTypes.add(abiType); - } - final parsedData = TupleType(abiTypes).decode(value.buffer, 0); - return parsedData.data; - } - - /// Encodes a list of types and values into ABI-encoded data. - /// - /// Parameters: - /// - `types`: A list of string types describing the ABI types. - /// - `values`: A list of dynamic values to be ABI-encoded. - /// - /// Returns: - /// A [Uint8List] containing the ABI-encoded types and values. - /// - /// Example: - /// ```dart - /// var encodedData = abi.encode(['uint256', 'string'], [BigInt.from(123), 'Hello']); - /// ``` - static Uint8List encode(List types, List values) { - List abiTypes = []; - LengthTrackingByteSink result = LengthTrackingByteSink(); - for (String type in types) { - var abiType = parseAbiType(type); - abiTypes.add(abiType); - } - TupleType(abiTypes).encode(values, result); - var resultBytes = result.asBytes(); - result.close(); - return resultBytes; - } -} diff --git a/lib/src/common/address.dart b/lib/src/common/address.dart deleted file mode 100644 index a6cb4fe..0000000 --- a/lib/src/common/address.dart +++ /dev/null @@ -1,71 +0,0 @@ -part of 'common.dart'; - -class Address extends EthereumAddress implements ENSResolverBase { - String? _ens; - - ChainBaseApiBase? client; - - Address(super.addressBytes, {bool ens = false, this.client}) { - _setEnsName(); - } - - factory Address.fromEthAddress(EthereumAddress ethAddress) { - return Address(ethAddress.addressBytes); - } - - @override - String? get ens => _ens; - - @override - Future? getEnsName() async { - return _ens ?? await _getEnsName(this); - } - - @override - Future? getEnsNameForAddress(EthereumAddress address) { - return _getEnsName(address); - } - - @override - Address withClient(ChainBaseApiBase client) { - return Address(addressBytes, client: client); - } - - Future? _getEnsName(EthereumAddress address) { - return client?.reverseENSAddress(address).then((value) => value.data?.name); - } - - Future _setEnsName() async { - _getEnsName(this)?.then((name) { - _ens = name; - }).catchError((err) { - _ens = null; - }); - } - - /// Creates an instance of Address from an ENS name. - /// - /// - [name]: The ENS name. - /// - /// Returns a [Future] that completes with an instance of Address. - static Future? fromEns(String name, {ChainBaseApiBase? client}) { - return client - ?.resolveENSName(name) - .then((value) => value.data?.address) - .then((address) => address == null - ? null - : Address(EthereumAddress.fromHex(address).addressBytes)); - } -} - -extension AddressExtension on EthereumAddress { - String avatarUrl() { - return 'https://effigy.im/a/$hex.svg'; - } - - String formattedAddress({int length = 6}) { - final prefix = hex.substring(0, 2 + length); - final suffix = hex.substring(hex.length - length); - return '$prefix...$suffix'; - } -} diff --git a/lib/src/common/common.dart b/lib/src/common/common.dart deleted file mode 100644 index 041793f..0000000 --- a/lib/src/common/common.dart +++ /dev/null @@ -1,17 +0,0 @@ -library common; - -import 'dart:math'; -import 'dart:typed_data'; - -import 'package:web3dart/crypto.dart'; -import 'package:web3dart/web3dart.dart'; - -import '../../utils.dart'; -import '../../variance.dart'; -import '../abis/abis.dart' show ContractAbis; -import '../interfaces/interfaces.dart'; - -part 'abi_coder.dart'; -part 'address.dart'; -part 'contract.dart'; -part 'uint256.dart'; diff --git a/lib/src/common/contract.dart b/lib/src/common/contract.dart index 31f3798..b04e2b7 100644 --- a/lib/src/common/contract.dart +++ b/lib/src/common/contract.dart @@ -1,4 +1,4 @@ -part of 'common.dart'; +part of '../../variance.dart'; /// A wrapper for interacting with deployed Ethereum contracts through [RPCProvider]. class Contract { @@ -270,7 +270,7 @@ class Contract { /// ); // transfer to 0x1234567890abcdef1234567890abcdef12345678 with 1000000000000000000 wei /// ``` /// This method uses the 'execute' function ABI to encode the smart wallet operation. - static Uint8List execute(EthereumAddress? walletAddress, + static Uint8List execute(EthereumAddress walletAddress, {required EthereumAddress to, EtherAmount? amount, Uint8List? innerCallData}) { @@ -280,11 +280,6 @@ class Contract { innerCallData ?? Uint8List.fromList([]) ]; - if (walletAddress == null) { - throw SmartWalletError( - "Invlaid Operation, SmartWallet Address is undefined! (contract.execute)"); - } - return encodeFunctionCall( 'execute', walletAddress, @@ -321,7 +316,7 @@ class Contract { /// ``` /// This method uses the 'executeBatch' function ABI to encode the smart wallet batch operation. static Uint8List executeBatch( - {required EthereumAddress? walletAddress, + {required EthereumAddress walletAddress, required List recipients, List? amounts, List? innerCalls}) { @@ -334,11 +329,6 @@ class Contract { require(amounts != null && amounts.isNotEmpty, "malformed batch request"); } - if (walletAddress == null) { - throw SmartWalletError( - "Invlaid Operation, SmartWallet Address is undefined! (contract.executeBatch)"); - } - return encodeFunctionCall( 'executeBatch', walletAddress, diff --git a/lib/src/common/factory.dart b/lib/src/common/factory.dart index bef8b9e..bde8891 100644 --- a/lib/src/common/factory.dart +++ b/lib/src/common/factory.dart @@ -1,4 +1,4 @@ -part of 'package:variance_dart/variance.dart'; +part of '../../variance.dart'; class _AccountFactory extends AccountFactory implements AccountFactoryBase { _AccountFactory( diff --git a/lib/src/common/mixins.dart b/lib/src/common/mixins.dart index e3ac920..b2222bb 100644 --- a/lib/src/common/mixins.dart +++ b/lib/src/common/mixins.dart @@ -1,4 +1,4 @@ -part of 'package:variance_dart/variance.dart'; +part of '../../variance.dart'; typedef Percent = double; diff --git a/lib/src/common/pack.dart b/lib/src/common/pack.dart new file mode 100644 index 0000000..5a3ea86 --- /dev/null +++ b/lib/src/common/pack.dart @@ -0,0 +1,27 @@ +part of '../../variance.dart'; + +/// 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; +} diff --git a/lib/src/common/uint256.dart b/lib/src/common/uint256.dart deleted file mode 100644 index 521582e..0000000 --- a/lib/src/common/uint256.dart +++ /dev/null @@ -1,114 +0,0 @@ -part of 'common.dart'; - -class Uint256 implements Uint256Base { - static Uint256 get zero => Uint256(BigInt.zero); - - final BigInt _value; - - const Uint256(this._value); - - /// Creates a [Uint256] instance from a hexadecimal string [hex]. - /// - /// Example: - /// ```dart - /// final value = Uint256.fromHex('0x1a'); // Creates Uint256 with value 26 - /// ``` - factory Uint256.fromHex(String hex) { - return Uint256(hexToInt(hex)); - } - - /// Creates a [Uint256] instance from an [EtherAmount] value [inWei]. - /// - /// Example: - /// ```dart - /// final amount = EtherAmount.inWei(BigInt.from(5)).getInWei; - /// final value = Uint256.fromWei(amount); // Creates Uint256 with value 5 - /// ``` - factory Uint256.fromWei(EtherAmount inWei) { - return Uint256(inWei.getInWei); - } - - /// Creates a [Uint256] instance from an integer value [value]. - /// - /// Example: - /// ```dart - /// final value = Uint256.fromInt(42); // Creates Uint256 with value 42 - /// ``` - factory Uint256.fromInt(int value) { - return Uint256(BigInt.from(value)); - } - - /// Creates a [Uint256] instance from a string representation of a number [value]. - /// - /// Example: - /// ```dart - /// final value = Uint256.fromString('123'); // Creates Uint256 with value 123 - /// ``` - factory Uint256.fromString(String value) { - return Uint256(BigInt.parse(value)); - } - - @override - BigInt get value => _value; - - @override - Uint256 operator *(Uint256 other) { - return Uint256(_value * other._value); - } - - @override - Uint256 operator +(Uint256 other) { - return Uint256(_value + other._value); - } - - @override - Uint256 operator -(Uint256 other) { - return Uint256(_value - other._value); - } - - @override - Uint256 operator /(Uint256 other) { - return Uint256(BigInt.from(_value / other._value)); - } - - @override - BigInt toEther() { - return toEtherAmount().getInEther; - } - - @override - EtherAmount toEtherAmount() { - return EtherAmount.fromBigInt(EtherUnit.wei, _value); - } - - @override - String toHex() { - final hexString = _value.toRadixString(16); - return '0x${hexString.padLeft(64, '0')}'; - } - - @override - int toInt() { - return _value.toInt(); - } - - @override - String toString() { - return toHex(); - } - - @override - BigInt toUnit(int decimals) { - return _value * BigInt.from(pow(10, decimals)); - } - - @override - double fromUnit(int decimals) { - return _value / BigInt.from(pow(10, decimals)); - } - - @override - BigInt toWei() { - return toEtherAmount().getInWei; - } -} diff --git a/lib/src/errors/wallet_errors.dart b/lib/src/errors/wallet_errors.dart new file mode 100644 index 0000000..6b3204f --- /dev/null +++ b/lib/src/errors/wallet_errors.dart @@ -0,0 +1,50 @@ +part of '../../variance.dart'; + +class EstimateError extends Error { + final String message; + + final UserOperation operation; + + EstimateError(this.message, this.operation); + + @override + String toString() { + return ''' + Error estimating user operation gas! Failed with error: $message + -------------------------------------------------- + User operation: ${operation.toJson()}. + '''; + } +} + +class NonceError extends Error { + final String message; + final EthereumAddress? address; + + NonceError(this.message, this.address); + + @override + String toString() { + return ''' + Error fetching user account nonce for address ${address?.hex}! + -------------------------------------------------- + Failed with error: $message + '''; + } +} + +class SendError extends Error { + final String message; + final UserOperation operation; + + SendError(this.message, this.operation); + + @override + String toString() { + return ''' + Error sending user operation! Failed with error: $message + -------------------------------------------------- + User operation: ${operation.toJson()}. + '''; + } +} diff --git a/lib/src/interfaces/ens_resolver.dart b/lib/src/interfaces/ens_resolver.dart deleted file mode 100644 index ac757e6..0000000 --- a/lib/src/interfaces/ens_resolver.dart +++ /dev/null @@ -1,24 +0,0 @@ -part of 'interfaces.dart'; - -/// Abstract base class for handling Ethereum Name Service (ENS) resolution. -/// -/// This class provides methods to interact with ENS, including resolving -/// ENS names to addresses and formatting addresses. -abstract class ENSResolverBase { - String? get ens; - - /// Gets the ENS name associated with the current address. - /// - /// Returns a [Future] that completes with the ENS name. - Future? getEnsName(); - - /// Converts an Ethereum address to its corresponding ENS name. - /// - /// - [address]: The Ethereum address to convert to an ENS name. - /// - /// Returns a [Future] that completes with the ENS name. - Future? getEnsNameForAddress(EthereumAddress address); - - /// Returns a new [ENSResolver] instance with the specified [client]. - ENSResolverBase withClient(ChainBaseApiBase client); -} diff --git a/lib/src/interfaces/hd_interface.dart b/lib/src/interfaces/hd_interface.dart deleted file mode 100644 index d8dedba..0000000 --- a/lib/src/interfaces/hd_interface.dart +++ /dev/null @@ -1,47 +0,0 @@ -part of 'interfaces.dart'; - -/// An interface for hierarchical deterministic (HD) wallets. -/// -/// This interface defines the basic contract for interacting with HD wallets, -/// allowing the creation of accounts, exporting mnemonic phrases, exporting -/// private keys, signing messages, and more. -abstract class HDInterface extends MultiSignerInterface { - /// Adds an Ethereum account derived from the HD wallet using the specified [index]. - /// - /// Parameters: - /// - [index]: The index used to derive the Ethereum account. - /// - /// Returns the Ethereum address associated with the specified index. - /// - /// Example: - /// ```dart - /// final walletSigner = HDWalletSigner.recoverAccount('mnemonic phrase'); - /// final newAccount = walletSigner.addAccount(1); - /// ``` - EthereumAddress addAccount(int index); - - /// Exports the mnemonic phrase associated with the HD wallet signer. - /// - /// Returns the mnemonic phrase. - /// - /// Example: - /// ```dart - /// final walletSigner = HDWalletSigner.recoverAccount('mnemonic phrase'); - /// final exportedMnemonic = walletSigner.exportMnemonic(); - /// ``` - String? exportMnemonic(); - - /// Exports the private key associated with the Ethereum account derived from the HD wallet using the specified [index]. - /// - /// Parameters: - /// - [index]: The index used to derive the Ethereum account. - /// - /// Returns the exported private key as a hexadecimal string. - /// - /// Example: - /// ```dart - /// final walletSigner = HDWalletSigner.recoverAccount('mnemonic phrase'); - /// final exportedPrivateKey = walletSigner.exportPrivateKey(1); - /// ``` - String exportPrivateKey(int index); -} diff --git a/lib/src/interfaces/interfaces.dart b/lib/src/interfaces/interfaces.dart index 87acacc..f815ea8 100644 --- a/lib/src/interfaces/interfaces.dart +++ b/lib/src/interfaces/interfaces.dart @@ -1,28 +1,13 @@ -library interfaces; - import 'dart:typed_data'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:local_auth_android/local_auth_android.dart'; -import 'package:local_auth_ios/local_auth_ios.dart'; -import 'package:web3dart/crypto.dart'; +import 'package:web3_signers/web3_signers.dart' show PassKeyPair, Uint256; import 'package:web3dart/json_rpc.dart' show RpcService; import 'package:web3dart/web3dart.dart'; -import '../../utils.dart' - show - SSAuthOperationOptions, - ChainBaseApiBase, - CredentialType, - SecureStorageMiddleware; import '../../variance.dart' show Chain, EntryPoint, - PassKeyPair, - PassKeySignature, - PassKeysOptions, - Uint256, UserOperation, UserOperationByHash, UserOperationGas, @@ -31,13 +16,8 @@ import '../../variance.dart' part 'account_factory.dart'; part 'bundler_provider.dart'; -part 'ens_resolver.dart'; -part 'hd_interface.dart'; -part 'local_authentication.dart'; -part 'multi_signer_interface.dart'; -part 'passkey_interface.dart'; + part 'rpc_provider.dart'; -part 'secure_storage_repository.dart'; +part 'smart_wallet_factory.dart'; part 'smart_wallet.dart'; -part 'uint256_interface.dart'; part 'user_operations.dart'; diff --git a/lib/src/interfaces/local_authentication.dart b/lib/src/interfaces/local_authentication.dart deleted file mode 100644 index 5bd8a22..0000000 --- a/lib/src/interfaces/local_authentication.dart +++ /dev/null @@ -1,45 +0,0 @@ -part of 'interfaces.dart'; - -/// An abstract class representing authentication operations. -/// -/// Subclasses of this abstract class should provide concrete implementations -/// for authentication mechanisms on specific platforms. -abstract class Authentication { - /// Performs an authentication operation. - /// - /// The authentication operation may involve displaying a prompt to the user - /// for providing authentication credentials, such as a password, fingerprint, - /// or face recognition. - /// - /// The [localizedReason] parameter is a human-readable message describing - /// why authentication is required. The [androidAuthMessages] and [iosAuthMessages] - /// parameters allow providing custom strings for platform-specific authentication - /// scenarios. - /// - /// The [useErrorDialogs] parameter, when set to `true`, indicates that error - /// dialogs should be used to communicate authentication failures. The - /// [stickyAuth] parameter, when set to `true`, allows maintaining the - /// authentication state across app launches. - /// - /// Throws an exception if the authentication operation fails. - Future authenticate({ - required String localizedReason, - AndroidAuthMessages? androidAuthMessages, - IOSAuthMessages? iosAuthMessages, - bool useErrorDialogs = true, - bool stickyAuth = true, - }); - - /// Checks whether the device supports biometric authentication. - /// - /// This method determines whether the device has the necessary hardware and - /// configuration to perform biometric authentication, such as fingerprint or - /// face recognition. - /// - /// Returns `true` if biometric authentication is supported; otherwise, - /// returns `false`. - /// - /// Throws an exception if there is an error while determining biometric - /// authentication support. - Future canAuthenticateWithBiometrics(); -} diff --git a/lib/src/interfaces/multi_signer_interface.dart b/lib/src/interfaces/multi_signer_interface.dart deleted file mode 100644 index 8b0edce..0000000 --- a/lib/src/interfaces/multi_signer_interface.dart +++ /dev/null @@ -1,74 +0,0 @@ -part of 'interfaces.dart'; - -/// An interface for a multi-signer, allowing signing of data and returning the result. -/// -/// the multi-signer interface provides a uniform interface for accessing signer address and signing -/// messages in the Ethereum context. This allows for flexibility in creating different implementations -/// of multi-signers while adhering to a common interface. -/// interfaces include: [PrivateKeySigner], [PassKeySigner] and [HDWalletSigner] -abstract class MultiSignerInterface { - /// The dummy signature is a valid signature that can be used for testing purposes. - /// specifically, this will be used to simulate user operation on the entrypoint. - /// You must specify a dummy signature that matches your transaction signature standard. - String dummySignature = "0x"; - - /// Generates an Ethereum address from the key at the specified [index]. - /// - /// Parameters: - /// - [index]: The index to determine which key to use for address generation. Defaults to 0. - /// - [bytes]: Optional bytes for key generation. If not provided, it defaults to `null`. - /// - /// Example: - /// ```dart - /// final address = getAddress(); - /// ``` - String getAddress({int index = 0, bytes}); - - /// Signs the provided [hash] using the personal sign method. - /// - /// Parameters: - /// - [hash]: The hash to be signed. - /// - [index]: The optional index to specify which privatekey to use for signing (required for HD wallets). If not provided, it defaults to `null`. - /// - [id]: The optional identifier for the signing key. If not provided, it defaults to `null`. Required for passkey signers. - /// - /// Example: - /// ```dart - /// final hashToSign = Uint8List.fromList([0x01, 0x02, 0x03, 0x04]); - /// final signature = await personalSign(hashToSign, index: 0, id: 'credentialId'); // credentialId is only required for passkey signers - /// ``` - - Future personalSign(Uint8List hash, {int? index, String? id}); - - /// Signs the provided [hash] using elliptic curve (EC) signatures and returns the r and s values. - /// - /// Parameters: - /// - [hash]: The hash to be signed. - /// - [index]: The optional index to specify which key to use for signing. If not provided, it defaults to `null`. - /// - [id]: The optional identifier for the signing key. If not provided, it defaults to `null`. Required for passkey signers. - /// - /// Example: - /// ```dart - /// final hashToSign = Uint8List.fromList([0x01, 0x02, 0x03, 0x04]); - /// final signature = await signToEc(hashToSign, index: 0, id: 'credentialId'); - /// ``` - Future signToEc(Uint8List hash, {int? index, String? id}); -} - -mixin SecureStorageMixin { - /// Creates a `SecureStorageMiddleware` instance with the provided [FlutterSecureStorage]. - /// - /// Parameters: - /// - [secureStorage]: The FlutterSecureStorage instance to be used for secure storage. - /// - [authMiddleware]: Optional authentication middleware. Defaults to `null`. - /// - /// Example: - /// ```dart - /// final flutterSecureStorage = FlutterSecureStorage(); - /// final secureStorageMiddleware = this.withSecureStorage( - /// flutterSecureStorage, - /// authMiddleware: myAuthMiddleware, - /// ); - /// ``` - SecureStorageMiddleware withSecureStorage(FlutterSecureStorage secureStorage, - {Authentication? authMiddleware}); -} diff --git a/lib/src/interfaces/passkey_interface.dart b/lib/src/interfaces/passkey_interface.dart deleted file mode 100644 index 7f8d700..0000000 --- a/lib/src/interfaces/passkey_interface.dart +++ /dev/null @@ -1,101 +0,0 @@ -part of 'interfaces.dart'; - -abstract class PasskeyInterface extends MultiSignerInterface { - /// Gets the PassKeysOptions used by the PasskeyInterface. - PassKeysOptions get opts; - - /// Gets the default credential ID used by the Passkey. - String? get defaultId; - - /// Generates the client data hash for the given [PassKeysOptions] and optional challenge. - /// - /// Parameters: - /// - [options]: PassKeysOptions containing the authentication options. - /// - [challenge]: Optional challenge value. Defaults to a randomly generated challenge if not provided. - /// - /// Returns the Uint8List representation of the client data hash. - /// - /// Example: - /// ```dart - /// final passKeysOptions = PassKeysOptions(type: 'webauthn', origin: 'https://example.com'); - /// final clientDataHash = clientDataHash(passKeysOptions); - /// ``` - Uint8List clientDataHash(PassKeysOptions options, {String? challenge}); - - /// Generates the 32-byte client data hash for the given [PassKeysOptions] and optional challenge. - /// - /// Parameters: - /// - [options]: PassKeysOptions containing the authentication options. - /// - [challenge]: Optional challenge value. Defaults to a randomly generated challenge if not provided. - /// - /// Returns the Uint8List representation of the 32-byte client data hash. - /// - /// Example: - /// ```dart - /// final passKeysOptions = PassKeysOptions(type: 'webauthn', origin: 'https://example.com'); - /// final clientDataHash32 = clientDataHash32(passKeysOptions); - /// ``` - Uint8List clientDataHash32(PassKeysOptions options, {String? challenge}); - - /// Converts a List credentialId to a hex string representation with a length of 32 bytes. - /// - /// Parameters: - /// - [credentialId]: List of integers representing the credentialId. - /// - /// Returns the hex string representation of the credentialId padded to 32 bytes. - /// - /// Example: - /// ```dart - /// final credentialId = [1, 2, 3]; - /// final hexString = credentialIdToBytes32Hex(credentialId); - /// ``` - String credentialIdToBytes32Hex(List credentialId); - - /// Parses ASN1-encoded signature bytes and returns a List of two hex strings representing the `r` and `s` values. - /// - /// Parameters: - /// - [signatureBytes]: Uint8List containing the ASN1-encoded signature bytes. - /// - /// Returns a Future> containing hex strings for `r` and `s` values. - /// - /// Example: - /// ```dart - /// final signatureBytes = Uint8List.fromList([48, 68, 2, 32, ...]); - /// final signatureHexValues = await getMessagingSignature(signatureBytes); - /// ``` - Future> getMessagingSignature(Uint8List signatureBytes); - - /// Registers a new PassKeyPair. - /// - /// Parameters: - /// - [name]: The name associated with the PassKeyPair. - /// - [requiresUserVerification]: A boolean indicating whether user verification is required. - /// - /// Returns a Future representing the registered PassKeyPair. - /// - /// Example: - /// ```dart - /// final pkps = PassKeySigner("example", "example.com", "https://example.com"); - /// final passKeyPair = await pkps.register('geffy', true); - /// ``` - Future register(String name, bool requiresUserVerification); - - /// Signs a hash using the PassKeyPair associated with the given credentialId. - /// - /// Parameters: - /// - [hash]: The hash to be signed. - /// - [credentialId]: The credentialId associated with the PassKeyPair. - /// - /// Returns a Future representing the PassKeySignature of the signed hash. - /// - /// Example: - /// ```dart - /// final hash = Uint8List.fromList([/* your hash bytes here */]); - /// final credentialId = 'your_credential_id'; - /// - /// final pkps = PassKeySigner("example", "example.com", "https://example.com"); - /// final passKeySignature = await pkps.signToPasskeySignature(hash, credentialId); - /// ``` - Future signToPasskeySignature( - Uint8List hash, String credentialId); -} diff --git a/lib/src/interfaces/secure_storage_repository.dart b/lib/src/interfaces/secure_storage_repository.dart deleted file mode 100644 index ad7d881..0000000 --- a/lib/src/interfaces/secure_storage_repository.dart +++ /dev/null @@ -1,149 +0,0 @@ -part of 'interfaces.dart'; - -/// A repository for secure storage operations. -/// -/// This abstract class defines methods for saving, reading, updating, and -/// deleting key-value pairs in a secure storage medium. The storage operations -/// can optionally require authentication for additional security. -/// -/// Subclasses of this abstract class should provide concrete implementations -/// for these methods based on the specific secure storage mechanism they intend -/// to use. -abstract class SecureStorageRepository { - /// Saves a key-value pair to the secure storage. - /// - /// Parameters: - /// - [key]: The key under which to store the value. - /// - [value]: The value to be stored. - /// - [options]: Options for the secure storage operation, including authentication requirements. - /// - /// Throws a [SecureStorageAuthMiddlewareError] if authentication is required but no authentication middleware is provided. - /// - /// Example: - /// ```dart - /// final saveKey = 'myKey'; - /// final saveValue = 'myValue'; - /// final saveOptions = SSAuthOperationOptions( - /// requiresAuth: true, - /// authReason: 'Authenticate to save the key-value pair.', - /// ssNameSpace: 'myNamespace', - /// ); - /// await save(saveKey, saveValue, options: saveOptions); - /// print('Key-value pair saved successfully.'); - /// ``` - Future save(String key, String value, - {SSAuthOperationOptions? options}); - - /// Saves a credential to the secure storage for a specified [CredentialType]. - /// - /// Parameters: - /// - [type]: The type of credential to be saved. - /// - [options]: Options for the secure storage operation, including authentication requirements. - /// - /// Throws a [SecureStorageAuthMiddlewareError] if authentication is required but no authentication middleware is provided. - /// - /// Example: - /// ```dart - /// final saveCredentialType = CredentialType.exampleCredential; - /// final saveOptions = SSAuthOperationOptions( - /// requiresAuth: true, - /// authReason: 'Authenticate to save the credential.', - /// ssNameSpace: 'myNamespace', - /// ); - /// await saveCredential(saveCredentialType, options: saveOptions); - /// print('Credential saved successfully.'); - /// ``` - Future saveCredential(CredentialType type, - {SSAuthOperationOptions? options}); - - /// Reads a value from the secure storage. - /// - /// Parameters: - /// - [key]: The key for the value to be read. - /// - [options]: Options for the secure storage operation, including authentication requirements. - /// - /// Throws a [SecureStorageAuthMiddlewareError] if authentication is required but no authentication middleware is provided. - /// - /// Returns the value associated with the provided key, or `null` if the key is not found. - /// - /// Example: - /// ```dart - /// final keyToRead = 'exampleKey'; - /// final readOptions = SSAuthOperationOptions( - /// requiresAuth: true, - /// authReason: 'Authenticate to read the key.', - /// ssNameSpace: 'myNamespace', - /// ); - /// final storedValue = await read(keyToRead, options: readOptions); - /// print('Stored value: $storedValue'); - /// ``` - Future read(String key, {SSAuthOperationOptions? options}); - - /// Reads a credential from the secure storage. - /// - /// Parameters: - /// - [type]: The type of credential to be read. - /// - [options]: Options for the secure storage operation, including authentication requirements. - /// - /// Throws a [SecureStorageAuthMiddlewareError] if authentication is required but no authentication middleware is provided. - /// - /// Returns the credential associated with the provided type, or `null` if the credential is not found. - /// - /// Example: - /// ```dart - /// final credentialType = CredentialType.hdwallet; - /// final readOptions = SSAuthOperationOptions( - /// requiresAuth: true, - /// authReason: 'Authenticate to read the credential.', - /// ssNameSpace: 'myNamespace', - /// ); - /// final storedCredential = await readCredential(credentialType, options: readOptions); - /// print('Stored credential: $storedCredential'); - /// ``` - Future readCredential(CredentialType type, - {SSAuthOperationOptions? options}); - - /// Updates the value of an existing key in the secure storage. - /// - /// Parameters: - /// - [key]: The key for which to update the value. - /// - [value]: The new value to be stored. - /// - [options]: Options for the secure storage operation, including authentication requirements. - /// - /// Throws a [SecureStorageAuthMiddlewareError] if authentication is required but no authentication middleware is provided. - /// - /// Example: - /// ```dart - /// final updateKey = 'myKey'; - /// final updateValue = 'newValue'; - /// final updateOptions = SSAuthOperationOptions( - /// requiresAuth: true, - /// authReason: 'Authenticate to update the key value.', - /// ssNameSpace: 'myNamespace', - /// ); - /// await update(updateKey, updateValue, options: updateOptions); - /// print('Key updated successfully.'); - /// ``` - Future update(String key, String value, - {SSAuthOperationOptions? options}); - - /// Deletes a key from the secure storage. - /// - /// Parameters: - /// - [key]: The key to be deleted. - /// - [options]: Options for the secure storage operation, including authentication requirements. - /// - /// Throws a [SecureStorageAuthMiddlewareError] if authentication is required but no authentication middleware is provided. - /// - /// Example: - /// ```dart - /// final keyToDelete = 'exampleKey'; - /// final deleteOptions = SSAuthOperationOptions( - /// requiresAuth: true, - /// authReason: 'Authenticate to delete the key.', - /// ssNameSpace: 'myNamespace', - /// ); - /// await delete(keyToDelete, options: deleteOptions); - /// ``` - Future delete(String key, {SSAuthOperationOptions? options}); -} diff --git a/lib/src/interfaces/smart_wallet.dart b/lib/src/interfaces/smart_wallet.dart index f86775b..d3101ea 100644 --- a/lib/src/interfaces/smart_wallet.dart +++ b/lib/src/interfaces/smart_wallet.dart @@ -28,9 +28,6 @@ abstract class SmartWalletBase { /// Converts the Smart Wallet address to its hexadecimal representation. String? get toHex; - /// Sets the smart wallet address for this account; - set setWalletAddress(EthereumAddress address); - /// Builds a [UserOperation] instance with the specified parameters. /// /// Parameters: @@ -59,87 +56,13 @@ abstract class SmartWalletBase { /// if used improperly. It is intended for advanced use cases where the caller is aware of the potential risks. /// /// Parameters: - /// - `code`: The initialization calldata as a [Uint8List]. Set to `null` to clear the existing data. + /// - `code`: The initialization calldata as a [Uint8List]. /// /// Example: /// ```dart /// dangerouslySetInitCallData(Uint8List.fromList([0x01, 0x02, 0x03])); /// ``` - void dangerouslySetInitCallData(Uint8List? code); - - /// Asynchronously creates a simple Ethereum smart account using the provided salt value. - /// Uses counterfactactual deployment to create the account and [isDeployed] should be used to check deployment status. - /// An `initCode` will be attached on the first transaction. - /// - /// Parameters: - /// - `salt`: A [Uint256] representing the salt value for account creation. - /// - `index`: Optional parameter specifying the index for selecting a signer. Defaults to `null`. - /// - /// Returns: - /// A [Future] that completes with the created [SmartWallet] instance. - /// - /// Example: - /// ```dart - /// var smartWallet = await createSimpleAccount(Uint256.zero, index: 1); - /// ``` - /// This method generates initialization calldata using the 'createAccount' method and the provided signer and salt. - /// It then retrieves the Ethereum address for the simple account and sets it to the wallet instance. - Future createSimpleAccount(Uint256 salt, {int? index}); - - /// Asynchronously creates a simple Ethereum smart account using a passkey pair and the provided salt value. - /// - /// Parameters: - /// - `pkp`: A [PassKeyPair] representing the passkey pair for account creation. - /// - `salt`: A [Uint256] representing the salt value for account creation. - /// - /// Returns: - /// A [Future] that completes with the created [SmartWallet] instance. - /// - /// Example: - /// ```dart - /// var smartWallet = await createSimplePasskeyAccount(myPassKeyPair, Uint256.zero); - /// ``` - /// This method generates initialization calldata using the 'createPasskeyAccount' method and the provided - /// passkey pair and salt. The passkey pair includes the credential and public key values. - Future createSimplePasskeyAccount(PassKeyPair pkp, Uint256 salt); - - /// Asynchronously retrieves the Ethereum address for a simple account created with the specified signer and salt. - /// - /// Parameters: - /// - `signer`: The [EthereumAddress] of the signer associated with the account. - /// - `salt`: A [Uint256] representing the salt value used in the account creation. - /// - /// Returns: - /// A [Future] that completes with the Ethereum address of the simple account. - /// - /// Example: - /// ```dart - /// var address = await getSimpleAccountAddress( - /// EthereumAddress.fromHex('0x1234567890abcdef1234567890abcdef12345678'), - /// Uint256.zero, - /// ); - /// ``` - Future getSimpleAccountAddress( - EthereumAddress signer, Uint256 salt); - - /// Asynchronously retrieves the Ethereum address for a simple account created with the specified passkey pair and salt. - /// - /// Parameters: - /// - `pkp`: The [PassKeyPair] used for creating the account. - /// - `salt`: A [Uint256] representing the salt value used in the account creation. - /// - /// Returns: - /// A [Future] that completes with the Ethereum address of the simple account. - /// - /// Example: - /// ```dart - /// var address = await getSimplePassKeyAccountAddress( - /// myPassKeyPair, - /// Uint256.zero, - /// ); - /// ``` - Future getSimplePassKeyAccountAddress( - PassKeyPair pkp, Uint256 salt); + void dangerouslySetInitCallData(Uint8List code); /// Asynchronously transfers native Token (ETH) to the specified recipient with the given amount. /// diff --git a/lib/src/interfaces/smart_wallet_factory.dart b/lib/src/interfaces/smart_wallet_factory.dart new file mode 100644 index 0000000..d036dda --- /dev/null +++ b/lib/src/interfaces/smart_wallet_factory.dart @@ -0,0 +1,79 @@ +part of 'interfaces.dart'; + +abstract class SmartWalletFactoryBase {} + +abstract class SmartWalletFactoryOld { + /// Asynchronously creates a simple Ethereum smart account using the provided salt value. + /// Uses counterfactactual deployment to create the account and [isDeployed] should be used to check deployment status. + /// An `initCode` will be attached on the first transaction. + /// + /// Parameters: + /// - `salt`: A [Uint256] representing the salt value for account creation. + /// - `index`: Optional parameter specifying the index for selecting a signer. Defaults to `null`. + /// + /// Returns: + /// A [Future] that completes with the created [SmartWallet] instance. + /// + /// Example: + /// ```dart + /// var smartWallet = await createSimpleAccount(Uint256.zero, index: 1); + /// ``` + /// This method generates initialization calldata using the 'createAccount' method and the provided signer and salt. + /// It then retrieves the Ethereum address for the simple account and sets it to the wallet instance. + Future createSimpleAccount(Uint256 salt, {int? index}); + + /// Asynchronously creates a simple Ethereum smart account using a passkey pair and the provided salt value. + /// + /// Parameters: + /// - `pkp`: A [PassKeyPair] representing the passkey pair for account creation. + /// - `salt`: A [Uint256] representing the salt value for account creation. + /// + /// Returns: + /// A [Future] that completes with the created [SmartWallet] instance. + /// + /// Example: + /// ```dart + /// var smartWallet = await createSimplePasskeyAccount(myPassKeyPair, Uint256.zero); + /// ``` + /// This method generates initialization calldata using the 'createPasskeyAccount' method and the provided + /// passkey pair and salt. The passkey pair includes the credential and public key values. + Future createLightP256Account(PassKeyPair pkp, Uint256 salt); + + /// Asynchronously retrieves the Ethereum address for a simple account created with the specified signer and salt. + /// + /// Parameters: + /// - `signer`: The [EthereumAddress] of the signer associated with the account. + /// - `salt`: A [Uint256] representing the salt value used in the account creation. + /// + /// Returns: + /// A [Future] that completes with the Ethereum address of the simple account. + /// + /// Example: + /// ```dart + /// var address = await getSimpleAccountAddress( + /// EthereumAddress.fromHex('0x1234567890abcdef1234567890abcdef12345678'), + /// Uint256.zero, + /// ); + /// ``` + Future getSimpleAccountAddress( + EthereumAddress signer, Uint256 salt); + + /// Asynchronously retrieves the Ethereum address for a simple account created with the specified passkey pair and salt. + /// + /// Parameters: + /// - `pkp`: The [PassKeyPair] used for creating the account. + /// - `salt`: A [Uint256] representing the salt value used in the account creation. + /// + /// Returns: + /// A [Future] that completes with the Ethereum address of the simple account. + /// + /// Example: + /// ```dart + /// var address = await getSimplePassKeyAccountAddress( + /// myPassKeyPair, + /// Uint256.zero, + /// ); + /// ``` + Future getSimplePassKeyAccountAddress( + PassKeyPair pkp, Uint256 salt); +} diff --git a/lib/src/interfaces/uint256_interface.dart b/lib/src/interfaces/uint256_interface.dart deleted file mode 100644 index 64a315e..0000000 --- a/lib/src/interfaces/uint256_interface.dart +++ /dev/null @@ -1,138 +0,0 @@ -part of 'interfaces.dart'; - -/// Abstract base class representing a 64-bit length big number, similar to Solidity. -/// -/// This interface defines methods and properties for working with 64-bit length big numbers, -/// with operations such as multiplication, addition, subtraction, division, and various conversions. -abstract class Uint256Base { - BigInt get value; - - Uint256Base operator *(covariant Uint256Base other); - - Uint256Base operator +(covariant Uint256Base other); - - Uint256Base operator -(covariant Uint256Base other); - - Uint256Base operator /(covariant Uint256Base other); - - /// Converts the value of this [Uint256] instance to a [BigInt] representing the equivalent amount in ether. - /// - /// Example 1: - /// ```dart - /// final value = Uint256(BigInt.from(5000000000)); - /// final etherValue = value.toEther(); // Converts the value to ether (0.000000000000000005) - /// ``` - - /// Example 2: - /// ```dart - /// final value = Uint256(BigInt.from(1000000000000000000)); - /// final etherValue = value.toEther(); // Converts the value to ether (1.0) - /// ``` - BigInt toEther(); - - /// Converts the value of this [Uint256] instance to an [EtherAmount] with the equivalent amount in wei. - /// - /// Example 1: - /// ```dart - /// final value = Uint256(BigInt.from(5000000000)); - /// final etherAmount = value.toEtherAmount(); // Converts the value to EtherAmount (5 wei) - /// ``` - - /// Example 2: - /// ```dart - /// final value = Uint256(BigInt.from(1000000000000000000)); - /// final etherAmount = value.toEtherAmount(); // Converts the value to EtherAmount (1 ether) - /// ``` - EtherAmount toEtherAmount(); - - /// Converts the value of this [Uint256] instance to a hexadecimal string with a length of 64 characters, padded with leading zeros. - /// - /// Example 1: - /// ```dart - /// final value = Uint256(BigInt.from(42)); - /// final hexString = value.toHex(); // Converts the value to hex (0x000000000000000000000000000000000000000000000000000000000000002a) - /// ``` - - /// Example 2: - /// ```dart - /// final value = Uint256(BigInt.from(255)); - /// final hexString = value.toHex(); // Converts the value to hex (0x00000000000000000000000000000000000000000000000000000000000000ff) - /// ``` - String toHex(); - - /// Converts the value of this [Uint256] instance to an integer. - /// - /// Example 1: - /// ```dart - /// final value = Uint256(BigInt.from(42)); - /// final intValue = value.toInt(); // Converts the value to an integer (42) - /// ``` - - /// Example 2: - /// ```dart - /// final value = Uint256(BigInt.from(123456789)); - /// final intValue = value.toInt(); // Converts the value to an integer (123456789) - /// ``` - int toInt(); - - /// Returns the hexadecimal representation of this [Uint256] instance as a string. - /// - /// Example 1: - /// ```dart - /// final value = Uint256(BigInt.from(42)); - /// final stringValue = value.toString(); // Converts the value to a string (0x000000000000000000000000000000000000000000000000000000000000002a) - /// ``` - - /// Example 2: - /// ```dart - /// final value = Uint256(BigInt.from(255)); - /// final stringValue = value.toString(); // Converts the value to a string (0x00000000000000000000000000000000000000000000000000000000000000ff) - /// ``` - @override - String toString(); - - /// Converts the value of this [Uint256] instance to a [BigInt] with a scale defined by [decimals]. - /// - /// Example 1: - /// ```dart - /// final value = Uint256(BigInt.from(42)); - /// final unitValue = value.toUnit(3); // Converts the value to a unit with 3 decimals (42000) - /// ``` - - /// Example 2: - /// ```dart - /// final value = Uint256(BigInt.from(123456789)); - /// final unitValue = value.toUnit(6); // Converts the value to a unit with 6 decimals (123456789000000) - /// ``` - BigInt toUnit(int decimals); - - /// Converts the value of this [Uint256] instance from a unit with [decimals] to a double. - /// - /// Example 1: - /// ```dart - /// final value = Uint256(BigInt.from(42000)); - /// final doubleValue = value.fromUnit(3); // Converts the value from a unit with 3 decimals to a double (42.0) - /// ``` - - /// Example 2: - /// ```dart - /// final value = Uint256(BigInt.from(123456789000000)); - /// final doubleValue = value.fromUnit(6); // Converts the value from a unit with 6 decimals to a double (123.456789) - /// ``` - double fromUnit(int decimals); - - /// Converts the value of this [Uint256] instance to a [BigInt] representing the equivalent amount in wei. - /// - /// Example 1: - /// ```dart - /// final value = Uint256(BigInt.from(5000000000)); - /// final weiValue = value.toWei(); // Converts the value to wei (5000000000) - /// ``` - - /// Example 2: - /// ```dart - /// final value = Uint256(BigInt.from(1000000000000000000)); - /// final weiValue = value.toWei(); // Converts the value to wei (1000000000000000000) - /// ``` - BigInt toWei(); -} diff --git a/lib/src/signers/hd_wallet_signer.dart b/lib/src/signers/hd_wallet_signer.dart deleted file mode 100644 index 9bef3ca..0000000 --- a/lib/src/signers/hd_wallet_signer.dart +++ /dev/null @@ -1,158 +0,0 @@ -part of '../../variance.dart'; - -class HDWalletSigner with SecureStorageMixin implements HDInterface { - final String _mnemonic; - - final String _seed; - - late final EthereumAddress zerothAddress; - - @override - String dummySignature = - "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c"; - - /// Creates a new HD wallet signer instance by generating a random mnemonic phrase. - /// - /// Example: - /// ```dart - /// final walletSigner = HDWalletSigner.createWallet(); - /// ``` - factory HDWalletSigner.createWallet() { - return HDWalletSigner.recoverAccount(bip39.generateMnemonic()); - } - - /// Recovers an HD wallet signer instance from a given mnemonic phrase. - /// - /// Parameters: - /// - [mnemonic]: The mnemonic phrase used for recovering the HD wallet signer. - /// - /// Example: - /// ```dart - /// final mnemonicPhrase = 'word1 word2 word3 ...'; // Replace with an actual mnemonic phrase - /// final recoveredSigner = HDWalletSigner.recoverAccount(mnemonicPhrase); - /// ``` - - factory HDWalletSigner.recoverAccount(String mnemonic) { - final seed = bip39.mnemonicToSeedHex(mnemonic); - final signer = HDWalletSigner._internal(seed: seed, mnemonic: mnemonic); - signer.zerothAddress = signer._add(seed, 0); - return signer; - } - - HDWalletSigner._internal({required String seed, required String mnemonic}) - : _seed = seed, - _mnemonic = mnemonic { - assert(seed.isNotEmpty, "seed cannot be empty"); - } - - @override - EthereumAddress addAccount(int index) { - return _add(_seed, index); - } - - @override - String exportMnemonic() { - return _getMnemonic(); - } - - @override - String exportPrivateKey(int index) { - final ethPrivateKey = _getPrivateKey(index); - Uint8List privKey = ethPrivateKey.privateKey; - bool rlz = shouldRemoveLeadingZero(privKey); - if (rlz) { - privKey = privKey.sublist(1); - } - return hexlify(privKey); - } - - @override - String getAddress({int index = 0, bytes}) { - return _getEthereumAddress(index: index).hex; - } - - @override - Future personalSign(Uint8List hash, - {int? index, String? id}) async { - final privKey = _getPrivateKey(index ?? 0); - return privKey.signPersonalMessageToUint8List(hash); - } - - @override - Future signToEc(Uint8List hash, - {int? index, String? id}) async { - final privKey = _getPrivateKey(index ?? 0); - return privKey.signToEcSignature(hash); - } - - @override - SecureStorageMiddleware withSecureStorage(FlutterSecureStorage secureStorage, - {Authentication? authMiddleware}) { - return SecureStorageMiddleware( - secureStorage: secureStorage, - authMiddleware: authMiddleware, - credential: _getMnemonic()); - } - - EthereumAddress _add(String seed, int index) { - final hdKey = _deriveHdKey(seed, index); - final privKey = _deriveEthPrivKey(hdKey.privateKeyHex()); - return privKey.address; - } - - EthPrivateKey _deriveEthPrivKey(String key) { - final ethPrivateKey = EthPrivateKey.fromHex(key); - return ethPrivateKey; - } - - bip44.ExtendedPrivateKey _deriveHdKey(String seed, int idx) { - final path = "m/44'/60'/0'/0/$idx"; - final chain = bip44.Chain.seed(seed); - final hdKey = chain.forPath(path) as bip44.ExtendedPrivateKey; - return hdKey; - } - - EthereumAddress _getEthereumAddress({int index = 0}) { - bip44.ExtendedPrivateKey hdKey = _getHdKey(index); - final privKey = _deriveEthPrivKey(hdKey.privateKeyHex()); - return privKey.address; - } - - bip44.ExtendedPrivateKey _getHdKey(int index) { - return _deriveHdKey(_seed, index); - } - - String _getMnemonic() { - return _mnemonic; - } - - EthPrivateKey _getPrivateKey(int index) { - final hdKey = _getHdKey(index); - final privateKey = _deriveEthPrivKey(hdKey.privateKeyHex()); - return privateKey; - } - - /// Loads an HD wallet signer instance from secure storage using the provided [SecureStorageRepository]. - /// - /// Parameters: - /// - [storageMiddleware]: The secure storage repository used to retrieve the HD wallet credentials. - /// - [options]: Optional authentication operation options. Defaults to `null`. - /// - /// Returns a `Future` that resolves to a `HDWalletSigner` instance if successfully loaded, or `null` otherwise. - /// - /// Example: - /// ```dart - /// final secureStorageRepo = SecureStorageRepository(); // Replace with an actual instance - /// final loadedSigner = await HDWalletSigner.loadFromSecureStorage( - /// storageMiddleware: secureStorageRepo, - /// ); - /// ``` - static Future loadFromSecureStorage( - {required SecureStorageRepository storageMiddleware, - SSAuthOperationOptions? options}) { - return storageMiddleware - .readCredential(CredentialType.hdwallet, options: options) - .then((value) => - value != null ? HDWalletSigner.recoverAccount(value) : null); - } -} diff --git a/lib/src/signers/passkey_signer.dart b/lib/src/signers/passkey_signer.dart deleted file mode 100644 index 103d25a..0000000 --- a/lib/src/signers/passkey_signer.dart +++ /dev/null @@ -1,420 +0,0 @@ -part of '../../variance.dart'; - -class AuthData { - final String credentialHex; - final String credentialId; - final List publicKey; - final String aaGUID; - AuthData(this.credentialHex, this.credentialId, this.publicKey, this.aaGUID); -} - -class PassKeyPair with SecureStorageMixin { - final Uint8List credentialHexBytes; - final String credentialId; - final List publicKey; - final String name; - final String aaGUID; - final DateTime registrationTime; - PassKeyPair(this.credentialHexBytes, this.credentialId, this.publicKey, - this.name, this.aaGUID, this.registrationTime); - - factory PassKeyPair.fromJson(String source) => - PassKeyPair.fromMap(json.decode(source) as Map); - - factory PassKeyPair.fromMap(Map map) { - return PassKeyPair( - Uint8List.fromList(map['credentialHexBytes']), - map['credentialId'], - List.from( - (map['publicKey'] as List).map( - (x) => Uint256.fromHex(x), - ), - ), - map['name'], - map['aaGUID'], - DateTime.fromMillisecondsSinceEpoch(map['registrationTime']), - ); - } - - String toJson() => json.encode(toMap()); - - Map toMap() { - return { - 'credentialHexBytes': credentialHexBytes.toList(), - 'credentialId': credentialId, - 'publicKey': publicKey.map((x) => x.toHex()).toList(), - 'name': name, - 'aaGUID': aaGUID, - 'registrationTime': registrationTime.millisecondsSinceEpoch, - }; - } - - @override - SecureStorageMiddleware withSecureStorage(FlutterSecureStorage secureStorage, - {Authentication? authMiddleware}) { - return SecureStorageMiddleware( - secureStorage: secureStorage, - authMiddleware: authMiddleware, - credential: toJson()); - } - - /// Loads a passkey pair from secure storage using the provided [SecureStorageRepository]. - /// - /// Parameters: - /// - [storageMiddleware]: The secure storage repository used to retrieve the passkey pair credentials. - /// - [options]: Optional authentication operation options. Defaults to `null`. - /// - /// Returns a `Future` that resolves to a `PassKeyPair` instance if successfully loaded, or `null` otherwise. - /// - /// Example: - /// ```dart - /// final secureStorageRepo = SecureStorageRepository(); // Replace with an actual instance - /// final loadedPassKeyPair = await PassKeyPair.loadFromSecureStorage( - /// storageMiddleware: secureStorageRepo, - /// ); - /// ``` - static Future loadFromSecureStorage( - {required SecureStorageRepository storageMiddleware, - SSAuthOperationOptions? options}) { - return storageMiddleware - .readCredential(CredentialType.passkeypair, options: options) - .then((value) => value != null ? PassKeyPair.fromJson(value) : null); - } -} - -class PassKeySignature { - final String credentialId; - final List rs; - final Uint8List authData; - final String clientDataPrefix; - final String clientDataSuffix; - PassKeySignature(this.credentialId, this.rs, this.authData, - this.clientDataPrefix, this.clientDataSuffix); - - /// Converts the `PassKeySignature` to a `Uint8List` using the specified ABI encoding. - /// - /// Returns the encoded Uint8List. - /// - /// Example: - /// ```dart - /// final Uint8List encodedSig = pkpSig.toUint8List(); - /// ``` - Uint8List toUint8List() { - return abi.encode([ - 'uint256', - 'uint256', - 'bytes', - 'string', - 'string' - ], [ - rs[0].value, - rs[1].value, - authData, - clientDataPrefix, - clientDataSuffix - ]); - } -} - -class PassKeySigner implements PasskeyInterface { - final _makeCredentialJson = '''{ - "authenticatorExtensions": "", - "clientDataHash": "", - "credTypesAndPubKeyAlgs": [ - ["public-key", -7] - ], - "excludeCredentials": [], - "requireResidentKey": true, - "requireUserPresence": true, - "requireUserVerification": false, - "rp": { - "name": "", - "id": "" - }, - "user": { - "name": "", - "displayName": "", - "id": "" - } - }'''; - - final _getAssertionJson = '''{ - "allowCredentialDescriptorList": [], - "authenticatorExtensions": "", - "clientDataHash": "", - "requireUserPresence": true, - "requireUserVerification": false, - "rpId": "" - }'''; - - final PassKeysOptions _opts; - - final Authenticator _auth; - - String? _defaultId; - - PassKeySigner(String namespace, String name, String origin, - {bool? crossOrigin}) - : _opts = PassKeysOptions( - namespace: namespace, - name: name, - origin: origin, - crossOrigin: crossOrigin ?? false, - ), - _auth = Authenticator(true, true); - - @override - String? get defaultId => _defaultId; - - @override - PassKeysOptions get opts => _opts; - - @override - String dummySignature = - "0xe017c9b829f0d550c9a0f1d791d460485b774c5e157d2eaabdf690cba2a62726b3e3a3c5022dc5301d272a752c05053941b1ca608bf6bc8ec7c71dfe15d5305900000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000025205f5f63c4a6cebdc67844b75186367e6d2e4f19b976ab0affefb4e981c22435050000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a2200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001d222c226f726967696e223a226170692e776562617574686e2e696f227d000000"; - - @override - Uint8List clientDataHash(PassKeysOptions options, {String? challenge}) { - options.challenge = challenge ?? _randomChallenge(options); - final clientDataJson = jsonEncode({ - "type": options.type, - "challenge": options.challenge, - "origin": options.origin, - "crossOrigin": options.crossOrigin - }); - return Uint8List.fromList(utf8.encode(clientDataJson)); - } - - @override - Uint8List clientDataHash32(PassKeysOptions options, {String? challenge}) { - final dataBuffer = clientDataHash(options, challenge: challenge); - final hash = sha256Hash(dataBuffer); - return Uint8List.fromList(hash.bytes); - } - - @override - String credentialIdToBytes32Hex(List credentialId) { - require(credentialId.length <= 32, "exception: credentialId too long"); - while (credentialId.length < 32) { - credentialId.insert(0, 0); - } - return hexlify(credentialId); - } - - @override - String getAddress({int index = 0, bytes}) { - return credentialIdToBytes32Hex(bytes); - } - - @override - Future> getMessagingSignature(Uint8List signatureBytes) async { - ASN1Parser parser = ASN1Parser(signatureBytes); - ASN1Sequence parsedSignature = parser.nextObject() as ASN1Sequence; - ASN1Integer rValue = parsedSignature.elements[0] as ASN1Integer; - ASN1Integer sValue = parsedSignature.elements[1] as ASN1Integer; - Uint8List rBytes = rValue.valueBytes(); - Uint8List sBytes = sValue.valueBytes(); - - if (shouldRemoveLeadingZero(rBytes)) { - rBytes = rBytes.sublist(1); - } - if (shouldRemoveLeadingZero(sBytes)) { - sBytes = sBytes.sublist(1); - } - - final r = hexlify(rBytes); - final s = hexlify(sBytes); - return [r, s]; - } - - @override - Future personalSign(Uint8List hash, - {int? index, String? id}) async { - require(id != null, "credential id expected"); - final signature = await signToPasskeySignature(hash, id!); - return signature.toUint8List(); - } - - @override - Future register( - String name, bool requiresUserVerification) async { - final attestation = await _register(name, requiresUserVerification); - final authData = _decodeAttestation(attestation); - if (authData.publicKey.length != 2) { - throw "Invalid public key"; - } - _defaultId = authData.credentialId; - return PassKeyPair( - hexToBytes(authData.credentialHex), - authData.credentialId, - [ - Uint256.fromHex(authData.publicKey[0]), - Uint256.fromHex(authData.publicKey[1]), - ], - name, - authData.aaGUID, - DateTime.now(), - ); - } - - @override - Future signToEc(Uint8List hash, - {int? index, String? id}) async { - require(id != null, "credential id expected"); - final signature = await signToPasskeySignature(hash, id!); - return MsgSignature(signature.rs[0].value, signature.rs[1].value, 0); - } - - @override - Future signToPasskeySignature( - Uint8List hash, String credentialId) async { - final webAuthnOptions = _opts; - webAuthnOptions.type = "webauthn.get"; - - // Prepare hash - final hashBase64 = base64Url - .encode(hash) - .replaceAll(RegExp(r'=', multiLine: true, caseSensitive: false), ''); - - // Prepare challenge - final challenge32 = - clientDataHash32(webAuthnOptions, challenge: hashBase64); - - // Authenticate - final assertion = await _authenticate([credentialId], challenge32, true); - final sig = await getMessagingSignature(assertion.signature); - - // Prepare challenge for response - final challenge = clientDataHash(webAuthnOptions, challenge: hashBase64); - final clientDataJSON = utf8.decode(challenge); - int challengePos = clientDataJSON.indexOf(hashBase64); - String challengePrefix = clientDataJSON.substring(0, challengePos); - String challengeSuffix = - clientDataJSON.substring(challengePos + hashBase64.length); - - return PassKeySignature( - base64Url.encode(assertion.selectedCredentialId), - [ - Uint256.fromHex(sig[0]), - Uint256.fromHex(sig[1]), - ], - assertion.authenticatorData, - challengePrefix, - challengeSuffix, - ); - } - - Future _authenticate(List credentialIds, - Uint8List challenge, bool requiresUserVerification) async { - final entity = GetAssertionOptions.fromJson(jsonDecode(_getAssertionJson)); - entity.allowCredentialDescriptorList = credentialIds - .map((credentialId) => PublicKeyCredentialDescriptor( - type: PublicKeyCredentialType.publicKey, - id: base64Url.decode(credentialId))) - .toList(); - if (entity.allowCredentialDescriptorList!.isEmpty) { - throw AuthenticatorException('User not found'); - } - entity.clientDataHash = challenge; - entity.rpId = _opts.namespace; - entity.requireUserVerification = requiresUserVerification; - entity.requireUserPresence = !requiresUserVerification; - return await _auth.getAssertion(entity); - } - - AuthData _decode(dynamic authData) { - // Extract the length of the public key from the authentication data. - final l = (authData[53] << 8) + authData[54]; - - // Calculate the offset for the start of the public key data. - final publicKeyOffset = 55 + l; - - // Extract the public key data from the authentication data. - final pKey = authData.sublist(publicKeyOffset); - - // Extract the credential ID from the authentication data. - final List credentialId = authData.sublist(55, publicKeyOffset); - - // Extract and encode the aaGUID from the authentication data. - final aaGUID = base64Url.encode(authData.sublist(37, 53)); - - // Decode the CBOR-encoded public key and convert it to a map. - final decodedPubKey = cbor.decode(pKey).toObject() as Map; - - // Calculate the hash of the credential ID. - final credentialHex = credentialIdToBytes32Hex(credentialId); - - // Extract x and y coordinates from the decoded public key. - final x = hexlify(decodedPubKey[-2]); - final y = hexlify(decodedPubKey[-3]); - - return AuthData( - credentialHex, base64Url.encode(credentialId), [x, y], aaGUID); - } - - AuthData _decodeAttestation(Attestation attestation) { - final attestationAsCbor = attestation.asCBOR(); - final decodedAttestationAsCbor = - cbor.decode(attestationAsCbor).toObject() as Map; - final authData = decodedAttestationAsCbor["authData"]; - final decode = _decode(authData); - return decode; - } - - String _randomChallenge(PassKeysOptions options) { - final uuid = const Uuid() - .v5buffer(Uuid.NAMESPACE_URL, options.name, List.filled(32, 0)); - return base64Url.encode(uuid); - } - - Future _register( - String name, bool requiresUserVerification) async { - final options = _opts; - options.type = "webauthn.create"; - final hash = clientDataHash32(options); - final entity = - MakeCredentialOptions.fromJson(jsonDecode(_makeCredentialJson)); - entity.userEntity = UserEntity( - id: Uint8List.fromList(utf8.encode(name)), - displayName: name, - name: name, - ); - entity.clientDataHash = hash; - entity.rpEntity.id = options.namespace; - entity.rpEntity.name = options.name; - entity.requireUserVerification = requiresUserVerification; - entity.requireUserPresence = !requiresUserVerification; - return await _auth.makeCredential(entity); - } - - /// [credentialIdToBytes32Hex] converts a 32 byte credentialAddress hex to a base64 string - static String credentialHexToBase64(String credentialHex) { - // Remove the "0x" prefix if present. - if (credentialHex.startsWith("0x")) { - credentialHex = credentialHex.substring(2); - } - - List credentialId = hexToBytes(credentialHex); - - while (credentialId.isNotEmpty && credentialId[0] == 0) { - credentialId.removeAt(0); - } - return base64Url.encode(credentialId); - } -} - -class PassKeysOptions { - final String namespace; - final String name; - final String origin; - bool? crossOrigin; - String? challenge; - String? type; - PassKeysOptions( - {required this.namespace, - required this.name, - required this.origin, - this.crossOrigin, - this.challenge, - this.type}); -} diff --git a/lib/src/signers/private_key_signer.dart b/lib/src/signers/private_key_signer.dart deleted file mode 100644 index bd20676..0000000 --- a/lib/src/signers/private_key_signer.dart +++ /dev/null @@ -1,128 +0,0 @@ -part of '../../variance.dart'; - -class PrivateKeySigner with SecureStorageMixin implements MultiSignerInterface { - final Wallet _credential; - - @override - String dummySignature = - "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c"; - - /// Creates a PrivateKeySigner instance using the provided EthPrivateKey. - /// - /// Parameters: - /// - [privateKey]: The EthPrivateKey used to create the PrivateKeySigner. - /// - [password]: The password for encrypting the private key. - /// - [random]: The Random instance for generating random values. - /// - [scryptN]: Scrypt parameter N (CPU/memory cost) for key derivation. Defaults to 8192. - /// - [p]: Scrypt parameter p (parallelization factor) for key derivation. Defaults to 1. - /// - /// Example: - /// ```dart - /// final ethPrivateKey = EthPrivateKey.fromHex('your_private_key_hex'); - /// final password = 'your_password'; - /// final random = Random.secure(); - /// final privateKeySigner = PrivateKeySigner.create(ethPrivateKey, password, random); - /// ``` - PrivateKeySigner.create( - EthPrivateKey privateKey, String password, Random random, - {int scryptN = 8192, int p = 1}) - : _credential = Wallet.createNew(privateKey, password, random, - scryptN: scryptN, p: p); - - /// Creates a PrivateKeySigner instance with a randomly generated EthPrivateKey. - /// - /// Parameters: - /// - [password]: The password for encrypting the private key. - /// - /// Example: - /// ```dart - /// final password = 'your_password'; - /// final privateKeySigner = PrivateKeySigner.createRandom(password); - /// ``` - factory PrivateKeySigner.createRandom(String password) { - final random = Random.secure(); - final privateKey = EthPrivateKey.createRandom(random); - final credential = Wallet.createNew(privateKey, password, random); - return PrivateKeySigner._internal(credential); - } - - /// Creates a PrivateKeySigner instance from JSON representation. - /// - /// Parameters: - /// - [source]: The JSON representation of the wallet. - /// - [password]: The password for decrypting the private key. - /// - /// Example: - /// ```dart - /// final sourceJson = '{"privateKey": "your_private_key_encrypted", ...}'; - /// final password = 'your_password'; - /// final privateKeySigner = PrivateKeySigner.fromJson(sourceJson, password); - /// ``` - factory PrivateKeySigner.fromJson(String source, String password) => - PrivateKeySigner._internal( - Wallet.fromJson(source, password), - ); - - PrivateKeySigner._internal(this._credential); - - /// Returns the Ethereum address associated with the PrivateKeySigner. - EthereumAddress get address => _credential.privateKey.address; - - /// Returns the public key associated with the PrivateKeySigner. - Uint8List get publicKey => _credential.privateKey.encodedPublicKey; - - @override - String getAddress({int index = 0, bytes}) { - return address.hex; - } - - @override - Future personalSign(Uint8List hash, - {int? index, String? id}) async { - return _credential.privateKey.signPersonalMessageToUint8List(hash); - } - - @override - Future signToEc(Uint8List hash, - {int? index, String? id}) async { - return _credential.privateKey.signToEcSignature(hash); - } - - String toJson() => _credential.toJson(); - - @override - SecureStorageMiddleware withSecureStorage(FlutterSecureStorage secureStorage, - {Authentication? authMiddleware}) { - return SecureStorageMiddleware( - secureStorage: secureStorage, - authMiddleware: authMiddleware, - credential: toJson()); - } - - /// Loads a PrivateKeySigner encrypted credentialJson from secure storage. - /// - /// Parameters: - /// - [storageMiddleware]: The repository for secure storage. - /// - [password]: The password for decrypting the private key. - /// - [options]: Additional options for the authentication operation. - /// - /// Example: - /// ```dart - /// final storageMiddleware = SecureStorageRepository(); // Initialize your storage middleware - /// final password = 'your_password'; - /// final privateKeySigner = await PrivateKeySigner.loadFromSecureStorage( - /// storageMiddleware: storageMiddleware, - /// password: password, - /// options: yourSSAuthOperationOptions, - /// ); - /// ``` - static Future loadFromSecureStorage( - {required SecureStorageRepository storageMiddleware, - required String password, - SSAuthOperationOptions? options}) { - return storageMiddleware - .readCredential(CredentialType.hdwallet, options: options) - .then((value) => - value != null ? PrivateKeySigner.fromJson(value, password) : null); - } -} diff --git a/lib/src/utils/chainbase_api.dart b/lib/src/utils/chainbase_api.dart deleted file mode 100644 index 8e6a88f..0000000 --- a/lib/src/utils/chainbase_api.dart +++ /dev/null @@ -1,416 +0,0 @@ -part of 'package:variance_dart/utils.dart'; - -class ChainBaseApi implements ChainBaseApiBase { - final RestClient _restClient; - final Chain _chain; - - ChainBaseApi({required RestClient restClient, required Chain chain}) - : _restClient = restClient, - _chain = chain; - - @override - Future getERC20TokenMarketPrice( - EthereumAddress tokenAddress) async { - return TokenPriceResponse.fromJson(await _restClient - .get>('/token/price', queryParameters: { - 'contract_address': tokenAddress.hex, - 'chain_id': _chain.chainId - })); - } - - @override - Future getNFTBalancesForAddress( - EthereumAddress address, { - EthereumAddress? tokenAddress, - int page = 1, - int pageSize = 20, - }) async { - return NFTBalancesResponse.fromJson(await _restClient - .get>('/account/nfts', queryParameters: { - 'address': address.hex, - 'chain_id': _chain.chainId, - if (tokenAddress != null) 'contract_address': tokenAddress.hex, - 'page': page, - 'limit': pageSize, - })); - } - - @override - Future getTokenBalancesForAddress( - EthereumAddress address, - {EthereumAddress? tokenAddress, - int page = 1, - int pageSize = 20}) async { - return TokenBalancesResponse.fromJson(await _restClient - .get>('/account/tokens', queryParameters: { - 'address': address.hex, - 'chain_id': _chain.chainId, - if (tokenAddress != null) 'contract_address': tokenAddress.hex, - 'page': page, - 'limit': pageSize, - })); - } - - @override - Future getTokenMetadata( - EthereumAddress tokenAddress, - ) async { - return TokenMetadataResponse.fromJson(await _restClient - .get>('/token/metadata', queryParameters: { - 'contract_address': tokenAddress.hex, - 'chain_id': _chain.chainId, - })); - } - - @override - Future getTokenTransfersForAddress( - EthereumAddress address, { - EthereumAddress? tokenAddress, - BlockNum? fromBlock, - BlockNum? toBlock, - DateTime? fromTime, - DateTime? toTime, - int page = 1, - int pageSize = 20, - }) async { - return TokenTransfersResponse.fromJson( - await _restClient - .get>('/token/transfers', queryParameters: { - 'address': address.hex, - 'chain_id': _chain.chainId, - if (tokenAddress != null) 'contract_address': tokenAddress.hex, - if (fromBlock != null) 'from_block': fromBlock.toBlockParam(), - if (toBlock != null) 'to_block': toBlock.toBlockParam(), - if (fromTime != null) - 'from_timestamp': fromTime.millisecondsSinceEpoch, - if (toTime != null) 'end_timestamp': toTime.millisecondsSinceEpoch, - 'page': page, - 'limit': pageSize, - }), - address.hex); - } - - @override - Future getTransactionsForAddress( - EthereumAddress address, { - EthereumAddress? tokenAddress, - BlockNum? fromBlock, - BlockNum? toBlock, - DateTime? fromTime, - DateTime? toTime, - int page = 1, - int pageSize = 20, - }) async { - return TransactionsResponse.fromJson(await _restClient - .get>('/account/txs', queryParameters: { - 'address': address.hex, - 'chain_id': _chain.chainId, - if (tokenAddress != null) 'contract_address': tokenAddress.hex, - if (fromBlock != null) 'from_block': fromBlock.toBlockParam(), - if (toBlock != null) 'to_block': toBlock.toBlockParam(), - if (fromTime != null) 'from_timestamp': fromTime.millisecondsSinceEpoch, - if (toTime != null) 'end_timestamp': toTime.millisecondsSinceEpoch, - 'page': page, - 'limit': pageSize, - })); - } - - @override - Future resolveENSName(String name, {BlockNum? toBlock}) async { - return ENSResponse.fromJson(await _restClient - .get>('/ens/records', queryParameters: { - 'domain': name, - 'chain_id': 1, - if (toBlock != null) 'to_block': toBlock.toBlockParam() - })); - } - - @override - Future reverseENSAddress(EthereumAddress address, - {BlockNum? toBlock}) async { - return ENSResponse.fromJson(await _restClient - .get>('/ens/reverse', queryParameters: { - 'address': address, - 'chain_id': 1, - if (toBlock != null) 'to_block': toBlock.toBlockParam() - })); - } -} - -/// An abstract class representing the base API for interacting with a blockchain. -/// -/// This class defines methods for retrieving various information related to -/// ERC-20 tokens, NFT balances, token balances, token transfers, transactions, -/// ENS name resolution, and reverse ENS address lookup. -abstract class ChainBaseApiBase { - /// Retrieves the market price of an ERC-20 token. - /// - /// Given the [tokenAddress], this method returns a [TokenPriceResponse] - /// containing information about the market price of the token. - Future getERC20TokenMarketPrice( - EthereumAddress tokenAddress); - - /// Retrieves NFT balances for a specific address. - /// - /// Given the [address], this method returns a [NFTBalancesResponse] with - /// information about NFT balances. Additional parameters like [tokenAddress], - /// [page], and [pageSize] can be specified for more targeted results. - Future getNFTBalancesForAddress( - EthereumAddress address, { - EthereumAddress? tokenAddress, - int page = 1, - int pageSize = 20, - }); - - /// Retrieves token balances for a specific address. - /// - /// Given the [address], this method returns a [TokenBalancesResponse] with - /// information about the token balances. Additional parameters like [tokenAddress], - /// [page], and [pageSize] can be specified for more targeted results. - Future getTokenBalancesForAddress( - EthereumAddress address, - {EthereumAddress? tokenAddress, - int page = 1, - int pageSize = 20}); - - /// Retrieves token metadata for a specific address. - /// - /// Given the [tokenAddress], this method returns a [TokenMetadataResponse] - /// with information about the token metadata. - Future getTokenMetadata( - EthereumAddress tokenAddress, - ); - - /// Retrieves token transfers for a specific address and token. - /// - /// Given the [address] and [tokenAddress], this method returns a - /// [TokenTransfersResponse] with information about token transfers. Additional - /// parameters like [fromBlock], [toBlock], [fromTime], [toTime], [page], and - /// [pageSize] can be specified for more targeted results. - Future getTokenTransfersForAddress( - EthereumAddress address, { - EthereumAddress? tokenAddress, - BlockNum? fromBlock, - BlockNum? toBlock, - DateTime? fromTime, - DateTime? toTime, - int page = 1, - int pageSize = 20, - }); - - /// Retrieves transactions for a specific address. - /// - /// Given the [address], this method returns a [TransactionsResponse] - /// containing information about transactions related to the address. Additional - /// parameters like [fromBlock], [toBlock], [fromTime], [toTime], [page], and - /// [pageSize] can be specified for more targeted results. - Future getTransactionsForAddress( - EthereumAddress address, { - EthereumAddress? tokenAddress, - BlockNum? fromBlock, - BlockNum? toBlock, - DateTime? fromTime, - DateTime? toTime, - int page = 1, - int pageSize = 20, - }); - - /// Resolves an ENS name to its corresponding Ethereum address. - /// - /// Given the [name], this method returns an [ENSResponse] containing - /// information about the Ethereum address associated with the ENS name. - Future resolveENSName(String name, {BlockNum? toBlock}); - - /// Performs a reverse ENS address lookup to obtain the associated ENS name. - /// - /// Given the [address], this method returns an [ENSResponse] with - /// information about the ENS name associated with the Ethereum address. - Future reverseENSAddress(EthereumAddress address, - {BlockNum? toBlock}); -} - -class ChainBaseResponse { - final int code; - final String message; - final int? nextPageNumber; - final int? count; - - ChainBaseResponse({ - required this.code, - required this.message, - this.nextPageNumber, - this.count, - }); -} - -class ENSResponse extends ChainBaseResponse { - final ENS? data; - ENSResponse({ - required super.code, - required super.message, - this.data, - super.nextPageNumber, - super.count, - }); - - factory ENSResponse.fromJson(Map json) { - return ENSResponse( - code: json['code'], - message: json['message'], - data: json['data'] != null - ? json['data'] is List - ? ENS.fromJson(json['data'][0]) - : ENS.fromJson(json['data']) - : null, - nextPageNumber: json['next_page'], - count: json['count'], - ); - } -} - -class NFTBalancesResponse extends ChainBaseResponse { - final List? data; - NFTBalancesResponse({ - required super.code, - required super.message, - this.data, - super.nextPageNumber, - super.count, - }); - - factory NFTBalancesResponse.fromJson(Map json) { - return NFTBalancesResponse( - code: json['code'], - message: json['message'], - data: json['data'] != null - ? List.from(json['data'].map((x) => NFT.fromJson(x))) - : null, - nextPageNumber: json['next_page'], - count: json['count'], - ); - } -} - -class TokenBalancesResponse extends ChainBaseResponse { - final List? data; - TokenBalancesResponse({ - required super.code, - required super.message, - this.data, - super.nextPageNumber, - super.count, - }); - - factory TokenBalancesResponse.fromJson(Map json) { - return TokenBalancesResponse( - code: json['code'], - message: json['message'], - data: json['data'] != null - ? List.from(json['data'].map((x) => Token.fromJson(x))) - : null, - nextPageNumber: json['next_page'], - count: json['count'], - ); - } -} - -class TokenMetadataResponse extends ChainBaseResponse { - final TokenMetadata? data; - - TokenMetadataResponse({ - required super.code, - required super.message, - this.data, - super.nextPageNumber, - super.count, - }); - - factory TokenMetadataResponse.fromJson(Map json) { - return TokenMetadataResponse( - code: json['code'], - message: json['message'], - data: json['data'] != null ? TokenMetadata.fromJson(json['data']) : null, - nextPageNumber: json['next_page'], - count: json['count'], - ); - } -} - -class TokenPriceResponse extends ChainBaseResponse { - final TokenPrice? data; - TokenPriceResponse({ - required super.code, - required super.message, - this.data, - super.nextPageNumber, - super.count, - }); - - factory TokenPriceResponse.fromJson(Map json) { - return TokenPriceResponse( - code: json['code'], - message: json['message'], - data: json['data'] != null ? TokenPrice.fromJson(json['data']) : null, - nextPageNumber: json['next_page'], - count: json['count'], - ); - } -} - -class TokenTransfersResponse extends ChainBaseResponse { - final List? data; - TokenTransfersResponse({ - required super.code, - required super.message, - this.data, - super.nextPageNumber, - super.count, - }); - - factory TokenTransfersResponse.fromJson( - Map json, String caller) { - Map getModifiedJson(Map json) { - if (json['from_address'].toLowerCase() == caller.toLowerCase()) { - json['direction'] = 'SEND'; - } else { - json['direction'] = 'RECEIVE'; - } - return json; - } - - return TokenTransfersResponse( - code: json['code'], - message: json['message'], - data: json['data'] != null - ? List.from(json['data'] - .map((x) => TokenTransfer.fromJson(getModifiedJson(x)))) - : null, - nextPageNumber: json['next_page'], - count: json['count'], - ); - } -} - -class TransactionsResponse extends ChainBaseResponse { - final List? data; - TransactionsResponse({ - required super.code, - required super.message, - this.data, - super.nextPageNumber, - super.count, - }); - - factory TransactionsResponse.fromJson(Map json) { - return TransactionsResponse( - code: json['code'], - message: json['message'], - data: json['data'] != null - ? List.from( - json['data'].map((x) => Transaction.fromJson(x))) - : null, - nextPageNumber: json['next_page'], - count: json['count'], - ); - } -} diff --git a/lib/src/utils/crypto.dart b/lib/src/utils/crypto.dart deleted file mode 100644 index cf1c969..0000000 --- a/lib/src/utils/crypto.dart +++ /dev/null @@ -1,173 +0,0 @@ -part of '../../utils.dart'; - -/// Converts a hex string to a 32bytes `Uint8List`. -/// -/// Parameters: -/// - [hexString]: The input hex string. -/// -/// Returns a Uint8List containing the converted bytes. -/// -/// Example: -/// ```dart -/// final hexString = '0x1a2b3c'; -/// final resultBytes = arrayify(hexString); -/// ``` -Uint8List arrayify(String hexString) { - hexString = hexString.replaceAll(RegExp(r'\s+'), ''); - List bytes = []; - for (int i = 0; i < hexString.length; i += 2) { - String byteHex = hexString.substring(i, i + 2); - int byteValue = int.parse(byteHex, radix: 16); - bytes.add(byteValue); - } - return Uint8List.fromList(bytes); -} - -/// Retrieves the X and Y components of an ECDSA public key from its bytes. -/// -/// Parameters: -/// - [publicKeyBytes]: The bytes of the ECDSA public key. -/// -/// Returns a Future containing a List of two strings representing the X and Y components of the public key. -/// -/// Example: -/// ```dart -/// final publicKeyBytes = Uint8List.fromList([4, 1, 2, 3]); // Replace with actual public key bytes -/// final components = await getPublicKeyFromBytes(publicKeyBytes); -/// print(components); // Output: ['01', '02'] -/// ``` -Future?> getPublicKeyFromBytes(Uint8List publicKeyBytes) async { - final pKey = - await EcdsaPublicKey.importSpkiKey(publicKeyBytes, EllipticCurve.p256); - final jwk = await pKey.exportJsonWebKey(); - if (jwk.containsKey('x') && jwk.containsKey('y')) { - final x = base64Url.normalize(jwk['x']); - final y = base64Url.normalize(jwk['y']); - - final decodedX = hexlify(base64Url.decode(x)); - final decodedY = hexlify(base64Url.decode(y)); - - return [decodedX, decodedY]; - } else { - throw "Invalid public key"; - } -} - -/// Converts a list of integers to a hexadecimal string. -/// -/// Parameters: -/// - [intArray]: The list of integers to be converted. -/// -/// Returns a string representing the hexadecimal value. -/// -/// Example: -/// ```dart -/// final intArray = [1, 15, 255]; -/// final hexString = hexlify(intArray); -/// print(hexString); // Output: '0x01ff' -/// ``` -String hexlify(List intArray) { - var ss = []; - for (int value in intArray) { - ss.add(value.toRadixString(16).padLeft(2, '0')); - } - return "0x${ss.join('')}"; -} - -/// Throws an exception if the specified requirement is not met. -/// -/// Parameters: -/// - [requirement]: The boolean requirement to be checked. -/// - [exception]: The exception message to be thrown if the requirement is not met. -/// -/// Throws an exception with the specified message if the requirement is not met. -/// -/// Example: -/// ```dart -/// final value = 42; -/// require(value > 0, "Value must be greater than 0"); -/// print("Value is valid: $value"); -/// ``` -require(bool requirement, String exception) { - if (!requirement) { - throw Exception(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: -/// - [input]: The list of integers representing the input data. -/// -/// Returns a [Digest] object representing the SHA-256 hash. -/// -/// Example: -/// ```dart -/// final data = utf8.encode("Hello, World!"); -/// final hash = sha256Hash(data); -/// print("SHA-256 Hash: ${hash.toString()}"); -/// ``` -Digest sha256Hash(List input) { - return sha256.convert(input); -} - -/// Checks whether the leading zero should be removed from the byte array. -/// -/// Parameters: -/// - [bytes]: The list of integers representing the byte array. -/// -/// Returns `true` if the leading zero should be removed, otherwise `false`. -/// -/// Example: -/// ```dart -/// final byteData = Uint8List.fromList([0x00, 0x01, 0x02, 0x03]); -/// final removeZero = shouldRemoveLeadingZero(byteData); -/// print("Remove Leading Zero: $removeZero"); -/// ``` -bool shouldRemoveLeadingZero(Uint8List bytes) { - return bytes[0] == 0x0 && (bytes[1] & (1 << 7)) != 0; -} - -/// Combines multiple lists of integers into a single list. -/// -/// Parameters: -/// - [buff]: List of lists of integers to be combined. -/// -/// Returns a new list containing all the integers from the input lists. -/// -/// Example: -/// ```dart -/// final list1 = [1, 2, 3]; -/// final list2 = [4, 5, 6]; -/// final combinedList = toBuffer([list1, list2]); -/// print("Combined List: $combinedList"); -/// ``` -List toBuffer(List> buff) { - return List.from(buff.expand((element) => element).toList()); -} diff --git a/lib/src/utils/dio_client.dart b/lib/src/utils/dio_client.dart deleted file mode 100644 index 3ff2c78..0000000 --- a/lib/src/utils/dio_client.dart +++ /dev/null @@ -1,90 +0,0 @@ -part of '../../utils.dart'; - -class DioClient implements RestClient { - final Dio _dio = Dio() - ..interceptors.add(DioCacheInterceptor( - options: CacheOptions( - store: MemCacheStore(maxSize: 10485760, maxEntrySize: 1048576), - policy: CachePolicy.request, - hitCacheOnErrorExcept: [401, 403], - maxStale: const Duration(days: 1), - ), - )); - - DioClient({BaseOptions? baseOptions}) { - _dio.options = baseOptions ?? - BaseOptions( - contentType: 'application/json', - connectTimeout: const Duration(seconds: 30), - receiveTimeout: const Duration(seconds: 30), - sendTimeout: const Duration(seconds: 60), - ); - } - - @override - Future get(String path, - {Object? body, - Map? queryParameters, - Options? options}) async { - try { - final response = await _dio.get( - path, - data: body, - queryParameters: queryParameters, - options: options, - ); - return response.data as T; - } on DioException catch (e) { - log("message: ${e.message}"); - rethrow; - } - } - - @override - Future post(String path, - {Object? body, - Map? queryParameters, - Options? options}) async { - try { - final response = await _dio.post(path, - data: body, queryParameters: queryParameters, options: options); - return response.data as T; - } on DioException catch (e) { - log("message: ${e.message}"); - rethrow; - } - } -} - -/// Rest client utility class using Dio for making HTTP requests over Rest Endpoints. -/// -/// The RestClient class provides methods to perform GET and POST requests with additional caching. -/// It utilizes the Dio library and includes error handling for DioException. -/// The RestClient class is mainly used by the ChainBaseApi class to make requests to the ChainBase API. -abstract class RestClient { - /// Performs a GET request to the provided API URL and returns the response. - /// - /// - [path]: The URL for the GET request. - /// - [body]: (optional) request body. - /// - [queryParameters]: (optional) The query parameters for the GET request. - /// - [options]: (optional) The options to be merged with the base options. - /// - /// Returns a [Future] that completes with the response data of type [T]. - /// - /// Throws a [DioException] if the request fails. - Future get(String path, - {Object? body, Map? queryParameters, Options? options}); - - /// Performs a POST request to the provided API URL with the given [body] and returns the response. - /// - /// - [path]: The URL for the POST request. - /// - [body]: (optional) The request body. - /// - [queryParameters]: (optional) The query parameters for the POST request. - /// - [options]: (optional) The options to be merged with the base options. - /// - /// Returns a [Future] that completes with the response data of type [T]. - /// - /// Throws a [DioException] if the request fails. - Future post(String path, - {Object? body, Map? queryParameters, Options? options}); -} diff --git a/lib/src/utils/local_authentication.dart b/lib/src/utils/local_authentication.dart deleted file mode 100644 index 211a396..0000000 --- a/lib/src/utils/local_authentication.dart +++ /dev/null @@ -1,83 +0,0 @@ -part of '../../utils.dart'; - -class AuthenticationError extends Error { - final String message; - - AuthenticationError(this.message); -} - -class AuthenticationMiddleware implements Authentication { - final LocalAuthentication _auth = LocalAuthentication(); - - @override - Future authenticate( - {required String localizedReason, - AndroidAuthMessages? androidAuthMessages, - IOSAuthMessages? iosAuthMessages, - bool useErrorDialogs = true, - bool stickyAuth = true}) async { - final bool canAuthenticate = await canAuthenticateWithBiometrics(); - - if (!canAuthenticate) { - throw AuthenticationError( - 'Unable to authenticate with biometrics: NO BIOMETRICS ENROLLED'); - } - - try { - final bool authenticated = await _auth.authenticate( - localizedReason: localizedReason, - options: AuthenticationOptions( - useErrorDialogs: useErrorDialogs, - stickyAuth: stickyAuth, - biometricOnly: true, - ), - authMessages: [ - androidAuthMessages ?? - const AndroidAuthMessages( - signInTitle: 'Authentication required!', - cancelButton: 'No thanks', - ), - iosAuthMessages ?? - const IOSAuthMessages( - cancelButton: 'No thanks', - ), - ]); - - if (!authenticated) { - throw AuthenticationError( - 'Unable to authenticate with biometrics: AUTHENTICATION FAILED'); - } - } on PlatformException catch (e) { - if (e.code == auth_error.notAvailable) { - throw AuthenticationError( - 'Unable to authenticate with biometrics: NOT AVAILABLE'); - } else if (e.code == auth_error.notEnrolled) { - throw AuthenticationError( - 'Unable to authenticate with biometrics: NOT ENROLLED'); - } else if (e.code == auth_error.lockedOut || - e.code == auth_error.permanentlyLockedOut) { - throw AuthenticationError( - 'Unable to authenticate with biometrics: LOCKED OUT'); - } else { - rethrow; - } - } - } - - @override - Future canAuthenticateWithBiometrics() async { - final bool canAuthenticateWithBiometrics = await _auth.canCheckBiometrics; - final bool canAuthenticate = - canAuthenticateWithBiometrics || await _auth.isDeviceSupported(); - - if (!canAuthenticate) { - throw AuthenticationError( - 'Unable to authenticate with biometrics: BIOMETRICS NOT SUPPORTED'); - } - - final List availableBiometrics = - await _auth.getAvailableBiometrics(); - - return availableBiometrics.isNotEmpty; - } -} diff --git a/lib/src/utils/models/ens.dart b/lib/src/utils/models/ens.dart deleted file mode 100644 index e36813c..0000000 --- a/lib/src/utils/models/ens.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'ens.freezed.dart'; -part 'ens.g.dart'; - -@freezed -class ENS with _$ENS { - const factory ENS({ - required String name, - required String address, - required String registrant, - required String owner, - required String resolver, - @JsonKey(name: 'registrant_time') required DateTime registrantTime, - @JsonKey(name: 'expiration_time') required DateTime expirationTime, - @JsonKey(name: 'token_id') required String tokenId, - @JsonKey(name: 'text_records') required dynamic textRecords, - }) = _ENS; - - factory ENS.fromJson(Map json) => _$ENSFromJson(json); -} diff --git a/lib/src/utils/models/ens.freezed.dart b/lib/src/utils/models/ens.freezed.dart deleted file mode 100644 index b061334..0000000 --- a/lib/src/utils/models/ens.freezed.dart +++ /dev/null @@ -1,335 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'ens.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); - -ENS _$ENSFromJson(Map json) { - return _ENS.fromJson(json); -} - -/// @nodoc -mixin _$ENS { - String get name => throw _privateConstructorUsedError; - String get address => throw _privateConstructorUsedError; - String get registrant => throw _privateConstructorUsedError; - String get owner => throw _privateConstructorUsedError; - String get resolver => throw _privateConstructorUsedError; - @JsonKey(name: 'registrant_time') - DateTime get registrantTime => throw _privateConstructorUsedError; - @JsonKey(name: 'expiration_time') - DateTime get expirationTime => throw _privateConstructorUsedError; - @JsonKey(name: 'token_id') - String get tokenId => throw _privateConstructorUsedError; - @JsonKey(name: 'text_records') - dynamic get textRecords => throw _privateConstructorUsedError; - - Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) - $ENSCopyWith get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $ENSCopyWith<$Res> { - factory $ENSCopyWith(ENS value, $Res Function(ENS) then) = - _$ENSCopyWithImpl<$Res, ENS>; - @useResult - $Res call( - {String name, - String address, - String registrant, - String owner, - String resolver, - @JsonKey(name: 'registrant_time') DateTime registrantTime, - @JsonKey(name: 'expiration_time') DateTime expirationTime, - @JsonKey(name: 'token_id') String tokenId, - @JsonKey(name: 'text_records') dynamic textRecords}); -} - -/// @nodoc -class _$ENSCopyWithImpl<$Res, $Val extends ENS> implements $ENSCopyWith<$Res> { - _$ENSCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? name = null, - Object? address = null, - Object? registrant = null, - Object? owner = null, - Object? resolver = null, - Object? registrantTime = null, - Object? expirationTime = null, - Object? tokenId = null, - Object? textRecords = freezed, - }) { - return _then(_value.copyWith( - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - address: null == address - ? _value.address - : address // ignore: cast_nullable_to_non_nullable - as String, - registrant: null == registrant - ? _value.registrant - : registrant // ignore: cast_nullable_to_non_nullable - as String, - owner: null == owner - ? _value.owner - : owner // ignore: cast_nullable_to_non_nullable - as String, - resolver: null == resolver - ? _value.resolver - : resolver // ignore: cast_nullable_to_non_nullable - as String, - registrantTime: null == registrantTime - ? _value.registrantTime - : registrantTime // ignore: cast_nullable_to_non_nullable - as DateTime, - expirationTime: null == expirationTime - ? _value.expirationTime - : expirationTime // ignore: cast_nullable_to_non_nullable - as DateTime, - tokenId: null == tokenId - ? _value.tokenId - : tokenId // ignore: cast_nullable_to_non_nullable - as String, - textRecords: freezed == textRecords - ? _value.textRecords - : textRecords // ignore: cast_nullable_to_non_nullable - as dynamic, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$ENSImplCopyWith<$Res> implements $ENSCopyWith<$Res> { - factory _$$ENSImplCopyWith(_$ENSImpl value, $Res Function(_$ENSImpl) then) = - __$$ENSImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String name, - String address, - String registrant, - String owner, - String resolver, - @JsonKey(name: 'registrant_time') DateTime registrantTime, - @JsonKey(name: 'expiration_time') DateTime expirationTime, - @JsonKey(name: 'token_id') String tokenId, - @JsonKey(name: 'text_records') dynamic textRecords}); -} - -/// @nodoc -class __$$ENSImplCopyWithImpl<$Res> extends _$ENSCopyWithImpl<$Res, _$ENSImpl> - implements _$$ENSImplCopyWith<$Res> { - __$$ENSImplCopyWithImpl(_$ENSImpl _value, $Res Function(_$ENSImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? name = null, - Object? address = null, - Object? registrant = null, - Object? owner = null, - Object? resolver = null, - Object? registrantTime = null, - Object? expirationTime = null, - Object? tokenId = null, - Object? textRecords = freezed, - }) { - return _then(_$ENSImpl( - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - address: null == address - ? _value.address - : address // ignore: cast_nullable_to_non_nullable - as String, - registrant: null == registrant - ? _value.registrant - : registrant // ignore: cast_nullable_to_non_nullable - as String, - owner: null == owner - ? _value.owner - : owner // ignore: cast_nullable_to_non_nullable - as String, - resolver: null == resolver - ? _value.resolver - : resolver // ignore: cast_nullable_to_non_nullable - as String, - registrantTime: null == registrantTime - ? _value.registrantTime - : registrantTime // ignore: cast_nullable_to_non_nullable - as DateTime, - expirationTime: null == expirationTime - ? _value.expirationTime - : expirationTime // ignore: cast_nullable_to_non_nullable - as DateTime, - tokenId: null == tokenId - ? _value.tokenId - : tokenId // ignore: cast_nullable_to_non_nullable - as String, - textRecords: freezed == textRecords - ? _value.textRecords - : textRecords // ignore: cast_nullable_to_non_nullable - as dynamic, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$ENSImpl implements _ENS { - const _$ENSImpl( - {required this.name, - required this.address, - required this.registrant, - required this.owner, - required this.resolver, - @JsonKey(name: 'registrant_time') required this.registrantTime, - @JsonKey(name: 'expiration_time') required this.expirationTime, - @JsonKey(name: 'token_id') required this.tokenId, - @JsonKey(name: 'text_records') required this.textRecords}); - - factory _$ENSImpl.fromJson(Map json) => - _$$ENSImplFromJson(json); - - @override - final String name; - @override - final String address; - @override - final String registrant; - @override - final String owner; - @override - final String resolver; - @override - @JsonKey(name: 'registrant_time') - final DateTime registrantTime; - @override - @JsonKey(name: 'expiration_time') - final DateTime expirationTime; - @override - @JsonKey(name: 'token_id') - final String tokenId; - @override - @JsonKey(name: 'text_records') - final dynamic textRecords; - - @override - String toString() { - return 'ENS(name: $name, address: $address, registrant: $registrant, owner: $owner, resolver: $resolver, registrantTime: $registrantTime, expirationTime: $expirationTime, tokenId: $tokenId, textRecords: $textRecords)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$ENSImpl && - (identical(other.name, name) || other.name == name) && - (identical(other.address, address) || other.address == address) && - (identical(other.registrant, registrant) || - other.registrant == registrant) && - (identical(other.owner, owner) || other.owner == owner) && - (identical(other.resolver, resolver) || - other.resolver == resolver) && - (identical(other.registrantTime, registrantTime) || - other.registrantTime == registrantTime) && - (identical(other.expirationTime, expirationTime) || - other.expirationTime == expirationTime) && - (identical(other.tokenId, tokenId) || other.tokenId == tokenId) && - const DeepCollectionEquality() - .equals(other.textRecords, textRecords)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => Object.hash( - runtimeType, - name, - address, - registrant, - owner, - resolver, - registrantTime, - expirationTime, - tokenId, - const DeepCollectionEquality().hash(textRecords)); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$ENSImplCopyWith<_$ENSImpl> get copyWith => - __$$ENSImplCopyWithImpl<_$ENSImpl>(this, _$identity); - - @override - Map toJson() { - return _$$ENSImplToJson( - this, - ); - } -} - -abstract class _ENS implements ENS { - const factory _ENS( - {required final String name, - required final String address, - required final String registrant, - required final String owner, - required final String resolver, - @JsonKey(name: 'registrant_time') required final DateTime registrantTime, - @JsonKey(name: 'expiration_time') required final DateTime expirationTime, - @JsonKey(name: 'token_id') required final String tokenId, - @JsonKey(name: 'text_records') - required final dynamic textRecords}) = _$ENSImpl; - - factory _ENS.fromJson(Map json) = _$ENSImpl.fromJson; - - @override - String get name; - @override - String get address; - @override - String get registrant; - @override - String get owner; - @override - String get resolver; - @override - @JsonKey(name: 'registrant_time') - DateTime get registrantTime; - @override - @JsonKey(name: 'expiration_time') - DateTime get expirationTime; - @override - @JsonKey(name: 'token_id') - String get tokenId; - @override - @JsonKey(name: 'text_records') - dynamic get textRecords; - @override - @JsonKey(ignore: true) - _$$ENSImplCopyWith<_$ENSImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/src/utils/models/ens.g.dart b/lib/src/utils/models/ens.g.dart deleted file mode 100644 index b027b84..0000000 --- a/lib/src/utils/models/ens.g.dart +++ /dev/null @@ -1,31 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'ens.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$ENSImpl _$$ENSImplFromJson(Map json) => _$ENSImpl( - name: json['name'] as String, - address: json['address'] as String, - registrant: json['registrant'] as String, - owner: json['owner'] as String, - resolver: json['resolver'] as String, - registrantTime: DateTime.parse(json['registrant_time'] as String), - expirationTime: DateTime.parse(json['expiration_time'] as String), - tokenId: json['token_id'] as String, - textRecords: json['text_records'], - ); - -Map _$$ENSImplToJson(_$ENSImpl instance) => { - 'name': instance.name, - 'address': instance.address, - 'registrant': instance.registrant, - 'owner': instance.owner, - 'resolver': instance.resolver, - 'registrant_time': instance.registrantTime.toIso8601String(), - 'expiration_time': instance.expirationTime.toIso8601String(), - 'token_id': instance.tokenId, - 'text_records': instance.textRecords, - }; diff --git a/lib/src/utils/models/metadata.dart b/lib/src/utils/models/metadata.dart deleted file mode 100644 index f07005d..0000000 --- a/lib/src/utils/models/metadata.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:variance_dart/utils.dart' - show BigIntConverter, TokenLogo, TokenUrl; -import 'package:variance_dart/variance.dart' show Uint256; - -part 'metadata.freezed.dart'; -part 'metadata.g.dart'; - -@freezed -class TokenMetadata with _$TokenMetadata { - const factory TokenMetadata({ - @JsonKey(name: 'contract_address') required String contractAddress, - required num decimals, - required String name, - required String symbol, - @BigIntConverter() - @JsonKey(name: 'total_supply') - required Uint256 totalSupply, - required List? logos, - required List? urls, - @JsonKey(name: 'current_usd_price') required num? currentUsdPrice, - }) = _TokenMetadata; - - factory TokenMetadata.fromJson(Map json) => - _$TokenMetadataFromJson(json); -} diff --git a/lib/src/utils/models/metadata.freezed.dart b/lib/src/utils/models/metadata.freezed.dart deleted file mode 100644 index 160e5c3..0000000 --- a/lib/src/utils/models/metadata.freezed.dart +++ /dev/null @@ -1,343 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'metadata.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); - -TokenMetadata _$TokenMetadataFromJson(Map json) { - return _TokenMetadata.fromJson(json); -} - -/// @nodoc -mixin _$TokenMetadata { - @JsonKey(name: 'contract_address') - String get contractAddress => throw _privateConstructorUsedError; - num get decimals => throw _privateConstructorUsedError; - String get name => throw _privateConstructorUsedError; - String get symbol => throw _privateConstructorUsedError; - @BigIntConverter() - @JsonKey(name: 'total_supply') - Uint256 get totalSupply => throw _privateConstructorUsedError; - List? get logos => throw _privateConstructorUsedError; - List? get urls => throw _privateConstructorUsedError; - @JsonKey(name: 'current_usd_price') - num? get currentUsdPrice => throw _privateConstructorUsedError; - - Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) - $TokenMetadataCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $TokenMetadataCopyWith<$Res> { - factory $TokenMetadataCopyWith( - TokenMetadata value, $Res Function(TokenMetadata) then) = - _$TokenMetadataCopyWithImpl<$Res, TokenMetadata>; - @useResult - $Res call( - {@JsonKey(name: 'contract_address') String contractAddress, - num decimals, - String name, - String symbol, - @BigIntConverter() @JsonKey(name: 'total_supply') Uint256 totalSupply, - List? logos, - List? urls, - @JsonKey(name: 'current_usd_price') num? currentUsdPrice}); -} - -/// @nodoc -class _$TokenMetadataCopyWithImpl<$Res, $Val extends TokenMetadata> - implements $TokenMetadataCopyWith<$Res> { - _$TokenMetadataCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? contractAddress = null, - Object? decimals = null, - Object? name = null, - Object? symbol = null, - Object? totalSupply = null, - Object? logos = freezed, - Object? urls = freezed, - Object? currentUsdPrice = freezed, - }) { - return _then(_value.copyWith( - contractAddress: null == contractAddress - ? _value.contractAddress - : contractAddress // ignore: cast_nullable_to_non_nullable - as String, - decimals: null == decimals - ? _value.decimals - : decimals // ignore: cast_nullable_to_non_nullable - as num, - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - symbol: null == symbol - ? _value.symbol - : symbol // ignore: cast_nullable_to_non_nullable - as String, - totalSupply: null == totalSupply - ? _value.totalSupply - : totalSupply // ignore: cast_nullable_to_non_nullable - as Uint256, - logos: freezed == logos - ? _value.logos - : logos // ignore: cast_nullable_to_non_nullable - as List?, - urls: freezed == urls - ? _value.urls - : urls // ignore: cast_nullable_to_non_nullable - as List?, - currentUsdPrice: freezed == currentUsdPrice - ? _value.currentUsdPrice - : currentUsdPrice // ignore: cast_nullable_to_non_nullable - as num?, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$TokenMetadataImplCopyWith<$Res> - implements $TokenMetadataCopyWith<$Res> { - factory _$$TokenMetadataImplCopyWith( - _$TokenMetadataImpl value, $Res Function(_$TokenMetadataImpl) then) = - __$$TokenMetadataImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {@JsonKey(name: 'contract_address') String contractAddress, - num decimals, - String name, - String symbol, - @BigIntConverter() @JsonKey(name: 'total_supply') Uint256 totalSupply, - List? logos, - List? urls, - @JsonKey(name: 'current_usd_price') num? currentUsdPrice}); -} - -/// @nodoc -class __$$TokenMetadataImplCopyWithImpl<$Res> - extends _$TokenMetadataCopyWithImpl<$Res, _$TokenMetadataImpl> - implements _$$TokenMetadataImplCopyWith<$Res> { - __$$TokenMetadataImplCopyWithImpl( - _$TokenMetadataImpl _value, $Res Function(_$TokenMetadataImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? contractAddress = null, - Object? decimals = null, - Object? name = null, - Object? symbol = null, - Object? totalSupply = null, - Object? logos = freezed, - Object? urls = freezed, - Object? currentUsdPrice = freezed, - }) { - return _then(_$TokenMetadataImpl( - contractAddress: null == contractAddress - ? _value.contractAddress - : contractAddress // ignore: cast_nullable_to_non_nullable - as String, - decimals: null == decimals - ? _value.decimals - : decimals // ignore: cast_nullable_to_non_nullable - as num, - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - symbol: null == symbol - ? _value.symbol - : symbol // ignore: cast_nullable_to_non_nullable - as String, - totalSupply: null == totalSupply - ? _value.totalSupply - : totalSupply // ignore: cast_nullable_to_non_nullable - as Uint256, - logos: freezed == logos - ? _value._logos - : logos // ignore: cast_nullable_to_non_nullable - as List?, - urls: freezed == urls - ? _value._urls - : urls // ignore: cast_nullable_to_non_nullable - as List?, - currentUsdPrice: freezed == currentUsdPrice - ? _value.currentUsdPrice - : currentUsdPrice // ignore: cast_nullable_to_non_nullable - as num?, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$TokenMetadataImpl implements _TokenMetadata { - const _$TokenMetadataImpl( - {@JsonKey(name: 'contract_address') required this.contractAddress, - required this.decimals, - required this.name, - required this.symbol, - @BigIntConverter() - @JsonKey(name: 'total_supply') - required this.totalSupply, - required final List? logos, - required final List? urls, - @JsonKey(name: 'current_usd_price') required this.currentUsdPrice}) - : _logos = logos, - _urls = urls; - - factory _$TokenMetadataImpl.fromJson(Map json) => - _$$TokenMetadataImplFromJson(json); - - @override - @JsonKey(name: 'contract_address') - final String contractAddress; - @override - final num decimals; - @override - final String name; - @override - final String symbol; - @override - @BigIntConverter() - @JsonKey(name: 'total_supply') - final Uint256 totalSupply; - final List? _logos; - @override - List? get logos { - final value = _logos; - if (value == null) return null; - if (_logos is EqualUnmodifiableListView) return _logos; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(value); - } - - final List? _urls; - @override - List? get urls { - final value = _urls; - if (value == null) return null; - if (_urls is EqualUnmodifiableListView) return _urls; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(value); - } - - @override - @JsonKey(name: 'current_usd_price') - final num? currentUsdPrice; - - @override - String toString() { - return 'TokenMetadata(contractAddress: $contractAddress, decimals: $decimals, name: $name, symbol: $symbol, totalSupply: $totalSupply, logos: $logos, urls: $urls, currentUsdPrice: $currentUsdPrice)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$TokenMetadataImpl && - (identical(other.contractAddress, contractAddress) || - other.contractAddress == contractAddress) && - (identical(other.decimals, decimals) || - other.decimals == decimals) && - (identical(other.name, name) || other.name == name) && - (identical(other.symbol, symbol) || other.symbol == symbol) && - (identical(other.totalSupply, totalSupply) || - other.totalSupply == totalSupply) && - const DeepCollectionEquality().equals(other._logos, _logos) && - const DeepCollectionEquality().equals(other._urls, _urls) && - (identical(other.currentUsdPrice, currentUsdPrice) || - other.currentUsdPrice == currentUsdPrice)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => Object.hash( - runtimeType, - contractAddress, - decimals, - name, - symbol, - totalSupply, - const DeepCollectionEquality().hash(_logos), - const DeepCollectionEquality().hash(_urls), - currentUsdPrice); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$TokenMetadataImplCopyWith<_$TokenMetadataImpl> get copyWith => - __$$TokenMetadataImplCopyWithImpl<_$TokenMetadataImpl>(this, _$identity); - - @override - Map toJson() { - return _$$TokenMetadataImplToJson( - this, - ); - } -} - -abstract class _TokenMetadata implements TokenMetadata { - const factory _TokenMetadata( - {@JsonKey(name: 'contract_address') required final String contractAddress, - required final num decimals, - required final String name, - required final String symbol, - @BigIntConverter() - @JsonKey(name: 'total_supply') - required final Uint256 totalSupply, - required final List? logos, - required final List? urls, - @JsonKey(name: 'current_usd_price') - required final num? currentUsdPrice}) = _$TokenMetadataImpl; - - factory _TokenMetadata.fromJson(Map json) = - _$TokenMetadataImpl.fromJson; - - @override - @JsonKey(name: 'contract_address') - String get contractAddress; - @override - num get decimals; - @override - String get name; - @override - String get symbol; - @override - @BigIntConverter() - @JsonKey(name: 'total_supply') - Uint256 get totalSupply; - @override - List? get logos; - @override - List? get urls; - @override - @JsonKey(name: 'current_usd_price') - num? get currentUsdPrice; - @override - @JsonKey(ignore: true) - _$$TokenMetadataImplCopyWith<_$TokenMetadataImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/src/utils/models/metadata.g.dart b/lib/src/utils/models/metadata.g.dart deleted file mode 100644 index d119b29..0000000 --- a/lib/src/utils/models/metadata.g.dart +++ /dev/null @@ -1,35 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'metadata.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$TokenMetadataImpl _$$TokenMetadataImplFromJson(Map json) => - _$TokenMetadataImpl( - contractAddress: json['contract_address'] as String, - decimals: json['decimals'] as num, - name: json['name'] as String, - symbol: json['symbol'] as String, - totalSupply: const BigIntConverter().fromJson(json['total_supply']), - logos: (json['logos'] as List?) - ?.map((e) => TokenLogo.fromJson(e as Map)) - .toList(), - urls: (json['urls'] as List?) - ?.map((e) => TokenUrl.fromJson(e as Map)) - .toList(), - currentUsdPrice: json['current_usd_price'] as num?, - ); - -Map _$$TokenMetadataImplToJson(_$TokenMetadataImpl instance) => - { - 'contract_address': instance.contractAddress, - 'decimals': instance.decimals, - 'name': instance.name, - 'symbol': instance.symbol, - 'total_supply': const BigIntConverter().toJson(instance.totalSupply), - 'logos': instance.logos, - 'urls': instance.urls, - 'current_usd_price': instance.currentUsdPrice, - }; diff --git a/lib/src/utils/models/nft.dart b/lib/src/utils/models/nft.dart deleted file mode 100644 index 377331a..0000000 --- a/lib/src/utils/models/nft.dart +++ /dev/null @@ -1,138 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'nft.freezed.dart'; -part 'nft.g.dart'; - -@freezed -class NFT with _$NFT { - const factory NFT({ - @JsonKey(name: 'contract_address') required String contractAddress, - @JsonKey(name: 'erc_type') required String? ercType, - @JsonKey(name: 'floor_prices') required List? floorPrices, - @JsonKey(name: 'image_uri') required String? imageUri, - required NFTMetadata? metadata, - @JsonKey(name: 'mint_time') required DateTime mintTime, - @JsonKey(name: 'mint_transaction_hash') required String mintTransactionHash, - required String? name, - required String? owner, - @JsonKey(name: 'rarity_rank') required num? rarityRank, - @JsonKey(name: 'rarity_score') required num? rarityScore, - required String symbol, - @JsonKey(name: 'token_id') required String tokenId, - @JsonKey(name: 'token_uri') required String? tokenUri, - required num? total, - @JsonKey(name: 'total_string') required String? totalString, - required List? traits, - }) = _NFT; - - factory NFT.fromJson(Map json) => _$NFTFromJson(json); -} - -class NFTMetadata { - NFTMetadata({ - required this.attributes, - required this.backgroundImage, - required this.description, - required this.image, - required this.imageUrl, - required this.isNormalized, - required this.name, - required this.nameLength, - required this.segmentLength, - required this.url, - required this.version, - }); - - factory NFTMetadata.fromJson(Map json) => NFTMetadata( - attributes: json['attributes'] != null - ? List.from( - json['attributes'].map((x) => NFTAttributes.fromJson(x))) - : null, - backgroundImage: json['background_image'], - description: json['description'], - image: json['image'], - imageUrl: json['image_url'], - isNormalized: json['is_normalized'], - name: json['name'], - nameLength: json['name_length'], - segmentLength: json['segment_length'], - url: json['url'], - version: json['version'], - ); - - final List? attributes; - final String? backgroundImage; - final String? description; - final String? image; - final String? imageUrl; - final bool? isNormalized; - final String? name; - final num? nameLength; - final num? segmentLength; - final String? url; - final num? version; - - Map toJson() => { - 'attributes': attributes != null - ? attributes!.map((x) => x.toJson()).toList() - : null, - 'background_image': backgroundImage, - 'description': description, - 'image': image, - 'image_url': imageUrl, - 'is_normalized': isNormalized, - 'name': name, - 'name_length': nameLength, - 'segment_length': segmentLength, - 'url': url, - 'version': version, - }; -} - -class NFTAttributes { - NFTAttributes({ - required this.displayType, - required this.traitType, - required this.value, - }); - - factory NFTAttributes.fromJson(Map json) => NFTAttributes( - displayType: json['display_type'], - traitType: json['trait_type'], - value: json['value'], - ); - - final String? displayType; - final String? traitType; - final dynamic value; - - Map toJson() => { - 'display_type': displayType, - 'trait_type': traitType, - 'value': value, - }; -} - -class NFTFloorPrice { - NFTFloorPrice({ - required this.address, - required this.symbol, - required this.value, - }); - - factory NFTFloorPrice.fromJson(Map json) => NFTFloorPrice( - address: json['address'], - symbol: json['symbol'], - value: json['value'], - ); - - final String address; - final String symbol; - final String value; - - Map toJson() => { - 'address': address, - 'symbol': symbol, - 'value': value, - }; -} diff --git a/lib/src/utils/models/nft.freezed.dart b/lib/src/utils/models/nft.freezed.dart deleted file mode 100644 index 4163272..0000000 --- a/lib/src/utils/models/nft.freezed.dart +++ /dev/null @@ -1,548 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'nft.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); - -NFT _$NFTFromJson(Map json) { - return _NFT.fromJson(json); -} - -/// @nodoc -mixin _$NFT { - @JsonKey(name: 'contract_address') - String get contractAddress => throw _privateConstructorUsedError; - @JsonKey(name: 'erc_type') - String? get ercType => throw _privateConstructorUsedError; - @JsonKey(name: 'floor_prices') - List? get floorPrices => throw _privateConstructorUsedError; - @JsonKey(name: 'image_uri') - String? get imageUri => throw _privateConstructorUsedError; - NFTMetadata? get metadata => throw _privateConstructorUsedError; - @JsonKey(name: 'mint_time') - DateTime get mintTime => throw _privateConstructorUsedError; - @JsonKey(name: 'mint_transaction_hash') - String get mintTransactionHash => throw _privateConstructorUsedError; - String? get name => throw _privateConstructorUsedError; - String? get owner => throw _privateConstructorUsedError; - @JsonKey(name: 'rarity_rank') - num? get rarityRank => throw _privateConstructorUsedError; - @JsonKey(name: 'rarity_score') - num? get rarityScore => throw _privateConstructorUsedError; - String get symbol => throw _privateConstructorUsedError; - @JsonKey(name: 'token_id') - String get tokenId => throw _privateConstructorUsedError; - @JsonKey(name: 'token_uri') - String? get tokenUri => throw _privateConstructorUsedError; - num? get total => throw _privateConstructorUsedError; - @JsonKey(name: 'total_string') - String? get totalString => throw _privateConstructorUsedError; - List? get traits => throw _privateConstructorUsedError; - - Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) - $NFTCopyWith get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $NFTCopyWith<$Res> { - factory $NFTCopyWith(NFT value, $Res Function(NFT) then) = - _$NFTCopyWithImpl<$Res, NFT>; - @useResult - $Res call( - {@JsonKey(name: 'contract_address') String contractAddress, - @JsonKey(name: 'erc_type') String? ercType, - @JsonKey(name: 'floor_prices') List? floorPrices, - @JsonKey(name: 'image_uri') String? imageUri, - NFTMetadata? metadata, - @JsonKey(name: 'mint_time') DateTime mintTime, - @JsonKey(name: 'mint_transaction_hash') String mintTransactionHash, - String? name, - String? owner, - @JsonKey(name: 'rarity_rank') num? rarityRank, - @JsonKey(name: 'rarity_score') num? rarityScore, - String symbol, - @JsonKey(name: 'token_id') String tokenId, - @JsonKey(name: 'token_uri') String? tokenUri, - num? total, - @JsonKey(name: 'total_string') String? totalString, - List? traits}); -} - -/// @nodoc -class _$NFTCopyWithImpl<$Res, $Val extends NFT> implements $NFTCopyWith<$Res> { - _$NFTCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? contractAddress = null, - Object? ercType = freezed, - Object? floorPrices = freezed, - Object? imageUri = freezed, - Object? metadata = freezed, - Object? mintTime = null, - Object? mintTransactionHash = null, - Object? name = freezed, - Object? owner = freezed, - Object? rarityRank = freezed, - Object? rarityScore = freezed, - Object? symbol = null, - Object? tokenId = null, - Object? tokenUri = freezed, - Object? total = freezed, - Object? totalString = freezed, - Object? traits = freezed, - }) { - return _then(_value.copyWith( - contractAddress: null == contractAddress - ? _value.contractAddress - : contractAddress // ignore: cast_nullable_to_non_nullable - as String, - ercType: freezed == ercType - ? _value.ercType - : ercType // ignore: cast_nullable_to_non_nullable - as String?, - floorPrices: freezed == floorPrices - ? _value.floorPrices - : floorPrices // ignore: cast_nullable_to_non_nullable - as List?, - imageUri: freezed == imageUri - ? _value.imageUri - : imageUri // ignore: cast_nullable_to_non_nullable - as String?, - metadata: freezed == metadata - ? _value.metadata - : metadata // ignore: cast_nullable_to_non_nullable - as NFTMetadata?, - mintTime: null == mintTime - ? _value.mintTime - : mintTime // ignore: cast_nullable_to_non_nullable - as DateTime, - mintTransactionHash: null == mintTransactionHash - ? _value.mintTransactionHash - : mintTransactionHash // ignore: cast_nullable_to_non_nullable - as String, - name: freezed == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String?, - owner: freezed == owner - ? _value.owner - : owner // ignore: cast_nullable_to_non_nullable - as String?, - rarityRank: freezed == rarityRank - ? _value.rarityRank - : rarityRank // ignore: cast_nullable_to_non_nullable - as num?, - rarityScore: freezed == rarityScore - ? _value.rarityScore - : rarityScore // ignore: cast_nullable_to_non_nullable - as num?, - symbol: null == symbol - ? _value.symbol - : symbol // ignore: cast_nullable_to_non_nullable - as String, - tokenId: null == tokenId - ? _value.tokenId - : tokenId // ignore: cast_nullable_to_non_nullable - as String, - tokenUri: freezed == tokenUri - ? _value.tokenUri - : tokenUri // ignore: cast_nullable_to_non_nullable - as String?, - total: freezed == total - ? _value.total - : total // ignore: cast_nullable_to_non_nullable - as num?, - totalString: freezed == totalString - ? _value.totalString - : totalString // ignore: cast_nullable_to_non_nullable - as String?, - traits: freezed == traits - ? _value.traits - : traits // ignore: cast_nullable_to_non_nullable - as List?, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$NFTImplCopyWith<$Res> implements $NFTCopyWith<$Res> { - factory _$$NFTImplCopyWith(_$NFTImpl value, $Res Function(_$NFTImpl) then) = - __$$NFTImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {@JsonKey(name: 'contract_address') String contractAddress, - @JsonKey(name: 'erc_type') String? ercType, - @JsonKey(name: 'floor_prices') List? floorPrices, - @JsonKey(name: 'image_uri') String? imageUri, - NFTMetadata? metadata, - @JsonKey(name: 'mint_time') DateTime mintTime, - @JsonKey(name: 'mint_transaction_hash') String mintTransactionHash, - String? name, - String? owner, - @JsonKey(name: 'rarity_rank') num? rarityRank, - @JsonKey(name: 'rarity_score') num? rarityScore, - String symbol, - @JsonKey(name: 'token_id') String tokenId, - @JsonKey(name: 'token_uri') String? tokenUri, - num? total, - @JsonKey(name: 'total_string') String? totalString, - List? traits}); -} - -/// @nodoc -class __$$NFTImplCopyWithImpl<$Res> extends _$NFTCopyWithImpl<$Res, _$NFTImpl> - implements _$$NFTImplCopyWith<$Res> { - __$$NFTImplCopyWithImpl(_$NFTImpl _value, $Res Function(_$NFTImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? contractAddress = null, - Object? ercType = freezed, - Object? floorPrices = freezed, - Object? imageUri = freezed, - Object? metadata = freezed, - Object? mintTime = null, - Object? mintTransactionHash = null, - Object? name = freezed, - Object? owner = freezed, - Object? rarityRank = freezed, - Object? rarityScore = freezed, - Object? symbol = null, - Object? tokenId = null, - Object? tokenUri = freezed, - Object? total = freezed, - Object? totalString = freezed, - Object? traits = freezed, - }) { - return _then(_$NFTImpl( - contractAddress: null == contractAddress - ? _value.contractAddress - : contractAddress // ignore: cast_nullable_to_non_nullable - as String, - ercType: freezed == ercType - ? _value.ercType - : ercType // ignore: cast_nullable_to_non_nullable - as String?, - floorPrices: freezed == floorPrices - ? _value._floorPrices - : floorPrices // ignore: cast_nullable_to_non_nullable - as List?, - imageUri: freezed == imageUri - ? _value.imageUri - : imageUri // ignore: cast_nullable_to_non_nullable - as String?, - metadata: freezed == metadata - ? _value.metadata - : metadata // ignore: cast_nullable_to_non_nullable - as NFTMetadata?, - mintTime: null == mintTime - ? _value.mintTime - : mintTime // ignore: cast_nullable_to_non_nullable - as DateTime, - mintTransactionHash: null == mintTransactionHash - ? _value.mintTransactionHash - : mintTransactionHash // ignore: cast_nullable_to_non_nullable - as String, - name: freezed == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String?, - owner: freezed == owner - ? _value.owner - : owner // ignore: cast_nullable_to_non_nullable - as String?, - rarityRank: freezed == rarityRank - ? _value.rarityRank - : rarityRank // ignore: cast_nullable_to_non_nullable - as num?, - rarityScore: freezed == rarityScore - ? _value.rarityScore - : rarityScore // ignore: cast_nullable_to_non_nullable - as num?, - symbol: null == symbol - ? _value.symbol - : symbol // ignore: cast_nullable_to_non_nullable - as String, - tokenId: null == tokenId - ? _value.tokenId - : tokenId // ignore: cast_nullable_to_non_nullable - as String, - tokenUri: freezed == tokenUri - ? _value.tokenUri - : tokenUri // ignore: cast_nullable_to_non_nullable - as String?, - total: freezed == total - ? _value.total - : total // ignore: cast_nullable_to_non_nullable - as num?, - totalString: freezed == totalString - ? _value.totalString - : totalString // ignore: cast_nullable_to_non_nullable - as String?, - traits: freezed == traits - ? _value._traits - : traits // ignore: cast_nullable_to_non_nullable - as List?, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$NFTImpl implements _NFT { - const _$NFTImpl( - {@JsonKey(name: 'contract_address') required this.contractAddress, - @JsonKey(name: 'erc_type') required this.ercType, - @JsonKey(name: 'floor_prices') - required final List? floorPrices, - @JsonKey(name: 'image_uri') required this.imageUri, - required this.metadata, - @JsonKey(name: 'mint_time') required this.mintTime, - @JsonKey(name: 'mint_transaction_hash') required this.mintTransactionHash, - required this.name, - required this.owner, - @JsonKey(name: 'rarity_rank') required this.rarityRank, - @JsonKey(name: 'rarity_score') required this.rarityScore, - required this.symbol, - @JsonKey(name: 'token_id') required this.tokenId, - @JsonKey(name: 'token_uri') required this.tokenUri, - required this.total, - @JsonKey(name: 'total_string') required this.totalString, - required final List? traits}) - : _floorPrices = floorPrices, - _traits = traits; - - factory _$NFTImpl.fromJson(Map json) => - _$$NFTImplFromJson(json); - - @override - @JsonKey(name: 'contract_address') - final String contractAddress; - @override - @JsonKey(name: 'erc_type') - final String? ercType; - final List? _floorPrices; - @override - @JsonKey(name: 'floor_prices') - List? get floorPrices { - final value = _floorPrices; - if (value == null) return null; - if (_floorPrices is EqualUnmodifiableListView) return _floorPrices; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(value); - } - - @override - @JsonKey(name: 'image_uri') - final String? imageUri; - @override - final NFTMetadata? metadata; - @override - @JsonKey(name: 'mint_time') - final DateTime mintTime; - @override - @JsonKey(name: 'mint_transaction_hash') - final String mintTransactionHash; - @override - final String? name; - @override - final String? owner; - @override - @JsonKey(name: 'rarity_rank') - final num? rarityRank; - @override - @JsonKey(name: 'rarity_score') - final num? rarityScore; - @override - final String symbol; - @override - @JsonKey(name: 'token_id') - final String tokenId; - @override - @JsonKey(name: 'token_uri') - final String? tokenUri; - @override - final num? total; - @override - @JsonKey(name: 'total_string') - final String? totalString; - final List? _traits; - @override - List? get traits { - final value = _traits; - if (value == null) return null; - if (_traits is EqualUnmodifiableListView) return _traits; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(value); - } - - @override - String toString() { - return 'NFT(contractAddress: $contractAddress, ercType: $ercType, floorPrices: $floorPrices, imageUri: $imageUri, metadata: $metadata, mintTime: $mintTime, mintTransactionHash: $mintTransactionHash, name: $name, owner: $owner, rarityRank: $rarityRank, rarityScore: $rarityScore, symbol: $symbol, tokenId: $tokenId, tokenUri: $tokenUri, total: $total, totalString: $totalString, traits: $traits)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$NFTImpl && - (identical(other.contractAddress, contractAddress) || - other.contractAddress == contractAddress) && - (identical(other.ercType, ercType) || other.ercType == ercType) && - const DeepCollectionEquality() - .equals(other._floorPrices, _floorPrices) && - (identical(other.imageUri, imageUri) || - other.imageUri == imageUri) && - (identical(other.metadata, metadata) || - other.metadata == metadata) && - (identical(other.mintTime, mintTime) || - other.mintTime == mintTime) && - (identical(other.mintTransactionHash, mintTransactionHash) || - other.mintTransactionHash == mintTransactionHash) && - (identical(other.name, name) || other.name == name) && - (identical(other.owner, owner) || other.owner == owner) && - (identical(other.rarityRank, rarityRank) || - other.rarityRank == rarityRank) && - (identical(other.rarityScore, rarityScore) || - other.rarityScore == rarityScore) && - (identical(other.symbol, symbol) || other.symbol == symbol) && - (identical(other.tokenId, tokenId) || other.tokenId == tokenId) && - (identical(other.tokenUri, tokenUri) || - other.tokenUri == tokenUri) && - (identical(other.total, total) || other.total == total) && - (identical(other.totalString, totalString) || - other.totalString == totalString) && - const DeepCollectionEquality().equals(other._traits, _traits)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => Object.hash( - runtimeType, - contractAddress, - ercType, - const DeepCollectionEquality().hash(_floorPrices), - imageUri, - metadata, - mintTime, - mintTransactionHash, - name, - owner, - rarityRank, - rarityScore, - symbol, - tokenId, - tokenUri, - total, - totalString, - const DeepCollectionEquality().hash(_traits)); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$NFTImplCopyWith<_$NFTImpl> get copyWith => - __$$NFTImplCopyWithImpl<_$NFTImpl>(this, _$identity); - - @override - Map toJson() { - return _$$NFTImplToJson( - this, - ); - } -} - -abstract class _NFT implements NFT { - const factory _NFT( - {@JsonKey(name: 'contract_address') required final String contractAddress, - @JsonKey(name: 'erc_type') required final String? ercType, - @JsonKey(name: 'floor_prices') - required final List? floorPrices, - @JsonKey(name: 'image_uri') required final String? imageUri, - required final NFTMetadata? metadata, - @JsonKey(name: 'mint_time') required final DateTime mintTime, - @JsonKey(name: 'mint_transaction_hash') - required final String mintTransactionHash, - required final String? name, - required final String? owner, - @JsonKey(name: 'rarity_rank') required final num? rarityRank, - @JsonKey(name: 'rarity_score') required final num? rarityScore, - required final String symbol, - @JsonKey(name: 'token_id') required final String tokenId, - @JsonKey(name: 'token_uri') required final String? tokenUri, - required final num? total, - @JsonKey(name: 'total_string') required final String? totalString, - required final List? traits}) = _$NFTImpl; - - factory _NFT.fromJson(Map json) = _$NFTImpl.fromJson; - - @override - @JsonKey(name: 'contract_address') - String get contractAddress; - @override - @JsonKey(name: 'erc_type') - String? get ercType; - @override - @JsonKey(name: 'floor_prices') - List? get floorPrices; - @override - @JsonKey(name: 'image_uri') - String? get imageUri; - @override - NFTMetadata? get metadata; - @override - @JsonKey(name: 'mint_time') - DateTime get mintTime; - @override - @JsonKey(name: 'mint_transaction_hash') - String get mintTransactionHash; - @override - String? get name; - @override - String? get owner; - @override - @JsonKey(name: 'rarity_rank') - num? get rarityRank; - @override - @JsonKey(name: 'rarity_score') - num? get rarityScore; - @override - String get symbol; - @override - @JsonKey(name: 'token_id') - String get tokenId; - @override - @JsonKey(name: 'token_uri') - String? get tokenUri; - @override - num? get total; - @override - @JsonKey(name: 'total_string') - String? get totalString; - @override - List? get traits; - @override - @JsonKey(ignore: true) - _$$NFTImplCopyWith<_$NFTImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/src/utils/models/nft.g.dart b/lib/src/utils/models/nft.g.dart deleted file mode 100644 index 47d0a64..0000000 --- a/lib/src/utils/models/nft.g.dart +++ /dev/null @@ -1,53 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'nft.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$NFTImpl _$$NFTImplFromJson(Map json) => _$NFTImpl( - contractAddress: json['contract_address'] as String, - ercType: json['erc_type'] as String?, - floorPrices: (json['floor_prices'] as List?) - ?.map((e) => NFTFloorPrice.fromJson(e as Map)) - .toList(), - imageUri: json['image_uri'] as String?, - metadata: json['metadata'] == null - ? null - : NFTMetadata.fromJson(json['metadata'] as Map), - mintTime: DateTime.parse(json['mint_time'] as String), - mintTransactionHash: json['mint_transaction_hash'] as String, - name: json['name'] as String?, - owner: json['owner'] as String?, - rarityRank: json['rarity_rank'] as num?, - rarityScore: json['rarity_score'] as num?, - symbol: json['symbol'] as String, - tokenId: json['token_id'] as String, - tokenUri: json['token_uri'] as String?, - total: json['total'] as num?, - totalString: json['total_string'] as String?, - traits: (json['traits'] as List?) - ?.map((e) => NFTAttributes.fromJson(e as Map)) - .toList(), - ); - -Map _$$NFTImplToJson(_$NFTImpl instance) => { - 'contract_address': instance.contractAddress, - 'erc_type': instance.ercType, - 'floor_prices': instance.floorPrices, - 'image_uri': instance.imageUri, - 'metadata': instance.metadata, - 'mint_time': instance.mintTime.toIso8601String(), - 'mint_transaction_hash': instance.mintTransactionHash, - 'name': instance.name, - 'owner': instance.owner, - 'rarity_rank': instance.rarityRank, - 'rarity_score': instance.rarityScore, - 'symbol': instance.symbol, - 'token_id': instance.tokenId, - 'token_uri': instance.tokenUri, - 'total': instance.total, - 'total_string': instance.totalString, - 'traits': instance.traits, - }; diff --git a/lib/src/utils/models/price.dart b/lib/src/utils/models/price.dart deleted file mode 100644 index 957738b..0000000 --- a/lib/src/utils/models/price.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'price.freezed.dart'; -part 'price.g.dart'; - -@freezed -class TokenPrice with _$TokenPrice { - const factory TokenPrice({ - required num? price, - required String? symbol, - required num? decimals, - @JsonKey(name: 'updated_at') required DateTime? updatedAt, - }) = _TokenPrice; - - factory TokenPrice.fromJson(Map json) => - _$TokenPriceFromJson(json); -} diff --git a/lib/src/utils/models/price.freezed.dart b/lib/src/utils/models/price.freezed.dart deleted file mode 100644 index a0a10d8..0000000 --- a/lib/src/utils/models/price.freezed.dart +++ /dev/null @@ -1,222 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'price.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); - -TokenPrice _$TokenPriceFromJson(Map json) { - return _TokenPrice.fromJson(json); -} - -/// @nodoc -mixin _$TokenPrice { - num? get price => throw _privateConstructorUsedError; - String? get symbol => throw _privateConstructorUsedError; - num? get decimals => throw _privateConstructorUsedError; - @JsonKey(name: 'updated_at') - DateTime? get updatedAt => throw _privateConstructorUsedError; - - Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) - $TokenPriceCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $TokenPriceCopyWith<$Res> { - factory $TokenPriceCopyWith( - TokenPrice value, $Res Function(TokenPrice) then) = - _$TokenPriceCopyWithImpl<$Res, TokenPrice>; - @useResult - $Res call( - {num? price, - String? symbol, - num? decimals, - @JsonKey(name: 'updated_at') DateTime? updatedAt}); -} - -/// @nodoc -class _$TokenPriceCopyWithImpl<$Res, $Val extends TokenPrice> - implements $TokenPriceCopyWith<$Res> { - _$TokenPriceCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? price = freezed, - Object? symbol = freezed, - Object? decimals = freezed, - Object? updatedAt = freezed, - }) { - return _then(_value.copyWith( - price: freezed == price - ? _value.price - : price // ignore: cast_nullable_to_non_nullable - as num?, - symbol: freezed == symbol - ? _value.symbol - : symbol // ignore: cast_nullable_to_non_nullable - as String?, - decimals: freezed == decimals - ? _value.decimals - : decimals // ignore: cast_nullable_to_non_nullable - as num?, - updatedAt: freezed == updatedAt - ? _value.updatedAt - : updatedAt // ignore: cast_nullable_to_non_nullable - as DateTime?, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$TokenPriceImplCopyWith<$Res> - implements $TokenPriceCopyWith<$Res> { - factory _$$TokenPriceImplCopyWith( - _$TokenPriceImpl value, $Res Function(_$TokenPriceImpl) then) = - __$$TokenPriceImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {num? price, - String? symbol, - num? decimals, - @JsonKey(name: 'updated_at') DateTime? updatedAt}); -} - -/// @nodoc -class __$$TokenPriceImplCopyWithImpl<$Res> - extends _$TokenPriceCopyWithImpl<$Res, _$TokenPriceImpl> - implements _$$TokenPriceImplCopyWith<$Res> { - __$$TokenPriceImplCopyWithImpl( - _$TokenPriceImpl _value, $Res Function(_$TokenPriceImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? price = freezed, - Object? symbol = freezed, - Object? decimals = freezed, - Object? updatedAt = freezed, - }) { - return _then(_$TokenPriceImpl( - price: freezed == price - ? _value.price - : price // ignore: cast_nullable_to_non_nullable - as num?, - symbol: freezed == symbol - ? _value.symbol - : symbol // ignore: cast_nullable_to_non_nullable - as String?, - decimals: freezed == decimals - ? _value.decimals - : decimals // ignore: cast_nullable_to_non_nullable - as num?, - updatedAt: freezed == updatedAt - ? _value.updatedAt - : updatedAt // ignore: cast_nullable_to_non_nullable - as DateTime?, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$TokenPriceImpl implements _TokenPrice { - const _$TokenPriceImpl( - {required this.price, - required this.symbol, - required this.decimals, - @JsonKey(name: 'updated_at') required this.updatedAt}); - - factory _$TokenPriceImpl.fromJson(Map json) => - _$$TokenPriceImplFromJson(json); - - @override - final num? price; - @override - final String? symbol; - @override - final num? decimals; - @override - @JsonKey(name: 'updated_at') - final DateTime? updatedAt; - - @override - String toString() { - return 'TokenPrice(price: $price, symbol: $symbol, decimals: $decimals, updatedAt: $updatedAt)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$TokenPriceImpl && - (identical(other.price, price) || other.price == price) && - (identical(other.symbol, symbol) || other.symbol == symbol) && - (identical(other.decimals, decimals) || - other.decimals == decimals) && - (identical(other.updatedAt, updatedAt) || - other.updatedAt == updatedAt)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => - Object.hash(runtimeType, price, symbol, decimals, updatedAt); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$TokenPriceImplCopyWith<_$TokenPriceImpl> get copyWith => - __$$TokenPriceImplCopyWithImpl<_$TokenPriceImpl>(this, _$identity); - - @override - Map toJson() { - return _$$TokenPriceImplToJson( - this, - ); - } -} - -abstract class _TokenPrice implements TokenPrice { - const factory _TokenPrice( - {required final num? price, - required final String? symbol, - required final num? decimals, - @JsonKey(name: 'updated_at') required final DateTime? updatedAt}) = - _$TokenPriceImpl; - - factory _TokenPrice.fromJson(Map json) = - _$TokenPriceImpl.fromJson; - - @override - num? get price; - @override - String? get symbol; - @override - num? get decimals; - @override - @JsonKey(name: 'updated_at') - DateTime? get updatedAt; - @override - @JsonKey(ignore: true) - _$$TokenPriceImplCopyWith<_$TokenPriceImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/src/utils/models/price.g.dart b/lib/src/utils/models/price.g.dart deleted file mode 100644 index f60bc83..0000000 --- a/lib/src/utils/models/price.g.dart +++ /dev/null @@ -1,25 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'price.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$TokenPriceImpl _$$TokenPriceImplFromJson(Map json) => - _$TokenPriceImpl( - price: json['price'] as num?, - symbol: json['symbol'] as String?, - decimals: json['decimals'] as num?, - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - ); - -Map _$$TokenPriceImplToJson(_$TokenPriceImpl instance) => - { - 'price': instance.price, - 'symbol': instance.symbol, - 'decimals': instance.decimals, - 'updated_at': instance.updatedAt?.toIso8601String(), - }; diff --git a/lib/src/utils/models/token.dart b/lib/src/utils/models/token.dart deleted file mode 100644 index caf72d0..0000000 --- a/lib/src/utils/models/token.dart +++ /dev/null @@ -1,98 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:variance_dart/variance.dart' show Uint256; - -part 'token.freezed.dart'; -part 'token.g.dart'; - -@freezed -class Token with _$Token { - const factory Token( - {@HexConverter() required Uint256 balance, - @JsonKey(name: 'contract_address') required String contractAddress, - @JsonKey(name: 'current_usd_price') required num? currentUsdPrice, - required num decimals, - required List? logos, - required String name, - required String symbol, - @BigIntConverter() - @JsonKey(name: 'total_supply') - required Uint256? totalSupply, - required List? urls}) = _Token; - - factory Token.fromJson(Map json) => _$TokenFromJson(json); -} - -class HexConverter implements JsonConverter { - const HexConverter(); - - @override - Uint256 fromJson(String json) { - return Uint256.fromHex(json); - } - - @override - String toJson(Uint256 balance) { - return balance.toHex(); - } -} - -class BigIntConverter implements JsonConverter { - const BigIntConverter(); - - @override - Uint256 fromJson(dynamic json) { - if (json is String) { - return Uint256(BigInt.parse(json)); - } - return Uint256(BigInt.from(json)); - } - - @override - BigInt toJson(Uint256 balance) { - return balance.value; - } -} - -class TokenUrl { - TokenUrl({ - required this.name, - required this.url, - }); - - factory TokenUrl.fromJson(Map json) => TokenUrl( - name: json['name'], - url: json['url'], - ); - - final String name; - final String url; - - Map toJson() => { - 'name': name, - 'url': url, - }; -} - -class TokenLogo { - TokenLogo({ - required this.height, - required this.uri, - required this.width, - }); - - factory TokenLogo.fromJson(Map json) => TokenLogo( - height: json['height'], - uri: json['uri'], - width: json['width'], - ); - - final num height; - final String uri; - final num width; - - Map toJson() => { - 'height': height, - 'uri': uri, - 'width': width, - }; -} diff --git a/lib/src/utils/models/token.freezed.dart b/lib/src/utils/models/token.freezed.dart deleted file mode 100644 index f3b0f4e..0000000 --- a/lib/src/utils/models/token.freezed.dart +++ /dev/null @@ -1,361 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'token.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); - -Token _$TokenFromJson(Map json) { - return _Token.fromJson(json); -} - -/// @nodoc -mixin _$Token { - @HexConverter() - Uint256 get balance => throw _privateConstructorUsedError; - @JsonKey(name: 'contract_address') - String get contractAddress => throw _privateConstructorUsedError; - @JsonKey(name: 'current_usd_price') - num? get currentUsdPrice => throw _privateConstructorUsedError; - num get decimals => throw _privateConstructorUsedError; - List? get logos => throw _privateConstructorUsedError; - String get name => throw _privateConstructorUsedError; - String get symbol => throw _privateConstructorUsedError; - @BigIntConverter() - @JsonKey(name: 'total_supply') - Uint256? get totalSupply => throw _privateConstructorUsedError; - List? get urls => throw _privateConstructorUsedError; - - Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) - $TokenCopyWith get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $TokenCopyWith<$Res> { - factory $TokenCopyWith(Token value, $Res Function(Token) then) = - _$TokenCopyWithImpl<$Res, Token>; - @useResult - $Res call( - {@HexConverter() Uint256 balance, - @JsonKey(name: 'contract_address') String contractAddress, - @JsonKey(name: 'current_usd_price') num? currentUsdPrice, - num decimals, - List? logos, - String name, - String symbol, - @BigIntConverter() @JsonKey(name: 'total_supply') Uint256? totalSupply, - List? urls}); -} - -/// @nodoc -class _$TokenCopyWithImpl<$Res, $Val extends Token> - implements $TokenCopyWith<$Res> { - _$TokenCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? balance = null, - Object? contractAddress = null, - Object? currentUsdPrice = freezed, - Object? decimals = null, - Object? logos = freezed, - Object? name = null, - Object? symbol = null, - Object? totalSupply = freezed, - Object? urls = freezed, - }) { - return _then(_value.copyWith( - balance: null == balance - ? _value.balance - : balance // ignore: cast_nullable_to_non_nullable - as Uint256, - contractAddress: null == contractAddress - ? _value.contractAddress - : contractAddress // ignore: cast_nullable_to_non_nullable - as String, - currentUsdPrice: freezed == currentUsdPrice - ? _value.currentUsdPrice - : currentUsdPrice // ignore: cast_nullable_to_non_nullable - as num?, - decimals: null == decimals - ? _value.decimals - : decimals // ignore: cast_nullable_to_non_nullable - as num, - logos: freezed == logos - ? _value.logos - : logos // ignore: cast_nullable_to_non_nullable - as List?, - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - symbol: null == symbol - ? _value.symbol - : symbol // ignore: cast_nullable_to_non_nullable - as String, - totalSupply: freezed == totalSupply - ? _value.totalSupply - : totalSupply // ignore: cast_nullable_to_non_nullable - as Uint256?, - urls: freezed == urls - ? _value.urls - : urls // ignore: cast_nullable_to_non_nullable - as List?, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$TokenImplCopyWith<$Res> implements $TokenCopyWith<$Res> { - factory _$$TokenImplCopyWith( - _$TokenImpl value, $Res Function(_$TokenImpl) then) = - __$$TokenImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {@HexConverter() Uint256 balance, - @JsonKey(name: 'contract_address') String contractAddress, - @JsonKey(name: 'current_usd_price') num? currentUsdPrice, - num decimals, - List? logos, - String name, - String symbol, - @BigIntConverter() @JsonKey(name: 'total_supply') Uint256? totalSupply, - List? urls}); -} - -/// @nodoc -class __$$TokenImplCopyWithImpl<$Res> - extends _$TokenCopyWithImpl<$Res, _$TokenImpl> - implements _$$TokenImplCopyWith<$Res> { - __$$TokenImplCopyWithImpl( - _$TokenImpl _value, $Res Function(_$TokenImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? balance = null, - Object? contractAddress = null, - Object? currentUsdPrice = freezed, - Object? decimals = null, - Object? logos = freezed, - Object? name = null, - Object? symbol = null, - Object? totalSupply = freezed, - Object? urls = freezed, - }) { - return _then(_$TokenImpl( - balance: null == balance - ? _value.balance - : balance // ignore: cast_nullable_to_non_nullable - as Uint256, - contractAddress: null == contractAddress - ? _value.contractAddress - : contractAddress // ignore: cast_nullable_to_non_nullable - as String, - currentUsdPrice: freezed == currentUsdPrice - ? _value.currentUsdPrice - : currentUsdPrice // ignore: cast_nullable_to_non_nullable - as num?, - decimals: null == decimals - ? _value.decimals - : decimals // ignore: cast_nullable_to_non_nullable - as num, - logos: freezed == logos - ? _value._logos - : logos // ignore: cast_nullable_to_non_nullable - as List?, - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - symbol: null == symbol - ? _value.symbol - : symbol // ignore: cast_nullable_to_non_nullable - as String, - totalSupply: freezed == totalSupply - ? _value.totalSupply - : totalSupply // ignore: cast_nullable_to_non_nullable - as Uint256?, - urls: freezed == urls - ? _value._urls - : urls // ignore: cast_nullable_to_non_nullable - as List?, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$TokenImpl implements _Token { - const _$TokenImpl( - {@HexConverter() required this.balance, - @JsonKey(name: 'contract_address') required this.contractAddress, - @JsonKey(name: 'current_usd_price') required this.currentUsdPrice, - required this.decimals, - required final List? logos, - required this.name, - required this.symbol, - @BigIntConverter() - @JsonKey(name: 'total_supply') - required this.totalSupply, - required final List? urls}) - : _logos = logos, - _urls = urls; - - factory _$TokenImpl.fromJson(Map json) => - _$$TokenImplFromJson(json); - - @override - @HexConverter() - final Uint256 balance; - @override - @JsonKey(name: 'contract_address') - final String contractAddress; - @override - @JsonKey(name: 'current_usd_price') - final num? currentUsdPrice; - @override - final num decimals; - final List? _logos; - @override - List? get logos { - final value = _logos; - if (value == null) return null; - if (_logos is EqualUnmodifiableListView) return _logos; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(value); - } - - @override - final String name; - @override - final String symbol; - @override - @BigIntConverter() - @JsonKey(name: 'total_supply') - final Uint256? totalSupply; - final List? _urls; - @override - List? get urls { - final value = _urls; - if (value == null) return null; - if (_urls is EqualUnmodifiableListView) return _urls; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(value); - } - - @override - String toString() { - return 'Token(balance: $balance, contractAddress: $contractAddress, currentUsdPrice: $currentUsdPrice, decimals: $decimals, logos: $logos, name: $name, symbol: $symbol, totalSupply: $totalSupply, urls: $urls)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$TokenImpl && - (identical(other.balance, balance) || other.balance == balance) && - (identical(other.contractAddress, contractAddress) || - other.contractAddress == contractAddress) && - (identical(other.currentUsdPrice, currentUsdPrice) || - other.currentUsdPrice == currentUsdPrice) && - (identical(other.decimals, decimals) || - other.decimals == decimals) && - const DeepCollectionEquality().equals(other._logos, _logos) && - (identical(other.name, name) || other.name == name) && - (identical(other.symbol, symbol) || other.symbol == symbol) && - (identical(other.totalSupply, totalSupply) || - other.totalSupply == totalSupply) && - const DeepCollectionEquality().equals(other._urls, _urls)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => Object.hash( - runtimeType, - balance, - contractAddress, - currentUsdPrice, - decimals, - const DeepCollectionEquality().hash(_logos), - name, - symbol, - totalSupply, - const DeepCollectionEquality().hash(_urls)); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$TokenImplCopyWith<_$TokenImpl> get copyWith => - __$$TokenImplCopyWithImpl<_$TokenImpl>(this, _$identity); - - @override - Map toJson() { - return _$$TokenImplToJson( - this, - ); - } -} - -abstract class _Token implements Token { - const factory _Token( - {@HexConverter() required final Uint256 balance, - @JsonKey(name: 'contract_address') required final String contractAddress, - @JsonKey(name: 'current_usd_price') required final num? currentUsdPrice, - required final num decimals, - required final List? logos, - required final String name, - required final String symbol, - @BigIntConverter() - @JsonKey(name: 'total_supply') - required final Uint256? totalSupply, - required final List? urls}) = _$TokenImpl; - - factory _Token.fromJson(Map json) = _$TokenImpl.fromJson; - - @override - @HexConverter() - Uint256 get balance; - @override - @JsonKey(name: 'contract_address') - String get contractAddress; - @override - @JsonKey(name: 'current_usd_price') - num? get currentUsdPrice; - @override - num get decimals; - @override - List? get logos; - @override - String get name; - @override - String get symbol; - @override - @BigIntConverter() - @JsonKey(name: 'total_supply') - Uint256? get totalSupply; - @override - List? get urls; - @override - @JsonKey(ignore: true) - _$$TokenImplCopyWith<_$TokenImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/src/utils/models/token.g.dart b/lib/src/utils/models/token.g.dart deleted file mode 100644 index f147d11..0000000 --- a/lib/src/utils/models/token.g.dart +++ /dev/null @@ -1,43 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'token.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$TokenImpl _$$TokenImplFromJson(Map json) => _$TokenImpl( - balance: const HexConverter().fromJson(json['balance'] as String), - contractAddress: json['contract_address'] as String, - currentUsdPrice: json['current_usd_price'] as num?, - decimals: json['decimals'] as num, - logos: (json['logos'] as List?) - ?.map((e) => TokenLogo.fromJson(e as Map)) - .toList(), - name: json['name'] as String, - symbol: json['symbol'] as String, - totalSupply: const BigIntConverter().fromJson(json['total_supply']), - urls: (json['urls'] as List?) - ?.map((e) => TokenUrl.fromJson(e as Map)) - .toList(), - ); - -Map _$$TokenImplToJson(_$TokenImpl instance) => - { - 'balance': const HexConverter().toJson(instance.balance), - 'contract_address': instance.contractAddress, - 'current_usd_price': instance.currentUsdPrice, - 'decimals': instance.decimals, - 'logos': instance.logos, - 'name': instance.name, - 'symbol': instance.symbol, - 'total_supply': _$JsonConverterToJson( - instance.totalSupply, const BigIntConverter().toJson), - 'urls': instance.urls, - }; - -Json? _$JsonConverterToJson( - Value? value, - Json? Function(Value value) toJson, -) => - value == null ? null : toJson(value); diff --git a/lib/src/utils/models/transaction.dart b/lib/src/utils/models/transaction.dart deleted file mode 100644 index 729bbb5..0000000 --- a/lib/src/utils/models/transaction.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:variance_dart/src/common/common.dart' show Uint256; -import 'package:variance_dart/utils.dart' show BigIntConverter; - -part 'transaction.freezed.dart'; -part 'transaction.g.dart'; - -@freezed -class Transaction with _$Transaction { - const factory Transaction({ - required num type, - required num status, - @JsonKey(name: 'block_number') required num blockNumber, - @JsonKey(name: 'block_timestamp') required DateTime blockTimestamp, - @JsonKey(name: 'transaction_hash') required String transactionHash, - @JsonKey(name: 'transaction_index') required num transactionIndex, - @JsonKey(name: 'from_address') required String fromAddress, - @JsonKey(name: 'to_address') required String toAddress, - @BigIntConverter() required Uint256 value, - required String? input, - required num nonce, - @JsonKey(name: 'contract_address') required String? contractAddress, - @BigIntConverter() required Uint256 gas, - @BigIntConverter() @JsonKey(name: 'gas_price') required Uint256 gasPrice, - @BigIntConverter() @JsonKey(name: 'gas_used') required Uint256 gasUsed, - @BigIntConverter() - @JsonKey(name: 'effective_gas_price') - required Uint256 effectiveGasPrice, - @BigIntConverter() - @JsonKey(name: 'cumulative_gas_used') - required Uint256 cumulativeGasUsed, - @BigIntConverter() - @JsonKey(name: 'max_fee_per_gas') - required Uint256? maxFeePerGas, - @BigIntConverter() - @JsonKey(name: 'max_priority_fee_per_gas') - required Uint256? maxPriorityFeePerGas, - @JsonKey(name: 'tx_fee') required num txFee, - @JsonKey(name: 'saving_fee') required num? savingFee, - @JsonKey(name: 'burnt_fee') required num? burntFee, - }) = _Transaction; - - factory Transaction.fromJson(Map json) => - _$TransactionFromJson(json); -} diff --git a/lib/src/utils/models/transaction.freezed.dart b/lib/src/utils/models/transaction.freezed.dart deleted file mode 100644 index 44702a7..0000000 --- a/lib/src/utils/models/transaction.freezed.dart +++ /dev/null @@ -1,722 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'transaction.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); - -Transaction _$TransactionFromJson(Map json) { - return _Transaction.fromJson(json); -} - -/// @nodoc -mixin _$Transaction { - num get type => throw _privateConstructorUsedError; - num get status => throw _privateConstructorUsedError; - @JsonKey(name: 'block_number') - num get blockNumber => throw _privateConstructorUsedError; - @JsonKey(name: 'block_timestamp') - DateTime get blockTimestamp => throw _privateConstructorUsedError; - @JsonKey(name: 'transaction_hash') - String get transactionHash => throw _privateConstructorUsedError; - @JsonKey(name: 'transaction_index') - num get transactionIndex => throw _privateConstructorUsedError; - @JsonKey(name: 'from_address') - String get fromAddress => throw _privateConstructorUsedError; - @JsonKey(name: 'to_address') - String get toAddress => throw _privateConstructorUsedError; - @BigIntConverter() - Uint256 get value => throw _privateConstructorUsedError; - String? get input => throw _privateConstructorUsedError; - num get nonce => throw _privateConstructorUsedError; - @JsonKey(name: 'contract_address') - String? get contractAddress => throw _privateConstructorUsedError; - @BigIntConverter() - Uint256 get gas => throw _privateConstructorUsedError; - @BigIntConverter() - @JsonKey(name: 'gas_price') - Uint256 get gasPrice => throw _privateConstructorUsedError; - @BigIntConverter() - @JsonKey(name: 'gas_used') - Uint256 get gasUsed => throw _privateConstructorUsedError; - @BigIntConverter() - @JsonKey(name: 'effective_gas_price') - Uint256 get effectiveGasPrice => throw _privateConstructorUsedError; - @BigIntConverter() - @JsonKey(name: 'cumulative_gas_used') - Uint256 get cumulativeGasUsed => throw _privateConstructorUsedError; - @BigIntConverter() - @JsonKey(name: 'max_fee_per_gas') - Uint256? get maxFeePerGas => throw _privateConstructorUsedError; - @BigIntConverter() - @JsonKey(name: 'max_priority_fee_per_gas') - Uint256? get maxPriorityFeePerGas => throw _privateConstructorUsedError; - @JsonKey(name: 'tx_fee') - num get txFee => throw _privateConstructorUsedError; - @JsonKey(name: 'saving_fee') - num? get savingFee => throw _privateConstructorUsedError; - @JsonKey(name: 'burnt_fee') - num? get burntFee => throw _privateConstructorUsedError; - - Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) - $TransactionCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $TransactionCopyWith<$Res> { - factory $TransactionCopyWith( - Transaction value, $Res Function(Transaction) then) = - _$TransactionCopyWithImpl<$Res, Transaction>; - @useResult - $Res call( - {num type, - num status, - @JsonKey(name: 'block_number') num blockNumber, - @JsonKey(name: 'block_timestamp') DateTime blockTimestamp, - @JsonKey(name: 'transaction_hash') String transactionHash, - @JsonKey(name: 'transaction_index') num transactionIndex, - @JsonKey(name: 'from_address') String fromAddress, - @JsonKey(name: 'to_address') String toAddress, - @BigIntConverter() Uint256 value, - String? input, - num nonce, - @JsonKey(name: 'contract_address') String? contractAddress, - @BigIntConverter() Uint256 gas, - @BigIntConverter() @JsonKey(name: 'gas_price') Uint256 gasPrice, - @BigIntConverter() @JsonKey(name: 'gas_used') Uint256 gasUsed, - @BigIntConverter() - @JsonKey(name: 'effective_gas_price') - Uint256 effectiveGasPrice, - @BigIntConverter() - @JsonKey(name: 'cumulative_gas_used') - Uint256 cumulativeGasUsed, - @BigIntConverter() - @JsonKey(name: 'max_fee_per_gas') - Uint256? maxFeePerGas, - @BigIntConverter() - @JsonKey(name: 'max_priority_fee_per_gas') - Uint256? maxPriorityFeePerGas, - @JsonKey(name: 'tx_fee') num txFee, - @JsonKey(name: 'saving_fee') num? savingFee, - @JsonKey(name: 'burnt_fee') num? burntFee}); -} - -/// @nodoc -class _$TransactionCopyWithImpl<$Res, $Val extends Transaction> - implements $TransactionCopyWith<$Res> { - _$TransactionCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? type = null, - Object? status = null, - Object? blockNumber = null, - Object? blockTimestamp = null, - Object? transactionHash = null, - Object? transactionIndex = null, - Object? fromAddress = null, - Object? toAddress = null, - Object? value = null, - Object? input = freezed, - Object? nonce = null, - Object? contractAddress = freezed, - Object? gas = null, - Object? gasPrice = null, - Object? gasUsed = null, - Object? effectiveGasPrice = null, - Object? cumulativeGasUsed = null, - Object? maxFeePerGas = freezed, - Object? maxPriorityFeePerGas = freezed, - Object? txFee = null, - Object? savingFee = freezed, - Object? burntFee = freezed, - }) { - return _then(_value.copyWith( - type: null == type - ? _value.type - : type // ignore: cast_nullable_to_non_nullable - as num, - status: null == status - ? _value.status - : status // ignore: cast_nullable_to_non_nullable - as num, - blockNumber: null == blockNumber - ? _value.blockNumber - : blockNumber // ignore: cast_nullable_to_non_nullable - as num, - blockTimestamp: null == blockTimestamp - ? _value.blockTimestamp - : blockTimestamp // ignore: cast_nullable_to_non_nullable - as DateTime, - transactionHash: null == transactionHash - ? _value.transactionHash - : transactionHash // ignore: cast_nullable_to_non_nullable - as String, - transactionIndex: null == transactionIndex - ? _value.transactionIndex - : transactionIndex // ignore: cast_nullable_to_non_nullable - as num, - fromAddress: null == fromAddress - ? _value.fromAddress - : fromAddress // ignore: cast_nullable_to_non_nullable - as String, - toAddress: null == toAddress - ? _value.toAddress - : toAddress // ignore: cast_nullable_to_non_nullable - as String, - value: null == value - ? _value.value - : value // ignore: cast_nullable_to_non_nullable - as Uint256, - input: freezed == input - ? _value.input - : input // ignore: cast_nullable_to_non_nullable - as String?, - nonce: null == nonce - ? _value.nonce - : nonce // ignore: cast_nullable_to_non_nullable - as num, - contractAddress: freezed == contractAddress - ? _value.contractAddress - : contractAddress // ignore: cast_nullable_to_non_nullable - as String?, - gas: null == gas - ? _value.gas - : gas // ignore: cast_nullable_to_non_nullable - as Uint256, - gasPrice: null == gasPrice - ? _value.gasPrice - : gasPrice // ignore: cast_nullable_to_non_nullable - as Uint256, - gasUsed: null == gasUsed - ? _value.gasUsed - : gasUsed // ignore: cast_nullable_to_non_nullable - as Uint256, - effectiveGasPrice: null == effectiveGasPrice - ? _value.effectiveGasPrice - : effectiveGasPrice // ignore: cast_nullable_to_non_nullable - as Uint256, - cumulativeGasUsed: null == cumulativeGasUsed - ? _value.cumulativeGasUsed - : cumulativeGasUsed // ignore: cast_nullable_to_non_nullable - as Uint256, - maxFeePerGas: freezed == maxFeePerGas - ? _value.maxFeePerGas - : maxFeePerGas // ignore: cast_nullable_to_non_nullable - as Uint256?, - maxPriorityFeePerGas: freezed == maxPriorityFeePerGas - ? _value.maxPriorityFeePerGas - : maxPriorityFeePerGas // ignore: cast_nullable_to_non_nullable - as Uint256?, - txFee: null == txFee - ? _value.txFee - : txFee // ignore: cast_nullable_to_non_nullable - as num, - savingFee: freezed == savingFee - ? _value.savingFee - : savingFee // ignore: cast_nullable_to_non_nullable - as num?, - burntFee: freezed == burntFee - ? _value.burntFee - : burntFee // ignore: cast_nullable_to_non_nullable - as num?, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$TransactionImplCopyWith<$Res> - implements $TransactionCopyWith<$Res> { - factory _$$TransactionImplCopyWith( - _$TransactionImpl value, $Res Function(_$TransactionImpl) then) = - __$$TransactionImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {num type, - num status, - @JsonKey(name: 'block_number') num blockNumber, - @JsonKey(name: 'block_timestamp') DateTime blockTimestamp, - @JsonKey(name: 'transaction_hash') String transactionHash, - @JsonKey(name: 'transaction_index') num transactionIndex, - @JsonKey(name: 'from_address') String fromAddress, - @JsonKey(name: 'to_address') String toAddress, - @BigIntConverter() Uint256 value, - String? input, - num nonce, - @JsonKey(name: 'contract_address') String? contractAddress, - @BigIntConverter() Uint256 gas, - @BigIntConverter() @JsonKey(name: 'gas_price') Uint256 gasPrice, - @BigIntConverter() @JsonKey(name: 'gas_used') Uint256 gasUsed, - @BigIntConverter() - @JsonKey(name: 'effective_gas_price') - Uint256 effectiveGasPrice, - @BigIntConverter() - @JsonKey(name: 'cumulative_gas_used') - Uint256 cumulativeGasUsed, - @BigIntConverter() - @JsonKey(name: 'max_fee_per_gas') - Uint256? maxFeePerGas, - @BigIntConverter() - @JsonKey(name: 'max_priority_fee_per_gas') - Uint256? maxPriorityFeePerGas, - @JsonKey(name: 'tx_fee') num txFee, - @JsonKey(name: 'saving_fee') num? savingFee, - @JsonKey(name: 'burnt_fee') num? burntFee}); -} - -/// @nodoc -class __$$TransactionImplCopyWithImpl<$Res> - extends _$TransactionCopyWithImpl<$Res, _$TransactionImpl> - implements _$$TransactionImplCopyWith<$Res> { - __$$TransactionImplCopyWithImpl( - _$TransactionImpl _value, $Res Function(_$TransactionImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? type = null, - Object? status = null, - Object? blockNumber = null, - Object? blockTimestamp = null, - Object? transactionHash = null, - Object? transactionIndex = null, - Object? fromAddress = null, - Object? toAddress = null, - Object? value = null, - Object? input = freezed, - Object? nonce = null, - Object? contractAddress = freezed, - Object? gas = null, - Object? gasPrice = null, - Object? gasUsed = null, - Object? effectiveGasPrice = null, - Object? cumulativeGasUsed = null, - Object? maxFeePerGas = freezed, - Object? maxPriorityFeePerGas = freezed, - Object? txFee = null, - Object? savingFee = freezed, - Object? burntFee = freezed, - }) { - return _then(_$TransactionImpl( - type: null == type - ? _value.type - : type // ignore: cast_nullable_to_non_nullable - as num, - status: null == status - ? _value.status - : status // ignore: cast_nullable_to_non_nullable - as num, - blockNumber: null == blockNumber - ? _value.blockNumber - : blockNumber // ignore: cast_nullable_to_non_nullable - as num, - blockTimestamp: null == blockTimestamp - ? _value.blockTimestamp - : blockTimestamp // ignore: cast_nullable_to_non_nullable - as DateTime, - transactionHash: null == transactionHash - ? _value.transactionHash - : transactionHash // ignore: cast_nullable_to_non_nullable - as String, - transactionIndex: null == transactionIndex - ? _value.transactionIndex - : transactionIndex // ignore: cast_nullable_to_non_nullable - as num, - fromAddress: null == fromAddress - ? _value.fromAddress - : fromAddress // ignore: cast_nullable_to_non_nullable - as String, - toAddress: null == toAddress - ? _value.toAddress - : toAddress // ignore: cast_nullable_to_non_nullable - as String, - value: null == value - ? _value.value - : value // ignore: cast_nullable_to_non_nullable - as Uint256, - input: freezed == input - ? _value.input - : input // ignore: cast_nullable_to_non_nullable - as String?, - nonce: null == nonce - ? _value.nonce - : nonce // ignore: cast_nullable_to_non_nullable - as num, - contractAddress: freezed == contractAddress - ? _value.contractAddress - : contractAddress // ignore: cast_nullable_to_non_nullable - as String?, - gas: null == gas - ? _value.gas - : gas // ignore: cast_nullable_to_non_nullable - as Uint256, - gasPrice: null == gasPrice - ? _value.gasPrice - : gasPrice // ignore: cast_nullable_to_non_nullable - as Uint256, - gasUsed: null == gasUsed - ? _value.gasUsed - : gasUsed // ignore: cast_nullable_to_non_nullable - as Uint256, - effectiveGasPrice: null == effectiveGasPrice - ? _value.effectiveGasPrice - : effectiveGasPrice // ignore: cast_nullable_to_non_nullable - as Uint256, - cumulativeGasUsed: null == cumulativeGasUsed - ? _value.cumulativeGasUsed - : cumulativeGasUsed // ignore: cast_nullable_to_non_nullable - as Uint256, - maxFeePerGas: freezed == maxFeePerGas - ? _value.maxFeePerGas - : maxFeePerGas // ignore: cast_nullable_to_non_nullable - as Uint256?, - maxPriorityFeePerGas: freezed == maxPriorityFeePerGas - ? _value.maxPriorityFeePerGas - : maxPriorityFeePerGas // ignore: cast_nullable_to_non_nullable - as Uint256?, - txFee: null == txFee - ? _value.txFee - : txFee // ignore: cast_nullable_to_non_nullable - as num, - savingFee: freezed == savingFee - ? _value.savingFee - : savingFee // ignore: cast_nullable_to_non_nullable - as num?, - burntFee: freezed == burntFee - ? _value.burntFee - : burntFee // ignore: cast_nullable_to_non_nullable - as num?, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$TransactionImpl implements _Transaction { - const _$TransactionImpl( - {required this.type, - required this.status, - @JsonKey(name: 'block_number') required this.blockNumber, - @JsonKey(name: 'block_timestamp') required this.blockTimestamp, - @JsonKey(name: 'transaction_hash') required this.transactionHash, - @JsonKey(name: 'transaction_index') required this.transactionIndex, - @JsonKey(name: 'from_address') required this.fromAddress, - @JsonKey(name: 'to_address') required this.toAddress, - @BigIntConverter() required this.value, - required this.input, - required this.nonce, - @JsonKey(name: 'contract_address') required this.contractAddress, - @BigIntConverter() required this.gas, - @BigIntConverter() @JsonKey(name: 'gas_price') required this.gasPrice, - @BigIntConverter() @JsonKey(name: 'gas_used') required this.gasUsed, - @BigIntConverter() - @JsonKey(name: 'effective_gas_price') - required this.effectiveGasPrice, - @BigIntConverter() - @JsonKey(name: 'cumulative_gas_used') - required this.cumulativeGasUsed, - @BigIntConverter() - @JsonKey(name: 'max_fee_per_gas') - required this.maxFeePerGas, - @BigIntConverter() - @JsonKey(name: 'max_priority_fee_per_gas') - required this.maxPriorityFeePerGas, - @JsonKey(name: 'tx_fee') required this.txFee, - @JsonKey(name: 'saving_fee') required this.savingFee, - @JsonKey(name: 'burnt_fee') required this.burntFee}); - - factory _$TransactionImpl.fromJson(Map json) => - _$$TransactionImplFromJson(json); - - @override - final num type; - @override - final num status; - @override - @JsonKey(name: 'block_number') - final num blockNumber; - @override - @JsonKey(name: 'block_timestamp') - final DateTime blockTimestamp; - @override - @JsonKey(name: 'transaction_hash') - final String transactionHash; - @override - @JsonKey(name: 'transaction_index') - final num transactionIndex; - @override - @JsonKey(name: 'from_address') - final String fromAddress; - @override - @JsonKey(name: 'to_address') - final String toAddress; - @override - @BigIntConverter() - final Uint256 value; - @override - final String? input; - @override - final num nonce; - @override - @JsonKey(name: 'contract_address') - final String? contractAddress; - @override - @BigIntConverter() - final Uint256 gas; - @override - @BigIntConverter() - @JsonKey(name: 'gas_price') - final Uint256 gasPrice; - @override - @BigIntConverter() - @JsonKey(name: 'gas_used') - final Uint256 gasUsed; - @override - @BigIntConverter() - @JsonKey(name: 'effective_gas_price') - final Uint256 effectiveGasPrice; - @override - @BigIntConverter() - @JsonKey(name: 'cumulative_gas_used') - final Uint256 cumulativeGasUsed; - @override - @BigIntConverter() - @JsonKey(name: 'max_fee_per_gas') - final Uint256? maxFeePerGas; - @override - @BigIntConverter() - @JsonKey(name: 'max_priority_fee_per_gas') - final Uint256? maxPriorityFeePerGas; - @override - @JsonKey(name: 'tx_fee') - final num txFee; - @override - @JsonKey(name: 'saving_fee') - final num? savingFee; - @override - @JsonKey(name: 'burnt_fee') - final num? burntFee; - - @override - String toString() { - return 'Transaction(type: $type, status: $status, blockNumber: $blockNumber, blockTimestamp: $blockTimestamp, transactionHash: $transactionHash, transactionIndex: $transactionIndex, fromAddress: $fromAddress, toAddress: $toAddress, value: $value, input: $input, nonce: $nonce, contractAddress: $contractAddress, gas: $gas, gasPrice: $gasPrice, gasUsed: $gasUsed, effectiveGasPrice: $effectiveGasPrice, cumulativeGasUsed: $cumulativeGasUsed, maxFeePerGas: $maxFeePerGas, maxPriorityFeePerGas: $maxPriorityFeePerGas, txFee: $txFee, savingFee: $savingFee, burntFee: $burntFee)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$TransactionImpl && - (identical(other.type, type) || other.type == type) && - (identical(other.status, status) || other.status == status) && - (identical(other.blockNumber, blockNumber) || - other.blockNumber == blockNumber) && - (identical(other.blockTimestamp, blockTimestamp) || - other.blockTimestamp == blockTimestamp) && - (identical(other.transactionHash, transactionHash) || - other.transactionHash == transactionHash) && - (identical(other.transactionIndex, transactionIndex) || - other.transactionIndex == transactionIndex) && - (identical(other.fromAddress, fromAddress) || - other.fromAddress == fromAddress) && - (identical(other.toAddress, toAddress) || - other.toAddress == toAddress) && - (identical(other.value, value) || other.value == value) && - (identical(other.input, input) || other.input == input) && - (identical(other.nonce, nonce) || other.nonce == nonce) && - (identical(other.contractAddress, contractAddress) || - other.contractAddress == contractAddress) && - (identical(other.gas, gas) || other.gas == gas) && - (identical(other.gasPrice, gasPrice) || - other.gasPrice == gasPrice) && - (identical(other.gasUsed, gasUsed) || other.gasUsed == gasUsed) && - (identical(other.effectiveGasPrice, effectiveGasPrice) || - other.effectiveGasPrice == effectiveGasPrice) && - (identical(other.cumulativeGasUsed, cumulativeGasUsed) || - other.cumulativeGasUsed == cumulativeGasUsed) && - (identical(other.maxFeePerGas, maxFeePerGas) || - other.maxFeePerGas == maxFeePerGas) && - (identical(other.maxPriorityFeePerGas, maxPriorityFeePerGas) || - other.maxPriorityFeePerGas == maxPriorityFeePerGas) && - (identical(other.txFee, txFee) || other.txFee == txFee) && - (identical(other.savingFee, savingFee) || - other.savingFee == savingFee) && - (identical(other.burntFee, burntFee) || - other.burntFee == burntFee)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => Object.hashAll([ - runtimeType, - type, - status, - blockNumber, - blockTimestamp, - transactionHash, - transactionIndex, - fromAddress, - toAddress, - value, - input, - nonce, - contractAddress, - gas, - gasPrice, - gasUsed, - effectiveGasPrice, - cumulativeGasUsed, - maxFeePerGas, - maxPriorityFeePerGas, - txFee, - savingFee, - burntFee - ]); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$TransactionImplCopyWith<_$TransactionImpl> get copyWith => - __$$TransactionImplCopyWithImpl<_$TransactionImpl>(this, _$identity); - - @override - Map toJson() { - return _$$TransactionImplToJson( - this, - ); - } -} - -abstract class _Transaction implements Transaction { - const factory _Transaction( - {required final num type, - required final num status, - @JsonKey(name: 'block_number') required final num blockNumber, - @JsonKey(name: 'block_timestamp') required final DateTime blockTimestamp, - @JsonKey(name: 'transaction_hash') required final String transactionHash, - @JsonKey(name: 'transaction_index') required final num transactionIndex, - @JsonKey(name: 'from_address') required final String fromAddress, - @JsonKey(name: 'to_address') required final String toAddress, - @BigIntConverter() required final Uint256 value, - required final String? input, - required final num nonce, - @JsonKey(name: 'contract_address') required final String? contractAddress, - @BigIntConverter() required final Uint256 gas, - @BigIntConverter() - @JsonKey(name: 'gas_price') - required final Uint256 gasPrice, - @BigIntConverter() - @JsonKey(name: 'gas_used') - required final Uint256 gasUsed, - @BigIntConverter() - @JsonKey(name: 'effective_gas_price') - required final Uint256 effectiveGasPrice, - @BigIntConverter() - @JsonKey(name: 'cumulative_gas_used') - required final Uint256 cumulativeGasUsed, - @BigIntConverter() - @JsonKey(name: 'max_fee_per_gas') - required final Uint256? maxFeePerGas, - @BigIntConverter() - @JsonKey(name: 'max_priority_fee_per_gas') - required final Uint256? maxPriorityFeePerGas, - @JsonKey(name: 'tx_fee') required final num txFee, - @JsonKey(name: 'saving_fee') required final num? savingFee, - @JsonKey(name: 'burnt_fee') - required final num? burntFee}) = _$TransactionImpl; - - factory _Transaction.fromJson(Map json) = - _$TransactionImpl.fromJson; - - @override - num get type; - @override - num get status; - @override - @JsonKey(name: 'block_number') - num get blockNumber; - @override - @JsonKey(name: 'block_timestamp') - DateTime get blockTimestamp; - @override - @JsonKey(name: 'transaction_hash') - String get transactionHash; - @override - @JsonKey(name: 'transaction_index') - num get transactionIndex; - @override - @JsonKey(name: 'from_address') - String get fromAddress; - @override - @JsonKey(name: 'to_address') - String get toAddress; - @override - @BigIntConverter() - Uint256 get value; - @override - String? get input; - @override - num get nonce; - @override - @JsonKey(name: 'contract_address') - String? get contractAddress; - @override - @BigIntConverter() - Uint256 get gas; - @override - @BigIntConverter() - @JsonKey(name: 'gas_price') - Uint256 get gasPrice; - @override - @BigIntConverter() - @JsonKey(name: 'gas_used') - Uint256 get gasUsed; - @override - @BigIntConverter() - @JsonKey(name: 'effective_gas_price') - Uint256 get effectiveGasPrice; - @override - @BigIntConverter() - @JsonKey(name: 'cumulative_gas_used') - Uint256 get cumulativeGasUsed; - @override - @BigIntConverter() - @JsonKey(name: 'max_fee_per_gas') - Uint256? get maxFeePerGas; - @override - @BigIntConverter() - @JsonKey(name: 'max_priority_fee_per_gas') - Uint256? get maxPriorityFeePerGas; - @override - @JsonKey(name: 'tx_fee') - num get txFee; - @override - @JsonKey(name: 'saving_fee') - num? get savingFee; - @override - @JsonKey(name: 'burnt_fee') - num? get burntFee; - @override - @JsonKey(ignore: true) - _$$TransactionImplCopyWith<_$TransactionImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/src/utils/models/transaction.g.dart b/lib/src/utils/models/transaction.g.dart deleted file mode 100644 index fd5449d..0000000 --- a/lib/src/utils/models/transaction.g.dart +++ /dev/null @@ -1,72 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'transaction.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$TransactionImpl _$$TransactionImplFromJson(Map json) => - _$TransactionImpl( - type: json['type'] as num, - status: json['status'] as num, - blockNumber: json['block_number'] as num, - blockTimestamp: DateTime.parse(json['block_timestamp'] as String), - transactionHash: json['transaction_hash'] as String, - transactionIndex: json['transaction_index'] as num, - fromAddress: json['from_address'] as String, - toAddress: json['to_address'] as String, - value: const BigIntConverter().fromJson(json['value']), - input: json['input'] as String?, - nonce: json['nonce'] as num, - contractAddress: json['contract_address'] as String?, - gas: const BigIntConverter().fromJson(json['gas']), - gasPrice: const BigIntConverter().fromJson(json['gas_price']), - gasUsed: const BigIntConverter().fromJson(json['gas_used']), - effectiveGasPrice: - const BigIntConverter().fromJson(json['effective_gas_price']), - cumulativeGasUsed: - const BigIntConverter().fromJson(json['cumulative_gas_used']), - maxFeePerGas: const BigIntConverter().fromJson(json['max_fee_per_gas']), - maxPriorityFeePerGas: - const BigIntConverter().fromJson(json['max_priority_fee_per_gas']), - txFee: json['tx_fee'] as num, - savingFee: json['saving_fee'] as num?, - burntFee: json['burnt_fee'] as num?, - ); - -Map _$$TransactionImplToJson(_$TransactionImpl instance) => - { - 'type': instance.type, - 'status': instance.status, - 'block_number': instance.blockNumber, - 'block_timestamp': instance.blockTimestamp.toIso8601String(), - 'transaction_hash': instance.transactionHash, - 'transaction_index': instance.transactionIndex, - 'from_address': instance.fromAddress, - 'to_address': instance.toAddress, - 'value': const BigIntConverter().toJson(instance.value), - 'input': instance.input, - 'nonce': instance.nonce, - 'contract_address': instance.contractAddress, - 'gas': const BigIntConverter().toJson(instance.gas), - 'gas_price': const BigIntConverter().toJson(instance.gasPrice), - 'gas_used': const BigIntConverter().toJson(instance.gasUsed), - 'effective_gas_price': - const BigIntConverter().toJson(instance.effectiveGasPrice), - 'cumulative_gas_used': - const BigIntConverter().toJson(instance.cumulativeGasUsed), - 'max_fee_per_gas': _$JsonConverterToJson( - instance.maxFeePerGas, const BigIntConverter().toJson), - 'max_priority_fee_per_gas': _$JsonConverterToJson( - instance.maxPriorityFeePerGas, const BigIntConverter().toJson), - 'tx_fee': instance.txFee, - 'saving_fee': instance.savingFee, - 'burnt_fee': instance.burntFee, - }; - -Json? _$JsonConverterToJson( - Value? value, - Json? Function(Value value) toJson, -) => - value == null ? null : toJson(value); diff --git a/lib/src/utils/models/transfer.dart b/lib/src/utils/models/transfer.dart deleted file mode 100644 index d10e36e..0000000 --- a/lib/src/utils/models/transfer.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:variance_dart/src/common/common.dart' show Uint256; -import 'package:variance_dart/utils.dart' show BigIntConverter; - -part 'transfer.freezed.dart'; -part 'transfer.g.dart'; - -@freezed -class TokenTransfer with _$TokenTransfer { - const factory TokenTransfer({ - required TxType direction, - @JsonKey(name: 'block_number') required num blockNumber, - @JsonKey(name: 'block_timestamp') required DateTime blockTimestamp, - @JsonKey(name: 'contract_address') required String contractAddress, - @JsonKey(name: 'from_address') required String fromAddress, - @JsonKey(name: 'log_index') required num logIndex, - @JsonKey(name: 'to_address') required String toAddress, - @JsonKey(name: 'transaction_hash') required String transactionHash, - @JsonKey(name: 'transaction_index') required num transactionIndex, - @JsonKey(name: 'tx_fee') required num txFee, - @JsonKey(name: 'tx_type') required num txType, - @BigIntConverter() required Uint256 value, - }) = _TokenTransfer; - - factory TokenTransfer.fromJson(Map json) => - _$TokenTransferFromJson(json); -} - -// ignore: constant_identifier_names -enum TxType { RECEIVE, SEND } diff --git a/lib/src/utils/models/transfer.freezed.dart b/lib/src/utils/models/transfer.freezed.dart deleted file mode 100644 index 841b010..0000000 --- a/lib/src/utils/models/transfer.freezed.dart +++ /dev/null @@ -1,430 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'transfer.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); - -TokenTransfer _$TokenTransferFromJson(Map json) { - return _TokenTransfer.fromJson(json); -} - -/// @nodoc -mixin _$TokenTransfer { - TxType get direction => throw _privateConstructorUsedError; - @JsonKey(name: 'block_number') - num get blockNumber => throw _privateConstructorUsedError; - @JsonKey(name: 'block_timestamp') - DateTime get blockTimestamp => throw _privateConstructorUsedError; - @JsonKey(name: 'contract_address') - String get contractAddress => throw _privateConstructorUsedError; - @JsonKey(name: 'from_address') - String get fromAddress => throw _privateConstructorUsedError; - @JsonKey(name: 'log_index') - num get logIndex => throw _privateConstructorUsedError; - @JsonKey(name: 'to_address') - String get toAddress => throw _privateConstructorUsedError; - @JsonKey(name: 'transaction_hash') - String get transactionHash => throw _privateConstructorUsedError; - @JsonKey(name: 'transaction_index') - num get transactionIndex => throw _privateConstructorUsedError; - @JsonKey(name: 'tx_fee') - num get txFee => throw _privateConstructorUsedError; - @JsonKey(name: 'tx_type') - num get txType => throw _privateConstructorUsedError; - @BigIntConverter() - Uint256 get value => throw _privateConstructorUsedError; - - Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) - $TokenTransferCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $TokenTransferCopyWith<$Res> { - factory $TokenTransferCopyWith( - TokenTransfer value, $Res Function(TokenTransfer) then) = - _$TokenTransferCopyWithImpl<$Res, TokenTransfer>; - @useResult - $Res call( - {TxType direction, - @JsonKey(name: 'block_number') num blockNumber, - @JsonKey(name: 'block_timestamp') DateTime blockTimestamp, - @JsonKey(name: 'contract_address') String contractAddress, - @JsonKey(name: 'from_address') String fromAddress, - @JsonKey(name: 'log_index') num logIndex, - @JsonKey(name: 'to_address') String toAddress, - @JsonKey(name: 'transaction_hash') String transactionHash, - @JsonKey(name: 'transaction_index') num transactionIndex, - @JsonKey(name: 'tx_fee') num txFee, - @JsonKey(name: 'tx_type') num txType, - @BigIntConverter() Uint256 value}); -} - -/// @nodoc -class _$TokenTransferCopyWithImpl<$Res, $Val extends TokenTransfer> - implements $TokenTransferCopyWith<$Res> { - _$TokenTransferCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? direction = null, - Object? blockNumber = null, - Object? blockTimestamp = null, - Object? contractAddress = null, - Object? fromAddress = null, - Object? logIndex = null, - Object? toAddress = null, - Object? transactionHash = null, - Object? transactionIndex = null, - Object? txFee = null, - Object? txType = null, - Object? value = null, - }) { - return _then(_value.copyWith( - direction: null == direction - ? _value.direction - : direction // ignore: cast_nullable_to_non_nullable - as TxType, - blockNumber: null == blockNumber - ? _value.blockNumber - : blockNumber // ignore: cast_nullable_to_non_nullable - as num, - blockTimestamp: null == blockTimestamp - ? _value.blockTimestamp - : blockTimestamp // ignore: cast_nullable_to_non_nullable - as DateTime, - contractAddress: null == contractAddress - ? _value.contractAddress - : contractAddress // ignore: cast_nullable_to_non_nullable - as String, - fromAddress: null == fromAddress - ? _value.fromAddress - : fromAddress // ignore: cast_nullable_to_non_nullable - as String, - logIndex: null == logIndex - ? _value.logIndex - : logIndex // ignore: cast_nullable_to_non_nullable - as num, - toAddress: null == toAddress - ? _value.toAddress - : toAddress // ignore: cast_nullable_to_non_nullable - as String, - transactionHash: null == transactionHash - ? _value.transactionHash - : transactionHash // ignore: cast_nullable_to_non_nullable - as String, - transactionIndex: null == transactionIndex - ? _value.transactionIndex - : transactionIndex // ignore: cast_nullable_to_non_nullable - as num, - txFee: null == txFee - ? _value.txFee - : txFee // ignore: cast_nullable_to_non_nullable - as num, - txType: null == txType - ? _value.txType - : txType // ignore: cast_nullable_to_non_nullable - as num, - value: null == value - ? _value.value - : value // ignore: cast_nullable_to_non_nullable - as Uint256, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$TokenTransferImplCopyWith<$Res> - implements $TokenTransferCopyWith<$Res> { - factory _$$TokenTransferImplCopyWith( - _$TokenTransferImpl value, $Res Function(_$TokenTransferImpl) then) = - __$$TokenTransferImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {TxType direction, - @JsonKey(name: 'block_number') num blockNumber, - @JsonKey(name: 'block_timestamp') DateTime blockTimestamp, - @JsonKey(name: 'contract_address') String contractAddress, - @JsonKey(name: 'from_address') String fromAddress, - @JsonKey(name: 'log_index') num logIndex, - @JsonKey(name: 'to_address') String toAddress, - @JsonKey(name: 'transaction_hash') String transactionHash, - @JsonKey(name: 'transaction_index') num transactionIndex, - @JsonKey(name: 'tx_fee') num txFee, - @JsonKey(name: 'tx_type') num txType, - @BigIntConverter() Uint256 value}); -} - -/// @nodoc -class __$$TokenTransferImplCopyWithImpl<$Res> - extends _$TokenTransferCopyWithImpl<$Res, _$TokenTransferImpl> - implements _$$TokenTransferImplCopyWith<$Res> { - __$$TokenTransferImplCopyWithImpl( - _$TokenTransferImpl _value, $Res Function(_$TokenTransferImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? direction = null, - Object? blockNumber = null, - Object? blockTimestamp = null, - Object? contractAddress = null, - Object? fromAddress = null, - Object? logIndex = null, - Object? toAddress = null, - Object? transactionHash = null, - Object? transactionIndex = null, - Object? txFee = null, - Object? txType = null, - Object? value = null, - }) { - return _then(_$TokenTransferImpl( - direction: null == direction - ? _value.direction - : direction // ignore: cast_nullable_to_non_nullable - as TxType, - blockNumber: null == blockNumber - ? _value.blockNumber - : blockNumber // ignore: cast_nullable_to_non_nullable - as num, - blockTimestamp: null == blockTimestamp - ? _value.blockTimestamp - : blockTimestamp // ignore: cast_nullable_to_non_nullable - as DateTime, - contractAddress: null == contractAddress - ? _value.contractAddress - : contractAddress // ignore: cast_nullable_to_non_nullable - as String, - fromAddress: null == fromAddress - ? _value.fromAddress - : fromAddress // ignore: cast_nullable_to_non_nullable - as String, - logIndex: null == logIndex - ? _value.logIndex - : logIndex // ignore: cast_nullable_to_non_nullable - as num, - toAddress: null == toAddress - ? _value.toAddress - : toAddress // ignore: cast_nullable_to_non_nullable - as String, - transactionHash: null == transactionHash - ? _value.transactionHash - : transactionHash // ignore: cast_nullable_to_non_nullable - as String, - transactionIndex: null == transactionIndex - ? _value.transactionIndex - : transactionIndex // ignore: cast_nullable_to_non_nullable - as num, - txFee: null == txFee - ? _value.txFee - : txFee // ignore: cast_nullable_to_non_nullable - as num, - txType: null == txType - ? _value.txType - : txType // ignore: cast_nullable_to_non_nullable - as num, - value: null == value - ? _value.value - : value // ignore: cast_nullable_to_non_nullable - as Uint256, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$TokenTransferImpl implements _TokenTransfer { - const _$TokenTransferImpl( - {required this.direction, - @JsonKey(name: 'block_number') required this.blockNumber, - @JsonKey(name: 'block_timestamp') required this.blockTimestamp, - @JsonKey(name: 'contract_address') required this.contractAddress, - @JsonKey(name: 'from_address') required this.fromAddress, - @JsonKey(name: 'log_index') required this.logIndex, - @JsonKey(name: 'to_address') required this.toAddress, - @JsonKey(name: 'transaction_hash') required this.transactionHash, - @JsonKey(name: 'transaction_index') required this.transactionIndex, - @JsonKey(name: 'tx_fee') required this.txFee, - @JsonKey(name: 'tx_type') required this.txType, - @BigIntConverter() required this.value}); - - factory _$TokenTransferImpl.fromJson(Map json) => - _$$TokenTransferImplFromJson(json); - - @override - final TxType direction; - @override - @JsonKey(name: 'block_number') - final num blockNumber; - @override - @JsonKey(name: 'block_timestamp') - final DateTime blockTimestamp; - @override - @JsonKey(name: 'contract_address') - final String contractAddress; - @override - @JsonKey(name: 'from_address') - final String fromAddress; - @override - @JsonKey(name: 'log_index') - final num logIndex; - @override - @JsonKey(name: 'to_address') - final String toAddress; - @override - @JsonKey(name: 'transaction_hash') - final String transactionHash; - @override - @JsonKey(name: 'transaction_index') - final num transactionIndex; - @override - @JsonKey(name: 'tx_fee') - final num txFee; - @override - @JsonKey(name: 'tx_type') - final num txType; - @override - @BigIntConverter() - final Uint256 value; - - @override - String toString() { - return 'TokenTransfer(direction: $direction, blockNumber: $blockNumber, blockTimestamp: $blockTimestamp, contractAddress: $contractAddress, fromAddress: $fromAddress, logIndex: $logIndex, toAddress: $toAddress, transactionHash: $transactionHash, transactionIndex: $transactionIndex, txFee: $txFee, txType: $txType, value: $value)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$TokenTransferImpl && - (identical(other.direction, direction) || - other.direction == direction) && - (identical(other.blockNumber, blockNumber) || - other.blockNumber == blockNumber) && - (identical(other.blockTimestamp, blockTimestamp) || - other.blockTimestamp == blockTimestamp) && - (identical(other.contractAddress, contractAddress) || - other.contractAddress == contractAddress) && - (identical(other.fromAddress, fromAddress) || - other.fromAddress == fromAddress) && - (identical(other.logIndex, logIndex) || - other.logIndex == logIndex) && - (identical(other.toAddress, toAddress) || - other.toAddress == toAddress) && - (identical(other.transactionHash, transactionHash) || - other.transactionHash == transactionHash) && - (identical(other.transactionIndex, transactionIndex) || - other.transactionIndex == transactionIndex) && - (identical(other.txFee, txFee) || other.txFee == txFee) && - (identical(other.txType, txType) || other.txType == txType) && - (identical(other.value, value) || other.value == value)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => Object.hash( - runtimeType, - direction, - blockNumber, - blockTimestamp, - contractAddress, - fromAddress, - logIndex, - toAddress, - transactionHash, - transactionIndex, - txFee, - txType, - value); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$TokenTransferImplCopyWith<_$TokenTransferImpl> get copyWith => - __$$TokenTransferImplCopyWithImpl<_$TokenTransferImpl>(this, _$identity); - - @override - Map toJson() { - return _$$TokenTransferImplToJson( - this, - ); - } -} - -abstract class _TokenTransfer implements TokenTransfer { - const factory _TokenTransfer( - {required final TxType direction, - @JsonKey(name: 'block_number') required final num blockNumber, - @JsonKey(name: 'block_timestamp') required final DateTime blockTimestamp, - @JsonKey(name: 'contract_address') required final String contractAddress, - @JsonKey(name: 'from_address') required final String fromAddress, - @JsonKey(name: 'log_index') required final num logIndex, - @JsonKey(name: 'to_address') required final String toAddress, - @JsonKey(name: 'transaction_hash') required final String transactionHash, - @JsonKey(name: 'transaction_index') required final num transactionIndex, - @JsonKey(name: 'tx_fee') required final num txFee, - @JsonKey(name: 'tx_type') required final num txType, - @BigIntConverter() required final Uint256 value}) = _$TokenTransferImpl; - - factory _TokenTransfer.fromJson(Map json) = - _$TokenTransferImpl.fromJson; - - @override - TxType get direction; - @override - @JsonKey(name: 'block_number') - num get blockNumber; - @override - @JsonKey(name: 'block_timestamp') - DateTime get blockTimestamp; - @override - @JsonKey(name: 'contract_address') - String get contractAddress; - @override - @JsonKey(name: 'from_address') - String get fromAddress; - @override - @JsonKey(name: 'log_index') - num get logIndex; - @override - @JsonKey(name: 'to_address') - String get toAddress; - @override - @JsonKey(name: 'transaction_hash') - String get transactionHash; - @override - @JsonKey(name: 'transaction_index') - num get transactionIndex; - @override - @JsonKey(name: 'tx_fee') - num get txFee; - @override - @JsonKey(name: 'tx_type') - num get txType; - @override - @BigIntConverter() - Uint256 get value; - @override - @JsonKey(ignore: true) - _$$TokenTransferImplCopyWith<_$TokenTransferImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/src/utils/models/transfer.g.dart b/lib/src/utils/models/transfer.g.dart deleted file mode 100644 index a843a76..0000000 --- a/lib/src/utils/models/transfer.g.dart +++ /dev/null @@ -1,44 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'transfer.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$TokenTransferImpl _$$TokenTransferImplFromJson(Map json) => - _$TokenTransferImpl( - direction: $enumDecode(_$TxTypeEnumMap, json['direction']), - blockNumber: json['block_number'] as num, - blockTimestamp: DateTime.parse(json['block_timestamp'] as String), - contractAddress: json['contract_address'] as String, - fromAddress: json['from_address'] as String, - logIndex: json['log_index'] as num, - toAddress: json['to_address'] as String, - transactionHash: json['transaction_hash'] as String, - transactionIndex: json['transaction_index'] as num, - txFee: json['tx_fee'] as num, - txType: json['tx_type'] as num, - value: const BigIntConverter().fromJson(json['value']), - ); - -Map _$$TokenTransferImplToJson(_$TokenTransferImpl instance) => - { - 'direction': _$TxTypeEnumMap[instance.direction]!, - 'block_number': instance.blockNumber, - 'block_timestamp': instance.blockTimestamp.toIso8601String(), - 'contract_address': instance.contractAddress, - 'from_address': instance.fromAddress, - 'log_index': instance.logIndex, - 'to_address': instance.toAddress, - 'transaction_hash': instance.transactionHash, - 'transaction_index': instance.transactionIndex, - 'tx_fee': instance.txFee, - 'tx_type': instance.txType, - 'value': const BigIntConverter().toJson(instance.value), - }; - -const _$TxTypeEnumMap = { - TxType.RECEIVE: 'RECEIVE', - TxType.SEND: 'SEND', -}; diff --git a/lib/src/utils/secure_storage_repository.dart b/lib/src/utils/secure_storage_repository.dart deleted file mode 100644 index a3fb354..0000000 --- a/lib/src/utils/secure_storage_repository.dart +++ /dev/null @@ -1,136 +0,0 @@ -part of '../../utils.dart'; - -enum CredentialType { hdwallet, privateKey, passkeypair } - -class SecureStorageAuthMiddlewareError extends Error { - final String message = - "requires auth, but Authentication middleware is not set"; - - SecureStorageAuthMiddlewareError(); - - @override - String toString() { - return 'SecureStorageAuthMiddlewareError: $message'; - } -} - -class SecureStorageMiddleware implements SecureStorageRepository { - final AndroidOptions androidOptions; - final IOSOptions iosOptions; - - final FlutterSecureStorage secureStorage; - final Authentication? authMiddleware; - - final String? _credential; - - SecureStorageMiddleware( - {required this.secureStorage, this.authMiddleware, String? credential}) - : androidOptions = const AndroidOptions( - encryptedSharedPreferences: true, - ), - iosOptions = const IOSOptions( - accessibility: KeychainAccessibility.unlocked, - ), - _credential = credential; - - @override - Future delete(String key, - {SSAuthOperationOptions? options = - const SSAuthOperationOptions()}) async { - if (options!.requiresAuth) { - if (authMiddleware == null) { - throw SecureStorageAuthMiddlewareError(); - } - await authMiddleware?.authenticate(localizedReason: options.authReason); - } - await secureStorage.delete( - key: "${options.ssNameSpace ?? "vaariance"}_$key"); - } - - @override - Future read(String key, - {SSAuthOperationOptions? options = - const SSAuthOperationOptions()}) async { - if (options!.requiresAuth) { - if (authMiddleware == null) { - throw SecureStorageAuthMiddlewareError(); - } - await authMiddleware?.authenticate(localizedReason: options.authReason); - } - return await secureStorage.read( - key: "${options.ssNameSpace ?? "vaariance"}_$key"); - } - - @override - Future readCredential(CredentialType type, - {SSAuthOperationOptions? options = - const SSAuthOperationOptions()}) async { - if (options!.requiresAuth) { - if (authMiddleware == null) { - throw SecureStorageAuthMiddlewareError(); - } - await authMiddleware?.authenticate(localizedReason: options.authReason); - } - return await secureStorage.read( - key: "${options.ssNameSpace ?? "vaariance"}_${type.name}"); - } - - @override - Future save(String key, String value, - {SSAuthOperationOptions? options = - const SSAuthOperationOptions()}) async { - if (options!.requiresAuth) { - if (authMiddleware == null) { - throw SecureStorageAuthMiddlewareError(); - } - await authMiddleware?.authenticate(localizedReason: options.authReason); - } - - await secureStorage.write( - key: "${options.ssNameSpace ?? "vaariance"}_$key", value: value); - } - - @override - Future saveCredential(CredentialType type, - {SSAuthOperationOptions? options = - const SSAuthOperationOptions()}) async { - if (options!.requiresAuth) { - if (authMiddleware == null) { - throw SecureStorageAuthMiddlewareError(); - } - await authMiddleware?.authenticate(localizedReason: options.authReason); - } - await secureStorage.write( - key: "${options.ssNameSpace ?? "vaariance"}_${type.name}", - value: _credential); - } - - @override - Future update(String key, String value, - {SSAuthOperationOptions? options = - const SSAuthOperationOptions()}) async { - if (options!.requiresAuth) { - if (authMiddleware == null) { - throw SecureStorageAuthMiddlewareError(); - } - await authMiddleware?.authenticate(localizedReason: options.authReason); - } - - await secureStorage.write( - key: "${options.ssNameSpace ?? "vaariance"}_$key", value: value); - } -} - -class SSAuthOperationOptions { - final bool requiresAuth; - final String authReason; - // Namespace for uniquely addressing the secure storage keys. - // if provided the secure storage keys will be prefixed with this value, defaults to "vaariance" - // namespace ?? "vaariance" + "_" + identifier - final String? ssNameSpace; - - const SSAuthOperationOptions( - {bool? requiresAuth, String? authReason, this.ssNameSpace}) - : authReason = authReason ?? "unlock to access secure storage", - requiresAuth = requiresAuth ?? false; -} diff --git a/lib/utils.dart b/lib/utils.dart deleted file mode 100644 index 594853d..0000000 --- a/lib/utils.dart +++ /dev/null @@ -1,43 +0,0 @@ -library utils; - -import 'dart:convert'; -import 'dart:developer'; - -import 'package:crypto/crypto.dart'; -import 'package:dio/dio.dart'; -import 'package:dio_cache_interceptor/dio_cache_interceptor.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -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'; - -import 'src/interfaces/interfaces.dart'; -import 'src/utils/models/ens.dart'; -import 'src/utils/models/metadata.dart'; -import 'src/utils/models/nft.dart'; -import 'src/utils/models/price.dart'; -import 'src/utils/models/token.dart'; -import 'src/utils/models/transaction.dart'; -import 'src/utils/models/transfer.dart'; -import 'variance.dart' show Chain; - -export 'package:dio/dio.dart' show BaseOptions; - -export 'src/utils/models/ens.dart'; -export 'src/utils/models/metadata.dart'; -export 'src/utils/models/nft.dart'; -export 'src/utils/models/price.dart'; -export 'src/utils/models/token.dart'; -export 'src/utils/models/transaction.dart'; -export 'src/utils/models/transfer.dart'; - -part 'src/utils/chainbase_api.dart'; -part 'src/utils/crypto.dart'; -part 'src/utils/dio_client.dart'; -part 'src/utils/local_authentication.dart'; -part 'src/utils/secure_storage_repository.dart'; diff --git a/lib/variance.dart b/lib/variance.dart index cb402ac..a832b66 100644 --- a/lib/variance.dart +++ b/lib/variance.dart @@ -1,39 +1,27 @@ -library variance; +library; import 'dart:async'; import 'dart:convert'; -import 'dart:math'; import 'dart:typed_data'; -import 'package:asn1lib/asn1lib.dart'; -import 'package:bip32_bip44/dart_bip32_bip44.dart' as bip44; -import "package:bip39/bip39.dart" as bip39; -import 'package:cbor/cbor.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart' as http; import 'package:string_validator/string_validator.dart'; -import 'package:uuid/uuid.dart'; import 'package:web3dart/crypto.dart'; import 'package:web3dart/json_rpc.dart'; import 'package:web3dart/web3dart.dart'; -import 'package:webauthn/webauthn.dart'; +import 'package:web3_signers/web3_signers.dart'; import 'src/interfaces/interfaces.dart'; import 'src/abis/abis.dart'; -import 'src/common/common.dart'; -import 'utils.dart'; - -export 'src/abis/abis.dart' show Entrypoint; -export 'src/common/common.dart'; part 'src/4337/chains.dart'; part 'src/4337/paymaster.dart'; part 'src/4337/providers.dart'; part 'src/4337/userop.dart'; part 'src/4337/wallet.dart'; +part 'src/4337/factory.dart'; +part 'src/common/contract.dart'; part 'src/common/factory.dart'; part 'src/common/mixins.dart'; -part 'src/signers/private_key_signer.dart'; -part 'src/signers/hd_wallet_signer.dart'; -part 'src/signers/passkey_signer.dart'; +part 'src/common/pack.dart'; +part 'src/errors/wallet_errors.dart'; diff --git a/pubspec.lock b/pubspec.lock index c8c040c..520244c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -26,13 +26,13 @@ packages: source: hosted version: "2.4.2" asn1lib: - dependency: "direct main" + dependency: transitive description: name: asn1lib - sha256: "21afe4333076c02877d14f4a89df111e658a6d466cbfc802eb705eb91bd5adfd" + sha256: c9c85fedbe2188b95133cbe960e16f5f448860f7133330e272edbbca5893ddc6 url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "1.5.2" async: dependency: transitive description: @@ -41,30 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" - base58check: + blockchain_utils: dependency: transitive description: - name: base58check - sha256: "6c300dfc33e598d2fe26319e13f6243fea81eaf8204cb4c6b69ef20a625319a5" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - bip32_bip44: - dependency: "direct main" - description: - name: bip32_bip44 - sha256: "8e28e6bde00da1ed207f2c0a5361792375799196176742c0d36c71e89a5485d3" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - bip39: - dependency: "direct main" - description: - name: bip39 - sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc + name: blockchain_utils + sha256: "38ef5f4a22441ac4370aed9071dc71c460acffc37c79b344533f67d15f24c13c" url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "2.1.1" boolean_selector: dependency: transitive description: @@ -137,22 +121,6 @@ packages: url: "https://pub.dev" source: hosted version: "8.8.1" - byte_extensions: - dependency: transitive - description: - name: byte_extensions - sha256: d69db1bb5926638d937f2382fe7ad97772e61a4fb842a3143d1510224666385c - url: "https://pub.dev" - source: hosted - version: "0.0.3" - cbor: - dependency: "direct main" - description: - name: cbor - sha256: eccfcb3c16a46146517f6970d4d6df94bfd443e048d5825e85b9e23cc08d1132 - url: "https://pub.dev" - source: hosted - version: "6.1.1" characters: dependency: transitive description: @@ -201,22 +169,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" - crypto: - dependency: "direct main" + corbado_frontend_api_client: + dependency: transitive description: - name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + name: corbado_frontend_api_client + sha256: a6d65fc0da88f2e6a6e95251de0b67735556128c5d96c9b609e7b18010a6f6c1 url: "https://pub.dev" source: hosted - version: "3.0.3" - crypto_keys: + version: "1.1.0" + crypto: dependency: transitive description: - name: crypto_keys - sha256: acc19abf34623d990a0e8aec69463d74a824c31f137128f42e2810befc509ad0 + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted - version: "0.3.0+1" + version: "3.0.3" dart_style: dependency: transitive description: @@ -225,22 +193,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.4" - dio: - dependency: "direct main" - description: - name: dio - sha256: "797e1e341c3dd2f69f2dad42564a6feff3bfb87187d05abb93b9609e6f1645c3" - url: "https://pub.dev" - source: hosted - version: "5.4.0" - dio_cache_interceptor: - dependency: "direct main" - description: - name: dio_cache_interceptor - sha256: fb7905c0d12075d8786a6b63bffd64ae062d053f682cfaf28d145a2686507308 - url: "https://pub.dev" - source: hosted - version: "3.5.0" eip1559: dependency: transitive description: @@ -257,22 +209,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" - equatable: - dependency: transitive - description: - name: equatable - sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 - url: "https://pub.dev" - source: hosted - version: "2.0.5" - ffi: - dependency: transitive - description: - name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" - url: "https://pub.dev" - source: hosted - version: "2.1.0" file: dependency: transitive description: @@ -302,62 +238,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" - flutter_plugin_android_lifecycle: - dependency: transitive - description: - name: flutter_plugin_android_lifecycle - sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da - url: "https://pub.dev" - source: hosted - version: "2.0.17" - flutter_secure_storage: - dependency: "direct main" - description: - name: flutter_secure_storage - sha256: ffdbb60130e4665d2af814a0267c481bcf522c41ae2e43caf69fa0146876d685 - url: "https://pub.dev" - source: hosted - version: "9.0.0" - flutter_secure_storage_linux: - dependency: transitive - description: - name: flutter_secure_storage_linux - sha256: "3d5032e314774ee0e1a7d0a9f5e2793486f0dff2dd9ef5a23f4e3fb2a0ae6a9e" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - flutter_secure_storage_macos: - dependency: transitive - description: - name: flutter_secure_storage_macos - sha256: bd33935b4b628abd0b86c8ca20655c5b36275c3a3f5194769a7b3f37c905369c - url: "https://pub.dev" - source: hosted - version: "3.0.1" - flutter_secure_storage_platform_interface: - dependency: transitive - description: - name: flutter_secure_storage_platform_interface - sha256: "0d4d3a5dd4db28c96ae414d7ba3b8422fd735a8255642774803b2532c9a61d7e" - url: "https://pub.dev" - source: hosted - version: "1.0.2" - flutter_secure_storage_web: - dependency: transitive - description: - name: flutter_secure_storage_web - sha256: "30f84f102df9dcdaa2241866a958c2ec976902ebdaa8883fbfe525f1f2f3cf20" - url: "https://pub.dev" - source: hosted - version: "1.1.2" - flutter_secure_storage_windows: - dependency: transitive - description: - name: flutter_secure_storage_windows - sha256: "5809c66f9dd3b4b93b0a6e2e8561539405322ee767ac2f64d084e2ab5429d108" - url: "https://pub.dev" - source: hosted - version: "3.0.0" flutter_web_plugins: dependency: transitive description: flutter @@ -372,7 +252,7 @@ packages: source: hosted version: "2.4.6" freezed_annotation: - dependency: "direct main" + dependency: transitive description: name: freezed_annotation sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d @@ -403,14 +283,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.1" - hex: - dependency: transitive - description: - name: hex - sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" - url: "https://pub.dev" - source: hosted - version: "0.2.0" http: dependency: "direct main" description: @@ -435,14 +307,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" - ieee754: - dependency: transitive - description: - name: ieee754 - sha256: "7d87451c164a56c156180d34a4e93779372edd191d2c219206100b976203128c" - url: "https://pub.dev" - source: hosted - version: "1.0.3" intl: dependency: transitive description: @@ -468,7 +332,7 @@ packages: source: hosted version: "0.6.7" json_annotation: - dependency: "direct main" + dependency: transitive description: name: json_annotation sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 @@ -499,54 +363,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" - local_auth: - dependency: "direct main" - description: - name: local_auth - sha256: "27679ed8e0d7daab2357db6bb7076359e083a56b295c0c59723845301da6aed9" - url: "https://pub.dev" - source: hosted - version: "2.1.8" - local_auth_android: - dependency: "direct main" - description: - name: local_auth_android - sha256: "54e9c35ce52c06333355ab0d0f41e4c06dbca354b23426765ba41dfb1de27598" - url: "https://pub.dev" - source: hosted - version: "1.0.36" - local_auth_ios: - dependency: "direct main" - description: - name: local_auth_ios - sha256: "8293faf72ef0ac4710f209edd03916c2d4c1eeab0483bdcf9b2e659c2f7d737b" - url: "https://pub.dev" - source: hosted - version: "1.1.5" - local_auth_platform_interface: - dependency: transitive - description: - name: local_auth_platform_interface - sha256: "1b842ff177a7068442eae093b64abe3592f816afd2a533c0ebcdbe40f9d2075a" - url: "https://pub.dev" - source: hosted - version: "1.0.10" - local_auth_windows: - dependency: transitive - description: - name: local_auth_windows - sha256: "505ba3367ca781efb1c50d3132e44a2446bccc4163427bc203b9b4d8994d97ea" - url: "https://pub.dev" - source: hosted - version: "1.0.10" - logger: - dependency: transitive - description: - name: logger - sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac" - url: "https://pub.dev" - source: hosted - version: "2.0.2+1" logging: dependency: transitive description: @@ -595,78 +411,70 @@ packages: url: "https://pub.dev" source: hosted version: "5.4.4" - package_config: + openapi_generator_annotations: dependency: transitive description: - name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - path: - dependency: transitive - description: - name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + name: openapi_generator_annotations + sha256: "46f1fb675029d78e19ce9143e70ce414d738b0f6c45c49c004b4b3afdb405a5c" url: "https://pub.dev" source: hosted - version: "1.9.0" - path_provider: + version: "4.13.1" + package_config: dependency: transitive description: - name: path_provider - sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" url: "https://pub.dev" source: hosted - version: "2.1.2" - path_provider_android: + version: "2.1.0" + passkeys: dependency: transitive description: - name: path_provider_android - sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + name: passkeys + sha256: "79f07498b44d8372a569904d5e3e1de238d83abcf83b79d583a06394b98d2789" url: "https://pub.dev" source: hosted - version: "2.2.2" - path_provider_foundation: + version: "2.0.7" + passkeys_android: dependency: transitive description: - name: path_provider_foundation - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + name: passkeys_android + sha256: ab245d18d88040409d3aa93c3359a4c10c569894361c56929cb367d4b892bbaf url: "https://pub.dev" source: hosted - version: "2.3.1" - path_provider_linux: + version: "2.0.3" + passkeys_ios: dependency: transitive description: - name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + name: passkeys_ios + sha256: "411b10c3cd159c9601426d47b2dc5aa4dd1e34ef69deefaac01ff81712ea6064" url: "https://pub.dev" source: hosted - version: "2.2.1" - path_provider_platform_interface: + version: "2.0.3" + passkeys_platform_interface: dependency: transitive description: - name: path_provider_platform_interface - sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + name: passkeys_platform_interface + sha256: a1f1f5c637049f68350cf43323df9689be2e86fe5822a6e098362e7f6168351e url: "https://pub.dev" source: hosted - version: "2.1.2" - path_provider_windows: + version: "2.0.1" + passkeys_web: dependency: transitive description: - name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + name: passkeys_web + sha256: "1c7815020332b9be1af4df67686826a91b6dd29fea53be947d6082654abd6280" url: "https://pub.dev" source: hosted - version: "2.2.1" - platform: + version: "2.0.1" + path: dependency: transitive description: - name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "1.9.0" plugin_platform_interface: dependency: transitive description: @@ -691,14 +499,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - precis: - dependency: transitive - description: - name: precis - sha256: "3473e97e1a01d0f92afcdd9991549727447559edd185764a24fc54d48181bd29" - url: "https://pub.dev" - source: hosted - version: "0.0.3" pub_semver: dependency: transitive description: @@ -715,14 +515,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.3" - quiver: - dependency: transitive - description: - name: quiver - sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47 - url: "https://pub.dev" - source: hosted - version: "3.2.1" sec: dependency: transitive description: @@ -784,22 +576,6 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" - sqflite: - dependency: transitive - description: - name: sqflite - sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a" - url: "https://pub.dev" - source: hosted - version: "2.3.0" - sqflite_common: - dependency: transitive - description: - name: sqflite_common - sha256: bb4738f15b23352822f4c42a531677e5c6f522e079461fd240ead29d8d8a54a6 - url: "https://pub.dev" - source: hosted - version: "2.5.0+2" stack_trace: dependency: transitive description: @@ -840,14 +616,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" - synchronized: - dependency: transitive - description: - name: synchronized - sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" - url: "https://pub.dev" - source: hosted - version: "3.1.0+1" term_glyph: dependency: transitive description: @@ -880,16 +648,16 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" - unorm_dart: + ua_client_hints: dependency: transitive description: - name: unorm_dart - sha256: "5b35bff83fce4d76467641438f9e867dc9bcfdb8c1694854f230579d68cd8f4b" + name: ua_client_hints + sha256: "8401d7bec261f61b3d3b61cd877653ddf840de2d9e07bd164f34588572aa0c8b" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "1.2.2" uuid: - dependency: "direct main" + dependency: transitive description: name: uuid sha256: "22c94e5ad1e75f9934b766b53c742572ee2677c56bc871d850a57dad0f82127f" @@ -928,6 +696,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.0" + web3_signers: + dependency: "direct main" + description: + name: web3_signers + sha256: "7dc7f83d2eba5e78eb0310fcdf0cb4c0b167c27abafc86c8027383913dd64488" + url: "https://pub.dev" + source: hosted + version: "0.0.6-r2" web3dart: dependency: "direct main" description: @@ -952,38 +728,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.0" - webauthn: - dependency: "direct main" - description: - name: webauthn - sha256: b4a9a58c73cbf501a28e6cc78dd9b48542bfbfdc6dbcb7677ef06114bc0cf6da - url: "https://pub.dev" - source: hosted - version: "0.2.3" - webcrypto: - dependency: "direct main" - description: - name: webcrypto - sha256: a3cc45ce5efa053435505a958d32785f7497a684a859e6910d805ddf094f903f - url: "https://pub.dev" - source: hosted - version: "0.5.3" - win32: - dependency: transitive - description: - name: win32 - sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574 - url: "https://pub.dev" - source: hosted - version: "5.1.1" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d - url: "https://pub.dev" - source: hosted - version: "1.0.4" yaml: dependency: transitive description: @@ -993,5 +737,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0-194.0.dev <4.0.0" - flutter: ">=3.13.0" + dart: ">=3.2.6 <4.0.0" + flutter: ">=3.16.9" diff --git a/pubspec.yaml b/pubspec.yaml index 44a1457..a9e78e4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,25 +18,10 @@ platforms: dependencies: flutter: sdk: flutter - webauthn: ^0.2.1 web3dart: ^2.7.2 - webcrypto: ^0.5.3 - asn1lib: ^1.5.0 - uuid: ^4.1.0 - crypto: ^3.0.3 - cbor: ^6.1.1 http: ^1.1.0 - bip39: ^1.0.6 - bip32_bip44: ^1.0.0 - dio: ^5.3.2 - dio_cache_interceptor: ^3.4.3 - freezed_annotation: ^2.4.1 - json_annotation: ^4.8.1 - local_auth: ^2.1.7 - local_auth_ios: ^1.1.5 - local_auth_android: ^1.0.36 - flutter_secure_storage: ^9.0.0 string_validator: ^1.0.2 + web3_signers: ^0.0.6-r2 dev_dependencies: web3dart_builders: ^0.0.7 From 01c0005b23b500f6008ffbbf4374798772fb279c Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Sun, 24 Mar 2024 10:01:34 +0100 Subject: [PATCH 09/31] feat: complete rewrite --- example/lib/main.dart | 2 +- lib/src/4337/chains.dart | 11 -- lib/src/4337/factory.dart | 185 ++++++++++-------------- lib/src/4337/paymaster.dart | 6 +- lib/src/4337/providers.dart | 74 ++++++---- lib/src/4337/userop.dart | 118 ++++----------- lib/src/4337/wallet.dart | 54 ++----- lib/src/common/contract.dart | 12 +- lib/src/common/factory.dart | 7 +- lib/src/common/logger.dart | 55 +++++++ lib/src/common/pack.dart | 17 ++- lib/src/errors/wallet_errors.dart | 16 ++ lib/src/interfaces/interfaces.dart | 1 - lib/src/interfaces/rpc_provider.dart | 17 +-- lib/src/interfaces/smart_wallet.dart | 5 +- lib/src/interfaces/user_operations.dart | 8 - lib/variance.dart | 7 +- 17 files changed, 273 insertions(+), 322 deletions(-) create mode 100644 lib/src/common/logger.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index a679b75..8a4361e 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -59,7 +59,7 @@ Future main() async { final walletClient = SmartWallet( chain: chain, signer: hd, - bundler: BundlerProvider(chain, RPCProvider(chain.bundlerUrl!)), + bundler: BundlerProvider(chain, JsonRPCProvider(chain.bundlerUrl!)), ); // create a simple account based on hd diff --git a/lib/src/4337/chains.dart b/lib/src/4337/chains.dart index 3464bc9..557d606 100644 --- a/lib/src/4337/chains.dart +++ b/lib/src/4337/chains.dart @@ -17,17 +17,6 @@ class Chain { this.jsonRpcUrl, this.bundlerUrl, this.paymasterUrl}); - - /// asserts that [jsonRpcUrl] and [bundlerUrl] is provided - Chain validate() { - require(isURL(jsonRpcUrl), - "Chain Config Error: please provide a valid eth json rpc url"); - require(isURL(bundlerUrl), - "Chain Config Error: please provide a valid bundler url"); - require(accountFactory != null, - "Chain Config Error: please provide account factory address"); - return this; - } } //predefined Chains you can use diff --git a/lib/src/4337/factory.dart b/lib/src/4337/factory.dart index 1543b72..249b02e 100644 --- a/lib/src/4337/factory.dart +++ b/lib/src/4337/factory.dart @@ -1,123 +1,92 @@ - part of '../../variance.dart'; +part of '../../variance.dart'; - class SmartWalletFactory implements SmartWalletFactoryBase { +class SmartWalletFactory implements SmartWalletFactoryBase { + final Chain _chain; + final MSI _signer; - // createSimpleAccount - // createP256Account - // createVendorAccount - // createSafeAccount - // _createAccount - + late final JsonRPCProvider _jsonRpc; + late final BundlerProvider _bundler; + late final Contract _contract; + + SmartWalletFactory(this._chain, this._signer) + : assert(_chain.accountFactory != null, "account factory not set"), + _jsonRpc = JsonRPCProvider(_chain), + _bundler = BundlerProvider(_chain) { + _contract = Contract(_jsonRpc.rpc); } - - SmartWallet( - {required Chain chain, - required MultiSignerInterface signer, - @Deprecated( - "Bundler instance will be constructed by by factory from chain params") - required BundlerProviderBase bundler, - @Deprecated("to be removed: address will be made final in the future") - EthereumAddress? address}) - : _chain = chain.validate(), - _walletAddress = address { - // 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 jsonRpc = RPCProvider(chain.jsonRpcUrl!); - final bundlerRpc = RPCProvider(chain.bundlerUrl!); - final bundler = BundlerProvider(chain, bundlerRpc); - final fact = _AccountFactory( - address: chain.accountFactory!, chainId: chain.chainId, rpc: jsonRpc); + _SimpleAccountFactory get _simpleAccountfactory => _SimpleAccountFactory( + address: _chain.accountFactory!, + chainId: _chain.chainId, + rpc: _jsonRpc.rpc); - addPlugin('signer', signer); - addPlugin('bundler', bundler); - addPlugin('jsonRpc', jsonRpc); - addPlugin('contract', Contract(jsonRpc)); - addPlugin('factory', fact); + Future createSimpleAccount(Uint256 salt, {int? index}) async { + final signer = _signer.getAddress(index: index ?? 0); + final address = await _simpleAccountfactory.getAddress( + EthereumAddress.fromHex(signer), salt.value); + final initCode = _getInitCode('createAccount', [signer, salt.value]); + return _createAccount(_chain, address, initCode); + } - if (chain.paymasterUrl != null) { - final paymasterRpc = RPCProvider(chain.paymasterUrl!); - final paymaster = Paymaster(chain, paymasterRpc); - addPlugin('paymaster', paymaster); + Future createP256Account(T keyPair, Uint256 salt) { + switch (keyPair.runtimeType) { + case PassKeyPair _: + return _createPasskeyAccount(keyPair as PassKeyPair, salt); + case P256Credential _: + return _createSecureEnclaveAccount(keyPair as P256Credential, salt); + default: + throw ArgumentError("unsupported key pair type"); } } - /// Initializes a [SmartWallet] instance for a specific chain with the provided parameters. - /// - /// Parameters: - /// - `chain`: The blockchain [Chain] associated with the smart wallet. - /// - `signer`: The [MultiSignerInterface] responsible for signing transactions. - /// - `bundler`: The [BundlerProviderBase] that provides bundling services. - /// - `address`: Optional Ethereum address associated with the smart wallet. - /// - `initCallData`: Optional initialization calldata of the factory create method as a [Uint8List]. - /// - /// Returns: - /// A fully initialized [SmartWallet] instance. - /// - /// Example: - /// ```dart - /// var smartWallet = SmartWallet.init( - /// chain: Chain.ethereum, - /// signer: myMultiSigner, - /// bundler: myBundler, - /// address: myWalletAddress, - /// initCallData: Uint8List.fromList([0x01, 0x02, 0x03]), - /// ); - /// ``` - factory SmartWallet.init( - {required Chain chain, - required MultiSignerInterface signer, - @Deprecated( - "Bundler instance will be constructed by by factory from chain params") - required BundlerProviderBase bundler, - @Deprecated("address will be made final in the future") - EthereumAddress? address, - @Deprecated("seperation of factory from wallet soon will be enforced") - Uint8List? initCallData}) { - final instance = SmartWallet( - chain: chain, signer: signer, bundler: bundler, address: address); - return instance; + Future createSafeAccount() { + // TODO: implement createSafeAccount + throw UnimplementedError(); } -@override -Future createSimplePasskeyAccount( - PassKeyPair pkp, Uint256 salt) async { - _initCalldata = _getInitCallData('createPasskeyAccount', [ - pkp.credentialHexBytes, - pkp.publicKey[0].value, - pkp.publicKey[1].value, - salt.value - ]); - - await getSimplePassKeyAccountAddress(pkp, salt) - .then((addr) => {_walletAddress = addr}); - return this; -} + Future createVendorAccount( + EthereumAddress address, + Uint8List initCode, + ) async { + return _createAccount(_chain, address, initCode); + } -@override -Future createSimpleAccount(Uint256 salt, {int? index}) async { - EthereumAddress signer = EthereumAddress.fromHex( - plugin('signer').getAddress(index: index ?? 0)); - _initCalldata = _getInitCallData('createAccount', [signer, salt.value]); - await getSimpleAccountAddress(signer, salt) - .then((addr) => {_walletAddress = addr}); - return this; -} + Future _createPasskeyAccount( + PassKeyPair pkp, Uint256 salt) async { + final initCode = _getInitCode('createPasskeyAccount', [ + hexToBytes(pkp.credentialHex), + pkp.publicKey.item1.value, + pkp.publicKey.item2.value, + salt.value + ]); + final address = await _simpleAccountfactory.getPasskeyAccountAddress( + hexToBytes(pkp.credentialHex), + pkp.publicKey.item1.value, + pkp.publicKey.item2.value, + salt.value); + return _createAccount(_chain, address, initCode); + } -@override -Future getSimpleAccountAddress( - EthereumAddress signer, Uint256 salt) => - plugin('factory').getAddress(signer, salt.value); + Future _createSecureEnclaveAccount( + P256Credential p256, Uint256 salt) { + // TODO: implement _createSimpleSecureEnclaveAccount + throw UnimplementedError(); + } -@override -Future getSimplePassKeyAccountAddress( - PassKeyPair pkp, Uint256 salt) => - plugin('factory').getPasskeyAccountAddress( - pkp.credentialHexBytes, - pkp.publicKey[0].value, - pkp.publicKey[1].value, - salt.value); + SmartWallet _createAccount( + Chain chain, EthereumAddress address, Uint8List initCalldata) { + return SmartWallet(chain, address, initCalldata) + ..addPlugin('signer', _signer) + ..addPlugin('bundler', _bundler) + ..addPlugin('jsonRpc', _jsonRpc) + ..addPlugin('contract', _contract); + } -Uint8List _getInitCallData(String functionName, List params) => - plugin('factory').self.function(functionName).encodeCall(params); + Uint8List _getInitCode(String functionName, List params) { + final initCalldata = + _simpleAccountfactory.self.function(functionName).encodeCall(params); + List extended = _chain.accountFactory!.addressBytes.toList(); + extended.addAll(initCalldata); + return Uint8List.fromList(extended); + } +} diff --git a/lib/src/4337/paymaster.dart b/lib/src/4337/paymaster.dart index a96b65a..bbd73a7 100644 --- a/lib/src/4337/paymaster.dart +++ b/lib/src/4337/paymaster.dart @@ -24,7 +24,7 @@ class PaymasterResponse { } class Paymaster { - final RPCProviderBase _rpc; + final RPCBase _rpc; final Chain _chain; Map? _context; @@ -32,7 +32,9 @@ class Paymaster { _context = context; } - Paymaster(this._chain, this._rpc, [this._context]); + Paymaster(this._chain, [this._context]) + : assert(isURL(_chain.paymasterUrl), "invalid paymaster Url"), + _rpc = RPCBase(_chain.paymasterUrl!); Future intercept(UserOperation operation) async { final paymasterResponse = await sponsorUserOperation( diff --git a/lib/src/4337/providers.dart b/lib/src/4337/providers.dart index 903b5e2..b61e5bd 100644 --- a/lib/src/4337/providers.dart +++ b/lib/src/4337/providers.dart @@ -1,19 +1,25 @@ part of '../../variance.dart'; class BundlerProvider implements BundlerProviderBase { - final Chain _chain; - final RPCProviderBase _rpc; + final RPCBase _rpc; - late final bool _initialized; - - BundlerProvider(this._chain, this._rpc) { - _initializeBundlerProvider(); + BundlerProvider(Chain chain) + : assert(isURL(chain.bundlerUrl), "invalid bundler Url"), + _rpc = RPCBase(chain.bundlerUrl!) { + _rpc + .send('eth_chainId') + .then(BigInt.parse) + .then((value) => value.toInt() == chain.chainId) + .then((value) => _initialized = value == true); } + late final bool _initialized; + @override Future estimateUserOperationGas( Map userOp, EntryPoint entrypoint) async { - require(_initialized, "estimateUserOpGas: Wallet Provider not initialized"); + Logger.conditionalWarning( + !_initialized, "estimateUserOpGas may fail: chainId mismatch"); final opGas = await _rpc.send>( 'eth_estimateUserOperationGas', [userOp, entrypoint.hex]); return UserOperationGas.fromMap(opGas); @@ -21,7 +27,8 @@ class BundlerProvider implements BundlerProviderBase { @override Future getUserOperationByHash(String userOpHash) async { - require(_initialized, "getUserOpByHash: Wallet Provider not initialized"); + Logger.conditionalWarning( + !_initialized, "getUserOpByHash may fail: chainId mismatch"); final opExtended = await _rpc .send>('eth_getUserOperationByHash', [userOpHash]); return UserOperationByHash.fromMap(opExtended); @@ -29,7 +36,8 @@ class BundlerProvider implements BundlerProviderBase { @override Future getUserOpReceipt(String userOpHash) async { - require(_initialized, "getUserOpReceipt: Wallet Provider not initialized"); + Logger.conditionalWarning( + !_initialized, "getUserOpReceipt may fail: chainId mismatch"); final opReceipt = await _rpc.send>( 'eth_getUserOperationReceipt', [userOpHash]); return UserOperationReceipt.fromMap(opReceipt); @@ -38,7 +46,8 @@ class BundlerProvider implements BundlerProviderBase { @override Future sendUserOperation( Map userOp, EntryPoint entrypoint) async { - require(_initialized, "sendUserOp: Wallet Provider not initialized"); + Logger.conditionalWarning( + !_initialized, "sendUserOp may fail: chainId mismatch"); final opHash = await _rpc .send('eth_sendUserOperation', [userOp, entrypoint.hex]); return UserOperationResponse(opHash); @@ -50,38 +59,33 @@ class BundlerProvider implements BundlerProviderBase { await _rpc.send>('eth_supportedEntryPoints'); return List.castFrom(entrypointList); } - - Future _initializeBundlerProvider() async { - final chainId = await _rpc - .send('eth_chainId') - .then(BigInt.parse) - .then((value) => value.toInt()); - require(chainId == _chain.chainId, - "bundler ${_rpc.url} is on chainId $chainId, but provider is on chainId ${_chain.chainId}"); - _initialized = true; - } } -class RPCProvider extends JsonRPC implements RPCProviderBase { - RPCProvider(String url) : super(url, http.Client()); +class JsonRPCProvider implements JsonRPCProviderBase { + final RPCBase rpc; + + JsonRPCProvider(Chain chain) + : assert(isURL(chain.jsonRpcUrl), "invalid jsonRpc Url"), + rpc = RPCBase(chain.jsonRpcUrl!); @override Future estimateGas(EthereumAddress to, String calldata) { - return _makeRPCCall('eth_estimateGas', [ + return rpc.send('eth_estimateGas', [ {'to': to.hex, 'data': calldata} ]).then(hexToInt); } @override Future getBlockNumber() { - return _makeRPCCall('eth_blockNumber') + return rpc + .send('eth_blockNumber') .then(hexToInt) .then((value) => value.toInt()); } @override Future> getEip1559GasPrice() async { - final fee = await _makeRPCCall("eth_maxPriorityFeePerGas"); + final fee = await rpc.send("eth_maxPriorityFeePerGas"); final tip = Uint256.fromHex(fee); final mul = Uint256(BigInt.from(100 * 13)); final buffer = tip / mul; @@ -109,11 +113,27 @@ class RPCProvider extends JsonRPC implements RPCProviderBase { @override Future getLegacyGasPrice() async { - final data = await _makeRPCCall('eth_gasPrice'); + final data = await rpc.send('eth_gasPrice'); return EtherAmount.fromBigInt(EtherUnit.wei, hexToInt(data)); } +} - @override +class RPCBase extends JsonRPC { + RPCBase(String url) : super(url, http.Client()); + + /// Asynchronously sends an RPC call to the Ethereum node for the specified function and parameters. + /// + /// Parameters: + /// - `function`: The Ethereum RPC function to call. eg: `eth_getBalance` + /// - `params`: Optional parameters for the RPC call. + /// + /// Returns: + /// A [Future] that completes with the result of the RPC call. + /// + /// Example: + /// ```dart + /// var result = await send('eth_getBalance', ['0x9876543210abcdef9876543210abcdef98765432']); + /// ``` Future send(String function, [List? params]) { return _makeRPCCall(function, params); } diff --git a/lib/src/4337/userop.dart b/lib/src/4337/userop.dart index 1ea3706..12f57a4 100644 --- a/lib/src/4337/userop.dart +++ b/lib/src/4337/userop.dart @@ -14,30 +14,20 @@ 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; @@ -52,10 +42,8 @@ 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, }); @@ -69,25 +57,11 @@ class UserOperation implements UserOperationBase { nonce: BigInt.parse(map['nonce'] as String), initCode: hexToBytes(map['initCode'] as String), callData: hexToBytes(map['callData'] 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), + callGasLimit: BigInt.parse(map['callGasLimit'] as String), + verificationGasLimit: BigInt.parse(map['verificationGasLimit'] as String), preVerificationGas: BigInt.parse(map['preVerificationGas'] 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), + maxFeePerGas: BigInt.parse(map['maxFeePerGas'] as String), + maxPriorityFeePerGas: BigInt.parse(map['maxPriorityFeePerGas'] as String), signature: map['signature'] as String, paymasterAndData: hexToBytes(map['paymasterAndData'] as String), ); @@ -116,18 +90,17 @@ 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, - Uint8List? accountGasLimits, - BigInt? preVerificationGas, - BigInt? maxFeePerGas, - BigInt? maxPriorityFeePerGas, - Uint8List? gasFees}) => + factory UserOperation.partial({ + required Uint8List callData, + EthereumAddress? sender, + BigInt? nonce, + Uint8List? initCode, + BigInt? callGasLimit, + BigInt? verificationGasLimit, + BigInt? preVerificationGas, + BigInt? maxFeePerGas, + BigInt? maxPriorityFeePerGas, + }) => UserOperation( sender: sender ?? Constants.zeroAddress, nonce: nonce ?? BigInt.zero, @@ -136,10 +109,8 @@ 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), ); @@ -151,11 +122,9 @@ class UserOperation implements UserOperationBase { Uint8List? callData, BigInt? callGasLimit, BigInt? verificationGasLimit, - Uint8List? accountGasLimits, BigInt? preVerificationGas, BigInt? maxFeePerGas, BigInt? maxPriorityFeePerGas, - Uint8List? gasFees, String? signature, Uint8List? paymasterAndData, }) { @@ -166,11 +135,9 @@ 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, ); @@ -194,9 +161,9 @@ class UserOperation implements UserOperationBase { nonce, keccak256(initCode), keccak256(callData), - accountGasLimits, + packUints(verificationGasLimit, callGasLimit), preVerificationGas, - gasFees, + packUints(maxPriorityFeePerGas, maxFeePerGas), keccak256(paymasterAndData), ])); } else { @@ -235,46 +202,32 @@ class UserOperation implements UserOperationBase { @override Map toMap() { - Map op = { + return { '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['callGasLimit'] = '0x${callGasLimit.toRadixString(16)}'; - op['verificationGasLimit'] = - '0x${verificationGasLimit.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) { return copyWith( - callGasLimit: opGas?.callGasLimit, - verificationGasLimit: opGas?.verificationGasLimit, - accountGasLimits: opGas?.accountGasLimits, - preVerificationGas: opGas?.preVerificationGas, - maxFeePerGas: (feePerGas?["maxFeePerGas"] as EtherAmount?)?.getInWei, - maxPriorityFeePerGas: - (feePerGas?["maxPriorityFeePerGas"] as EtherAmount?)?.getInWei, - gasFees: feePerGas?["gasFees"] as Uint8List?); + callGasLimit: opGas?.callGasLimit, + verificationGasLimit: opGas?.verificationGasLimit, + preVerificationGas: opGas?.preVerificationGas, + maxFeePerGas: (feePerGas?["maxFeePerGas"] as EtherAmount?)?.getInWei, + maxPriorityFeePerGas: + (feePerGas?["maxPriorityFeePerGas"] as EtherAmount?)?.getInWei, + ); } Future validate(bool deployed, [String? initCode]) async { @@ -316,29 +269,20 @@ class UserOperationByHash { class UserOperationGas { final BigInt callGasLimit; final BigInt verificationGasLimit; - final Uint8List accountGasLimits; final BigInt preVerificationGas; BigInt? validAfter; BigInt? validUntil; UserOperationGas({ required this.callGasLimit, required this.verificationGasLimit, - required this.accountGasLimits, required this.preVerificationGas, this.validAfter, this.validUntil, }); 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), + callGasLimit: BigInt.parse(map['callGasLimit']), + verificationGasLimit: BigInt.parse(map['verificationGasLimit']), preVerificationGas: BigInt.parse(map['preVerificationGas']), validAfter: map['validAfter'] != null ? BigInt.parse(map['validAfter']) : null, diff --git a/lib/src/4337/wallet.dart b/lib/src/4337/wallet.dart index 3461be1..4f08bd9 100644 --- a/lib/src/4337/wallet.dart +++ b/lib/src/4337/wallet.dart @@ -5,9 +5,9 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { final EthereumAddress _walletAddress; - Uint8List _initCalldata; + Uint8List _initCode; - SmartWallet(this._chain, this._walletAddress, this._initCalldata); + SmartWallet(this._chain, this._walletAddress, this._initCode); @override EthereumAddress get address => _walletAddress; @@ -21,8 +21,7 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { plugin("contract").deployed(_walletAddress); @override - String get initCode => - _chain.accountFactory!.hexEip55 + hexlify(_initCalldata).substring(2); + String get initCode => hexlify(_initCode); @override Future get initCodeGas => _initCodeGas; @@ -33,18 +32,12 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { @override String? get toHex => _walletAddress.hexEip55; - Uint8List get _initCodeBytes { - List extended = _chain.accountFactory!.addressBytes.toList(); - extended.addAll(_initCalldata); - return Uint8List.fromList(extended); - } - - Future get _initCodeGas => plugin('jsonRpc') + Future get _initCodeGas => plugin('jsonRpc') .estimateGas(_chain.entrypoint.address, initCode); @override - void dangerouslySetInitCallData(Uint8List code) { - _initCalldata = code; + void dangerouslySetInitCode(Uint8List code) { + _initCode = code; } @override @@ -54,7 +47,7 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { }) => UserOperation.partial( callData: callData, - initCode: _initCodeBytes, + initCode: _initCode, sender: _walletAddress, nonce: customNonce); @@ -91,19 +84,17 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { .catchError((e) => throw SendError(e.toString(), op)); @override - Future sendUserOperation(UserOperation op, - {String? id}) => - signUserOperation(op, id: id) + Future sendUserOperation(UserOperation op) => + signUserOperation(op) .then(sendSignedUserOperation) .catchError((e) => retryOp(() => sendSignedUserOperation(op), e)); @override Future signUserOperation(UserOperation userOp, - {bool update = true, String? id, int? index}) async { + {bool update = true, int? index}) async { if (update) userOp = await _updateUserOperation(userOp); final opHash = userOp.hash(_chain); - if (hasPlugin('paymaster')) { userOp = await plugin('paymaster').intercept(userOp); } @@ -113,7 +104,6 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { userOp.signature = hexlify(signature); userOp.validate(userOp.nonce > BigInt.zero, initCode); - return userOp; } @@ -127,29 +117,17 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { .catchError((e) => throw NonceError(e.toString(), _walletAddress))); Future _updateUserOperation(UserOperation op) => - Future.wait( - [_getNonce(), plugin('jsonRpc').getGasPrice()]) - .then((responses) { + 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: _formatGasFees(responses[1])); + return _updateUserOperationGas(op, feePerGas: 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') @@ -158,5 +136,3 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { .then((op) => multiply(op)) .catchError((e) => throw EstimateError(e.toString(), op)); } - -typedef MSI = MultiSignerInterface; diff --git a/lib/src/common/contract.dart b/lib/src/common/contract.dart index b04e2b7..867cf37 100644 --- a/lib/src/common/contract.dart +++ b/lib/src/common/contract.dart @@ -1,11 +1,11 @@ part of '../../variance.dart'; -/// A wrapper for interacting with deployed Ethereum contracts through [RPCProvider]. +/// A wrapper for interacting with deployed Ethereum contracts through [JsonRPCProvider]. class Contract { - final RPCProviderBase _provider; + final RPCBase _rpc; Contract( - this._provider, + this._rpc, ); /// Asynchronously calls a function on a smart contract with the provided parameters. @@ -43,7 +43,7 @@ class Contract { : "0x", if (sender != null) 'from': sender.hex, }; - return _provider.send('eth_call', [ + return _rpc.send('eth_call', [ calldata, BlockNum.current().toBlockParam() ]).then((value) => function.decodeReturnValues(value)); @@ -71,7 +71,7 @@ class Contract { if (address == null) { return Future.value(false); } - final isDeployed = _provider + final isDeployed = _rpc .send('eth_getCode', [address.hex, atBlock.toBlockParam()]) .then(hexToBytes) .then((value) => value.isNotEmpty); @@ -100,7 +100,7 @@ class Contract { if (address == null) { return Future.value(EtherAmount.zero()); } - return _provider + return _rpc .send('eth_getBalance', [address.hex, atBlock.toBlockParam()]) .then(BigInt.parse) .then((value) => EtherAmount.fromBigInt(EtherUnit.wei, value)); diff --git a/lib/src/common/factory.dart b/lib/src/common/factory.dart index bde8891..5494f96 100644 --- a/lib/src/common/factory.dart +++ b/lib/src/common/factory.dart @@ -1,7 +1,8 @@ part of '../../variance.dart'; -class _AccountFactory extends AccountFactory implements AccountFactoryBase { - _AccountFactory( - {required super.address, super.chainId, required RPCProviderBase rpc}) +class _SimpleAccountFactory extends AccountFactory + implements AccountFactoryBase { + _SimpleAccountFactory( + {required super.address, super.chainId, required RPCBase rpc}) : super(client: Web3Client.custom(rpc)); } diff --git a/lib/src/common/logger.dart b/lib/src/common/logger.dart new file mode 100644 index 0000000..0b49d2e --- /dev/null +++ b/lib/src/common/logger.dart @@ -0,0 +1,55 @@ +class Logger { + static final _errorColor = '\x1B[31m'; + static final _warningColor = '\x1B[33m'; + static final _resetColor = '\x1B[0m'; + + static void warning(String message) { + _logMessage('WARNING', _warningColor, message); + } + + static void error(String message, [Object? error, StackTrace? stackTrace]) { + _logError('ERROR', _errorColor, message, error, stackTrace); + } + + static void conditionalWarning(bool condition, String message) { + if (condition) { + _logMessage('WARNING', _warningColor, message); + } + } + + static void conditionalError(bool condition, String message, + [Object? error, StackTrace? stackTrace]) { + if (condition) { + _logError('ERROR', _errorColor, message, error, stackTrace); + } + } + + static void _logMessage(String level, String color, String message) { + _log(level, color, message); + } + + static void _logError(String level, String color, String message, + [Object? error, StackTrace? stackTrace]) { + String errorMessage = '$message'; + if (error != null) { + errorMessage += '\nError: $error'; + } + if (stackTrace != null) { + errorMessage += '\nStackTrace: $stackTrace'; + } + _log(level, color, errorMessage); + } + + static void _log(String level, String color, String message) { + final now = DateTime.now(); + final formattedTime = '${now.year.toString().padLeft(4, '0')}-' + '${now.month.toString().padLeft(2, '0')}-' + '${now.day.toString().padLeft(2, '0')} ' + '${now.hour.toString().padLeft(2, '0')}:' + '${now.minute.toString().padLeft(2, '0')}:' + '${now.second.toString().padLeft(2, '0')}'; + + final logMessage = '$formattedTime [$color$level$_resetColor] $message'; + print(logMessage); + } +} diff --git a/lib/src/common/pack.dart b/lib/src/common/pack.dart index 5a3ea86..a846ec8 100644 --- a/lib/src/common/pack.dart +++ b/lib/src/common/pack.dart @@ -16,12 +16,15 @@ part of '../../variance.dart'; /// 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 shiftedHigh = high128 << 128; + final combined = shiftedHigh + low128; + return hexToBytes(combined.toRadixString(16).padLeft(64, '0')); +} - final high = high128.toRadixString(16).padLeft(32, '0'); - final low = low128.toRadixString(16).padLeft(32, '0'); - final packedBytes = hexToBytes('$high$low'); - return packedBytes; +List unpackUints(Uint8List bytes) { + final hex = bytesToHex(bytes); + final value = BigInt.parse(hex, radix: 16); + final mask = + BigInt.from(0xFFFFFFFFFFFFFFFF) << 64 | BigInt.from(0xFFFFFFFFFFFFFFFF); + return [value >> 128, value & mask]; } diff --git a/lib/src/errors/wallet_errors.dart b/lib/src/errors/wallet_errors.dart index 6b3204f..9f2f0e2 100644 --- a/lib/src/errors/wallet_errors.dart +++ b/lib/src/errors/wallet_errors.dart @@ -48,3 +48,19 @@ class SendError extends Error { '''; } } + +class InvalidAccountFactoryAddress extends Error { + final EthereumAddress? address; + final String message = 'Invalid account factory address!'; + + InvalidAccountFactoryAddress(this.address); + + @override + String toString() { + return ''' + $message + -------------------------------------------------- + Provided factory address: $address + '''; + } +} diff --git a/lib/src/interfaces/interfaces.dart b/lib/src/interfaces/interfaces.dart index f815ea8..b696a27 100644 --- a/lib/src/interfaces/interfaces.dart +++ b/lib/src/interfaces/interfaces.dart @@ -1,7 +1,6 @@ import 'dart:typed_data'; import 'package:web3_signers/web3_signers.dart' show PassKeyPair, Uint256; -import 'package:web3dart/json_rpc.dart' show RpcService; import 'package:web3dart/web3dart.dart'; import '../../variance.dart' diff --git a/lib/src/interfaces/rpc_provider.dart b/lib/src/interfaces/rpc_provider.dart index 3aff221..02cc99e 100644 --- a/lib/src/interfaces/rpc_provider.dart +++ b/lib/src/interfaces/rpc_provider.dart @@ -4,7 +4,7 @@ part of 'interfaces.dart'; /// /// Implementations of this class are expected to provide functionality for specifically interacting /// with bundlers only. -abstract class RPCProviderBase implements RpcService { +abstract class JsonRPCProviderBase { /// Asynchronously estimates the gas cost for a transaction to the specified address with the given calldata. /// /// Parameters: @@ -74,19 +74,4 @@ abstract class RPCProviderBase implements RpcService { /// ``` /// This method uses an ethereum jsonRPC to fetch the legacy gas price from the Ethereum node. Future getLegacyGasPrice(); - - /// Asynchronously sends an RPC call to the Ethereum node for the specified function and parameters. - /// - /// Parameters: - /// - `function`: The Ethereum RPC function to call. eg: `eth_getBalance` - /// - `params`: Optional parameters for the RPC call. - /// - /// Returns: - /// A [Future] that completes with the result of the RPC call. - /// - /// Example: - /// ```dart - /// var result = await send('eth_getBalance', ['0x9876543210abcdef9876543210abcdef98765432']); - /// ``` - Future send(String function, [List? params]); } diff --git a/lib/src/interfaces/smart_wallet.dart b/lib/src/interfaces/smart_wallet.dart index d3101ea..8da3105 100644 --- a/lib/src/interfaces/smart_wallet.dart +++ b/lib/src/interfaces/smart_wallet.dart @@ -62,7 +62,7 @@ abstract class SmartWalletBase { /// ```dart /// dangerouslySetInitCallData(Uint8List.fromList([0x01, 0x02, 0x03])); /// ``` - void dangerouslySetInitCallData(Uint8List code); + void dangerouslySetInitCode(Uint8List code); /// Asynchronously transfers native Token (ETH) to the specified recipient with the given amount. /// @@ -180,7 +180,6 @@ abstract class SmartWalletBase { /// Parameters: /// - `userOp`: The [UserOperation] to be signed. /// - `update`: Optional parameter indicating whether to update the user operation before signing. Defaults to `true`. - /// - `id`: Optional identifier (credential Id) when using a passkey signer Defaults to `null`. /// - `index`: Optional index parameter for selecting a signer. Defaults to `null`. /// /// Returns: @@ -195,6 +194,6 @@ abstract class SmartWalletBase { Future signUserOperation( UserOperation userOp, { bool update = true, - String? id, + int? index, }); } diff --git a/lib/src/interfaces/user_operations.dart b/lib/src/interfaces/user_operations.dart index 2a92e8e..e6985b4 100644 --- a/lib/src/interfaces/user_operations.dart +++ b/lib/src/interfaces/user_operations.dart @@ -13,24 +13,16 @@ 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/variance.dart b/lib/variance.dart index a832b66..a252eef 100644 --- a/lib/variance.dart +++ b/lib/variance.dart @@ -6,20 +6,21 @@ import 'dart:typed_data'; import 'package:http/http.dart' as http; import 'package:string_validator/string_validator.dart'; +import 'package:web3_signers/web3_signers.dart'; import 'package:web3dart/crypto.dart'; import 'package:web3dart/json_rpc.dart'; import 'package:web3dart/web3dart.dart'; -import 'package:web3_signers/web3_signers.dart'; -import 'src/interfaces/interfaces.dart'; import 'src/abis/abis.dart'; +import 'src/common/logger.dart'; +import 'src/interfaces/interfaces.dart'; part 'src/4337/chains.dart'; +part 'src/4337/factory.dart'; part 'src/4337/paymaster.dart'; part 'src/4337/providers.dart'; part 'src/4337/userop.dart'; part 'src/4337/wallet.dart'; -part 'src/4337/factory.dart'; part 'src/common/contract.dart'; part 'src/common/factory.dart'; part 'src/common/mixins.dart'; From 527b2a500bcad42b4ec6cd1312835f0d15f8881e Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Mon, 25 Mar 2024 19:44:19 +0100 Subject: [PATCH 10/31] feat: segregate factories in factory --- lib/src/4337/chains.dart | 70 ---------- lib/src/4337/factory.dart | 65 ++++++--- lib/src/4337/paymaster.dart | 3 +- lib/src/4337/providers.dart | 4 +- lib/src/4337/wallet.dart | 2 +- lib/src/abis/abis.dart | 3 +- lib/src/abis/accountFactory.abi.json | 95 ------------- lib/src/abis/accountFactory.g.dart | 154 --------------------- lib/src/abis/p256AccountFactory.abi.json | 48 +++++++ lib/src/abis/p256AccountFactory.g.dart | 85 ++++++++++++ lib/src/abis/simpleAccountFactory.abi.json | 74 ++++++++++ lib/src/abis/simpleAccountFactory.g.dart | 85 ++++++++++++ lib/src/common/factories.dart | 15 ++ lib/src/common/factory.dart | 8 -- lib/src/common/mixins.dart | 2 +- lib/src/common/pack.dart | 13 ++ lib/src/errors/wallet_errors.dart | 110 +++++++++++++-- lib/src/interfaces/account_factory.dart | 20 +-- lib/src/interfaces/bundler_provider.dart | 3 +- lib/src/interfaces/interfaces.dart | 1 + lib/variance.dart | 2 +- pubspec.lock | 32 ----- pubspec.yaml | 2 - 23 files changed, 481 insertions(+), 415 deletions(-) delete mode 100644 lib/src/abis/accountFactory.abi.json delete mode 100644 lib/src/abis/accountFactory.g.dart create mode 100644 lib/src/abis/p256AccountFactory.abi.json create mode 100644 lib/src/abis/p256AccountFactory.g.dart create mode 100644 lib/src/abis/simpleAccountFactory.abi.json create mode 100644 lib/src/abis/simpleAccountFactory.g.dart create mode 100644 lib/src/common/factories.dart delete mode 100644 lib/src/common/factory.dart diff --git a/lib/src/4337/chains.dart b/lib/src/4337/chains.dart index 557d606..e061c18 100644 --- a/lib/src/4337/chains.dart +++ b/lib/src/4337/chains.dart @@ -52,71 +52,23 @@ class Chains { jsonRpcUrl: "https://arb1.arbitrum.io/rpc", entrypoint: EntryPoint.v06, ), - Network.mantle: Chain( - chainId: 5000, - explorer: "https://explorer.mantle.xyz/", - jsonRpcUrl: "https://rpc.mantle.xyz/", - entrypoint: EntryPoint.v06, - ), Network.linea: Chain( chainId: 59144, explorer: "https://lineascan.build/", jsonRpcUrl: "https://rpc.linea.build", entrypoint: EntryPoint.v06), - Network.avalanche: Chain( - chainId: 43114, - explorer: "https://snowtrace.io/", - jsonRpcUrl: "https://api.avax.network/ext/bc/C/rpc", - entrypoint: EntryPoint.v06, - ), - Network.gnosis: Chain( - chainId: 100, - explorer: "https://gnosisscan.io/", - jsonRpcUrl: "https://rpc.ankr.com/gnosis", - entrypoint: EntryPoint.v06, - ), - Network.celo: Chain( - chainId: 42220, - explorer: "https://celoscan.io/", - jsonRpcUrl: "https://forno.celo.org", - entrypoint: EntryPoint.v06, - ), - Network.fantom: Chain( - chainId: 250, - explorer: "https://ftmscan.com/", - jsonRpcUrl: "https://rpc.fantom.network", - entrypoint: EntryPoint.v06, - ), Network.opBnB: Chain( chainId: 204, explorer: "http://opbnbscan.com/", jsonRpcUrl: "https://opbnb-mainnet-rpc.bnbchain.org", entrypoint: EntryPoint.v06, ), - Network.arbitrumNova: Chain( - chainId: 42170, - explorer: "https://nova.arbiscan.io/", - jsonRpcUrl: "https://nova.arbitrum.io/rpc", - entrypoint: EntryPoint.v06, - ), - Network.polygonzkEvm: Chain( - chainId: 1101, - explorer: "https://zkevm.polygonscan.com/", - jsonRpcUrl: "https://polygonzkevm-mainnet.g.alchemy.com/v2/demo", - entrypoint: EntryPoint.v06, - ), Network.scroll: Chain( chainId: 534352, explorer: "https://scrollscan.com/", jsonRpcUrl: "https://rpc.scroll.io/", entrypoint: EntryPoint.v06, ), - Network.mode: Chain( - chainId: 34443, - explorer: "https://explorer.mode.network/", - jsonRpcUrl: "https://mainnet.mode.network/", - entrypoint: EntryPoint.v06, - ), Network.sepolia: Chain( chainId: 11155111, explorer: "https://sepolia.etherscan.io/", @@ -135,18 +87,6 @@ class Chains { jsonRpcUrl: "https://api-sepolia.basescan.org/api", entrypoint: EntryPoint.v06, ), - Network.fuji: Chain( - chainId: 43113, - explorer: "https://testnet.snowtrace.io/", - jsonRpcUrl: "https://api.avax-test.network/ext/bc/C/rpc", - entrypoint: EntryPoint.v06, - ), - Network.katla: Chain( - chainId: 167008, - explorer: "https://explorer.katla.taiko.xyz/", - jsonRpcUrl: "https://rpc.katla.taiko.xyz", - entrypoint: EntryPoint.v06, - ), Network.localhost: Chain( chainId: 1337, explorer: "http://localhost:8545", @@ -193,24 +133,14 @@ enum Network { optimism, base, arbitrumOne, - mantle, linea, - avalanche, - gnosis, - celo, - fantom, opBnB, - arbitrumNova, - polygonzkEvm, scroll, - mode, // testnet sepolia, mumbai, baseTestent, - fuji, - katla, // localhost localhost diff --git a/lib/src/4337/factory.dart b/lib/src/4337/factory.dart index 249b02e..deb5083 100644 --- a/lib/src/4337/factory.dart +++ b/lib/src/4337/factory.dart @@ -9,7 +9,8 @@ class SmartWalletFactory implements SmartWalletFactoryBase { late final Contract _contract; SmartWalletFactory(this._chain, this._signer) - : assert(_chain.accountFactory != null, "account factory not set"), + : assert(_chain.accountFactory != null, + InvalidFactoryAddress(_chain.accountFactory)), _jsonRpc = JsonRPCProvider(_chain), _bundler = BundlerProvider(_chain) { _contract = Contract(_jsonRpc.rpc); @@ -20,6 +21,13 @@ class SmartWalletFactory implements SmartWalletFactoryBase { chainId: _chain.chainId, rpc: _jsonRpc.rpc); + _P256AccountFactory get _p256Accountfactory => _P256AccountFactory( + address: _chain.accountFactory!, + chainId: _chain.chainId, + rpc: _jsonRpc.rpc); + + // SafeAccountFactory + Future createSimpleAccount(Uint256 salt, {int? index}) async { final signer = _signer.getAddress(index: index ?? 0); final address = await _simpleAccountfactory.getAddress( @@ -28,14 +36,18 @@ class SmartWalletFactory implements SmartWalletFactoryBase { return _createAccount(_chain, address, initCode); } - Future createP256Account(T keyPair, Uint256 salt) { + Future createP256Account(T keyPair, Uint256 salt, + [EthereumAddress? recoveryAddress]) { switch (keyPair.runtimeType) { case PassKeyPair _: - return _createPasskeyAccount(keyPair as PassKeyPair, salt); + return _createPasskeyAccount( + keyPair as PassKeyPair, salt, recoveryAddress); case P256Credential _: - return _createSecureEnclaveAccount(keyPair as P256Credential, salt); + return _createSecureEnclaveAccount( + keyPair as P256Credential, salt, recoveryAddress); default: - throw ArgumentError("unsupported key pair type"); + throw ArgumentError.value(keyPair, 'keyPair', + 'createP256Account: An instance of `PassKeyPair` or `P256Credential` is expected'); } } @@ -51,26 +63,45 @@ class SmartWalletFactory implements SmartWalletFactoryBase { return _createAccount(_chain, address, initCode); } - Future _createPasskeyAccount( - PassKeyPair pkp, Uint256 salt) async { - final initCode = _getInitCode('createPasskeyAccount', [ + Future _createPasskeyAccount(PassKeyPair pkp, Uint256 salt, + [EthereumAddress? recoveryAddress]) async { + final Uint8List creation = abi.encode([ + 'address', + 'bytes32', + 'uint256', + 'uint256' + ], [ + recoveryAddress ?? Constants.zeroAddress, hexToBytes(pkp.credentialHex), pkp.publicKey.item1.value, pkp.publicKey.item2.value, - salt.value ]); - final address = await _simpleAccountfactory.getPasskeyAccountAddress( - hexToBytes(pkp.credentialHex), - pkp.publicKey.item1.value, - pkp.publicKey.item2.value, - salt.value); + + final initCode = _getInitCode('createP256Account', [creation, salt.value]); + final address = + await _p256Accountfactory.getP256AccountAddress(salt.value, creation); return _createAccount(_chain, address, initCode); } Future _createSecureEnclaveAccount( - P256Credential p256, Uint256 salt) { - // TODO: implement _createSimpleSecureEnclaveAccount - throw UnimplementedError(); + P256Credential p256, Uint256 salt, + [EthereumAddress? recoveryAddress]) async { + final Uint8List creation = abi.encode([ + 'address', + 'bytes32', + 'uint256', + 'uint256' + ], [ + recoveryAddress ?? Constants.zeroAddress, + Uint8List(0), + p256.publicKey.item1.value, + p256.publicKey.item2.value, + ]); + + final initCode = _getInitCode('createP256Account', [creation, salt.value]); + final address = + await _p256Accountfactory.getP256AccountAddress(salt.value, creation); + return _createAccount(_chain, address, initCode); } SmartWallet _createAccount( diff --git a/lib/src/4337/paymaster.dart b/lib/src/4337/paymaster.dart index bbd73a7..9e87f36 100644 --- a/lib/src/4337/paymaster.dart +++ b/lib/src/4337/paymaster.dart @@ -33,7 +33,8 @@ class Paymaster { } Paymaster(this._chain, [this._context]) - : assert(isURL(_chain.paymasterUrl), "invalid paymaster Url"), + : assert(isURL(_chain.paymasterUrl), + InvalidPaymasterUrl(_chain.paymasterUrl)), _rpc = RPCBase(_chain.paymasterUrl!); Future intercept(UserOperation operation) async { diff --git a/lib/src/4337/providers.dart b/lib/src/4337/providers.dart index b61e5bd..3729a42 100644 --- a/lib/src/4337/providers.dart +++ b/lib/src/4337/providers.dart @@ -4,7 +4,7 @@ class BundlerProvider implements BundlerProviderBase { final RPCBase _rpc; BundlerProvider(Chain chain) - : assert(isURL(chain.bundlerUrl), "invalid bundler Url"), + : assert(isURL(chain.bundlerUrl), InvalidBundlerUrl(chain.bundlerUrl)), _rpc = RPCBase(chain.bundlerUrl!) { _rpc .send('eth_chainId') @@ -65,7 +65,7 @@ class JsonRPCProvider implements JsonRPCProviderBase { final RPCBase rpc; JsonRPCProvider(Chain chain) - : assert(isURL(chain.jsonRpcUrl), "invalid jsonRpc Url"), + : assert(isURL(chain.jsonRpcUrl), InvalidJsonRpcUrl(chain.jsonRpcUrl)), rpc = RPCBase(chain.jsonRpcUrl!); @override diff --git a/lib/src/4337/wallet.dart b/lib/src/4337/wallet.dart index 4f08bd9..54fc749 100644 --- a/lib/src/4337/wallet.dart +++ b/lib/src/4337/wallet.dart @@ -134,5 +134,5 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { .estimateUserOperationGas(op.toMap(), _chain.entrypoint) .then((opGas) => op.updateOpGas(opGas, feePerGas)) .then((op) => multiply(op)) - .catchError((e) => throw EstimateError(e.toString(), op)); + .catchError((e) => throw GasEstimationError(e.toString(), op)); } diff --git a/lib/src/abis/abis.dart b/lib/src/abis/abis.dart index bbaf6ab..c8a541e 100644 --- a/lib/src/abis/abis.dart +++ b/lib/src/abis/abis.dart @@ -1,3 +1,4 @@ -export 'accountFactory.g.dart'; +export 'simpleAccountFactory.g.dart'; +export 'p256AccountFactory.g.dart'; export 'contract_abis.dart'; export 'entrypoint.g.dart'; diff --git a/lib/src/abis/accountFactory.abi.json b/lib/src/abis/accountFactory.abi.json deleted file mode 100644 index 37e104d..0000000 --- a/lib/src/abis/accountFactory.abi.json +++ /dev/null @@ -1,95 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "contract IEntryPoint", - "name": "_entryPoint", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" }, - { "internalType": "uint256", "name": "salt", "type": "uint256" } - ], - "name": "createAccount", - "outputs": [ - { - "internalType": "contract SimpleAccount", - "name": "ret", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes32", "name": "credentialHex", "type": "bytes32" }, - { "internalType": "uint256", "name": "x", "type": "uint256" }, - { "internalType": "uint256", "name": "y", "type": "uint256" }, - { "internalType": "uint256", "name": "salt", "type": "uint256" } - ], - "name": "createPasskeyAccount", - "outputs": [ - { - "internalType": "contract SimplePasskeyAccount", - "name": "ret", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" }, - { "internalType": "uint256", "name": "salt", "type": "uint256" } - ], - "name": "getAddress", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes32", "name": "credentialHex", "type": "bytes32" }, - { "internalType": "uint256", "name": "x", "type": "uint256" }, - { "internalType": "uint256", "name": "y", "type": "uint256" }, - { "internalType": "uint256", "name": "salt", "type": "uint256" } - ], - "name": "getPasskeyAccountAddress", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "simpleAccount", - "outputs": [ - { - "internalType": "contract SimpleAccount", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "simplePasskeyAccount", - "outputs": [ - { - "internalType": "contract SimplePasskeyAccount", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - } -] diff --git a/lib/src/abis/accountFactory.g.dart b/lib/src/abis/accountFactory.g.dart deleted file mode 100644 index d076fda..0000000 --- a/lib/src/abis/accountFactory.g.dart +++ /dev/null @@ -1,154 +0,0 @@ -// Generated code, do not modify. Run `build_runner build` to re-generate! -// @dart=2.12 -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:web3dart/web3dart.dart' as _i1; -import 'dart:typed_data' as _i2; - -final _contractAbi = _i1.ContractAbi.fromJson( - '[{"inputs":[{"internalType":"contract IEntryPoint","name":"_entryPoint","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"salt","type":"uint256"}],"name":"createAccount","outputs":[{"internalType":"contract SimpleAccount","name":"ret","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"credentialHex","type":"bytes32"},{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"},{"internalType":"uint256","name":"salt","type":"uint256"}],"name":"createPasskeyAccount","outputs":[{"internalType":"contract SimplePasskeyAccount","name":"ret","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"salt","type":"uint256"}],"name":"getAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"credentialHex","type":"bytes32"},{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"},{"internalType":"uint256","name":"salt","type":"uint256"}],"name":"getPasskeyAccountAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"simpleAccount","outputs":[{"internalType":"contract SimpleAccount","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"simplePasskeyAccount","outputs":[{"internalType":"contract SimplePasskeyAccount","name":"","type":"address"}],"stateMutability":"view","type":"function"}]', - 'AccountFactory', -); - -class AccountFactory extends _i1.GeneratedContract { - AccountFactory({ - required _i1.EthereumAddress address, - required _i1.Web3Client client, - int? chainId, - }) : super( - _i1.DeployedContract( - _contractAbi, - address, - ), - client, - chainId, - ); - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future createAccount( - _i1.EthereumAddress owner, - BigInt salt, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[1]; - assert(checkSignature(function, '5fbfb9cf')); - final params = [ - owner, - salt, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future createPasskeyAccount( - _i2.Uint8List credentialHex, - BigInt x, - BigInt y, - BigInt salt, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[2]; - assert(checkSignature(function, 'e2bb0bd2')); - final params = [ - credentialHex, - x, - y, - salt, - ]; - return write( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future<_i1.EthereumAddress> getAddress( - _i1.EthereumAddress owner, - BigInt salt, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[3]; - assert(checkSignature(function, '8cb84e18')); - final params = [ - owner, - salt, - ]; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as _i1.EthereumAddress); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future<_i1.EthereumAddress> getPasskeyAccountAddress( - _i2.Uint8List credentialHex, - BigInt x, - BigInt y, - BigInt salt, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[4]; - assert(checkSignature(function, 'c162e788')); - final params = [ - credentialHex, - x, - y, - salt, - ]; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as _i1.EthereumAddress); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future<_i1.EthereumAddress> simpleAccount({_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[5]; - assert(checkSignature(function, '65e4731a')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as _i1.EthereumAddress); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future<_i1.EthereumAddress> simplePasskeyAccount( - {_i1.BlockNum? atBlock}) async { - final function = self.abi.functions[6]; - assert(checkSignature(function, 'b2d46b17')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as _i1.EthereumAddress); - } -} diff --git a/lib/src/abis/p256AccountFactory.abi.json b/lib/src/abis/p256AccountFactory.abi.json new file mode 100644 index 0000000..17df5bc --- /dev/null +++ b/lib/src/abis/p256AccountFactory.abi.json @@ -0,0 +1,48 @@ +[ + { + "type": "constructor", + "inputs": [ + { + "name": "_entryPoint", + "type": "address", + "internalType": "contract IEntryPoint" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "createP256Account", + "inputs": [ + { "name": "salt", "type": "uint256", "internalType": "uint256" }, + { "name": "creation", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [ + { + "name": "ret", + "type": "address", + "internalType": "contract P256Account" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "getP256AccountAddress", + "inputs": [ + { "name": "salt", "type": "uint256", "internalType": "uint256" }, + { "name": "creation", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "p256Account", + "inputs": [], + "outputs": [ + { "name": "", "type": "address", "internalType": "contract P256Account" } + ], + "stateMutability": "view" + } +] diff --git a/lib/src/abis/p256AccountFactory.g.dart b/lib/src/abis/p256AccountFactory.g.dart new file mode 100644 index 0000000..fa5031c --- /dev/null +++ b/lib/src/abis/p256AccountFactory.g.dart @@ -0,0 +1,85 @@ +// Generated code, do not modify. Run `build_runner build` to re-generate! +// @dart=2.12 +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:web3dart/web3dart.dart' as _i1; +import 'dart:typed_data' as _i2; + +final _contractAbi = _i1.ContractAbi.fromJson( + '[{"type":"constructor","inputs":[{"name":"_entryPoint","type":"address","internalType":"contract IEntryPoint"}],"stateMutability":"nonpayable"},{"type":"function","name":"createP256Account","inputs":[{"name":"salt","type":"uint256","internalType":"uint256"},{"name":"creation","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"ret","type":"address","internalType":"contract P256Account"}],"stateMutability":"nonpayable"},{"type":"function","name":"getP256AccountAddress","inputs":[{"name":"salt","type":"uint256","internalType":"uint256"},{"name":"creation","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"p256Account","inputs":[],"outputs":[{"name":"","type":"address","internalType":"contract P256Account"}],"stateMutability":"view"}]', + 'P256AccountFactory', +); + +class P256AccountFactory extends _i1.GeneratedContract { + P256AccountFactory({ + required _i1.EthereumAddress address, + required _i1.Web3Client client, + int? chainId, + }) : super( + _i1.DeployedContract( + _contractAbi, + address, + ), + client, + chainId, + ); + + /// The optional [transaction] parameter can be used to override parameters + /// like the gas price, nonce and max gas. The `data` and `to` fields will be + /// set by the contract. + Future createP256Account( + BigInt salt, + _i2.Uint8List creation, { + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[1]; + assert(checkSignature(function, '8bb4387f')); + final params = [ + salt, + creation, + ]; + return write( + credentials, + transaction, + function, + params, + ); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future<_i1.EthereumAddress> getP256AccountAddress( + BigInt salt, + _i2.Uint8List creation, { + _i1.BlockNum? atBlock, + }) async { + final function = self.abi.functions[2]; + assert(checkSignature(function, '28ef50f0')); + final params = [ + salt, + creation, + ]; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as _i1.EthereumAddress); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future<_i1.EthereumAddress> p256Account({_i1.BlockNum? atBlock}) async { + final function = self.abi.functions[3]; + assert(checkSignature(function, 'e8eb9e23')); + final params = []; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as _i1.EthereumAddress); + } +} diff --git a/lib/src/abis/simpleAccountFactory.abi.json b/lib/src/abis/simpleAccountFactory.abi.json new file mode 100644 index 0000000..0e5bc9b --- /dev/null +++ b/lib/src/abis/simpleAccountFactory.abi.json @@ -0,0 +1,74 @@ +[ + { + "inputs": [ + { + "internalType": "contract IEntryPoint", + "name": "_entryPoint", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "accountImplementation", + "outputs": [ + { + "internalType": "contract SimpleAccount", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "salt", + "type": "uint256" + } + ], + "name": "createAccount", + "outputs": [ + { + "internalType": "contract SimpleAccount", + "name": "ret", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "salt", + "type": "uint256" + } + ], + "name": "getAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/lib/src/abis/simpleAccountFactory.g.dart b/lib/src/abis/simpleAccountFactory.g.dart new file mode 100644 index 0000000..b452414 --- /dev/null +++ b/lib/src/abis/simpleAccountFactory.g.dart @@ -0,0 +1,85 @@ +// Generated code, do not modify. Run `build_runner build` to re-generate! +// @dart=2.12 +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:web3dart/web3dart.dart' as _i1; + +final _contractAbi = _i1.ContractAbi.fromJson( + '[{"inputs":[{"internalType":"contract IEntryPoint","name":"_entryPoint","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"accountImplementation","outputs":[{"internalType":"contract SimpleAccount","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"salt","type":"uint256"}],"name":"createAccount","outputs":[{"internalType":"contract SimpleAccount","name":"ret","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"salt","type":"uint256"}],"name":"getAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]', + 'SimpleAccountFactory', +); + +class SimpleAccountFactory extends _i1.GeneratedContract { + SimpleAccountFactory({ + required _i1.EthereumAddress address, + required _i1.Web3Client client, + int? chainId, + }) : super( + _i1.DeployedContract( + _contractAbi, + address, + ), + client, + chainId, + ); + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future<_i1.EthereumAddress> accountImplementation( + {_i1.BlockNum? atBlock}) async { + final function = self.abi.functions[1]; + assert(checkSignature(function, '11464fbe')); + final params = []; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as _i1.EthereumAddress); + } + + /// The optional [transaction] parameter can be used to override parameters + /// like the gas price, nonce and max gas. The `data` and `to` fields will be + /// set by the contract. + Future createAccount( + _i1.EthereumAddress owner, + BigInt salt, { + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[2]; + assert(checkSignature(function, '5fbfb9cf')); + final params = [ + owner, + salt, + ]; + return write( + credentials, + transaction, + function, + params, + ); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future<_i1.EthereumAddress> getAddress( + _i1.EthereumAddress owner, + BigInt salt, { + _i1.BlockNum? atBlock, + }) async { + final function = self.abi.functions[3]; + assert(checkSignature(function, '8cb84e18')); + final params = [ + owner, + salt, + ]; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as _i1.EthereumAddress); + } +} diff --git a/lib/src/common/factories.dart b/lib/src/common/factories.dart new file mode 100644 index 0000000..fee5447 --- /dev/null +++ b/lib/src/common/factories.dart @@ -0,0 +1,15 @@ +part of '../../variance.dart'; + +class _SimpleAccountFactory extends SimpleAccountFactory + implements SimpleAccountFactoryBase { + _SimpleAccountFactory( + {required super.address, super.chainId, required RPCBase rpc}) + : super(client: Web3Client.custom(rpc)); +} + +class _P256AccountFactory extends P256AccountFactory + implements P256AccountFactoryBase { + _P256AccountFactory( + {required super.address, super.chainId, required RPCBase rpc}) + : super(client: Web3Client.custom(rpc)); +} diff --git a/lib/src/common/factory.dart b/lib/src/common/factory.dart deleted file mode 100644 index 5494f96..0000000 --- a/lib/src/common/factory.dart +++ /dev/null @@ -1,8 +0,0 @@ -part of '../../variance.dart'; - -class _SimpleAccountFactory extends AccountFactory - implements AccountFactoryBase { - _SimpleAccountFactory( - {required super.address, super.chainId, required RPCBase rpc}) - : super(client: Web3Client.custom(rpc)); -} diff --git a/lib/src/common/mixins.dart b/lib/src/common/mixins.dart index b2222bb..65d2789 100644 --- a/lib/src/common/mixins.dart +++ b/lib/src/common/mixins.dart @@ -14,7 +14,7 @@ class GasSettings { this.userDefinedMaxPriorityFeePerGas, this.retryFailedSendUserOp = false, }) : assert(gasMultiplierPercentage! >= 0, - 'Gas multiplier percentage should be 0 to 100'); + RangeOutOfBounds("Wrong Gas multiplier percentage", 0, 100)); } mixin _GasSettings { diff --git a/lib/src/common/pack.dart b/lib/src/common/pack.dart index a846ec8..789d0cd 100644 --- a/lib/src/common/pack.dart +++ b/lib/src/common/pack.dart @@ -21,6 +21,19 @@ Uint8List packUints(BigInt high128, BigInt low128) { return hexToBytes(combined.toRadixString(16).padLeft(64, '0')); } +/// Unpacks two 128-bit unsigned integers from a 32-byte array. +/// +/// Parameters: +/// - [bytes]: The 32-byte array containing the packed bytes. +/// +/// Returns a list containing the unpacked high and low 128-bit unsigned integers. +/// +/// Example: +/// ```dart +/// final bytes = Uint8List.fromList([0x01, 0x02, 0x03, 0x04]); +/// final unpacked = unpackUints(bytes); +/// print(unpacked); +/// ``` List unpackUints(Uint8List bytes) { final hex = bytesToHex(bytes); final value = BigInt.parse(hex, radix: 16); diff --git a/lib/src/errors/wallet_errors.dart b/lib/src/errors/wallet_errors.dart index 9f2f0e2..009d032 100644 --- a/lib/src/errors/wallet_errors.dart +++ b/lib/src/errors/wallet_errors.dart @@ -1,11 +1,10 @@ part of '../../variance.dart'; -class EstimateError extends Error { +class GasEstimationError extends Error { final String message; - final UserOperation operation; - EstimateError(this.message, this.operation); + GasEstimationError(this.message, this.operation); @override String toString() { @@ -17,6 +16,86 @@ class EstimateError extends Error { } } +class InvalidBundlerMethod extends Error { + final String message = 'Invalid bundler method!'; + final String? method; + + InvalidBundlerMethod(this.method); + + @override + String toString() { + return ''' + $message + -------------------------------------------------- + method ::'$method':: is not supported by the bundler. + '''; + } +} + +class InvalidBundlerUrl extends Error { + final String message = 'Invalid bundler url!'; + final String? url; + + InvalidBundlerUrl(this.url); + + @override + String toString() { + return ''' + $message + -------------------------------------------------- + Provided bundler url: $url + '''; + } +} + +class InvalidFactoryAddress extends Error { + final EthereumAddress? address; + final String message = 'Invalid account factory address!'; + + InvalidFactoryAddress(this.address); + + @override + String toString() { + return ''' + $message + -------------------------------------------------- + Provided factory address: $address + '''; + } +} + +class InvalidJsonRpcUrl extends Error { + final String message = 'Invalid json rpc url!'; + final String? url; + + InvalidJsonRpcUrl(this.url); + + @override + String toString() { + return ''' + $message + -------------------------------------------------- + Provided json rpc url: $url + '''; + } +} + +class InvalidPaymasterUrl extends Error { + final String message = 'Invalid paymaster url!'; + final String? url; + + InvalidPaymasterUrl(this.url); + + @override + String toString() { + return ''' + $message + -------------------------------------------------- + Provided paymaster url: $url + '''; + } +} + class NonceError extends Error { final String message; final EthereumAddress? address; @@ -33,34 +112,35 @@ class NonceError extends Error { } } -class SendError extends Error { - final String message; - final UserOperation operation; +class RangeOutOfBounds extends Error { + final String? message; + final int? start; + final int? end; - SendError(this.message, this.operation); + RangeOutOfBounds(this.message, this.start, this.end); @override String toString() { return ''' - Error sending user operation! Failed with error: $message + $message -------------------------------------------------- - User operation: ${operation.toJson()}. + only start ::'$start':: and end ::'$end':: is permissible. '''; } } -class InvalidAccountFactoryAddress extends Error { - final EthereumAddress? address; - final String message = 'Invalid account factory address!'; +class SendError extends Error { + final String message; + final UserOperation operation; - InvalidAccountFactoryAddress(this.address); + SendError(this.message, this.operation); @override String toString() { return ''' - $message + Error sending user operation! Failed with error: $message -------------------------------------------------- - Provided factory address: $address + User operation: ${operation.toJson()}. '''; } } diff --git a/lib/src/interfaces/account_factory.dart b/lib/src/interfaces/account_factory.dart index 1e65397..4d0e19c 100644 --- a/lib/src/interfaces/account_factory.dart +++ b/lib/src/interfaces/account_factory.dart @@ -4,26 +4,20 @@ part of 'interfaces.dart'; /// /// This class defines the common interface for interacting with an Ethereum smart contract /// responsible for creating and managing accounts. -abstract class AccountFactoryBase { +abstract class SimpleAccountFactoryBase { /// Retrieves the Ethereum address associated with a standard account. Future getAddress( EthereumAddress owner, BigInt salt, { BlockNum? atBlock, }); +} - /// Retrieves the Ethereum address associated with a passkey account. - Future getPasskeyAccountAddress( - Uint8List credentialHex, - BigInt x, - BigInt y, - BigInt salt, { +abstract class P256AccountFactoryBase { + /// Retrieves the Ethereum address associated with a standard p256 account. + Future getP256AccountAddress( + BigInt salt, + Uint8List creation, { BlockNum? atBlock, }); - - /// Retrieves the Ethereum address associated with the simpleAccount contract. - Future simpleAccount({BlockNum? atBlock}); - - /// Retrieves the Ethereum address associated with the simplePasskeyAccount contract. - Future simplePasskeyAccount({BlockNum? atBlock}); } diff --git a/lib/src/interfaces/bundler_provider.dart b/lib/src/interfaces/bundler_provider.dart index 9e27735..217a950 100644 --- a/lib/src/interfaces/bundler_provider.dart +++ b/lib/src/interfaces/bundler_provider.dart @@ -110,7 +110,6 @@ abstract class BundlerProviderBase { /// validateBundlerMethod('eth_sendUserOperation'); /// ``` static validateBundlerMethod(String method) { - assert(methods.contains(method), - "validateMethod: method ::'$method':: is not a valid method"); + assert(methods.contains(method), InvalidBundlerMethod(method)); } } diff --git a/lib/src/interfaces/interfaces.dart b/lib/src/interfaces/interfaces.dart index b696a27..e01b379 100644 --- a/lib/src/interfaces/interfaces.dart +++ b/lib/src/interfaces/interfaces.dart @@ -7,6 +7,7 @@ import '../../variance.dart' show Chain, EntryPoint, + InvalidBundlerMethod, UserOperation, UserOperationByHash, UserOperationGas, diff --git a/lib/variance.dart b/lib/variance.dart index a252eef..2cb57f6 100644 --- a/lib/variance.dart +++ b/lib/variance.dart @@ -22,7 +22,7 @@ part 'src/4337/providers.dart'; part 'src/4337/userop.dart'; part 'src/4337/wallet.dart'; part 'src/common/contract.dart'; -part 'src/common/factory.dart'; +part 'src/common/factories.dart'; part 'src/common/mixins.dart'; part 'src/common/pack.dart'; part 'src/errors/wallet_errors.dart'; diff --git a/pubspec.lock b/pubspec.lock index 520244c..70f7886 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -243,22 +243,6 @@ packages: description: flutter source: sdk version: "0.0.0" - freezed: - dependency: "direct dev" - description: - name: freezed - sha256: "6c5031daae12c7072b3a87eff98983076434b4889ef2a44384d0cae3f82372ba" - url: "https://pub.dev" - source: hosted - version: "2.4.6" - freezed_annotation: - dependency: transitive - description: - name: freezed_annotation - sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d - url: "https://pub.dev" - source: hosted - version: "2.4.1" frontend_server_client: dependency: transitive description: @@ -347,14 +331,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" - json_serializable: - dependency: "direct dev" - description: - name: json_serializable - sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969 - url: "https://pub.dev" - source: hosted - version: "6.7.1" lints: dependency: transitive description: @@ -552,14 +528,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.0" - source_helper: - dependency: transitive - description: - name: source_helper - sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" - url: "https://pub.dev" - source: hosted - version: "1.3.4" source_span: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a9e78e4..6eb6376 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,8 +27,6 @@ dev_dependencies: web3dart_builders: ^0.0.7 build_runner: ^2.4.6 flutter_lints: ^3.0.0 - freezed: ^2.4.5 - json_serializable: ^6.7.1 mockito: ^5.4.2 topics: From 5e5fdc675af0beb94a9c48bcab5b650bb81b4452 Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Mon, 25 Mar 2024 23:51:28 +0100 Subject: [PATCH 11/31] returned the useroperationresponse.wait() --- lib/src/4337/providers.dart | 6 +-- lib/src/4337/userop.dart | 50 +++++++++++++++++++++++- lib/src/4337/wallet.dart | 3 +- lib/src/interfaces/bundler_provider.dart | 2 +- lib/src/interfaces/interfaces.dart | 1 + lib/variance.dart | 1 + 6 files changed, 57 insertions(+), 6 deletions(-) diff --git a/lib/src/4337/providers.dart b/lib/src/4337/providers.dart index 3729a42..f32e5ce 100644 --- a/lib/src/4337/providers.dart +++ b/lib/src/4337/providers.dart @@ -44,13 +44,13 @@ class BundlerProvider implements BundlerProviderBase { } @override - Future sendUserOperation( - Map userOp, EntryPoint entrypoint) async { + Future sendUserOperation(Map userOp, + EntryPoint entrypoint, RPCBase fallback) async { Logger.conditionalWarning( !_initialized, "sendUserOp may fail: chainId mismatch"); final opHash = await _rpc .send('eth_sendUserOperation', [userOp, entrypoint.hex]); - return UserOperationResponse(opHash); + return UserOperationResponse(opHash, entrypoint, fallback); } @override diff --git a/lib/src/4337/userop.dart b/lib/src/4337/userop.dart index 12f57a4..a076371 100644 --- a/lib/src/4337/userop.dart +++ b/lib/src/4337/userop.dart @@ -333,8 +333,56 @@ class UserOperationReceipt { } } +class UserOperationEventFilter extends FilterOptions { + UserOperationEventFilter.events({ + required super.contract, + required super.event, + super.fromBlock, + super.toBlock, + required String userOpHash, + }) : super.events() { + if (userOpHash.isNotEmpty) { + topics?.add([userOpHash]); + } + } +} + class UserOperationResponse { final String userOpHash; + final EntryPoint _entrypoint; + final RPCBase _rpc; + + UserOperationResponse(this.userOpHash, this._entrypoint, this._rpc); - UserOperationResponse(this.userOpHash); + Future wait([int millisecond = 3000]) async { + final end = DateTime.now().millisecondsSinceEpoch + millisecond; + + return await Isolate.run(() async { + final client = Web3Client.custom(_rpc); + final entrypoint = + Entrypoint(address: _entrypoint.address, client: client); + + final block = await client.getBlockNumber(); + while (DateTime.now().millisecondsSinceEpoch < end) { + final filterEvent = await client + .events( + UserOperationEventFilter.events( + contract: entrypoint.self, + event: entrypoint.self.event('UserOperationEvent'), + userOpHash: userOpHash, + fromBlock: BlockNum.exact(block - 100), + ), + ) + .take(1) + .first; + if (filterEvent.transactionHash != null) { + return filterEvent; + } + + await Future.delayed(Duration(milliseconds: millisecond)); + } + + return null; + }); + } } diff --git a/lib/src/4337/wallet.dart b/lib/src/4337/wallet.dart index 54fc749..5e0c063 100644 --- a/lib/src/4337/wallet.dart +++ b/lib/src/4337/wallet.dart @@ -80,7 +80,8 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { @override Future sendSignedUserOperation(UserOperation op) => plugin('bundler') - .sendUserOperation(op.toMap(), _chain.entrypoint) + .sendUserOperation( + op.toMap(), _chain.entrypoint, plugin('jsonRpc').rpc) .catchError((e) => throw SendError(e.toString(), op)); @override diff --git a/lib/src/interfaces/bundler_provider.dart b/lib/src/interfaces/bundler_provider.dart index 217a950..51ef227 100644 --- a/lib/src/interfaces/bundler_provider.dart +++ b/lib/src/interfaces/bundler_provider.dart @@ -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, EntryPoint entrypoint); + Map userOp, EntryPoint entrypoint, RPCBase rpc); /// 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 e01b379..91f50c2 100644 --- a/lib/src/interfaces/interfaces.dart +++ b/lib/src/interfaces/interfaces.dart @@ -8,6 +8,7 @@ import '../../variance.dart' Chain, EntryPoint, InvalidBundlerMethod, + RPCBase, UserOperation, UserOperationByHash, UserOperationGas, diff --git a/lib/variance.dart b/lib/variance.dart index 2cb57f6..66ec670 100644 --- a/lib/variance.dart +++ b/lib/variance.dart @@ -2,6 +2,7 @@ library; import 'dart:async'; import 'dart:convert'; +import 'dart:isolate'; import 'dart:typed_data'; import 'package:http/http.dart' as http; From 3db203a7db40927a0deef89b18b431deb217ab3d Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Tue, 26 Mar 2024 15:35:12 +0100 Subject: [PATCH 12/31] feat: add safe account creation --- example/lib/main.dart | 2 +- lib/src/4337/chains.dart | 50 +++--- lib/src/4337/factory.dart | 76 +++++--- lib/src/4337/paymaster.dart | 2 +- lib/src/4337/providers.dart | 4 +- lib/src/4337/userop.dart | 4 +- lib/src/abis/abis.dart | 1 + lib/src/abis/contract_abis.dart | 8 +- lib/src/abis/safeProxyFactory.abi.json | 91 ++++++++++ lib/src/abis/safeProxyFactory.g.dart | 170 ++++++++++++++++++ lib/src/common/factories.dart | 66 ++++++- ...nt_factory.dart => account_factories.dart} | 12 ++ lib/src/interfaces/bundler_provider.dart | 8 +- lib/src/interfaces/interfaces.dart | 4 +- 14 files changed, 427 insertions(+), 71 deletions(-) create mode 100644 lib/src/abis/safeProxyFactory.abi.json create mode 100644 lib/src/abis/safeProxyFactory.g.dart rename lib/src/interfaces/{account_factory.dart => account_factories.dart} (71%) diff --git a/example/lib/main.dart b/example/lib/main.dart index 8a4361e..bd7481c 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -17,7 +17,7 @@ Future main() async { final Chain chain = Chain( jsonRpcUrl: rpcUrl, bundlerUrl: bundlerUrl, - entrypoint: EntryPoint.v06, + entrypoint: EntryPointAddress.v06, accountFactory: EthereumAddress.fromHex("0xCCaE5F64307D86346B83E55e7865f77906F9c7b4"), chainId: 1337, diff --git a/lib/src/4337/chains.dart b/lib/src/4337/chains.dart index e061c18..2f97126 100644 --- a/lib/src/4337/chains.dart +++ b/lib/src/4337/chains.dart @@ -3,7 +3,7 @@ part of '../../variance.dart'; class Chain { final int chainId; final String explorer; - final EntryPoint entrypoint; + final EntryPointAddress entrypoint; EthereumAddress? accountFactory; String? jsonRpcUrl; String? bundlerUrl; @@ -26,73 +26,73 @@ class Chains { chainId: 1, explorer: "https://etherscan.io/", jsonRpcUrl: "https://rpc.ankr.com/eth", - entrypoint: EntryPoint.v06, + entrypoint: EntryPointAddress.v06, ), Network.polygon: Chain( chainId: 137, explorer: "https://polygonscan.com/", - jsonRpcUrl: "https://polygon-rpc.com/", - entrypoint: EntryPoint.v06, + jsonRpcUrl: "https://rpc.ankr.com/polygon", + entrypoint: EntryPointAddress.v06, ), Network.optimism: Chain( chainId: 10, explorer: "https://explorer.optimism.io", - jsonRpcUrl: "https://mainnet.optimism.io", - entrypoint: EntryPoint.v06, + jsonRpcUrl: "https://rpc.ankr.com/optimism", + entrypoint: EntryPointAddress.v06, ), Network.base: Chain( chainId: 8453, explorer: "https://basescan.org", - jsonRpcUrl: "https://mainnet.base.org", - entrypoint: EntryPoint.v06, + jsonRpcUrl: "https://rpc.ankr.com/base", + entrypoint: EntryPointAddress.v06, ), Network.arbitrumOne: Chain( chainId: 42161, explorer: "https://arbiscan.io/", - jsonRpcUrl: "https://arb1.arbitrum.io/rpc", - entrypoint: EntryPoint.v06, + jsonRpcUrl: "https://rpc.ankr.com/arbitrum", + entrypoint: EntryPointAddress.v06, ), Network.linea: Chain( chainId: 59144, explorer: "https://lineascan.build/", jsonRpcUrl: "https://rpc.linea.build", - entrypoint: EntryPoint.v06), + entrypoint: EntryPointAddress.v06), Network.opBnB: Chain( chainId: 204, explorer: "http://opbnbscan.com/", jsonRpcUrl: "https://opbnb-mainnet-rpc.bnbchain.org", - entrypoint: EntryPoint.v06, + entrypoint: EntryPointAddress.v06, ), Network.scroll: Chain( chainId: 534352, explorer: "https://scrollscan.com/", - jsonRpcUrl: "https://rpc.scroll.io/", - entrypoint: EntryPoint.v06, + jsonRpcUrl: "https://rpc.ankr.com/scroll", + entrypoint: EntryPointAddress.v06, ), Network.sepolia: Chain( chainId: 11155111, explorer: "https://sepolia.etherscan.io/", - jsonRpcUrl: "https://rpc.sepolia.org", - entrypoint: EntryPoint.v06, + jsonRpcUrl: "https://rpc.ankr.com/eth_sepolia", + entrypoint: EntryPointAddress.v06, ), Network.mumbai: Chain( chainId: 80001, explorer: "https://mumbai.polygonscan.com/", - jsonRpcUrl: "https://rpc-mumbai.maticvigil.com/", - entrypoint: EntryPoint.v06, + jsonRpcUrl: "https://rpc.ankr.com/polygon_mumbai", + entrypoint: EntryPointAddress.v06, ), Network.baseTestent: Chain( chainId: 84531, - explorer: "https://sepolia.basescan.org", - jsonRpcUrl: "https://api-sepolia.basescan.org/api", - entrypoint: EntryPoint.v06, + explorer: "https://sepolia.basescan.org/", + jsonRpcUrl: "https://rpc.ankr.com/base_sepolia", + entrypoint: EntryPointAddress.v06, ), Network.localhost: Chain( chainId: 1337, explorer: "http://localhost:8545", jsonRpcUrl: "http://localhost:8545", bundlerUrl: "http://localhost:3000/rpc", - entrypoint: EntryPoint.v06, + entrypoint: EntryPointAddress.v06, ) }; @@ -110,18 +110,20 @@ class Constants { EthereumAddress.fromHex("0x0000000071727De22E5E9d8BAf0edAc6f37da032"); static EthereumAddress zeroAddress = EthereumAddress.fromHex("0x0000000000000000000000000000000000000000"); + static final EthereumAddress safeProxyFactoryAddress = + EthereumAddress.fromHex("0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67"); Constants._(); } -enum EntryPoint { +enum EntryPointAddress { v06(0.6, "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"), v07(0.7, "0x0000000071727De22E5E9d8BAf0edAc6f37da032"); final double version; final String hex; - const EntryPoint(this.version, this.hex); + const EntryPointAddress(this.version, this.hex); EthereumAddress get address => EthereumAddress.fromHex(hex); } diff --git a/lib/src/4337/factory.dart b/lib/src/4337/factory.dart index deb5083..7a373d4 100644 --- a/lib/src/4337/factory.dart +++ b/lib/src/4337/factory.dart @@ -16,25 +16,20 @@ class SmartWalletFactory implements SmartWalletFactoryBase { _contract = Contract(_jsonRpc.rpc); } - _SimpleAccountFactory get _simpleAccountfactory => _SimpleAccountFactory( + _P256AccountFactory get _p256Accountfactory => _P256AccountFactory( address: _chain.accountFactory!, chainId: _chain.chainId, rpc: _jsonRpc.rpc); - _P256AccountFactory get _p256Accountfactory => _P256AccountFactory( + _SafeProxyFactory get _safeProxyFactory => _SafeProxyFactory( address: _chain.accountFactory!, chainId: _chain.chainId, rpc: _jsonRpc.rpc); - // SafeAccountFactory - - Future createSimpleAccount(Uint256 salt, {int? index}) async { - final signer = _signer.getAddress(index: index ?? 0); - final address = await _simpleAccountfactory.getAddress( - EthereumAddress.fromHex(signer), salt.value); - final initCode = _getInitCode('createAccount', [signer, salt.value]); - return _createAccount(_chain, address, initCode); - } + _SimpleAccountFactory get _simpleAccountfactory => _SimpleAccountFactory( + address: _chain.accountFactory!, + chainId: _chain.chainId, + rpc: _jsonRpc.rpc); Future createP256Account(T keyPair, Uint256 salt, [EthereumAddress? recoveryAddress]) { @@ -51,9 +46,30 @@ class SmartWalletFactory implements SmartWalletFactoryBase { } } - Future createSafeAccount() { - // TODO: implement createSafeAccount - throw UnimplementedError(); + Future createSafeAccount(Uint256 salt, {int? index}) async { + final signer = _signer.getAddress(index: index ?? 0); + final initializer = + _safeProxyFactory.getInitializer(EthereumAddress.fromHex(signer)); + final creation = await _safeProxyFactory.proxyCreationCode(); + final address = + _safeProxyFactory.getPredictedSafe(initializer, salt, creation); + final initCallData = _safeProxyFactory.self + .function("createProxyWithNonce") + .encodeCall( + [_safeProxyFactory.safeSingletonAddress, initializer, salt.value]); + final initCode = _getInitCode(initCallData); + return _createAccount(_chain, address, initCode); + } + + Future createSimpleAccount(Uint256 salt, {int? index}) async { + final signer = _signer.getAddress(index: index ?? 0); + final address = await _simpleAccountfactory.getAddress( + EthereumAddress.fromHex(signer), salt.value); + final initCalldata = _simpleAccountfactory.self + .function('createAccount') + .encodeCall([signer, salt.value]); + final initCode = _getInitCode(initCalldata); + return _createAccount(_chain, address, initCode); } Future createVendorAccount( @@ -63,6 +79,15 @@ class SmartWalletFactory implements SmartWalletFactoryBase { return _createAccount(_chain, address, initCode); } + SmartWallet _createAccount( + Chain chain, EthereumAddress address, Uint8List initCalldata) { + return SmartWallet(chain, address, initCalldata) + ..addPlugin('signer', _signer) + ..addPlugin('bundler', _bundler) + ..addPlugin('jsonRpc', _jsonRpc) + ..addPlugin('contract', _contract); + } + Future _createPasskeyAccount(PassKeyPair pkp, Uint256 salt, [EthereumAddress? recoveryAddress]) async { final Uint8List creation = abi.encode([ @@ -77,7 +102,10 @@ class SmartWalletFactory implements SmartWalletFactoryBase { pkp.publicKey.item2.value, ]); - final initCode = _getInitCode('createP256Account', [creation, salt.value]); + final initCalldata = _p256Accountfactory.self + .function('createP256Account') + .encodeCall([creation, salt.value]); + final initCode = _getInitCode(initCalldata); final address = await _p256Accountfactory.getP256AccountAddress(salt.value, creation); return _createAccount(_chain, address, initCode); @@ -98,24 +126,16 @@ class SmartWalletFactory implements SmartWalletFactoryBase { p256.publicKey.item2.value, ]); - final initCode = _getInitCode('createP256Account', [creation, salt.value]); + final initCalldata = _p256Accountfactory.self + .function('createP256Account') + .encodeCall([creation, salt.value]); + final initCode = _getInitCode(initCalldata); final address = await _p256Accountfactory.getP256AccountAddress(salt.value, creation); return _createAccount(_chain, address, initCode); } - SmartWallet _createAccount( - Chain chain, EthereumAddress address, Uint8List initCalldata) { - return SmartWallet(chain, address, initCalldata) - ..addPlugin('signer', _signer) - ..addPlugin('bundler', _bundler) - ..addPlugin('jsonRpc', _jsonRpc) - ..addPlugin('contract', _contract); - } - - Uint8List _getInitCode(String functionName, List params) { - final initCalldata = - _simpleAccountfactory.self.function(functionName).encodeCall(params); + Uint8List _getInitCode(Uint8List initCalldata) { List extended = _chain.accountFactory!.addressBytes.toList(); extended.addAll(initCalldata); return Uint8List.fromList(extended); diff --git a/lib/src/4337/paymaster.dart b/lib/src/4337/paymaster.dart index 9e87f36..4d3e3d8 100644 --- a/lib/src/4337/paymaster.dart +++ b/lib/src/4337/paymaster.dart @@ -50,7 +50,7 @@ class Paymaster { } Future sponsorUserOperation(Map userOp, - EntryPoint entrypoint, Map? context) async { + EntryPointAddress 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 f32e5ce..ed986a9 100644 --- a/lib/src/4337/providers.dart +++ b/lib/src/4337/providers.dart @@ -17,7 +17,7 @@ class BundlerProvider implements BundlerProviderBase { @override Future estimateUserOperationGas( - Map userOp, EntryPoint entrypoint) async { + Map userOp, EntryPointAddress entrypoint) async { Logger.conditionalWarning( !_initialized, "estimateUserOpGas may fail: chainId mismatch"); final opGas = await _rpc.send>( @@ -45,7 +45,7 @@ class BundlerProvider implements BundlerProviderBase { @override Future sendUserOperation(Map userOp, - EntryPoint entrypoint, RPCBase fallback) async { + EntryPointAddress entrypoint, RPCBase fallback) async { Logger.conditionalWarning( !_initialized, "sendUserOp may fail: chainId mismatch"); final opHash = await _rpc diff --git a/lib/src/4337/userop.dart b/lib/src/4337/userop.dart index a076371..e58cd90 100644 --- a/lib/src/4337/userop.dart +++ b/lib/src/4337/userop.dart @@ -146,7 +146,7 @@ class UserOperation implements UserOperationBase { @override Uint8List hash(Chain chain) { Uint8List encoded; - if (chain.entrypoint == EntryPoint.v07) { + if (chain.entrypoint == EntryPointAddress.v07) { encoded = keccak256(abi.encode([ 'address', 'uint256', @@ -349,7 +349,7 @@ class UserOperationEventFilter extends FilterOptions { class UserOperationResponse { final String userOpHash; - final EntryPoint _entrypoint; + final EntryPointAddress _entrypoint; final RPCBase _rpc; UserOperationResponse(this.userOpHash, this._entrypoint, this._rpc); diff --git a/lib/src/abis/abis.dart b/lib/src/abis/abis.dart index c8a541e..b39adbe 100644 --- a/lib/src/abis/abis.dart +++ b/lib/src/abis/abis.dart @@ -1,4 +1,5 @@ export 'simpleAccountFactory.g.dart'; export 'p256AccountFactory.g.dart'; +export 'safeProxyFactory.g.dart'; export 'contract_abis.dart'; export 'entrypoint.g.dart'; diff --git a/lib/src/abis/contract_abis.dart b/lib/src/abis/contract_abis.dart index 602054e..cd5d490 100644 --- a/lib/src/abis/contract_abis.dart +++ b/lib/src/abis/contract_abis.dart @@ -21,8 +21,14 @@ class ContractAbis { case 'executeBatch': abi = '[{"inputs":[{"internalType":"address[]","name":"dest","type":"address[]"},{"internalType":"uint256[]","name":"value","type":"uint256[]"},{"internalType":"bytes[]","name":"func","type":"bytes[]"}],"name":"executeBatch","outputs":[],"stateMutability":"nonpayable","type":"function"}]'; + case 'enableModules': + abi = + '[{"type":"function","name":"enableModules","inputs":[{"name":"modules","type":"address[]","internalType":"address[]"}],"outputs":[],"stateMutability":"nonpayable"}]'; + case 'setup': + abi = + '[{"type":"function","name":"setup","inputs":[{"name":"_owners","type":"address[]","internalType":"address[]"},{"name":"_threshold","type":"uint256","internalType":"uint256"},{"name":"to","type":"address","internalType":"address"},{"name":"data","type":"bytes","internalType":"bytes"},{"name":"fallbackHandler","type":"address","internalType":"address"},{"name":"paymentToken","type":"address","internalType":"address"},{"name":"payment","type":"uint256","internalType":"uint256"},{"name":"paymentReceiver","type":"address","internalType":"address payable"}],"outputs":[],"stateMutability":"nonpayable"}]'; default: - throw 'ABI of $name is not available, but you can add it yourself'; + throw 'ABI of $name is not available by default. Please provide the ABI manually.'; } return ContractAbi.fromJson(abi, name); } diff --git a/lib/src/abis/safeProxyFactory.abi.json b/lib/src/abis/safeProxyFactory.abi.json new file mode 100644 index 0000000..d630760 --- /dev/null +++ b/lib/src/abis/safeProxyFactory.abi.json @@ -0,0 +1,91 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract SafeProxy", + "name": "proxy", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "singleton", + "type": "address" + } + ], + "name": "ProxyCreation", + "type": "event" + }, + { + "inputs": [ + { "internalType": "address", "name": "_singleton", "type": "address" }, + { "internalType": "bytes", "name": "initializer", "type": "bytes" }, + { "internalType": "uint256", "name": "saltNonce", "type": "uint256" } + ], + "name": "createChainSpecificProxyWithNonce", + "outputs": [ + { + "internalType": "contract SafeProxy", + "name": "proxy", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_singleton", "type": "address" }, + { "internalType": "bytes", "name": "initializer", "type": "bytes" }, + { "internalType": "uint256", "name": "saltNonce", "type": "uint256" }, + { + "internalType": "contract IProxyCreationCallback", + "name": "callback", + "type": "address" + } + ], + "name": "createProxyWithCallback", + "outputs": [ + { + "internalType": "contract SafeProxy", + "name": "proxy", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_singleton", "type": "address" }, + { "internalType": "bytes", "name": "initializer", "type": "bytes" }, + { "internalType": "uint256", "name": "saltNonce", "type": "uint256" } + ], + "name": "createProxyWithNonce", + "outputs": [ + { + "internalType": "contract SafeProxy", + "name": "proxy", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getChainId", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proxyCreationCode", + "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], + "stateMutability": "pure", + "type": "function" + } +] diff --git a/lib/src/abis/safeProxyFactory.g.dart b/lib/src/abis/safeProxyFactory.g.dart new file mode 100644 index 0000000..7790cbd --- /dev/null +++ b/lib/src/abis/safeProxyFactory.g.dart @@ -0,0 +1,170 @@ +// Generated code, do not modify. Run `build_runner build` to re-generate! +// @dart=2.12 +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:web3dart/web3dart.dart' as _i1; +import 'dart:typed_data' as _i2; + +final _contractAbi = _i1.ContractAbi.fromJson( + '[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract SafeProxy","name":"proxy","type":"address"},{"indexed":false,"internalType":"address","name":"singleton","type":"address"}],"name":"ProxyCreation","type":"event"},{"inputs":[{"internalType":"address","name":"_singleton","type":"address"},{"internalType":"bytes","name":"initializer","type":"bytes"},{"internalType":"uint256","name":"saltNonce","type":"uint256"}],"name":"createChainSpecificProxyWithNonce","outputs":[{"internalType":"contract SafeProxy","name":"proxy","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_singleton","type":"address"},{"internalType":"bytes","name":"initializer","type":"bytes"},{"internalType":"uint256","name":"saltNonce","type":"uint256"},{"internalType":"contract IProxyCreationCallback","name":"callback","type":"address"}],"name":"createProxyWithCallback","outputs":[{"internalType":"contract SafeProxy","name":"proxy","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_singleton","type":"address"},{"internalType":"bytes","name":"initializer","type":"bytes"},{"internalType":"uint256","name":"saltNonce","type":"uint256"}],"name":"createProxyWithNonce","outputs":[{"internalType":"contract SafeProxy","name":"proxy","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getChainId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxyCreationCode","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"pure","type":"function"}]', + 'SafeProxyFactory', +); + +class SafeProxyFactory extends _i1.GeneratedContract { + SafeProxyFactory({ + required _i1.EthereumAddress address, + required _i1.Web3Client client, + int? chainId, + }) : super( + _i1.DeployedContract( + _contractAbi, + address, + ), + client, + chainId, + ); + + /// The optional [transaction] parameter can be used to override parameters + /// like the gas price, nonce and max gas. The `data` and `to` fields will be + /// set by the contract. + Future createChainSpecificProxyWithNonce( + _i1.EthereumAddress _singleton, + _i2.Uint8List initializer, + BigInt saltNonce, { + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[0]; + assert(checkSignature(function, 'ec9e80bb')); + final params = [ + _singleton, + initializer, + saltNonce, + ]; + return write( + credentials, + transaction, + function, + params, + ); + } + + /// The optional [transaction] parameter can be used to override parameters + /// like the gas price, nonce and max gas. The `data` and `to` fields will be + /// set by the contract. + Future createProxyWithCallback( + _i1.EthereumAddress _singleton, + _i2.Uint8List initializer, + BigInt saltNonce, + _i1.EthereumAddress callback, { + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[1]; + assert(checkSignature(function, 'd18af54d')); + final params = [ + _singleton, + initializer, + saltNonce, + callback, + ]; + return write( + credentials, + transaction, + function, + params, + ); + } + + /// The optional [transaction] parameter can be used to override parameters + /// like the gas price, nonce and max gas. The `data` and `to` fields will be + /// set by the contract. + Future createProxyWithNonce( + _i1.EthereumAddress _singleton, + _i2.Uint8List initializer, + BigInt saltNonce, { + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[2]; + assert(checkSignature(function, '1688f0b9')); + final params = [ + _singleton, + initializer, + saltNonce, + ]; + return write( + credentials, + transaction, + function, + params, + ); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future getChainId({_i1.BlockNum? atBlock}) async { + final function = self.abi.functions[3]; + assert(checkSignature(function, '3408e470')); + final params = []; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as BigInt); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future<_i2.Uint8List> proxyCreationCode({_i1.BlockNum? atBlock}) async { + final function = self.abi.functions[4]; + assert(checkSignature(function, '53e5d935')); + final params = []; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as _i2.Uint8List); + } + + /// Returns a live stream of all ProxyCreation events emitted by this contract. + Stream proxyCreationEvents({ + _i1.BlockNum? fromBlock, + _i1.BlockNum? toBlock, + }) { + final event = self.event('ProxyCreation'); + final filter = _i1.FilterOptions.events( + contract: self, + event: event, + fromBlock: fromBlock, + toBlock: toBlock, + ); + return client.events(filter).map((_i1.FilterEvent result) { + final decoded = event.decodeResults( + result.topics!, + result.data!, + ); + return ProxyCreation( + decoded, + result, + ); + }); + } +} + +class ProxyCreation { + ProxyCreation( + List response, + this.event, + ) : proxy = (response[0] as _i1.EthereumAddress), + singleton = (response[1] as _i1.EthereumAddress); + + final _i1.EthereumAddress proxy; + + final _i1.EthereumAddress singleton; + + final _i1.FilterEvent event; +} diff --git a/lib/src/common/factories.dart b/lib/src/common/factories.dart index fee5447..381b6b5 100644 --- a/lib/src/common/factories.dart +++ b/lib/src/common/factories.dart @@ -1,15 +1,69 @@ part of '../../variance.dart'; -class _SimpleAccountFactory extends SimpleAccountFactory - implements SimpleAccountFactoryBase { - _SimpleAccountFactory( +class _P256AccountFactory extends P256AccountFactory + implements P256AccountFactoryBase { + _P256AccountFactory( {required super.address, super.chainId, required RPCBase rpc}) : super(client: Web3Client.custom(rpc)); } -class _P256AccountFactory extends P256AccountFactory - implements P256AccountFactoryBase { - _P256AccountFactory( +class _SafeProxyFactory extends SafeProxyFactory + implements SafeProxyFactoryBase { + final EthereumAddress safe4337ModuleAddress = + EthereumAddress.fromHex("0xa581c4A4DB7175302464fF3C06380BC3270b4037"); + final EthereumAddress safeSingletonAddress = + EthereumAddress.fromHex("0x41675C099F32341bf84BFc5382aF534df5C7461a"); + final EthereumAddress safeModuleSetupAddress = + EthereumAddress.fromHex("0x8EcD4ec46D4D2a6B64fE960B3D64e8B94B2234eb"); + + _SafeProxyFactory( + {required super.address, super.chainId, required RPCBase rpc}) + : super(client: Web3Client.custom(rpc)); + + Uint8List getInitializer(EthereumAddress owner) { + return Contract.encodeFunctionCall( + "setup", safeSingletonAddress, ContractAbis.get("setup"), [ + [owner], + BigInt.one, + safeModuleSetupAddress, + Contract.encodeFunctionCall("enableModules", safeModuleSetupAddress, + ContractAbis.get("enableModules"), [ + [safe4337ModuleAddress] + ]), + safe4337ModuleAddress, + Constants.zeroAddress, + BigInt.zero, + Constants.zeroAddress, + ]); + } + + EthereumAddress getPredictedSafe( + Uint8List initializer, Uint256 salt, Uint8List creationCode) { + final deploymentData = Uint8List.fromList( + [...creationCode, ...safeSingletonAddress.addressBytes], + ); + + final hash = keccak256( + Uint8List.fromList([ + 0xff, + ...self.address.addressBytes, + ...keccak256(Uint8List.fromList([ + ...keccak256(initializer), + ...intToBytes(salt.value), + ])), + ...keccak256(deploymentData), + ]), + ); + + final predictedAddress = + EthereumAddress(Uint8List.fromList(hash.skip(12).take(20).toList())); + return predictedAddress; + } +} + +class _SimpleAccountFactory extends SimpleAccountFactory + implements SimpleAccountFactoryBase { + _SimpleAccountFactory( {required super.address, super.chainId, required RPCBase rpc}) : super(client: Web3Client.custom(rpc)); } diff --git a/lib/src/interfaces/account_factory.dart b/lib/src/interfaces/account_factories.dart similarity index 71% rename from lib/src/interfaces/account_factory.dart rename to lib/src/interfaces/account_factories.dart index 4d0e19c..9c68f6c 100644 --- a/lib/src/interfaces/account_factory.dart +++ b/lib/src/interfaces/account_factories.dart @@ -21,3 +21,15 @@ abstract class P256AccountFactoryBase { BlockNum? atBlock, }); } + +abstract class SafeProxyFactoryBase { + Future proxyCreationCode({BlockNum? atBlock}); + + Future createProxyWithNonce( + EthereumAddress _singleton, + Uint8List initializer, + BigInt saltNonce, { + required Credentials credentials, + Transaction? transaction, + }); +} diff --git a/lib/src/interfaces/bundler_provider.dart b/lib/src/interfaces/bundler_provider.dart index 51ef227..f5c9bd6 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 [EntryPoint] representing the entrypoint for the operation. + /// - `entrypoint`: The [EntryPointAddress] 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, EntryPoint entrypoint); + Map userOp, EntryPointAddress 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 [EntryPoint] representing the entrypoint for the operation. + /// - `entrypoint`: The [EntryPointAddress] 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, EntryPoint entrypoint, RPCBase rpc); + Map userOp, EntryPointAddress entrypoint, RPCBase rpc); /// 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 91f50c2..90b5e9d 100644 --- a/lib/src/interfaces/interfaces.dart +++ b/lib/src/interfaces/interfaces.dart @@ -6,7 +6,7 @@ import 'package:web3dart/web3dart.dart'; import '../../variance.dart' show Chain, - EntryPoint, + EntryPointAddress, InvalidBundlerMethod, RPCBase, UserOperation, @@ -15,7 +15,7 @@ import '../../variance.dart' UserOperationReceipt, UserOperationResponse; -part 'account_factory.dart'; +part 'account_factories.dart'; part 'bundler_provider.dart'; part 'rpc_provider.dart'; From a4f5f9d2fdba3a6fbf2e010065535ca5b442bcf2 Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Tue, 26 Mar 2024 16:59:56 +0100 Subject: [PATCH 13/31] feat: abi chunks --- lib/src/4337/providers.dart | 16 +++++------ lib/src/abis/contract_abis.dart | 49 ++++++++++++++++++++++++++++++--- lib/src/common/contract.dart | 13 +++++---- 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/lib/src/4337/providers.dart b/lib/src/4337/providers.dart index ed986a9..28655f9 100644 --- a/lib/src/4337/providers.dart +++ b/lib/src/4337/providers.dart @@ -1,12 +1,12 @@ part of '../../variance.dart'; class BundlerProvider implements BundlerProviderBase { - final RPCBase _rpc; + final RPCBase rpc; BundlerProvider(Chain chain) : assert(isURL(chain.bundlerUrl), InvalidBundlerUrl(chain.bundlerUrl)), - _rpc = RPCBase(chain.bundlerUrl!) { - _rpc + rpc = RPCBase(chain.bundlerUrl!) { + rpc .send('eth_chainId') .then(BigInt.parse) .then((value) => value.toInt() == chain.chainId) @@ -20,7 +20,7 @@ class BundlerProvider implements BundlerProviderBase { Map userOp, EntryPointAddress entrypoint) async { Logger.conditionalWarning( !_initialized, "estimateUserOpGas may fail: chainId mismatch"); - final opGas = await _rpc.send>( + final opGas = await rpc.send>( 'eth_estimateUserOperationGas', [userOp, entrypoint.hex]); return UserOperationGas.fromMap(opGas); } @@ -29,7 +29,7 @@ class BundlerProvider implements BundlerProviderBase { Future getUserOperationByHash(String userOpHash) async { Logger.conditionalWarning( !_initialized, "getUserOpByHash may fail: chainId mismatch"); - final opExtended = await _rpc + final opExtended = await rpc .send>('eth_getUserOperationByHash', [userOpHash]); return UserOperationByHash.fromMap(opExtended); } @@ -38,7 +38,7 @@ class BundlerProvider implements BundlerProviderBase { Future getUserOpReceipt(String userOpHash) async { Logger.conditionalWarning( !_initialized, "getUserOpReceipt may fail: chainId mismatch"); - final opReceipt = await _rpc.send>( + final opReceipt = await rpc.send>( 'eth_getUserOperationReceipt', [userOpHash]); return UserOperationReceipt.fromMap(opReceipt); } @@ -48,7 +48,7 @@ class BundlerProvider implements BundlerProviderBase { EntryPointAddress entrypoint, RPCBase fallback) async { Logger.conditionalWarning( !_initialized, "sendUserOp may fail: chainId mismatch"); - final opHash = await _rpc + final opHash = await rpc .send('eth_sendUserOperation', [userOp, entrypoint.hex]); return UserOperationResponse(opHash, entrypoint, fallback); } @@ -56,7 +56,7 @@ class BundlerProvider implements BundlerProviderBase { @override Future> supportedEntryPoints() async { final entrypointList = - await _rpc.send>('eth_supportedEntryPoints'); + await rpc.send>('eth_supportedEntryPoints'); return List.castFrom(entrypointList); } } diff --git a/lib/src/abis/contract_abis.dart b/lib/src/abis/contract_abis.dart index cd5d490..53c1426 100644 --- a/lib/src/abis/contract_abis.dart +++ b/lib/src/abis/contract_abis.dart @@ -4,29 +4,70 @@ class ContractAbis { static ContractAbi get(String name, {ContractAbi? abi}) { String abi; switch (name) { - case 'ERC721': + case 'ERC20_BalanceOf': abi = - '[{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"string","name":"_tokenURI","type":"string"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"baseURI","type":"string"}],"name":"setBaseURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"}]'; + '[{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]'; break; - case 'ERC20': + case 'ERC20_Approve': abi = - '[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"mint","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"account","type":"address"}],"name":"addMinter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"renounceMinter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"isMinter","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"name","type":"string"},{"name":"symbol","type":"string"},{"name":"decimals","type":"uint8"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"}],"name":"MinterAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"}],"name":"MinterRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"}]'; + '[{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]'; + break; + case 'ERC20_Allowance': + abi = + '[{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]'; + break; + case 'ERC20_Transfer': + abi = + '[{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]'; + break; + case 'ERC20_TransferFrom': + abi = + '[{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]'; + break; + case 'ERC20_Mint': + abi = + '[{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"mint","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]'; + break; + case 'ERC721_BalanceOf': + abi = + '[{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]'; + break; + case 'ERC721_OwnerOf': + abi = + '[{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]'; + break; + case 'ERC721_Approve': + abi = + '[{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"}]'; + break; + case 'ERC721_SafeTransferFrom': + abi = + '[{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"}]'; + break; + case 'ERC721_Burn': + abi = + '[{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"}]'; break; case 'getNonce': abi = '[{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint192","name":"key","type":"uint192"}],"name":"getNonce","outputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"stateMutability":"view","type":"function" }]'; + break; case 'execute': abi = '[{"inputs":[{"internalType":"address","name":"dest","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"func","type":"bytes"}],"name":"execute","outputs":[],"stateMutability":"nonpayable","type":"function"}]'; + break; case 'executeBatch': abi = '[{"inputs":[{"internalType":"address[]","name":"dest","type":"address[]"},{"internalType":"uint256[]","name":"value","type":"uint256[]"},{"internalType":"bytes[]","name":"func","type":"bytes[]"}],"name":"executeBatch","outputs":[],"stateMutability":"nonpayable","type":"function"}]'; + break; case 'enableModules': abi = '[{"type":"function","name":"enableModules","inputs":[{"name":"modules","type":"address[]","internalType":"address[]"}],"outputs":[],"stateMutability":"nonpayable"}]'; + break; case 'setup': abi = '[{"type":"function","name":"setup","inputs":[{"name":"_owners","type":"address[]","internalType":"address[]"},{"name":"_threshold","type":"uint256","internalType":"uint256"},{"name":"to","type":"address","internalType":"address"},{"name":"data","type":"bytes","internalType":"bytes"},{"name":"fallbackHandler","type":"address","internalType":"address"},{"name":"paymentToken","type":"address","internalType":"address"},{"name":"payment","type":"uint256","internalType":"uint256"},{"name":"paymentReceiver","type":"address","internalType":"address payable"}],"outputs":[],"stateMutability":"nonpayable"}]'; + break; default: throw 'ABI of $name is not available by default. Please provide the ABI manually.'; } diff --git a/lib/src/common/contract.dart b/lib/src/common/contract.dart index 867cf37..97ab824 100644 --- a/lib/src/common/contract.dart +++ b/lib/src/common/contract.dart @@ -133,7 +133,7 @@ class Contract { return encodeFunctionCall( 'approve', address, - ContractAbis.get('ERC20'), + ContractAbis.get('ERC20_Approve'), [spender, amount.getInWei], ); } @@ -165,7 +165,7 @@ class Contract { return encodeFunctionCall( 'transfer', address, - ContractAbis.get('ERC20'), + ContractAbis.get('ERC20_Transfer'), [recipient, amount.getInWei], ); } @@ -192,7 +192,7 @@ class Contract { static Uint8List encodeERC721ApproveCall( EthereumAddress contractAddress, EthereumAddress to, BigInt tokenId) { return encodeFunctionCall("approve", contractAddress, - ContractAbis.get("ERC721"), [to.hex, tokenId]); + ContractAbis.get("ERC721_Approve"), [to.hex, tokenId]); } /// Encodes an ERC-721 token safe transfer function call. @@ -218,8 +218,11 @@ class Contract { /// This method uses the ERC-721 contract ABI to return a `calldata` for 'safeTransferFrom' function call. static Uint8List encodeERC721SafeTransferCall(EthereumAddress contractAddress, EthereumAddress from, EthereumAddress to, BigInt tokenId) { - return encodeFunctionCall("safeTransferFrom", contractAddress, - ContractAbis.get("ERC721"), [from.hex, to.hex, tokenId]); + return encodeFunctionCall( + "safeTransferFrom", + contractAddress, + ContractAbis.get("ERC721_SafeTransferFrom"), + [from.hex, to.hex, tokenId]); } /// Encodes a function call for a smart contract. From e1f9dd60d77ec97c47ee1638555bfd0092815001 Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Fri, 29 Mar 2024 16:03:18 +0100 Subject: [PATCH 14/31] v1.0.0: safe smart accounts --- lib/src/4337/chains.dart | 49 ++++++--- lib/src/4337/factory.dart | 34 +++--- lib/src/4337/paymaster.dart | 2 +- lib/src/4337/providers.dart | 14 +-- lib/src/4337/safe.dart | 36 +++++++ lib/src/4337/userop.dart | 74 ++++++------- lib/src/4337/wallet.dart | 53 ++++++---- lib/src/abis/abis.dart | 1 + lib/src/abis/contract_abis.dart | 4 + lib/src/abis/safe4337Module.abi.json | 99 ++++++++++++++++++ lib/src/abis/safe4337Module.g.dart | 128 +++++++++++++++++++++++ lib/src/common/contract.dart | 32 ++++-- lib/src/common/factories.dart | 28 ++--- lib/src/common/mixins.dart | 10 -- lib/src/common/pack.dart | 16 +++ lib/src/interfaces/bundler_provider.dart | 4 +- lib/src/interfaces/interfaces.dart | 2 +- lib/src/interfaces/safe_module.dart | 28 +++++ lib/src/interfaces/smart_wallet.dart | 13 ++- lib/variance.dart | 1 + 20 files changed, 487 insertions(+), 141 deletions(-) create mode 100644 lib/src/4337/safe.dart create mode 100644 lib/src/abis/safe4337Module.abi.json create mode 100644 lib/src/abis/safe4337Module.g.dart create mode 100644 lib/src/interfaces/safe_module.dart diff --git a/lib/src/4337/chains.dart b/lib/src/4337/chains.dart index 2f97126..e766807 100644 --- a/lib/src/4337/chains.dart +++ b/lib/src/4337/chains.dart @@ -112,22 +112,16 @@ class Constants { EthereumAddress.fromHex("0x0000000000000000000000000000000000000000"); static final EthereumAddress safeProxyFactoryAddress = EthereumAddress.fromHex("0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67"); + static final EthereumAddress safe4337ModuleAddress = + EthereumAddress.fromHex("0xa581c4A4DB7175302464fF3C06380BC3270b4037"); + static final EthereumAddress safeSingletonAddress = + EthereumAddress.fromHex("0x41675C099F32341bf84BFc5382aF534df5C7461a"); + static final EthereumAddress safeModuleSetupAddress = + EthereumAddress.fromHex("0x8EcD4ec46D4D2a6B64fE960B3D64e8B94B2234eb"); Constants._(); } -enum EntryPointAddress { - v06(0.6, "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"), - v07(0.7, "0x0000000071727De22E5E9d8BAf0edAc6f37da032"); - - final double version; - final String hex; - - const EntryPointAddress(this.version, this.hex); - - EthereumAddress get address => EthereumAddress.fromHex(hex); -} - enum Network { // mainnet ethereum, @@ -147,3 +141,34 @@ enum Network { // localhost localhost } + +class EntryPointAddress { + static EntryPointAddress get v06 => + EntryPointAddress(0.6, Constants.entrypointv06); + static EntryPointAddress get v07 => + EntryPointAddress(0.7, Constants.entrypointv07); + + final double version; + final EthereumAddress address; + + const EntryPointAddress(this.version, this.address); +} + +class Safe4337ModuleAddress { + static Safe4337ModuleAddress v06 = + Safe4337ModuleAddress(0.6, Constants.safe4337ModuleAddress); + + final double version; + final EthereumAddress address; + + const Safe4337ModuleAddress(this.version, this.address); + + factory Safe4337ModuleAddress.fromVersion(double version) { + switch (version) { + case 0.6: + return Safe4337ModuleAddress.v06; + default: + throw Exception("Unsupported version: $version"); + } + } +} diff --git a/lib/src/4337/factory.dart b/lib/src/4337/factory.dart index 7a373d4..24f4739 100644 --- a/lib/src/4337/factory.dart +++ b/lib/src/4337/factory.dart @@ -31,6 +31,12 @@ class SmartWalletFactory implements SmartWalletFactoryBase { chainId: _chain.chainId, rpc: _jsonRpc.rpc); + _SafePlugin get _safePlugin => _SafePlugin( + address: + Safe4337ModuleAddress.fromVersion(_chain.entrypoint.version).address, + chainId: _chain.chainId, + client: _safeProxyFactory.client); + Future createP256Account(T keyPair, Uint256 salt, [EthereumAddress? recoveryAddress]) { switch (keyPair.runtimeType) { @@ -46,22 +52,26 @@ class SmartWalletFactory implements SmartWalletFactoryBase { } } - Future createSafeAccount(Uint256 salt, {int? index}) async { - final signer = _signer.getAddress(index: index ?? 0); - final initializer = - _safeProxyFactory.getInitializer(EthereumAddress.fromHex(signer)); + Future createSafeAccount(Uint256 salt, + [List? owners, int? threshold]) async { + final signer = EthereumAddress.fromHex(_signer.getAddress()); + final ownerSet = owners != null ? {signer, ...owners} : [signer]; + final initializer = _safeProxyFactory.getInitializer( + ownerSet, + threshold ?? 1, + Safe4337ModuleAddress.fromVersion(_chain.entrypoint.version)); final creation = await _safeProxyFactory.proxyCreationCode(); final address = _safeProxyFactory.getPredictedSafe(initializer, salt, creation); final initCallData = _safeProxyFactory.self .function("createProxyWithNonce") - .encodeCall( - [_safeProxyFactory.safeSingletonAddress, initializer, salt.value]); + .encodeCall([Constants.safeSingletonAddress, initializer, salt.value]); final initCode = _getInitCode(initCallData); - return _createAccount(_chain, address, initCode); + return _createAccount(_chain, address, initCode) + ..addPlugin<_SafePlugin>('safe', _safePlugin); } - Future createSimpleAccount(Uint256 salt, {int? index}) async { + Future createSimpleAccount(Uint256 salt, [int? index]) async { final signer = _signer.getAddress(index: index ?? 0); final address = await _simpleAccountfactory.getAddress( EthereumAddress.fromHex(signer), salt.value); @@ -82,10 +92,10 @@ class SmartWalletFactory implements SmartWalletFactoryBase { SmartWallet _createAccount( Chain chain, EthereumAddress address, Uint8List initCalldata) { return SmartWallet(chain, address, initCalldata) - ..addPlugin('signer', _signer) - ..addPlugin('bundler', _bundler) - ..addPlugin('jsonRpc', _jsonRpc) - ..addPlugin('contract', _contract); + ..addPlugin('signer', _signer) + ..addPlugin('bundler', _bundler) + ..addPlugin('jsonRpc', _jsonRpc) + ..addPlugin('contract', _contract); } Future _createPasskeyAccount(PassKeyPair pkp, Uint256 salt, diff --git a/lib/src/4337/paymaster.dart b/lib/src/4337/paymaster.dart index 4d3e3d8..388faf3 100644 --- a/lib/src/4337/paymaster.dart +++ b/lib/src/4337/paymaster.dart @@ -52,7 +52,7 @@ class Paymaster { Future sponsorUserOperation(Map userOp, EntryPointAddress entrypoint, Map? context) async { final response = await _rpc.send>( - 'pm_sponsorUserOperation', [userOp, entrypoint.hex, context]); + 'pm_sponsorUserOperation', [userOp, entrypoint.address.hex, context]); return PaymasterResponse.fromMap(response); } } diff --git a/lib/src/4337/providers.dart b/lib/src/4337/providers.dart index 28655f9..d35a4d6 100644 --- a/lib/src/4337/providers.dart +++ b/lib/src/4337/providers.dart @@ -21,7 +21,7 @@ class BundlerProvider implements BundlerProviderBase { Logger.conditionalWarning( !_initialized, "estimateUserOpGas may fail: chainId mismatch"); final opGas = await rpc.send>( - 'eth_estimateUserOperationGas', [userOp, entrypoint.hex]); + 'eth_estimateUserOperationGas', [userOp, entrypoint.address.hex]); return UserOperationGas.fromMap(opGas); } @@ -35,7 +35,7 @@ class BundlerProvider implements BundlerProviderBase { } @override - Future getUserOpReceipt(String userOpHash) async { + Future getUserOpReceipt(String userOpHash) async { Logger.conditionalWarning( !_initialized, "getUserOpReceipt may fail: chainId mismatch"); final opReceipt = await rpc.send>( @@ -44,13 +44,13 @@ class BundlerProvider implements BundlerProviderBase { } @override - Future sendUserOperation(Map userOp, - EntryPointAddress entrypoint, RPCBase fallback) async { + Future sendUserOperation( + Map userOp, EntryPointAddress entrypoint) async { Logger.conditionalWarning( !_initialized, "sendUserOp may fail: chainId mismatch"); - final opHash = await rpc - .send('eth_sendUserOperation', [userOp, entrypoint.hex]); - return UserOperationResponse(opHash, entrypoint, fallback); + final opHash = await rpc.send( + 'eth_sendUserOperation', [userOp, entrypoint.address.hex]); + return UserOperationResponse(opHash, getUserOpReceipt); } @override diff --git a/lib/src/4337/safe.dart b/lib/src/4337/safe.dart new file mode 100644 index 0000000..694e9f0 --- /dev/null +++ b/lib/src/4337/safe.dart @@ -0,0 +1,36 @@ +part of '../../variance.dart'; + +class _SafePlugin extends Safe4337Module implements Safe4337ModuleBase { + _SafePlugin({ + required super.address, + super.chainId, + required super.client, + }); + + Future getUserOperationHash(UserOperation op) async => + getOperationHash([ + op.sender, + op.nonce, + op.initCode, + op.callData, + op.callGasLimit, + op.verificationGasLimit, + op.preVerificationGas, + op.maxFeePerGas, + op.maxPriorityFeePerGas, + op.paymasterAndData, + _getEncodedSignature(op.signature) + ]); + + Uint8List _getEncodedSignature(String signature) { + final currentTimestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000; + + String validAfter = currentTimestamp.toRadixString(16); + validAfter = '0' * (12 - validAfter.length) + validAfter; + + String validUntil = (currentTimestamp + 3600).toRadixString(16); + validUntil = '0' * (12 - validUntil.length) + validUntil; + + return hexToBytes('0x$validAfter$validUntil${signature.substring(2)}'); + } +} diff --git a/lib/src/4337/userop.dart b/lib/src/4337/userop.dart index e58cd90..087d825 100644 --- a/lib/src/4337/userop.dart +++ b/lib/src/4337/userop.dart @@ -146,7 +146,7 @@ class UserOperation implements UserOperationBase { @override Uint8List hash(Chain chain) { Uint8List encoded; - if (chain.entrypoint == EntryPointAddress.v07) { + if (chain.entrypoint.version >= EntryPointAddress.v07.version) { encoded = keccak256(abi.encode([ 'address', 'uint256', @@ -230,7 +230,7 @@ class UserOperation implements UserOperationBase { ); } - Future validate(bool deployed, [String? initCode]) async { + void validate(bool deployed, [String? initCode]) { require( deployed ? hexlify(this.initCode).toLowerCase() == "0x" @@ -333,56 +333,44 @@ class UserOperationReceipt { } } -class UserOperationEventFilter extends FilterOptions { - UserOperationEventFilter.events({ - required super.contract, - required super.event, - super.fromBlock, - super.toBlock, - required String userOpHash, - }) : super.events() { - if (userOpHash.isNotEmpty) { - topics?.add([userOpHash]); - } - } -} - class UserOperationResponse { final String userOpHash; - final EntryPointAddress _entrypoint; - final RPCBase _rpc; + final Future Function(String) _callback; + + UserOperationResponse(this.userOpHash, this._callback); + + Future wait( + [Duration timeout = const Duration(seconds: 15), + Duration pollInterval = const Duration(seconds: 2)]) async { + assert( + timeout.inSeconds > 0, + RangeError.value( + timeout.inSeconds, "timeout", "wait timeout must be >= 0 sec")); + assert( + pollInterval.inSeconds > 0, + RangeError.value(pollInterval.inSeconds, "pollInterval", + "pollInterval must be >= 0 sec")); + assert( + pollInterval < timeout, + RangeError.value( + timeout.inSeconds, "timeout", "timeout must be > pollInterval")); - UserOperationResponse(this.userOpHash, this._entrypoint, this._rpc); + return await Isolate.run(() async { + Duration count = Duration.zero; - Future wait([int millisecond = 3000]) async { - final end = DateTime.now().millisecondsSinceEpoch + millisecond; + while (count < timeout) { + await Future.delayed(pollInterval); - return await Isolate.run(() async { - final client = Web3Client.custom(_rpc); - final entrypoint = - Entrypoint(address: _entrypoint.address, client: client); - - final block = await client.getBlockNumber(); - while (DateTime.now().millisecondsSinceEpoch < end) { - final filterEvent = await client - .events( - UserOperationEventFilter.events( - contract: entrypoint.self, - event: entrypoint.self.event('UserOperationEvent'), - userOpHash: userOpHash, - fromBlock: BlockNum.exact(block - 100), - ), - ) - .take(1) - .first; - if (filterEvent.transactionHash != null) { - return filterEvent; + final receipt = await _callback(userOpHash); + if (receipt != null) { + return receipt; } - await Future.delayed(Duration(milliseconds: millisecond)); + count += pollInterval; } - return null; + throw TimeoutException( + "can't find useroperation with hash $userOpHash", timeout); }); } } diff --git a/lib/src/4337/wallet.dart b/lib/src/4337/wallet.dart index 5e0c063..57d5955 100644 --- a/lib/src/4337/wallet.dart +++ b/lib/src/4337/wallet.dart @@ -32,10 +32,14 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { @override String? get toHex => _walletAddress.hexEip55; + @override + String get dummySignature => plugin('signer').dummySignature; + Future get _initCodeGas => plugin('jsonRpc') .estimateGas(_chain.entrypoint.address, initCode); @override + @Deprecated("Not recommended to modify the initcode") void dangerouslySetInitCode(Uint8List code) { _initCode = code; } @@ -55,8 +59,8 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { Future send( EthereumAddress recipient, EtherAmount amount) => sendUserOperation(buildUserOperation( - callData: - Contract.execute(_walletAddress, to: recipient, amount: amount))); + callData: Contract.execute(_walletAddress, + to: recipient, amount: amount, isSafe: hasPlugin("safe")))); @override Future sendTransaction( @@ -64,7 +68,10 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { {EtherAmount? amount}) => sendUserOperation(buildUserOperation( callData: Contract.execute(_walletAddress, - to: to, amount: amount, innerCallData: encodedFunctionData))); + to: to, + amount: amount, + innerCallData: encodedFunctionData, + isSafe: hasPlugin("safe")))); @override Future sendBatchedTransaction( @@ -75,37 +82,43 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { walletAddress: _walletAddress, recipients: recipients, amounts: amounts, - innerCalls: calls))); + innerCalls: calls, + isSafe: hasPlugin("safe")))); @override Future sendSignedUserOperation(UserOperation op) => plugin('bundler') - .sendUserOperation( - op.toMap(), _chain.entrypoint, plugin('jsonRpc').rpc) + .sendUserOperation(op.toMap(), _chain.entrypoint) .catchError((e) => throw SendError(e.toString(), op)); @override Future sendUserOperation(UserOperation op) => - signUserOperation(op) - .then(sendSignedUserOperation) - .catchError((e) => retryOp(() => sendSignedUserOperation(op), e)); + prepareUserOperation(op) + .then(signUserOperation) + .then(sendSignedUserOperation); @override - Future signUserOperation(UserOperation userOp, - {bool update = true, int? index}) async { - if (update) userOp = await _updateUserOperation(userOp); - - final opHash = userOp.hash(_chain); + Future prepareUserOperation(UserOperation op, + {bool update = true}) async { + if (update) op = await _updateUserOperation(op); if (hasPlugin('paymaster')) { - userOp = await plugin('paymaster').intercept(userOp); + op = await plugin('paymaster').intercept(op); } + op.validate(op.nonce > BigInt.zero, initCode); + return op; + } + + @override + Future signUserOperation(UserOperation op, + {int? index}) async { + final opHash = hasPlugin("safe") + ? await plugin<_SafePlugin>("safe").getUserOperationHash(op) + : op.hash(_chain); Uint8List signature = await plugin('signer').personalSign(opHash, index: index); - - userOp.signature = hexlify(signature); - userOp.validate(userOp.nonce > BigInt.zero, initCode); - return userOp; + op.signature = hexlify(signature); + return op; } Future _getNonce() => isDeployed.then((deployed) => !deployed @@ -125,7 +138,7 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { 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); + signature: dummySignature); return _updateUserOperationGas(op, feePerGas: responses[1]); }); diff --git a/lib/src/abis/abis.dart b/lib/src/abis/abis.dart index b39adbe..6b08f08 100644 --- a/lib/src/abis/abis.dart +++ b/lib/src/abis/abis.dart @@ -1,5 +1,6 @@ export 'simpleAccountFactory.g.dart'; export 'p256AccountFactory.g.dart'; export 'safeProxyFactory.g.dart'; +export 'safe4337Module.g.dart'; export 'contract_abis.dart'; export 'entrypoint.g.dart'; diff --git a/lib/src/abis/contract_abis.dart b/lib/src/abis/contract_abis.dart index 53c1426..7b54989 100644 --- a/lib/src/abis/contract_abis.dart +++ b/lib/src/abis/contract_abis.dart @@ -56,6 +56,10 @@ class ContractAbis { abi = '[{"inputs":[{"internalType":"address","name":"dest","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"func","type":"bytes"}],"name":"execute","outputs":[],"stateMutability":"nonpayable","type":"function"}]'; break; + case 'executeUserOpWithErrorString': + abi = + '[{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"uint8","name":"operation","type":"uint8"}],"name":"executeUserOpWithErrorString","outputs":[],"stateMutability":"nonpayable","type":"function"}]'; + break; case 'executeBatch': abi = '[{"inputs":[{"internalType":"address[]","name":"dest","type":"address[]"},{"internalType":"uint256[]","name":"value","type":"uint256[]"},{"internalType":"bytes[]","name":"func","type":"bytes[]"}],"name":"executeBatch","outputs":[],"stateMutability":"nonpayable","type":"function"}]'; diff --git a/lib/src/abis/safe4337Module.abi.json b/lib/src/abis/safe4337Module.abi.json new file mode 100644 index 0000000..45a4f90 --- /dev/null +++ b/lib/src/abis/safe4337Module.abi.json @@ -0,0 +1,99 @@ +[ + { + "inputs": [ + { "internalType": "address", "name": "entryPoint", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "SUPPORTED_ENTRYPOINT", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "domainSeparator", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "value", "type": "uint256" }, + { "internalType": "bytes", "name": "data", "type": "bytes" }, + { "internalType": "uint8", "name": "operation", "type": "uint8" } + ], + "name": "executeUserOp", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "value", "type": "uint256" }, + { "internalType": "bytes", "name": "data", "type": "bytes" }, + { "internalType": "uint8", "name": "operation", "type": "uint8" } + ], + "name": "executeUserOpWithErrorString", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "uint256", "name": "nonce", "type": "uint256" }, + { "internalType": "bytes", "name": "initCode", "type": "bytes" }, + { "internalType": "bytes", "name": "callData", "type": "bytes" }, + { + "internalType": "uint256", + "name": "callGasLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "verificationGasLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "preVerificationGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxFeePerGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxPriorityFeePerGas", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "paymasterAndData", + "type": "bytes" + }, + { "internalType": "bytes", "name": "signature", "type": "bytes" } + ], + "internalType": "struct UserOperation", + "name": "userOp", + "type": "tuple" + } + ], + "name": "getOperationHash", + "outputs": [ + { "internalType": "bytes32", "name": "operationHash", "type": "bytes32" } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/lib/src/abis/safe4337Module.g.dart b/lib/src/abis/safe4337Module.g.dart new file mode 100644 index 0000000..7df61a6 --- /dev/null +++ b/lib/src/abis/safe4337Module.g.dart @@ -0,0 +1,128 @@ +// Generated code, do not modify. Run `build_runner build` to re-generate! +// @dart=2.12 +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:web3dart/web3dart.dart' as _i1; +import 'dart:typed_data' as _i2; + +final _contractAbi = _i1.ContractAbi.fromJson( + '[{"inputs":[{"internalType":"address","name":"entryPoint","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"SUPPORTED_ENTRYPOINT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"domainSeparator","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"uint8","name":"operation","type":"uint8"}],"name":"executeUserOp","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"uint8","name":"operation","type":"uint8"}],"name":"executeUserOpWithErrorString","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"uint256","name":"callGasLimit","type":"uint256"},{"internalType":"uint256","name":"verificationGasLimit","type":"uint256"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"uint256","name":"maxFeePerGas","type":"uint256"},{"internalType":"uint256","name":"maxPriorityFeePerGas","type":"uint256"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct UserOperation","name":"userOp","type":"tuple"}],"name":"getOperationHash","outputs":[{"internalType":"bytes32","name":"operationHash","type":"bytes32"}],"stateMutability":"view","type":"function"}]', + 'Safe4337Module', +); + +class Safe4337Module extends _i1.GeneratedContract { + Safe4337Module({ + required _i1.EthereumAddress address, + required _i1.Web3Client client, + int? chainId, + }) : super( + _i1.DeployedContract( + _contractAbi, + address, + ), + client, + chainId, + ); + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future<_i1.EthereumAddress> SUPPORTED_ENTRYPOINT( + {_i1.BlockNum? atBlock}) async { + final function = self.abi.functions[1]; + assert(checkSignature(function, '137e051e')); + final params = []; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as _i1.EthereumAddress); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future<_i2.Uint8List> domainSeparator({_i1.BlockNum? atBlock}) async { + final function = self.abi.functions[2]; + assert(checkSignature(function, 'f698da25')); + final params = []; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as _i2.Uint8List); + } + + /// The optional [transaction] parameter can be used to override parameters + /// like the gas price, nonce and max gas. The `data` and `to` fields will be + /// set by the contract. + Future executeUserOp( + _i1.EthereumAddress to, + BigInt value, + _i2.Uint8List data, + BigInt operation, { + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[3]; + assert(checkSignature(function, '7bb37428')); + final params = [ + to, + value, + data, + operation, + ]; + return write( + credentials, + transaction, + function, + params, + ); + } + + /// The optional [transaction] parameter can be used to override parameters + /// like the gas price, nonce and max gas. The `data` and `to` fields will be + /// set by the contract. + Future executeUserOpWithErrorString( + _i1.EthereumAddress to, + BigInt value, + _i2.Uint8List data, + BigInt operation, { + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[4]; + assert(checkSignature(function, '541d63c8')); + final params = [ + to, + value, + data, + operation, + ]; + return write( + credentials, + transaction, + function, + params, + ); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future<_i2.Uint8List> getOperationHash( + dynamic userOp, { + _i1.BlockNum? atBlock, + }) async { + final function = self.abi.functions[5]; + assert(checkSignature(function, 'b25f3776')); + final params = [userOp]; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as _i2.Uint8List); + } +} diff --git a/lib/src/common/contract.dart b/lib/src/common/contract.dart index 97ab824..dd71097 100644 --- a/lib/src/common/contract.dart +++ b/lib/src/common/contract.dart @@ -2,10 +2,10 @@ part of '../../variance.dart'; /// A wrapper for interacting with deployed Ethereum contracts through [JsonRPCProvider]. class Contract { - final RPCBase _rpc; + final RPCBase rpc; Contract( - this._rpc, + this.rpc, ); /// Asynchronously calls a function on a smart contract with the provided parameters. @@ -43,7 +43,7 @@ class Contract { : "0x", if (sender != null) 'from': sender.hex, }; - return _rpc.send('eth_call', [ + return rpc.send('eth_call', [ calldata, BlockNum.current().toBlockParam() ]).then((value) => function.decodeReturnValues(value)); @@ -71,7 +71,7 @@ class Contract { if (address == null) { return Future.value(false); } - final isDeployed = _rpc + final isDeployed = rpc .send('eth_getCode', [address.hex, atBlock.toBlockParam()]) .then(hexToBytes) .then((value) => value.isNotEmpty); @@ -100,7 +100,7 @@ class Contract { if (address == null) { return Future.value(EtherAmount.zero()); } - return _rpc + return rpc .send('eth_getBalance', [address.hex, atBlock.toBlockParam()]) .then(BigInt.parse) .then((value) => EtherAmount.fromBigInt(EtherUnit.wei, value)); @@ -276,17 +276,21 @@ class Contract { static Uint8List execute(EthereumAddress walletAddress, {required EthereumAddress to, EtherAmount? amount, - Uint8List? innerCallData}) { + Uint8List? innerCallData, + bool isSafe = false}) { final params = [ to, amount?.getInWei ?? EtherAmount.zero().getInWei, - innerCallData ?? Uint8List.fromList([]) + innerCallData ?? Uint8List(0) ]; + if (isSafe) params.add(BigInt.zero); + final method = isSafe ? 'executeUserOpWithErrorString' : 'execute'; + return encodeFunctionCall( - 'execute', + method, walletAddress, - ContractAbis.get('execute'), + ContractAbis.get(method), params, ); } @@ -322,11 +326,17 @@ class Contract { {required EthereumAddress walletAddress, required List recipients, List? amounts, - List? innerCalls}) { + List? innerCalls, + bool isSafe = false}) { + if (isSafe) { + throw UnimplementedError( + "Batch Transactions with Safe are not yet implemented."); + } + final params = [ recipients, amounts?.map((e) => e.getInWei) ?? [], - innerCalls ?? Uint8List.fromList([]), + innerCalls ?? Uint8List(0), ]; if (innerCalls == null || innerCalls.isEmpty) { require(amounts != null && amounts.isNotEmpty, "malformed batch request"); diff --git a/lib/src/common/factories.dart b/lib/src/common/factories.dart index 381b6b5..34e140c 100644 --- a/lib/src/common/factories.dart +++ b/lib/src/common/factories.dart @@ -9,28 +9,22 @@ class _P256AccountFactory extends P256AccountFactory class _SafeProxyFactory extends SafeProxyFactory implements SafeProxyFactoryBase { - final EthereumAddress safe4337ModuleAddress = - EthereumAddress.fromHex("0xa581c4A4DB7175302464fF3C06380BC3270b4037"); - final EthereumAddress safeSingletonAddress = - EthereumAddress.fromHex("0x41675C099F32341bf84BFc5382aF534df5C7461a"); - final EthereumAddress safeModuleSetupAddress = - EthereumAddress.fromHex("0x8EcD4ec46D4D2a6B64fE960B3D64e8B94B2234eb"); - _SafeProxyFactory( {required super.address, super.chainId, required RPCBase rpc}) : super(client: Web3Client.custom(rpc)); - Uint8List getInitializer(EthereumAddress owner) { + Uint8List getInitializer(Iterable owners, int threshold, + Safe4337ModuleAddress module) { return Contract.encodeFunctionCall( - "setup", safeSingletonAddress, ContractAbis.get("setup"), [ - [owner], - BigInt.one, - safeModuleSetupAddress, - Contract.encodeFunctionCall("enableModules", safeModuleSetupAddress, - ContractAbis.get("enableModules"), [ - [safe4337ModuleAddress] + "setup", Constants.safeSingletonAddress, ContractAbis.get("setup"), [ + owners.toList(), + BigInt.from(threshold), + Constants.safeModuleSetupAddress, + Contract.encodeFunctionCall("enableModules", + Constants.safeModuleSetupAddress, ContractAbis.get("enableModules"), [ + [module.address] ]), - safe4337ModuleAddress, + module.address, Constants.zeroAddress, BigInt.zero, Constants.zeroAddress, @@ -40,7 +34,7 @@ class _SafeProxyFactory extends SafeProxyFactory EthereumAddress getPredictedSafe( Uint8List initializer, Uint256 salt, Uint8List creationCode) { final deploymentData = Uint8List.fromList( - [...creationCode, ...safeSingletonAddress.addressBytes], + [...creationCode, ...Constants.safeSingletonAddress.addressBytes], ); final hash = keccak256( diff --git a/lib/src/common/mixins.dart b/lib/src/common/mixins.dart index 65d2789..dba8b26 100644 --- a/lib/src/common/mixins.dart +++ b/lib/src/common/mixins.dart @@ -6,13 +6,11 @@ class GasSettings { Percent? gasMultiplierPercentage; BigInt? userDefinedMaxFeePerGas; BigInt? userDefinedMaxPriorityFeePerGas; - bool retryFailedSendUserOp; GasSettings({ this.gasMultiplierPercentage = 0, this.userDefinedMaxFeePerGas, this.userDefinedMaxPriorityFeePerGas, - this.retryFailedSendUserOp = false, }) : assert(gasMultiplierPercentage! >= 0, RangeOutOfBounds("Wrong Gas multiplier percentage", 0, 100)); } @@ -33,14 +31,6 @@ mixin _GasSettings { maxFeePerGas: _gasParams.userDefinedMaxFeePerGas, maxPriorityFeePerGas: _gasParams.userDefinedMaxPriorityFeePerGas); } - - Future retryOp( - Future Function() callback, dynamic err) { - if (_gasParams.retryFailedSendUserOp) { - return callback(); - } - throw err; - } } mixin _PluginManager { diff --git a/lib/src/common/pack.dart b/lib/src/common/pack.dart index 789d0cd..e69099c 100644 --- a/lib/src/common/pack.dart +++ b/lib/src/common/pack.dart @@ -41,3 +41,19 @@ List unpackUints(Uint8List bytes) { BigInt.from(0xFFFFFFFFFFFFFFFF) << 64 | BigInt.from(0xFFFFFFFFFFFFFFFF); return [value >> 128, value & mask]; } + +Map packUserOperation(UserOperation userOp) { + return { + 'sender': userOp.sender.hex, + 'nonce': '0x${userOp.nonce.toRadixString(16)}', + 'initCode': hexlify(userOp.initCode), + 'callData': hexlify(userOp.callData), + 'accountGasLimits': + hexlify(packUints(userOp.verificationGasLimit, userOp.callGasLimit)), + 'preVerificationGas': '0x${userOp.preVerificationGas.toRadixString(16)}', + 'gasFees': + hexlify(packUints(userOp.maxPriorityFeePerGas, userOp.maxFeePerGas)), + 'signature': userOp.signature, + 'paymasterAndData': hexlify(userOp.paymasterAndData), + }; +} diff --git a/lib/src/interfaces/bundler_provider.dart b/lib/src/interfaces/bundler_provider.dart index f5c9bd6..fe336a3 100644 --- a/lib/src/interfaces/bundler_provider.dart +++ b/lib/src/interfaces/bundler_provider.dart @@ -64,7 +64,7 @@ abstract class BundlerProviderBase { /// var userOpReceipt = await getUserOpReceipt('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'); /// ``` /// This method uses the bundled RPC to fetch the receipt of the specified user operation using its hash. - Future getUserOpReceipt(String userOpHash); + Future getUserOpReceipt(String userOpHash); /// Asynchronously sends a user operation to the bundler for execution. /// @@ -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, EntryPointAddress entrypoint, RPCBase rpc); + Map userOp, EntryPointAddress 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 90b5e9d..a110457 100644 --- a/lib/src/interfaces/interfaces.dart +++ b/lib/src/interfaces/interfaces.dart @@ -8,7 +8,6 @@ import '../../variance.dart' Chain, EntryPointAddress, InvalidBundlerMethod, - RPCBase, UserOperation, UserOperationByHash, UserOperationGas, @@ -21,4 +20,5 @@ part 'bundler_provider.dart'; part 'rpc_provider.dart'; part 'smart_wallet_factory.dart'; part 'smart_wallet.dart'; +part 'safe_module.dart'; part 'user_operations.dart'; diff --git a/lib/src/interfaces/safe_module.dart b/lib/src/interfaces/safe_module.dart new file mode 100644 index 0000000..1556abc --- /dev/null +++ b/lib/src/interfaces/safe_module.dart @@ -0,0 +1,28 @@ +part of 'interfaces.dart'; + +abstract class Safe4337ModuleBase { + Future SUPPORTED_ENTRYPOINT({BlockNum? atBlock}); + + Future executeUserOpWithErrorString( + EthereumAddress to, + BigInt value, + Uint8List data, + BigInt operation, { + required Credentials credentials, + Transaction? transaction, + }); + + Future executeUserOp( + EthereumAddress to, + BigInt value, + Uint8List data, + BigInt operation, { + required Credentials credentials, + Transaction? transaction, + }); + + Future getOperationHash( + dynamic userOp, { + BlockNum? atBlock, + }); +} diff --git a/lib/src/interfaces/smart_wallet.dart b/lib/src/interfaces/smart_wallet.dart index 8da3105..a68cc6d 100644 --- a/lib/src/interfaces/smart_wallet.dart +++ b/lib/src/interfaces/smart_wallet.dart @@ -28,6 +28,9 @@ abstract class SmartWalletBase { /// Converts the Smart Wallet address to its hexadecimal representation. String? get toHex; + /// Retrieves the dummy signature required for gas estimation from the Smart Wallet. + String get dummySignature; + /// Builds a [UserOperation] instance with the specified parameters. /// /// Parameters: @@ -62,6 +65,7 @@ abstract class SmartWalletBase { /// ```dart /// dangerouslySetInitCallData(Uint8List.fromList([0x01, 0x02, 0x03])); /// ``` + @Deprecated("Not recommended to modify the initcode") void dangerouslySetInitCode(Uint8List code); /// Asynchronously transfers native Token (ETH) to the specified recipient with the given amount. @@ -191,9 +195,8 @@ abstract class SmartWalletBase { /// var signedOperation = await signUserOperation(myUserOperation, index: 0); // signer 0 /// var signedOperation = await signUserOperation(myUserOperation, index: 1); // signer 1 /// ``` - Future signUserOperation( - UserOperation userOp, { - bool update = true, - int? index, - }); + Future signUserOperation(UserOperation op, {int? index}); + + Future prepareUserOperation(UserOperation op, + {bool update = true}); } diff --git a/lib/variance.dart b/lib/variance.dart index 66ec670..e69e14f 100644 --- a/lib/variance.dart +++ b/lib/variance.dart @@ -26,4 +26,5 @@ part 'src/common/contract.dart'; part 'src/common/factories.dart'; part 'src/common/mixins.dart'; part 'src/common/pack.dart'; +part 'src/4337/safe.dart'; part 'src/errors/wallet_errors.dart'; From 63bc9b172667cec99085aae31a013e5fca28d705 Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Sat, 30 Mar 2024 16:15:54 +0100 Subject: [PATCH 15/31] feat: redocument the sdk --- README.md | 171 ++++++++---------- lib/src/4337/chains.dart | 105 ++++++++++- lib/src/4337/factory.dart | 102 ++++++++++- lib/src/4337/paymaster.dart | 21 ++- lib/src/4337/providers.dart | 22 +++ lib/src/4337/safe.dart | 17 ++ lib/src/4337/userop.dart | 2 + lib/src/4337/wallet.dart | 66 ++++++- lib/src/abis/contract_abis.dart | 8 +- lib/src/common/contract.dart | 7 +- lib/src/common/factories.dart | 64 ++++++- lib/src/common/logger.dart | 41 +++++ lib/src/common/mixins.dart | 29 +++ lib/src/common/pack.dart | 25 +++ lib/src/interfaces/interfaces.dart | 5 +- ...c_provider.dart => json_rpc_provider.dart} | 0 lib/src/interfaces/paymaster.dart | 33 ++++ lib/src/interfaces/smart_wallet.dart | 49 +++-- lib/src/interfaces/smart_wallet_factory.dart | 99 ++++------ lib/src/interfaces/user_operations.dart | 18 ++ 20 files changed, 691 insertions(+), 193 deletions(-) rename lib/src/interfaces/{rpc_provider.dart => json_rpc_provider.dart} (100%) create mode 100644 lib/src/interfaces/paymaster.dart diff --git a/README.md b/README.md index f833e26..7cd7d2a 100644 --- a/README.md +++ b/README.md @@ -7,163 +7,150 @@ Variance is a Dart SDK designed to simplify interaction with Ethereum-based bloc - **ABI Encoding/Decoding:** Easily encode and decode ABI data for Ethereum smart contract and Entrypoint interactions. - **Transaction Handling:** Simplify the process of creating and sending UserOperations. - **Token Operations:** Work with ERC20 and ERC721 tokens, including transfer and approval functionalities. -- **Secure Storage:** Securely store and manage sensitive data such as private keys and credentials. -- **Web3 Functionality:** Interact with Ethereum nodes and bundlers using web3 functions like `eth_call`, `eth_sendTransaction`, `eth_sendUserOperation`, etc. -- **PassKeyPair and HDWalletSigner:** Manage smart accounts signers using Passkeys or Seed Phrases. +- **Web3 Functionality:** Interact with Ethereum nodes and bundlers using abstracted methods over, `eth_sendTransaction`, and `eth_sendUserOperation`. +- **SecP256r1 Signatures:** Sign transactions with SecP256r1 signatures. ## Getting Started ### Installation -```yml -// Add this line to your pubspec.yaml file - -dependencies: - variance_dart: ^0.0.4 -``` - -Then run: +open your terminal and run the following command: ```sh -flutter pub get +flutter pub get variance_dart +flutter pub get web3_signers ``` ### Usage ```dart // Import the package -import 'package:variance_dart/utils.dart'; +import 'package:web3_signers/web3_signers.dart'; import 'package:variance_dart/variance.dart'; // optionally -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:web3dart/web3dart.dart'; ``` -configure your chains: there are 2 ways to get the chain configuration. either manually or using the already supported configurations. +### Chain Configuration ```dart -Chain chain; - -// manually const String rpcUrl = 'http://localhost:8545'; const String bundlerUrl = 'http://localhost:3000/rpc'; +const Uint256 salt = const Uint256.zero; -chain = Chain( - jsonRpcUrl: rpcUrl, - bundlerUrl: bundlerUrl, - entrypoint: EntryPoint.v06, - accountFactory: Constants.accountFactory, - chainId: 1337, - explorer: ""); - -// using pre configured chain -chain = Chains.getChain(Network.localhost) +final Chain chain = Chains.getChain(Network.localhost) ..jsonRpcUrl = rpcUrl ..bundlerUrl = bundlerUrl; ``` -In order to create a smart wallet client you need to set up a signer, which will sign useroperation hashes to be verified onchain. -there are 3 available signers: +> There are 8 available networks: ethereum, polygon, optimism, base, arbitrumOne, linea, opBnB and scroll. 3 available testnets: sepolia, mumbai and baseTestent. You can also develop on localHost. -- passkeys -- hd wallet -- simple credential (privatekey) +Additionally, you can specify a different Entrypoint address. By default, the entrypoin v0.6 is used. -> Variance SDK can be used to create both EOA and Smart Wallets. the `HD wallet signer` itself is a fully featured EOA wallet that can be used to build any EOA wallet like metamask. it can also be used as an account signer for a smart wallet. +```dart +final EntryPointAddress entrypointAddress = EntryPointAddress.v07; +chain.entrypoint = entrypointAddress; +``` + +Also if wish to use paymasters with your smart wallet you can do so by specifying the endpoint of the paymaster. By default the paymaster is set to null. This would add a paymaster Plugin to the smart wallet. ```dart -// create smart wallet signer based of seed phrase -final HDWalletSigner hd = HDWalletSigner.createWallet(); -print("mnemonic: ${hd.exportMnemonic()}"); - -// create a smart wallet signer based on passkeys -// this operation requires biometrics verification from the user -final PassKeyPair pkp = - await PassKeySigner("myapp.xyz", "myapp", "https://myapp.xyz") - .register("", true); -print("pkp: ${pkp.toJson()}"); +final String paymasterUrl = 'https://pimlico.io/...'; +chain.paymasterUrl = paymasterUrl; ``` -Optionally the credentials returned from the signer instances can be securely saved on device android encrypted shared preferences or ios keychain using the `SecureStorageMiddleware`. +If you have additional context for the paymaster, you will be able to add it to the smart wallet. ```dart -// save a signer credential to device -await hd - .withSecureStorage(FlutterSecureStorage()) - .saveCredential(CredentialType.hdwallet); +wallet.plugin('paymaster').context = {'key': 'value'}; +``` -await pkp - .withSecureStorage(FlutterSecureStorage()) - .saveCredential(CredentialType.passkeypair); +### Signers -// load a credential from the device -final ss = SecureStorageMiddleware(secureStorage: FlutterSecureStorage()); -final hdInstance = - await HDWalletSigner.loadFromSecureStorage(storageMiddleware: ss); -print("pkp: ${hdInstance?.exportMnemonic()}"); +In order to create a smart wallet client you need to set up a signer, which will sign useroperation hashes to be verified onchain. Only signers available in the [web3signers](https://pub.dev/packages/web3_signers) package can be used. -// NOTE: interactions with securestorage can be authenticated when using `SecureStorageMiddleware` +> You have to use the correct signer for the type of account you want to create. -final ss = SecureStorageMiddleware(secureStorage: FlutterSecureStorage(), authMiddleware: AuthenticationMiddleware()); -// then used with `SecureStorageMiddleware` in the following way +1. `PrivateKeys` - use with simple accounts and safe accounts only +2. `Passkey` - use with p256 smart accounts and safe Passkey accounts only +3. `EOA Wallet (Seed Phrases)` - use with simple smart accounts and safe accounts only +4. `HardWare Signers (Secure Enclave/Keystore)` - use with p256 smart accounts only -ss.save("key", "value", options: SSAuthOperationOptions(requiresAuth: true, authReason: "reason")); -ss.read("key"); // default options are used i.e requiresAuth: false -ss.delete("key", options: SSAuthOperationOptions(requiresAuth: false)); // explicitly reject authentication +### Smart Wallet Factory + +The smart wallet factory handles the creation of smart wallet instances. Make sure you have created a signer from the previous step. + +```dart +final SmartWalletFactory smartWalletFactory = SmartWalletFactory(chain, signer); ``` -Interacting with the smart wallet: +#### To Create a Simple Account Smart Wallet ```dart -// create a smart wallet client -final walletClient = SmartWallet( - chain: chain, - signer: hd, - bundler: BundlerProvider(chain, RPCProvider(chain.bundlerUrl!)), -); +final Smartwallet wallet = await smartWalletFactory.createSimpleAccount(salt); +print("simple wallet address: ${wallet.address.hex}"); +``` + +#### To create a P256 Smart Account -// create a simple account based on hd -final SmartWallet simpleSmartAccount = - await walletClient.createSimpleAccount(salt); -print("simple account address: ${simpleSmartAccount.address}"); +```dart +final Smartwallet wallet = await smartWalletFactory.createP256Account(keypair, salt); +print("p256 wallet address: ${wallet.address.hex}"); +``` -// create a simple account based on pkp -final SmartWallet simplePkpAccount = - await walletClient.createSimplePasskeyAccount(pkp, salt); -print("simple pkp account address: ${simplePkpAccount.address}"); +Your keypair must be either be the `PassKeyPair` or `P256Credential` return when registering with your secp256r1 signer. +Additionally, you can pass a recovery address to the `createP256Account` method. +```dart +final Smartwallet wallet = await smartWalletFactory.createP256Account(keypair, salt, recoveryAddress); +print("p256 wallet address: ${wallet.address.hex}"); +``` + +#### To create a Safe Smart Account + +```dart +final Smartwallet wallet = await smartWalletFactory + .createSafeAccount(salt); +print("safe wallet address: ${wallet.address.hex}"); +``` + +Additionally, you can pass in multisig `owners` and the `threshold` to the `createSafeAccount` method. + +```dart +final Smartwallet wallet = await smartWalletFactory + .createSafeAccount(salt, [...extraOwners], threshold); +print("safe wallet address: ${wallet.address.hex}"); +``` + +> Safe SecP256r1 signers can not be used with this SDK yet. + +### Interacting with the Smart Wallet + +```dart // retrieve the balance of a smart wallet -final EtherAmount balance = await simpleSmartAccount.balance; +final EtherAmount balance = await wallet.balance; print("account balance: ${balance.getInWei}"); // retrive the account nonce -final Uint256 nonce = await simpleSmartAccount.nonce; +final Uint256 nonce = await wallet.nonce; print("account nonce: ${nonce.toInt()}"); // check if a smart wallet has been deployed -final bool deployed = await simpleSmartAccount.deployed; +final bool deployed = await wallet.deployed; print("account deployed: $deployed"); // get the init code of the smart wallet -final String initCode = simpleSmartAccount.initCode; +final String initCode = wallet.initCode; print("account init code: $initCode"); // perform a simple transaction (send ether to another account) // account must be prefunded with native token. paymaster is not yet implemented -await simpleSmartAccount.send( +await wallet.send( EthereumAddress.fromHex( "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"), // receive address - getConversion("0.7142"), // 0.7142 ether + EtherAmount.fromInt(EtherUnit.ether, 0.7142), // 0.7142 ether ); - - -// utility function to convert eth amount from string to wei -EtherAmount getConversion(String amount) { - final amtToDb = double.parse(amount); - return EtherAmount.fromBigInt( - EtherUnit.wei, BigInt.from(amtToDb * pow(10, 18))); -} ``` For detailed usage and examples, refer to the [documentation](https://docs.variance.space). Additional refer to the [demo](https://github.com/vaariance/variancedemo) for use in a flutter app. diff --git a/lib/src/4337/chains.dart b/lib/src/4337/chains.dart index e766807..594dae1 100644 --- a/lib/src/4337/chains.dart +++ b/lib/src/4337/chains.dart @@ -1,14 +1,57 @@ part of '../../variance.dart'; +/// Represents an Ethereum-based blockchain chain. class Chain { + /// The unique identifier of the chain. final int chainId; + + /// The URL of the block explorer for this chain. final String explorer; + + /// The address of the EntryPoint contract on this chain. final EntryPointAddress entrypoint; + + /// The address of the AccountFactory contract on this chain. EthereumAddress? accountFactory; + + /// The URL of the JSON-RPC endpoint for this chain. String? jsonRpcUrl; + + /// The URL of the bundler service for this chain. String? bundlerUrl; + + /// The URL of the paymaster service for this chain. + /// + /// This is an optional parameter and can be left null if the paymaster URL + /// is not known or needed. String? paymasterUrl; + /// Creates a new instance of the [Chain] class. + /// + /// [chainId] is the unique identifier of the chain. + /// [explorer] is the URL of the block explorer for this chain. + /// [entrypoint] is the address of the EntryPoint contract on this chain. + /// [accountFactory] is the address of the AccountFactory contract on this + /// chain. + /// [jsonRpcUrl] is the URL of the JSON-RPC endpoint for this chain. + /// [bundlerUrl] is the URL of the bundler service for this chain. + /// [paymasterUrl] is the optional URL of the paymaster service for this + /// chain. + /// + /// Example: + /// + /// ```dart + /// final chain = Chain( + /// chainId: 1, + /// explorer: 'https://etherscan.io', + /// entrypoint: EntryPointAddress('0x...'), + /// accountFactory: EthereumAddress('0x...'), + /// jsonRpcUrl: 'https://mainnet.infura.io/v3/...', + /// bundlerUrl: 'https://bundler.example.com', + /// paymasterUrl: 'https://paymaster.example.com', + /// ); + /// ``` + Chain( {required this.chainId, required this.explorer, @@ -98,6 +141,19 @@ class Chains { const Chains._(); + /// Returns the [Chain] instance for the given [Network]. + /// + /// [network] is the target network for which the [Chain] instance is required. + /// + /// This method retrieves the [Chain] instance from a predefined map of + /// networks and their corresponding chain configurations. + /// + /// Example: + /// + /// ```dart + /// final mainnetChain = Chain.getChain(Network.ethereum); + /// final sepoliaChain = Chain.getChain(Network.sepolia); + /// ``` static Chain getChain(Network network) { return chains[network]!; } @@ -142,27 +198,64 @@ enum Network { localhost } +/// Represents the address of an EntryPoint contract on the Ethereum blockchain. class EntryPointAddress { - static EntryPointAddress get v06 => - EntryPointAddress(0.6, Constants.entrypointv06); - static EntryPointAddress get v07 => - EntryPointAddress(0.7, Constants.entrypointv07); + /// Returns the EntryPoint address for version 0.6 of the EntryPoint contract. + static EntryPointAddress get v06 => EntryPointAddress( + 0.6, + Constants.entrypointv06, + ); + + /// Returns the EntryPoint address for version 0.7 of the EntryPoint contract. + static EntryPointAddress get v07 => EntryPointAddress( + 0.7, + Constants.entrypointv07, + ); + /// The version of the EntryPoint contract. final double version; + + /// The Ethereum address of the EntryPoint contract. final EthereumAddress address; + /// Creates a new instance of the [EntryPointAddress] class. + /// + /// [version] is the version of the EntryPoint contract. + /// [address] is the Ethereum address of the EntryPoint contract. const EntryPointAddress(this.version, this.address); } +/// Represents the address of the Safe4337Module contract on the Ethereum blockchain. class Safe4337ModuleAddress { - static Safe4337ModuleAddress v06 = - Safe4337ModuleAddress(0.6, Constants.safe4337ModuleAddress); + /// The address of the Safe4337Module contract for version 0.6. + static Safe4337ModuleAddress v06 = Safe4337ModuleAddress( + 0.6, + Constants.safe4337ModuleAddress, + ); + /// The version of the Safe4337Module contract. final double version; + + /// The Ethereum address of the Safe4337Module contract. final EthereumAddress address; + /// Creates a new instance of the [Safe4337ModuleAddress] class. + /// + /// [version] is the version of the Safe4337Module contract. + /// [address] is the Ethereum address of the Safe4337Module contract. const Safe4337ModuleAddress(this.version, this.address); + /// Creates a new instance of the [Safe4337ModuleAddress] class from a given version. + /// + /// [version] is the version of the Safe4337Module contract. + /// + /// If the provided version is not supported, an [Exception] will be thrown. + /// + /// Example: + /// + /// ```dart + /// final moduleAddress = Safe4337ModuleAddress.fromVersion(0.6); + /// ``` factory Safe4337ModuleAddress.fromVersion(double version) { switch (version) { case 0.6: diff --git a/lib/src/4337/factory.dart b/lib/src/4337/factory.dart index 24f4739..d89b62c 100644 --- a/lib/src/4337/factory.dart +++ b/lib/src/4337/factory.dart @@ -1,5 +1,7 @@ part of '../../variance.dart'; +/// A factory class for creating various types of Ethereum smart wallets. +/// {@inheritDoc SmartWalletFactoryBase} class SmartWalletFactory implements SmartWalletFactoryBase { final Chain _chain; final MSI _signer; @@ -8,6 +10,13 @@ class SmartWalletFactory implements SmartWalletFactoryBase { late final BundlerProvider _bundler; late final Contract _contract; + /// Creates a new instance of the [SmartWalletFactory] class. + /// + /// [_chain] is the Ethereum chain configuration. + /// [_signer] is the signer instance used for signing transactions. + /// + /// Throws an [InvalidFactoryAddress] exception if the provided chain does not + /// have a valid account factory address. SmartWalletFactory(this._chain, this._signer) : assert(_chain.accountFactory != null, InvalidFactoryAddress(_chain.accountFactory)), @@ -16,27 +25,32 @@ class SmartWalletFactory implements SmartWalletFactoryBase { _contract = Contract(_jsonRpc.rpc); } + /// A getter for the P256AccountFactory contract instance. _P256AccountFactory get _p256Accountfactory => _P256AccountFactory( address: _chain.accountFactory!, chainId: _chain.chainId, rpc: _jsonRpc.rpc); + /// A getter for the SafeProxyFactory contract instance. _SafeProxyFactory get _safeProxyFactory => _SafeProxyFactory( address: _chain.accountFactory!, chainId: _chain.chainId, rpc: _jsonRpc.rpc); + /// A getter for the SimpleAccountFactory contract instance. _SimpleAccountFactory get _simpleAccountfactory => _SimpleAccountFactory( address: _chain.accountFactory!, chainId: _chain.chainId, rpc: _jsonRpc.rpc); + /// A getter for the SafePlugin instance. _SafePlugin get _safePlugin => _SafePlugin( address: Safe4337ModuleAddress.fromVersion(_chain.entrypoint.version).address, chainId: _chain.chainId, client: _safeProxyFactory.client); + @override Future createP256Account(T keyPair, Uint256 salt, [EthereumAddress? recoveryAddress]) { switch (keyPair.runtimeType) { @@ -52,36 +66,61 @@ class SmartWalletFactory implements SmartWalletFactoryBase { } } + @override Future createSafeAccount(Uint256 salt, [List? owners, int? threshold]) async { final signer = EthereumAddress.fromHex(_signer.getAddress()); final ownerSet = owners != null ? {signer, ...owners} : [signer]; + + // Get the initializer data for the Safe account final initializer = _safeProxyFactory.getInitializer( ownerSet, threshold ?? 1, Safe4337ModuleAddress.fromVersion(_chain.entrypoint.version)); + + // Get the proxy creation code for the Safe account final creation = await _safeProxyFactory.proxyCreationCode(); + + // Predict the address of the Safe account final address = _safeProxyFactory.getPredictedSafe(initializer, salt, creation); + + // Encode the call data for the `createProxyWithNonce` function + // This function is used to create the Safe account with the given initializer data and salt final initCallData = _safeProxyFactory.self .function("createProxyWithNonce") .encodeCall([Constants.safeSingletonAddress, initializer, salt.value]); + + // Generate the initialization code by combining the account factory address and the encoded call data final initCode = _getInitCode(initCallData); + + // Create the SmartWallet instance for the Safe account return _createAccount(_chain, address, initCode) ..addPlugin<_SafePlugin>('safe', _safePlugin); } + @override Future createSimpleAccount(Uint256 salt, [int? index]) async { final signer = _signer.getAddress(index: index ?? 0); + + // Get the predicted address of the simple account final address = await _simpleAccountfactory.getAddress( EthereumAddress.fromHex(signer), salt.value); + + // Encode the call data for the `createAccount` function + // This function is used to create the simple account with the given signer address and salt final initCalldata = _simpleAccountfactory.self .function('createAccount') .encodeCall([signer, salt.value]); + + // Generate the initialization code by combining the account factory address and the encoded call data final initCode = _getInitCode(initCalldata); + + // Create the SmartWallet instance for the simple account return _createAccount(_chain, address, initCode); } + @override Future createVendorAccount( EthereumAddress address, Uint8List initCode, @@ -89,15 +128,53 @@ class SmartWalletFactory implements SmartWalletFactoryBase { return _createAccount(_chain, address, initCode); } + /// Creates a new [SmartWallet] instance with the provided chain, address, and initialization code. + /// + /// [chain] is the Ethereum chain configuration. + /// [address] is the Ethereum address of the account. + /// [initCalldata] is the initialization code for the account. + /// + /// The [SmartWallet] instance is created with various plugins added to it, including: + /// - [MSI] signer plugin + /// - [BundlerProviderBase] bundler plugin + /// - [JsonRPCProviderBase] JSON-RPC provider plugin + /// - [Contract] contract plugin + /// + /// Returns a [SmartWallet] instance representing the created account. SmartWallet _createAccount( Chain chain, EthereumAddress address, Uint8List initCalldata) { - return SmartWallet(chain, address, initCalldata) + final wallet = SmartWallet(chain, address, initCalldata) ..addPlugin('signer', _signer) ..addPlugin('bundler', _bundler) ..addPlugin('jsonRpc', _jsonRpc) ..addPlugin('contract', _contract); + + if (chain.paymasterUrl != null) { + wallet.addPlugin('paymaster', Paymaster(chain)); + } + + return wallet; } + /// Creates a new passkey account with the provided [PassKeyPair], salt, and optional recovery address. + /// + /// [pkp] is the [PassKeyPair] instance used to create the account. + /// [salt] is the salt value used in the account creation process. + /// [recoveryAddress] is an optional recovery address for the account. + /// + /// Returns a [Future] that resolves to a [SmartWallet] instance representing + /// the created passkey account. + /// + /// The process involves: + /// 1. Encoding the account creation data with the provided [PassKeyPair], [salt], and [recoveryAddress]. + /// - The encoded data includes the recovery address, credential hex, and public key components. + /// 2. Calling the `createP256Account` function on the `_p256AccountFactory` contract with the encoded data and [salt]. + /// - This function initiates the account creation process on the Ethereum blockchain. + /// 3. Getting the initialization code by combining the account factory address and the encoded call data. + /// - The initialization code is required to create the account on the client-side. + /// 4. Predicting the account address using the `_p256AccountFactory` contract's `getP256AccountAddress` function. + /// - This function predicts the address of the account based on the creation data and salt. + /// 5. Creating a new [SmartWallet] instance with the predicted address and initialization code. Future _createPasskeyAccount(PassKeyPair pkp, Uint256 salt, [EthereumAddress? recoveryAddress]) async { final Uint8List creation = abi.encode([ @@ -121,6 +198,22 @@ class SmartWalletFactory implements SmartWalletFactoryBase { return _createAccount(_chain, address, initCode); } + /// Creates a new secure enclave account with the provided [P256Credential], salt, and optional recovery address. + /// + /// [p256] is the [P256Credential] instance used to create the account. + /// [salt] is the salt value used in the account creation process. + /// [recoveryAddress] is an optional recovery address for the account. + /// + /// Returns a [Future] that resolves to a [SmartWallet] instance representing + /// the created secure enclave account. + /// + /// The process is similar to [_createPasskeyAccount] but with a different encoding for the account creation data. + /// 1. Encoding the account creation data with the provided [P256Credential], [salt], and [recoveryAddress]. + /// - The encoded data includes the recovery address, an empty bytes32 value, and the public key components. + /// 2. Calling the `createP256Account` function on the `_p256AccountFactory` contract with the encoded data and [salt]. + /// 3. Getting the initialization code by combining the account factory address and the encoded call data. + /// 4. Predicting the account address using the `_p256AccountFactory` contract's `getP256AccountAddress` function. + /// 5. Creating a new [SmartWallet] instance with the predicted address and initialization code. Future _createSecureEnclaveAccount( P256Credential p256, Uint256 salt, [EthereumAddress? recoveryAddress]) async { @@ -145,6 +238,13 @@ class SmartWalletFactory implements SmartWalletFactoryBase { return _createAccount(_chain, address, initCode); } + /// Returns the initialization code for the account by concatenating the account factory address with the provided initialization call data. + /// + /// [initCalldata] is the initialization call data for the account. + /// + /// The initialization code is required to create the account on the client-side. It is generated by combining the account factory address and the encoded call data for the account creation function. + /// + /// Returns a [Uint8List] containing the initialization code. Uint8List _getInitCode(Uint8List initCalldata) { List extended = _chain.accountFactory!.addressBytes.toList(); extended.addAll(initCalldata); diff --git a/lib/src/4337/paymaster.dart b/lib/src/4337/paymaster.dart index 388faf3..031de41 100644 --- a/lib/src/4337/paymaster.dart +++ b/lib/src/4337/paymaster.dart @@ -23,24 +23,40 @@ class PaymasterResponse { } } -class Paymaster { +/// Represents a Paymaster contract for sponsoring user operations. +class Paymaster implements PaymasterBase { final RPCBase _rpc; final Chain _chain; + + /// The context data for the Paymaster. + /// + /// This is an optional parameter and can be used to provide additional context + /// information to the Paymaster when sponsoring user operations. Map? _context; + @override set context(Map? context) { _context = context; } + /// Creates a new instance of the [Paymaster] class. + /// + /// [_chain] is the Ethereum chain configuration. + /// [_context] is an optional map containing the context data for the Paymaster. + /// + /// Throws an [InvalidPaymasterUrl] exception if the paymaster URL in the + /// provided chain configuration is not a valid URL. Paymaster(this._chain, [this._context]) : assert(isURL(_chain.paymasterUrl), InvalidPaymasterUrl(_chain.paymasterUrl)), _rpc = RPCBase(_chain.paymasterUrl!); + @override Future intercept(UserOperation operation) async { final paymasterResponse = await sponsorUserOperation( operation.toMap(), _chain.entrypoint, _context); + // Create a new UserOperation with the updated Paymaster data and gas limits return operation.copyWith( paymasterAndData: paymasterResponse.paymasterAndData, preVerificationGas: paymasterResponse.preVerificationGas, @@ -49,10 +65,13 @@ class Paymaster { ); } + @override Future sponsorUserOperation(Map userOp, EntryPointAddress entrypoint, Map? context) async { final response = await _rpc.send>( 'pm_sponsorUserOperation', [userOp, entrypoint.address.hex, context]); + + // Parse the response into a PaymasterResponse object return PaymasterResponse.fromMap(response); } } diff --git a/lib/src/4337/providers.dart b/lib/src/4337/providers.dart index d35a4d6..bc34f8c 100644 --- a/lib/src/4337/providers.dart +++ b/lib/src/4337/providers.dart @@ -1,8 +1,20 @@ part of '../../variance.dart'; +/// A class that implements the `BundlerProviderBase` interface and provides methods +/// for interacting with a bundler for sending and tracking user operations on +/// an Ethereum-like blockchain. class BundlerProvider implements BundlerProviderBase { + /// The remote procedure call (RPC) client used to communicate with the bundler. final RPCBase rpc; + /// Creates a new instance of the BundlerProvider class. + /// + /// [chain] is an object representing the blockchain chain configuration. + /// + /// The constructor checks if the bundler URL is a valid URL and initializes + /// the RPC client with the bundler URL. It also sends an RPC request to + /// retrieve the chain ID and verifies that it matches the expected chain ID. + /// If the chain IDs don't match, the _initialized flag is set to false. BundlerProvider(Chain chain) : assert(isURL(chain.bundlerUrl), InvalidBundlerUrl(chain.bundlerUrl)), rpc = RPCBase(chain.bundlerUrl!) { @@ -13,6 +25,7 @@ class BundlerProvider implements BundlerProviderBase { .then((value) => _initialized = value == true); } + /// A flag indicating whether the initialization process was successful. late final bool _initialized; @override @@ -61,9 +74,18 @@ class BundlerProvider implements BundlerProviderBase { } } +/// A class that implements the JsonRPCProviderBase interface and provides methods +/// for interacting with an Ethereum-like blockchain via the JSON-RPC protocol. class JsonRPCProvider implements JsonRPCProviderBase { + /// The remote procedure call (RPC) client used to communicate with the blockchain. final RPCBase rpc; + /// Creates a new instance of the JsonRPCProvider class. + /// + /// [chain] is an object representing the blockchain chain configuration. + /// + /// The constructor checks if the JSON-RPC URL is a valid URL and initializes + /// the RPC client with the JSON-RPC URL. JsonRPCProvider(Chain chain) : assert(isURL(chain.jsonRpcUrl), InvalidJsonRpcUrl(chain.jsonRpcUrl)), rpc = RPCBase(chain.jsonRpcUrl!); diff --git a/lib/src/4337/safe.dart b/lib/src/4337/safe.dart index 694e9f0..2be8860 100644 --- a/lib/src/4337/safe.dart +++ b/lib/src/4337/safe.dart @@ -1,12 +1,24 @@ part of '../../variance.dart'; +/// A class that extends the Safe4337Module and implements the Safe4337ModuleBase interface. +/// It provides functionality related to Safe accounts and user operations on an Ethereum-like blockchain. class _SafePlugin extends Safe4337Module implements Safe4337ModuleBase { + /// Creates a new instance of the _SafePlugin class. + /// + /// [address] is the address of the Safe 4337 module. + /// [chainId] is the ID of the blockchain chain. + /// [client] is the client used for interacting with the blockchain. _SafePlugin({ required super.address, super.chainId, required super.client, }); + /// Computes the hash of a Safe UserOperation. + /// + /// [op] is an object representing the user operation details. + /// + /// Returns a Future that resolves to the hash of the user operation as a Uint8List. Future getUserOperationHash(UserOperation op) async => getOperationHash([ op.sender, @@ -22,6 +34,11 @@ class _SafePlugin extends Safe4337Module implements Safe4337ModuleBase { _getEncodedSignature(op.signature) ]); + /// Encodes the signature of a user operation with a validity period. + /// + /// [signature] is the signature of the user operation. + /// + /// Returns a Uint8List representing the encoded signature with a validity period. Uint8List _getEncodedSignature(String signature) { final currentTimestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000; diff --git a/lib/src/4337/userop.dart b/lib/src/4337/userop.dart index 087d825..72fa654 100644 --- a/lib/src/4337/userop.dart +++ b/lib/src/4337/userop.dart @@ -1,5 +1,6 @@ part of '../../variance.dart'; +/// A class that implements the user operation struct defined in EIP4337. class UserOperation implements UserOperationBase { @override final EthereumAddress sender; @@ -230,6 +231,7 @@ class UserOperation implements UserOperationBase { ); } + @override void validate(bool deployed, [String? initCode]) { require( deployed diff --git a/lib/src/4337/wallet.dart b/lib/src/4337/wallet.dart index 57d5955..108def3 100644 --- a/lib/src/4337/wallet.dart +++ b/lib/src/4337/wallet.dart @@ -1,12 +1,44 @@ part of '../../variance.dart'; +/// A class that represents a Smart Wallet on an Ethereum-like blockchain. +/// +/// The [SmartWallet] class implements the [SmartWalletBase] interface and provides +/// various methods for interacting with the wallet, such as sending transactions, +/// estimating gas, and retrieving balances. It uses various plugins for different +/// functionalities, such as contract interaction, gas estimation, and signing operations. +/// +/// The class utilizes the `_PluginManager` and `_GasSettings` mixins for managing plugins +/// and gas settings, respectively. +/// +/// Example usage: +/// +/// ```dart +/// // Create a new instance of the SmartWallet +/// final wallet = SmartWallet(chain, walletAddress, initCode); +/// +/// // Get the wallet balance +/// final balance = await wallet.balance; +/// +/// // Send a transaction +/// final recipient = EthereumAddress.fromHex('0x...'); +/// final amount = EtherAmount.fromUnitAndValue(EtherUnit.ether, 1); +/// final response = await wallet.send(recipient, amount); +/// ``` class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { + /// The blockchain chain configuration. final Chain _chain; + /// The address of the Smart Wallet. final EthereumAddress _walletAddress; + /// The initialization code for deploying the Smart Wallet contract. Uint8List _initCode; + /// Creates a new instance of the [SmartWallet] class. + /// + /// [_chain] is an object representing the blockchain chain configuration. + /// [_walletAddress] is the address of the Smart Wallet. + /// [_initCode] is the initialization code for deploying the Smart Wallet contract. SmartWallet(this._chain, this._walletAddress, this._initCode); @override @@ -35,6 +67,10 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { @override String get dummySignature => plugin('signer').dummySignature; + /// Returns the estimated gas required for deploying the Smart Wallet contract. + /// + /// The gas estimation is performed by interacting with the 'jsonRpc' plugin + /// and estimating the gas for the initialization code. Future get _initCodeGas => plugin('jsonRpc') .estimateGas(_chain.entrypoint.address, initCode); @@ -100,10 +136,15 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { @override Future prepareUserOperation(UserOperation op, {bool update = true}) async { + // Update the user operation with the latest nonce and gas prices if needed if (update) op = await _updateUserOperation(op); + + // If the 'paymaster' plugin is enabled, intercept the user operation if (hasPlugin('paymaster')) { op = await plugin('paymaster').intercept(op); } + + // Validate the user operation op.validate(op.nonce > BigInt.zero, initCode); return op; @@ -112,24 +153,39 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { @override Future signUserOperation(UserOperation op, {int? index}) async { + // Calculate the operation hash. safe accounts uses EIP712 + // The hash is retrieved by calling the SafeModule with the operation and a dummy signature. final opHash = hasPlugin("safe") ? await plugin<_SafePlugin>("safe").getUserOperationHash(op) : op.hash(_chain); + + // Sign the operation hash using the 'signer' plugin Uint8List signature = await plugin('signer').personalSign(opHash, index: index); op.signature = hexlify(signature); return op; } + /// Returns the nonce for the Smart Wallet address. + /// + /// If the wallet is not deployed, returns 0. + /// Otherwise, retrieves the nonce by calling the 'getNonce' function on the entrypoint. + /// + /// If an error occurs during the nonce retrieval process, a [NonceError] exception is thrown. Future _getNonce() => isDeployed.then((deployed) => !deployed ? Future.value(Uint256.zero) : plugin("contract") - .call(_chain.entrypoint.address, ContractAbis.get('getNonce'), + .read(_chain.entrypoint.address, ContractAbis.get('getNonce'), "getNonce", params: [_walletAddress, BigInt.zero]) .then((value) => Uint256(value[0])) .catchError((e) => throw NonceError(e.toString(), _walletAddress))); + /// Updates the user operation with the latest nonce and gas prices. + /// + /// [op] is the user operation to update. + /// + /// Returns a [Future] that resolves to the updated [UserOperation] object. Future _updateUserOperation(UserOperation op) => Future.wait([ _getNonce(), @@ -142,6 +198,14 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { return _updateUserOperationGas(op, feePerGas: responses[1]); }); + /// Updates the gas information for the user operation. + /// + /// [op] is the user operation to update. + /// [feePerGas] is an optional map containing the gas prices. + /// + /// Returns a [Future] that resolves to the updated [UserOperation] object. + /// + /// If an error occurs during the gas estimation process, a [GasEstimationError] exception is thrown. Future _updateUserOperationGas(UserOperation op, {Map? feePerGas}) => plugin('bundler') diff --git a/lib/src/abis/contract_abis.dart b/lib/src/abis/contract_abis.dart index 7b54989..db9e08f 100644 --- a/lib/src/abis/contract_abis.dart +++ b/lib/src/abis/contract_abis.dart @@ -1,7 +1,13 @@ import 'package:web3dart/web3dart.dart'; +/// Contract ABIs +/// Getters for contract ABIs for onchain operations class ContractAbis { - static ContractAbi get(String name, {ContractAbi? abi}) { + /// Get contract ABI + /// - `name`: name of the contract + /// + /// Returns ABI of the contract. + static ContractAbi get(String name) { String abi; switch (name) { case 'ERC20_BalanceOf': diff --git a/lib/src/common/contract.dart b/lib/src/common/contract.dart index dd71097..4945e8e 100644 --- a/lib/src/common/contract.dart +++ b/lib/src/common/contract.dart @@ -2,6 +2,7 @@ part of '../../variance.dart'; /// A wrapper for interacting with deployed Ethereum contracts through [JsonRPCProvider]. class Contract { + /// The remote procedure call (RPC) client used to communicate with the bundler. final RPCBase rpc; Contract( @@ -22,7 +23,7 @@ class Contract { /// /// Example: /// ```dart - /// var result = await call( + /// var result = await read( /// EthereumAddress.fromHex('0x9876543210abcdef9876543210abcdef98765432'), /// myErc20ContractAbi, /// 'balanceOf', @@ -31,7 +32,7 @@ class Contract { /// ``` /// This method uses the an Ethereum jsonRPC to `staticcall` a function on the specified smart contract. /// **Note:** This method does not support contract calls with state changes. - Future> call( + Future> read( EthereumAddress contractAddress, ContractAbi abi, String methodName, {List? params, EthereumAddress? sender}) { final function = getContractFunction(methodName, contractAddress, abi); @@ -259,6 +260,7 @@ class Contract { /// - `to`: The [EthereumAddress] of the target recipient for the operation. /// - `amount`: The [EtherAmount] representing the amount to transfer, if applicable. /// - `innerCallData`: The [Uint8List] containing inner call data, if applicable. + /// - `isSafe`: An optional flag indicating whether the operation is a GnosisSafeOperation or not. defaults to false. /// /// Returns: /// A [Uint8List] containing the ABI-encoded data for the 'execute' function call. @@ -302,6 +304,7 @@ class Contract { /// - `recipients`: A list of [EthereumAddress] instances representing the recipients for each operation. /// - `amounts`: Optional list of [EtherAmount] instances representing the amounts to transfer for each operation. /// - `innerCalls`: Optional list of [Uint8List] instances containing inner call data for each operation. + /// - `isSafe`: An optional flag indicating whether the operation is a GnosisSafeOperation or not. defaults to false. /// /// Returns: /// A [Uint8List] containing the ABI-encoded data for the 'executeBatch' function call. diff --git a/lib/src/common/factories.dart b/lib/src/common/factories.dart index 34e140c..989fb6d 100644 --- a/lib/src/common/factories.dart +++ b/lib/src/common/factories.dart @@ -1,18 +1,46 @@ part of '../../variance.dart'; +/// A class that extends [P256AccountFactory] and implements [P256AccountFactoryBase]. +/// It creates an instance of [P256AccountFactory] with a custom [RPCBase] client. +/// Used to create instances of [SmartWallet] for P256 accounts. class _P256AccountFactory extends P256AccountFactory implements P256AccountFactoryBase { - _P256AccountFactory( - {required super.address, super.chainId, required RPCBase rpc}) - : super(client: Web3Client.custom(rpc)); + /// Creates a new instance of [_P256AccountFactory]. + /// + /// [address] is the address of the account factory. + /// [chainId] is the ID of the blockchain chain. + /// [rpc] is the [RPCBase] client used for communication with the blockchain. + _P256AccountFactory({ + required super.address, + super.chainId, + required RPCBase rpc, + }) : super(client: Web3Client.custom(rpc)); } +/// A class that extends [SafeProxyFactory] and implements [SafeProxyFactoryBase]. +/// It creates an instance of [SafeProxyFactory] with a custom [RPCBase] client. +/// Used to create instances of [SmartWallet] for Safe accounts. +/// Additionally, it provides methods for getting the initializer data and predicting the Safe address. class _SafeProxyFactory extends SafeProxyFactory implements SafeProxyFactoryBase { - _SafeProxyFactory( - {required super.address, super.chainId, required RPCBase rpc}) - : super(client: Web3Client.custom(rpc)); + /// Creates a new instance of [_SafeProxyFactory]. + /// + /// [address] is the address of the Safe proxy factory. + /// [chainId] is the ID of the blockchain chain. + /// [rpc] is the [RPCBase] client used for communication with the blockchain. + _SafeProxyFactory({ + required super.address, + super.chainId, + required RPCBase rpc, + }) : super(client: Web3Client.custom(rpc)); + /// Generates the initializer data for deploying a new Safe contract. + /// + /// [owners] is an iterable of owner addresses for the Safe. + /// [threshold] is the required number of confirmations for executing transactions. + /// [module] is the address of the Safe module to enable. + /// + /// Returns a [Uint8List] containing the encoded initializer data. Uint8List getInitializer(Iterable owners, int threshold, Safe4337ModuleAddress module) { return Contract.encodeFunctionCall( @@ -31,6 +59,14 @@ class _SafeProxyFactory extends SafeProxyFactory ]); } + /// Predicts the address of the Safe Smart Account based on the initializer data, salt, and creation code. + /// + /// [initializer] is the initializer data for deploying the Safe contract. + /// [salt] is the salt value used for address calculation. + /// [creationCode] is the creation code for deploying the Safe contract. + /// + /// Returns the predicted [EthereumAddress] of the Safe contract. + EthereumAddress getPredictedSafe( Uint8List initializer, Uint256 salt, Uint8List creationCode) { final deploymentData = Uint8List.fromList( @@ -55,9 +91,19 @@ class _SafeProxyFactory extends SafeProxyFactory } } +/// A class that extends [SimpleAccountFactory] and implements [SimpleAccountFactoryBase]. +/// It creates an instance of [SimpleAccountFactory] with a custom [RPCBase] client. +/// Used to create instances of [SmartWallet] for simple accounts. class _SimpleAccountFactory extends SimpleAccountFactory implements SimpleAccountFactoryBase { - _SimpleAccountFactory( - {required super.address, super.chainId, required RPCBase rpc}) - : super(client: Web3Client.custom(rpc)); + /// Creates a new instance of [_SimpleAccountFactory]. + /// + /// [address] is the address of the simple account factory. + /// [chainId] is the ID of the blockchain chain. + /// [rpc] is the [RPCBase] client used for communication with the blockchain. + _SimpleAccountFactory({ + required super.address, + super.chainId, + required RPCBase rpc, + }) : super(client: Web3Client.custom(rpc)); } diff --git a/lib/src/common/logger.dart b/lib/src/common/logger.dart index 0b49d2e..6f0d654 100644 --- a/lib/src/common/logger.dart +++ b/lib/src/common/logger.dart @@ -1,22 +1,46 @@ +/// A class that provides logging functionality with colored output for warnings and errors. class Logger { + /// The ANSI escape code for red color. static final _errorColor = '\x1B[31m'; + + /// The ANSI escape code for yellow color. static final _warningColor = '\x1B[33m'; + + /// The ANSI escape code to reset the color. static final _resetColor = '\x1B[0m'; + /// Logs a warning message. + /// + /// [message] is the warning message to be logged. static void warning(String message) { _logMessage('WARNING', _warningColor, message); } + /// Logs an error message. + /// + /// [message] is the error message to be logged. + /// [error] is an optional error object associated with the error message. + /// [stackTrace] is an optional stack trace associated with the error message. static void error(String message, [Object? error, StackTrace? stackTrace]) { _logError('ERROR', _errorColor, message, error, stackTrace); } + /// Logs a warning message if a condition is met. + /// + /// [condition] is the condition to check. + /// [message] is the warning message to be logged if the condition is true. static void conditionalWarning(bool condition, String message) { if (condition) { _logMessage('WARNING', _warningColor, message); } } + /// Logs an error message if a condition is met. + /// + /// [condition] is the condition to check. + /// [message] is the error message to be logged if the condition is true. + /// [error] is an optional error object associated with the error message. + /// [stackTrace] is an optional stack trace associated with the error message. static void conditionalError(bool condition, String message, [Object? error, StackTrace? stackTrace]) { if (condition) { @@ -24,10 +48,22 @@ class Logger { } } + /// Logs a message with the specified level and color. + /// + /// [level] is the log level (e.g., WARNING, ERROR). + /// [color] is the ANSI escape code for the color. + /// [message] is the message to be logged. static void _logMessage(String level, String color, String message) { _log(level, color, message); } + /// Logs an error message with additional error and stack trace information. + /// + /// [level] is the log level (e.g., WARNING, ERROR). + /// [color] is the ANSI escape code for the color. + /// [message] is the error message to be logged. + /// [error] is an optional error object associated with the error message. + /// [stackTrace] is an optional stack trace associated with the error message. static void _logError(String level, String color, String message, [Object? error, StackTrace? stackTrace]) { String errorMessage = '$message'; @@ -40,6 +76,11 @@ class Logger { _log(level, color, errorMessage); } + /// Logs a message with the specified level, color, and timestamp. + /// + /// [level] is the log level (e.g., WARNING, ERROR). + /// [color] is the ANSI escape code for the color. + /// [message] is the message to be logged. static void _log(String level, String color, String message) { final now = DateTime.now(); final formattedTime = '${now.year.toString().padLeft(4, '0')}-' diff --git a/lib/src/common/mixins.dart b/lib/src/common/mixins.dart index dba8b26..4609373 100644 --- a/lib/src/common/mixins.dart +++ b/lib/src/common/mixins.dart @@ -2,11 +2,29 @@ part of '../../variance.dart'; typedef Percent = double; +/// A class that represents gas settings for Ethereum transactions. class GasSettings { + /// The percentage by which the gas limits should be multiplied. + /// + /// This value should be between 0 and 100. Percent? gasMultiplierPercentage; + + /// The user-defined maximum fee per gas for the transaction. BigInt? userDefinedMaxFeePerGas; + + /// The user-defined maximum priority fee per gas for the transaction. BigInt? userDefinedMaxPriorityFeePerGas; + /// Creates a new instance of the [GasSettings] class. + /// + /// [gasMultiplierPercentage] is the percentage by which the gas limits should be multiplied. + /// Defaults to 0. + /// + /// [userDefinedMaxFeePerGas] is the user-defined maximum fee per gas for the transaction. + /// + /// [userDefinedMaxPriorityFeePerGas] is the user-defined maximum priority fee per gas for the transaction. + /// + /// An assertion is made to ensure that [gasMultiplierPercentage] is between 0 and 100. GasSettings({ this.gasMultiplierPercentage = 0, this.userDefinedMaxFeePerGas, @@ -15,11 +33,21 @@ class GasSettings { RangeOutOfBounds("Wrong Gas multiplier percentage", 0, 100)); } +/// A mixin that provides methods for managing gas settings for user operations. mixin _GasSettings { + /// The gas settings for user operations. GasSettings _gasParams = GasSettings(); + /// Sets the gas settings for user operations. + /// + /// [gasParams] is an instance of the [GasSettings] class containing the gas settings. set setGasParams(GasSettings gasParams) => _gasParams = gasParams; + /// Applies the gas settings to a user operation. + /// + /// [op] is the user operation to which the gas settings should be applied. + /// + /// Returns a new [UserOperation] object with the updated gas settings. UserOperation multiply(UserOperation op) { final multiplier = BigInt.from(_gasParams.gasMultiplierPercentage! / 100 + 1); @@ -33,6 +61,7 @@ mixin _GasSettings { } } +/// Used to manage the plugins used in the [Smartwallet] instance mixin _PluginManager { final Map _plugins = {}; diff --git a/lib/src/common/pack.dart b/lib/src/common/pack.dart index e69099c..d3c551b 100644 --- a/lib/src/common/pack.dart +++ b/lib/src/common/pack.dart @@ -42,6 +42,31 @@ List unpackUints(Uint8List bytes) { return [value >> 128, value & mask]; } +/// Packs a [UserOperation] into a PackedUserOperation map for EntryPoint v0.7 and above. +/// +/// Parameters: +/// - [userOp]: The [UserOperation] to pack. +/// +/// Returns a [Map] containing the packed user operation. +/// +/// Example: +/// ```dart +/// final userOp = UserOperation( +/// sender: EthereumAddress.fromHex('0x1234567890123456789012345678901234567890'), +/// nonce: BigInt.from(1), +/// initCode: Uint8List(0), +/// callData: Uint8List(0), +/// callGasLimit: BigInt.from(2), +/// verificationGasLimit: BigInt.from(3), +/// preVerificationGas: BigInt.from(4), +/// maxFeePerGas: BigInt.from(5), +/// maxPriorityFeePerGas: BigInt.from(6), +/// signature: '0x1234567890123456789012345678901234567890', +/// paymasterAndData: Uint8List(0), +/// ); +/// final packedUserOp = packUserOperation(userOp); +/// print(packedUserOp); +/// ``` Map packUserOperation(UserOperation userOp) { return { 'sender': userOp.sender.hex, diff --git a/lib/src/interfaces/interfaces.dart b/lib/src/interfaces/interfaces.dart index a110457..bbb15e2 100644 --- a/lib/src/interfaces/interfaces.dart +++ b/lib/src/interfaces/interfaces.dart @@ -8,6 +8,8 @@ import '../../variance.dart' Chain, EntryPointAddress, InvalidBundlerMethod, + PaymasterResponse, + SmartWallet, UserOperation, UserOperationByHash, UserOperationGas, @@ -17,8 +19,9 @@ import '../../variance.dart' part 'account_factories.dart'; part 'bundler_provider.dart'; -part 'rpc_provider.dart'; +part 'json_rpc_provider.dart'; part 'smart_wallet_factory.dart'; part 'smart_wallet.dart'; part 'safe_module.dart'; part 'user_operations.dart'; +part 'paymaster.dart'; diff --git a/lib/src/interfaces/rpc_provider.dart b/lib/src/interfaces/json_rpc_provider.dart similarity index 100% rename from lib/src/interfaces/rpc_provider.dart rename to lib/src/interfaces/json_rpc_provider.dart diff --git a/lib/src/interfaces/paymaster.dart b/lib/src/interfaces/paymaster.dart new file mode 100644 index 0000000..edf6341 --- /dev/null +++ b/lib/src/interfaces/paymaster.dart @@ -0,0 +1,33 @@ +part of 'interfaces.dart'; + +abstract class PaymasterBase { + /// Sets the context data for the Paymaster. + /// + /// [context] is a map containing the context data to be set. + set context(Map? context); + + /// Intercepts a [UserOperation] and sponsors it with the Paymaster. + /// + /// [operation] is the [UserOperation] to be sponsored. + /// + /// Returns a [Future] that resolves to the sponsored [UserOperation]. + /// + /// This method calls the `sponsorUserOperation` method to get the Paymaster + /// response, and then creates a new [UserOperation] with the updated + /// Paymaster data and gas limits. + Future intercept(UserOperation operation); + + /// Sponsors a user operation with the Paymaster. + /// + /// [userOp] is a map containing the user operation data. + /// [entrypoint] is the address of the EntryPoint contract. + /// [context] is an optional map containing the context data for the Paymaster. + /// + /// Returns a [Future] that resolves to a [PaymasterResponse] containing the + /// Paymaster data and gas limits for the sponsored user operation. + /// + /// This method calls the `pm_sponsorUserOperation` RPC method on the Paymaster + /// contract to sponsor the user operation. + Future sponsorUserOperation(Map userOp, + EntryPointAddress entrypoint, Map? context); +} diff --git a/lib/src/interfaces/smart_wallet.dart b/lib/src/interfaces/smart_wallet.dart index a68cc6d..ef816ba 100644 --- a/lib/src/interfaces/smart_wallet.dart +++ b/lib/src/interfaces/smart_wallet.dart @@ -7,30 +7,38 @@ part of 'interfaces.dart'; /// creating different implementations of Smart Wallets while adhering to a /// common interface. abstract class SmartWalletBase { - /// The Ethereum address associated with the Smart Wallet. + /// The Ethereum address of the Smart Wallet. EthereumAddress? get address; - /// Retrieves the balance of the Smart Wallet. + /// Returns the balance of the Smart Wallet. + /// + /// The balance is retrieved by interacting with the 'contract' plugin. Future get balance; - /// Checks if the Smart Wallet has been deployed on the blockchain. - Future get isDeployed; + /// Retrieves the dummy signature required for gas estimation from the Smart Wallet. + String get dummySignature; - /// Retrieves the init code of the Smart Wallet. + /// Returns the initialization code for deploying the Smart Wallet contract. String? get initCode; - /// Retrieves the gas required to deploy the Smart Wallet. + /// Returns the estimated gas required for deploying the Smart Wallet contract. + /// + /// The gas estimation is performed by interacting with the 'jsonRpc' plugin. Future get initCodeGas; - /// Retrieves the nonce of the Smart Wallet. + /// Checks if the Smart Wallet is deployed on the blockchain. + /// + /// The deployment status is checked by interacting with the 'contract' plugin. + Future get isDeployed; + + /// Returns the nonce for the Smart Wallet from the entrypoint. + /// + /// The nonce is retrieved by calling the `_getNonce` method. Future get nonce; - /// Converts the Smart Wallet address to its hexadecimal representation. + /// Returns the hexadecimal representation of the Smart Wallet address in EIP-55 format. String? get toHex; - /// Retrieves the dummy signature required for gas estimation from the Smart Wallet. - String get dummySignature; - /// Builds a [UserOperation] instance with the specified parameters. /// /// Parameters: @@ -52,7 +60,10 @@ abstract class SmartWalletBase { BigInt? customNonce, }); - /// Sets the account initialization calldata for a [SmartWalletBase] in a potentially unsafe manner. + /// Sets the initialization code for deploying the Smart Wallet contract. + /// + /// This method is marked as `@Deprecated` and should not be used in production code. + /// It is recommended to set the initialization code during the construction of the [SmartWallet] instance. /// /// **Warning:** /// This method allows setting the initialization calldata directly, which may lead to unexpected behavior @@ -68,6 +79,17 @@ abstract class SmartWalletBase { @Deprecated("Not recommended to modify the initcode") void dangerouslySetInitCode(Uint8List code); + /// Prepares a user operation by updating it with the latest nonce and gas prices, + /// intercepting it with a paymaster (if enabled), and validating it. + /// + /// [op] is the user operation to prepare. + /// [update] is a flag indicating whether to update the user operation with the + /// latest nonce and gas prices. Defaults to `true`. + /// + /// Returns a [Future] that resolves to the prepared [UserOperation] object. + Future prepareUserOperation(UserOperation op, + {bool update = true}); + /// Asynchronously transfers native Token (ETH) to the specified recipient with the given amount. /// /// Parameters: @@ -196,7 +218,4 @@ abstract class SmartWalletBase { /// var signedOperation = await signUserOperation(myUserOperation, index: 1); // signer 1 /// ``` Future signUserOperation(UserOperation op, {int? index}); - - Future prepareUserOperation(UserOperation op, - {bool update = true}); } diff --git a/lib/src/interfaces/smart_wallet_factory.dart b/lib/src/interfaces/smart_wallet_factory.dart index d036dda..805ed89 100644 --- a/lib/src/interfaces/smart_wallet_factory.dart +++ b/lib/src/interfaces/smart_wallet_factory.dart @@ -1,79 +1,50 @@ part of 'interfaces.dart'; -abstract class SmartWalletFactoryBase {} - -abstract class SmartWalletFactoryOld { - /// Asynchronously creates a simple Ethereum smart account using the provided salt value. - /// Uses counterfactactual deployment to create the account and [isDeployed] should be used to check deployment status. - /// An `initCode` will be attached on the first transaction. +abstract class SmartWalletFactoryBase { + /// Creates a new P256 account using the provided key pair and salt. /// - /// Parameters: - /// - `salt`: A [Uint256] representing the salt value for account creation. - /// - `index`: Optional parameter specifying the index for selecting a signer. Defaults to `null`. + /// [keyPair] is the key pair used to create the account. It can be either a + /// [PassKeyPair] or a [P256Credential] instance. + /// [salt] is the salt value used in the account creation process. + /// [recoveryAddress] is an optional recovery address for the account. /// - /// Returns: - /// A [Future] that completes with the created [SmartWallet] instance. + /// Returns a [Future] that resolves to a [SmartWallet] instance representing + /// the created account. /// - /// Example: - /// ```dart - /// var smartWallet = await createSimpleAccount(Uint256.zero, index: 1); - /// ``` - /// This method generates initialization calldata using the 'createAccount' method and the provided signer and salt. - /// It then retrieves the Ethereum address for the simple account and sets it to the wallet instance. - Future createSimpleAccount(Uint256 salt, {int? index}); + /// Throws an [ArgumentError] if the provided [keyPair] is not a + /// [PassKeyPair] or [P256Credential] instance. + Future createP256Account(T keyPair, Uint256 salt, + [EthereumAddress? recoveryAddress]); - /// Asynchronously creates a simple Ethereum smart account using a passkey pair and the provided salt value. - /// - /// Parameters: - /// - `pkp`: A [PassKeyPair] representing the passkey pair for account creation. - /// - `salt`: A [Uint256] representing the salt value for account creation. + /// Creates a new Safe account with the provided salt and optional owners and threshold. /// - /// Returns: - /// A [Future] that completes with the created [SmartWallet] instance. + /// [salt] is the salt value used in the account creation process. + /// [owners] is an optional list of owner addresses for the Safe account. + /// [threshold] is an optional threshold value for the Safe account. /// - /// Example: - /// ```dart - /// var smartWallet = await createSimplePasskeyAccount(myPassKeyPair, Uint256.zero); - /// ``` - /// This method generates initialization calldata using the 'createPasskeyAccount' method and the provided - /// passkey pair and salt. The passkey pair includes the credential and public key values. - Future createLightP256Account(PassKeyPair pkp, Uint256 salt); + /// Returns a [Future] that resolves to a [SmartWallet] instance representing + /// the created Safe account. - /// Asynchronously retrieves the Ethereum address for a simple account created with the specified signer and salt. - /// - /// Parameters: - /// - `signer`: The [EthereumAddress] of the signer associated with the account. - /// - `salt`: A [Uint256] representing the salt value used in the account creation. + Future createSafeAccount(Uint256 salt, + [List? owners, int? threshold]); + + /// Creates a new simple account with the provided salt and optional index. /// - /// Returns: - /// A [Future] that completes with the Ethereum address of the simple account. + /// [salt] is the salt value used in the account creation process. + /// [index] is an optional index value for the signer address. /// - /// Example: - /// ```dart - /// var address = await getSimpleAccountAddress( - /// EthereumAddress.fromHex('0x1234567890abcdef1234567890abcdef12345678'), - /// Uint256.zero, - /// ); - /// ``` - Future getSimpleAccountAddress( - EthereumAddress signer, Uint256 salt); + /// Returns a [Future] that resolves to a [SmartWallet] instance representing + /// the created simple account. + Future createSimpleAccount(Uint256 salt, [int? index]); - /// Asynchronously retrieves the Ethereum address for a simple account created with the specified passkey pair and salt. - /// - /// Parameters: - /// - `pkp`: The [PassKeyPair] used for creating the account. - /// - `salt`: A [Uint256] representing the salt value used in the account creation. + /// Creates a new vendor account with the provided address and initialization code. /// - /// Returns: - /// A [Future] that completes with the Ethereum address of the simple account. + /// [address] is the Ethereum address of the vendor account. + /// [initCode] is the initialization code for the vendor account. /// - /// Example: - /// ```dart - /// var address = await getSimplePassKeyAccountAddress( - /// myPassKeyPair, - /// Uint256.zero, - /// ); - /// ``` - Future getSimplePassKeyAccountAddress( - PassKeyPair pkp, Uint256 salt); + /// Returns a [SmartWallet] instance representing the created vendor account. + Future createVendorAccount( + EthereumAddress address, + Uint8List initCode, + ); } diff --git a/lib/src/interfaces/user_operations.dart b/lib/src/interfaces/user_operations.dart index e6985b4..baaa939 100644 --- a/lib/src/interfaces/user_operations.dart +++ b/lib/src/interfaces/user_operations.dart @@ -5,26 +5,37 @@ part of 'interfaces.dart'; /// Implementations of this class are expected to provide functionality for creating, /// updating, and hashing user operations. abstract class UserOperationBase { + /// Address of the smart wallet. EthereumAddress get sender; + /// Nonce of the Smart Account. BigInt get nonce; + /// Initialization code for the Smart Account. Uint8List get initCode; + /// Call data for execution in a user operation. Uint8List get callData; + /// Maximum amount of gas that can be used for executing a user operation calldata. BigInt get callGasLimit; + /// Maximum amount of gas that can be used for executing a user operation signature verification. BigInt get verificationGasLimit; + /// Gas for executing a user operation pre-verification. BigInt get preVerificationGas; + /// Maximum fee per gas for the contract call. BigInt get maxFeePerGas; + /// EIP1559 priority fee per gas for the contract call. BigInt get maxPriorityFeePerGas; + /// Signature of the user operation. String get signature; + /// Details of the paymaster and data for the gas sponsorship. Uint8List get paymasterAndData; /// Hashes the user operation for the given chain. @@ -62,4 +73,11 @@ abstract class UserOperationBase { /// ``` UserOperation updateOpGas( UserOperationGas? opGas, Map? feePerGas); + + /// Validates the user operation fields for accuracy + /// + /// Parameters: + /// - `deployed`: Whether the user operation sender is deployed or not + /// - `initCode`: (optional) The initialization code of the user operation + void validate(bool deployed, [String? initCode]); } From dc9573f1cd824d2b35ffdfc720b84dc801239e02 Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Sat, 30 Mar 2024 16:44:33 +0100 Subject: [PATCH 16/31] feat: merge example demo with sdk --- example/.gitignore | 43 + example/.metadata | 30 + example/README.md | 16 + example/analysis_options.yaml | 28 + example/android/.gitignore | 13 + example/android/app/build.gradle | 67 ++ .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 33 + .../com/example/variancedemo/MainActivity.kt | 6 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + example/android/build.gradle | 30 + example/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + example/android/settings.gradle | 29 + example/ios/.gitignore | 34 + example/ios/Flutter/AppFrameworkInfo.plist | 26 + example/ios/Flutter/Debug.xcconfig | 2 + example/ios/Flutter/Release.xcconfig | 2 + example/ios/Podfile | 44 + example/ios/Runner.xcodeproj/project.pbxproj | 614 ++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 98 +++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + example/ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 +++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 295 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 450 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 282 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 462 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 704 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 586 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 1674 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 762 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 1226 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 1418 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 + example/ios/Runner/Base.lproj/Main.storyboard | 26 + example/ios/Runner/Info.plist | 49 ++ example/ios/Runner/Runner-Bridging-Header.h | 1 + example/ios/RunnerTests/RunnerTests.swift | 12 + example/lib/main.dart | 137 +--- .../lib/providers/send_crypto_provider.dart | 6 + example/lib/providers/wallet_provider.dart | 65 ++ example/lib/screens/create_account.dart | 134 +++ example/lib/screens/home_screen.dart | 392 +++++++++ example/lib/utils/widgets.dart | 8 + example/lib/variance_colors.dart | 8 + example/pubspec.lock | 761 ++++++++++++++++++ example/pubspec.yaml | 39 + example/pubspec.yml | 16 - example/test/widget_test.dart | 30 + 74 files changed, 3010 insertions(+), 117 deletions(-) create mode 100644 example/.gitignore create mode 100644 example/.metadata create mode 100644 example/README.md create mode 100644 example/analysis_options.yaml create mode 100644 example/android/.gitignore create mode 100644 example/android/app/build.gradle create mode 100644 example/android/app/src/debug/AndroidManifest.xml create mode 100644 example/android/app/src/main/AndroidManifest.xml create mode 100644 example/android/app/src/main/kotlin/com/example/variancedemo/MainActivity.kt create mode 100644 example/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 example/android/app/src/main/res/drawable/launch_background.xml create mode 100644 example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/values-night/styles.xml create mode 100644 example/android/app/src/main/res/values/styles.xml create mode 100644 example/android/app/src/profile/AndroidManifest.xml create mode 100644 example/android/build.gradle create mode 100644 example/android/gradle.properties create mode 100644 example/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 example/android/settings.gradle create mode 100644 example/ios/.gitignore create mode 100644 example/ios/Flutter/AppFrameworkInfo.plist create mode 100644 example/ios/Flutter/Debug.xcconfig create mode 100644 example/ios/Flutter/Release.xcconfig create mode 100644 example/ios/Podfile create mode 100644 example/ios/Runner.xcodeproj/project.pbxproj create mode 100644 example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 example/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 example/ios/Runner/AppDelegate.swift create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 example/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 example/ios/Runner/Base.lproj/Main.storyboard create mode 100644 example/ios/Runner/Info.plist create mode 100644 example/ios/Runner/Runner-Bridging-Header.h create mode 100644 example/ios/RunnerTests/RunnerTests.swift create mode 100644 example/lib/providers/send_crypto_provider.dart create mode 100644 example/lib/providers/wallet_provider.dart create mode 100644 example/lib/screens/create_account.dart create mode 100644 example/lib/screens/home_screen.dart create mode 100644 example/lib/utils/widgets.dart create mode 100644 example/lib/variance_colors.dart create mode 100644 example/pubspec.lock create mode 100644 example/pubspec.yaml delete mode 100644 example/pubspec.yml create mode 100644 example/test/widget_test.dart diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..29a3a50 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,43 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/example/.metadata b/example/.metadata new file mode 100644 index 0000000..7d0a304 --- /dev/null +++ b/example/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "41456452f29d64e8deb623a3c927524bcf9f111b" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b + base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b + - platform: android + create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b + base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..ae69948 --- /dev/null +++ b/example/README.md @@ -0,0 +1,16 @@ +# variancedemo + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/example/android/.gitignore b/example/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/example/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle new file mode 100644 index 0000000..ce57a0d --- /dev/null +++ b/example/android/app/build.gradle @@ -0,0 +1,67 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +android { + namespace "com.example.variancedemo" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.variancedemo" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies {} diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..349159e --- /dev/null +++ b/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/example/android/app/src/main/kotlin/com/example/variancedemo/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/variancedemo/MainActivity.kt new file mode 100644 index 0000000..96a3efb --- /dev/null +++ b/example/android/app/src/main/kotlin/com/example/variancedemo/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.variancedemo + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/drawable/launch_background.xml b/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/build.gradle b/example/android/build.gradle new file mode 100644 index 0000000..e83fb5d --- /dev/null +++ b/example/android/build.gradle @@ -0,0 +1,30 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/example/android/gradle.properties b/example/android/gradle.properties new file mode 100644 index 0000000..598d13f --- /dev/null +++ b/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G +android.useAndroidX=true +android.enableJetifier=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle new file mode 100644 index 0000000..7cd7128 --- /dev/null +++ b/example/android/settings.gradle @@ -0,0 +1,29 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + + plugins { + id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false +} + +include ":app" diff --git a/example/ios/.gitignore b/example/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/example/ios/Podfile b/example/ios/Podfile new file mode 100644 index 0000000..d97f17e --- /dev/null +++ b/example/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..034a877 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,614 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807E294A63A400263BE5 /* Frameworks */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.variancedemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.variancedemo.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.variancedemo.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.variancedemo.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.variancedemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.variancedemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..87131a0 --- /dev/null +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..797d452e458972bab9d994556c8305db4c827017 GIT binary patch literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed2d933e1120817fe9182483a228007b18ab6ae GIT binary patch literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd7b0099ca80c806f8fe495613e8d6c69460d76 GIT binary patch literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe730945a01f64a61e2235dbe3f45b08f7729182 GIT binary patch literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..502f463a9bc882b461c96aadf492d1729e49e725 GIT binary patch literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec303439225b78712f49115768196d8d76f6790 GIT binary patch literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fea27c705180eb716271f41b582e76dcbd90 GIT binary patch literal 1674 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 literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8 GIT binary patch literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner/Base.lproj/Main.storyboard b/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist new file mode 100644 index 0000000..480e401 --- /dev/null +++ b/example/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Variancedemo + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + variancedemo + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/example/ios/Runner/Runner-Bridging-Header.h b/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/example/ios/RunnerTests/RunnerTests.swift b/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/example/lib/main.dart b/example/lib/main.dart index bd7481c..0e094ee 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,104 +1,39 @@ -// ignore_for_file: public_member_api_docs - -import 'dart:math'; - -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:variance_dart/utils.dart'; -import 'package:variance_dart/variance.dart'; -import 'package:web3dart/web3dart.dart'; - -const String rpcUrl = 'http://localhost:8545'; -const String bundlerUrl = 'http://localhost:3000/rpc'; - -Future main() async { - final Uint256 salt = Uint256.zero; - - // configure your chain - final Chain chain = Chain( - jsonRpcUrl: rpcUrl, - bundlerUrl: bundlerUrl, - entrypoint: EntryPointAddress.v06, - accountFactory: - EthereumAddress.fromHex("0xCCaE5F64307D86346B83E55e7865f77906F9c7b4"), - chainId: 1337, - explorer: ""); - - // create smart wallet signer based of seed phrase - final HDWalletSigner hd = HDWalletSigner.createWallet(); - print("mnemonic: ${hd.exportMnemonic()}"); - - // create a smart wallet signer based on passkeys - // this operation requires biometrics verification from the user - final PassKeyPair pkp = - await PassKeySigner("myapp.xyz", "myapp", "https://myapp.xyz") - .register("", true); - print("pkp: ${pkp.toJson()}"); - - // save a signer credential to device - await hd - .withSecureStorage(FlutterSecureStorage()) - .saveCredential(CredentialType.hdwallet); - - // load a credential from the device - final ss = SecureStorageMiddleware(secureStorage: FlutterSecureStorage()); - final hdInstance = - await HDWalletSigner.loadFromSecureStorage(storageMiddleware: ss); - print("pkp: ${hdInstance?.exportMnemonic()}"); - - // NOTE: interactions with securestorage can be authenticated when using `SecureStorageMiddleware` - // - // final ss = SecureStorageMiddleware(secureStorage: FlutterSecureStorage(), authMiddleware: AuthenticationMiddleware()); - // then used with `SecureStorageMiddleware` in the following way - // - // ss.save("key", "value", options: SSAuthOperationOptions(requiresAuth: true, authReason: "reason")) - // ss.read("key") // default options are used i.e requiresAuth: false - // ss.delete("key", options: SSAuthOperationOptions(requiresAuth: false)) // explicitly reject authentication - //; - - // create a smart wallet client - final walletClient = SmartWallet( - chain: chain, - signer: hd, - bundler: BundlerProvider(chain, JsonRPCProvider(chain.bundlerUrl!)), - ); - - // create a simple account based on hd - final SmartWallet simpleSmartAccount = - await walletClient.createSimpleAccount(salt); - print("simple account address: ${simpleSmartAccount.address}"); - - // create a simple account based on pkp - final SmartWallet simplePkpAccount = - await walletClient.createSimplePasskeyAccount(pkp, salt); - print("simple pkp account address: ${simplePkpAccount.address}"); - - // retrieve the balance of a smart wallet - final EtherAmount balance = await simpleSmartAccount.balance; - print("account balance: ${balance.getInWei}"); - - // retrive the account nonce - final Uint256 nonce = await simpleSmartAccount.nonce; - print("account nonce: ${nonce.toInt()}"); - - // check if a smart wallet has been deployed - final bool deployed = await simpleSmartAccount.isDeployed; - print("account deployed: $deployed"); - - // get the init code of the smart wallet - final String initCode = simpleSmartAccount.initCode; - print("account init code: $initCode"); - - // perform a simple transaction (send ether to another account) - // account must be prefunded with native token. paymaster is not yet implemented - await simpleSmartAccount.send( - EthereumAddress.fromHex( - "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"), // receive address - getConversion("0.7142"), // 0.7142 ether - ); +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:provider/provider.dart'; +import 'package:variancedemo/providers/wallet_provider.dart'; +import 'package:variancedemo/screens/create_account.dart'; +import 'package:variancedemo/screens/home_screen.dart'; + +final globalScaffoldMessengerKey = GlobalKey(); + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + runApp(MultiProvider(providers: [ + ChangeNotifierProvider(create: (_) => WalletProvider()), + ], child: const MyApp())); } -EtherAmount getConversion(String amount) { - final amtToDb = double.parse(amount); - return EtherAmount.fromBigInt( - EtherUnit.wei, BigInt.from(amtToDb * pow(10, 18))); +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return ScreenUtilInit( + designSize: const Size(375, 812), + child: MaterialApp( + title: 'Variance Dart', + routes: { + '/': (context) => const CreateAccountScreen(), + '/home': (context) => const WalletHome(), + }, + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xffE1FF01)), + textTheme: GoogleFonts.poppinsTextTheme(), + ), + debugShowCheckedModeBanner: false, + ), + ); + } } diff --git a/example/lib/providers/send_crypto_provider.dart b/example/lib/providers/send_crypto_provider.dart new file mode 100644 index 0000000..7d30948 --- /dev/null +++ b/example/lib/providers/send_crypto_provider.dart @@ -0,0 +1,6 @@ +import 'package:flutter/material.dart'; + +/// + class SendCryptoProvider extends ChangeNotifier { + + } \ No newline at end of file diff --git a/example/lib/providers/wallet_provider.dart b/example/lib/providers/wallet_provider.dart new file mode 100644 index 0000000..bf91cba --- /dev/null +++ b/example/lib/providers/wallet_provider.dart @@ -0,0 +1,65 @@ +import 'dart:convert'; +import 'dart:developer'; +import 'package:flutter/foundation.dart'; +import 'package:web3_signers/web3_signers.dart'; +import 'package:variance_dart/variance.dart'; +import 'package:web3dart/credentials.dart'; +import 'package:web3dart/web3dart.dart' as w3d; +import 'package:web3dart/crypto.dart' as w3d; + +class WalletProvider extends ChangeNotifier { + final Chain _chain; + + PassKeyPair? _keyPair; + + late SmartWallet _wallet; + + SmartWallet get wallet => _wallet; + + WalletProvider() + : _chain = Chains.getChain(Network.baseTestent) + ..bundlerUrl = + "https://base-sepolia.g.alchemy.com/v2/RWbMhXe00ZY-SjGQF72kyCVQJ_nQopba" + ..jsonRpcUrl = + "https://base-sepolia.g.alchemy.com/v2/RWbMhXe00ZY-SjGQF72kyCVQJ_nQopba"; + + Future registerWithPassKey(String name, + {bool? requiresUserVerification}) async { + final signer = + PassKeySigner("webauthn.io", "webauthn", "https://webauthn.io"); + final SmartWalletFactory walletFactory = SmartWalletFactory(_chain, signer); + + _keyPair = await signer.register(name); + + try { + final salt = Uint256.fromHex( + hexlify(w3d.keccak256(Uint8List.fromList(utf8.encode(name))))); + _wallet = + await walletFactory.createP256Account(_keyPair!, salt); + log("wallet created ${_wallet.address.hex} "); + } catch (e) { + log("something happened: $e"); + } + } + + Future registerWithHDWallet() async { + final signer = EOAWallet.createWallet(); + log("mnemonic: ${signer.exportMnemonic()}"); + final SmartWalletFactory walletFactory = SmartWalletFactory(_chain, signer); + + try { + final salt = Uint256.fromHex(hexlify(w3d.keccak256( + Uint8List.fromList(utf8.encode(signer.getAddress().substring(2)))))); + _wallet = await walletFactory.createSimpleAccount(salt); + log("wallet created ${_wallet.address.hex} "); + } catch (e) { + log("something happened: $e"); + } + } + + Future sendTransaction(String recipient, String amount) async { + final etherAmount = + w3d.EtherAmount.fromBase10String(w3d.EtherUnit.ether, amount); + await wallet.send(EthereumAddress.fromHex(recipient), etherAmount); + } +} diff --git a/example/lib/screens/create_account.dart b/example/lib/screens/create_account.dart new file mode 100644 index 0000000..f4dfe03 --- /dev/null +++ b/example/lib/screens/create_account.dart @@ -0,0 +1,134 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:provider/provider.dart'; +import 'package:variancedemo/providers/wallet_provider.dart'; +import 'package:variancedemo/utils/widgets.dart'; + +class CreateAccountScreen extends StatefulWidget { + const CreateAccountScreen({super.key}); + + @override + State createState() => _CreateAccountScreenState(); +} + +class _CreateAccountScreenState extends State { + final TextEditingController controller = TextEditingController(); + final _formKey = GlobalKey(); + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + body: Consumer( + builder: (context, value, child) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 16.sp), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Create Account', + style: TextStyle(fontSize: 51, fontWeight: FontWeight.w500), + ), + const SizedBox(height: 50), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(25)), + child: TextFormField( + key: _formKey, + autovalidateMode: AutovalidateMode.onUserInteraction, + validator: (value) { + if (value!.isEmpty) { + return 'Please enter a username'; + } + if (!RegExp(r'^[a-zA-Z]+$').hasMatch(value)) { + return 'Username must contain only alphabets with no spaces'; + } + return null; + }, + onChanged: (value) { + setState(() { + controller.text = value; + }); + }, + controller: controller, + maxLines: 1, + textAlign: TextAlign.center, + decoration: InputDecoration( + contentPadding: const EdgeInsets.all(8), + hintText: 'Choose a username', + hintStyle: TextStyle( + fontSize: 20.sp, + ), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(12), + ), + ), + fillColor: Colors.white, + filled: true), + ), + )), + const SizedBox( + width: 10, + ), + SizedBox( + height: 45.h, + child: TextButton( + onPressed: () async { + try { + await value.registerWithPassKey(controller.text, + requiresUserVerification: true); + // ignore: use_build_context_synchronously + Navigator.pushNamed(context, '/home'); + } catch (e) { + showSnackbar(e.toString()); + } + }, + style: TextButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12)), + backgroundColor: const Color(0xffE1FF01), + ), + child: const Text( + 'Continue', + style: TextStyle(fontSize: 14, color: Colors.black), + ), + ), + ) + ], + ), + 18.verticalSpace, + Container( + margin: const EdgeInsets.only(left: 135), + child: Text('OR', style: TextStyle(fontSize: 18.sp))), + 24.verticalSpace, + Container( + margin: const EdgeInsets.only(left: 30), + child: TextButton.icon( + onPressed: () { + try { + context + .read() + .registerWithHDWallet(); + Navigator.pushNamed(context, '/home'); + } catch (e) { + 'Something went wrong: $e'; + } + }, + icon: const Icon(Icons.key), + label: const Text('Generate Account with HD Key')), + ) + ], + ), + ); + }, + ), + ), + ); + } +} diff --git a/example/lib/screens/home_screen.dart b/example/lib/screens/home_screen.dart new file mode 100644 index 0000000..42a5c4b --- /dev/null +++ b/example/lib/screens/home_screen.dart @@ -0,0 +1,392 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:provider/provider.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:variancedemo/providers/wallet_provider.dart'; +import 'package:variancedemo/variance_colors.dart'; +import 'dart:ui' as ui; + +import 'package:web3_signers/web3_signers.dart'; + +class WalletHome extends StatefulWidget { + const WalletHome({super.key}); + + @override + State createState() => _WalletHomeState(); +} + +class _WalletHomeState extends State { + final _formKey = GlobalKey(); + TextEditingController amountController = TextEditingController(); + TextEditingController addressController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + backgroundColor: VarianceColors.primary, + body: Padding( + padding: EdgeInsets.symmetric(vertical: 19.h, horizontal: 16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const WalletBalance(), + 110.verticalSpace, + AddressBar( + hintText: 'Eth Address', + textEditingController: addressController, + ), + 18.verticalSpace, + TextFormField( + style: TextStyle( + fontSize: 51.sp, + fontWeight: FontWeight.w600, + color: VarianceColors.secondary), + key: _formKey, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a value'; + } else if (int.parse(value) > 100) { + return 'Value should be less than or equal to 100'; + } + return null; + }, + onChanged: (value) { + if (value.isEmpty) { + return; + } + }, + textAlign: TextAlign.center, + controller: amountController, + keyboardType: TextInputType.number, + decoration: const InputDecoration( + focusColor: Colors.white, + fillColor: Colors.white, + border: InputBorder.none, + hintText: '0.0', + hintStyle: TextStyle( + fontSize: 51, color: VarianceColors.secondary), + ), + cursorColor: VarianceColors.secondary, + inputFormatters: [ + FilteringTextInputFormatter.allow( + RegExp(r'^\.?\d*(?().sendTransaction( + addressController.text, amountController.text); + }, + child: const Text('Send')), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton.large( + child: const Icon(Icons.qr_code_2_sharp), + onPressed: () { + showModalBottomSheetContent(context); + }, + ), + ), + ); + } +} + +String address = ''; + +class WalletBalance extends StatefulWidget { + const WalletBalance({super.key}); + + @override + State createState() => _WalletBalanceState(); +} + +class _WalletBalanceState extends State { + Uint256 balance = Uint256.zero; + + @override + Widget build(BuildContext context) { + final wallet = context.select( + (WalletProvider provider) => provider.wallet, + ); + // final hdWallet = context.select( + // (WalletProvider provider) => provider.hdWalletSigner, + // ); + + address = wallet.address.hex; + + Future getBalance() async { + final ether = await wallet.balance; + setState(() { + balance = Uint256.fromWei(ether); + }); + } + //if the wallet is created with a passkey + + getBalance(); + return Consumer( + builder: (context, value, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + 'Total Balance', + style: TextStyle( + color: VarianceColors.secondary, fontSize: 14.sp), + ), + 10.horizontalSpace, + const Image( + image: AssetImage( + 'assets/images/down-arrow.png', + ), + height: 10, + width: 10, + color: VarianceColors.secondary, + ), + const Spacer(), + Expanded( + child: Text( + address, + style: const TextStyle( + color: VarianceColors.secondary, + overflow: TextOverflow.ellipsis, + ), + ), + ) + ], + ), + 18.verticalSpace, + Text( + '${balance.fromUnit(18)} ETH', + style: TextStyle(color: Colors.white, fontSize: 24.sp), + ), + 18.verticalSpace, + ], + ); + }, + ); + } +} + +class CryptoTransaction { + final String name; + final double amount; + final String date; + + CryptoTransaction({ + required this.name, + required this.amount, + required this.date, + }); +} + +class AddressBar extends StatefulWidget { + final String hintText; + final TextEditingController? textEditingController; + final TextStyle? hintTextStyle; + + // Add an optional parameter for the initial value + final String initialValue; + + const AddressBar({ + required this.hintText, + this.hintTextStyle, + this.textEditingController, + this.initialValue = "0.0", // Provide a default initial value + Key? key, + }) : super(key: key); + + @override + State createState() => _AddressBarState(); +} + +class _AddressBarState extends State { + bool pwdVisibility = false; + final formKey = GlobalKey(); + late final TextEditingController textEditingController; + @override + void initState() { + super.initState(); + // Initialize the TextEditingController with the initial value + textEditingController = widget.textEditingController ?? + TextEditingController(text: widget.initialValue); + } + + @override + Widget build(BuildContext context) { + return TextFormField( + cursorColor: VarianceColors.primary, + controller: widget.textEditingController, + textAlign: TextAlign.center, + decoration: InputDecoration( + fillColor: VarianceColors.secondary, + filled: true, + hintText: widget.hintText, + hintStyle: widget.hintTextStyle, + enabledBorder: OutlineInputBorder( + borderSide: const BorderSide( + color: Colors.white, + width: 1, + ), + borderRadius: BorderRadius.circular(10), + ), + focusedBorder: OutlineInputBorder( + borderSide: const BorderSide( + color: Colors.white, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + ), + validator: (val) { + if (val!.isEmpty) { + return 'Required'; + } + return null; + }, + ); + } +} + +String message = address; +final FutureBuilder qrFutureBuilder = FutureBuilder( + future: _loadOverlayImage(), + builder: (BuildContext ctx, AsyncSnapshot snapshot) { + const double size = 280.0; + if (!snapshot.hasData) { + return const SizedBox(width: size, height: size); + } + return CustomPaint( + size: const Size.square(size), + painter: QrPainter( + data: message.toString(), + version: QrVersions.auto, + eyeStyle: const QrEyeStyle( + eyeShape: QrEyeShape.square, + color: Color(0xff000000), + ), + dataModuleStyle: const QrDataModuleStyle( + dataModuleShape: QrDataModuleShape.circle, + color: Color(0xff000000), + ), + // size: 320.0, + embeddedImage: snapshot.data, + embeddedImageStyle: const QrEmbeddedImageStyle( + size: Size.square(60), + ), + ), + ); + }, +); +Future _loadOverlayImage() async { + final Completer completer = Completer(); + final ByteData byteData = await rootBundle.load('assets/images/ethereum.png'); + ui.decodeImageFromList(byteData.buffer.asUint8List(), completer.complete); + return completer.future; +} + +showModalBottomSheetContent(BuildContext context) { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: SizedBox( + height: MediaQuery.of(context).size.height * 0.89, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Center( + child: Container( + padding: const EdgeInsets.all(10.0), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + ), + child: qrFutureBuilder, // Replace with your content + ), + ), + const SizedBox(height: 50), + Container( + width: 280, + padding: const EdgeInsets.symmetric(horizontal: 10), + margin: const EdgeInsets.symmetric(horizontal: 15), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: Colors.grey.shade400), + ), + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Your Ethereum address', + style: TextStyle( + fontFamily: 'Inter', + color: const Color(0xff32353E).withOpacity(0.5), + ), + ), + const SizedBox(height: 5), + SizedBox( + width: 280, + child: Text( + message, + style: const TextStyle( + color: Color(0xff32353E), + ), + ), + ), + ], + ), + Expanded( + child: TextButton( + onPressed: () { + Clipboard.setData(ClipboardData( + text: message, + )); + }, + style: TextButton.styleFrom( + backgroundColor: const Color(0xff32353E), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: const Text( + 'copy', + style: TextStyle( + color: Colors.white, + fontFamily: 'Inter', + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + }, + ); +} diff --git a/example/lib/utils/widgets.dart b/example/lib/utils/widgets.dart new file mode 100644 index 0000000..71449f3 --- /dev/null +++ b/example/lib/utils/widgets.dart @@ -0,0 +1,8 @@ + import 'package:flutter/material.dart'; +import 'package:variancedemo/main.dart'; + +void showSnackbar(String message) { + var currentScaffoldMessenger = globalScaffoldMessengerKey.currentState; + currentScaffoldMessenger?.hideCurrentSnackBar(); + currentScaffoldMessenger?.showSnackBar(SnackBar(content: Text(message))); + } \ No newline at end of file diff --git a/example/lib/variance_colors.dart b/example/lib/variance_colors.dart new file mode 100644 index 0000000..3a012d8 --- /dev/null +++ b/example/lib/variance_colors.dart @@ -0,0 +1,8 @@ +import 'package:flutter/material.dart'; + +/// +class VarianceColors { + static const Color primary = Color(0xff1B1D1F); + static const Color secondary = Color(0xffC0C1C1); + static const Color white = Color(0xffffffff); +} diff --git a/example/pubspec.lock b/example/pubspec.lock new file mode 100644 index 0000000..ba641a6 --- /dev/null +++ b/example/pubspec.lock @@ -0,0 +1,761 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + ansicolor: + dependency: transitive + description: + name: ansicolor + sha256: "8bf17a8ff6ea17499e40a2d2542c2f481cd7615760c6d34065cb22bfd22e6880" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + archive: + dependency: transitive + description: + name: archive + sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" + url: "https://pub.dev" + source: hosted + version: "3.4.10" + args: + dependency: transitive + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + asn1lib: + dependency: transitive + description: + name: asn1lib + sha256: c9c85fedbe2188b95133cbe960e16f5f448860f7133330e272edbbca5893ddc6 + url: "https://pub.dev" + source: hosted + version: "1.5.2" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + blockchain_utils: + dependency: transitive + description: + name: blockchain_utils + sha256: "38ef5f4a22441ac4370aed9071dc71c460acffc37c79b344533f67d15f24c13c" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + url: "https://pub.dev" + source: hosted + version: "0.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + corbado_frontend_api_client: + dependency: transitive + description: + name: corbado_frontend_api_client + sha256: a6d65fc0da88f2e6a6e95251de0b67735556128c5d96c9b609e7b18010a6f6c1 + url: "https://pub.dev" + source: hosted + version: "1.1.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + csslib: + dependency: transitive + description: + name: csslib + sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + url: "https://pub.dev" + source: hosted + version: "1.0.6" + eip1559: + dependency: transitive + description: + name: eip1559 + sha256: c2b81ac85f3e0e71aaf558201dd9a4600f051ece7ebacd0c5d70065c9b458004 + url: "https://pub.dev" + source: hosted + version: "0.6.2" + eip55: + dependency: transitive + description: + name: eip55 + sha256: "213a9b86add87a5216328e8494b0ab836e401210c4d55eb5e521bd39e39169e1" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_launcher_icons: + dependency: "direct main" + description: + name: flutter_launcher_icons + sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" + url: "https://pub.dev" + source: hosted + version: "0.13.1" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_native_splash: + dependency: "direct main" + description: + name: flutter_native_splash + sha256: "558f10070f03ee71f850a78f7136ab239a67636a294a44a06b6b7345178edb1e" + url: "https://pub.dev" + source: hosted + version: "2.3.10" + flutter_screenutil: + dependency: "direct main" + description: + name: flutter_screenutil + sha256: "8cf100b8e4973dc570b6415a2090b0bfaa8756ad333db46939efc3e774ee100d" + url: "https://pub.dev" + source: hosted + version: "5.9.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + sha256: "5b1726fee554d1cc9db1baef8061b126567ff0a1140a03ed7de936e62f2ab98b" + url: "https://pub.dev" + source: hosted + version: "6.2.0" + html: + dependency: transitive + description: + name: html + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" + url: "https://pub.dev" + source: hosted + version: "0.15.4" + http: + dependency: transitive + description: + name: http + sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba + url: "https://pub.dev" + source: hosted + version: "1.2.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + image: + dependency: transitive + description: + name: image + sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e" + url: "https://pub.dev" + source: hosted + version: "4.1.7" + intl: + dependency: transitive + description: + name: intl + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + url: "https://pub.dev" + source: hosted + version: "0.18.1" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" + json_rpc_2: + dependency: transitive + description: + name: json_rpc_2 + sha256: "5e469bffa23899edacb7b22787780068d650b106a21c76db3c49218ab7ca447e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + meta: + dependency: transitive + description: + name: meta + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + url: "https://pub.dev" + source: hosted + version: "1.10.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + openapi_generator_annotations: + dependency: transitive + description: + name: openapi_generator_annotations + sha256: "46f1fb675029d78e19ce9143e70ce414d738b0f6c45c49c004b4b3afdb405a5c" + url: "https://pub.dev" + source: hosted + version: "4.13.1" + passkeys: + dependency: transitive + description: + name: passkeys + sha256: "79f07498b44d8372a569904d5e3e1de238d83abcf83b79d583a06394b98d2789" + url: "https://pub.dev" + source: hosted + version: "2.0.7" + passkeys_android: + dependency: transitive + description: + name: passkeys_android + sha256: ab245d18d88040409d3aa93c3359a4c10c569894361c56929cb367d4b892bbaf + url: "https://pub.dev" + source: hosted + version: "2.0.3" + passkeys_ios: + dependency: transitive + description: + name: passkeys_ios + sha256: "411b10c3cd159c9601426d47b2dc5aa4dd1e34ef69deefaac01ff81712ea6064" + url: "https://pub.dev" + source: hosted + version: "2.0.3" + passkeys_platform_interface: + dependency: transitive + description: + name: passkeys_platform_interface + sha256: a1f1f5c637049f68350cf43323df9689be2e86fe5822a6e098362e7f6168351e + url: "https://pub.dev" + source: hosted + version: "2.0.1" + passkeys_web: + dependency: transitive + description: + name: passkeys_web + sha256: "1c7815020332b9be1af4df67686826a91b6dd29fea53be947d6082654abd6280" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" + url: "https://pub.dev" + source: hosted + version: "3.7.4" + provider: + dependency: "direct main" + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" + qr: + dependency: transitive + description: + name: qr + sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + qr_flutter: + dependency: "direct main" + description: + name: qr_flutter + sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + sec: + dependency: transitive + description: + name: sec + sha256: "8bbd56df884502192a441b5f5d667265498f2f8728a282beccd9db79e215f379" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" + url: "https://pub.dev" + source: hosted + version: "2.3.5" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: "direct overridden" + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + string_validator: + dependency: transitive + description: + name: string_validator + sha256: "54d4f42cd6878ae72793a58a529d9a18ebfdfbfebd9793bbe55c9b28935e8543" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" + source: hosted + version: "0.6.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + ua_client_hints: + dependency: transitive + description: + name: ua_client_hints + sha256: "8401d7bec261f61b3d3b61cd877653ddf840de2d9e07bd164f34588572aa0c8b" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + uuid: + dependency: transitive + description: + name: uuid + sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8 + url: "https://pub.dev" + source: hosted + version: "4.3.3" + variance_dart: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.0.9" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + wallet: + dependency: transitive + description: + name: wallet + sha256: "687fd89a16557649b26189e597792962f405797fc64113e8758eabc2c2605c32" + url: "https://pub.dev" + source: hosted + version: "0.0.13" + web: + dependency: transitive + description: + name: web + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + url: "https://pub.dev" + source: hosted + version: "0.3.0" + web3_signers: + dependency: "direct main" + description: + name: web3_signers + sha256: "7dc7f83d2eba5e78eb0310fcdf0cb4c0b167c27abafc86c8027383913dd64488" + url: "https://pub.dev" + source: hosted + version: "0.0.6-r2" + web3dart: + dependency: "direct main" + description: + name: web3dart + sha256: "885e5e8f0cc3c87c09f160a7fce6279226ca41316806f7ece2001959c62ecced" + url: "https://pub.dev" + source: hosted + version: "2.7.3" + win32: + dependency: transitive + description: + name: win32 + sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" + url: "https://pub.dev" + source: hosted + version: "5.2.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.2.6 <4.0.0" + flutter: ">=3.16.9" diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 0000000..2b1013a --- /dev/null +++ b/example/pubspec.yaml @@ -0,0 +1,39 @@ +name: variancedemo +description: A new Flutter project. + +publish_to: "none" +version: 1.0.0+1 + +environment: + sdk: ">=3.1.5 <4.0.0" + +dependencies: + flutter: + sdk: flutter + cupertino_icons: ^1.0.2 + flutter_launcher_icons: ^0.13.1 + flutter_native_splash: ^2.3.7 + provider: ^6.1.1 + google_fonts: ^6.1.0 + flutter_screenutil: ^5.9.0 + qr_flutter: ^4.1.0 + web3dart: ^2.7.2 + shared_preferences: ^2.2.2 + path_provider: ^2.1.1 + web3_signers: ^0.0.6-r2 + variance_dart: + path: ../ + +dev_dependencies: + flutter_test: + sdk: flutter + + flutter_lints: ^2.0.0 + +flutter: + uses-material-design: true + assets: + - assets/images/ + +dependency_overrides: + stream_channel: ^2.1.2 diff --git a/example/pubspec.yml b/example/pubspec.yml deleted file mode 100644 index b1879ff..0000000 --- a/example/pubspec.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: example -publish_to: none -version: 0.0.1 -homepage: https://variance.space -repository: https://github.com/vaariance/variance-dart - -environment: - sdk: ">=2.12.0 <4.0.0" - -dependencies: - variance_dart: - path: ../ - -dev_dependencies: - coverage: ^1.1.0 - lints: ^2.0.0 diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart new file mode 100644 index 0000000..339fefa --- /dev/null +++ b/example/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:variancedemo/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} From e562beb32127baf7716bc5860491dc40d87d60e8 Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Sat, 30 Mar 2024 18:18:06 +0100 Subject: [PATCH 17/31] feat: update example to be p256 compatible --- example/android/app/build.gradle | 4 +- .../android/app/src/main/AndroidManifest.xml | 10 ++ .../com/example/variancedemo/MainActivity.kt | 5 +- example/assets/images/down-arrow.png | Bin 0 -> 2806 bytes example/assets/images/ethereum.png | Bin 0 -> 4600 bytes example/assets/images/variance_logo.png | Bin 0 -> 89657 bytes example/ios/Podfile | 2 +- example/ios/Podfile.lock | 54 +++++++ example/ios/Runner.xcodeproj/project.pbxproj | 138 ++++++++++++++++-- .../contents.xcworkspacedata | 3 + example/ios/Runner/Info.plist | 2 + example/lib/providers/wallet_provider.dart | 25 +++- example/pubspec.lock | 4 +- example/pubspec.yaml | 2 +- 14 files changed, 218 insertions(+), 31 deletions(-) create mode 100644 example/assets/images/down-arrow.png create mode 100644 example/assets/images/ethereum.png create mode 100644 example/assets/images/variance_logo.png create mode 100644 example/ios/Podfile.lock diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index ce57a0d..35000b9 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -24,7 +24,7 @@ if (flutterVersionName == null) { android { namespace "com.example.variancedemo" - compileSdkVersion flutter.compileSdkVersion + compileSdkVersion 34 ndkVersion flutter.ndkVersion compileOptions { @@ -45,7 +45,7 @@ android { applicationId "com.example.variancedemo" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdkVersion flutter.minSdkVersion + minSdkVersion 28 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 349159e..cde5a63 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ + + + + + + + + + + diff --git a/example/android/app/src/main/kotlin/com/example/variancedemo/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/variancedemo/MainActivity.kt index 96a3efb..ffd0113 100644 --- a/example/android/app/src/main/kotlin/com/example/variancedemo/MainActivity.kt +++ b/example/android/app/src/main/kotlin/com/example/variancedemo/MainActivity.kt @@ -1,6 +1,5 @@ package com.example.variancedemo -import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.android.FlutterFragmentActivity -class MainActivity: FlutterActivity() { -} +class MainActivity : FlutterFragmentActivity() {} diff --git a/example/assets/images/down-arrow.png b/example/assets/images/down-arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..3dfec09ec269db62b33f2f0c850969b2c7f19ed0 GIT binary patch literal 2806 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 literal 0 HcmV?d00001 diff --git a/example/assets/images/variance_logo.png b/example/assets/images/variance_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e66f8a718f5f7d1275e7385ff353df0fafdbd8f4 GIT binary patch literal 89657 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"; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 430F0A4173282D78C8F28354 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 4A74FAFB68A53B658D418631 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -53,8 +59,12 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; - 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 983CD1B4D4ED14A1962E9010 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9F3DAD41ACBE62DCC647F3B8 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + A7E83DBB1826CD9D3A064E3D /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + AF0E938D37BDA368A07CB299 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + CD2ADA656814A888BCA45D23 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + D405FD9596CCED7157430C74 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -62,12 +72,38 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 3825D4244B2E713B314728A6 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AC89F723EB996FDAE502A039 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EE7C4C7D0A3AA61623F2F769 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 7DCC2EFB852679CF0E6782C0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 983CD1B4D4ED14A1962E9010 /* Pods_Runner.framework */, + 4A74FAFB68A53B658D418631 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -79,14 +115,6 @@ name = Flutter; sourceTree = ""; }; - 331C8082294A63A400263BE5 /* RunnerTests */ = { - isa = PBXGroup; - children = ( - 331C807B294A618700263BE5 /* RunnerTests.swift */, - ); - path = RunnerTests; - sourceTree = ""; - }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( @@ -94,6 +122,8 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, + E7114C3F32BD70DD1950BD59 /* Pods */, + 7DCC2EFB852679CF0E6782C0 /* Frameworks */, ); sourceTree = ""; }; @@ -121,6 +151,20 @@ path = Runner; sourceTree = ""; }; + E7114C3F32BD70DD1950BD59 /* Pods */ = { + isa = PBXGroup; + children = ( + 9F3DAD41ACBE62DCC647F3B8 /* Pods-Runner.debug.xcconfig */, + 430F0A4173282D78C8F28354 /* Pods-Runner.release.xcconfig */, + AF0E938D37BDA368A07CB299 /* Pods-Runner.profile.xcconfig */, + D405FD9596CCED7157430C74 /* Pods-RunnerTests.debug.xcconfig */, + A7E83DBB1826CD9D3A064E3D /* Pods-RunnerTests.release.xcconfig */, + CD2ADA656814A888BCA45D23 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -128,9 +172,10 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + F3D0508EA3321B66D50657EC /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, - 331C807E294A63A400263BE5 /* Frameworks */, 331C807F294A63A400263BE5 /* Resources */, + AC89F723EB996FDAE502A039 /* Frameworks */, ); buildRules = ( ); @@ -146,12 +191,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 42325FF05B70CB02BFCCE667 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 96EF6373ECA3887762113812 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -239,6 +286,45 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 42325FF05B70CB02BFCCE667 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 96EF6373ECA3887762113812 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -254,6 +340,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + F3D0508EA3321B66D50657EC /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -377,7 +485,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = D405FD9596CCED7157430C74 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -395,7 +503,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = A7E83DBB1826CD9D3A064E3D /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -411,7 +519,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */; + baseConfigurationReference = CD2ADA656814A888BCA45D23 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; diff --git a/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/example/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index 480e401..ec6255d 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -45,5 +45,7 @@ UIApplicationSupportsIndirectInputEvents + NSFaceIDUsageDescription + Why is my app authenticating using face id? diff --git a/example/lib/providers/wallet_provider.dart b/example/lib/providers/wallet_provider.dart index bf91cba..ce7cf78 100644 --- a/example/lib/providers/wallet_provider.dart +++ b/example/lib/providers/wallet_provider.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:developer'; +import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:web3_signers/web3_signers.dart'; import 'package:variance_dart/variance.dart'; @@ -10,14 +11,14 @@ import 'package:web3dart/crypto.dart' as w3d; class WalletProvider extends ChangeNotifier { final Chain _chain; - PassKeyPair? _keyPair; - late SmartWallet _wallet; SmartWallet get wallet => _wallet; WalletProvider() : _chain = Chains.getChain(Network.baseTestent) + ..accountFactory = EthereumAddress.fromHex( + "0x402A266e92993EbF04a5B3fd6F0e2b21bFC83070") ..bundlerUrl = "https://base-sepolia.g.alchemy.com/v2/RWbMhXe00ZY-SjGQF72kyCVQJ_nQopba" ..jsonRpcUrl = @@ -25,17 +26,27 @@ class WalletProvider extends ChangeNotifier { Future registerWithPassKey(String name, {bool? requiresUserVerification}) async { - final signer = + final pkpSigner = PassKeySigner("webauthn.io", "webauthn", "https://webauthn.io"); - final SmartWalletFactory walletFactory = SmartWalletFactory(_chain, signer); + final hwdSigner = HardwareSigner.withTag(name); - _keyPair = await signer.register(name); + final SmartWalletFactory walletFactory = + SmartWalletFactory(_chain, pkpSigner); try { final salt = Uint256.fromHex( hexlify(w3d.keccak256(Uint8List.fromList(utf8.encode(name))))); - _wallet = - await walletFactory.createP256Account(_keyPair!, salt); + + if (Platform.isAndroid) { + final keypair = await pkpSigner.register(name); + _wallet = + await walletFactory.createP256Account(keypair, salt); + } else if (Platform.isIOS) { + final keypair = await hwdSigner.generateKeyPair(); + _wallet = await walletFactory.createP256Account( + keypair, salt); + } + log("wallet created ${_wallet.address.hex} "); } catch (e) { log("something happened: $e"); diff --git a/example/pubspec.lock b/example/pubspec.lock index ba641a6..eea63d6 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -236,10 +236,10 @@ packages: dependency: "direct main" description: name: google_fonts - sha256: "5b1726fee554d1cc9db1baef8061b126567ff0a1140a03ed7de936e62f2ab98b" + sha256: f0b8d115a13ecf827013ec9fc883390ccc0e87a96ed5347a3114cac177ef18e8 url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.1.0" html: dependency: transitive description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 2b1013a..b6111f9 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: flutter_launcher_icons: ^0.13.1 flutter_native_splash: ^2.3.7 provider: ^6.1.1 - google_fonts: ^6.1.0 + google_fonts: 6.1.0 flutter_screenutil: ^5.9.0 qr_flutter: ^4.1.0 web3dart: ^2.7.2 From c657ea543569d2efe5d7cf3ffe0fcf7de9926fd0 Mon Sep 17 00:00:00 2001 From: maxiggle Date: Sat, 30 Mar 2024 21:39:16 +0100 Subject: [PATCH 18/31] fix:late initialization field --- example/ios/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- example/lib/providers/wallet_provider.dart | 25 +++++----- example/lib/screens/home_screen.dart | 7 +-- example/pubspec.lock | 48 +++++++++++++++---- pubspec.lock | 16 ++----- 6 files changed, 64 insertions(+), 36 deletions(-) diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 64e1869..bc1d45b 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -216,7 +216,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 87131a0..8e3ca5d 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ _wallet; + SmartWallet? _wallet; + SmartWallet? get wallet => _wallet; WalletProvider() : _chain = Chains.getChain(Network.baseTestent) @@ -24,7 +22,7 @@ class WalletProvider extends ChangeNotifier { ..jsonRpcUrl = "https://base-sepolia.g.alchemy.com/v2/RWbMhXe00ZY-SjGQF72kyCVQJ_nQopba"; - Future registerWithPassKey(String name, + Future registerWithPassKey(String name, {bool? requiresUserVerification}) async { final pkpSigner = PassKeySigner("webauthn.io", "webauthn", "https://webauthn.io"); @@ -43,17 +41,18 @@ class WalletProvider extends ChangeNotifier { await walletFactory.createP256Account(keypair, salt); } else if (Platform.isIOS) { final keypair = await hwdSigner.generateKeyPair(); + print(keypair.toJson()); _wallet = await walletFactory.createP256Account( keypair, salt); } - log("wallet created ${_wallet.address.hex} "); + log("wallet created ${_wallet?.address.hex} "); } catch (e) { log("something happened: $e"); } } - Future registerWithHDWallet() async { + Future registerWithHDWallet() async { final signer = EOAWallet.createWallet(); log("mnemonic: ${signer.exportMnemonic()}"); final SmartWalletFactory walletFactory = SmartWalletFactory(_chain, signer); @@ -62,15 +61,19 @@ class WalletProvider extends ChangeNotifier { final salt = Uint256.fromHex(hexlify(w3d.keccak256( Uint8List.fromList(utf8.encode(signer.getAddress().substring(2)))))); _wallet = await walletFactory.createSimpleAccount(salt); - log("wallet created ${_wallet.address.hex} "); + log("wallet created ${_wallet?.address.hex} "); } catch (e) { log("something happened: $e"); } } Future sendTransaction(String recipient, String amount) async { - final etherAmount = - w3d.EtherAmount.fromBase10String(w3d.EtherUnit.ether, amount); - await wallet.send(EthereumAddress.fromHex(recipient), etherAmount); + if (_wallet != null) { + final etherAmount = + w3d.EtherAmount.fromBase10String(w3d.EtherUnit.ether, amount); + await _wallet!.send(EthereumAddress.fromHex(recipient), etherAmount); + } else { + log("No wallet available to send transaction"); + } } } diff --git a/example/lib/screens/home_screen.dart b/example/lib/screens/home_screen.dart index 42a5c4b..a68c7e1 100644 --- a/example/lib/screens/home_screen.dart +++ b/example/lib/screens/home_screen.dart @@ -11,6 +11,7 @@ import 'package:variancedemo/variance_colors.dart'; import 'dart:ui' as ui; import 'package:web3_signers/web3_signers.dart'; +import 'package:web3dart/web3dart.dart'; class WalletHome extends StatefulWidget { const WalletHome({super.key}); @@ -129,12 +130,12 @@ class _WalletBalanceState extends State { // (WalletProvider provider) => provider.hdWalletSigner, // ); - address = wallet.address.hex; + address = wallet?.address.hex ?? ''; Future getBalance() async { - final ether = await wallet.balance; + final ether = await wallet?.balance; setState(() { - balance = Uint256.fromWei(ether); + balance = Uint256.fromWei(ether ?? EtherAmount.zero()); }); } //if the wallet is created with a passkey diff --git a/example/pubspec.lock b/example/pubspec.lock index eea63d6..a9d01ab 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -304,6 +304,30 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" lints: dependency: transitive description: @@ -316,26 +340,26 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" nested: dependency: transitive description: @@ -396,10 +420,10 @@ packages: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" path_provider: dependency: "direct main" description: @@ -692,6 +716,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + url: "https://pub.dev" + source: hosted + version: "13.0.0" wallet: dependency: transitive description: diff --git a/pubspec.lock b/pubspec.lock index 70f7886..2d24973 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -359,18 +359,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" mime: dependency: transitive description: @@ -656,14 +656,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" - web: - dependency: transitive - description: - name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 - url: "https://pub.dev" - source: hosted - version: "0.3.0" web3_signers: dependency: "direct main" description: From 5ae408616b5182a8d350c1c113e59264d76e4b30 Mon Sep 17 00:00:00 2001 From: maxiggle Date: Sat, 30 Mar 2024 21:43:15 +0100 Subject: [PATCH 19/31] fix: ui fix --- example/lib/providers/wallet_provider.dart | 1 + example/lib/screens/home_screen.dart | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/example/lib/providers/wallet_provider.dart b/example/lib/providers/wallet_provider.dart index 7371d34..1b5fab2 100644 --- a/example/lib/providers/wallet_provider.dart +++ b/example/lib/providers/wallet_provider.dart @@ -10,6 +10,7 @@ import 'package:web3dart/crypto.dart' as w3d; class WalletProvider extends ChangeNotifier { final Chain _chain; + SmartWallet? _wallet; SmartWallet? get wallet => _wallet; diff --git a/example/lib/screens/home_screen.dart b/example/lib/screens/home_screen.dart index a68c7e1..4f050fc 100644 --- a/example/lib/screens/home_screen.dart +++ b/example/lib/screens/home_screen.dart @@ -27,10 +27,10 @@ class _WalletHomeState extends State { @override Widget build(BuildContext context) { - return SafeArea( - child: Scaffold( - backgroundColor: VarianceColors.primary, - body: Padding( + return Scaffold( + backgroundColor: VarianceColors.primary, + body: SafeArea( + child: Padding( padding: EdgeInsets.symmetric(vertical: 19.h, horizontal: 16.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -98,12 +98,12 @@ class _WalletHomeState extends State { ], ), ), - floatingActionButton: FloatingActionButton.large( - child: const Icon(Icons.qr_code_2_sharp), - onPressed: () { - showModalBottomSheetContent(context); - }, - ), + ), + floatingActionButton: FloatingActionButton.large( + child: const Icon(Icons.qr_code_2_sharp), + onPressed: () { + showModalBottomSheetContent(context); + }, ), ); } From a0af84e66f9f8cf96089a27d742014a3fad08b5c Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Sat, 30 Mar 2024 23:21:12 +0100 Subject: [PATCH 20/31] fix: create account errors --- example/ios/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- example/lib/providers/wallet_provider.dart | 19 +++++++++++++------ lib/src/4337/chains.dart | 2 ++ lib/src/4337/factory.dart | 18 +++++++++--------- lib/src/common/contract.dart | 4 ++-- 6 files changed, 28 insertions(+), 19 deletions(-) diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index bc1d45b..64e1869 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -216,7 +216,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1510; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 8e3ca5d..87131a0 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ (keypair, salt); } else if (Platform.isIOS) { final keypair = await hwdSigner.generateKeyPair(); - print(keypair.toJson()); _wallet = await walletFactory.createP256Account( keypair, salt); } @@ -56,11 +56,18 @@ class WalletProvider extends ChangeNotifier { Future registerWithHDWallet() async { final signer = EOAWallet.createWallet(); log("mnemonic: ${signer.exportMnemonic()}"); + final SmartWalletFactory walletFactory = SmartWalletFactory(_chain, signer); + final salt = Uint256.fromHex(hexlify(w3d.keccak256( + Uint8List.fromList(utf8.encode(signer.getAddress().substring(2)))))); + + _chain.accountFactory = Constants.safeProxyFactoryAddress; + final safe = await walletFactory.createSafeAccount(salt); + log("safe created ${safe.address.hex} "); + try { - final salt = Uint256.fromHex(hexlify(w3d.keccak256( - Uint8List.fromList(utf8.encode(signer.getAddress().substring(2)))))); + _chain.accountFactory = Constants.simpleAccountFactoryAddress; _wallet = await walletFactory.createSimpleAccount(salt); log("wallet created ${_wallet?.address.hex} "); } catch (e) { diff --git a/lib/src/4337/chains.dart b/lib/src/4337/chains.dart index 594dae1..10c5a7d 100644 --- a/lib/src/4337/chains.dart +++ b/lib/src/4337/chains.dart @@ -166,6 +166,8 @@ class Constants { EthereumAddress.fromHex("0x0000000071727De22E5E9d8BAf0edAc6f37da032"); static EthereumAddress zeroAddress = EthereumAddress.fromHex("0x0000000000000000000000000000000000000000"); + static final EthereumAddress simpleAccountFactoryAddress = + EthereumAddress.fromHex("0x9406Cc6185a346906296840746125a0E44976454"); static final EthereumAddress safeProxyFactoryAddress = EthereumAddress.fromHex("0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67"); static final EthereumAddress safe4337ModuleAddress = diff --git a/lib/src/4337/factory.dart b/lib/src/4337/factory.dart index d89b62c..32f4e25 100644 --- a/lib/src/4337/factory.dart +++ b/lib/src/4337/factory.dart @@ -54,15 +54,15 @@ class SmartWalletFactory implements SmartWalletFactoryBase { Future createP256Account(T keyPair, Uint256 salt, [EthereumAddress? recoveryAddress]) { switch (keyPair.runtimeType) { - case PassKeyPair _: + case const (PassKeyPair): return _createPasskeyAccount( keyPair as PassKeyPair, salt, recoveryAddress); - case P256Credential _: + case const (P256Credential): return _createSecureEnclaveAccount( keyPair as P256Credential, salt, recoveryAddress); default: throw ArgumentError.value(keyPair, 'keyPair', - 'createP256Account: An instance of `PassKeyPair` or `P256Credential` is expected'); + 'createP256Account: An instance of `PassKeyPair` or `P256Credential` is expected, but got: ${keyPair.runtimeType}'); } } @@ -101,11 +101,11 @@ class SmartWalletFactory implements SmartWalletFactoryBase { @override Future createSimpleAccount(Uint256 salt, [int? index]) async { - final signer = _signer.getAddress(index: index ?? 0); + final signer = + EthereumAddress.fromHex(_signer.getAddress(index: index ?? 0)); // Get the predicted address of the simple account - final address = await _simpleAccountfactory.getAddress( - EthereumAddress.fromHex(signer), salt.value); + final address = await _simpleAccountfactory.getAddress(signer, salt.value); // Encode the call data for the `createAccount` function // This function is used to create the simple account with the given signer address and salt @@ -191,7 +191,7 @@ class SmartWalletFactory implements SmartWalletFactoryBase { final initCalldata = _p256Accountfactory.self .function('createP256Account') - .encodeCall([creation, salt.value]); + .encodeCall([salt.value, creation]); final initCode = _getInitCode(initCalldata); final address = await _p256Accountfactory.getP256AccountAddress(salt.value, creation); @@ -224,14 +224,14 @@ class SmartWalletFactory implements SmartWalletFactoryBase { 'uint256' ], [ recoveryAddress ?? Constants.zeroAddress, - Uint8List(0), + Uint8List(32), p256.publicKey.item1.value, p256.publicKey.item2.value, ]); final initCalldata = _p256Accountfactory.self .function('createP256Account') - .encodeCall([creation, salt.value]); + .encodeCall([salt.value, creation]); final initCode = _getInitCode(initCalldata); final address = await _p256Accountfactory.getP256AccountAddress(salt.value, creation); diff --git a/lib/src/common/contract.dart b/lib/src/common/contract.dart index 4945e8e..5fe0854 100644 --- a/lib/src/common/contract.dart +++ b/lib/src/common/contract.dart @@ -283,7 +283,7 @@ class Contract { final params = [ to, amount?.getInWei ?? EtherAmount.zero().getInWei, - innerCallData ?? Uint8List(0) + innerCallData ?? Uint8List.fromList([]) ]; if (isSafe) params.add(BigInt.zero); @@ -339,7 +339,7 @@ class Contract { final params = [ recipients, amounts?.map((e) => e.getInWei) ?? [], - innerCalls ?? Uint8List(0), + innerCalls ?? Uint8List.fromList([]), ]; if (innerCalls == null || innerCalls.isEmpty) { require(amounts != null && amounts.isNotEmpty, "malformed batch request"); From 421c5251d43253f9dc9dcec62185a1c07a0b4fcc Mon Sep 17 00:00:00 2001 From: maxiggle Date: Sun, 31 Mar 2024 06:22:30 +0100 Subject: [PATCH 21/31] fix: broken copy button and address holder --- example/lib/screens/home_screen.dart | 34 +++++++++++----------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/example/lib/screens/home_screen.dart b/example/lib/screens/home_screen.dart index 4f050fc..c003f4c 100644 --- a/example/lib/screens/home_screen.dart +++ b/example/lib/screens/home_screen.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:provider/provider.dart'; import 'package:qr_flutter/qr_flutter.dart'; @@ -337,29 +338,20 @@ showModalBottomSheetContent(BuildContext context) { ), child: Row( children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Your Ethereum address', - style: TextStyle( - fontFamily: 'Inter', - color: const Color(0xff32353E).withOpacity(0.5), - ), + SizedBox( + width: 250, + child: Text( + message, + style: TextStyle( + fontFamily: 'Inter', + color: const Color(0xff32353E).withOpacity(0.5), ), - const SizedBox(height: 5), - SizedBox( - width: 280, - child: Text( - message, - style: const TextStyle( - color: Color(0xff32353E), - ), - ), - ), - ], + ), ), - Expanded( + const SizedBox(height: 5), + const Spacer(), + SizedBox( + width: 50, child: TextButton( onPressed: () { Clipboard.setData(ClipboardData( From d4ddbaa4ddb836183207dea23cb303e5cd425e04 Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Sun, 31 Mar 2024 22:25:51 +0100 Subject: [PATCH 22/31] fix: predicted safe deployment data --- example/lib/providers/wallet_provider.dart | 2 +- lib/src/common/factories.dart | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/example/lib/providers/wallet_provider.dart b/example/lib/providers/wallet_provider.dart index 14a8f13..a39b2bd 100644 --- a/example/lib/providers/wallet_provider.dart +++ b/example/lib/providers/wallet_provider.dart @@ -38,7 +38,7 @@ class WalletProvider extends ChangeNotifier { try { // uses passkeys on android, secure enclave on iOS if (Platform.isAndroid) { - final keypair = await pkpSigner.register(name); + final keypair = await pkpSigner.register(name, name); _wallet = await walletFactory.createP256Account(keypair, salt); } else if (Platform.isIOS) { diff --git a/lib/src/common/factories.dart b/lib/src/common/factories.dart index 989fb6d..bf397ad 100644 --- a/lib/src/common/factories.dart +++ b/lib/src/common/factories.dart @@ -69,8 +69,15 @@ class _SafeProxyFactory extends SafeProxyFactory EthereumAddress getPredictedSafe( Uint8List initializer, Uint256 salt, Uint8List creationCode) { + paddedAddressBytes(Uint8List addressBytes) { + return [...Uint8List(32 - addressBytes.length), ...addressBytes]; + } + final deploymentData = Uint8List.fromList( - [...creationCode, ...Constants.safeSingletonAddress.addressBytes], + [ + ...creationCode, + ...paddedAddressBytes(Constants.safeSingletonAddress.addressBytes) + ], ); final hash = keccak256( From ea7351b8c5b857ffde14c4921b64590af2ac3bc2 Mon Sep 17 00:00:00 2001 From: maxiggle Date: Mon, 1 Apr 2024 12:58:20 +0100 Subject: [PATCH 23/31] fixes --- example/ios/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- example/lib/providers/wallet_provider.dart | 7 ++ example/lib/screens/create_account.dart | 26 ++++-- example/lib/screens/home_screen.dart | 80 +------------------ example/lib/utils/widgets.dart | 78 ++++++++++++++++-- example/pubspec.lock | 8 ++ example/pubspec.yaml | 1 + 8 files changed, 112 insertions(+), 92 deletions(-) diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 64e1869..bc1d45b 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -216,7 +216,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 87131a0..8e3ca5d 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ _wallet; + String? _error; + String? get errorMessage => _error; WalletProvider() : _chain = Chains.getChain(Network.baseTestent) @@ -50,6 +53,8 @@ class WalletProvider extends ChangeNotifier { log("wallet created ${_wallet?.address.hex} "); } catch (e) { log("something happened: $e"); + _error = e.toString(); + notifyListeners(); } } @@ -72,6 +77,8 @@ class WalletProvider extends ChangeNotifier { log("wallet created ${_wallet?.address.hex} "); } catch (e) { log("something happened: $e"); + _error = e.toString(); + notifyListeners(); } } diff --git a/example/lib/screens/create_account.dart b/example/lib/screens/create_account.dart index f4dfe03..8a31084 100644 --- a/example/lib/screens/create_account.dart +++ b/example/lib/screens/create_account.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:provider/provider.dart'; +import 'package:variancedemo/main.dart'; import 'package:variancedemo/providers/wallet_provider.dart'; import 'package:variancedemo/utils/widgets.dart'; @@ -14,6 +16,15 @@ class CreateAccountScreen extends StatefulWidget { class _CreateAccountScreenState extends State { final TextEditingController controller = TextEditingController(); final _formKey = GlobalKey(); + FToast? fToast; + + @override + void initState() { + super.initState(); + fToast = FToast(); + fToast?.init(context); + } + @override Widget build(BuildContext context) { return SafeArea( @@ -79,14 +90,17 @@ class _CreateAccountScreenState extends State { SizedBox( height: 45.h, child: TextButton( + key: globalScaffoldMessengerKey, onPressed: () async { - try { - await value.registerWithPassKey(controller.text, - requiresUserVerification: true); - // ignore: use_build_context_synchronously + await value.registerWithPassKey(controller.text, + requiresUserVerification: true); + // ignore: use_build_context_synchronously + if (value.errorMessage != null) { + fToast?.showToast( + gravity: ToastGravity.BOTTOM, + child: Text(value.errorMessage!)); + } else { Navigator.pushNamed(context, '/home'); - } catch (e) { - showSnackbar(e.toString()); } }, style: TextButton.styleFrom( diff --git a/example/lib/screens/home_screen.dart b/example/lib/screens/home_screen.dart index c003f4c..9750938 100644 --- a/example/lib/screens/home_screen.dart +++ b/example/lib/screens/home_screen.dart @@ -8,6 +8,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:provider/provider.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:variancedemo/providers/wallet_provider.dart'; +import 'package:variancedemo/utils/widgets.dart'; import 'package:variancedemo/variance_colors.dart'; import 'dart:ui' as ui; @@ -188,85 +189,6 @@ class _WalletBalanceState extends State { } } -class CryptoTransaction { - final String name; - final double amount; - final String date; - - CryptoTransaction({ - required this.name, - required this.amount, - required this.date, - }); -} - -class AddressBar extends StatefulWidget { - final String hintText; - final TextEditingController? textEditingController; - final TextStyle? hintTextStyle; - - // Add an optional parameter for the initial value - final String initialValue; - - const AddressBar({ - required this.hintText, - this.hintTextStyle, - this.textEditingController, - this.initialValue = "0.0", // Provide a default initial value - Key? key, - }) : super(key: key); - - @override - State createState() => _AddressBarState(); -} - -class _AddressBarState extends State { - bool pwdVisibility = false; - final formKey = GlobalKey(); - late final TextEditingController textEditingController; - @override - void initState() { - super.initState(); - // Initialize the TextEditingController with the initial value - textEditingController = widget.textEditingController ?? - TextEditingController(text: widget.initialValue); - } - - @override - Widget build(BuildContext context) { - return TextFormField( - cursorColor: VarianceColors.primary, - controller: widget.textEditingController, - textAlign: TextAlign.center, - decoration: InputDecoration( - fillColor: VarianceColors.secondary, - filled: true, - hintText: widget.hintText, - hintStyle: widget.hintTextStyle, - enabledBorder: OutlineInputBorder( - borderSide: const BorderSide( - color: Colors.white, - width: 1, - ), - borderRadius: BorderRadius.circular(10), - ), - focusedBorder: OutlineInputBorder( - borderSide: const BorderSide( - color: Colors.white, - width: 1, - ), - borderRadius: BorderRadius.circular(10)), - ), - validator: (val) { - if (val!.isEmpty) { - return 'Required'; - } - return null; - }, - ); - } -} - String message = address; final FutureBuilder qrFutureBuilder = FutureBuilder( future: _loadOverlayImage(), diff --git a/example/lib/utils/widgets.dart b/example/lib/utils/widgets.dart index 71449f3..2431ee7 100644 --- a/example/lib/utils/widgets.dart +++ b/example/lib/utils/widgets.dart @@ -1,8 +1,76 @@ - import 'package:flutter/material.dart'; +import 'package:flutter/material.dart'; import 'package:variancedemo/main.dart'; +import 'package:variancedemo/variance_colors.dart'; void showSnackbar(String message) { - var currentScaffoldMessenger = globalScaffoldMessengerKey.currentState; - currentScaffoldMessenger?.hideCurrentSnackBar(); - currentScaffoldMessenger?.showSnackBar(SnackBar(content: Text(message))); - } \ No newline at end of file + var currentScaffoldMessenger = globalScaffoldMessengerKey.currentState; + currentScaffoldMessenger?.hideCurrentSnackBar(); + currentScaffoldMessenger?.showSnackBar(SnackBar(content: Text(message))); +} + +class AddressBar extends StatefulWidget { + final String hintText; + final TextEditingController? textEditingController; + final TextStyle? hintTextStyle; + + // Add an optional parameter for the initial value + final String initialValue; + + const AddressBar({ + required this.hintText, + this.hintTextStyle, + this.textEditingController, + this.initialValue = "0.0", // Provide a default initial value + Key? key, + }) : super(key: key); + + @override + State createState() => _AddressBarState(); +} + +class _AddressBarState extends State { + bool pwdVisibility = false; + final formKey = GlobalKey(); + late final TextEditingController textEditingController; + @override + void initState() { + super.initState(); + // Initialize the TextEditingController with the initial value + textEditingController = widget.textEditingController ?? + TextEditingController(text: widget.initialValue); + } + + @override + Widget build(BuildContext context) { + return TextFormField( + cursorColor: VarianceColors.primary, + controller: widget.textEditingController, + textAlign: TextAlign.center, + decoration: InputDecoration( + fillColor: VarianceColors.secondary, + filled: true, + hintText: widget.hintText, + hintStyle: widget.hintTextStyle, + enabledBorder: OutlineInputBorder( + borderSide: const BorderSide( + color: Colors.white, + width: 1, + ), + borderRadius: BorderRadius.circular(10), + ), + focusedBorder: OutlineInputBorder( + borderSide: const BorderSide( + color: Colors.white, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + ), + validator: (val) { + if (val!.isEmpty) { + return 'Required'; + } + return null; + }, + ); + } +} diff --git a/example/pubspec.lock b/example/pubspec.lock index a9d01ab..7edad6e 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -232,6 +232,14 @@ packages: description: flutter source: sdk version: "0.0.0" + fluttertoast: + dependency: "direct main" + description: + name: fluttertoast + sha256: dfdde255317af381bfc1c486ed968d5a43a2ded9c931e87cbecd88767d6a71c1 + url: "https://pub.dev" + source: hosted + version: "8.2.4" google_fonts: dependency: "direct main" description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index b6111f9..b660c99 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: shared_preferences: ^2.2.2 path_provider: ^2.1.1 web3_signers: ^0.0.6-r2 + fluttertoast: ^8.2.4 variance_dart: path: ../ From c08d89f45a58727ffc9eb6c71db07e92fd4d6357 Mon Sep 17 00:00:00 2001 From: maxiggle Date: Tue, 2 Apr 2024 04:13:03 +0100 Subject: [PATCH 24/31] ui: added mintnft button, fixed typography, recfator code --- example/ios/Podfile.lock | 13 ++ example/lib/main.dart | 2 +- example/lib/providers/wallet_provider.dart | 16 +-- example/lib/screens/create_account.dart | 5 +- example/lib/screens/home/home_screen.dart | 123 ++++++++++++++++++ .../home_widgets.dart} | 106 +-------------- lib/src/4337/chains.dart | 4 +- 7 files changed, 153 insertions(+), 116 deletions(-) create mode 100644 example/lib/screens/home/home_screen.dart rename example/lib/screens/{home_screen.dart => home/home_widgets.dart} (64%) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index ced12a7..1388937 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2,6 +2,9 @@ PODS: - Flutter (1.0.0) - flutter_native_splash (0.0.1): - Flutter + - fluttertoast (0.0.2): + - Flutter + - Toast - passkeys_ios (0.0.1): - Flutter - path_provider_foundation (0.0.1): @@ -10,6 +13,7 @@ PODS: - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS + - Toast (4.1.0) - ua_client_hints (1.2.2): - Flutter - web3_signers (0.0.1): @@ -18,17 +22,24 @@ PODS: DEPENDENCIES: - Flutter (from `Flutter`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) + - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - passkeys_ios (from `.symlinks/plugins/passkeys_ios/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - ua_client_hints (from `.symlinks/plugins/ua_client_hints/ios`) - web3_signers (from `.symlinks/plugins/web3_signers/ios`) +SPEC REPOS: + trunk: + - Toast + EXTERNAL SOURCES: Flutter: :path: Flutter flutter_native_splash: :path: ".symlinks/plugins/flutter_native_splash/ios" + fluttertoast: + :path: ".symlinks/plugins/fluttertoast/ios" passkeys_ios: :path: ".symlinks/plugins/passkeys_ios/ios" path_provider_foundation: @@ -43,9 +54,11 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef + fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265 passkeys_ios: fdae8c06e2178a9fcb9261a6cb21fb9a06a81d53 path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 + Toast: ec33c32b8688982cecc6348adeae667c1b9938da ua_client_hints: 7f4e0f5d390685e8f7efd6eb363594f760108926 web3_signers: 5f49d582ab0d1fe673b3220aa6ecf25bb3cbed6b diff --git a/example/lib/main.dart b/example/lib/main.dart index 0e094ee..e203d25 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -4,7 +4,7 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:provider/provider.dart'; import 'package:variancedemo/providers/wallet_provider.dart'; import 'package:variancedemo/screens/create_account.dart'; -import 'package:variancedemo/screens/home_screen.dart'; +import 'package:variancedemo/screens/home/home_screen.dart'; final globalScaffoldMessengerKey = GlobalKey(); diff --git a/example/lib/providers/wallet_provider.dart b/example/lib/providers/wallet_provider.dart index 9dfc1cf..d6d56a5 100644 --- a/example/lib/providers/wallet_provider.dart +++ b/example/lib/providers/wallet_provider.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'dart:developer'; import 'dart:io'; import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; import 'package:web3_signers/web3_signers.dart'; import 'package:variance_dart/variance.dart'; import 'package:web3dart/credentials.dart'; @@ -14,11 +13,12 @@ class WalletProvider extends ChangeNotifier { SmartWallet? _wallet; SmartWallet? get wallet => _wallet; - String? _error; - String? get errorMessage => _error; + + String _errorMessage = ""; + String get errorMessage => _errorMessage; WalletProvider() - : _chain = Chains.getChain(Network.baseTestent) + : _chain = Chains.getChain(Network.baseTestnet) ..accountFactory = EthereumAddress.fromHex( "0x402A266e92993EbF04a5B3fd6F0e2b21bFC83070") ..bundlerUrl = @@ -52,9 +52,9 @@ class WalletProvider extends ChangeNotifier { log("wallet created ${_wallet?.address.hex} "); } catch (e) { - log("something happened: $e"); - _error = e.toString(); + _errorMessage = e.toString(); notifyListeners(); + log("something happened: $e"); } } @@ -77,11 +77,11 @@ class WalletProvider extends ChangeNotifier { log("wallet created ${_wallet?.address.hex} "); } catch (e) { log("something happened: $e"); - _error = e.toString(); - notifyListeners(); } } + Future mintNFt() async {} + Future sendTransaction(String recipient, String amount) async { if (_wallet != null) { final etherAmount = diff --git a/example/lib/screens/create_account.dart b/example/lib/screens/create_account.dart index 8a31084..1edc173 100644 --- a/example/lib/screens/create_account.dart +++ b/example/lib/screens/create_account.dart @@ -4,7 +4,6 @@ import 'package:fluttertoast/fluttertoast.dart'; import 'package:provider/provider.dart'; import 'package:variancedemo/main.dart'; import 'package:variancedemo/providers/wallet_provider.dart'; -import 'package:variancedemo/utils/widgets.dart'; class CreateAccountScreen extends StatefulWidget { const CreateAccountScreen({super.key}); @@ -95,10 +94,10 @@ class _CreateAccountScreenState extends State { await value.registerWithPassKey(controller.text, requiresUserVerification: true); // ignore: use_build_context_synchronously - if (value.errorMessage != null) { + if (value.errorMessage.isNotEmpty) { fToast?.showToast( gravity: ToastGravity.BOTTOM, - child: Text(value.errorMessage!)); + child: Text(value.errorMessage)); } else { Navigator.pushNamed(context, '/home'); } diff --git a/example/lib/screens/home/home_screen.dart b/example/lib/screens/home/home_screen.dart new file mode 100644 index 0000000..7f34217 --- /dev/null +++ b/example/lib/screens/home/home_screen.dart @@ -0,0 +1,123 @@ +import 'dart:developer'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:provider/provider.dart'; +import 'package:variancedemo/providers/wallet_provider.dart'; +import 'package:variancedemo/screens/home/home_widgets.dart'; +import 'package:variancedemo/utils/widgets.dart'; +import 'package:variancedemo/variance_colors.dart'; + +class WalletHome extends StatefulWidget { + const WalletHome({super.key}); + + @override + State createState() => _WalletHomeState(); +} + +class _WalletHomeState extends State { + final _formKey = GlobalKey(); + TextEditingController amountController = TextEditingController(); + TextEditingController addressController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: VarianceColors.primary, + body: SafeArea( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 19.h, horizontal: 16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const WalletBalance(), + 50.verticalSpace, + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton.icon( + onPressed: () { + showModalBottomSheetContent(context); + }, + style: TextButton.styleFrom( + backgroundColor: const Color(0xffE1FF01)), + icon: const Icon(Icons.qr_code_2_sharp), + label: const Text(' Receive')), + 50.horizontalSpace, + TextButton.icon( + onPressed: () {}, + style: TextButton.styleFrom( + backgroundColor: const Color(0xffE1FF01)), + icon: const Icon( + Icons.all_inclusive_outlined, + ), + label: const Text('Mint NFT')), + ], + ), + 60.verticalSpace, + AddressBar( + hintText: 'Eth Address', + textEditingController: addressController, + ), + 18.verticalSpace, + TextFormField( + style: TextStyle( + fontSize: 51.sp, + fontWeight: FontWeight.w600, + color: VarianceColors.secondary), + key: _formKey, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a value'; + } else if (int.parse(value) > 100) { + return 'Value should be less than or equal to 100'; + } + return null; + }, + onChanged: (value) { + if (value.isEmpty) { + return; + } + }, + textAlign: TextAlign.center, + controller: amountController, + keyboardType: TextInputType.number, + decoration: const InputDecoration( + focusColor: Colors.white, + fillColor: Colors.white, + border: InputBorder.none, + hintText: '0.0', + hintStyle: TextStyle( + fontSize: 51, color: VarianceColors.secondary), + ), + cursorColor: VarianceColors.secondary, + inputFormatters: [ + FilteringTextInputFormatter.allow( + RegExp(r'^\.?\d*(?().sendTransaction( + addressController.text, amountController.text); + }, + child: const Text('Send')), + ), + ], + ), + ), + ), + ); + } +} diff --git a/example/lib/screens/home_screen.dart b/example/lib/screens/home/home_widgets.dart similarity index 64% rename from example/lib/screens/home_screen.dart rename to example/lib/screens/home/home_widgets.dart index 9750938..abf928b 100644 --- a/example/lib/screens/home_screen.dart +++ b/example/lib/screens/home/home_widgets.dart @@ -1,116 +1,16 @@ import 'dart:async'; -import 'dart:developer'; import 'package:flutter/material.dart'; +import 'dart:ui' as ui; import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:provider/provider.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:variancedemo/providers/wallet_provider.dart'; -import 'package:variancedemo/utils/widgets.dart'; import 'package:variancedemo/variance_colors.dart'; -import 'dart:ui' as ui; - import 'package:web3_signers/web3_signers.dart'; import 'package:web3dart/web3dart.dart'; -class WalletHome extends StatefulWidget { - const WalletHome({super.key}); - - @override - State createState() => _WalletHomeState(); -} - -class _WalletHomeState extends State { - final _formKey = GlobalKey(); - TextEditingController amountController = TextEditingController(); - TextEditingController addressController = TextEditingController(); - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: VarianceColors.primary, - body: SafeArea( - child: Padding( - padding: EdgeInsets.symmetric(vertical: 19.h, horizontal: 16.w), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const WalletBalance(), - 110.verticalSpace, - AddressBar( - hintText: 'Eth Address', - textEditingController: addressController, - ), - 18.verticalSpace, - TextFormField( - style: TextStyle( - fontSize: 51.sp, - fontWeight: FontWeight.w600, - color: VarianceColors.secondary), - key: _formKey, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a value'; - } else if (int.parse(value) > 100) { - return 'Value should be less than or equal to 100'; - } - return null; - }, - onChanged: (value) { - if (value.isEmpty) { - return; - } - }, - textAlign: TextAlign.center, - controller: amountController, - keyboardType: TextInputType.number, - decoration: const InputDecoration( - focusColor: Colors.white, - fillColor: Colors.white, - border: InputBorder.none, - hintText: '0.0', - hintStyle: TextStyle( - fontSize: 51, color: VarianceColors.secondary), - ), - cursorColor: VarianceColors.secondary, - inputFormatters: [ - FilteringTextInputFormatter.allow( - RegExp(r'^\.?\d*(?().sendTransaction( - addressController.text, amountController.text); - }, - child: const Text('Send')), - ), - ], - ), - ), - ), - floatingActionButton: FloatingActionButton.large( - child: const Icon(Icons.qr_code_2_sharp), - onPressed: () { - showModalBottomSheetContent(context); - }, - ), - ); - } -} - String address = ''; class WalletBalance extends StatefulWidget { @@ -273,7 +173,8 @@ showModalBottomSheetContent(BuildContext context) { const SizedBox(height: 5), const Spacer(), SizedBox( - width: 50, + width: 60, + height: 30, child: TextButton( onPressed: () { Clipboard.setData(ClipboardData( @@ -282,6 +183,7 @@ showModalBottomSheetContent(BuildContext context) { }, style: TextButton.styleFrom( backgroundColor: const Color(0xff32353E), + padding: const EdgeInsets.all(5), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), diff --git a/lib/src/4337/chains.dart b/lib/src/4337/chains.dart index 10c5a7d..94ec80d 100644 --- a/lib/src/4337/chains.dart +++ b/lib/src/4337/chains.dart @@ -124,7 +124,7 @@ class Chains { jsonRpcUrl: "https://rpc.ankr.com/polygon_mumbai", entrypoint: EntryPointAddress.v06, ), - Network.baseTestent: Chain( + Network.baseTestnet: Chain( chainId: 84531, explorer: "https://sepolia.basescan.org/", jsonRpcUrl: "https://rpc.ankr.com/base_sepolia", @@ -194,7 +194,7 @@ enum Network { // testnet sepolia, mumbai, - baseTestent, + baseTestnet, // localhost localhost From 5d9a1a4beab6b3f8e483fa2438247ccbfaab6512 Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Tue, 2 Apr 2024 13:09:06 +0100 Subject: [PATCH 25/31] feat: fix safe final sig encoding --- example/lib/providers/wallet_provider.dart | 30 ++++++++++++---------- example/lib/screens/create_account.dart | 6 ++--- lib/src/4337/chains.dart | 2 +- lib/src/4337/providers.dart | 2 +- lib/src/4337/safe.dart | 15 ++++++----- lib/src/4337/wallet.dart | 24 ++++++++++++----- lib/src/common/logger.dart | 6 +++-- lib/variance.dart | 1 + 8 files changed, 51 insertions(+), 35 deletions(-) diff --git a/example/lib/providers/wallet_provider.dart b/example/lib/providers/wallet_provider.dart index a39b2bd..e1f134f 100644 --- a/example/lib/providers/wallet_provider.dart +++ b/example/lib/providers/wallet_provider.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:developer'; import 'dart:io'; +import 'dart:math' as math; import 'package:flutter/foundation.dart'; import 'package:web3_signers/web3_signers.dart'; import 'package:variance_dart/variance.dart'; @@ -53,23 +54,22 @@ class WalletProvider extends ChangeNotifier { } } - Future registerWithHDWallet() async { + Future createSafeWallet() async { + _chain.accountFactory = Constants.safeProxyFactoryAddress; + final signer = EOAWallet.createWallet(); - log("mnemonic: ${signer.exportMnemonic()}"); + log("signer: ${signer.getAddress()}"); final SmartWalletFactory walletFactory = SmartWalletFactory(_chain, signer); - final salt = Uint256.fromHex(hexlify(w3d.keccak256( - Uint8List.fromList(utf8.encode(signer.getAddress().substring(2)))))); + final salt = Uint256.fromHex(hexlify(w3d + .keccak256(EthereumAddress.fromHex(signer.getAddress()).addressBytes))); - _chain.accountFactory = Constants.safeProxyFactoryAddress; - final safe = await walletFactory.createSafeAccount(salt); - log("safe created ${safe.address.hex} "); + log("salt: ${salt.toHex()}"); try { - _chain.accountFactory = Constants.simpleAccountFactoryAddress; - _wallet = await walletFactory.createSimpleAccount(salt); - log("wallet created ${_wallet?.address.hex} "); + _wallet = await walletFactory.createSafeAccount(salt); + log("safe created ${_wallet?.address.hex} "); } catch (e) { log("something happened: $e"); } @@ -77,9 +77,13 @@ class WalletProvider extends ChangeNotifier { Future sendTransaction(String recipient, String amount) async { if (_wallet != null) { - final etherAmount = - w3d.EtherAmount.fromBase10String(w3d.EtherUnit.ether, amount); - await _wallet!.send(EthereumAddress.fromHex(recipient), etherAmount); + final etherAmount = w3d.EtherAmount.fromBigInt(w3d.EtherUnit.wei, + BigInt.from(double.parse(amount) * math.pow(10, 18))); + final response = + await _wallet!.send(EthereumAddress.fromHex(recipient), etherAmount); + final receipt = await response.wait(); + + log("Transaction receipt Hash: ${receipt?.userOpHash}"); } else { log("No wallet available to send transaction"); } diff --git a/example/lib/screens/create_account.dart b/example/lib/screens/create_account.dart index f4dfe03..387cc42 100644 --- a/example/lib/screens/create_account.dart +++ b/example/lib/screens/create_account.dart @@ -112,16 +112,14 @@ class _CreateAccountScreenState extends State { child: TextButton.icon( onPressed: () { try { - context - .read() - .registerWithHDWallet(); + context.read().createSafeWallet(); Navigator.pushNamed(context, '/home'); } catch (e) { 'Something went wrong: $e'; } }, icon: const Icon(Icons.key), - label: const Text('Generate Account with HD Key')), + label: const Text('Create Safe Smart Account')), ) ], ), diff --git a/lib/src/4337/chains.dart b/lib/src/4337/chains.dart index 10c5a7d..a6fc878 100644 --- a/lib/src/4337/chains.dart +++ b/lib/src/4337/chains.dart @@ -125,7 +125,7 @@ class Chains { entrypoint: EntryPointAddress.v06, ), Network.baseTestent: Chain( - chainId: 84531, + chainId: 84532, explorer: "https://sepolia.basescan.org/", jsonRpcUrl: "https://rpc.ankr.com/base_sepolia", entrypoint: EntryPointAddress.v06, diff --git a/lib/src/4337/providers.dart b/lib/src/4337/providers.dart index bc34f8c..ed0f40f 100644 --- a/lib/src/4337/providers.dart +++ b/lib/src/4337/providers.dart @@ -22,7 +22,7 @@ class BundlerProvider implements BundlerProviderBase { .send('eth_chainId') .then(BigInt.parse) .then((value) => value.toInt() == chain.chainId) - .then((value) => _initialized = value == true); + .then((value) => _initialized = value); } /// A flag indicating whether the initialization process was successful. diff --git a/lib/src/4337/safe.dart b/lib/src/4337/safe.dart index 2be8860..95d4c4f 100644 --- a/lib/src/4337/safe.dart +++ b/lib/src/4337/safe.dart @@ -17,9 +17,11 @@ class _SafePlugin extends Safe4337Module implements Safe4337ModuleBase { /// Computes the hash of a Safe UserOperation. /// /// [op] is an object representing the user operation details. + /// [currentTimestamp] is the current timestamp in seconds. /// /// Returns a Future that resolves to the hash of the user operation as a Uint8List. - Future getUserOperationHash(UserOperation op) async => + Future getUserOperationHash( + UserOperation op, int currentTimestamp) async => getOperationHash([ op.sender, op.nonce, @@ -31,23 +33,22 @@ class _SafePlugin extends Safe4337Module implements Safe4337ModuleBase { op.maxFeePerGas, op.maxPriorityFeePerGas, op.paymasterAndData, - _getEncodedSignature(op.signature) + hexToBytes(getEncodedSignature(op.signature, currentTimestamp)) ]); /// Encodes the signature of a user operation with a validity period. /// /// [signature] is the signature of the user operation. + /// [currentTimestamp] is the current timestamp in seconds. /// - /// Returns a Uint8List representing the encoded signature with a validity period. - Uint8List _getEncodedSignature(String signature) { - final currentTimestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000; - + /// Returns a HexString representing the encoded signature with a validity period. + String getEncodedSignature(String signature, int currentTimestamp) { String validAfter = currentTimestamp.toRadixString(16); validAfter = '0' * (12 - validAfter.length) + validAfter; String validUntil = (currentTimestamp + 3600).toRadixString(16); validUntil = '0' * (12 - validUntil.length) + validUntil; - return hexToBytes('0x$validAfter$validUntil${signature.substring(2)}'); + return '0x$validAfter$validUntil${signature.substring(2)}'; } } diff --git a/lib/src/4337/wallet.dart b/lib/src/4337/wallet.dart index 108def3..affb7e1 100644 --- a/lib/src/4337/wallet.dart +++ b/lib/src/4337/wallet.dart @@ -153,16 +153,26 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { @override Future signUserOperation(UserOperation op, {int? index}) async { - // Calculate the operation hash. safe accounts uses EIP712 - // The hash is retrieved by calling the SafeModule with the operation and a dummy signature. - final opHash = hasPlugin("safe") - ? await plugin<_SafePlugin>("safe").getUserOperationHash(op) + final isSafe = hasPlugin('safe'); + final currentTimestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000; + + // Calculate the operation hash + final opHash = isSafe + ? await plugin<_SafePlugin>('safe') + .getUserOperationHash(op, currentTimestamp) : op.hash(_chain); // Sign the operation hash using the 'signer' plugin - Uint8List signature = + final signature = await plugin('signer').personalSign(opHash, index: index); - op.signature = hexlify(signature); + final signatureHex = hexlify(signature); + + // Append the signature validity period if the 'safe' plugin is enabled + op.signature = isSafe + ? plugin<_SafePlugin>('safe') + .getEncodedSignature(signatureHex, currentTimestamp) + : signatureHex; + return op; } @@ -193,7 +203,7 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { ]).then((responses) { op = op.copyWith( nonce: op.nonce > BigInt.zero ? op.nonce : responses[0].value, - initCode: responses[0] > BigInt.zero ? Uint8List(0) : null, + initCode: responses[0].value > BigInt.zero ? Uint8List(0) : null, signature: dummySignature); return _updateUserOperationGas(op, feePerGas: responses[1]); }); diff --git a/lib/src/common/logger.dart b/lib/src/common/logger.dart index 6f0d654..f8df5db 100644 --- a/lib/src/common/logger.dart +++ b/lib/src/common/logger.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + /// A class that provides logging functionality with colored output for warnings and errors. class Logger { /// The ANSI escape code for red color. @@ -66,7 +68,7 @@ class Logger { /// [stackTrace] is an optional stack trace associated with the error message. static void _logError(String level, String color, String message, [Object? error, StackTrace? stackTrace]) { - String errorMessage = '$message'; + String errorMessage = message; if (error != null) { errorMessage += '\nError: $error'; } @@ -91,6 +93,6 @@ class Logger { '${now.second.toString().padLeft(2, '0')}'; final logMessage = '$formattedTime [$color$level$_resetColor] $message'; - print(logMessage); + log(logMessage); } } diff --git a/lib/variance.dart b/lib/variance.dart index e69e14f..a6d4a25 100644 --- a/lib/variance.dart +++ b/lib/variance.dart @@ -3,6 +3,7 @@ library; import 'dart:async'; import 'dart:convert'; import 'dart:isolate'; +import 'dart:developer'; import 'dart:typed_data'; import 'package:http/http.dart' as http; From 686de74cc3e61a99f275a5f0c3e0e15970df99ae Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Thu, 4 Apr 2024 16:03:06 +0100 Subject: [PATCH 26/31] feat: safe accounts working --- CHANGELOG.md | 21 +++ README.md | 15 +- example/ios/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- example/lib/providers/wallet_provider.dart | 111 +++++++++++-- example/lib/screens/home/home_screen.dart | 4 +- example/pubspec.lock | 22 +-- example/pubspec.yaml | 2 +- lib/src/4337/chains.dart | 2 +- lib/src/4337/factory.dart | 24 ++- lib/src/4337/paymaster.dart | 79 ++++++---- lib/src/4337/providers.dart | 23 ++- lib/src/4337/safe.dart | 55 ++++--- lib/src/4337/userop.dart | 19 +-- lib/src/4337/wallet.dart | 13 +- lib/src/abis/contract_abis.dart | 4 + lib/src/common/contract.dart | 84 +++++----- lib/src/common/factories.dart | 2 +- lib/src/common/logger.dart | 68 ++++---- lib/src/common/mixins.dart | 26 +-- lib/src/common/pack.dart | 2 +- lib/src/common/string.dart | 148 ++++++++++++++++++ lib/src/errors/wallet_errors.dart | 2 +- lib/src/interfaces/interfaces.dart | 9 +- lib/src/interfaces/json_rpc_provider.dart | 20 +++ lib/src/interfaces/smart_wallet_factory.dart | 5 +- lib/{variance.dart => variance_dart.dart} | 8 +- pubspec.lock | 64 ++++---- pubspec.yaml | 5 +- 29 files changed, 574 insertions(+), 267 deletions(-) create mode 100644 lib/src/common/string.dart rename lib/{variance.dart => variance_dart.dart} (88%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e66c3c..ab394b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +## 0.1.0-r2 + +* Fix safe transaction encoding +* Remove _checkDeployment function in counterfactual creation +* Add getBlockInformation in JsonRPCProvider + +## 0.1.0-r1 + +* Mainnet Pre-release +* refactor sdk to use the factory method for creating smart-accounts +* add safe smart accounts via safe plugin +* reduce external dependencies to 3 +* implement custom errors and add logger for debugging +* update contract abis, adding more erc20/erc721 abi snippets +* fix paymaster plugin context incompatibility +* add utility for packing and unpacking uint256 values +* update chain configuration to surpport minimal modest chains +* update example to a real flutter example +* rename library name from variance to variance_dart for consistency +* update API refs and README to reflect new changes + ## 0.0.9 * Add support for entrypoint v0.7 in parrallel. diff --git a/README.md b/README.md index 7cd7d2a..27ddb28 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,11 @@ Variance is a Dart SDK designed to simplify interaction with Ethereum-based bloc open your terminal and run the following command: ```sh -flutter pub get variance_dart -flutter pub get web3_signers +flutter pub add variance_dart +flutter pub add web3_signers + +# optionally +flutter pub add web3dart ``` ### Usage @@ -26,7 +29,7 @@ flutter pub get web3_signers ```dart // Import the package import 'package:web3_signers/web3_signers.dart'; -import 'package:variance_dart/variance.dart'; +import 'package:variance_dart/variance_dart.dart'; // optionally import 'package:web3dart/web3dart.dart'; @@ -56,11 +59,11 @@ chain.entrypoint = entrypointAddress; Also if wish to use paymasters with your smart wallet you can do so by specifying the endpoint of the paymaster. By default the paymaster is set to null. This would add a paymaster Plugin to the smart wallet. ```dart -final String paymasterUrl = 'https://pimlico.io/...'; +final String paymasterUrl = 'https://api.pimlico.io/v2/84532/rpc?apikey=...'; chain.paymasterUrl = paymasterUrl; ``` -If you have additional context for the paymaster, you will be able to add it to the smart wallet. +If you have additional context for the paymaster, you will be able to add it to the smart wallet after creation or before initiating a transaction. ```dart wallet.plugin('paymaster').context = {'key': 'value'}; @@ -153,7 +156,7 @@ await wallet.send( ); ``` -For detailed usage and examples, refer to the [documentation](https://docs.variance.space). Additional refer to the [demo](https://github.com/vaariance/variancedemo) for use in a flutter app. +For detailed usage and examples, refer to the [documentation](https://docs.variance.space). Additional refer to the [example](./example/) for use in a flutter app. ## API Reference diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index bc1d45b..64e1869 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -216,7 +216,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1510; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 8e3ca5d..87131a0 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ _errorMessage; + final EthereumAddress nft = + EthereumAddress.fromHex("0x4B509a7e891Dc8fd45491811d67a8B9e7ef547B9"); + final EthereumAddress erc20 = + EthereumAddress.fromHex("0xAEaF19097D8a8da728438D6B57edd9Bc5DAc4795"); + final EthereumAddress deployer = + EthereumAddress.fromHex("0x218F6Bbc32Ef28F547A67c70AbCF8c2ea3b468BA"); + WalletProvider() : _chain = Chains.getChain(Network.baseTestnet) ..accountFactory = EthereumAddress.fromHex( "0x402A266e92993EbF04a5B3fd6F0e2b21bFC83070") - ..bundlerUrl = - "https://base-sepolia.g.alchemy.com/v2/RWbMhXe00ZY-SjGQF72kyCVQJ_nQopba" - ..jsonRpcUrl = - "https://base-sepolia.g.alchemy.com/v2/RWbMhXe00ZY-SjGQF72kyCVQJ_nQopba"; + ..bundlerUrl = "https://api.pimlico.io/v2/84532/rpc?apikey=" + ..paymasterUrl = "https://paymaster.optimism.io/v1/84532/rpc"; Future registerWithPassKey(String name, {bool? requiresUserVerification}) async { @@ -33,19 +38,20 @@ class WalletProvider extends ChangeNotifier { PassKeySigner("webauthn.io", "webauthn", "https://webauthn.io"); final hwdSigner = HardwareSigner.withTag(name); - final SmartWalletFactory walletFactory = - SmartWalletFactory(_chain, pkpSigner); - final salt = Uint256.fromHex( hexlify(w3d.keccak256(Uint8List.fromList(utf8.encode(name))))); try { // uses passkeys on android, secure enclave on iOS if (Platform.isAndroid) { + final SmartWalletFactory walletFactory = + SmartWalletFactory(_chain, pkpSigner); final keypair = await pkpSigner.register(name, name); _wallet = await walletFactory.createP256Account(keypair, salt); } else if (Platform.isIOS) { + final SmartWalletFactory walletFactory = + SmartWalletFactory(_chain, hwdSigner); final keypair = await hwdSigner.generateKeyPair(); _wallet = await walletFactory.createP256Account( keypair, salt); @@ -59,6 +65,49 @@ class WalletProvider extends ChangeNotifier { } } + Future createEOAWallet() async { + _chain.accountFactory = Constants.simpleAccountFactoryAddress; + + final signer = EOAWallet.createWallet(); + log("signer: ${signer.getAddress()}"); + + final SmartWalletFactory walletFactory = SmartWalletFactory(_chain, signer); + final salt = Uint256.fromHex(hexlify(w3d + .keccak256(EthereumAddress.fromHex(signer.getAddress()).addressBytes))); + + try { + _wallet = await walletFactory.createSimpleAccount(salt); + log("wallet created ${_wallet?.address.hex} "); + } catch (e) { + _errorMessage = e.toString(); + notifyListeners(); + log("something happened: $e"); + } + } + + Future createPrivateKeyWallet() async { + _chain.accountFactory = Constants.simpleAccountFactoryAddress; + + final signer = PrivateKeySigner.createRandom("123456"); + log("signer: ${signer.getAddress()}"); + + final SmartWalletFactory walletFactory = SmartWalletFactory(_chain, signer); + + final salt = Uint256.fromHex(hexlify(w3d + .keccak256(EthereumAddress.fromHex(signer.getAddress()).addressBytes))); + + log("salt: ${salt.toHex()}"); + + try { + _wallet = await walletFactory.createSimpleAccount(salt); + log("wallet created ${_wallet?.address.hex} "); + } catch (e) { + _errorMessage = e.toString(); + notifyListeners(); + log("something happened: $e"); + } + } + Future createSafeWallet() async { _chain.accountFactory = Constants.safeProxyFactoryAddress; @@ -80,12 +129,56 @@ class WalletProvider extends ChangeNotifier { } } - Future mintNFt() async {} + Future mintNFt() async { + // mints nft + final tx1 = await _wallet?.sendTransaction( + nft, + Contract.encodeFunctionCall("safeMint", nft, + ContractAbis.get("ERC721_SafeMint"), [_wallet?.address])); + await tx1?.wait(); + + // mints erc20 tokens + final tx2 = await _wallet?.sendTransaction( + erc20, + Contract.encodeFunctionCall( + "mint", erc20, ContractAbis.get("ERC20_Mint"), [ + _wallet?.address, + w3d.EtherAmount.fromInt(w3d.EtherUnit.ether, 20).getInWei + ])); + await tx2?.wait(); + + // transfers the tokens + final tx3 = await _wallet?.sendTransaction( + erc20, + Contract.encodeERC20TransferCall( + erc20, deployer, w3d.EtherAmount.fromInt(w3d.EtherUnit.ether, 18))); + await tx3?.wait(); + log("trying batched transaction"); + await sendBatchedTransaction(); + } + + Future sendBatchedTransaction() async { + final tx = await _wallet?.sendBatchedTransaction([ + erc20, + erc20 + ], [ + Contract.encodeFunctionCall( + "mint", erc20, ContractAbis.get("ERC20_Mint"), [ + _wallet?.address, + w3d.EtherAmount.fromInt(w3d.EtherUnit.ether, 20).getInWei + ]), + Contract.encodeERC20TransferCall( + erc20, deployer, w3d.EtherAmount.fromInt(w3d.EtherUnit.ether, 20)) + ]); + + await tx?.wait(); + } Future sendTransaction(String recipient, String amount) async { if (_wallet != null) { final etherAmount = w3d.EtherAmount.fromBigInt(w3d.EtherUnit.wei, BigInt.from(double.parse(amount) * math.pow(10, 18))); + final response = await _wallet?.send(EthereumAddress.fromHex(recipient), etherAmount); final receipt = await response?.wait(); diff --git a/example/lib/screens/home/home_screen.dart b/example/lib/screens/home/home_screen.dart index 7f34217..1ddac2f 100644 --- a/example/lib/screens/home/home_screen.dart +++ b/example/lib/screens/home/home_screen.dart @@ -46,7 +46,9 @@ class _WalletHomeState extends State { label: const Text(' Receive')), 50.horizontalSpace, TextButton.icon( - onPressed: () {}, + onPressed: () { + context.read().mintNFt(); + }, style: TextButton.styleFrom( backgroundColor: const Color(0xffE1FF01)), icon: const Icon( diff --git a/example/pubspec.lock b/example/pubspec.lock index 751e951..128a764 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -364,18 +364,18 @@ packages: dependency: transitive description: name: passkeys - sha256: "79f07498b44d8372a569904d5e3e1de238d83abcf83b79d583a06394b98d2789" + sha256: "59e50b21746aff90cbc56145174caa3b99523f449e42f7d8aa2199ec09c511cd" url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "2.0.8" passkeys_android: dependency: transitive description: name: passkeys_android - sha256: ab245d18d88040409d3aa93c3359a4c10c569894361c56929cb367d4b892bbaf + sha256: "9dc0b84dad03329ff2f3be18bedecf1b8de9309c8e9cda6ef821dc88556a126d" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "2.0.4" passkeys_ios: dependency: transitive description: @@ -629,14 +629,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" - string_validator: - dependency: transitive - description: - name: string_validator - sha256: "54d4f42cd6878ae72793a58a529d9a18ebfdfbfebd9793bbe55c9b28935e8543" - url: "https://pub.dev" - source: hosted - version: "1.0.2" term_glyph: dependency: transitive description: @@ -691,7 +683,7 @@ packages: path: ".." relative: true source: path - version: "0.0.9" + version: "0.1.0-r1" vector_math: dependency: transitive description: @@ -720,10 +712,10 @@ packages: dependency: "direct main" description: name: web3_signers - sha256: "7dc7f83d2eba5e78eb0310fcdf0cb4c0b167c27abafc86c8027383913dd64488" + sha256: fd2a4ee394537f2140c08a395eadd8611aa713e04699c29b6aaba75e11264faf url: "https://pub.dev" source: hosted - version: "0.0.6-r2" + version: "0.0.6" web3dart: dependency: "direct main" description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index b660c99..03b61bb 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -20,10 +20,10 @@ dependencies: web3dart: ^2.7.2 shared_preferences: ^2.2.2 path_provider: ^2.1.1 - web3_signers: ^0.0.6-r2 fluttertoast: ^8.2.4 variance_dart: path: ../ + web3_signers: ^0.0.6 dev_dependencies: flutter_test: diff --git a/lib/src/4337/chains.dart b/lib/src/4337/chains.dart index c141c61..2c9ef66 100644 --- a/lib/src/4337/chains.dart +++ b/lib/src/4337/chains.dart @@ -1,4 +1,4 @@ -part of '../../variance.dart'; +part of '../../variance_dart.dart'; /// Represents an Ethereum-based blockchain chain. class Chain { diff --git a/lib/src/4337/factory.dart b/lib/src/4337/factory.dart index 32f4e25..f17beff 100644 --- a/lib/src/4337/factory.dart +++ b/lib/src/4337/factory.dart @@ -1,4 +1,4 @@ -part of '../../variance.dart'; +part of '../../variance_dart.dart'; /// A factory class for creating various types of Ethereum smart wallets. /// {@inheritDoc SmartWalletFactoryBase} @@ -31,6 +31,13 @@ class SmartWalletFactory implements SmartWalletFactoryBase { chainId: _chain.chainId, rpc: _jsonRpc.rpc); + /// A getter for the SafePlugin instance. + _SafePlugin get _safePlugin => _SafePlugin( + address: + Safe4337ModuleAddress.fromVersion(_chain.entrypoint.version).address, + chainId: _chain.chainId, + client: _safeProxyFactory.client); + /// A getter for the SafeProxyFactory contract instance. _SafeProxyFactory get _safeProxyFactory => _SafeProxyFactory( address: _chain.accountFactory!, @@ -43,13 +50,6 @@ class SmartWalletFactory implements SmartWalletFactoryBase { chainId: _chain.chainId, rpc: _jsonRpc.rpc); - /// A getter for the SafePlugin instance. - _SafePlugin get _safePlugin => _SafePlugin( - address: - Safe4337ModuleAddress.fromVersion(_chain.entrypoint.version).address, - chainId: _chain.chainId, - client: _safeProxyFactory.client); - @override Future createP256Account(T keyPair, Uint256 salt, [EthereumAddress? recoveryAddress]) { @@ -67,15 +67,11 @@ class SmartWalletFactory implements SmartWalletFactoryBase { } @override - Future createSafeAccount(Uint256 salt, - [List? owners, int? threshold]) async { + Future createSafeAccount(Uint256 salt) async { final signer = EthereumAddress.fromHex(_signer.getAddress()); - final ownerSet = owners != null ? {signer, ...owners} : [signer]; // Get the initializer data for the Safe account - final initializer = _safeProxyFactory.getInitializer( - ownerSet, - threshold ?? 1, + final initializer = _safeProxyFactory.getInitializer([signer], 1, Safe4337ModuleAddress.fromVersion(_chain.entrypoint.version)); // Get the proxy creation code for the Safe account diff --git a/lib/src/4337/paymaster.dart b/lib/src/4337/paymaster.dart index 031de41..f5cbe84 100644 --- a/lib/src/4337/paymaster.dart +++ b/lib/src/4337/paymaster.dart @@ -1,27 +1,4 @@ -part of '../../variance.dart'; - -class PaymasterResponse { - final Uint8List paymasterAndData; - final BigInt preVerificationGas; - final BigInt verificationGasLimit; - final BigInt callGasLimit; - - PaymasterResponse({ - required this.paymasterAndData, - required this.preVerificationGas, - required this.verificationGasLimit, - required this.callGasLimit, - }); - - factory PaymasterResponse.fromMap(Map map) { - return PaymasterResponse( - paymasterAndData: hexToBytes(map['paymasterAndData']), - preVerificationGas: BigInt.parse(map['preVerificationGas']), - verificationGasLimit: BigInt.parse(map['verificationGasLimit']), - callGasLimit: BigInt.parse(map['callGasLimit']), - ); - } -} +part of '../../variance_dart.dart'; /// Represents a Paymaster contract for sponsoring user operations. class Paymaster implements PaymasterBase { @@ -34,11 +11,6 @@ class Paymaster implements PaymasterBase { /// information to the Paymaster when sponsoring user operations. Map? _context; - @override - set context(Map? context) { - _context = context; - } - /// Creates a new instance of the [Paymaster] class. /// /// [_chain] is the Ethereum chain configuration. @@ -47,10 +19,15 @@ class Paymaster implements PaymasterBase { /// Throws an [InvalidPaymasterUrl] exception if the paymaster URL in the /// provided chain configuration is not a valid URL. Paymaster(this._chain, [this._context]) - : assert(isURL(_chain.paymasterUrl), + : assert(_chain.paymasterUrl.isURL(), InvalidPaymasterUrl(_chain.paymasterUrl)), _rpc = RPCBase(_chain.paymasterUrl!); + @override + set context(Map? context) { + _context = context; + } + @override Future intercept(UserOperation operation) async { final paymasterResponse = await sponsorUserOperation( @@ -62,16 +39,56 @@ class Paymaster implements PaymasterBase { preVerificationGas: paymasterResponse.preVerificationGas, verificationGasLimit: paymasterResponse.verificationGasLimit, callGasLimit: paymasterResponse.callGasLimit, + maxFeePerGas: paymasterResponse.maxFeePerGas ?? operation.maxFeePerGas, + maxPriorityFeePerGas: paymasterResponse.maxPriorityFeePerGas ?? + operation.maxPriorityFeePerGas, ); } @override Future sponsorUserOperation(Map userOp, EntryPointAddress entrypoint, Map? context) async { + final request = [userOp, entrypoint.address.hex]; + if (context != null) { + request.add(context); + } final response = await _rpc.send>( - 'pm_sponsorUserOperation', [userOp, entrypoint.address.hex, context]); + 'pm_sponsorUserOperation', request); // Parse the response into a PaymasterResponse object return PaymasterResponse.fromMap(response); } } + +class PaymasterResponse { + final Uint8List paymasterAndData; + final BigInt preVerificationGas; + final BigInt verificationGasLimit; + final BigInt callGasLimit; + final BigInt? maxFeePerGas; + final BigInt? maxPriorityFeePerGas; + + PaymasterResponse({ + required this.paymasterAndData, + required this.preVerificationGas, + required this.verificationGasLimit, + required this.callGasLimit, + this.maxFeePerGas, + this.maxPriorityFeePerGas, + }); + + factory PaymasterResponse.fromMap(Map map) { + return PaymasterResponse( + paymasterAndData: hexToBytes(map['paymasterAndData']), + preVerificationGas: BigInt.parse(map['preVerificationGas']), + verificationGasLimit: BigInt.parse(map['verificationGasLimit']), + callGasLimit: BigInt.parse(map['callGasLimit']), + maxFeePerGas: map['maxFeePerGas'] != null + ? BigInt.parse(map['maxFeePerGas']) + : null, + maxPriorityFeePerGas: map['maxPriorityFeePerGas'] != null + ? BigInt.parse(map['maxPriorityFeePerGas']) + : null, + ); + } +} diff --git a/lib/src/4337/providers.dart b/lib/src/4337/providers.dart index ed0f40f..29b1153 100644 --- a/lib/src/4337/providers.dart +++ b/lib/src/4337/providers.dart @@ -1,4 +1,4 @@ -part of '../../variance.dart'; +part of '../../variance_dart.dart'; /// A class that implements the `BundlerProviderBase` interface and provides methods /// for interacting with a bundler for sending and tracking user operations on @@ -7,6 +7,9 @@ class BundlerProvider implements BundlerProviderBase { /// The remote procedure call (RPC) client used to communicate with the bundler. final RPCBase rpc; + /// A flag indicating whether the initialization process was successful. + late final bool _initialized; + /// Creates a new instance of the BundlerProvider class. /// /// [chain] is an object representing the blockchain chain configuration. @@ -16,7 +19,7 @@ class BundlerProvider implements BundlerProviderBase { /// retrieve the chain ID and verifies that it matches the expected chain ID. /// If the chain IDs don't match, the _initialized flag is set to false. BundlerProvider(Chain chain) - : assert(isURL(chain.bundlerUrl), InvalidBundlerUrl(chain.bundlerUrl)), + : assert(chain.bundlerUrl.isURL(), InvalidBundlerUrl(chain.bundlerUrl)), rpc = RPCBase(chain.bundlerUrl!) { rpc .send('eth_chainId') @@ -25,9 +28,6 @@ class BundlerProvider implements BundlerProviderBase { .then((value) => _initialized = value); } - /// A flag indicating whether the initialization process was successful. - late final bool _initialized; - @override Future estimateUserOperationGas( Map userOp, EntryPointAddress entrypoint) async { @@ -87,7 +87,7 @@ class JsonRPCProvider implements JsonRPCProviderBase { /// The constructor checks if the JSON-RPC URL is a valid URL and initializes /// the RPC client with the JSON-RPC URL. JsonRPCProvider(Chain chain) - : assert(isURL(chain.jsonRpcUrl), InvalidJsonRpcUrl(chain.jsonRpcUrl)), + : assert(chain.jsonRpcUrl.isURL(), InvalidJsonRpcUrl(chain.jsonRpcUrl)), rpc = RPCBase(chain.jsonRpcUrl!); @override @@ -105,6 +105,17 @@ class JsonRPCProvider implements JsonRPCProviderBase { .then((value) => value.toInt()); } + @override + Future getBlockInformation({ + String blockNumber = 'latest', + bool isContainFullObj = true, + }) { + return rpc.send>( + 'eth_getBlockByNumber', + [blockNumber, isContainFullObj], + ).then((json) => BlockInformation.fromJson(json)); + } + @override Future> getEip1559GasPrice() async { final fee = await rpc.send("eth_maxPriorityFeePerGas"); diff --git a/lib/src/4337/safe.dart b/lib/src/4337/safe.dart index 95d4c4f..7f024ca 100644 --- a/lib/src/4337/safe.dart +++ b/lib/src/4337/safe.dart @@ -1,4 +1,4 @@ -part of '../../variance.dart'; +part of '../../variance_dart.dart'; /// A class that extends the Safe4337Module and implements the Safe4337ModuleBase interface. /// It provides functionality related to Safe accounts and user operations on an Ethereum-like blockchain. @@ -14,14 +14,43 @@ class _SafePlugin extends Safe4337Module implements Safe4337ModuleBase { required super.client, }); + /// Encodes the signature of a user operation with a validity period. + /// + /// [signature] is the signature of the user operation. + /// [blockInfo] is the current blockInformation including the timestamp and baseFee. + /// + /// Returns a HexString representing the encoded signature with a validity period. + String getSafeSignature(String signature, BlockInformation blockInfo) { + final timestamp = blockInfo.timestamp.millisecondsSinceEpoch ~/ 1000; + + String validAfter = (timestamp - 3600).toRadixString(16); + validAfter = '0' * (12 - validAfter.length) + validAfter; + + String validUntil = (timestamp + 3600).toRadixString(16); + validUntil = '0' * (12 - validUntil.length) + validUntil; + + int v = int.parse(signature.substring(130, 132), radix: 16); + + if (v >= 27 && v <= 30) { + v += 4; + } + + String modifiedV = v.toRadixString(16); + if (modifiedV.length == 1) { + modifiedV = '0$modifiedV'; + } + + return '0x$validAfter$validUntil${signature.substring(2, 130)}$modifiedV'; + } + /// Computes the hash of a Safe UserOperation. /// /// [op] is an object representing the user operation details. - /// [currentTimestamp] is the current timestamp in seconds. + /// [blockInfo] is the current timestamp in seconds. /// /// Returns a Future that resolves to the hash of the user operation as a Uint8List. - Future getUserOperationHash( - UserOperation op, int currentTimestamp) async => + Future getSafeOperationHash( + UserOperation op, BlockInformation blockInfo) async => getOperationHash([ op.sender, op.nonce, @@ -33,22 +62,6 @@ class _SafePlugin extends Safe4337Module implements Safe4337ModuleBase { op.maxFeePerGas, op.maxPriorityFeePerGas, op.paymasterAndData, - hexToBytes(getEncodedSignature(op.signature, currentTimestamp)) + hexToBytes(getSafeSignature(op.signature, blockInfo)) ]); - - /// Encodes the signature of a user operation with a validity period. - /// - /// [signature] is the signature of the user operation. - /// [currentTimestamp] is the current timestamp in seconds. - /// - /// Returns a HexString representing the encoded signature with a validity period. - String getEncodedSignature(String signature, int currentTimestamp) { - String validAfter = currentTimestamp.toRadixString(16); - validAfter = '0' * (12 - validAfter.length) + validAfter; - - String validUntil = (currentTimestamp + 3600).toRadixString(16); - validUntil = '0' * (12 - validUntil.length) + validUntil; - - return '0x$validAfter$validUntil${signature.substring(2)}'; - } } diff --git a/lib/src/4337/userop.dart b/lib/src/4337/userop.dart index 72fa654..1bf2ab8 100644 --- a/lib/src/4337/userop.dart +++ b/lib/src/4337/userop.dart @@ -1,4 +1,4 @@ -part of '../../variance.dart'; +part of '../../variance_dart.dart'; /// A class that implements the user operation struct defined in EIP4337. class UserOperation implements UserOperationBase { @@ -357,22 +357,23 @@ class UserOperationResponse { RangeError.value( timeout.inSeconds, "timeout", "timeout must be > pollInterval")); - return await Isolate.run(() async { - Duration count = Duration.zero; + Duration count = Duration.zero; - while (count < timeout) { - await Future.delayed(pollInterval); + while (count < timeout) { + await Future.delayed(pollInterval); + try { final receipt = await _callback(userOpHash); if (receipt != null) { return receipt; } - + count += pollInterval; + } catch (e) { count += pollInterval; } + } - throw TimeoutException( - "can't find useroperation with hash $userOpHash", timeout); - }); + throw TimeoutException( + "can't find useroperation with hash $userOpHash", timeout); } } diff --git a/lib/src/4337/wallet.dart b/lib/src/4337/wallet.dart index affb7e1..1333b73 100644 --- a/lib/src/4337/wallet.dart +++ b/lib/src/4337/wallet.dart @@ -1,4 +1,4 @@ -part of '../../variance.dart'; +part of '../../variance_dart.dart'; /// A class that represents a Smart Wallet on an Ethereum-like blockchain. /// @@ -154,12 +154,12 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { Future signUserOperation(UserOperation op, {int? index}) async { final isSafe = hasPlugin('safe'); - final currentTimestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000; + final blockInfo = + await plugin('jsonRpc').getBlockInformation(); // Calculate the operation hash final opHash = isSafe - ? await plugin<_SafePlugin>('safe') - .getUserOperationHash(op, currentTimestamp) + ? await plugin<_SafePlugin>('safe').getSafeOperationHash(op, blockInfo) : op.hash(_chain); // Sign the operation hash using the 'signer' plugin @@ -169,8 +169,7 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { // Append the signature validity period if the 'safe' plugin is enabled op.signature = isSafe - ? plugin<_SafePlugin>('safe') - .getEncodedSignature(signatureHex, currentTimestamp) + ? plugin<_SafePlugin>('safe').getSafeSignature(signatureHex, blockInfo) : signatureHex; return op; @@ -203,7 +202,7 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { ]).then((responses) { op = op.copyWith( nonce: op.nonce > BigInt.zero ? op.nonce : responses[0].value, - initCode: responses[0].value > BigInt.zero ? Uint8List(0) : null, + initCode: responses[0] > Uint256.zero ? Uint8List(0) : null, signature: dummySignature); return _updateUserOperationGas(op, feePerGas: responses[1]); }); diff --git a/lib/src/abis/contract_abis.dart b/lib/src/abis/contract_abis.dart index db9e08f..022f519 100644 --- a/lib/src/abis/contract_abis.dart +++ b/lib/src/abis/contract_abis.dart @@ -46,6 +46,10 @@ class ContractAbis { abi = '[{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"}]'; break; + case 'ERC721_SafeMint': + abi = + '[{"type":"function","name":"safeMint","inputs":[{"name":"to","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"}]'; + break; case 'ERC721_SafeTransferFrom': abi = '[{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"}]'; diff --git a/lib/src/common/contract.dart b/lib/src/common/contract.dart index 5fe0854..85e7bec 100644 --- a/lib/src/common/contract.dart +++ b/lib/src/common/contract.dart @@ -1,4 +1,4 @@ -part of '../../variance.dart'; +part of '../../variance_dart.dart'; /// A wrapper for interacting with deployed Ethereum contracts through [JsonRPCProvider]. class Contract { @@ -9,47 +9,6 @@ class Contract { this.rpc, ); - /// Asynchronously calls a function on a smart contract with the provided parameters. - /// - /// Parameters: - /// - `contractAddress`: The [EthereumAddress] of the smart contract. - /// - `abi`: The [ContractAbi] representing the smart contract's ABI. - /// - `methodName`: The name of the method to call on the smart contract. - /// - `params`: Optional parameters for the function call. - /// - `sender`: The [EthereumAddress] of the sender, if applicable. - /// - /// Returns: - /// A [Future] that completes with a list of dynamic values representing the result of the function call. - /// - /// Example: - /// ```dart - /// var result = await read( - /// EthereumAddress.fromHex('0x9876543210abcdef9876543210abcdef98765432'), - /// myErc20ContractAbi, - /// 'balanceOf', - /// params: [ EthereumAddress.fromHex('0x9876543210abcdef9876543210abcdef98765432')], - /// ); - /// ``` - /// This method uses the an Ethereum jsonRPC to `staticcall` a function on the specified smart contract. - /// **Note:** This method does not support contract calls with state changes. - Future> read( - EthereumAddress contractAddress, ContractAbi abi, String methodName, - {List? params, EthereumAddress? sender}) { - final function = getContractFunction(methodName, contractAddress, abi); - final calldata = { - 'to': contractAddress.hex, - 'data': params != null - ? bytesToHex(function.encodeCall(params), - include0x: true, padToEvenLength: true) - : "0x", - if (sender != null) 'from': sender.hex, - }; - return rpc.send('eth_call', [ - calldata, - BlockNum.current().toBlockParam() - ]).then((value) => function.decodeReturnValues(value)); - } - /// Asynchronously checks whether a smart contract is deployed at the specified address. /// /// Parameters: @@ -107,6 +66,47 @@ class Contract { .then((value) => EtherAmount.fromBigInt(EtherUnit.wei, value)); } + /// Asynchronously calls a function on a smart contract with the provided parameters. + /// + /// Parameters: + /// - `contractAddress`: The [EthereumAddress] of the smart contract. + /// - `abi`: The [ContractAbi] representing the smart contract's ABI. + /// - `methodName`: The name of the method to call on the smart contract. + /// - `params`: Optional parameters for the function call. + /// - `sender`: The [EthereumAddress] of the sender, if applicable. + /// + /// Returns: + /// A [Future] that completes with a list of dynamic values representing the result of the function call. + /// + /// Example: + /// ```dart + /// var result = await read( + /// EthereumAddress.fromHex('0x9876543210abcdef9876543210abcdef98765432'), + /// myErc20ContractAbi, + /// 'balanceOf', + /// params: [ EthereumAddress.fromHex('0x9876543210abcdef9876543210abcdef98765432')], + /// ); + /// ``` + /// This method uses the an Ethereum jsonRPC to `staticcall` a function on the specified smart contract. + /// **Note:** This method does not support contract calls with state changes. + Future> read( + EthereumAddress contractAddress, ContractAbi abi, String methodName, + {List? params, EthereumAddress? sender}) { + final function = getContractFunction(methodName, contractAddress, abi); + final calldata = { + 'to': contractAddress.hex, + 'data': params != null + ? bytesToHex(function.encodeCall(params), + include0x: true, padToEvenLength: true) + : "0x", + if (sender != null) 'from': sender.hex, + }; + return rpc.send('eth_call', [ + calldata, + BlockNum.current().toBlockParam() + ]).then((value) => function.decodeReturnValues(value)); + } + /// Encodes an ERC-20 token approval function call. /// /// Parameters: diff --git a/lib/src/common/factories.dart b/lib/src/common/factories.dart index bf397ad..bce3614 100644 --- a/lib/src/common/factories.dart +++ b/lib/src/common/factories.dart @@ -1,4 +1,4 @@ -part of '../../variance.dart'; +part of '../../variance_dart.dart'; /// A class that extends [P256AccountFactory] and implements [P256AccountFactoryBase]. /// It creates an instance of [P256AccountFactory] with a custom [RPCBase] client. diff --git a/lib/src/common/logger.dart b/lib/src/common/logger.dart index f8df5db..8659048 100644 --- a/lib/src/common/logger.dart +++ b/lib/src/common/logger.dart @@ -11,20 +11,17 @@ class Logger { /// The ANSI escape code to reset the color. static final _resetColor = '\x1B[0m'; - /// Logs a warning message. - /// - /// [message] is the warning message to be logged. - static void warning(String message) { - _logMessage('WARNING', _warningColor, message); - } - - /// Logs an error message. + /// Logs an error message if a condition is met. /// - /// [message] is the error message to be logged. + /// [condition] is the condition to check. + /// [message] is the error message to be logged if the condition is true. /// [error] is an optional error object associated with the error message. /// [stackTrace] is an optional stack trace associated with the error message. - static void error(String message, [Object? error, StackTrace? stackTrace]) { - _logError('ERROR', _errorColor, message, error, stackTrace); + static void conditionalError(bool condition, String message, + [Object? error, StackTrace? stackTrace]) { + if (condition) { + _logError('ERROR', _errorColor, message, error, stackTrace); + } } /// Logs a warning message if a condition is met. @@ -37,26 +34,38 @@ class Logger { } } - /// Logs an error message if a condition is met. + /// Logs an error message. /// - /// [condition] is the condition to check. - /// [message] is the error message to be logged if the condition is true. + /// [message] is the error message to be logged. /// [error] is an optional error object associated with the error message. /// [stackTrace] is an optional stack trace associated with the error message. - static void conditionalError(bool condition, String message, - [Object? error, StackTrace? stackTrace]) { - if (condition) { - _logError('ERROR', _errorColor, message, error, stackTrace); - } + static void error(String message, [Object? error, StackTrace? stackTrace]) { + _logError('ERROR', _errorColor, message, error, stackTrace); } - /// Logs a message with the specified level and color. + /// Logs a warning message. + /// + /// [message] is the warning message to be logged. + static void warning(String message) { + _logMessage('WARNING', _warningColor, message); + } + + /// Logs a message with the specified level, color, and timestamp. /// /// [level] is the log level (e.g., WARNING, ERROR). /// [color] is the ANSI escape code for the color. /// [message] is the message to be logged. - static void _logMessage(String level, String color, String message) { - _log(level, color, message); + static void _log(String level, String color, String message) { + final now = DateTime.now(); + final formattedTime = '${now.year.toString().padLeft(4, '0')}-' + '${now.month.toString().padLeft(2, '0')}-' + '${now.day.toString().padLeft(2, '0')} ' + '${now.hour.toString().padLeft(2, '0')}:' + '${now.minute.toString().padLeft(2, '0')}:' + '${now.second.toString().padLeft(2, '0')}'; + + final logMessage = '$formattedTime [$color$level$_resetColor] $message'; + log(logMessage); } /// Logs an error message with additional error and stack trace information. @@ -78,21 +87,12 @@ class Logger { _log(level, color, errorMessage); } - /// Logs a message with the specified level, color, and timestamp. + /// Logs a message with the specified level and color. /// /// [level] is the log level (e.g., WARNING, ERROR). /// [color] is the ANSI escape code for the color. /// [message] is the message to be logged. - static void _log(String level, String color, String message) { - final now = DateTime.now(); - final formattedTime = '${now.year.toString().padLeft(4, '0')}-' - '${now.month.toString().padLeft(2, '0')}-' - '${now.day.toString().padLeft(2, '0')} ' - '${now.hour.toString().padLeft(2, '0')}:' - '${now.minute.toString().padLeft(2, '0')}:' - '${now.second.toString().padLeft(2, '0')}'; - - final logMessage = '$formattedTime [$color$level$_resetColor] $message'; - log(logMessage); + static void _logMessage(String level, String color, String message) { + _log(level, color, message); } } diff --git a/lib/src/common/mixins.dart b/lib/src/common/mixins.dart index 4609373..a8414ce 100644 --- a/lib/src/common/mixins.dart +++ b/lib/src/common/mixins.dart @@ -1,4 +1,4 @@ -part of '../../variance.dart'; +part of '../../variance_dart.dart'; typedef Percent = double; @@ -43,7 +43,7 @@ mixin _GasSettings { /// [gasParams] is an instance of the [GasSettings] class containing the gas settings. set setGasParams(GasSettings gasParams) => _gasParams = gasParams; - /// Applies the gas settings to a user operation. + /// Applies the gas settings to a user operation, by multiplying the gas limits by a certain percentage. /// /// [op] is the user operation to which the gas settings should be applied. /// @@ -84,6 +84,17 @@ mixin _PluginManager { _plugins[name] = module; } + /// checks if a plugin exists + /// + /// Parameters: + /// - `name`: The name of the plugin to check + /// + /// Returns: + /// true if the plugin exists + bool hasPlugin(String name) { + return _plugins.containsKey(name); + } + /// Gets a plugin by name. /// /// Parameters: @@ -102,17 +113,6 @@ mixin _PluginManager { return _plugins[name] as T; } - /// checks if a plugin exists - /// - /// Parameters: - /// - `name`: The name of the plugin to check - /// - /// Returns: - /// true if the plugin exists - bool hasPlugin(String name) { - return _plugins.containsKey(name); - } - /// Removes an unwanted plugin by name. /// /// Parameters: diff --git a/lib/src/common/pack.dart b/lib/src/common/pack.dart index d3c551b..eef4813 100644 --- a/lib/src/common/pack.dart +++ b/lib/src/common/pack.dart @@ -1,4 +1,4 @@ -part of '../../variance.dart'; +part of '../../variance_dart.dart'; /// Packs two 128-bit unsigned integers into a 32-byte array. /// diff --git a/lib/src/common/string.dart b/lib/src/common/string.dart new file mode 100644 index 0000000..62018df --- /dev/null +++ b/lib/src/common/string.dart @@ -0,0 +1,148 @@ +part of '../../variance_dart.dart'; + +RegExp _ipv4Maybe = RegExp(r'^(\d?\d?\d)\.(\d?\d?\d)\.(\d?\d?\d)\.(\d?\d?\d)$'); +RegExp _ipv6 = + RegExp(r'^::|^::1|^([a-fA-F0-9]{1,4}::?){1,7}([a-fA-F0-9]{1,4})$'); + +bool isFQDN(String str, Map options) { + final parts = str.split('.'); + if (options['require_tld'] as bool) { + var tld = parts.removeLast(); + if (parts.isEmpty || !RegExp(r'^[a-z]{2,}$').hasMatch(tld)) { + return false; + } + } + + for (final part in parts) { + if (options['allow_underscores'] as bool) { + if (part.contains('__')) { + return false; + } + } + if (!RegExp(r'^[a-z\\u00a1-\\uffff0-9-]+$').hasMatch(part)) { + return false; + } + if (part[0] == '-' || + part[part.length - 1] == '-' || + part.contains('---')) { + return false; + } + } + return true; +} + +bool isIP(String str, [Object? version]) { + assert(version == null || version is String || version is int); + version = version.toString(); + if (version == 'null') { + return isIP(str, 4) || isIP(str, 6); + } else if (version == '4') { + if (!_ipv4Maybe.hasMatch(str)) { + return false; + } + var parts = str.split('.'); + parts.sort((a, b) => int.parse(a) - int.parse(b)); + return int.parse(parts[3]) <= 255; + } + return version == '6' && _ipv6.hasMatch(str); +} + +String? _shift(List elements) { + if (elements.isEmpty) return null; + return elements.removeAt(0); +} + +extension StringExtension on String? { + bool isURL() { + var str = this; + if (str == null || + str.isEmpty || + str.length > 2083 || + str.indexOf('mailto:') == 0) { + return false; + } + + final Map options = { + 'protocols': ['http', 'https', 'ftp'], + 'require_tld': true, + 'require_protocol': false, + 'allow_underscores': false, + }; + + var split = str.split('://'); + if (split.length > 1) { + final protocol = _shift(split); + final protocols = options['protocols'] as List; + if (!protocols.contains(protocol)) { + return false; + } + } else if (options['require_protocol'] == true) { + return false; + } + str = split.join('://'); + + // check hash + split = str.split('#'); + str = _shift(split); + final hash = split.join('#'); + if (hash.isNotEmpty && RegExp(r'\s').hasMatch(hash)) { + return false; + } + + // check query params + split = str?.split('?') ?? []; + str = _shift(split); + final query = split.join('?'); + if (query != "" && RegExp(r'\s').hasMatch(query)) { + return false; + } + + // check path + split = str?.split('/') ?? []; + str = _shift(split); + final path = split.join('/'); + if (path != "" && RegExp(r'\s').hasMatch(path)) { + return false; + } + + // check auth type urls + split = str?.split('@') ?? []; + if (split.length > 1) { + final auth = _shift(split); + if (auth != null && auth.contains(':')) { + // final auth = auth.split(':'); + final parts = auth.split(':'); + final user = _shift(parts); + if (user == null || !RegExp(r'^\S+$').hasMatch(user)) { + return false; + } + final pass = parts.join(':'); + if (!RegExp(r'^\S*$').hasMatch(pass)) { + return false; + } + } + } + + // check hostname + final hostname = split.join('@'); + split = hostname.split(':'); + final host = _shift(split); + if (split.isNotEmpty) { + final portStr = split.join(':'); + final port = int.tryParse(portStr, radix: 10); + if (!RegExp(r'^[0-9]+$').hasMatch(portStr) || + port == null || + port <= 0 || + port > 65535) { + return false; + } + } + + if (host == null || + !isIP(host) && !isFQDN(host, options) && host != 'localhost') { + return false; + } + + return true; + } +} diff --git a/lib/src/errors/wallet_errors.dart b/lib/src/errors/wallet_errors.dart index 009d032..cab5efc 100644 --- a/lib/src/errors/wallet_errors.dart +++ b/lib/src/errors/wallet_errors.dart @@ -1,4 +1,4 @@ -part of '../../variance.dart'; +part of '../../variance_dart.dart'; class GasEstimationError extends Error { final String message; diff --git a/lib/src/interfaces/interfaces.dart b/lib/src/interfaces/interfaces.dart index bbb15e2..583bc8e 100644 --- a/lib/src/interfaces/interfaces.dart +++ b/lib/src/interfaces/interfaces.dart @@ -3,7 +3,7 @@ import 'dart:typed_data'; import 'package:web3_signers/web3_signers.dart' show PassKeyPair, Uint256; import 'package:web3dart/web3dart.dart'; -import '../../variance.dart' +import '../../variance_dart.dart' show Chain, EntryPointAddress, @@ -18,10 +18,9 @@ import '../../variance.dart' part 'account_factories.dart'; part 'bundler_provider.dart'; - part 'json_rpc_provider.dart'; -part 'smart_wallet_factory.dart'; -part 'smart_wallet.dart'; +part 'paymaster.dart'; part 'safe_module.dart'; +part 'smart_wallet.dart'; +part 'smart_wallet_factory.dart'; part 'user_operations.dart'; -part 'paymaster.dart'; diff --git a/lib/src/interfaces/json_rpc_provider.dart b/lib/src/interfaces/json_rpc_provider.dart index 02cc99e..e7662f3 100644 --- a/lib/src/interfaces/json_rpc_provider.dart +++ b/lib/src/interfaces/json_rpc_provider.dart @@ -39,6 +39,26 @@ abstract class JsonRPCProviderBase { /// This method uses an ethereum jsonRPC to fetch the current block number from the Ethereum node. Future getBlockNumber(); + /// Asynchronously retrieves information about the specified block. + /// If no block number is provided, it defaults to the latest block. + /// If `isContainFullObj` is set to `true`, the full block object will be returned. + /// + /// Parameters: + /// - `blockNumber`: The block number to retrieve information for. + /// - `isContainFullObj`: Whether to return the full block object. + /// + /// Returns: + /// A [Future] that completes with a [BlockInformation] object containing the block information. + /// + /// Example: + /// ```dart + /// var blockInfo = await getBlockInformation(); + /// ``` + Future getBlockInformation({ + String blockNumber = 'latest', + bool isContainFullObj = true, + }); + /// Asynchronously retrieves the EIP-1559 gas prices, including `maxFeePerGas` and `maxPriorityFeePerGas`. /// /// Returns: diff --git a/lib/src/interfaces/smart_wallet_factory.dart b/lib/src/interfaces/smart_wallet_factory.dart index 805ed89..5004038 100644 --- a/lib/src/interfaces/smart_wallet_factory.dart +++ b/lib/src/interfaces/smart_wallet_factory.dart @@ -19,14 +19,11 @@ abstract class SmartWalletFactoryBase { /// Creates a new Safe account with the provided salt and optional owners and threshold. /// /// [salt] is the salt value used in the account creation process. - /// [owners] is an optional list of owner addresses for the Safe account. - /// [threshold] is an optional threshold value for the Safe account. /// /// Returns a [Future] that resolves to a [SmartWallet] instance representing /// the created Safe account. - Future createSafeAccount(Uint256 salt, - [List? owners, int? threshold]); + Future createSafeAccount(Uint256 salt); /// Creates a new simple account with the provided salt and optional index. /// diff --git a/lib/variance.dart b/lib/variance_dart.dart similarity index 88% rename from lib/variance.dart rename to lib/variance_dart.dart index a6d4a25..15ab5f8 100644 --- a/lib/variance.dart +++ b/lib/variance_dart.dart @@ -2,12 +2,9 @@ library; import 'dart:async'; import 'dart:convert'; -import 'dart:isolate'; -import 'dart:developer'; import 'dart:typed_data'; import 'package:http/http.dart' as http; -import 'package:string_validator/string_validator.dart'; import 'package:web3_signers/web3_signers.dart'; import 'package:web3dart/crypto.dart'; import 'package:web3dart/json_rpc.dart'; @@ -17,15 +14,18 @@ import 'src/abis/abis.dart'; import 'src/common/logger.dart'; import 'src/interfaces/interfaces.dart'; +export 'src/abis/abis.dart' show ContractAbis; + part 'src/4337/chains.dart'; part 'src/4337/factory.dart'; part 'src/4337/paymaster.dart'; part 'src/4337/providers.dart'; +part 'src/4337/safe.dart'; part 'src/4337/userop.dart'; part 'src/4337/wallet.dart'; part 'src/common/contract.dart'; part 'src/common/factories.dart'; part 'src/common/mixins.dart'; part 'src/common/pack.dart'; -part 'src/4337/safe.dart'; +part 'src/common/string.dart'; part 'src/errors/wallet_errors.dart'; diff --git a/pubspec.lock b/pubspec.lock index 70f7886..07c8696 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -93,18 +93,18 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" + sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" url: "https://pub.dev" source: hosted - version: "2.4.8" + version: "2.4.9" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: c9e32d21dd6626b5c163d48b037ce906bbe428bc23ab77bcd77bb21e593b6185 + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" url: "https://pub.dev" source: hosted - version: "7.2.11" + version: "7.3.0" built_collection: dependency: transitive description: @@ -117,10 +117,10 @@ packages: dependency: transitive description: name: built_value - sha256: c9aabae0718ec394e5bc3c7272e6bb0dc0b32201a08fe185ec1d8401d3e39309 + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb url: "https://pub.dev" source: hosted - version: "8.8.1" + version: "8.9.2" characters: dependency: transitive description: @@ -189,10 +189,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368" + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.3.6" eip1559: dependency: transitive description: @@ -234,10 +234,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" flutter_web_plugins: dependency: transitive description: flutter @@ -247,10 +247,10 @@ packages: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" glob: dependency: transitive description: @@ -271,10 +271,10 @@ packages: dependency: "direct main" description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" http_multi_server: dependency: transitive description: @@ -375,10 +375,10 @@ packages: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" mockito: dependency: "direct dev" description: @@ -407,18 +407,18 @@ packages: dependency: transitive description: name: passkeys - sha256: "79f07498b44d8372a569904d5e3e1de238d83abcf83b79d583a06394b98d2789" + sha256: "59e50b21746aff90cbc56145174caa3b99523f449e42f7d8aa2199ec09c511cd" url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "2.0.8" passkeys_android: dependency: transitive description: name: passkeys_android - sha256: ab245d18d88040409d3aa93c3359a4c10c569894361c56929cb367d4b892bbaf + sha256: "9dc0b84dad03329ff2f3be18bedecf1b8de9309c8e9cda6ef821dc88556a126d" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "2.0.4" passkeys_ios: dependency: transitive description: @@ -463,10 +463,10 @@ packages: dependency: transitive description: name: pointycastle - sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" url: "https://pub.dev" source: hosted - version: "3.7.3" + version: "3.7.4" pool: dependency: transitive description: @@ -576,14 +576,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" - string_validator: - dependency: "direct main" - description: - name: string_validator - sha256: "54d4f42cd6878ae72793a58a529d9a18ebfdfbfebd9793bbe55c9b28935e8543" - url: "https://pub.dev" - source: hosted - version: "1.0.2" term_glyph: dependency: transitive description: @@ -628,10 +620,10 @@ packages: dependency: transitive description: name: uuid - sha256: "22c94e5ad1e75f9934b766b53c742572ee2677c56bc871d850a57dad0f82127f" + sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8 url: "https://pub.dev" source: hosted - version: "4.2.2" + version: "4.3.3" vector_math: dependency: transitive description: @@ -668,18 +660,18 @@ packages: dependency: "direct main" description: name: web3_signers - sha256: "7dc7f83d2eba5e78eb0310fcdf0cb4c0b167c27abafc86c8027383913dd64488" + sha256: fd2a4ee394537f2140c08a395eadd8611aa713e04699c29b6aaba75e11264faf url: "https://pub.dev" source: hosted - version: "0.0.6-r2" + version: "0.0.6" web3dart: dependency: "direct main" description: name: web3dart - sha256: "31f93cf84b8c874d7ffb363959249d7e479115fe12cf46f30b037dcad6750b22" + sha256: "885e5e8f0cc3c87c09f160a7fce6279226ca41316806f7ece2001959c62ecced" url: "https://pub.dev" source: hosted - version: "2.7.2" + version: "2.7.3" web3dart_builders: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index 6eb6376..a47100e 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.9 +version: 0.1.0-r2 documentation: https://docs.variance.space homepage: https://variance.space repository: https://github.com/vaariance/variance-dart @@ -20,8 +20,7 @@ dependencies: sdk: flutter web3dart: ^2.7.2 http: ^1.1.0 - string_validator: ^1.0.2 - web3_signers: ^0.0.6-r2 + web3_signers: ^0.0.6 dev_dependencies: web3dart_builders: ^0.0.7 From bb76e89f57af71a284b3a74d5e71aa8ac369f6ba Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Fri, 5 Apr 2024 17:06:20 +0100 Subject: [PATCH 27/31] safe batch transactions --- CHANGELOG.md | 5 +++ README.md | 6 ++-- example/lib/providers/wallet_provider.dart | 20 ++---------- lib/src/4337/chains.dart | 2 ++ lib/src/4337/safe.dart | 38 ++++++++++++++++++++++ lib/src/4337/wallet.dart | 23 ++++++++++--- lib/src/abis/contract_abis.dart | 4 +++ lib/src/common/contract.dart | 32 ++++++++++++------ pubspec.yaml | 2 +- 9 files changed, 95 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab394b9..61506b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.1.0-r3 + +* Add mutisend address to constants +* Add support for Safe account batch transactions + ## 0.1.0-r2 * Fix safe transaction encoding diff --git a/README.md b/README.md index 27ddb28..36aca10 100644 --- a/README.md +++ b/README.md @@ -88,14 +88,14 @@ The smart wallet factory handles the creation of smart wallet instances. Make su final SmartWalletFactory smartWalletFactory = SmartWalletFactory(chain, signer); ``` -#### To Create a Simple Account Smart Wallet +#### To Create a Simple Smart Account ```dart final Smartwallet wallet = await smartWalletFactory.createSimpleAccount(salt); print("simple wallet address: ${wallet.address.hex}"); ``` -#### To create a P256 Smart Account +#### To create a P256 Smart Account (Secure Enclave/Keystore) ```dart final Smartwallet wallet = await smartWalletFactory.createP256Account(keypair, salt); @@ -110,7 +110,7 @@ final Smartwallet wallet = await smartWalletFactory.createP256Account(keypair, s print("p256 wallet address: ${wallet.address.hex}"); ``` -#### To create a Safe Smart Account +#### To create a [Safe](https://safe.global) Smart Account ```dart final Smartwallet wallet = await smartWalletFactory diff --git a/example/lib/providers/wallet_provider.dart b/example/lib/providers/wallet_provider.dart index 5670e91..709747b 100644 --- a/example/lib/providers/wallet_provider.dart +++ b/example/lib/providers/wallet_provider.dart @@ -29,7 +29,8 @@ class WalletProvider extends ChangeNotifier { : _chain = Chains.getChain(Network.baseTestnet) ..accountFactory = EthereumAddress.fromHex( "0x402A266e92993EbF04a5B3fd6F0e2b21bFC83070") - ..bundlerUrl = "https://api.pimlico.io/v2/84532/rpc?apikey=" + ..bundlerUrl = + "https://api.pimlico.io/v2/84532/rpc?apikey=YOUR_API_KEY" ..paymasterUrl = "https://paymaster.optimism.io/v1/84532/rpc"; Future registerWithPassKey(String name, @@ -137,23 +138,6 @@ class WalletProvider extends ChangeNotifier { ContractAbis.get("ERC721_SafeMint"), [_wallet?.address])); await tx1?.wait(); - // mints erc20 tokens - final tx2 = await _wallet?.sendTransaction( - erc20, - Contract.encodeFunctionCall( - "mint", erc20, ContractAbis.get("ERC20_Mint"), [ - _wallet?.address, - w3d.EtherAmount.fromInt(w3d.EtherUnit.ether, 20).getInWei - ])); - await tx2?.wait(); - - // transfers the tokens - final tx3 = await _wallet?.sendTransaction( - erc20, - Contract.encodeERC20TransferCall( - erc20, deployer, w3d.EtherAmount.fromInt(w3d.EtherUnit.ether, 18))); - await tx3?.wait(); - log("trying batched transaction"); await sendBatchedTransaction(); } diff --git a/lib/src/4337/chains.dart b/lib/src/4337/chains.dart index 2c9ef66..617b7ec 100644 --- a/lib/src/4337/chains.dart +++ b/lib/src/4337/chains.dart @@ -176,6 +176,8 @@ class Constants { EthereumAddress.fromHex("0x41675C099F32341bf84BFc5382aF534df5C7461a"); static final EthereumAddress safeModuleSetupAddress = EthereumAddress.fromHex("0x8EcD4ec46D4D2a6B64fE960B3D64e8B94B2234eb"); + static final EthereumAddress safeMultiSendaddress = + EthereumAddress.fromHex("0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526"); Constants._(); } diff --git a/lib/src/4337/safe.dart b/lib/src/4337/safe.dart index 7f024ca..99e168e 100644 --- a/lib/src/4337/safe.dart +++ b/lib/src/4337/safe.dart @@ -64,4 +64,42 @@ class _SafePlugin extends Safe4337Module implements Safe4337ModuleBase { op.paymasterAndData, hexToBytes(getSafeSignature(op.signature, blockInfo)) ]); + + Uint8List getSafeMultisendCallData(List recipients, + List? amounts, List? innerCalls) { + Uint8List packedCallData = Uint8List(0); + + for (int i = 0; i < recipients.length; i++) { + Uint8List operation = Uint8List.fromList([0]); + assert(operation.length == 1); + Uint8List to = recipients[i].addressBytes; + assert(to.length == 20); + Uint8List value = amounts != null + ? padTo32Bytes(amounts[i].getInWei) + : padTo32Bytes(BigInt.zero); + assert(value.length == 32); + Uint8List dataLength = innerCalls != null + ? padTo32Bytes(BigInt.from(innerCalls[i].length)) + : padTo32Bytes(BigInt.zero); + assert(dataLength.length == 32); + Uint8List data = + innerCalls != null ? innerCalls[i] : Uint8List.fromList([]); + Uint8List encodedCall = Uint8List.fromList( + [...operation, ...to, ...value, ...dataLength, ...data]); + packedCallData = Uint8List.fromList([...packedCallData, ...encodedCall]); + } + + return Contract.encodeFunctionCall( + "multiSend", + Constants.safeMultiSendaddress, + ContractAbis.get("multiSend"), + [packedCallData]); + } + + Uint8List padTo32Bytes(BigInt number) { + final bytes = intToBytes(number); + return bytes.length < 32 + ? Uint8List.fromList([...Uint8List(32 - bytes.length), ...bytes]) + : bytes; + } } diff --git a/lib/src/4337/wallet.dart b/lib/src/4337/wallet.dart index 1333b73..5f3ad5a 100644 --- a/lib/src/4337/wallet.dart +++ b/lib/src/4337/wallet.dart @@ -111,15 +111,28 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { @override Future sendBatchedTransaction( - List recipients, List calls, - {List? amounts}) => - sendUserOperation(buildUserOperation( + List recipients, List calls, + {List? amounts}) { + final isSafe = hasPlugin("safe"); + if (isSafe) { + final innerCall = plugin<_SafePlugin>('safe') + .getSafeMultisendCallData(recipients, amounts, calls); + return sendUserOperation(buildUserOperation( + callData: Contract.executeBatch( + walletAddress: _walletAddress, + recipients: [Constants.safeMultiSendaddress], + amounts: [], + innerCalls: [innerCall], + isSafe: true))); + } else { + return sendUserOperation(buildUserOperation( callData: Contract.executeBatch( walletAddress: _walletAddress, recipients: recipients, amounts: amounts, - innerCalls: calls, - isSafe: hasPlugin("safe")))); + innerCalls: calls))); + } + } @override Future sendSignedUserOperation(UserOperation op) => diff --git a/lib/src/abis/contract_abis.dart b/lib/src/abis/contract_abis.dart index 022f519..05f429d 100644 --- a/lib/src/abis/contract_abis.dart +++ b/lib/src/abis/contract_abis.dart @@ -82,6 +82,10 @@ class ContractAbis { abi = '[{"type":"function","name":"setup","inputs":[{"name":"_owners","type":"address[]","internalType":"address[]"},{"name":"_threshold","type":"uint256","internalType":"uint256"},{"name":"to","type":"address","internalType":"address"},{"name":"data","type":"bytes","internalType":"bytes"},{"name":"fallbackHandler","type":"address","internalType":"address"},{"name":"paymentToken","type":"address","internalType":"address"},{"name":"payment","type":"uint256","internalType":"uint256"},{"name":"paymentReceiver","type":"address","internalType":"address payable"}],"outputs":[],"stateMutability":"nonpayable"}]'; break; + case 'multiSend': + abi = + '[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"bytes","name":"transactions","type":"bytes"}],"name":"multiSend","outputs":[],"stateMutability":"payable","type":"function"}]'; + break; default: throw 'ABI of $name is not available by default. Please provide the ABI manually.'; } diff --git a/lib/src/common/contract.dart b/lib/src/common/contract.dart index 85e7bec..fda0bef 100644 --- a/lib/src/common/contract.dart +++ b/lib/src/common/contract.dart @@ -286,8 +286,12 @@ class Contract { innerCallData ?? Uint8List.fromList([]) ]; - if (isSafe) params.add(BigInt.zero); - final method = isSafe ? 'executeUserOpWithErrorString' : 'execute'; + String method = 'execute'; + + if (isSafe) { + method = 'executeUserOpWithErrorString'; + params.add(BigInt.zero); + } return encodeFunctionCall( method, @@ -331,24 +335,32 @@ class Contract { List? amounts, List? innerCalls, bool isSafe = false}) { - if (isSafe) { - throw UnimplementedError( - "Batch Transactions with Safe are not yet implemented."); - } - - final params = [ + List params = [ recipients, amounts?.map((e) => e.getInWei) ?? [], innerCalls ?? Uint8List.fromList([]), ]; + if (innerCalls == null || innerCalls.isEmpty) { require(amounts != null && amounts.isNotEmpty, "malformed batch request"); } + String method = 'executeBatch'; + + if (isSafe) { + method = 'executeUserOpWithErrorString'; + params = [ + recipients[0], // multisend contract + EtherAmount.zero().getInWei, // 0 value to be passed + innerCalls?[0], // the encoded multisend calldata + BigInt.one // specifying delegate call operation + ]; + } + return encodeFunctionCall( - 'executeBatch', + method, walletAddress, - ContractAbis.get('executeBatch'), + ContractAbis.get(method), params, ); } diff --git a/pubspec.yaml b/pubspec.yaml index a47100e..f16439e 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.1.0-r2 +version: 0.1.0-r3 documentation: https://docs.variance.space homepage: https://variance.space repository: https://github.com/vaariance/variance-dart From d333e678eb4a5623368462015877079aa46a820f Mon Sep 17 00:00:00 2001 From: maxiggle Date: Fri, 5 Apr 2024 19:01:42 +0100 Subject: [PATCH 28/31] ui fix --- example/lib/screens/home/home_screen.dart | 252 ++++++++++++++------- example/lib/screens/home/home_widgets.dart | 136 +++++------ example/pubspec.lock | 50 +++- 3 files changed, 280 insertions(+), 158 deletions(-) diff --git a/example/lib/screens/home/home_screen.dart b/example/lib/screens/home/home_screen.dart index 1ddac2f..0fabee4 100644 --- a/example/lib/screens/home/home_screen.dart +++ b/example/lib/screens/home/home_screen.dart @@ -1,4 +1,5 @@ import 'dart:developer'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; @@ -16,11 +17,19 @@ class WalletHome extends StatefulWidget { State createState() => _WalletHomeState(); } -class _WalletHomeState extends State { +class _WalletHomeState extends State + with SingleTickerProviderStateMixin { final _formKey = GlobalKey(); + late TabController _tabController; TextEditingController amountController = TextEditingController(); TextEditingController addressController = TextEditingController(); + @override + void initState() { + _tabController = TabController(length: 3, vsync: this); + super.initState(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -33,89 +42,37 @@ class _WalletHomeState extends State { children: [ const WalletBalance(), 50.verticalSpace, - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - TextButton.icon( - onPressed: () { - showModalBottomSheetContent(context); - }, - style: TextButton.styleFrom( - backgroundColor: const Color(0xffE1FF01)), - icon: const Icon(Icons.qr_code_2_sharp), - label: const Text(' Receive')), - 50.horizontalSpace, - TextButton.icon( - onPressed: () { - context.read().mintNFt(); - }, - style: TextButton.styleFrom( - backgroundColor: const Color(0xffE1FF01)), - icon: const Icon( - Icons.all_inclusive_outlined, - ), - label: const Text('Mint NFT')), + TabBar( + controller: _tabController, + tabs: [ + Tab( + child: Text('Send', + style: TextStyle( + fontSize: 20.sp, color: VarianceColors.secondary)), + ), + Tab( + child: Text('Receive', + style: TextStyle( + fontSize: 20.sp, color: VarianceColors.secondary)), + ), + Tab( + child: Text('NFT', + style: TextStyle( + fontSize: 20.sp, color: VarianceColors.secondary)), + ), ], ), - 60.verticalSpace, - AddressBar( - hintText: 'Eth Address', - textEditingController: addressController, - ), - 18.verticalSpace, - TextFormField( - style: TextStyle( - fontSize: 51.sp, - fontWeight: FontWeight.w600, - color: VarianceColors.secondary), - key: _formKey, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a value'; - } else if (int.parse(value) > 100) { - return 'Value should be less than or equal to 100'; - } - return null; - }, - onChanged: (value) { - if (value.isEmpty) { - return; - } - }, - textAlign: TextAlign.center, - controller: amountController, - keyboardType: TextInputType.number, - decoration: const InputDecoration( - focusColor: Colors.white, - fillColor: Colors.white, - border: InputBorder.none, - hintText: '0.0', - hintStyle: TextStyle( - fontSize: 51, color: VarianceColors.secondary), - ), - cursorColor: VarianceColors.secondary, - inputFormatters: [ - FilteringTextInputFormatter.allow( - RegExp(r'^\.?\d*(?().sendTransaction( - addressController.text, amountController.text); - }, - child: const Text('Send')), + Expanded( + child: TabBarView(controller: _tabController, children: [ + Send( + addressController: addressController, + formKey: _formKey, + amountController: amountController), + const Receive(), + const NFT(), + ]), ), + 60.verticalSpace, ], ), ), @@ -123,3 +80,136 @@ class _WalletHomeState extends State { ); } } + +class Send extends StatelessWidget { + const Send({ + super.key, + required this.addressController, + required GlobalKey formKey, + required this.amountController, + }) : _formKey = formKey; + + final TextEditingController addressController; + final GlobalKey _formKey; + final TextEditingController amountController; + + @override + Widget build(BuildContext context) { + return Column( + // mainAxisAlignment: MainAxisAlignment.center, + children: [ + 50.verticalSpace, + AddressBar( + hintText: 'Eth Address', + textEditingController: addressController, + ), + 18.verticalSpace, + TextFormField( + style: TextStyle( + fontSize: 51.sp, + fontWeight: FontWeight.w600, + color: VarianceColors.secondary), + key: _formKey, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a value'; + } else if (int.parse(value) > 100) { + return 'Value should be less than or equal to 100'; + } + return null; + }, + onChanged: (value) { + if (value.isEmpty) { + return; + } + }, + textAlign: TextAlign.center, + controller: amountController, + keyboardType: TextInputType.number, + decoration: const InputDecoration( + focusColor: Colors.white, + fillColor: Colors.white, + border: InputBorder.none, + hintText: '0.0', + hintStyle: + TextStyle(fontSize: 51, color: VarianceColors.secondary), + ), + cursorColor: VarianceColors.secondary, + inputFormatters: [ + FilteringTextInputFormatter.allow( + RegExp(r'^\.?\d*(?().sendTransaction( + addressController.text, amountController.text); + }, + child: const Text('Send')), + ), + ], + ); + } +} + +class NFT extends StatelessWidget { + const NFT({super.key}); + + @override + Widget build(BuildContext context) { + final wallet = context.select( + (WalletProvider provider) => provider.wallet, + ); + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + 50.verticalSpace, + Row( + children: [ + const CircleAvatar(), + 10.horizontalSpace, + const Text( + 'NFT', + style: TextStyle(color: VarianceColors.secondary), + ), + const Spacer(), + const Text( + '\$0.00', + style: TextStyle(color: VarianceColors.secondary), + ), + ], + ), + 10.verticalSpace, + Row(children: [ + const CircleAvatar(), + 10.horizontalSpace, + const Text( + 'NFT', + style: TextStyle(color: VarianceColors.secondary), + ), + const Spacer(), + const Text( + '\$0.00', + style: TextStyle(color: VarianceColors.secondary), + ), + ]), + Spacer(), + ElevatedButton( + onPressed: () { + context.read().mintNFt(); + }, + child: const Text('Mint')), + ], + ); + } +} diff --git a/example/lib/screens/home/home_widgets.dart b/example/lib/screens/home/home_widgets.dart index abf928b..0e54fd8 100644 --- a/example/lib/screens/home/home_widgets.dart +++ b/example/lib/screens/home/home_widgets.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'dart:ui' as ui; import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:provider/provider.dart'; import 'package:qr_flutter/qr_flutter.dart'; @@ -126,84 +127,83 @@ Future _loadOverlayImage() async { return completer.future; } -showModalBottomSheetContent(BuildContext context) { - showModalBottomSheet( - context: context, - builder: (BuildContext context) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 10.0), - child: SizedBox( - height: MediaQuery.of(context).size.height * 0.89, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Center( - child: Container( - padding: const EdgeInsets.all(10.0), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(10), - ), - child: qrFutureBuilder, // Replace with your content - ), - ), - const SizedBox(height: 50), - Container( - width: 280, - padding: const EdgeInsets.symmetric(horizontal: 10), - margin: const EdgeInsets.symmetric(horizontal: 15), +class Receive extends StatelessWidget { + const Receive({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: SizedBox( + height: MediaQuery.of(context).size.height * 0.89, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + 50.verticalSpace, + Center( + child: Container( + padding: const EdgeInsets.all(10.0), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10), - border: Border.all(color: Colors.grey.shade400), ), - child: Row( - children: [ - SizedBox( - width: 250, - child: Text( - message, - style: TextStyle( - fontFamily: 'Inter', - color: const Color(0xff32353E).withOpacity(0.5), - ), + child: qrFutureBuilder, // Replace with your content + ), + ), + const SizedBox(height: 50), + Container( + padding: const EdgeInsets.symmetric(horizontal: 10), + // margin: const EdgeInsets.symmetric(horizontal: 15), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: Colors.grey.shade400), + ), + child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: 200, + child: Text( + message, + style: TextStyle( + fontFamily: 'Inter', + overflow: TextOverflow.ellipsis, + color: const Color(0xff32353E).withOpacity(0.5), ), ), - const SizedBox(height: 5), - const Spacer(), - SizedBox( - width: 60, - height: 30, - child: TextButton( - onPressed: () { - Clipboard.setData(ClipboardData( - text: message, - )); - }, - style: TextButton.styleFrom( - backgroundColor: const Color(0xff32353E), - padding: const EdgeInsets.all(5), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), + ), + const SizedBox(height: 5), + Expanded( + child: TextButton( + onPressed: () { + Clipboard.setData(ClipboardData( + text: message, + )); + }, + style: TextButton.styleFrom( + backgroundColor: const Color(0xff32353E), + padding: const EdgeInsets.only(left: 30, right: 30), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), ), - child: const Text( - 'copy', - style: TextStyle( - color: Colors.white, - fontFamily: 'Inter', - ), + ), + child: const Text( + 'copy', + style: TextStyle( + color: Colors.white, + fontFamily: 'Inter', ), ), ), - ], - ), + ), + ], ), - ], - ), + ), + ], ), - ); - }, - ); + ), + ); + } } diff --git a/example/pubspec.lock b/example/pubspec.lock index 128a764..1e27152 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -312,6 +312,30 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" lints: dependency: transitive description: @@ -324,26 +348,26 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" nested: dependency: transitive description: @@ -404,10 +428,10 @@ packages: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" path_provider: dependency: "direct main" description: @@ -683,7 +707,7 @@ packages: path: ".." relative: true source: path - version: "0.1.0-r1" + version: "0.1.0-r3" vector_math: dependency: transitive description: @@ -692,6 +716,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + url: "https://pub.dev" + source: hosted + version: "13.0.0" wallet: dependency: transitive description: From e2e3e11d5f3f17106bd9a289473c01f851de5888 Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Sun, 7 Apr 2024 23:22:09 +0100 Subject: [PATCH 29/31] feat: fully support v07 useroperations with pimlico --- .../lib/providers/send_crypto_provider.dart | 6 -- example/lib/providers/wallet_provider.dart | 26 ++++++-- example/lib/screens/home/home_screen.dart | 2 +- lib/src/4337/chains.dart | 30 +++++++-- lib/src/4337/paymaster.dart | 64 ++++++++++++------- lib/src/4337/safe.dart | 30 ++++++--- lib/src/4337/userop.dart | 50 +++++++++++++-- lib/src/4337/wallet.dart | 16 ++++- lib/src/abis/safe4337Module.abi.json | 38 +++++++++++ lib/src/abis/safe4337Module.g.dart | 20 +++++- lib/src/common/factories.dart | 6 +- lib/src/common/mixins.dart | 19 +++--- lib/src/common/pack.dart | 58 ++--------------- lib/src/interfaces/paymaster.dart | 5 ++ lib/src/interfaces/user_operations.dart | 14 ++++ 15 files changed, 261 insertions(+), 123 deletions(-) delete mode 100644 example/lib/providers/send_crypto_provider.dart diff --git a/example/lib/providers/send_crypto_provider.dart b/example/lib/providers/send_crypto_provider.dart deleted file mode 100644 index 7d30948..0000000 --- a/example/lib/providers/send_crypto_provider.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:flutter/material.dart'; - -/// - class SendCryptoProvider extends ChangeNotifier { - - } \ No newline at end of file diff --git a/example/lib/providers/wallet_provider.dart b/example/lib/providers/wallet_provider.dart index 709747b..baf881a 100644 --- a/example/lib/providers/wallet_provider.dart +++ b/example/lib/providers/wallet_provider.dart @@ -26,13 +26,19 @@ class WalletProvider extends ChangeNotifier { EthereumAddress.fromHex("0x218F6Bbc32Ef28F547A67c70AbCF8c2ea3b468BA"); WalletProvider() - : _chain = Chains.getChain(Network.baseTestnet) + : _chain = Chain( + chainId: 11155111, + explorer: "https://sepolia.etherscan.io/", + entrypoint: EntryPointAddress.v07) ..accountFactory = EthereumAddress.fromHex( - "0x402A266e92993EbF04a5B3fd6F0e2b21bFC83070") + "0xECA49857B32A12403F5a3A64ad291861EF4B63cb") // v07 p256 factory address + ..jsonRpcUrl = "https://rpc.ankr.com/eth_sepolia" ..bundlerUrl = - "https://api.pimlico.io/v2/84532/rpc?apikey=YOUR_API_KEY" - ..paymasterUrl = "https://paymaster.optimism.io/v1/84532/rpc"; + "https://api.pimlico.io/v2/11155111/rpc?apikey=YOUR_API_KEY" + ..paymasterUrl = + "https://api.pimlico.io/v2/11155111/rpc?apikey=YOUR_API_KEY"; + // "0x402A266e92993EbF04a5B3fd6F0e2b21bFC83070" v06 p256 factory address Future registerWithPassKey(String name, {bool? requiresUserVerification}) async { final pkpSigner = @@ -67,7 +73,7 @@ class WalletProvider extends ChangeNotifier { } Future createEOAWallet() async { - _chain.accountFactory = Constants.simpleAccountFactoryAddress; + _chain.accountFactory = Constants.simpleAccountFactoryAddressv06; final signer = EOAWallet.createWallet(); log("signer: ${signer.getAddress()}"); @@ -87,7 +93,7 @@ class WalletProvider extends ChangeNotifier { } Future createPrivateKeyWallet() async { - _chain.accountFactory = Constants.simpleAccountFactoryAddress; + _chain.accountFactory = Constants.simpleAccountFactoryAddressv06; final signer = PrivateKeySigner.createRandom("123456"); log("signer: ${signer.getAddress()}"); @@ -131,6 +137,14 @@ class WalletProvider extends ChangeNotifier { } Future mintNFt() async { + // pimlico requires us to get the gasfees from their bundler. + // that cannot be built into the sdk so we modify the internal fees manually + if (_chain.entrypoint.version == 0.7) { + _wallet?.gasSettings = GasSettings( + gasMultiplierPercentage: 5, + userDefinedMaxFeePerGas: BigInt.parse("0x524e1909"), + userDefinedMaxPriorityFeePerGas: BigInt.parse("0x52412100")); + } // mints nft final tx1 = await _wallet?.sendTransaction( nft, diff --git a/example/lib/screens/home/home_screen.dart b/example/lib/screens/home/home_screen.dart index 0fabee4..e8b32c9 100644 --- a/example/lib/screens/home/home_screen.dart +++ b/example/lib/screens/home/home_screen.dart @@ -203,7 +203,7 @@ class NFT extends StatelessWidget { style: TextStyle(color: VarianceColors.secondary), ), ]), - Spacer(), + const Spacer(), ElevatedButton( onPressed: () { context.read().mintNFt(); diff --git a/lib/src/4337/chains.dart b/lib/src/4337/chains.dart index 617b7ec..4c26a7f 100644 --- a/lib/src/4337/chains.dart +++ b/lib/src/4337/chains.dart @@ -166,16 +166,22 @@ class Constants { EthereumAddress.fromHex("0x0000000071727De22E5E9d8BAf0edAc6f37da032"); static EthereumAddress zeroAddress = EthereumAddress.fromHex("0x0000000000000000000000000000000000000000"); - static final EthereumAddress simpleAccountFactoryAddress = + static final EthereumAddress simpleAccountFactoryAddressv06 = EthereumAddress.fromHex("0x9406Cc6185a346906296840746125a0E44976454"); + static final EthereumAddress simpleAccountFactoryAddressv07 = + EthereumAddress.fromHex("0x91E60e0613810449d098b0b5Ec8b51A0FE8c8985"); static final EthereumAddress safeProxyFactoryAddress = EthereumAddress.fromHex("0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67"); - static final EthereumAddress safe4337ModuleAddress = + static final EthereumAddress safe4337ModuleAddressv06 = EthereumAddress.fromHex("0xa581c4A4DB7175302464fF3C06380BC3270b4037"); + static final EthereumAddress safe4337ModuleAddressv07 = + EthereumAddress.fromHex("0x75cf11467937ce3F2f357CE24ffc3DBF8fD5c226"); static final EthereumAddress safeSingletonAddress = EthereumAddress.fromHex("0x41675C099F32341bf84BFc5382aF534df5C7461a"); - static final EthereumAddress safeModuleSetupAddress = + static final EthereumAddress safeModuleSetupAddressv06 = EthereumAddress.fromHex("0x8EcD4ec46D4D2a6B64fE960B3D64e8B94B2234eb"); + static final EthereumAddress safeModuleSetupAddressv07 = + EthereumAddress.fromHex("0x2dd68b007B46fBe91B9A7c3EDa5A7a1063cB5b47"); static final EthereumAddress safeMultiSendaddress = EthereumAddress.fromHex("0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526"); @@ -234,7 +240,15 @@ class Safe4337ModuleAddress { /// The address of the Safe4337Module contract for version 0.6. static Safe4337ModuleAddress v06 = Safe4337ModuleAddress( 0.6, - Constants.safe4337ModuleAddress, + Constants.safe4337ModuleAddressv06, + Constants.safeModuleSetupAddressv06, + ); + + /// The address of the Safe4337Module contract for version 0.7. + static Safe4337ModuleAddress v07 = Safe4337ModuleAddress( + 0.7, + Constants.safe4337ModuleAddressv07, + Constants.safeModuleSetupAddressv07, ); /// The version of the Safe4337Module contract. @@ -243,11 +257,15 @@ class Safe4337ModuleAddress { /// The Ethereum address of the Safe4337Module contract. final EthereumAddress address; + /// The address of the SafeModuleSetup contract. + final EthereumAddress setup; + /// Creates a new instance of the [Safe4337ModuleAddress] class. /// /// [version] is the version of the Safe4337Module contract. /// [address] is the Ethereum address of the Safe4337Module contract. - const Safe4337ModuleAddress(this.version, this.address); + /// [setup] is the address of the SafeModuleSetup contract. + const Safe4337ModuleAddress(this.version, this.address, this.setup); /// Creates a new instance of the [Safe4337ModuleAddress] class from a given version. /// @@ -264,6 +282,8 @@ class Safe4337ModuleAddress { switch (version) { case 0.6: return Safe4337ModuleAddress.v06; + case 0.7: + return Safe4337ModuleAddress.v07; default: throw Exception("Unsupported version: $version"); } diff --git a/lib/src/4337/paymaster.dart b/lib/src/4337/paymaster.dart index f5cbe84..38915db 100644 --- a/lib/src/4337/paymaster.dart +++ b/lib/src/4337/paymaster.dart @@ -5,6 +5,12 @@ class Paymaster implements PaymasterBase { final RPCBase _rpc; final Chain _chain; + /// The address of the Paymaster contract. + /// + /// This is an optional parameter and can be left null if the paymaster address + /// is not known or needed. + EthereumAddress? _paymasterAddress; + /// The context data for the Paymaster. /// /// This is an optional parameter and can be used to provide additional context @@ -14,11 +20,12 @@ class Paymaster implements PaymasterBase { /// Creates a new instance of the [Paymaster] class. /// /// [_chain] is the Ethereum chain configuration. + /// [_paymasterAddress] is an optional address of the Paymaster contract. /// [_context] is an optional map containing the context data for the Paymaster. /// /// Throws an [InvalidPaymasterUrl] exception if the paymaster URL in the /// provided chain configuration is not a valid URL. - Paymaster(this._chain, [this._context]) + Paymaster(this._chain, [this._paymasterAddress, this._context]) : assert(_chain.paymasterUrl.isURL(), InvalidPaymasterUrl(_chain.paymasterUrl)), _rpc = RPCBase(_chain.paymasterUrl!); @@ -29,19 +36,27 @@ class Paymaster implements PaymasterBase { } @override - Future intercept(UserOperation operation) async { + set paymasterAddress(EthereumAddress? address) { + _paymasterAddress = address; + } + + @override + Future intercept(UserOperation op) async { + if (_paymasterAddress != null) { + op.paymasterAndData = Uint8List.fromList([ + ..._paymasterAddress!.addressBytes, + ...op.paymasterAndData.sublist(20) + ]); + } final paymasterResponse = await sponsorUserOperation( - operation.toMap(), _chain.entrypoint, _context); + op.toMap(_chain.entrypoint.version), _chain.entrypoint, _context); // Create a new UserOperation with the updated Paymaster data and gas limits - return operation.copyWith( + return op.copyWith( paymasterAndData: paymasterResponse.paymasterAndData, preVerificationGas: paymasterResponse.preVerificationGas, verificationGasLimit: paymasterResponse.verificationGasLimit, callGasLimit: paymasterResponse.callGasLimit, - maxFeePerGas: paymasterResponse.maxFeePerGas ?? operation.maxFeePerGas, - maxPriorityFeePerGas: paymasterResponse.maxPriorityFeePerGas ?? - operation.maxPriorityFeePerGas, ); } @@ -65,30 +80,35 @@ class PaymasterResponse { final BigInt preVerificationGas; final BigInt verificationGasLimit; final BigInt callGasLimit; - final BigInt? maxFeePerGas; - final BigInt? maxPriorityFeePerGas; PaymasterResponse({ required this.paymasterAndData, required this.preVerificationGas, required this.verificationGasLimit, required this.callGasLimit, - this.maxFeePerGas, - this.maxPriorityFeePerGas, }); factory PaymasterResponse.fromMap(Map map) { + final List accountGasLimits = map['accountGasLimits'] != null + ? unpackUints(map['accountGasLimits']) + : [ + BigInt.parse(map['verificationGasLimit']), + BigInt.parse(map['callGasLimit']) + ]; + + final paymasterAndData = map['paymasterAndData'] != null + ? hexToBytes(map['paymasterAndData']) + : Uint8List.fromList([ + ...EthereumAddress.fromHex(map['paymaster']).addressBytes, + ...packUints(BigInt.parse(map['paymasterVerificationGasLimit']), + BigInt.parse(map['paymasterPostOpGasLimit'])), + ...hexToBytes(map["paymasterData"]) + ]); + return PaymasterResponse( - paymasterAndData: hexToBytes(map['paymasterAndData']), - preVerificationGas: BigInt.parse(map['preVerificationGas']), - verificationGasLimit: BigInt.parse(map['verificationGasLimit']), - callGasLimit: BigInt.parse(map['callGasLimit']), - maxFeePerGas: map['maxFeePerGas'] != null - ? BigInt.parse(map['maxFeePerGas']) - : null, - maxPriorityFeePerGas: map['maxPriorityFeePerGas'] != null - ? BigInt.parse(map['maxPriorityFeePerGas']) - : null, - ); + paymasterAndData: paymasterAndData, + preVerificationGas: BigInt.parse(map['preVerificationGas']), + verificationGasLimit: accountGasLimits[0], + callGasLimit: accountGasLimits[1]); } } diff --git a/lib/src/4337/safe.dart b/lib/src/4337/safe.dart index 99e168e..064214e 100644 --- a/lib/src/4337/safe.dart +++ b/lib/src/4337/safe.dart @@ -50,20 +50,34 @@ class _SafePlugin extends Safe4337Module implements Safe4337ModuleBase { /// /// Returns a Future that resolves to the hash of the user operation as a Uint8List. Future getSafeOperationHash( - UserOperation op, BlockInformation blockInfo) async => - getOperationHash([ + UserOperation op, BlockInformation blockInfo) async { + if (self.address == Safe4337ModuleAddress.v07.address) { + return getOperationHash$2([ op.sender, op.nonce, op.initCode, op.callData, - op.callGasLimit, - op.verificationGasLimit, + packUints(op.verificationGasLimit, op.callGasLimit), op.preVerificationGas, - op.maxFeePerGas, - op.maxPriorityFeePerGas, + packUints(op.maxPriorityFeePerGas, op.maxFeePerGas), op.paymasterAndData, hexToBytes(getSafeSignature(op.signature, blockInfo)) ]); + } + return getOperationHash([ + op.sender, + op.nonce, + op.initCode, + op.callData, + op.callGasLimit, + op.verificationGasLimit, + op.preVerificationGas, + op.maxFeePerGas, + op.maxPriorityFeePerGas, + op.paymasterAndData, + hexToBytes(getSafeSignature(op.signature, blockInfo)) + ]); + } Uint8List getSafeMultisendCallData(List recipients, List? amounts, List? innerCalls) { @@ -71,17 +85,13 @@ class _SafePlugin extends Safe4337Module implements Safe4337ModuleBase { for (int i = 0; i < recipients.length; i++) { Uint8List operation = Uint8List.fromList([0]); - assert(operation.length == 1); Uint8List to = recipients[i].addressBytes; - assert(to.length == 20); Uint8List value = amounts != null ? padTo32Bytes(amounts[i].getInWei) : padTo32Bytes(BigInt.zero); - assert(value.length == 32); Uint8List dataLength = innerCalls != null ? padTo32Bytes(BigInt.from(innerCalls[i].length)) : padTo32Bytes(BigInt.zero); - assert(dataLength.length == 32); Uint8List data = innerCalls != null ? innerCalls[i] : Uint8List.fromList([]); Uint8List encodedCall = Uint8List.fromList( diff --git a/lib/src/4337/userop.dart b/lib/src/4337/userop.dart index 1bf2ab8..c650b8b 100644 --- a/lib/src/4337/userop.dart +++ b/lib/src/4337/userop.dart @@ -147,7 +147,7 @@ class UserOperation implements UserOperationBase { @override Uint8List hash(Chain chain) { Uint8List encoded; - if (chain.entrypoint.version >= EntryPointAddress.v07.version) { + if (chain.entrypoint.version == EntryPointAddress.v07.version) { encoded = keccak256(abi.encode([ 'address', 'uint256', @@ -196,13 +196,46 @@ class UserOperation implements UserOperationBase { [encoded, chain.entrypoint.address, BigInt.from(chain.chainId)])); } + /// uses pimlico v07 useroperation standard, which requires only pimlico bundlers + /// this is a training wheel and will revert to which ever schema bundlers accept in concensus + @override + Map packUserOperation() { + Map op = { + 'sender': sender.hexEip55, + 'nonce': '0x${nonce.toRadixString(16)}', + '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, + }; + if (initCode.isNotEmpty) { + op['factory'] = hexlify(initCode.sublist(0, 20)); + op['factoryData'] = hexlify(initCode.sublist(20)); + } + if (paymasterAndData.isNotEmpty) { + op['paymaster'] = hexlify(paymasterAndData.sublist(0, 20)); + final upackedPaymasterGasFields = + unpackUints(hexlify(paymasterAndData.sublist(20, 52))); + op['paymasterVerificationGasLimit'] = + '0x${upackedPaymasterGasFields[0].toRadixString(16)}'; + op['paymasterPostOpGasLimit'] = + '0x${upackedPaymasterGasFields[1].toRadixString(16)}'; + op['paymasterData'] = hexlify(paymasterAndData.sublist(52)); + } + return op; + } + @override String toJson() { return jsonEncode(toMap()); } @override - Map toMap() { + Map toMap([double version = 0.6]) { + if (version == 0.7) return packUserOperation(); return { 'sender': sender.hexEip55, 'nonce': '0x${nonce.toRadixString(16)}', @@ -213,8 +246,8 @@ class UserOperation implements UserOperationBase { 'preVerificationGas': '0x${preVerificationGas.toRadixString(16)}', 'maxFeePerGas': '0x${maxFeePerGas.toRadixString(16)}', 'maxPriorityFeePerGas': '0x${maxPriorityFeePerGas.toRadixString(16)}', - 'signature': signature, 'paymasterAndData': hexlify(paymasterAndData), + 'signature': signature, }; } @@ -282,9 +315,16 @@ class UserOperationGas { this.validUntil, }); factory UserOperationGas.fromMap(Map map) { + final List accountGasLimits = map['accountGasLimits'] != null + ? unpackUints(map['accountGasLimits']) + : [ + BigInt.parse(map['verificationGasLimit']), + BigInt.parse(map['callGasLimit']) + ]; + return UserOperationGas( - callGasLimit: BigInt.parse(map['callGasLimit']), - verificationGasLimit: BigInt.parse(map['verificationGasLimit']), + verificationGasLimit: accountGasLimits[0], + callGasLimit: accountGasLimits[1], preVerificationGas: BigInt.parse(map['preVerificationGas']), validAfter: map['validAfter'] != null ? BigInt.parse(map['validAfter']) : null, diff --git a/lib/src/4337/wallet.dart b/lib/src/4337/wallet.dart index 5f3ad5a..c3787b1 100644 --- a/lib/src/4337/wallet.dart +++ b/lib/src/4337/wallet.dart @@ -137,7 +137,8 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { @override Future sendSignedUserOperation(UserOperation op) => plugin('bundler') - .sendUserOperation(op.toMap(), _chain.entrypoint) + .sendUserOperation( + op.toMap(_chain.entrypoint.version), _chain.entrypoint) .catchError((e) => throw SendError(e.toString(), op)); @override @@ -217,6 +218,14 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { nonce: op.nonce > BigInt.zero ? op.nonce : responses[0].value, initCode: responses[0] > Uint256.zero ? Uint8List(0) : null, signature: dummySignature); + + // require pimlico bundlers for entrypoint v07 + if (_chain.entrypoint.version == 0.7) { + final bundlerHost = Uri.parse(_chain.bundlerUrl!).host; + Logger.conditionalError(!bundlerHost.contains("pimlico"), + "Entrypoint v07 is relatively new. Currently, only compatible with pimlico bundler https://pimlico.io."); + } + return _updateUserOperationGas(op, feePerGas: responses[1]); }); @@ -231,8 +240,9 @@ class SmartWallet with _PluginManager, _GasSettings implements SmartWalletBase { Future _updateUserOperationGas(UserOperation op, {Map? feePerGas}) => plugin('bundler') - .estimateUserOperationGas(op.toMap(), _chain.entrypoint) + .estimateUserOperationGas( + op.toMap(_chain.entrypoint.version), _chain.entrypoint) .then((opGas) => op.updateOpGas(opGas, feePerGas)) - .then((op) => multiply(op)) + .then((op) => applyCustomGasSettings(op)) .catchError((e) => throw GasEstimationError(e.toString(), op)); } diff --git a/lib/src/abis/safe4337Module.abi.json b/lib/src/abis/safe4337Module.abi.json index 45a4f90..41b0a5d 100644 --- a/lib/src/abis/safe4337Module.abi.json +++ b/lib/src/abis/safe4337Module.abi.json @@ -95,5 +95,43 @@ ], "stateMutability": "view", "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "uint256", "name": "nonce", "type": "uint256" }, + { "internalType": "bytes", "name": "initCode", "type": "bytes" }, + { "internalType": "bytes", "name": "callData", "type": "bytes" }, + { + "internalType": "bytes32", + "name": "accountGasLimits", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "preVerificationGas", + "type": "uint256" + }, + { "internalType": "bytes32", "name": "gasFees", "type": "bytes32" }, + { + "internalType": "bytes", + "name": "paymasterAndData", + "type": "bytes" + }, + { "internalType": "bytes", "name": "signature", "type": "bytes" } + ], + "internalType": "struct PackedUserOperation", + "name": "userOp", + "type": "tuple" + } + ], + "name": "getOperationHash", + "outputs": [ + { "internalType": "bytes32", "name": "operationHash", "type": "bytes32" } + ], + "stateMutability": "view", + "type": "function" } ] diff --git a/lib/src/abis/safe4337Module.g.dart b/lib/src/abis/safe4337Module.g.dart index 7df61a6..affc039 100644 --- a/lib/src/abis/safe4337Module.g.dart +++ b/lib/src/abis/safe4337Module.g.dart @@ -5,7 +5,7 @@ import 'package:web3dart/web3dart.dart' as _i1; import 'dart:typed_data' as _i2; final _contractAbi = _i1.ContractAbi.fromJson( - '[{"inputs":[{"internalType":"address","name":"entryPoint","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"SUPPORTED_ENTRYPOINT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"domainSeparator","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"uint8","name":"operation","type":"uint8"}],"name":"executeUserOp","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"uint8","name":"operation","type":"uint8"}],"name":"executeUserOpWithErrorString","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"uint256","name":"callGasLimit","type":"uint256"},{"internalType":"uint256","name":"verificationGasLimit","type":"uint256"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"uint256","name":"maxFeePerGas","type":"uint256"},{"internalType":"uint256","name":"maxPriorityFeePerGas","type":"uint256"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct UserOperation","name":"userOp","type":"tuple"}],"name":"getOperationHash","outputs":[{"internalType":"bytes32","name":"operationHash","type":"bytes32"}],"stateMutability":"view","type":"function"}]', + '[{"inputs":[{"internalType":"address","name":"entryPoint","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"SUPPORTED_ENTRYPOINT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"domainSeparator","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"uint8","name":"operation","type":"uint8"}],"name":"executeUserOp","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"uint8","name":"operation","type":"uint8"}],"name":"executeUserOpWithErrorString","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"uint256","name":"callGasLimit","type":"uint256"},{"internalType":"uint256","name":"verificationGasLimit","type":"uint256"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"uint256","name":"maxFeePerGas","type":"uint256"},{"internalType":"uint256","name":"maxPriorityFeePerGas","type":"uint256"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct UserOperation","name":"userOp","type":"tuple"}],"name":"getOperationHash","outputs":[{"internalType":"bytes32","name":"operationHash","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"bytes32","name":"accountGasLimits","type":"bytes32"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"bytes32","name":"gasFees","type":"bytes32"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct PackedUserOperation","name":"userOp","type":"tuple"}],"name":"getOperationHash","outputs":[{"internalType":"bytes32","name":"operationHash","type":"bytes32"}],"stateMutability":"view","type":"function"}]', 'Safe4337Module', ); @@ -125,4 +125,22 @@ class Safe4337Module extends _i1.GeneratedContract { ); return (response[0] as _i2.Uint8List); } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future<_i2.Uint8List> getOperationHash$2( + dynamic userOp, { + _i1.BlockNum? atBlock, + }) async { + final function = self.abi.functions[6]; + assert(checkSignature(function, 'bbe5dc4f')); + final params = [userOp]; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as _i2.Uint8List); + } } diff --git a/lib/src/common/factories.dart b/lib/src/common/factories.dart index bce3614..ced8352 100644 --- a/lib/src/common/factories.dart +++ b/lib/src/common/factories.dart @@ -47,9 +47,9 @@ class _SafeProxyFactory extends SafeProxyFactory "setup", Constants.safeSingletonAddress, ContractAbis.get("setup"), [ owners.toList(), BigInt.from(threshold), - Constants.safeModuleSetupAddress, - Contract.encodeFunctionCall("enableModules", - Constants.safeModuleSetupAddress, ContractAbis.get("enableModules"), [ + module.setup, + Contract.encodeFunctionCall( + "enableModules", module.setup, ContractAbis.get("enableModules"), [ [module.address] ]), module.address, diff --git a/lib/src/common/mixins.dart b/lib/src/common/mixins.dart index a8414ce..a7e833c 100644 --- a/lib/src/common/mixins.dart +++ b/lib/src/common/mixins.dart @@ -7,7 +7,7 @@ class GasSettings { /// The percentage by which the gas limits should be multiplied. /// /// This value should be between 0 and 100. - Percent? gasMultiplierPercentage; + Percent gasMultiplierPercentage; /// The user-defined maximum fee per gas for the transaction. BigInt? userDefinedMaxFeePerGas; @@ -29,7 +29,7 @@ class GasSettings { this.gasMultiplierPercentage = 0, this.userDefinedMaxFeePerGas, this.userDefinedMaxPriorityFeePerGas, - }) : assert(gasMultiplierPercentage! >= 0, + }) : assert(gasMultiplierPercentage >= 0, RangeOutOfBounds("Wrong Gas multiplier percentage", 0, 100)); } @@ -41,21 +41,22 @@ mixin _GasSettings { /// Sets the gas settings for user operations. /// /// [gasParams] is an instance of the [GasSettings] class containing the gas settings. - set setGasParams(GasSettings gasParams) => _gasParams = gasParams; + set gasSettings(GasSettings gasParams) => _gasParams = gasParams; /// Applies the gas settings to a user operation, by multiplying the gas limits by a certain percentage. /// /// [op] is the user operation to which the gas settings should be applied. /// /// Returns a new [UserOperation] object with the updated gas settings. - UserOperation multiply(UserOperation op) { - final multiplier = - BigInt.from(_gasParams.gasMultiplierPercentage! / 100 + 1); + UserOperation applyCustomGasSettings(UserOperation op) { + final multiplier = _gasParams.gasMultiplierPercentage / 100 + 1; return op.copyWith( - callGasLimit: op.callGasLimit * multiplier, - verificationGasLimit: op.verificationGasLimit * multiplier, - preVerificationGas: op.preVerificationGas * multiplier, + callGasLimit: BigInt.from(op.callGasLimit.toDouble() * multiplier), + verificationGasLimit: + BigInt.from(op.verificationGasLimit.toDouble() * multiplier), + preVerificationGas: + BigInt.from(op.preVerificationGas.toDouble() * multiplier), maxFeePerGas: _gasParams.userDefinedMaxFeePerGas, maxPriorityFeePerGas: _gasParams.userDefinedMaxPriorityFeePerGas); } diff --git a/lib/src/common/pack.dart b/lib/src/common/pack.dart index eef4813..cec7cab 100644 --- a/lib/src/common/pack.dart +++ b/lib/src/common/pack.dart @@ -16,9 +16,8 @@ part of '../../variance_dart.dart'; /// print(packedBytes); /// ``` Uint8List packUints(BigInt high128, BigInt low128) { - final shiftedHigh = high128 << 128; - final combined = shiftedHigh + low128; - return hexToBytes(combined.toRadixString(16).padLeft(64, '0')); + final packed = (high128 << 128) + low128; + return hexToBytes('0x${packed.toRadixString(16).padLeft(64, '0')}'); } /// Unpacks two 128-bit unsigned integers from a 32-byte array. @@ -30,55 +29,10 @@ Uint8List packUints(BigInt high128, BigInt low128) { /// /// Example: /// ```dart -/// final bytes = Uint8List.fromList([0x01, 0x02, 0x03, 0x04]); -/// final unpacked = unpackUints(bytes); +/// final unpacked = unpackUints("0x...32byteshex"); /// print(unpacked); /// ``` -List unpackUints(Uint8List bytes) { - final hex = bytesToHex(bytes); - final value = BigInt.parse(hex, radix: 16); - final mask = - BigInt.from(0xFFFFFFFFFFFFFFFF) << 64 | BigInt.from(0xFFFFFFFFFFFFFFFF); - return [value >> 128, value & mask]; -} - -/// Packs a [UserOperation] into a PackedUserOperation map for EntryPoint v0.7 and above. -/// -/// Parameters: -/// - [userOp]: The [UserOperation] to pack. -/// -/// Returns a [Map] containing the packed user operation. -/// -/// Example: -/// ```dart -/// final userOp = UserOperation( -/// sender: EthereumAddress.fromHex('0x1234567890123456789012345678901234567890'), -/// nonce: BigInt.from(1), -/// initCode: Uint8List(0), -/// callData: Uint8List(0), -/// callGasLimit: BigInt.from(2), -/// verificationGasLimit: BigInt.from(3), -/// preVerificationGas: BigInt.from(4), -/// maxFeePerGas: BigInt.from(5), -/// maxPriorityFeePerGas: BigInt.from(6), -/// signature: '0x1234567890123456789012345678901234567890', -/// paymasterAndData: Uint8List(0), -/// ); -/// final packedUserOp = packUserOperation(userOp); -/// print(packedUserOp); -/// ``` -Map packUserOperation(UserOperation userOp) { - return { - 'sender': userOp.sender.hex, - 'nonce': '0x${userOp.nonce.toRadixString(16)}', - 'initCode': hexlify(userOp.initCode), - 'callData': hexlify(userOp.callData), - 'accountGasLimits': - hexlify(packUints(userOp.verificationGasLimit, userOp.callGasLimit)), - 'preVerificationGas': '0x${userOp.preVerificationGas.toRadixString(16)}', - 'gasFees': - hexlify(packUints(userOp.maxPriorityFeePerGas, userOp.maxFeePerGas)), - 'signature': userOp.signature, - 'paymasterAndData': hexlify(userOp.paymasterAndData), - }; +List unpackUints(Hex hex) { + final value = BigInt.parse(hex.substring(2), radix: 16); + return [value >> 128, value.toUnsigned(128)]; } diff --git a/lib/src/interfaces/paymaster.dart b/lib/src/interfaces/paymaster.dart index edf6341..c2fffd3 100644 --- a/lib/src/interfaces/paymaster.dart +++ b/lib/src/interfaces/paymaster.dart @@ -6,6 +6,11 @@ abstract class PaymasterBase { /// [context] is a map containing the context data to be set. set context(Map? context); + /// Sets the address of the Paymaster. + /// + /// [address] is the address of the Paymaster. + set paymasterAddress(EthereumAddress? address); + /// Intercepts a [UserOperation] and sponsors it with the Paymaster. /// /// [operation] is the [UserOperation] to be sponsored. diff --git a/lib/src/interfaces/user_operations.dart b/lib/src/interfaces/user_operations.dart index baaa939..f205328 100644 --- a/lib/src/interfaces/user_operations.dart +++ b/lib/src/interfaces/user_operations.dart @@ -45,6 +45,20 @@ abstract class UserOperationBase { /// Returns a [Uint8List] representing the hashed user operation. Uint8List hash(Chain chain); + /// Packs a [UserOperation] into a PackedUserOperation map for EntryPoint v0.7 and above. + /// + /// Parameters: + /// - [userOp]: The [UserOperation] to pack. + /// + /// Returns a [Map] containing the packed user operation. + /// + /// Example: + /// ```dart + /// final packedUserOp = op.packUserOperation(); + /// print(packedUserOp); + /// ``` + Map packUserOperation(); + /// Converts the user operation to a JSON-encoded string. String toJson(); From fcf9051426ea9842b3457b3637d4a61511ed57e8 Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Sun, 7 Apr 2024 23:35:24 +0100 Subject: [PATCH 30/31] bump SDK version --- CHANGELOG.md | 7 +++++++ example/pubspec.lock | 50 ++++++++------------------------------------ pubspec.yaml | 2 +- 3 files changed, 17 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61506b8..e05badc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.1.0 + +* Training wheel support for entrypoint v0.7.0 via pimlico bundler +* Fix issue with gas multiplier +* Added safe4337module abi for v0.7.0 +* Add support for custom paymaster address + ## 0.1.0-r3 * Add mutisend address to constants diff --git a/example/pubspec.lock b/example/pubspec.lock index 1e27152..eedf18d 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -312,30 +312,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" - url: "https://pub.dev" - source: hosted - version: "10.0.0" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 - url: "https://pub.dev" - source: hosted - version: "2.0.1" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 - url: "https://pub.dev" - source: hosted - version: "2.0.1" lints: dependency: transitive description: @@ -348,26 +324,26 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.5.0" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.10.0" nested: dependency: transitive description: @@ -428,10 +404,10 @@ packages: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.8.3" path_provider: dependency: "direct main" description: @@ -707,7 +683,7 @@ packages: path: ".." relative: true source: path - version: "0.1.0-r3" + version: "0.1.0" vector_math: dependency: transitive description: @@ -716,14 +692,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 - url: "https://pub.dev" - source: hosted - version: "13.0.0" wallet: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f16439e..e584970 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.1.0-r3 +version: 0.1.0 documentation: https://docs.variance.space homepage: https://variance.space repository: https://github.com/vaariance/variance-dart From 95137400fd82e773818a7102736ed0cab53cf6e7 Mon Sep 17 00:00:00 2001 From: Peter Anyaogu Date: Mon, 8 Apr 2024 15:33:24 +0100 Subject: [PATCH 31/31] update readme --- README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.md b/README.md index 36aca10..da60629 100644 --- a/README.md +++ b/README.md @@ -118,14 +118,6 @@ final Smartwallet wallet = await smartWalletFactory print("safe wallet address: ${wallet.address.hex}"); ``` -Additionally, you can pass in multisig `owners` and the `threshold` to the `createSafeAccount` method. - -```dart -final Smartwallet wallet = await smartWalletFactory - .createSafeAccount(salt, [...extraOwners], threshold); -print("safe wallet address: ${wallet.address.hex}"); -``` - > Safe SecP256r1 signers can not be used with this SDK yet. ### Interacting with the Smart Wallet