From 3ba414eb7992388adf68d995401fca88990a372c Mon Sep 17 00:00:00 2001 From: redDwarf03 Date: Mon, 21 Apr 2025 21:21:36 +0200 Subject: [PATCH] Convert to JS interop --- CHANGELOG.md | 3 + example/metamask/web/main.dart | 110 ++++++------------ .../browser/binance_wallet/credentials.dart | 33 ++++-- .../browser/binance_wallet/dart_wrappers.dart | 19 +-- .../browser/binance_wallet/javascript.dart | 27 ++--- lib/src/browser/js_stub.dart | 35 +++--- lib/src/browser/js_util_stub.dart | 39 ++++--- lib/src/browser/metamask/credentials.dart | 33 ++++-- lib/src/browser/metamask/dart_wrappers.dart | 17 +-- lib/src/browser/metamask/javascript.dart | 33 +++--- lib/src/browser/okx_wallet/credentials.dart | 33 ++++-- lib/src/browser/okx_wallet/dart_wrappers.dart | 19 +-- lib/src/browser/okx_wallet/javascript.dart | 27 ++--- lib/src/contracts/abi/arrays.dart | 2 +- lib/src/contracts/abi/integers.dart | 2 +- lib/src/core/ether_amount.dart | 2 +- lib/src/core/exception_utils_js.dart | 2 +- pubspec.yaml | 4 +- 18 files changed, 231 insertions(+), 209 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 085ccca..b9f1a1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 2.8.0 +- Convert to JS interop + ## 2.7.6 - Update deps diff --git a/example/metamask/web/main.dart b/example/metamask/web/main.dart index ae9bce5..cacbe0b 100644 --- a/example/metamask/web/main.dart +++ b/example/metamask/web/main.dart @@ -1,74 +1,29 @@ import 'dart:convert'; -import 'dart:html'; +import 'dart:js_interop'; +import 'dart:js_util' as js_util; import 'dart:typed_data'; -import 'package:js/js.dart' - if (dart.library.io) 'package:webthree/src/browser/js_stub.dart' - if (dart.library.js) 'package:js/js.dart'; -import 'package:js/js_util.dart' - if (dart.library.io) 'package:webthree/src/browser/js_util_stub.dart' - if (dart.library.js) 'package:js/js_util.dart'; +import 'package:web/web.dart' as web; import 'package:webthree/browser.dart'; import 'package:webthree/webthree.dart'; -//Javascript object conversion -Object mapToJsObject(Map map) { - final object = newObject(); - map.forEach((k, v) { - if (v is Map) { - setProperty(object, k, mapToJsObject(v)); - } else { - setProperty(object, k, v); - } - }); - return object; -} - -Map jsObjectToMap(dynamic jsObject) { - final Map result = {}; - final List keys = _objectKeys(jsObject); - for (final dynamic key in keys) { - final dynamic value = getProperty(jsObject, key); - List nestedKeys = []; - if (value is List) { - nestedKeys = objectKeys(value); - } - if (nestedKeys.isNotEmpty) { - //nested property - result[key] = jsObjectToMap(value); - } else { - result[key] = value; - } - } - return result; -} - -List objectKeys(dynamic jsObject) { - return _objectKeys(jsObject); -} - -@JS('Object.keys') -external List _objectKeys(jsObject); - @JS() @anonymous -class JSrawRequestSwitchChainParams { - external String get chainId; +class _SwitchChainParams { + external JSString get chainId; - // Must have an unnamed factory constructor with named arguments. - external factory JSrawRequestSwitchChainParams({String chainId}); + external factory _SwitchChainParams({JSString chainId}); } @JS('JSON.stringify') -external String stringify(Object obj); -//javascript object conversion ends +external String stringify(JSAny? obj); Future main() async { await metamask(); } Future metamask() async { - final eth = window.ethereum; + final eth = web.window.ethereum; if (eth == null) { print('MetaMask is not available'); return; @@ -78,7 +33,7 @@ Future metamask() async { final client = Web3Client.custom(eth.asRpcService()); final credentials = await eth.requestAccounts(); - print('Using ${credentials[0].address}'); + print('Using ${credentials[0].address.hex}'); print('Client is listening: ${await client.isListeningForNetwork()}'); final message = Uint8List.fromList(utf8.encode('Hello from webthree')); @@ -87,7 +42,7 @@ Future metamask() async { } Future binanceChainWallet() async { - final bsc = window.BinanceChain; + final bsc = web.window.BinanceChain; if (bsc == null) { print('BinanceWallet is not available'); return; @@ -96,7 +51,7 @@ Future binanceChainWallet() async { final client = Web3Client.custom(bsc.asRpcService()); final credentials = await bsc.requestAccounts(); - print('Using ${credentials[0].address}'); + print('Using ${credentials[0].address.hex}'); print('Client is listening: ${await client.isListeningForNetwork()}'); final message = Uint8List.fromList(utf8.encode('Hello from webthree')); @@ -106,7 +61,7 @@ Future binanceChainWallet() async { } Future okxWallet() async { - final okx = window.OkxChainWallet; + final okx = web.window.OkxChainWallet; if (okx == null) { print('OkxChainWallet is not available'); return; @@ -115,7 +70,7 @@ Future okxWallet() async { final client = Web3Client.custom(okx.asRpcService()); final credentials = await okx.requestAccounts(); - print('Using ${credentials[0].address}'); + print('Using ${credentials[0].address.hex}'); print('Client is listening: ${await client.isListeningForNetwork()}'); final message = Uint8List.fromList(utf8.encode('Hello from webthree')); @@ -125,8 +80,7 @@ Future okxWallet() async { } Future addChain() async { - //must assign eth object in function, otherwise rawRequest is not available - final eth = window.ethereum; + final eth = web.window.ethereum; if (eth == null) { print('Wallet is not available'); return; @@ -145,32 +99,40 @@ Future addChain() async { ], 'iconUrls': [''], }; - await eth - .rawRequest('wallet_addEthereumChain', params: [mapToJsObject(params)]); + + try { + await js_util.promiseToFuture(eth.rawRequest('wallet_addEthereumChain', + params: js_util.jsify([params]) as JSObject?)); + print('Dodao network added'); + } on Object catch (e) { + print('Failed to add Dodao network: $e'); + } } Future switchChain() async { - //must assign eth object in function, otherwise rawRequest is not available - final eth = window.ethereum; + final eth = web.window.ethereum; if (eth == null) { print('Wallet is not available'); return; } try { - final chainIdHex = await eth.rawRequest('eth_chainId'); - print('current chain id $chainIdHex'); - } on EthereumException catch (e) { - print('user rejected ${e.message}'); - } - - try { - await eth.rawRequest('wallet_switchEthereumChain', - params: [JSrawRequestSwitchChainParams(chainId: '0xd0da0')]); + final chainIdResult = + await js_util.promiseToFuture(eth.rawRequest('eth_chainId')); + print('Current chain id $chainIdResult'); + + await js_util.promiseToFuture(eth.rawRequest('wallet_switchEthereumChain', + params: js_util.jsify([_SwitchChainParams(chainId: '0xd0da0'.toJS)]) + as JSObject?)); + print('Switched to Dodao network'); } on EthereumException catch (e) { + print('EthereumException during switchChain: ${e.code} ${e.message}'); if (e.code == 4902) { + print('Network not found, attempting to add...'); await addChain(); } else { - print('user rejected ${e.message}'); + print('User rejected switch or other error'); } + } catch (e) { + print('Generic error during switchChain: $e'); } } diff --git a/lib/src/browser/binance_wallet/credentials.dart b/lib/src/browser/binance_wallet/credentials.dart index 0c932eb..61fede5 100644 --- a/lib/src/browser/binance_wallet/credentials.dart +++ b/lib/src/browser/binance_wallet/credentials.dart @@ -1,14 +1,12 @@ -@JS() library webthree.internal.js.creds; +import 'dart:js_interop'; +import 'dart:js_util'; import 'dart:typed_data'; -import 'package:js/js.dart'; import 'package:webthree/webthree.dart'; import '../../../crypto.dart'; - -import 'dart_wrappers.dart'; import 'javascript.dart'; class BinanceWalletCredentials extends CredentialsWithKnownAddress @@ -28,10 +26,19 @@ class BinanceWalletCredentials extends CredentialsWithKnownAddress @override Future signPersonalMessage(Uint8List payload, {int? chainId}) { - return bsc.rawRequest('eth_sign', params: [ + final paramsValue = jsify([ address.hex, _bytesToData(payload), - ]).then(_responseToBytes); + ]); + + final responsePromise = bsc.request(RequestArguments( + method: 'eth_sign', + params: paramsValue as JSAny?, + )); + + return (responsePromise as JSPromise) + .toDart + .then((JSString jsResult) => _responseToBytes(jsResult.toDart)); } @override @@ -45,10 +52,16 @@ class BinanceWalletCredentials extends CredentialsWithKnownAddress data: _bytesToData(transaction.data), ); - return bsc.rawRequest( - 'eth_sendTransaction', - params: [param], - ).then((res) => res as String); + final paramsValue = jsify([param]); + + final responsePromise = bsc.request(RequestArguments( + method: 'eth_sendTransaction', + params: paramsValue as JSAny?, + )); + + return (responsePromise as JSPromise) + .toDart + .then((JSString jsResult) => jsResult.toDart); } } diff --git a/lib/src/browser/binance_wallet/dart_wrappers.dart b/lib/src/browser/binance_wallet/dart_wrappers.dart index ac32c09..ee5f66e 100644 --- a/lib/src/browser/binance_wallet/dart_wrappers.dart +++ b/lib/src/browser/binance_wallet/dart_wrappers.dart @@ -1,6 +1,7 @@ import 'dart:async'; +import 'dart:js_interop'; +import 'dart:js_util'; -import 'package:js/js_util.dart'; import 'package:webthree/src/core/exception_utils_js.dart' if (dart.library.io) 'package:webthree/src/core/exception_utils_io.dart' if (dart.library.js) 'package:webthree/src/core/exception_utils_js.dart'; @@ -41,13 +42,14 @@ extension DartBinanceChain on BinanceChainWallet { /// /// See also: /// - the rpc documentation under https://binance-wallet.gitbook.io/binance-chain-wallet/dev/get-started - Future rawRequest(String method, {Object? params}) { - // No, this can't be simplified. Binance Wallet wants `params` to be undefined. + Future rawRequest(String method, {JSAny? params}) { final args = params == null ? RequestArguments(method: method) : RequestArguments(method: method, params: params); - return promiseToFuture(request(args)).onError((error, stackTrace) { + final jsPromise = request(args); + return jsPromise.toDart.catchError((error, stackTrace) { ExceptionUtils.analyzeException(error!); + throw error; }); } @@ -96,7 +98,8 @@ class _BinanceWalletRpcService extends RpcService { @override Future call(String function, [List? params]) { - return _binancechain.rawRequest(function, params: params).then((res) { + final jsParams = params != null ? jsify(params) as JSAny? : null; + return _binancechain.rawRequest(function, params: jsParams).then((res) { return RPCResponse(0, res); }); } @@ -208,15 +211,15 @@ class _EventStreamSubscription implements StreamSubscription { void _resumeIfNecessary() { if (_onData != null && !isPaused) { - final cb = _jsCallback = allowInterop(_onData!); - _client.on(_eventName, cb); + final cb = _jsCallback = _onData!; + _client.on(_eventName, cb as JSFunction); } } void _stopListening() { final callback = _jsCallback; if (callback != null) { - _client.removeListener(_eventName, callback); + _client.removeListener(_eventName, callback as JSFunction); } } } diff --git a/lib/src/browser/binance_wallet/javascript.dart b/lib/src/browser/binance_wallet/javascript.dart index 5c64d92..4278250 100644 --- a/lib/src/browser/binance_wallet/javascript.dart +++ b/lib/src/browser/binance_wallet/javascript.dart @@ -1,19 +1,16 @@ -@JS() library webthree.internal.js; -import 'dart:html'; +import 'dart:js_interop'; -import 'package:js/js.dart'; import 'package:meta/meta.dart'; - -import 'dart_wrappers.dart'; +import 'package:web/web.dart' as web; @JS('BinanceChain') external BinanceChainWallet? get _binanceChain; /// Extension to load obtain the `BinanceChain` window property injected by /// BinanceChain browser plugins. -extension GetBinanceChain on Window { +extension GetBinanceChain on web.Window { /// Loads the ethereum instance provided by the browser. /// /// For more information on how to use this object with the webthree package, @@ -23,22 +20,26 @@ extension GetBinanceChain on Window { } @JS() -class BinanceChainWallet { +@staticInterop +class BinanceChainWallet {} + +extension BinanceChainWalletExtension on BinanceChainWallet { external int get chainId; - external bool autoRefreshOnNetworkChange; + external bool get autoRefreshOnNetworkChange; + external set autoRefreshOnNetworkChange(bool value); external bool isConnected(); /// This should not be used in user code. Use `stream(event)` instead. @internal - external void on(String event, Function callback); + external void on(String event, JSFunction? callback); /// This should not be used in user code. Use `stream(event)` instead. @internal - external void removeListener(String event, Function callback); + external void removeListener(String event, JSFunction? callback); /// This should not be used in user code. Use `requestRaw` instead. @internal - external Object request(RequestArguments args); + external JSPromise request(RequestArguments args); } @JS() @@ -46,7 +47,7 @@ class BinanceChainWallet { @internal class RequestArguments { external String get method; - external Object? get params; + external JSAny? get params; - external factory RequestArguments({required String method, Object? params}); + external factory RequestArguments({String method, JSAny? params}); } diff --git a/lib/src/browser/js_stub.dart b/lib/src/browser/js_stub.dart index fb24c37..b9052d8 100644 --- a/lib/src/browser/js_stub.dart +++ b/lib/src/browser/js_stub.dart @@ -1,10 +1,11 @@ -library js; - -// export 'dart:js' show allowInterop, allowInteropCaptureThis; +// Stubs for JS interop annotations from dart:js_interop for non-web platforms. +library js_interop_stubs; /// An annotation that indicates a library, class, or member is implemented /// directly in JavaScript. /// +/// Used with `dart:js_interop`. +/// /// All external members of a class or library with this annotation implicitly /// have it as well. /// @@ -16,27 +17,33 @@ class JS { const JS([this.name]); } -class _Anonymous { - const _Anonymous(); -} - -class _StaticInterop { - const _StaticInterop(); -} - /// An annotation that indicates a [JS] annotated class is structural and does /// not have a known JavaScript prototype. /// +/// Used with `dart:js_interop`. +/// /// A class marked with [anonymous] must have an unnamed factory constructor /// with no positional arguments, only named arguments. Invoking the constructor /// desugars to creating a JavaScript object literal with name-value pairs /// corresponding to the parameter names and values. -const _Anonymous anonymous = _Anonymous(); +const Object anonymous = + _Anonymous(); // Type Object pour correspondre à l'annotation réelle /// [staticInterop] enables the [JS] annotated class to be treated as a "static" /// interop class. /// -/// These classes allow interop with native types, like the ones in `dart:html`. +/// Used with `dart:js_interop`. +/// +/// These classes allow interop with native types, like the ones in `package:web`. /// These classes should not contain any instance members, inherited or /// otherwise, and should instead use static extension members. -const _StaticInterop staticInterop = _StaticInterop(); +const Object staticInterop = + _StaticInterop(); // Type Object pour correspondre à l'annotation réelle + +class _Anonymous { + const _Anonymous(); +} + +class _StaticInterop { + const _StaticInterop(); +} diff --git a/lib/src/browser/js_util_stub.dart b/lib/src/browser/js_util_stub.dart index 7a70f4c..237a491 100644 --- a/lib/src/browser/js_util_stub.dart +++ b/lib/src/browser/js_util_stub.dart @@ -2,32 +2,29 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -/// Utility methods to manipulate `package:js` annotated JavaScript interop -/// objects in cases where the name to call is not known at runtime. +/// Utility methods to manipulate JavaScript interop objects dynamically. /// -/// You should only use these methods when the same effect cannot be achieved -/// with `@JS()` annotations. +/// These methods correspond to the utilities available in `dart:js_util` +/// and are used for interacting with JavaScript objects when static typing +/// via `dart:js_interop` is not sufficient or desired. /// /// {@category Web} library dart.js_util; -// Examples can assume: -// class JS { const JS(); } -// class Promise {} - /// Recursively converts a JSON-like collection to JavaScript compatible /// representation. /// /// WARNING: performance of this method is much worse than other util /// methods in this library. Only use this method as a last resort. Prefer -/// instead to use `@anonymous` `@JS()` annotated classes to create map-like -/// objects for JS interop. +/// instead to use `@JS() @anonymous` classes (defined with `dart:js_interop`) +/// to create map-like objects for JS interop. /// /// The argument must be a [Map] or [Iterable], the contents of which are also /// deeply converted. Maps are converted into JavaScript objects. Iterables are -/// converted into arrays. Strings, numbers, bools, and `@JS()` annotated -/// objects are passed through unmodified. Dart objects are also passed through -/// unmodified, but their members aren't usable from JavaScript. +/// converted into arrays. Strings, numbers, bools, and JS interop objects +/// (defined with `dart:js_interop`) are passed through unmodified. Other Dart +/// objects are also passed through unmodified, but their members aren't usable +/// from JavaScript. external dynamic jsify(Object object); external Object get globalThis; @@ -116,14 +113,22 @@ class NullRejectionException implements Exception { /// Converts a JavaScript Promise to a Dart [Future]. /// -/// ```dart template:top +/// (Note: In modern JS interop, prefer using the `.toDart` extension getter +/// on `JSPromise` from `dart:js_interop` when possible). +/// +/// Example (conceptual): +/// ```dart /// @JS() -/// external Promise get threePromise; // Resolves to 3 +/// external JSPromise get threePromise; // Resolves to 3 /// /// void main() async { -/// final Future threeFuture = promiseToFuture(threePromise); +/// // Option 1: using promiseToFuture (less preferred now) +/// final Future threeFuture1 = promiseToFuture(threePromise); +/// final three1 = await threeFuture1; // == 3 /// -/// final three = await threeFuture; // == 3 +/// // Option 2: using .toDart (preferred) +/// final Future threeFuture2 = threePromise.toDart; +/// final three2 = (await threeFuture2).toDartInt; // == 3 /// } /// ``` external Future promiseToFuture(Object jsPromise); diff --git a/lib/src/browser/metamask/credentials.dart b/lib/src/browser/metamask/credentials.dart index 5eb7c0a..288cf2a 100644 --- a/lib/src/browser/metamask/credentials.dart +++ b/lib/src/browser/metamask/credentials.dart @@ -1,14 +1,12 @@ -@JS() library webthree.internal.js.creds; +import 'dart:js_interop'; +import 'dart:js_util'; import 'dart:typed_data'; -import 'package:js/js.dart'; import 'package:webthree/webthree.dart'; import '../../../crypto.dart'; - -import 'dart_wrappers.dart'; import 'javascript.dart'; class MetaMaskCredentials extends CredentialsWithKnownAddress @@ -28,10 +26,19 @@ class MetaMaskCredentials extends CredentialsWithKnownAddress @override Future signPersonalMessage(Uint8List payload, {int? chainId}) { - return ethereum.rawRequest('personal_sign', params: [ + final paramsValue = jsify([ address.hex, _bytesToData(payload), - ]).then(_responseToBytes); + ]); + + final responsePromise = ethereum.request(RequestArguments( + method: 'personal_sign', + params: paramsValue as JSAny?, + )); + + return (responsePromise as JSPromise) + .toDart + .then((JSString jsResult) => _responseToBytes(jsResult.toDart)); } @override @@ -45,10 +52,16 @@ class MetaMaskCredentials extends CredentialsWithKnownAddress data: _bytesToData(transaction.data), ); - return ethereum.rawRequest( - 'eth_sendTransaction', - params: [param], - ).then((res) => res as String); + final paramsValue = jsify([param]); + + final responsePromise = ethereum.request(RequestArguments( + method: 'eth_sendTransaction', + params: paramsValue as JSAny?, + )); + + return (responsePromise as JSPromise) + .toDart + .then((JSString jsResult) => jsResult.toDart); } } diff --git a/lib/src/browser/metamask/dart_wrappers.dart b/lib/src/browser/metamask/dart_wrappers.dart index 7116044..55ac11c 100644 --- a/lib/src/browser/metamask/dart_wrappers.dart +++ b/lib/src/browser/metamask/dart_wrappers.dart @@ -1,6 +1,7 @@ import 'dart:async'; -import 'package:js/js_util.dart'; +import 'dart:js_interop' as js_interop; +import 'dart:js_util'; import 'package:webthree/src/core/exception_utils_js.dart' if (dart.library.io) 'package:webthree/src/core/exception_utils_io.dart' if (dart.library.js) 'package:webthree/src/core/exception_utils_js.dart'; @@ -41,13 +42,14 @@ extension DartEthereum on Ethereum { /// /// See also: /// - the rpc documentation under https://docs.metamask.io/guide/rpc-api.html - Future rawRequest(String method, {Object? params}) { - // No, this can't be simplified. Metamask wants `params` to be undefined. + Future rawRequest(String method, {js_interop.JSAny? params}) { final args = params == null ? RequestArguments(method: method) : RequestArguments(method: method, params: params); - return promiseToFuture(request(args)).onError((error, stackTrace) { + final jsPromise = request(args); + return jsPromise.toDart.catchError((error, stackTrace) { ExceptionUtils.analyzeException(error!); + throw error; }); } @@ -96,7 +98,8 @@ class _MetaMaskRpcService extends RpcService { @override Future call(String function, [List? params]) { - return _ethereum.rawRequest(function, params: params).then((res) { + final jsParams = params != null ? jsify(params) as js_interop.JSAny? : null; + return _ethereum.rawRequest(function, params: jsParams).then((res) { return RPCResponse(0, res); }); } @@ -133,7 +136,7 @@ class _EventStreamSubscription implements StreamSubscription { final String _eventName; Function(dynamic)? _onData; - Function? _jsCallback; + js_interop.JSAny? _jsCallback; int _activePauseRequests = 0; bool _isCancelled = false; @@ -208,7 +211,7 @@ class _EventStreamSubscription implements StreamSubscription { void _resumeIfNecessary() { if (_onData != null && !isPaused) { - final cb = _jsCallback = allowInterop(_onData!); + final cb = _jsCallback = _onData!.toJS; _client.on(_eventName, cb); } } diff --git a/lib/src/browser/metamask/javascript.dart b/lib/src/browser/metamask/javascript.dart index 9828f55..26604be 100644 --- a/lib/src/browser/metamask/javascript.dart +++ b/lib/src/browser/metamask/javascript.dart @@ -1,45 +1,40 @@ -@JS() library webthree.internal.js; -import 'dart:html'; +import 'dart:js_interop'; -import 'package:js/js.dart'; import 'package:meta/meta.dart'; - -import 'dart_wrappers.dart'; +import 'package:web/web.dart' as web; @JS('ethereum') external Ethereum? get _ethereum; -/// Extension to load obtain the `ethereum` window property injected by -/// Ethereum browser plugins. -extension GetEthereum on Window { - /// Loads the ethereum instance provided by the browser. - /// - /// For more information on how to use this object with the webthree package, - /// see the methods on [DartEthereum]. +extension GetEthereum on web.Window { Ethereum? get ethereum => _ethereum; } @JS() -class Ethereum { +@staticInterop +class Ethereum {} + +extension EthereumExtension on Ethereum { external bool get isMetaMask; external bool get isTrust; external int get chainId; - external bool autoRefreshOnNetworkChange; + external bool get autoRefreshOnNetworkChange; + external set autoRefreshOnNetworkChange(bool value); external bool isConnected(); /// This should not be used in user code. Use `stream(event)` instead. @internal - external void on(String event, Function callback); + external void on(String event, JSAny? callback); /// This should not be used in user code. Use `stream(event)` instead. @internal - external void removeListener(String event, Function callback); + external void removeListener(String event, JSAny? callback); /// This should not be used in user code. Use `requestRaw` instead. @internal - external Object request(RequestArguments args); + external JSPromise request(RequestArguments args); } @JS() @@ -47,7 +42,7 @@ class Ethereum { @internal class RequestArguments { external String get method; - external Object? get params; + external JSAny? get params; - external factory RequestArguments({required String method, Object? params}); + external factory RequestArguments({String method, JSAny? params}); } diff --git a/lib/src/browser/okx_wallet/credentials.dart b/lib/src/browser/okx_wallet/credentials.dart index 6b2a6bb..de69e1a 100644 --- a/lib/src/browser/okx_wallet/credentials.dart +++ b/lib/src/browser/okx_wallet/credentials.dart @@ -1,14 +1,12 @@ -@JS() library webthree.internal.js.creds; +import 'dart:js_interop'; +import 'dart:js_util'; import 'dart:typed_data'; -import 'package:js/js.dart'; import 'package:webthree/webthree.dart'; import '../../../crypto.dart'; - -import 'dart_wrappers.dart'; import 'javascript.dart'; class OkxWalletCredentials extends CredentialsWithKnownAddress @@ -28,10 +26,19 @@ class OkxWalletCredentials extends CredentialsWithKnownAddress @override Future signPersonalMessage(Uint8List payload, {int? chainId}) { - return okx.rawRequest('personal_sign', params: [ + final paramsValue = jsify([ address.hex, _bytesToData(payload), - ]).then(_responseToBytes); + ]); + + final responsePromise = okx.request(RequestArguments( + method: 'personal_sign', + params: paramsValue as JSAny?, + )); + + return (responsePromise as JSPromise) + .toDart + .then((JSString jsResult) => _responseToBytes(jsResult.toDart)); } @override @@ -45,10 +52,16 @@ class OkxWalletCredentials extends CredentialsWithKnownAddress data: _bytesToData(transaction.data), ); - return okx.rawRequest( - 'eth_sendTransaction', - params: [param], - ).then((res) => res as String); + final paramsValue = jsify([param]); + + final responsePromise = okx.request(RequestArguments( + method: 'eth_sendTransaction', + params: paramsValue as JSAny?, + )); + + return (responsePromise as JSPromise) + .toDart + .then((JSString jsResult) => jsResult.toDart); } } diff --git a/lib/src/browser/okx_wallet/dart_wrappers.dart b/lib/src/browser/okx_wallet/dart_wrappers.dart index c93540d..0c305bd 100644 --- a/lib/src/browser/okx_wallet/dart_wrappers.dart +++ b/lib/src/browser/okx_wallet/dart_wrappers.dart @@ -1,6 +1,7 @@ import 'dart:async'; +import 'dart:js_interop'; +import 'dart:js_util'; -import 'package:js/js_util.dart'; import 'package:webthree/src/core/exception_utils_js.dart' if (dart.library.io) 'package:webthree/src/core/exception_utils_io.dart' if (dart.library.js) 'package:webthree/src/core/exception_utils_js.dart'; @@ -41,13 +42,14 @@ extension DartOkxWallet on OkxWallet { /// /// See also: /// - the rpc documentation under https://www.okx.com/vi/oktc/docs/dev/api/oktc-api/json-rpc-api - Future rawRequest(String method, {Object? params}) { - // No, this can't be simplified. Okx Wallet wants `params` to be undefined. + Future rawRequest(String method, {JSAny? params}) { final args = params == null ? RequestArguments(method: method) : RequestArguments(method: method, params: params); - return promiseToFuture(request(args)).onError((error, stackTrace) { + final jsPromise = request(args); + return jsPromise.toDart.catchError((error, stackTrace) { ExceptionUtils.analyzeException(error!); + throw error; }); } @@ -96,7 +98,8 @@ class _OkxWalletRpcService extends RpcService { @override Future call(String function, [List? params]) { - return _okxWallet.rawRequest(function, params: params).then((res) { + final jsParams = params != null ? jsify(params) as JSAny? : null; + return _okxWallet.rawRequest(function, params: jsParams).then((res) { return RPCResponse(0, res); }); } @@ -208,15 +211,15 @@ class _EventStreamSubscription implements StreamSubscription { void _resumeIfNecessary() { if (_onData != null && !isPaused) { - final cb = _jsCallback = allowInterop(_onData!); - _client.on(_eventName, cb); + final cb = _jsCallback = _onData!; + _client.on(_eventName, cb as JSFunction); } } void _stopListening() { final callback = _jsCallback; if (callback != null) { - _client.removeListener(_eventName, callback); + _client.removeListener(_eventName, callback as JSFunction); } } } diff --git a/lib/src/browser/okx_wallet/javascript.dart b/lib/src/browser/okx_wallet/javascript.dart index be9ba8b..b4cd055 100644 --- a/lib/src/browser/okx_wallet/javascript.dart +++ b/lib/src/browser/okx_wallet/javascript.dart @@ -1,19 +1,16 @@ -@JS() library webthree.internal.js; -import 'dart:html'; +import 'dart:js_interop'; -import 'package:js/js.dart'; import 'package:meta/meta.dart'; - -import 'dart_wrappers.dart'; +import 'package:web/web.dart' as web; @JS('okxwallet') external OkxWallet? get _okxWallet; /// Extension to load obtain the `okxwallet` window property injected by /// BinanceChain browser plugins. -extension GetOkxWallet on Window { +extension GetOkxWallet on web.Window { /// Loads the ethereum instance provided by the browser. /// /// For more information on how to use this object with the webthree package, @@ -23,22 +20,26 @@ extension GetOkxWallet on Window { } @JS() -class OkxWallet { +@staticInterop +class OkxWallet {} + +extension OkxWalletExtension on OkxWallet { external int get chainId; - external bool autoRefreshOnNetworkChange; + external bool get autoRefreshOnNetworkChange; + external set autoRefreshOnNetworkChange(bool value); external bool isConnected(); /// This should not be used in user code. Use `stream(event)` instead. @internal - external void on(String event, Function callback); + external void on(String event, JSFunction? callback); /// This should not be used in user code. Use `stream(event)` instead. @internal - external void removeListener(String event, Function callback); + external void removeListener(String event, JSFunction? callback); /// This should not be used in user code. Use `requestRaw` instead. @internal - external Object request(RequestArguments args); + external JSPromise request(RequestArguments args); } @JS() @@ -46,7 +47,7 @@ class OkxWallet { @internal class RequestArguments { external String get method; - external Object? get params; + external JSAny? get params; - external factory RequestArguments({required String method, Object? params}); + external factory RequestArguments({String method, JSAny? params}); } diff --git a/lib/src/contracts/abi/arrays.dart b/lib/src/contracts/abi/arrays.dart index d8a60fb..6d3b350 100644 --- a/lib/src/contracts/abi/arrays.dart +++ b/lib/src/contracts/abi/arrays.dart @@ -275,7 +275,7 @@ class DynamicLengthArray extends BaseArrayType { int get hashCode => 31 * type.hashCode; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other is DynamicLengthArray && other.type == type); } diff --git a/lib/src/contracts/abi/integers.dart b/lib/src/contracts/abi/integers.dart index ac93726..a8b2b34 100644 --- a/lib/src/contracts/abi/integers.dart +++ b/lib/src/contracts/abi/integers.dart @@ -161,7 +161,7 @@ class BoolType extends AbiType { int get hashCode => runtimeType.hashCode; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return other.runtimeType == BoolType; } } diff --git a/lib/src/core/ether_amount.dart b/lib/src/core/ether_amount.dart index 8010374..53cda0e 100644 --- a/lib/src/core/ether_amount.dart +++ b/lib/src/core/ether_amount.dart @@ -100,6 +100,6 @@ class EtherAmount { int get hashCode => getInWei.hashCode; @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => other is EtherAmount && other.getInWei == getInWei; } diff --git a/lib/src/core/exception_utils_js.dart b/lib/src/core/exception_utils_js.dart index 987a202..67dd13a 100644 --- a/lib/src/core/exception_utils_js.dart +++ b/lib/src/core/exception_utils_js.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:js/js_util.dart'; +import 'dart:js_util'; import 'package:webthree/src/core/exception.dart'; class ExceptionUtils { diff --git a/pubspec.yaml b/pubspec.yaml index 6107d4f..d513f9b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: webthree description: WebThree - a web3 library for dart for interaction with ethereum nodes using HTTP or WebSocket. Supports custom credentials providers like WalletConnect and Metamask. -version: 2.7.6 +version: 2.8.0 homepage: https://docs.dodao.dev repository: https://github.com/devopsdao/webthree @@ -18,7 +18,6 @@ dependencies: convert: ^3.1.2 dart_style: ^2.3.2 http: ^1.3.0 - js: ^0.6.3 json_rpc_2: ^3.0.3 meta: ^1.9.1 path: ^1.8.3 @@ -28,6 +27,7 @@ dependencies: typed_data: ^1.4.0 universal_html: ^2.2.4 uuid: ^4.5.1 + web: ^1.1.1 dev_dependencies: async: ^2.11.0