From 94e0c94a5a2f9b4d5baa179f60ce404427a3af8e Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Mon, 13 Sep 2021 16:37:27 +0700 Subject: [PATCH 01/30] Add chain enum and extension for information --- lib/src/utils/chains.dart | 49 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 lib/src/utils/chains.dart diff --git a/lib/src/utils/chains.dart b/lib/src/utils/chains.dart new file mode 100644 index 0000000..77fab32 --- /dev/null +++ b/lib/src/utils/chains.dart @@ -0,0 +1,49 @@ +enum Chains { + Mainnet, + Kovan, + Rinkeby, + Gorli, + Ropsten, + XDai, + Polygon, + Mumbai, + BSCMainnet, + BSCTestnet, +} + +extension ChainExtension on Chains { + static const _info = { + Chains.Mainnet: { + 'multicall': '0xeefba1e63905ef1d7acba5a8513c70307c1ce441', + }, + Chains.Gorli: { + 'multicall': '0x77dca2c955b15e9de4dbbcf1246b4b85b651e50e', + }, + Chains.Ropsten: { + 'multicall': '0x53c43764255c17bd724f74c4ef150724ac50a3ed', + }, + Chains.Kovan: { + 'multicall': '0x2cc8688c5f75e365aaeeb4ea8d6a480405a48d2a', + }, + Chains.Rinkeby: { + 'multicall': '0x42ad527de7d4e9d9d011ac45b31d8551f8fe9821', + }, + Chains.XDai: { + 'multicall': '0xb5b692a88bdfc81ca69dcb1d924f59f0413a602a', + }, + Chains.Polygon: { + 'multicall': '0x11ce4B23bD875D7F5C6a31084f55fDe1e9A87507', + }, + Chains.Mumbai: { + 'multicall': '0x08411ADd0b5AA8ee47563b146743C13b3556c9Cc', + }, + Chains.BSCMainnet: { + 'multicall': '0x41263cba59eb80dc200f3e2544eda4ed6a90e76c', + }, + Chains.BSCTestnet: { + 'multicall': '0xae11C5B5f29A6a25e955F0CB8ddCc416f522AF5C', + }, + }; + + String? get multicallAddress => _info[this]!['multicall']; +} From 77da3e340173200c80ed0d603912d57db805485a Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Mon, 13 Sep 2021 16:38:13 +0700 Subject: [PATCH 02/30] Add makerdao's multicall support --- lib/src/utils/multicall.dart | 59 ++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 lib/src/utils/multicall.dart diff --git a/lib/src/utils/multicall.dart b/lib/src/utils/multicall.dart new file mode 100644 index 0000000..660f983 --- /dev/null +++ b/lib/src/utils/multicall.dart @@ -0,0 +1,59 @@ +import '../ethers/ethers.dart'; +import 'chains.dart'; + +class Multicall { + static const abi = [ + 'function aggregate((address, bytes)[]) view returns (uint256, bytes[])', + ]; + + Contract contract; + + Multicall(String multicallAddress, dynamic providerOrSigner) + : assert(providerOrSigner != null, 'providerOrSigner should not be null'), + assert(multicallAddress.isNotEmpty, 'address should not be empty'), + assert(EthUtils.isAddress(multicallAddress), + 'address should be in address format'), + contract = Contract(multicallAddress, abi, providerOrSigner); + + factory Multicall.fromChain(Chains chain, providerOrSigner) { + assert(chain.multicallAddress != null, + 'Multicall not supported on this chain'); + return Multicall(chain.multicallAddress!, providerOrSigner); + } + + Future aggregate(List payload) async { + assert(payload.isNotEmpty, 'payload should not be empty'); + + final res = await contract.call('aggregate', [ + payload.map((e) => e.serialize()).toList(), + ]); + return MulticallResult( + int.parse(res[0].toString()), (res[1] as List).cast()); + } +} + +class MulticallPayload { + final String address; + final String data; + + const MulticallPayload(this.address, this.data); + + List serialize() => [address, data]; + + @override + String toString() { + return 'MulticallPayload: to $address with data $data'; + } +} + +class MulticallResult { + final int blockNumber; + final List returnData; + + const MulticallResult(this.blockNumber, this.returnData); + + @override + String toString() { + return 'MulticallResult: at block $blockNumber total ${returnData.length} item, ${returnData.take(3)}...'; + } +} From bc1677898136e767f80553ed8d5e3d13c621d728 Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Mon, 13 Sep 2021 16:40:11 +0700 Subject: [PATCH 03/30] Move ERC20 Contract to utils and add option use to multicall --- lib/ethers.dart | 1 - .../{ethers/utils.dart => utils/erc20.dart} | 54 +++++++++++++++---- 2 files changed, 44 insertions(+), 11 deletions(-) rename lib/src/{ethers/utils.dart => utils/erc20.dart} (78%) diff --git a/lib/ethers.dart b/lib/ethers.dart index 0549e18..f99ba3c 100644 --- a/lib/ethers.dart +++ b/lib/ethers.dart @@ -1,4 +1,3 @@ export 'src/ethers/constant.dart'; export 'src/ethers/ethers.dart'; export 'src/ethers/exception.dart'; -export 'src/ethers/utils.dart'; diff --git a/lib/src/ethers/utils.dart b/lib/src/utils/erc20.dart similarity index 78% rename from lib/src/ethers/utils.dart rename to lib/src/utils/erc20.dart index 888e664..15e4fc6 100644 --- a/lib/src/ethers/utils.dart +++ b/lib/src/utils/erc20.dart @@ -1,6 +1,7 @@ import 'dart:async'; -import 'ethers.dart'; +import '../ethers/ethers.dart'; +import 'multicall.dart'; /// Dart Class for ERC20 Contract, A standard API for tokens within smart contracts. /// @@ -106,23 +107,56 @@ class ContractERC20 { contract = contract.connect(providerOrSigner); } - /// Multicall of [allowance], may not be in the same block. + /// Multicall of [allowance], may not be in the same block unless [multicall] is provided. Future> multicallAllowance( - List owners, List spenders) async { + List owners, + List spenders, [ + Multicall? multicall, + ]) async { assert(owners.isNotEmpty, 'Owner list empty'); assert(spenders.isNotEmpty, 'Spender list empty'); assert(owners.length == spenders.length, 'Owner list length must be same as spender'); - return Future.wait(Iterable.generate(owners.length).map( - (e) => allowance(owners[e], spenders[e]), - )); + if (multicall != null) { + final res = + await multicall.aggregate(Iterable.generate(owners.length).map( + (e) { + final functionSig = contract.interface.getSighash('allowance'); + final argData = abiCoder.encode( + ['address', 'address'], [owners[e], spenders[e]]).substring(2); + return MulticallPayload(contract.address, functionSig + argData); + }, + ).toList()); + return res.returnData.map((e) => BigInt.parse(e)).toList(); + } else { + return Future.wait(Iterable.generate(owners.length).map( + (e) => allowance(owners[e], spenders[e]), + )); + } } - /// Multicall of [balanceOf], may not be in the same block. - Future> multicallBalanceOf(List addresses) async { + /// Multicall of [balanceOf], may not be in the same block unless [multicall] is provided. + Future> multicallBalanceOf( + List addresses, [ + Multicall? multicall, + ]) async { assert(addresses.isNotEmpty, 'address should not be empty'); - return Future.wait(Iterable.generate(addresses.length) - .map((e) => balanceOf(addresses[e]))); + + if (multicall != null) { + final res = await multicall + .aggregate(Iterable.generate(addresses.length).map( + (e) { + final functionSig = contract.interface.getSighash('balanceOf'); + final argData = + abiCoder.encode(['address'], [addresses[e]]).substring(2); + return MulticallPayload(contract.address, functionSig + argData); + }, + ).toList()); + return res.returnData.map((e) => BigInt.parse(e)).toList(); + } else { + return Future.wait(Iterable.generate(addresses.length) + .map((e) => balanceOf(addresses[e]))); + } } /// Emitted when the allowance of a `spender` for an `owner` is set by a call to `approve`. From ebd4a1d2a0a471149caec8c10e4e7594aafa8550 Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Mon, 13 Sep 2021 16:41:14 +0700 Subject: [PATCH 04/30] Add and seperate utils library --- lib/flutter_web3.dart | 1 + lib/utils.dart | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 lib/utils.dart diff --git a/lib/flutter_web3.dart b/lib/flutter_web3.dart index 921c3ab..09d49a0 100644 --- a/lib/flutter_web3.dart +++ b/lib/flutter_web3.dart @@ -1,4 +1,5 @@ export './ethereum.dart'; export './ethers.dart'; export './src/constant.dart'; +export './utils.dart'; export './wallet_connect.dart'; diff --git a/lib/utils.dart b/lib/utils.dart new file mode 100644 index 0000000..23fd597 --- /dev/null +++ b/lib/utils.dart @@ -0,0 +1,3 @@ +export 'src/utils/chains.dart'; +export 'src/utils/erc20.dart'; +export 'src/utils/multicall.dart'; From 9f3b80a2e3da6e2f1bad9d6705c677df331b4392 Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Mon, 13 Sep 2021 17:51:27 +0700 Subject: [PATCH 05/30] Add Fragment and ParamType --- lib/src/ethers/interface.dart | 94 +++++++++++++++++++++++++++++++++-- lib/src/ethers/interop.dart | 42 +++++++++++++++- 2 files changed, 131 insertions(+), 5 deletions(-) diff --git a/lib/src/ethers/interface.dart b/lib/src/ethers/interface.dart index 72fa499..aad1891 100644 --- a/lib/src/ethers/interface.dart +++ b/lib/src/ethers/interface.dart @@ -35,6 +35,37 @@ enum FormatTypes { /// ] /// ``` full, + + /// '0x70a08231' + sighash, +} + +/// An ABI is a collection of Fragments. +class Fragment extends Interop<_FragmentImpl> { + factory Fragment.from(String source) => + Fragment._(_FragmentImpl.from(source)); + + const Fragment._(_FragmentImpl impl) : super.internal(impl); + + /// This is the name of the Event or Function. This will be `null` for a `ConstructorFragment`. + String? get name => impl.name; + + /// This is an array of each [ParamType] for the input parameters to the Constructor, Event of Function. + List get paramType => + impl.inputs.cast<_ParamTypeImpl>().map((e) => ParamType._(e)).toList(); + + /// This is a [String] which indicates the type of the [Fragment]. This will be one of: + /// - constructor + /// - event + /// - function + String get type => impl.type; + + /// Creates a [String] representation of the [Fragment] using the available [type] formats. + String format([FormatTypes? type]) => + type != null ? impl.format(type.impl) : impl.format(); + + @override + String toString() => 'Fragment: ${format()}'; } /// The Interface Class abstracts the encoding and decoding required to interact with contracts on the Ethereum network. @@ -53,15 +84,19 @@ class Interface extends Interop<_InterfaceImpl> { return Interface._(_InterfaceImpl(abi)); } - Interface._(_InterfaceImpl impl) : super.internal(impl); + const Interface._(_InterfaceImpl impl) : super.internal(impl); + + /// All the [Fragment] in the interface. + List get fragments => + impl.fragments.cast<_FragmentImpl>().map((e) => Fragment._(e)).toList(); /// Return the formatted [Interface]. /// /// [types] must be from [FormatTypes] variable. /// /// If the format type is json a single string is returned, otherwise an Array of the human-readable strings is returned. - dynamic format([FormatTypes? types]) => - types != null ? impl.format(types.impl) : impl.format(); + dynamic format([FormatTypes? type]) => + type != null ? impl.format(type.impl) : impl.format(); /// Format into [FormatTypes.full]. /// @@ -135,10 +170,61 @@ class Interface extends Interop<_InterfaceImpl> { /// ``` String getSighash(String function) => impl.getSighash(function); + /// Return the sighash (or Function Selector) for [fragment]. + /// + /// --- + /// + /// ```dart + /// iface.getSighash(iface.fragments.first); + /// // '0x70a08231' + /// ``` + String getSighashByFragment(Fragment fragment) => + impl.getSighash(fragment.impl); + @override String toString() => 'Interface: ${format(FormatTypes.minimal)}'; } +/// A representation of a solidity parameter. +class ParamType extends Interop<_ParamTypeImpl> { + factory ParamType.from(String source) => + ParamType._(_ParamTypeImpl.from(source)); + + const ParamType._(_ParamTypeImpl impl) : super.internal(impl); + + /// The type of children of the array. This is `null` for any parameter which is not an array. + ParamType? get arrayChildren => + impl.arrayChildren == null ? null : ParamType._(impl.arrayChildren!); + + /// The length of the array, or -1 for dynamic-length arrays. This is `null` for parameters which are not arrays. + int? get arrayLength => impl.arrayLength; + + /// The base type of the parameter. For primitive types (e.g. `address`, `uint256`, etc) this is equal to type. For arrays, it will be the string array and for a tuple, it will be the string tuple. + String get baseType => impl.baseType; + + ///The components of a tuple. This is `null` for non-tuple parameters. + List? get components => impl.components + ?.cast<_ParamTypeImpl>() + .map((e) => ParamType._(e)) + .toList(); + + /// Whether the parameter has been marked as indexed. This only applies to parameters which are part of an EventFragment. + bool get indexed => impl.indexed; + + /// The local parameter name. This may be null for unnamed parameters. For example, the parameter definition `string foobar` would be `foobar`. + String? get name => impl.name; + + /// The full type of the parameter, including tuple and array symbols. This may be `null` for unnamed parameters. For the above example, this would be `foobar`. + String? get type => impl.type; + + /// Creates a [String] representation of the [Fragment] using the available [type] formats. + String format([FormatTypes? type]) => + type != null ? impl.format(type.impl) : impl.format(); + + @override + String toString() => 'ParamType: ${format()}'; +} + extension _FormatTypesExtImpl on FormatTypes { dynamic get impl { switch (this) { @@ -148,6 +234,8 @@ extension _FormatTypesExtImpl on FormatTypes { return _FormatTypesImpl.minimal; case FormatTypes.full: return _FormatTypesImpl.full; + case FormatTypes.sighash: + return _FormatTypesImpl.sighash; } } } diff --git a/lib/src/ethers/interop.dart b/lib/src/ethers/interop.dart index 80da472..aa94674 100644 --- a/lib/src/ethers/interop.dart +++ b/lib/src/ethers/interop.dart @@ -106,17 +106,34 @@ class _FormatTypesImpl { external static dynamic full; external static dynamic minimal; + + external static dynamic sighash; +} + +@JS('utils.Fragment') +class _FragmentImpl { + external List<_ParamTypeImpl> get inputs; + + external String? get name; + + external String get type; + + external String format([dynamic types]); + + external static _FragmentImpl from(String source); } @JS("utils.Interface") class _InterfaceImpl { external _InterfaceImpl(dynamic abi); - external dynamic format([dynamic types]); + external List<_FragmentImpl> get fragments; - external String getSighash(String function); + external dynamic format([dynamic types]); external String getEventTopic(String event); + + external String getSighash(dynamic function); } @JS("providers.JsonRpcProvider") @@ -158,6 +175,27 @@ class _NetworkImpl { external String get name; } +@JS('utils.ParamType') +class _ParamTypeImpl { + external _ParamTypeImpl? get arrayChildren; + + external int? get arrayLength; + + external String get baseType; + + external List<_ParamTypeImpl>? get components; + + external bool get indexed; + + external String? get name; + + external String? get type; + + external String format([dynamic types]); + + external static _ParamTypeImpl from(String source); +} + @JS("providers") class _ProviderImpl {} From 3889896cbdb1812623ae9348f5041dec0c0ad286 Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Tue, 14 Sep 2021 00:16:01 +0700 Subject: [PATCH 06/30] Add encode/decode data from interface function --- lib/src/ethers/interface.dart | 152 +++++++++++++++++++++++++++++----- lib/src/ethers/interop.dart | 6 ++ 2 files changed, 138 insertions(+), 20 deletions(-) diff --git a/lib/src/ethers/interface.dart b/lib/src/ethers/interface.dart index aad1891..7824cf9 100644 --- a/lib/src/ethers/interface.dart +++ b/lib/src/ethers/interface.dart @@ -6,15 +6,15 @@ enum FormatTypes { /// ''' /// [ /// { - /// "type": "function", - /// "name": "balanceOf", - /// "constant":true, - /// "stateMutability": "view", - /// "payable":false, "inputs": [ - /// { "type": "address", "name": "owner"} + /// 'type': 'function', + /// 'name': 'balanceOf', + /// 'constant':true, + /// 'stateMutability': 'view', + /// 'payable':false, 'inputs': [ + /// { 'type': 'address', 'name': 'owner'} /// ], - /// "outputs": [ - /// { "type": "uint256", "name": "balance"} + /// 'outputs': [ + /// { 'type': 'uint256', 'name': 'balance'} /// ] /// }, /// ] @@ -90,6 +90,118 @@ class Interface extends Interop<_InterfaceImpl> { List get fragments => impl.fragments.cast<_FragmentImpl>().map((e) => Fragment._(e)).toList(); + /// Returns the decoded values from the result of a call for [function] (see Specifying Fragments) for the given [data]. + /// + /// --- + /// + /// ```dart + /// // Decoding result data (e.g. from an eth_call) + /// resultData = '0x0000000000000000000000000000000000000000000000000de0b6b3a7640000'; + /// iface.decodeFunctionResult('balanceOf', resultData); + /// // [1000000000000000000] + /// ``` + List decodeFunctionResult(String function, String data) => + impl.decodeFunctionResult(function, data); + + /// Returns the decoded values from the result of a call for [function] (see Specifying Fragments) for the given [data]. + /// + /// --- + /// + /// ```dart + /// // Decoding result data (e.g. from an eth_call) + /// resultData = '0x0000000000000000000000000000000000000000000000000de0b6b3a7640000'; + /// iface.decodeFunctionResult(iface.fragments.first, resultData); + /// // [1000000000000000000] + /// ``` + List decodeFunctionResultFromFragment( + Fragment function, String data) => + impl.decodeFunctionResult(function.impl, data); + + /// Returns the encoded [topic] filter, which can be passed to getLogs for fragment (see Specifying Fragments) for the given [values]. + /// + /// Each topic is a 32 byte (64 nibble) `DataHexString`. + /// + /// --- + /// + /// ```dart + /// // Filter that matches all Transfer events + /// iface.encodeFilterTopics('Transfer', []); + /// // [ + /// // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' + /// // ] + /// + /// // Filter that matches the sender + /// iface.encodeFilterTopics('Transfer', [ + /// '0x8ba1f109551bD432803012645Ac136ddd64DBA72' + /// ]); + /// // [ + /// // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + /// // '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72' + /// // ] + /// ``` + List encodeFilterTopics(String topic, + [List values = const []]) => + impl.encodeFilterTopics(topic, values); + + /// Returns the encoded [topic] filter, which can be passed to getLogs for fragment (see Specifying Fragments) for the given [values]. + /// + /// Each topic is a 32 byte (64 nibble) `DataHexString`. + /// + /// --- + /// + /// ```dart + /// // Filter that matches all Transfer events + /// iface.encodeFilterTopics(iface.fragments.first, []); + /// // [ + /// // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' + /// // ] + /// + /// // Filter that matches the sender + /// iface.encodeFilterTopics(iface.fragments.first, [ + /// '0x8ba1f109551bD432803012645Ac136ddd64DBA72' + /// ]); + /// // [ + /// // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + /// // '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72' + /// // ] + /// ``` + List encodeFilterTopicsFromFragment(Fragment topic, + [List values = const []]) => + impl.encodeFilterTopics(topic.impl, values); + + /// Returns the encoded data, which can be used as the data for a transaction for [function] (see Specifying Fragments) for the given [values]. + /// + /// --- + /// + /// ```dart + /// // Encoding data for the tx.data of a call or transaction + /// iface.encodeFunctionData('transferFrom', [ + /// '0x8ba1f109551bD432803012645Ac136ddd64DBA72', + /// '0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C', + /// '1' + /// ]); + /// // '0x23b872dd0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72000000000000000000000000ab7c8803962c0f2f5bbbe3fa8bf41cd82aa1923c0000000000000000000000000000000000000000000000000de0b6b3a7640000' + /// ``` + String encodeFunctionData(String function, [List? values]) => + impl.encodeFunctionData(function, values); + + /// Returns the encoded data, which can be used as the data for a transaction for [function] (see Specifying Fragments) for the given [values]. + /// + /// --- + /// + /// ```dart + /// // Encoding data for the tx.data of a call or transaction + /// iface.encodeFunctionData(iface.fragments.first, [ + /// '0x8ba1f109551bD432803012645Ac136ddd64DBA72', + /// '0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C', + /// '1' + /// ]); + /// // '0x23b872dd0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72000000000000000000000000ab7c8803962c0f2f5bbbe3fa8bf41cd82aa1923c0000000000000000000000000000000000000000000000000de0b6b3a7640000' + /// ``` + String encodeFunctionDataFromFragment(Fragment function, + [List? values]) => + impl.encodeFunctionData(function.impl, values); + /// Return the formatted [Interface]. /// /// [types] must be from [FormatTypes] variable. @@ -117,15 +229,15 @@ class Interface extends Interop<_InterfaceImpl> { /// ''' /// [ /// { - /// "type": "function", - /// "name": "balanceOf", - /// "constant":true, - /// "stateMutability": "view", - /// "payable":false, "inputs": [ - /// { "type": "address", "name": "owner"} + /// 'type': 'function', + /// 'name': 'balanceOf', + /// 'constant':true, + /// 'stateMutability': 'view', + /// 'payable':false, 'inputs': [ + /// { 'type': 'address', 'name': 'owner'} /// ], - /// "outputs": [ - /// { "type": "uint256"} + /// 'outputs': [ + /// { 'type': 'uint256'} /// ] /// }, /// ] @@ -149,10 +261,10 @@ class Interface extends Interop<_InterfaceImpl> { /// --- /// /// ```dart - /// iface.getEventTopic("Transfer"); + /// iface.getEventTopic('Transfer'); /// // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' /// - /// iface.getEventTopic("Transfer(address, address, uint)"); + /// iface.getEventTopic('Transfer(address, address, uint)'); /// // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' /// ``` String getEventTopic(String event) => impl.getEventTopic(event); @@ -162,10 +274,10 @@ class Interface extends Interop<_InterfaceImpl> { /// --- /// /// ```dart - /// iface.getSighash("balanceOf"); + /// iface.getSighash('balanceOf'); /// // '0x70a08231' /// - /// iface.getSighash("balanceOf(address)"); + /// iface.getSighash('balanceOf(address)'); /// // '0x70a08231' /// ``` String getSighash(String function) => impl.getSighash(function); diff --git a/lib/src/ethers/interop.dart b/lib/src/ethers/interop.dart index aa94674..f5a55b1 100644 --- a/lib/src/ethers/interop.dart +++ b/lib/src/ethers/interop.dart @@ -134,6 +134,12 @@ class _InterfaceImpl { external String getEventTopic(String event); external String getSighash(dynamic function); + + external List encodeFilterTopics(dynamic fragment, List values); + + external String encodeFunctionData(dynamic fragment, [List? values]); + + external List decodeFunctionResult(dynamic fragment, String data); } @JS("providers.JsonRpcProvider") From d26ed74b872dfaccd118b31997c623230fea5a89 Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Tue, 14 Sep 2021 00:27:20 +0700 Subject: [PATCH 07/30] Add more MulticallPayload factory constructor --- lib/src/utils/multicall.dart | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/src/utils/multicall.dart b/lib/src/utils/multicall.dart index 660f983..a088f90 100644 --- a/lib/src/utils/multicall.dart +++ b/lib/src/utils/multicall.dart @@ -38,6 +38,21 @@ class MulticallPayload { const MulticallPayload(this.address, this.data); + factory MulticallPayload.fromFunctionAbi(String address, String functionAbi, + [List? args]) { + final interface = Interface([functionAbi]); + final data = interface.encodeFunctionDataFromFragment( + interface.fragments.first, args); + return MulticallPayload(address, data); + } + + factory MulticallPayload.fromInterfaceFunction( + String address, Interface interface, String function, + [List? args]) { + final data = interface.encodeFunctionData(function, args); + return MulticallPayload(address, data); + } + List serialize() => [address, data]; @override From 444664d628294d268a12cb0b75202b8b30589e31 Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Tue, 14 Sep 2021 13:50:02 +0700 Subject: [PATCH 08/30] Add chain information --- lib/src/utils/chains.dart | 125 +++++++++++++++++++++++++++++++++++--- 1 file changed, 115 insertions(+), 10 deletions(-) diff --git a/lib/src/utils/chains.dart b/lib/src/utils/chains.dart index 77fab32..df3b88c 100644 --- a/lib/src/utils/chains.dart +++ b/lib/src/utils/chains.dart @@ -1,9 +1,9 @@ +import 'package:flutter_web3/src/ethereum/ethereum.dart'; + enum Chains { Mainnet, - Kovan, - Rinkeby, - Gorli, Ropsten, + Rinkeby, XDai, Polygon, Mumbai, @@ -14,36 +14,141 @@ enum Chains { extension ChainExtension on Chains { static const _info = { Chains.Mainnet: { + 'name': 'Ethereum Mainnet', + 'chain': 'ETH', + 'network': 'mainnet', + 'rpc': [], + 'nativeCurrency': {'name': 'Ether', 'symbol': 'ETH', 'decimals': 18}, + 'chainId': 1, + "explorers": ["https://etherscan.io/"], 'multicall': '0xeefba1e63905ef1d7acba5a8513c70307c1ce441', }, - Chains.Gorli: { - 'multicall': '0x77dca2c955b15e9de4dbbcf1246b4b85b651e50e', - }, Chains.Ropsten: { + "name": "Ethereum Testnet Ropsten", + "chain": "ETH", + "network": "ropsten", + "rpc": [], + "nativeCurrency": { + "name": "Ropsten Ether", + "symbol": "ROP", + "decimals": 18 + }, + "chainId": 3, + "explorers": ["https://ropsten.etherscan.io/"], 'multicall': '0x53c43764255c17bd724f74c4ef150724ac50a3ed', }, - Chains.Kovan: { - 'multicall': '0x2cc8688c5f75e365aaeeb4ea8d6a480405a48d2a', - }, Chains.Rinkeby: { + "name": "Ethereum Testnet Rinkeby", + "chain": "ETH", + "network": "rinkeby", + "rpc": [], + "nativeCurrency": { + "name": "Rinkeby Ether", + "symbol": "RIN", + "decimals": 18 + }, + "chainId": 4, + "explorers": ["https://rinkeby.etherscan.io/"], 'multicall': '0x42ad527de7d4e9d9d011ac45b31d8551f8fe9821', }, Chains.XDai: { + "name": "xDAI Chain", + "chain": "XDAI", + "network": "mainnet", + "rpc": [ + "https://rpc.xdaichain.com", + "https://xdai.poanetwork.dev", + "http://xdai.poanetwork.dev", + "https://dai.poa.network", + ], + "nativeCurrency": {"name": "xDAI", "symbol": "xDAI", "decimals": 18}, + "chainId": 100, + "explorers": ["https://blockscout.com/xdai/mainnet/"], 'multicall': '0xb5b692a88bdfc81ca69dcb1d924f59f0413a602a', }, Chains.Polygon: { + "name": "Matic(Polygon) Mainnet", + "chain": "Matic(Polygon)", + "network": "mainnet", + "rpc": ['https://polygon-rpc.com/'], + "nativeCurrency": {"name": "Matic", "symbol": "MATIC", "decimals": 18}, + "chainId": 137, + "explorers": ["https://polygonscan.com/"], 'multicall': '0x11ce4B23bD875D7F5C6a31084f55fDe1e9A87507', }, Chains.Mumbai: { + "name": "Matic(Polygon) Testnet Mumbai", + "chain": "Matic(Polygon)", + "network": "testnet", + "rpc": ["https://rpc-mumbai.matic.today"], + "faucets": ["https://faucet.matic.network/"], + "nativeCurrency": {"name": "Matic", "symbol": "tMATIC", "decimals": 18}, + "chainId": 80001, + "explorers": ["https://mumbai.polygonscan.com/"], 'multicall': '0x08411ADd0b5AA8ee47563b146743C13b3556c9Cc', }, Chains.BSCMainnet: { + "name": "Binance Smart Chain Mainnet", + "chain": "BSC", + "network": "mainnet", + "rpc": [ + "https://bsc-dataseed1.binance.org", + "https://bsc-dataseed2.binance.org", + "https://bsc-dataseed3.binance.org", + "https://bsc-dataseed4.binance.org", + ], + "nativeCurrency": { + "name": "Binance Chain Native Token", + "symbol": "BNB", + "decimals": 18 + }, + "chainId": 56, + "explorers": ["https://bscscan.com"], 'multicall': '0x41263cba59eb80dc200f3e2544eda4ed6a90e76c', }, Chains.BSCTestnet: { + "name": "Binance Smart Chain Testnet", + "chain": "BSC", + "network": "Chapel", + "rpc": [ + "https://data-seed-prebsc-1-s1.binance.org:8545", + "https://data-seed-prebsc-2-s1.binance.org:8545", + "https://data-seed-prebsc-1-s2.binance.org:8545", + "https://data-seed-prebsc-2-s2.binance.org:8545", + "https://data-seed-prebsc-1-s3.binance.org:8545", + "https://data-seed-prebsc-2-s3.binance.org:8545" + ], + "faucets": ["https://testnet.binance.org/faucet-smart"], + "nativeCurrency": { + "name": "Binance Chain Native Token", + "symbol": "tBNB", + "decimals": 18 + }, + "chainId": 97, + "explorers": ["https://testnet.bscscan.com"], 'multicall': '0xae11C5B5f29A6a25e955F0CB8ddCc416f522AF5C', }, }; - String? get multicallAddress => _info[this]!['multicall']; + String? get multicallAddress => _info[this]!['multicall'] as String?; + + String get name => _info[this]!['name'] as String; + + String get chain => _info[this]!['chain'] as String; + + String get network => _info[this]!['network'] as String; + + int get chainId => _info[this]!['chainId'] as int; + + List get rpc => _info[this]!['rpc'] as List; + + List get explorers => _info[this]!['explorers'] as List; + + List? get faucets => _info[this]!['faucets'] as List?; + + CurrencyParams get nativeCurrency { + final info = _info[this]!['nativeCurrency']! as Map; + return CurrencyParams( + name: info['name'], symbol: info['symbol'], decimals: info['decimals']); + } } From ab88697922919bc7941c33dfbc135094d46ed356 Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Tue, 14 Sep 2021 16:11:30 +0700 Subject: [PATCH 09/30] docs: Fix dartdoc warning --- lib/src/ethers/interface.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/ethers/interface.dart b/lib/src/ethers/interface.dart index 7824cf9..ef9c9dc 100644 --- a/lib/src/ethers/interface.dart +++ b/lib/src/ethers/interface.dart @@ -204,7 +204,7 @@ class Interface extends Interop<_InterfaceImpl> { /// Return the formatted [Interface]. /// - /// [types] must be from [FormatTypes] variable. + /// [type] must be from [FormatTypes] variable. /// /// If the format type is json a single string is returned, otherwise an Array of the human-readable strings is returned. dynamic format([FormatTypes? type]) => From 0444d571651f73c47d9c0155433038e62291bf45 Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Tue, 14 Sep 2021 16:13:57 +0700 Subject: [PATCH 10/30] package: publish 2.2.0-pre.1 --- CHANGELOG.md | 4 ++++ example/pubspec.lock | 10 +++++----- pubspec.lock | 8 ++++---- pubspec.yaml | 2 +- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee99042..eb144b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.2.0-pre.1 + +- Pre-release for utils + ## 2.1.0 - Implement EIP-15559 properties (#7) diff --git a/example/pubspec.lock b/example/pubspec.lock index ebe012c..9a6a8c2 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.6.1" + version: "2.8.1" boolean_selector: dependency: transitive description: @@ -28,7 +28,7 @@ packages: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" clock: dependency: transitive description: @@ -73,7 +73,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-pre.6" + version: "2.1.0-pre.1" get: dependency: "direct main" description: @@ -101,7 +101,7 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.7.0" path: dependency: transitive description: @@ -155,7 +155,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.4.2" typed_data: dependency: transitive description: diff --git a/pubspec.lock b/pubspec.lock index 91cdc9c..8695db3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.6.1" + version: "2.8.1" boolean_selector: dependency: transitive description: @@ -28,7 +28,7 @@ packages: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" clock: dependency: transitive description: @@ -80,7 +80,7 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.7.0" path: dependency: transitive description: @@ -134,7 +134,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.4.2" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9ba2c55..4ef291f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_web3 description: Web3 Ethereum, Etherjs and Wallet Connect wrapper for Flutter Web. Made especially for developing Dapp. -version: 2.1.0 +version: 2.2.0-pre.1 repository: https://github.com/y-pakorn/flutter_web3 issue_tracker: https://github.com/y-pakorn/flutter_web3/issues From 17f8136796dab43fc6db63874e4b8b9a82ec1b54 Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Tue, 14 Sep 2021 21:18:48 +0700 Subject: [PATCH 11/30] Add ERC1155 contract --- lib/src/utils/erc1155.dart | 167 +++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 lib/src/utils/erc1155.dart diff --git a/lib/src/utils/erc1155.dart b/lib/src/utils/erc1155.dart new file mode 100644 index 0000000..42ce858 --- /dev/null +++ b/lib/src/utils/erc1155.dart @@ -0,0 +1,167 @@ +import 'dart:async'; + +import '../ethers/ethers.dart'; + +/// Dart Class for ERC1155 Contract, A standard API for fungibility-agnostic and gas-efficient tokens within smart contracts. +class ContractERC1155 { + /// Minimal abi interface of ERC20 + static const abi = [ + 'function balanceOf(address,uint) view returns (uint)', + 'function balanceOfBatch(address[],uint[]) view returns (uint[])', + 'function uri(uint) view returns (string)', + 'function isApprovedForAll(address owner, address spender) view returns (bool)', + 'function setApprovedForAll(address spender, bool approved)', + 'function safeTransferFrom(address, address, uint, uint, bytes)', + 'function safeBatchTransferFrom(address, address, uint[], uint[], bytes)', + 'event ApprovalForAll(address indexed account, address indexed operator, bool approved)', + 'event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values)', + 'event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value)', + ]; + + /// Ethers Contract object. + Contract contract; + + String _uri = ''; + + /// Instantiate ERC1155 Contract using default abi. + /// + /// [isReadOnly] is determined by whether `providerOrSigner` is [Signer] or not. + ContractERC1155(String address, dynamic providerOrSigner) + : assert(providerOrSigner != null, 'providerOrSigner should not be null'), + assert(address.isNotEmpty, 'address should not be empty'), + assert( + EthUtils.isAddress(address), 'address should be in address format'), + contract = Contract(address, abi, providerOrSigner); + + /// [Log] of `ApprovalForAll` events. + Future> approvalForAllEvents( + [List? args, dynamic startBlock, dynamic endBlock]) => + contract.queryFilter(contract.getFilter('ApprovalForAll', args ?? []), + startBlock, endBlock); + + /// Returns the amount of tokens [id] owned by [address] + Future balanceOf(String address, int id) async => + contract.call('balanceOf', [address, id]); + + /// Returns the amount of tokens [id] owned by [address] + Future> balanceOfBatch( + List address, List id) async => + (await contract.call('balanceOfBatch', [address, id])) + .cast() + .map((e) => e.toBigInt) + .toList(); + + /// Connect current [contract] with [providerOrSigner] + void connect(dynamic providerOrSigner) { + assert(providerOrSigner is Provider || providerOrSigner is Signer); + contract = contract.connect(providerOrSigner); + } + + /// Returns `true` if [spender] is approved to transfer [owner] tokens + Future isApprovedForAll(String owner, String spender) async => + contract.call('isApprovedForAll', [owner, spender]); + + /// Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to `approved`. + void onApprovalForAll( + void Function( + String account, + String operator, + Event event, + ) + callback, + ) => + contract.on( + 'ApprovalForAll', + (String account, String operator, dynamic data) => callback( + account, + operator, + Event.fromJS(data), + ), + ); + + /// Equivalent to multiple `TransferSingle` events, where `operator`, `from` and `to` are the same for all transfers. + void onTransferBatch( + void Function( + String operator, + String from, + String to, + Event event, + ) + callback, + ) => + contract.on( + 'TransferBatch', + (String operator, String from, String to, dynamic data) => callback( + operator, + from, + to, + Event.fromJS(data), + ), + ); + + /// Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`. + void onTransferSingle( + void Function( + String operator, + String from, + String to, + Event event, + ) + callback, + ) => + contract.on( + 'TransferSingle', + (String operator, String from, String to, dynamic data) => callback( + operator, + from, + to, + Event.fromJS(data), + ), + ); + + /// Batched version of [safeTransferFrom]. + Future safeBatchTransferFrom( + String from, + String to, + List id, + List amount, + String data, + ) => + contract.send('safeBatchTransferFrom', + [from, to, id, amount.map((e) => e.toString()).toList(), data]); + + /// Transfers [amount] tokens of token type [id] from [from] to [to]. + Future safeTransferFrom( + String from, + String to, + int id, + BigInt amount, + String data, + ) => + contract.send('safeTransferFrom', [from, to, id, amount, data]); + + /// Grants or revokes permission to [spender] to transfer the caller's tokens, according to [approved], + Future setApprovedForAll( + String spender, bool approved) => + contract.send('setApprovedForAll', [spender, approved]); + + /// [Log] of `TransferBatch` events. + Future> transferBatchEvents( + [List? args, dynamic startBlock, dynamic endBlock]) => + contract.queryFilter(contract.getFilter('TransferBatch', args ?? []), + startBlock, endBlock); + + /// [Log] of `TransferSingle` events. + Future> transferSingleEvents( + [List? args, dynamic startBlock, dynamic endBlock]) => + contract.queryFilter(contract.getFilter('TransferSingle', args ?? []), + startBlock, endBlock); + + /// Returns the URI for token type [id]. + /// + /// This will also replace `{id}` in original uri fetched by [id]. + FutureOr uri(int id) async { + if (_uri.isEmpty) _uri = await contract.call('uri', [id]); + return _uri.replaceAll('{id}', id.toString()); + } +} From 56e8549659be774ef93b2b747939400a9bada0e4 Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Thu, 16 Sep 2021 00:23:44 +0700 Subject: [PATCH 12/30] Add multicall support for contract --- lib/src/ethers/contract.dart | 54 ++++++++++++++++++++++++++++++------ lib/src/ethers/ethers.dart | 1 + lib/src/ethers/interop.dart | 2 ++ 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/lib/src/ethers/contract.dart b/lib/src/ethers/contract.dart index 568bf27..f876b60 100644 --- a/lib/src/ethers/contract.dart +++ b/lib/src/ethers/contract.dart @@ -72,6 +72,15 @@ class Contract extends Interop<_ContractImpl> { Future call(String method, [List args = const []]) => _call(method, args); + /// Returns a new instance of the [Contract] attached to [addressOrName]. + /// + /// This is useful if there are multiple similar or identical copies of a Contract on the network and you wish to interact with each of them. + Contract attach(String addressOrName) { + assert(EthUtils.isAddress(addressOrName), 'addressOrName must be valid'); + + return Contract._(impl.attach(addressOrName)); + } + ///Returns a new instance of the [Contract], but connected to [Provider] or [Signer]. /// ///By passing in a [Provider], this will return a downgraded Contract which only has read-only access (i.e. constant calls). @@ -115,16 +124,45 @@ class Contract extends Interop<_ContractImpl> { List listeners(Object event) => impl.listeners(event is EventFilter ? event.impl : event); - /// Multicall read-only constant [method] with [args]. `May not` be at the same block. + /// Multicall read-only constant [method] with [args]. Will use multiple https call unless [multicall] is provided. /// /// If [eagerError] is `true`, returns the error immediately on the first error found. - Future> multicall(String method, List> args, - [bool eagerError = false]) => - Future.wait( - Iterable.generate(args.length).map( - (e) => _call(method, args[e]), - ), - eagerError: eagerError); + Future> multicall( + String method, + List> args, [ + Multicall? multicall, + bool eagerError = false, + ]) async { + if (multicall != null) { + final res = await multicall.aggregate( + args + .map( + (e) => MulticallPayload.fromInterfaceFunction( + address, interface, method, e), + ) + .toList(), + ); + final decoded = res.returnData + .map((e) => interface.decodeFunctionResult(method, e)) + .toList(); + switch (T) { + case List: + return decoded as List; + case BigInt: + return decoded.map((e) => BigInt.parse(e[0].toString())).toList() + as List; + default: + return decoded.map((e) => e[0]).toList() as List; + } + } else { + return Future.wait( + Iterable.generate(args.length).map( + (e) => _call(method, args[e]), + ), + eagerError: eagerError, + ); + } + } /// Remove a [listener] for the [event]. If no [listener] is provided, all listeners for [event] are removed. off(dynamic event, [Function? listener]) => callMethod( diff --git a/lib/src/ethers/ethers.dart b/lib/src/ethers/ethers.dart index a2435b8..b6791d3 100644 --- a/lib/src/ethers/ethers.dart +++ b/lib/src/ethers/ethers.dart @@ -9,6 +9,7 @@ import '../ethereum/ethereum.dart'; import '../ethereum/exception.dart'; import '../ethereum/utils.dart'; import '../interop_wrapper.dart'; +import '../utils/multicall.dart'; import '../wallet_connect/wallet_connect.dart'; part 'access_list.dart'; diff --git a/lib/src/ethers/interop.dart b/lib/src/ethers/interop.dart index f5a55b1..e25a9a6 100644 --- a/lib/src/ethers/interop.dart +++ b/lib/src/ethers/interop.dart @@ -36,6 +36,8 @@ class _ContractImpl { external _ContractImpl connect(dynamic providerOrSigner); + external _ContractImpl attach(String addressOrName); + external int listenerCount([dynamic eventName]); external List listeners(dynamic eventName); From 3093c89e6a53b551da8d1c776720df49ff1ecc7e Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Thu, 16 Sep 2021 17:50:10 +0700 Subject: [PATCH 13/30] feat: add fragment inherited class and refactor --- lib/src/ethers/ethers.dart | 1 + lib/src/ethers/fragment.dart | 142 ++++++++++++++++++++++++++++++++++ lib/src/ethers/interface.dart | 83 -------------------- lib/src/ethers/interop.dart | 45 +++++++++-- 4 files changed, 180 insertions(+), 91 deletions(-) create mode 100644 lib/src/ethers/fragment.dart diff --git a/lib/src/ethers/ethers.dart b/lib/src/ethers/ethers.dart index b6791d3..14f7b8c 100644 --- a/lib/src/ethers/ethers.dart +++ b/lib/src/ethers/ethers.dart @@ -18,6 +18,7 @@ part 'contract.dart'; part 'event.dart'; part 'fee_data.dart'; part 'filter.dart'; +part 'fragment.dart'; part 'interface.dart'; part 'interop.dart'; part 'log.dart'; diff --git a/lib/src/ethers/fragment.dart b/lib/src/ethers/fragment.dart new file mode 100644 index 0000000..b71579a --- /dev/null +++ b/lib/src/ethers/fragment.dart @@ -0,0 +1,142 @@ +part of ethers; + +class ConstructorFragment + extends Fragment { + /// Creates a new [ConstructorFragment] from any compatible [source]. + factory ConstructorFragment.from(dynamic source) => ConstructorFragment._( + _ConstructorFragmentImpl.from(source is Interop ? source.impl : source) + as T, + ); + + const ConstructorFragment._(T impl) : super._(impl); + + /// This is the gas limit that should be used during deployment. It may be `null`. + BigInt? get gas => impl.gas?.toBigInt; + + /// This is whether the constructor may receive ether during deployment as an endowment (i.e. msg.value != 0). + bool get payable => impl.payable; + + /// This is the state mutability of the constructor. It can be any of: + /// - nonpayable + /// - payable + String get stateMutability => impl.stateMutability; +} + +class EventFragment extends Fragment<_EventFragmentImpl> { + /// Creates a new [EventFragment] from any compatible [source]. + factory EventFragment.from(dynamic source) => EventFragment._( + _EventFragmentImpl.from(source is Interop ? source.impl : source), + ); + + const EventFragment._(_EventFragmentImpl impl) : super._(impl); + + /// This is whether the event is anonymous. An anonymous Event does not inject its topic hash as topic0 when creating a log. + bool get anonymous => impl.anonymous; +} + +/// An ABI is a collection of Fragments. +class Fragment extends Interop { + /// Creates a new [Fragment] sub-class from any compatible [source]. + factory Fragment.from(dynamic source) => Fragment._( + _FragmentImpl.from(source is Interop ? source.impl : source) as T); + + const Fragment._(T impl) : super.internal(impl); + + /// This is the name of the Event or Function. This will be `null` for a `ConstructorFragment`. + String? get name => impl.name; + + /// This is an array of each [ParamType] for the input parameters to the Constructor, Event of Function. + List get paramType => + impl.inputs.cast<_ParamTypeImpl>().map((e) => ParamType._(e)).toList(); + + /// This is a [String] which indicates the type of the [Fragment]. This will be one of: + /// - constructor + /// - event + /// - function + String get type => impl.type; + + /// Creates a [String] representation of the [Fragment] using the available [type] formats. + String format([FormatTypes? type]) => + type != null ? impl.format(type.impl) : impl.format(); + + @override + String toString() => 'Fragment: ${format()}'; +} + +class FunctionFragment extends ConstructorFragment<_FunctionFragmentImpl> { + /// Creates a new [FunctionFragment] from any compatible [source]. + factory FunctionFragment.from(dynamic source) => FunctionFragment._( + _FunctionFragmentImpl.from(source is Interop ? source.impl : source), + ); + + const FunctionFragment._(_FunctionFragmentImpl impl) : super._(impl); + + /// This is whether the function is constant (i.e. does not change state). This is `true` if the state mutability is `pure` or `view`. + bool get constant => impl.constant; + + /// A list of the Function output parameters. + List get outputs => + impl.outputs.cast<_ParamTypeImpl>().map((e) => ParamType._(e)).toList(); + + /// This is the state mutability of the constructor. It can be any of: + /// - nonpayable + /// - payable + /// - pure + /// - view + String get stateMutability => impl.stateMutability; +} + +/// A representation of a solidity parameter. +class ParamType extends Interop<_ParamTypeImpl> { + factory ParamType.from(String source) => + ParamType._(_ParamTypeImpl.from(source)); + + const ParamType._(_ParamTypeImpl impl) : super.internal(impl); + + /// The type of children of the array. This is `null` for any parameter which is not an array. + ParamType? get arrayChildren => + impl.arrayChildren == null ? null : ParamType._(impl.arrayChildren!); + + /// The length of the array, or -1 for dynamic-length arrays. This is `null` for parameters which are not arrays. + int? get arrayLength => impl.arrayLength; + + /// The base type of the parameter. For primitive types (e.g. `address`, `uint256`, etc) this is equal to type. For arrays, it will be the string array and for a tuple, it will be the string tuple. + String get baseType => impl.baseType; + + ///The components of a tuple. This is `null` for non-tuple parameters. + List? get components => impl.components + ?.cast<_ParamTypeImpl>() + .map((e) => ParamType._(e)) + .toList(); + + /// Whether the parameter has been marked as indexed. This only applies to parameters which are part of an EventFragment. + bool get indexed => impl.indexed; + + /// The local parameter name. This may be null for unnamed parameters. For example, the parameter definition `string foobar` would be `foobar`. + String? get name => impl.name; + + /// The full type of the parameter, including tuple and array symbols. This may be `null` for unnamed parameters. For the above example, this would be `foobar`. + String? get type => impl.type; + + /// Creates a [String] representation of the [Fragment] using the available [type] formats. + String format([FormatTypes? type]) => + type != null ? impl.format(type.impl) : impl.format(); + + @override + String toString() => 'ParamType: ${format()}'; +} + +extension _FormatTypesExtImpl on FormatTypes { + dynamic get impl { + switch (this) { + case FormatTypes.json: + return _FormatTypesImpl.json; + case FormatTypes.minimal: + return _FormatTypesImpl.minimal; + case FormatTypes.full: + return _FormatTypesImpl.full; + case FormatTypes.sighash: + return _FormatTypesImpl.sighash; + } + } +} diff --git a/lib/src/ethers/interface.dart b/lib/src/ethers/interface.dart index ef9c9dc..9292c4e 100644 --- a/lib/src/ethers/interface.dart +++ b/lib/src/ethers/interface.dart @@ -40,34 +40,6 @@ enum FormatTypes { sighash, } -/// An ABI is a collection of Fragments. -class Fragment extends Interop<_FragmentImpl> { - factory Fragment.from(String source) => - Fragment._(_FragmentImpl.from(source)); - - const Fragment._(_FragmentImpl impl) : super.internal(impl); - - /// This is the name of the Event or Function. This will be `null` for a `ConstructorFragment`. - String? get name => impl.name; - - /// This is an array of each [ParamType] for the input parameters to the Constructor, Event of Function. - List get paramType => - impl.inputs.cast<_ParamTypeImpl>().map((e) => ParamType._(e)).toList(); - - /// This is a [String] which indicates the type of the [Fragment]. This will be one of: - /// - constructor - /// - event - /// - function - String get type => impl.type; - - /// Creates a [String] representation of the [Fragment] using the available [type] formats. - String format([FormatTypes? type]) => - type != null ? impl.format(type.impl) : impl.format(); - - @override - String toString() => 'Fragment: ${format()}'; -} - /// The Interface Class abstracts the encoding and decoding required to interact with contracts on the Ethereum network. /// /// Many of the standards organically evolved along side the Solidity language, which other languages have adopted to remain compatible with existing deployed contracts. @@ -296,58 +268,3 @@ class Interface extends Interop<_InterfaceImpl> { @override String toString() => 'Interface: ${format(FormatTypes.minimal)}'; } - -/// A representation of a solidity parameter. -class ParamType extends Interop<_ParamTypeImpl> { - factory ParamType.from(String source) => - ParamType._(_ParamTypeImpl.from(source)); - - const ParamType._(_ParamTypeImpl impl) : super.internal(impl); - - /// The type of children of the array. This is `null` for any parameter which is not an array. - ParamType? get arrayChildren => - impl.arrayChildren == null ? null : ParamType._(impl.arrayChildren!); - - /// The length of the array, or -1 for dynamic-length arrays. This is `null` for parameters which are not arrays. - int? get arrayLength => impl.arrayLength; - - /// The base type of the parameter. For primitive types (e.g. `address`, `uint256`, etc) this is equal to type. For arrays, it will be the string array and for a tuple, it will be the string tuple. - String get baseType => impl.baseType; - - ///The components of a tuple. This is `null` for non-tuple parameters. - List? get components => impl.components - ?.cast<_ParamTypeImpl>() - .map((e) => ParamType._(e)) - .toList(); - - /// Whether the parameter has been marked as indexed. This only applies to parameters which are part of an EventFragment. - bool get indexed => impl.indexed; - - /// The local parameter name. This may be null for unnamed parameters. For example, the parameter definition `string foobar` would be `foobar`. - String? get name => impl.name; - - /// The full type of the parameter, including tuple and array symbols. This may be `null` for unnamed parameters. For the above example, this would be `foobar`. - String? get type => impl.type; - - /// Creates a [String] representation of the [Fragment] using the available [type] formats. - String format([FormatTypes? type]) => - type != null ? impl.format(type.impl) : impl.format(); - - @override - String toString() => 'ParamType: ${format()}'; -} - -extension _FormatTypesExtImpl on FormatTypes { - dynamic get impl { - switch (this) { - case FormatTypes.json: - return _FormatTypesImpl.json; - case FormatTypes.minimal: - return _FormatTypesImpl.minimal; - case FormatTypes.full: - return _FormatTypesImpl.full; - case FormatTypes.sighash: - return _FormatTypesImpl.sighash; - } - } -} diff --git a/lib/src/ethers/interop.dart b/lib/src/ethers/interop.dart index e25a9a6..bc0afa2 100644 --- a/lib/src/ethers/interop.dart +++ b/lib/src/ethers/interop.dart @@ -22,6 +22,17 @@ class _BlockWithTransactionImpl extends _RawBlockImpl { external List get transactions; } +@JS('utils.ConstructorFragment') +class _ConstructorFragmentImpl extends _FragmentImpl { + external BigNumber? get gas; + + external bool get payable; + + external String get stateMutability; + + external static _ConstructorFragmentImpl from(String source); +} + @JS("Contract") class _ContractImpl { external _ContractImpl(String address, dynamic abi, dynamic providerOrSigner); @@ -34,10 +45,10 @@ class _ContractImpl { external _SignerImpl? get signer; - external _ContractImpl connect(dynamic providerOrSigner); - external _ContractImpl attach(String addressOrName); + external _ContractImpl connect(dynamic providerOrSigner); + external int listenerCount([dynamic eventName]); external List listeners(dynamic eventName); @@ -62,6 +73,13 @@ class _EventFilterImpl { external set topics(List? topics); } +@JS('utils.EventFragment') +class _EventFragmentImpl extends _FragmentImpl { + external bool get anonymous; + + external static _EventFragmentImpl from(String source); +} + @JS() @anonymous class _EventImpl extends _LogImpl { @@ -125,23 +143,34 @@ class _FragmentImpl { external static _FragmentImpl from(String source); } +@JS('utils.FunctionFragment') +class _FunctionFragmentImpl extends _ConstructorFragmentImpl { + external bool get constant; + + external List<_ParamTypeImpl> get outputs; + + external String get stateMutability; + + external static _FunctionFragmentImpl from(String source); +} + @JS("utils.Interface") class _InterfaceImpl { external _InterfaceImpl(dynamic abi); external List<_FragmentImpl> get fragments; - external dynamic format([dynamic types]); - - external String getEventTopic(String event); - - external String getSighash(dynamic function); + external List decodeFunctionResult(dynamic fragment, String data); external List encodeFilterTopics(dynamic fragment, List values); external String encodeFunctionData(dynamic fragment, [List? values]); - external List decodeFunctionResult(dynamic fragment, String data); + external dynamic format([dynamic types]); + + external String getEventTopic(String event); + + external String getSighash(dynamic function); } @JS("providers.JsonRpcProvider") From dead6c6852908d9761902c54418dc3a151cf3ce7 Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Thu, 16 Sep 2021 18:03:04 +0700 Subject: [PATCH 14/30] feat: add interface function related to specific fragment --- lib/src/ethers/interface.dart | 30 ++++++++++++++++++++++++++++++ lib/src/ethers/interop.dart | 10 ++++++++++ 2 files changed, 40 insertions(+) diff --git a/lib/src/ethers/interface.dart b/lib/src/ethers/interface.dart index 9292c4e..525e1a1 100644 --- a/lib/src/ethers/interface.dart +++ b/lib/src/ethers/interface.dart @@ -58,10 +58,25 @@ class Interface extends Interop<_InterfaceImpl> { const Interface._(_InterfaceImpl impl) : super.internal(impl); + /// The [ConstructorFragment] for the interface. + ConstructorFragment get deploy => ConstructorFragment._(impl.deploy); + + /// All the [EventFragment] in the interface. + List get events => impl.events + .cast<_EventFragmentImpl>() + .map((e) => EventFragment._(e)) + .toList(); + /// All the [Fragment] in the interface. List get fragments => impl.fragments.cast<_FragmentImpl>().map((e) => Fragment._(e)).toList(); + /// All the [FunctionFragment] in the interface. + List get functions => impl.functions + .cast<_FunctionFragmentImpl>() + .map((e) => FunctionFragment._(e)) + .toList(); + /// Returns the decoded values from the result of a call for [function] (see Specifying Fragments) for the given [data]. /// /// --- @@ -228,6 +243,13 @@ class Interface extends Interop<_InterfaceImpl> { /// ``` List formatMinimal() => (format(FormatTypes.minimal) as List).cast(); + /// Returns the [FunctionFragment] for [event]. + EventFragment getEvent(String event) => EventFragment._(impl.getEvent(event)); + + /// Returns the [FunctionFragment] for [event] fragment. + EventFragment getEventFromFragment(Fragment event) => + EventFragment._(impl.getEvent(event.impl)); + /// Return the topic hash for [event]. /// /// --- @@ -241,6 +263,14 @@ class Interface extends Interop<_InterfaceImpl> { /// ``` String getEventTopic(String event) => impl.getEventTopic(event); + /// Returns the [FunctionFragment] for [function]. + FunctionFragment getFunction(String function) => + FunctionFragment._(impl.getFunction(function)); + + /// Returns the [FunctionFragment] for [function] fragment. + FunctionFragment getFunctionFromFragment(Fragment function) => + FunctionFragment._(impl.getFunction(function.impl)); + /// Return the sighash (or Function Selector) for [function]. /// /// --- diff --git a/lib/src/ethers/interop.dart b/lib/src/ethers/interop.dart index bc0afa2..e4625f9 100644 --- a/lib/src/ethers/interop.dart +++ b/lib/src/ethers/interop.dart @@ -158,8 +158,14 @@ class _FunctionFragmentImpl extends _ConstructorFragmentImpl { class _InterfaceImpl { external _InterfaceImpl(dynamic abi); + external _ConstructorFragmentImpl get deploy; + + external List<_EventFragmentImpl> get events; + external List<_FragmentImpl> get fragments; + external List<_FunctionFragmentImpl> get functions; + external List decodeFunctionResult(dynamic fragment, String data); external List encodeFilterTopics(dynamic fragment, List values); @@ -168,8 +174,12 @@ class _InterfaceImpl { external dynamic format([dynamic types]); + external _EventFragmentImpl getEvent(dynamic fragment); + external String getEventTopic(String event); + external _FunctionFragmentImpl getFunction(dynamic fragment); + external String getSighash(dynamic function); } From 953d559ee04c8f7bd8ac17b2966f98c68dd7290f Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Fri, 1 Oct 2021 15:26:54 +0700 Subject: [PATCH 15/30] feat: add ethereum chain extension --- lib/src/utils/extensions.dart | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 lib/src/utils/extensions.dart diff --git a/lib/src/utils/extensions.dart b/lib/src/utils/extensions.dart new file mode 100644 index 0000000..84fb153 --- /dev/null +++ b/lib/src/utils/extensions.dart @@ -0,0 +1,17 @@ +import '../ethereum/ethereum.dart'; +import 'chains.dart'; + +extension EtherumChainExt on Ethereum { + /// Use `Ethereum.walletSwitchChain` function with [chain] information. RPC Url list in [chain] will be overridden if [rpcs] is not `null`. + Future walletSwitchChainByChains(Chains chain, [List? rpcs]) => + walletSwitchChain( + chain.chainId, + () => walletAddChain( + chainId: chain.chainId, + chainName: chain.name, + nativeCurrency: chain.nativeCurrency, + rpcUrls: rpcs ?? chain.rpc, + blockExplorerUrls: chain.explorers, + ), + ); +} From 2630e4dcd909055f163a7a6e38f58defd991d3b8 Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Fri, 1 Oct 2021 15:27:31 +0700 Subject: [PATCH 16/30] feat: add extension to export --- lib/utils.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/utils.dart b/lib/utils.dart index 23fd597..5aa0caf 100644 --- a/lib/utils.dart +++ b/lib/utils.dart @@ -1,3 +1,4 @@ export 'src/utils/chains.dart'; export 'src/utils/erc20.dart'; +export 'src/utils/extensions.dart'; export 'src/utils/multicall.dart'; From ae4604c4a0557390a85c6ba4825a942dc4340d20 Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Sun, 3 Oct 2021 19:35:59 +0700 Subject: [PATCH 17/30] feat: add erc20 multicall function --- lib/src/utils/multicall.dart | 47 ++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/lib/src/utils/multicall.dart b/lib/src/utils/multicall.dart index a088f90..06c69bd 100644 --- a/lib/src/utils/multicall.dart +++ b/lib/src/utils/multicall.dart @@ -30,6 +30,53 @@ class Multicall { return MulticallResult( int.parse(res[0].toString()), (res[1] as List).cast()); } + + Future> multipleERC20Balances( + List tokens, + List addresses, + ) async { + assert(addresses.isNotEmpty && tokens.isNotEmpty, + 'addresses and tokens should not be empty'); + assert(addresses.length == tokens.length, + 'addresses and tokens length should be equal'); + + final payload = new Iterable.generate(tokens.length) + .map( + (e) => MulticallPayload.fromFunctionAbi( + tokens[e], + 'function balanceOf(address) view returns (uint)', + [addresses[e]], + ), + ) + .toList(); + + final res = await aggregate(payload); + + return res.returnData.map((e) => BigInt.parse(e)).toList(); + } + + Future> multipleERC20Allowance( + List tokens, + List owners, + List spenders, + ) async { + assert(tokens.isNotEmpty && owners.isNotEmpty && spenders.isNotEmpty); + assert(tokens.length == owners.length && tokens.length == spenders.length); + + final payload = new Iterable.generate(tokens.length) + .map( + (e) => MulticallPayload.fromFunctionAbi( + tokens[e], + 'function allowance(address owner, address spender) external view returns (uint256)', + [owners[e], spenders[e]], + ), + ) + .toList(); + + final res = await aggregate(payload); + + return res.returnData.map((e) => BigInt.parse(e)).toList(); + } } class MulticallPayload { From d4faa8bf79bdbcd6c3950f7ae8b90b2b7d41083c Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Tue, 12 Oct 2021 16:09:48 +0700 Subject: [PATCH 18/30] feat: export erc1155 --- lib/utils.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/utils.dart b/lib/utils.dart index 5aa0caf..3732a9f 100644 --- a/lib/utils.dart +++ b/lib/utils.dart @@ -1,4 +1,5 @@ export 'src/utils/chains.dart'; +export 'src/utils/erc1155.dart'; export 'src/utils/erc20.dart'; export 'src/utils/extensions.dart'; export 'src/utils/multicall.dart'; From 3165be859b3bdbc6864d1d14bdb229dbf1fd298b Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Tue, 12 Oct 2021 16:14:27 +0700 Subject: [PATCH 19/30] feat: add custom abi constructor and more method --- lib/src/utils/erc1155.dart | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/lib/src/utils/erc1155.dart b/lib/src/utils/erc1155.dart index 42ce858..ccf52d3 100644 --- a/lib/src/utils/erc1155.dart +++ b/lib/src/utils/erc1155.dart @@ -23,15 +23,16 @@ class ContractERC1155 { String _uri = ''; - /// Instantiate ERC1155 Contract using default abi. + /// Instantiate ERC1155 Contract using default abi if [abi] is not `null`. /// - /// [isReadOnly] is determined by whether `providerOrSigner` is [Signer] or not. - ContractERC1155(String address, dynamic providerOrSigner) + /// [isReadOnly] is determined by whether [providerOrSigner] is [Signer] or not. + ContractERC1155(String address, dynamic providerOrSigner, [dynamic abi]) : assert(providerOrSigner != null, 'providerOrSigner should not be null'), assert(address.isNotEmpty, 'address should not be empty'), assert( EthUtils.isAddress(address), 'address should be in address format'), - contract = Contract(address, abi, providerOrSigner); + contract = + Contract(address, abi ?? ContractERC1155.abi, providerOrSigner); /// [Log] of `ApprovalForAll` events. Future> approvalForAllEvents( @@ -43,10 +44,24 @@ class ContractERC1155 { Future balanceOf(String address, int id) async => contract.call('balanceOf', [address, id]); - /// Returns the amount of tokens [id] owned by [address] + /// Returns the amount of tokens [ids] owned by [addresses] Future> balanceOfBatch( - List address, List id) async => - (await contract.call('balanceOfBatch', [address, id])) + List addresses, List ids) async => + (await contract.call('balanceOfBatch', [addresses, ids])) + .cast() + .map((e) => e.toBigInt) + .toList(); + + /// Returns the amount of tokens [ids] owned by [address] + Future> balanceOfBatchSingleAddress( + String address, List ids) async => + (await contract.call( + 'balanceOfBatch', + [ + List.generate(ids.length, (index) => address), + ids, + ], + )) .cast() .map((e) => e.toBigInt) .toList(); From fa8029fc3beecd8719202dc551ad4099e4f5115c Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Tue, 12 Oct 2021 16:35:20 +0700 Subject: [PATCH 20/30] feat: add erc1155 extension contract --- lib/src/utils/erc1155.dart | 63 +++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/lib/src/utils/erc1155.dart b/lib/src/utils/erc1155.dart index ccf52d3..2282476 100644 --- a/lib/src/utils/erc1155.dart +++ b/lib/src/utils/erc1155.dart @@ -4,7 +4,7 @@ import '../ethers/ethers.dart'; /// Dart Class for ERC1155 Contract, A standard API for fungibility-agnostic and gas-efficient tokens within smart contracts. class ContractERC1155 { - /// Minimal abi interface of ERC20 + /// Minimal abi interface of ERC1155 static const abi = [ 'function balanceOf(address,uint) view returns (uint)', 'function balanceOfBatch(address[],uint[]) view returns (uint[])', @@ -13,6 +13,10 @@ class ContractERC1155 { 'function setApprovedForAll(address spender, bool approved)', 'function safeTransferFrom(address, address, uint, uint, bytes)', 'function safeBatchTransferFrom(address, address, uint[], uint[], bytes)', + 'function totalSupply(uint256 id) view returns (uint256)', + 'function exists(uint256 id) view returns (bool)', + 'function burn(address account, uint256 id, uint256 value)', + 'function burnBatch(address account, uint256[] ids, uint256[] values)', 'event ApprovalForAll(address indexed account, address indexed operator, bool approved)', 'event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values)', 'event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value)', @@ -180,3 +184,60 @@ class ContractERC1155 { return _uri.replaceAll('{id}', id.toString()); } } + +/// Dart Class for ERC1155Burnable Contract that allows token holders to destroy both their own tokens and those that they have been approved to use. +class ContractERC1155Burnable extends ContractERC1155 with ERC1155Supply { + /// Instantiate ERC1155 Contract using default abi if [abi] is not `null`. + /// + /// [isReadOnly] is determined by whether [providerOrSigner] is [Signer] or not. + ContractERC1155Burnable(String address, dynamic providerOrSigner, + [dynamic abi]) + : super(address, providerOrSigner, abi); +} + +/// Dart Class for ERC1155Supply Contract that adds tracking of total supply per id to normal ERC1155. +class ContractERC1155Supply extends ContractERC1155 with ERC1155Supply { + /// Instantiate ERC1155 Contract using default abi if [abi] is not `null`. + /// + /// [isReadOnly] is determined by whether [providerOrSigner] is [Signer] or not. + ContractERC1155Supply(String address, dynamic providerOrSigner, [dynamic abi]) + : super(address, providerOrSigner, abi); +} + +/// Dart Class for both [ContractERC1155Supply] and [ContractERC1155Burnable] combined. +class ContractERC1155SupplyBurnable extends ContractERC1155 + with ERC1155Supply, ERC1155Burnable { + /// Instantiate ERC1155 Contract using default abi if [abi] is not `null`. + /// + /// [isReadOnly] is determined by whether [providerOrSigner] is [Signer] or not. + ContractERC1155SupplyBurnable(String address, dynamic providerOrSigner, + [dynamic abi]) + : super(address, providerOrSigner, abi); +} + +/// Dart Mixin for ERC1155Burnable that allows token holders to destroy both their own tokens and those that they have been approved to use. +mixin ERC1155Burnable on ContractERC1155 { + Future burn(String address, int id, BigInt value) => + contract.send('burn', [address, id, value.toString()]); + + Future burnBatch( + String address, List ids, List values) => + contract.send('burnBatch', [ + address, + ids, + values.map((e) => e.toString()).toList(), + ]); +} + +/// Dart Mixin for ERC1155Supply that adds tracking of total supply per id to normal ERC1155. +mixin ERC1155Supply on ContractERC1155 { + /// Indicates weither any token exist with a given [id], or not. + Future exists(int id) async { + return contract.call('exists', [id]); + } + + /// Total amount of tokens in with a given [id]. + Future totalSupply(int id) async { + return contract.call('totalSupply', [id]); + } +} From df7e8b02c230d8687cd918ee2a183222ab99d571 Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Wed, 13 Oct 2021 00:00:26 +0700 Subject: [PATCH 21/30] docs: correct removed property --- lib/src/utils/erc1155.dart | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/src/utils/erc1155.dart b/lib/src/utils/erc1155.dart index 2282476..ab5e91e 100644 --- a/lib/src/utils/erc1155.dart +++ b/lib/src/utils/erc1155.dart @@ -28,8 +28,6 @@ class ContractERC1155 { String _uri = ''; /// Instantiate ERC1155 Contract using default abi if [abi] is not `null`. - /// - /// [isReadOnly] is determined by whether [providerOrSigner] is [Signer] or not. ContractERC1155(String address, dynamic providerOrSigner, [dynamic abi]) : assert(providerOrSigner != null, 'providerOrSigner should not be null'), assert(address.isNotEmpty, 'address should not be empty'), @@ -188,8 +186,6 @@ class ContractERC1155 { /// Dart Class for ERC1155Burnable Contract that allows token holders to destroy both their own tokens and those that they have been approved to use. class ContractERC1155Burnable extends ContractERC1155 with ERC1155Supply { /// Instantiate ERC1155 Contract using default abi if [abi] is not `null`. - /// - /// [isReadOnly] is determined by whether [providerOrSigner] is [Signer] or not. ContractERC1155Burnable(String address, dynamic providerOrSigner, [dynamic abi]) : super(address, providerOrSigner, abi); @@ -198,8 +194,6 @@ class ContractERC1155Burnable extends ContractERC1155 with ERC1155Supply { /// Dart Class for ERC1155Supply Contract that adds tracking of total supply per id to normal ERC1155. class ContractERC1155Supply extends ContractERC1155 with ERC1155Supply { /// Instantiate ERC1155 Contract using default abi if [abi] is not `null`. - /// - /// [isReadOnly] is determined by whether [providerOrSigner] is [Signer] or not. ContractERC1155Supply(String address, dynamic providerOrSigner, [dynamic abi]) : super(address, providerOrSigner, abi); } @@ -208,8 +202,6 @@ class ContractERC1155Supply extends ContractERC1155 with ERC1155Supply { class ContractERC1155SupplyBurnable extends ContractERC1155 with ERC1155Supply, ERC1155Burnable { /// Instantiate ERC1155 Contract using default abi if [abi] is not `null`. - /// - /// [isReadOnly] is determined by whether [providerOrSigner] is [Signer] or not. ContractERC1155SupplyBurnable(String address, dynamic providerOrSigner, [dynamic abi]) : super(address, providerOrSigner, abi); From 3a5bb4fef84daebb0d1e198e18f700c2b8188707 Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Sat, 6 Nov 2021 02:00:10 +0700 Subject: [PATCH 22/30] fix: correct function arg and return types --- lib/src/ethers/interop.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/ethers/interop.dart b/lib/src/ethers/interop.dart index 6555deb..1c1e8bd 100644 --- a/lib/src/ethers/interop.dart +++ b/lib/src/ethers/interop.dart @@ -30,7 +30,7 @@ class _ConstructorFragmentImpl extends _FragmentImpl { external String get stateMutability; - external static _ConstructorFragmentImpl from(String source); + external static _ConstructorFragmentImpl from(dynamic source); } @JS("Contract") @@ -77,7 +77,7 @@ class _EventFilterImpl { class _EventFragmentImpl extends _FragmentImpl { external bool get anonymous; - external static _EventFragmentImpl from(String source); + external static _EventFragmentImpl from(dynamic source); } @JS() @@ -161,7 +161,7 @@ class _FunctionFragmentImpl extends _ConstructorFragmentImpl { external String get stateMutability; - external static _FunctionFragmentImpl from(String source); + external static _FunctionFragmentImpl from(dynamic source); } @JS("utils.Interface") @@ -170,11 +170,11 @@ class _InterfaceImpl { external _ConstructorFragmentImpl get deploy; - external List<_EventFragmentImpl> get events; + external dynamic get events; external List<_FragmentImpl> get fragments; - external List<_FunctionFragmentImpl> get functions; + external dynamic get functions; external List decodeFunctionResult(dynamic fragment, String data); From dfc8ef937c496db8242f9600ff98c13231fe0fb0 Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Sat, 6 Nov 2021 02:01:29 +0700 Subject: [PATCH 23/30] fix: overwrite format function to supress ethers error --- lib/src/ethers/fragment.dart | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/src/ethers/fragment.dart b/lib/src/ethers/fragment.dart index b71579a..8264795 100644 --- a/lib/src/ethers/fragment.dart +++ b/lib/src/ethers/fragment.dart @@ -32,6 +32,16 @@ class EventFragment extends Fragment<_EventFragmentImpl> { /// This is whether the event is anonymous. An anonymous Event does not inject its topic hash as topic0 when creating a log. bool get anonymous => impl.anonymous; + + @override + String format([FormatTypes? type]) { + return toString(); + } + + @override + String toString() { + return 'EventFragment: $name anonymous: $anonymous'; + } } /// An ABI is a collection of Fragments. @@ -84,6 +94,16 @@ class FunctionFragment extends ConstructorFragment<_FunctionFragmentImpl> { /// - pure /// - view String get stateMutability => impl.stateMutability; + + @override + String format([FormatTypes? type]) { + return toString(); + } + + @override + String toString() { + return 'FunctionFragment: $name constant: $constant stateMutability: $stateMutability'; + } } /// A representation of a solidity parameter. From 300c7564e4e66e71230824fe1aa158b9bb8e0d67 Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Sat, 6 Nov 2021 02:02:12 +0700 Subject: [PATCH 24/30] fix: correct events and functions getter return types --- lib/src/ethers/interface.dart | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/src/ethers/interface.dart b/lib/src/ethers/interface.dart index 525e1a1..4813368 100644 --- a/lib/src/ethers/interface.dart +++ b/lib/src/ethers/interface.dart @@ -62,20 +62,18 @@ class Interface extends Interop<_InterfaceImpl> { ConstructorFragment get deploy => ConstructorFragment._(impl.deploy); /// All the [EventFragment] in the interface. - List get events => impl.events - .cast<_EventFragmentImpl>() - .map((e) => EventFragment._(e)) - .toList(); + Map get events => + (dartify(impl.events) as Map) + .map((key, value) => MapEntry(key, EventFragment.from(jsify(value)))); /// All the [Fragment] in the interface. List get fragments => impl.fragments.cast<_FragmentImpl>().map((e) => Fragment._(e)).toList(); /// All the [FunctionFragment] in the interface. - List get functions => impl.functions - .cast<_FunctionFragmentImpl>() - .map((e) => FunctionFragment._(e)) - .toList(); + Map get functions => (dartify(impl.functions) + as Map) + .map((key, value) => MapEntry(key, FunctionFragment.from(jsify(value)))); /// Returns the decoded values from the result of a call for [function] (see Specifying Fragments) for the given [data]. /// From 566f36726a51567313cee0da78b71e1105e7109f Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Sat, 6 Nov 2021 02:36:41 +0700 Subject: [PATCH 25/30] package: publish 2.2.0-pre.5 --- CHANGELOG.md | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9631b7d..49ae8e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.2.0-pre.4 +## 2.2.0-pre.5 - Pre-release for utils diff --git a/pubspec.yaml b/pubspec.yaml index 778f91a..92f0cc5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_web3 description: Web3 Ethereum, Etherjs and Wallet Connect wrapper for Flutter Web. Made especially for developing Dapp. -version: 2.2.0-pre.4 +version: 2.2.0-pre.5 repository: https://github.com/y-pakorn/flutter_web3 issue_tracker: https://github.com/y-pakorn/flutter_web3/issues From 4b161c7cf2dfdca11b8916010822d965ddf20a57 Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Sun, 14 Nov 2021 18:26:43 +0700 Subject: [PATCH 26/30] feat: add js package injection support --- lib/flutter_web3.dart | 72 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/lib/flutter_web3.dart b/lib/flutter_web3.dart index 09d49a0..aed1494 100644 --- a/lib/flutter_web3.dart +++ b/lib/flutter_web3.dart @@ -1,5 +1,77 @@ +import 'package:amdjs/amdjs.dart'; + export './ethereum.dart'; export './ethers.dart'; export './src/constant.dart'; export './utils.dart'; export './wallet_connect.dart'; + +/// Static class for injecting required js module. +class FlutterWeb3 { + /// Inject js module that required by this package by [injectionType]. Optinally [version] can be provided, otherwise `latest` is used. + /// + /// --- + /// + /// ```dart + /// void main() async { + /// await FlutterWeb3.inject(FlutterWeb3InjectionTypes.ethers); + /// + /// runApp(MyApp()); + /// } + /// ``` + static Future inject(FlutterWeb3InjectionTypes injectionType, + [String version = 'latest']) async { + AMDJS.verbose = false; + + await AMDJS.require( + injectionType.module, + jsFullPath: injectionType.path.replaceFirst(r'latest', version), + globalJSVariableName: injectionType.variable, + ); + } + + /// Inject all js module that required by this package at `latest` version. + /// + /// --- + /// + /// ```dart + /// void main() async { + /// await FlutterWeb3.injectAll(); + /// + /// runApp(MyApp()); + /// } + /// ``` + static Future injectAll() async { + AMDJS.verbose = false; + await Future.wait(FlutterWeb3InjectionTypes.values.map((e) => inject(e))); + } +} + +/// Available module to inject, used in [FlutterWeb3.inject]. +enum FlutterWeb3InjectionTypes { + ethers, + walletConnect, +} + +extension _InjectionInformation on FlutterWeb3InjectionTypes { + static const _info = { + FlutterWeb3InjectionTypes.ethers: { + 'module': 'ethers', + 'variable': 'ethers', + 'path': + 'https://cdn.jsdelivr.net/npm/ethers@latest/dist/ethers.umd.min.js' + }, + FlutterWeb3InjectionTypes.walletConnect: { + 'module': 'WalletConnectProvider', + 'variable': 'WalletConnectProvider', + 'path': + 'https://cdn.jsdelivr.net/npm/@walletconnect/web3-provider@latest/dist/umd/index.min.js' + } + }; + + String get module => _info[this]!['module']!; + + String get variable => _info[this]!['variable']!; + + String get path => _info[this]!['path']!; +} From 89c080ee506f7cb5bb6daa3abebb6bbf762b662d Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Sun, 14 Nov 2021 18:27:18 +0700 Subject: [PATCH 27/30] package: update package dep --- example/pubspec.lock | 72 +++++++++++++++++++++++++++++++++++++++++++- pubspec.lock | 70 ++++++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 1 + 3 files changed, 142 insertions(+), 1 deletion(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index 9a6a8c2..14eb088 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -1,6 +1,20 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + amdjs: + dependency: transitive + description: + name: amdjs + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.0" async: dependency: transitive description: @@ -50,6 +64,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.3" + dom_tools: + dependency: transitive + description: + name: dom_tools + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + enum_to_string: + dependency: transitive + description: + name: enum_to_string + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" fake_async: dependency: transitive description: @@ -73,7 +101,7 @@ packages: path: ".." relative: true source: path - version: "2.1.0-pre.1" + version: "2.2.0-pre.5" get: dependency: "direct main" description: @@ -81,6 +109,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.1.4" + html_unescape: + dependency: transitive + description: + name: html_unescape + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + intl: + dependency: transitive + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.0" js: dependency: transitive description: @@ -88,6 +130,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.3" + json_object_mapper: + dependency: transitive + description: + name: json_object_mapper + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + markdown: + dependency: transitive + description: + name: markdown + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" matcher: dependency: transitive description: @@ -109,6 +165,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0" + resource_portable: + dependency: transitive + description: + name: resource_portable + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter @@ -142,6 +205,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + swiss_knife: + dependency: transitive + description: + name: swiss_knife + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.8" term_glyph: dependency: transitive description: diff --git a/pubspec.lock b/pubspec.lock index 8695db3..e18f5d4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,20 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + amdjs: + dependency: "direct main" + description: + name: amdjs + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.0" async: dependency: transitive description: @@ -43,6 +57,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.15.0" + dom_tools: + dependency: transitive + description: + name: dom_tools + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + enum_to_string: + dependency: transitive + description: + name: enum_to_string + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" fake_async: dependency: transitive description: @@ -60,6 +88,20 @@ packages: description: flutter source: sdk version: "0.0.0" + html_unescape: + dependency: transitive + description: + name: html_unescape + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + intl: + dependency: transitive + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.0" js: dependency: "direct main" description: @@ -67,6 +109,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.3" + json_object_mapper: + dependency: transitive + description: + name: json_object_mapper + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + markdown: + dependency: transitive + description: + name: markdown + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" matcher: dependency: transitive description: @@ -88,6 +144,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0" + resource_portable: + dependency: transitive + description: + name: resource_portable + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter @@ -121,6 +184,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + swiss_knife: + dependency: transitive + description: + name: swiss_knife + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.8" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 92f0cc5..4853151 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: sdk: flutter js: ^0.6.3 meta: ^1.3.0 + amdjs: ^2.0.1 dev_dependencies: flutter_test: From f5c90bba0376e7320a369e681456fde4add4cabd Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Sun, 14 Nov 2021 18:33:53 +0700 Subject: [PATCH 28/30] fix: correct EthereumException inheritance --- lib/src/ethereum/exception.dart | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/src/ethereum/exception.dart b/lib/src/ethereum/exception.dart index 89778d1..b21f890 100644 --- a/lib/src/ethereum/exception.dart +++ b/lib/src/ethereum/exception.dart @@ -1,24 +1,29 @@ -class EthereumUnrecognizedChainException implements Exception { - final int chainId; +class EthereumException implements Exception { + final int code; + final String message; - EthereumUnrecognizedChainException(this.chainId); + const EthereumException(this.code, this.message); @override - String toString() => - 'EthereumUnrecognizedChainException: Chain $chainId is not recognized, please add the chain using `walletAddChain` first'; + String toString() => 'EthereumException: $code $message'; } -class EthereumUserRejected implements Exception { +class EthereumUnrecognizedChainException extends EthereumException { + final int chainId; + + const EthereumUnrecognizedChainException(this.chainId, + [int code = 4902, String message = '']) + : super(code, message); + @override - String toString() => 'EthereumUserRejected: User rejected the request'; + String toString() => + 'EthereumUnrecognizedChainException: Chain $chainId is not recognized, please add the chain using `walletAddChain` first'; } -class EthereumException implements Exception { - final int code; - final String message; - - EthereumException(this.code, this.message); +class EthereumUserRejected extends EthereumException { + const EthereumUserRejected([int code = 4001, String message = '']) + : super(code, message); @override - String toString() => 'EthereumException: $code $message'; + String toString() => 'EthereumUserRejected: User rejected the request'; } From 1173f213f4ede1f844adcfb9d9c5de8c13aa6cb6 Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Sun, 14 Nov 2021 18:36:59 +0700 Subject: [PATCH 29/30] fix: append error catching to other method in Signer --- lib/src/ethers/signer.dart | 65 +++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/lib/src/ethers/signer.dart b/lib/src/ethers/signer.dart index f628513..95509c1 100644 --- a/lib/src/ethers/signer.dart +++ b/lib/src/ethers/signer.dart @@ -8,25 +8,17 @@ part of ethers; class Signer extends Interop { const Signer._(_SignerImpl impl) : super.internal(impl as T); - /// Returns `true` if an only if object is a [Signer]. - static bool isSigner(Object object) { - if (object is Interop) - return object is Signer || _SignerImpl.isSigner(object.impl); - return false; - } - - Future _call(String method, [List args = const []]) async { - switch (T) { - case BigInt: - return (await _call(method, args)).toBigInt as T; - default: - return promiseToFuture(callMethod(impl, method, args)); - } - } + /// Returns the result of calling using the [request], with this account address being used as the from field. + Future call(TransactionRequest request) => + _call('call', [request.impl]); /// Connect this [Signer] to new [provider]. May simply throw an error if changing providers is not supported. Signer connect(Provider provider) => Signer._(impl.connect(provider.impl)); + /// Returns the result of estimating the cost to send the [request], with this account address being used as the from field. + Future estimateGas(TransactionRequest request) => + _call('estimateGas', [request.impl]); + /// Returns a Future that resolves to the account address. Future getAddress() => _call('getAddress'); @@ -52,10 +44,26 @@ class Signer extends Interop { /// /// The transaction must be valid (i.e. the nonce is correct and the account has sufficient balance to pay for the transaction). Future sendTransaction( - TransactionRequest request) async { - try { - return TransactionResponse._(await _call<_TransactionResponseImpl>( + TransactionRequest request) async => + TransactionResponse._(await _call<_TransactionResponseImpl>( 'sendTransaction', [request.impl])); + + /// Returns a Future which resolves to the Raw Signature of [message]. + Future signMessage(String message) => + _call('signMessage', [message]); + + /// Returns a Future which resolves to the signed transaction of the [request]. This method does not populate any missing fields. + Future signTransaction(TransactionRequest request) => + _call('signTransaction', [request.impl]); + + Future _call(String method, [List args = const []]) async { + try { + switch (T) { + case BigInt: + return (await _call(method, args)).toBigInt as T; + default: + return await promiseToFuture(callMethod(impl, method, args)); + } } catch (error) { final err = dartify(error); switch (err['code']) { @@ -79,19 +87,10 @@ class Signer extends Interop { } } - /// Returns the result of calling using the [request], with this account address being used as the from field. - Future call(TransactionRequest request) => - _call('call', [request.impl]); - - /// Returns the result of estimating the cost to send the [request], with this account address being used as the from field. - Future estimateGas(TransactionRequest request) => - _call('estimateGas', [request.impl]); - - /// Returns a Future which resolves to the signed transaction of the [request]. This method does not populate any missing fields. - Future signTransaction(TransactionRequest request) => - _call('signTransaction', [request.impl]); - - /// Returns a Future which resolves to the Raw Signature of [message]. - Future signMessage(String message) => - _call('signMessage', [message]); + /// Returns `true` if an only if object is a [Signer]. + static bool isSigner(Object object) { + if (object is Interop) + return object is Signer || _SignerImpl.isSigner(object.impl); + return false; + } } From 0f78aff8c5d98d12541d1fd05b6515d7a2655c38 Mon Sep 17 00:00:00 2001 From: Pakorn Nathong Date: Sun, 14 Nov 2021 18:44:34 +0700 Subject: [PATCH 30/30] package: include injector usage to README --- README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5224441..f6e00fe 100644 --- a/README.md +++ b/README.md @@ -70,9 +70,19 @@ To use Ethers JS and Wallet Connect Provider, we need to include script to JS pa ```html - + - + +``` + +Optinally, use injector by asynchronous calling `inject` or `injectAll` before `runApp`. + +```dart +void main() async { + await FlutterWeb3.injectAll(); + + runApp(MyApp()); +} ``` ---