diff --git a/CHANGELOG.md b/CHANGELOG.md
index e29420f..c7b22b6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.2.0-pre.7
+
+- Pre-release for utils
+
## 2.1.9
- Add `toList` for list case of argument (#38)
@@ -23,7 +27,7 @@
## 2.1.3
-- Add provider call error catching
+- Add provider call error catching
- Fix ethereum error not thrown properly
- Add documentation for getFeeData
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());
+}
```
---
diff --git a/example/pubspec.lock b/example/pubspec.lock
index ebe012c..14eb088 100644
--- a/example/pubspec.lock
+++ b/example/pubspec.lock
@@ -1,13 +1,27 @@
# 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:
name: async
url: "https://pub.dartlang.org"
source: hosted
- version: "2.6.1"
+ version: "2.8.1"
boolean_selector:
dependency: transitive
description:
@@ -28,7 +42,7 @@ packages:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
- version: "1.2.0"
+ version: "1.3.1"
clock:
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.0.0-pre.6"
+ 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:
@@ -101,7 +157,7 @@ packages:
name: meta
url: "https://pub.dartlang.org"
source: hosted
- version: "1.3.0"
+ version: "1.7.0"
path:
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:
@@ -155,7 +225,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/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/flutter_web3.dart b/lib/flutter_web3.dart
index 921c3ab..aed1494 100644
--- a/lib/flutter_web3.dart
+++ b/lib/flutter_web3.dart
@@ -1,4 +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']!;
+}
diff --git a/lib/src/ethereum/exception.dart b/lib/src/ethereum/exception.dart
index e5d8e3c..67e0750 100644
--- a/lib/src/ethereum/exception.dart
+++ b/lib/src/ethereum/exception.dart
@@ -1,25 +1,30 @@
-class EthereumUnrecognizedChainException implements Exception {
- final int chainId;
+class EthereumException implements Exception {
+ final int code;
+ final String message;
+ final dynamic data;
- EthereumUnrecognizedChainException(this.chainId);
+ const EthereumException(this.code, this.message, this.data);
@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, null);
+
@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;
- final dynamic data;
-
- EthereumException(this.code, this.message, this.data);
+class EthereumUserRejected extends EthereumException {
+ const EthereumUserRejected([int code = 4001, String message = ''])
+ : super(code, message, null);
@override
- String toString() => 'EthereumException: $code $message';
+ String toString() => 'EthereumUserRejected: User rejected the request';
}
diff --git a/lib/src/ethers/contract.dart b/lib/src/ethers/contract.dart
index 5339299..485ed70 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 1555f73..b880617 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';
@@ -17,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..8264795
--- /dev/null
+++ b/lib/src/ethers/fragment.dart
@@ -0,0 +1,162 @@
+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;
+
+ @override
+ String format([FormatTypes? type]) {
+ return toString();
+ }
+
+ @override
+ String toString() {
+ return 'EventFragment: $name anonymous: $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;
+
+ @override
+ String format([FormatTypes? type]) {
+ return toString();
+ }
+
+ @override
+ String toString() {
+ return 'FunctionFragment: $name constant: $constant stateMutability: $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 72fa499..4813368 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'}
/// ]
/// },
/// ]
@@ -35,6 +35,9 @@ enum FormatTypes {
/// ]
/// ```
full,
+
+ /// '0x70a08231'
+ sighash,
}
/// The Interface Class abstracts the encoding and decoding required to interact with contracts on the Ethereum network.
@@ -53,15 +56,144 @@ class Interface extends Interop<_InterfaceImpl> {
return Interface._(_InterfaceImpl(abi));
}
- Interface._(_InterfaceImpl impl) : super.internal(impl);
+ const Interface._(_InterfaceImpl impl) : super.internal(impl);
+
+ /// The [ConstructorFragment] for the interface.
+ ConstructorFragment get deploy => ConstructorFragment._(impl.deploy);
+
+ /// All the [EventFragment] in the interface.
+ 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.
+ 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].
+ ///
+ /// ---
+ ///
+ /// ```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.
+ /// [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? 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].
///
@@ -82,15 +214,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'}
/// ]
/// },
/// ]
@@ -109,45 +241,58 @@ 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].
///
/// ---
///
/// ```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);
+ /// 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].
///
/// ---
///
/// ```dart
- /// iface.getSighash("balanceOf");
+ /// iface.getSighash('balanceOf');
/// // '0x70a08231'
///
- /// iface.getSighash("balanceOf(address)");
+ /// iface.getSighash('balanceOf(address)');
/// // '0x70a08231'
/// ```
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)}';
}
-
-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;
- }
- }
-}
diff --git a/lib/src/ethers/interop.dart b/lib/src/ethers/interop.dart
index f2dc902..1c1e8bd 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(dynamic source);
+}
+
@JS("Contract")
class _ContractImpl {
external _ContractImpl(String address, dynamic abi, dynamic providerOrSigner);
@@ -34,6 +45,8 @@ class _ContractImpl {
external _SignerImpl? get signer;
+ external _ContractImpl attach(String addressOrName);
+
external _ContractImpl connect(dynamic providerOrSigner);
external int listenerCount([dynamic eventName]);
@@ -60,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(dynamic source);
+}
+
@JS()
@anonymous
class _EventImpl extends _LogImpl {
@@ -116,17 +136,61 @@ 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.FunctionFragment')
+class _FunctionFragmentImpl extends _ConstructorFragmentImpl {
+ external bool get constant;
+
+ external List<_ParamTypeImpl> get outputs;
+
+ external String get stateMutability;
+
+ external static _FunctionFragmentImpl from(dynamic source);
}
@JS("utils.Interface")
class _InterfaceImpl {
external _InterfaceImpl(dynamic abi);
+ external _ConstructorFragmentImpl get deploy;
+
+ external dynamic get events;
+
+ external List<_FragmentImpl> get fragments;
+
+ external dynamic get functions;
+
+ external List decodeFunctionResult(dynamic fragment, String data);
+
+ external List encodeFilterTopics(dynamic fragment, List values);
+
+ external String encodeFunctionData(dynamic fragment, [List? values]);
+
external dynamic format([dynamic types]);
+ external _EventFragmentImpl getEvent(dynamic fragment);
+
external String getEventTopic(String event);
- external String getSighash(String function);
+ external _FunctionFragmentImpl getFunction(dynamic fragment);
+
+ external String getSighash(dynamic function);
}
@JS("providers.JsonRpcProvider")
@@ -177,6 +241,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 {}
diff --git a/lib/src/ethers/signer.dart b/lib/src/ethers/signer.dart
index 9cc1414..8b3eb47 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']) {
@@ -80,19 +88,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;
+ }
}
diff --git a/lib/src/utils/chains.dart b/lib/src/utils/chains.dart
new file mode 100644
index 0000000..df3b88c
--- /dev/null
+++ b/lib/src/utils/chains.dart
@@ -0,0 +1,154 @@
+import 'package:flutter_web3/src/ethereum/ethereum.dart';
+
+enum Chains {
+ Mainnet,
+ Ropsten,
+ Rinkeby,
+ XDai,
+ Polygon,
+ Mumbai,
+ BSCMainnet,
+ BSCTestnet,
+}
+
+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.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.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'] 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']);
+ }
+}
diff --git a/lib/src/utils/erc1155.dart b/lib/src/utils/erc1155.dart
new file mode 100644
index 0000000..ab5e91e
--- /dev/null
+++ b/lib/src/utils/erc1155.dart
@@ -0,0 +1,235 @@
+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 ERC1155
+ 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)',
+ '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)',
+ ];
+
+ /// Ethers Contract object.
+ Contract contract;
+
+ String _uri = '';
+
+ /// Instantiate ERC1155 Contract using default abi if [abi] is not `null`.
+ 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 ?? ContractERC1155.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 [ids] owned by [addresses]
+ Future> balanceOfBatch(
+ 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();
+
+ /// 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());
+ }
+}
+
+/// 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`.
+ 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`.
+ 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`.
+ 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]);
+ }
+}
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 13729c3..7e2408c 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`.
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,
+ ),
+ );
+}
diff --git a/lib/src/utils/multicall.dart b/lib/src/utils/multicall.dart
new file mode 100644
index 0000000..06c69bd
--- /dev/null
+++ b/lib/src/utils/multicall.dart
@@ -0,0 +1,121 @@
+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());
+ }
+
+ 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 {
+ final String address;
+ final String data;
+
+ 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
+ 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)}...';
+ }
+}
diff --git a/lib/utils.dart b/lib/utils.dart
new file mode 100644
index 0000000..3732a9f
--- /dev/null
+++ b/lib/utils.dart
@@ -0,0 +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';
diff --git a/pubspec.lock b/pubspec.lock
index 91cdc9c..e18f5d4 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -1,13 +1,27 @@
# 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:
name: async
url: "https://pub.dartlang.org"
source: hosted
- version: "2.6.1"
+ version: "2.8.1"
boolean_selector:
dependency: transitive
description:
@@ -28,7 +42,7 @@ packages:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
- version: "1.2.0"
+ version: "1.3.1"
clock:
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:
@@ -80,7 +136,7 @@ packages:
name: meta
url: "https://pub.dartlang.org"
source: hosted
- version: "1.3.0"
+ version: "1.7.0"
path:
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:
@@ -134,7 +204,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 b182037..2545948 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.9
+version: 2.2.0-pre.7
repository: https://github.com/y-pakorn/flutter_web3
issue_tracker: https://github.com/y-pakorn/flutter_web3/issues
@@ -13,6 +13,7 @@ dependencies:
sdk: flutter
js: ^0.6.3
meta: ^1.3.0
+ amdjs: ^2.0.1
dev_dependencies:
flutter_test: