From e5df355c6761ab7b660d0a1ae3243ed83eec7479 Mon Sep 17 00:00:00 2001 From: Alfreedom <00tango.bromine@icloud.com> Date: Thu, 7 Dec 2023 16:33:28 +0100 Subject: [PATCH 1/4] fixed an issue with generatedNamespaces during session proposal. Small enhancements in example wallet/dapp. Minor changes and bug fixes --- CHANGELOG.md | 4 + README.md | 45 +++--- .../android/app/src/main/AndroidManifest.xml | 6 + example/dapp/ios/Runner/Info.plist | 17 ++ example/dapp/lib/main.dart | 55 +++---- example/dapp/lib/pages/connect_page.dart | 124 +++++++-------- example/dapp/lib/utils/constants.dart | 2 +- example/dapp/lib/utils/crypto/chain_data.dart | 46 ++---- example/dapp/lib/utils/string_constants.dart | 2 +- .../android/app/src/main/AndroidManifest.xml | 6 + example/wallet/ios/Podfile.lock | 4 +- example/wallet/ios/Runner/Info.plist | 13 ++ .../lib/dependencies/chains/evm_service.dart | 146 +++++++----------- .../lib/dependencies/chains/i_chain.dart | 5 - .../dependencies/key_service/key_service.dart | 9 +- .../lib/dependencies/web3wallet_service.dart | 4 + example/wallet/lib/main.dart | 11 +- .../lib/utils/namespace_model_builder.dart | 22 +-- .../wc_connection_request_widget.dart | 22 +-- lib/apis/utils/constants.dart | 46 +++++- lib/apis/utils/namespace_utils.dart | 19 +-- lib/src/version.dart | 2 +- pubspec.yaml | 2 +- 23 files changed, 306 insertions(+), 306 deletions(-) delete mode 100644 example/wallet/lib/dependencies/chains/i_chain.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 5915794f..9e4cf52e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.1.11 + +- Fixed an issue with `generatedNamespaces` during session proposal + ## 2.1.10 - License change diff --git a/README.md b/README.md index cea39b26..665e5535 100644 --- a/README.md +++ b/README.md @@ -29,15 +29,17 @@ ConnectResponse resp = await wcClient.connect( requiredNamespaces: { 'eip155': RequiredNamespace( chains: ['eip155:1'], // Ethereum chain - methods: ['eth_signTransaction'], // Requestable Methods - events: ['eth_sendTransaction'], // Requestable Events + methods: ['personal_sign'], // Requestable Methods, see MethodsConstants for reference + events: ['chainChanged'], // Requestable Events, see EventsConstants for reference ), - 'kadena': RequiredNamespace( - chains: ['kadena:mainnet01'], // Kadena chain - methods: ['kadena_quicksign_v1'], // Requestable Methods - events: ['kadena_transaction_updated'], // Requestable Events + }, + optionalNamespaces: { + 'eip155': RequiredNamespace( + chains: ['eip155:1', 'eip155:5'], // Any other optional Ethereum chain + methods: ['eth_signTransaction'], // Optional requestable Methods, see MethodsConstants for reference + events: ['accountsChanged'], // Optional requestable events, see EventsConstants for reference ), - } + }, ); Uri? uri = resp.uri; @@ -50,7 +52,7 @@ final dynamic signResponse = await wcClient.request( chainId: 'eip155:1', request: SessionRequestParams( method: 'eth_signTransaction', - params: 'json serializable parameters', + params: '{json serializable parameters}', ), ); // Unpack, or use the signResponse. @@ -85,13 +87,13 @@ else { // You can also respond to events from the wallet, like session events +wcClient.registerEventHandler( + chainId: 'eip155:1', + event: 'accountsChanged', +); wcClient.onSessionEvent.subscribe((SessionEvent? session) { // Do something with the event }); -wcClient.registerEventHandler( - chainId: 'kadena', - event: 'kadena_transaction_updated', -); ``` ### Wallet Flow @@ -112,7 +114,15 @@ late int id; wcClient.onSessionProposal.subscribe((SessionProposal? args) async { // Handle UI updates using the args.params // Keep track of the args.id for the approval response - id = args!.id; + if (args != null) { + id = args!.id; + // To check VerifyAPI validation in regards of the dApp is trying to connnect you can check verifyContext + // More info about VerifyAPI https://docs.walletconnect.com/web3wallet/verify + final isScamApp = args.verifyContext?.validation.scam; + final isInvalidApp = args.verifyContext?.validation.invalid; + final isValidApp = args.verifyContext?.validation.valid; + final unknown = args.verifyContext?.validation.unknown; + } }); // Also setup the methods and chains that your wallet supports @@ -212,16 +222,13 @@ final walletNamespaces = { 'eip155': Namespace( accounts: ['eip155:1:abc'], methods: ['eth_signTransaction'], - ), - 'kadena': Namespace( - accounts: ['kadena:mainnet01:abc'], - methods: ['kadena_sign_v1', 'kadena_quicksign_v1'], - events: ['kadena_transaction_updated'], + events: ['accountsChanged'], ), } await wcClient.approveSession( id: id, namespaces: walletNamespaces // This will have the accounts requested in params + // If you registered correctly events emitters, methods handlers and accounts for your supported chains you can just us `args.params.generatedNamespaces!` value from SessionProposalEvent ); // Or to reject... // Error codes and reasons can be found here: https://docs.walletconnect.com/2.0/specs/clients/sign/error-codes @@ -246,7 +253,7 @@ await wcClient.respondAuthRequest( // Error codes and reasons can be found here: https://docs.walletconnect.com/2.0/specs/clients/sign/error-codes await wcClient.respondAuthRequest( id: args.id, - iss: 'did:pkh:eip155:1:0x06C6A22feB5f8CcEDA0db0D593e6F26A3611d5fa', + iss: 'did:pkh:eip155:1:ETH_ADDRESS', error: Errors.getSdkError(Errors.USER_REJECTED_AUTH), ); diff --git a/example/dapp/android/app/src/main/AndroidManifest.xml b/example/dapp/android/app/src/main/AndroidManifest.xml index 577d92aa..27e113ac 100644 --- a/example/dapp/android/app/src/main/AndroidManifest.xml +++ b/example/dapp/android/app/src/main/AndroidManifest.xml @@ -23,6 +23,12 @@ + + + + + + diff --git a/example/dapp/ios/Runner/Info.plist b/example/dapp/ios/Runner/Info.plist index 5109ec5f..909bdb52 100644 --- a/example/dapp/ios/Runner/Info.plist +++ b/example/dapp/ios/Runner/Info.plist @@ -49,5 +49,22 @@ LSApplicationCategoryType + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleURLSchemes + + myflutterdapp + + + + LSApplicationQueriesSchemes + + myflutterwallet + diff --git a/example/dapp/lib/main.dart b/example/dapp/lib/main.dart index d238b565..4dd6f766 100644 --- a/example/dapp/lib/main.dart +++ b/example/dapp/lib/main.dart @@ -69,6 +69,10 @@ class _MyHomePageState extends State { description: 'Flutter WalletConnect Dapp Example', url: 'https://walletconnect.com/', icons: ['https://walletconnect.com/walletconnect-logo.png'], + redirect: Redirect( + native: 'myflutterdapp://', + universal: 'https://walletconnect.com', + ), ), ); @@ -83,6 +87,8 @@ class _MyHomePageState extends State { // Register event handlers _web3App!.onSessionPing.subscribe(_onSessionPing); _web3App!.onSessionEvent.subscribe(_onSessionEvent); + _web3App!.core.relayClient.onRelayClientConnect.subscribe(_setState); + _web3App!.core.relayClient.onRelayClientDisconnect.subscribe(_setState); setState(() { _pageDatas = [ @@ -115,8 +121,12 @@ class _MyHomePageState extends State { // } } + void _setState(dynamic args) => setState(() {}); + @override void dispose() { + _web3App!.core.relayClient.onRelayClientConnect.unsubscribe(_setState); + _web3App!.core.relayClient.onRelayClientDisconnect.unsubscribe(_setState); _web3App!.onSessionPing.unsubscribe(_onSessionPing); _web3App!.onSessionEvent.unsubscribe(_onSessionEvent); super.dispose(); @@ -146,20 +156,18 @@ class _MyHomePageState extends State { right: StyleConstants.magic20, child: Row( children: [ - // Disconnect buttons for testing - _buildIconButton( - Icons.discord, - () { - _web3App!.core.relayClient.disconnect(); - }, - ), - const SizedBox( - width: StyleConstants.magic20, - ), - _buildIconButton( - Icons.connect_without_contact, - () { - _web3App!.core.relayClient.connect(); + Text(_web3App!.core.relayClient.isConnected + ? 'Relay Connected' + : 'Relay Disconnected'), + Switch( + value: _web3App!.core.relayClient.isConnected, + onChanged: (value) { + if (!value) { + _web3App!.core.relayClient.disconnect(); + } else { + _web3App!.core.relayClient.connect(); + } + setState(() {}); }, ), ], @@ -252,23 +260,4 @@ class _MyHomePageState extends State { }, ); } - - Widget _buildIconButton(IconData icon, void Function()? onPressed) { - return Container( - decoration: BoxDecoration( - color: StyleConstants.primaryColor, - borderRadius: BorderRadius.circular( - StyleConstants.linear48, - ), - ), - child: IconButton( - icon: Icon( - icon, - color: StyleConstants.titleTextColor, - ), - iconSize: StyleConstants.linear24, - onPressed: onPressed, - ), - ); - } } diff --git a/example/dapp/lib/pages/connect_page.dart b/example/dapp/lib/pages/connect_page.dart index e3f98110..e4bebdf7 100644 --- a/example/dapp/lib/pages/connect_page.dart +++ b/example/dapp/lib/pages/connect_page.dart @@ -8,7 +8,6 @@ import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; import 'package:walletconnect_flutter_v2_dapp/models/chain_metadata.dart'; import 'package:walletconnect_flutter_v2_dapp/utils/constants.dart'; import 'package:walletconnect_flutter_v2_dapp/utils/crypto/chain_data.dart'; -import 'package:walletconnect_flutter_v2_dapp/utils/crypto/helpers.dart'; import 'package:walletconnect_flutter_v2_dapp/utils/string_constants.dart'; import 'package:walletconnect_flutter_v2_dapp/widgets/chain_button.dart'; @@ -27,27 +26,19 @@ class ConnectPage extends StatefulWidget { class ConnectPageState extends State { bool _testnetOnly = false; final List _selectedChains = []; - bool _shouldDismissQrCode = true; - void setTestnet(bool value) { - if (value != _testnetOnly) { - _selectedChains.clear(); - } - _testnetOnly = value; - } - @override Widget build(BuildContext context) { // Build the list of chain buttons, clear if the textnet changed final List chains = _testnetOnly ? ChainData.testChains : ChainData.mainChains; - List chainButtons = []; + List children = []; for (final ChainMetadata chain in chains) { // Build the button - chainButtons.add( + children.add( ChainButton( chain: chain, onPressed: () { @@ -64,44 +55,46 @@ class ConnectPageState extends State { ); } + children.add(const SizedBox.square(dimension: 12.0)); + // Add a connect button - chainButtons.add( - Container( - width: double.infinity, - height: StyleConstants.linear48, - margin: const EdgeInsets.symmetric( - vertical: StyleConstants.linear8, - ), - child: ElevatedButton( - onPressed: () => _onConnect( - _selectedChains, - showToast: (m) async { - await showPlatformToast(child: Text(m), context: context); + children.add( + ElevatedButton( + onPressed: _selectedChains.isEmpty + ? null + : () => _onConnect(showToast: (m) async { + await showPlatformToast(child: Text(m), context: context); + }), + style: ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled)) { + return StyleConstants.grayColor; + } + return StyleConstants.primaryColor; }, ), - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - StyleConstants.primaryColor, - ), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - StyleConstants.linear8, - ), + minimumSize: MaterialStateProperty.all(const Size( + 1000.0, + StyleConstants.linear48, + )), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + StyleConstants.linear8, ), ), ), - child: const Text( - StringConstants.connect, - style: StyleConstants.buttonText, - ), + ), + child: const Text( + StringConstants.connect, + style: StyleConstants.buttonText, ), ), ); return Center( child: Container( - // color: StyleConstants.primaryColor, padding: const EdgeInsets.all( StyleConstants.linear8, ), @@ -110,16 +103,13 @@ class ConnectPageState extends State { ), child: ListView( children: [ - const SizedBox( - height: StyleConstants.linear48, - ), const Text( StringConstants.appTitle, style: StyleConstants.titleText, textAlign: TextAlign.center, ), const SizedBox( - height: StyleConstants.linear48, + height: StyleConstants.linear16, ), const Text( StringConstants.selectChains, @@ -127,7 +117,7 @@ class ConnectPageState extends State { textAlign: TextAlign.center, ), const SizedBox( - height: StyleConstants.linear24, + height: StyleConstants.linear16, ), SizedBox( height: StyleConstants.linear48, @@ -142,6 +132,7 @@ class ConnectPageState extends State { value: _testnetOnly, onChanged: (value) { setState(() { + _selectedChains.clear(); _testnetOnly = value; }); }, @@ -152,7 +143,7 @@ class ConnectPageState extends State { Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, - children: chainButtons, + children: children, ), ], ), @@ -160,43 +151,32 @@ class ConnectPageState extends State { ); } - Future _onConnect( - List chains, { - Function(String message)? showToast, - }) async { - // Use the chain metadata to build the required namespaces: - // Get the methods, get the events - final Map requiredNamespaces = {}; - for (final chain in chains) { - // If the chain is already in the required namespaces, add it to the chains list - final String chainName = chain.chainId.split(':')[0]; - if (requiredNamespaces.containsKey(chainName)) { - requiredNamespaces[chainName]!.chains!.add(chain.chainId); - continue; - } - final RequiredNamespace rNamespace = RequiredNamespace( - chains: [chain.chainId], - methods: getChainMethods(chain.type), - events: getChainEvents(chain.type), - ); - requiredNamespaces[chainName] = rNamespace; - } - debugPrint('Required namespaces: $requiredNamespaces'); - - // Send off a connect + Future _onConnect({Function(String message)? showToast}) async { debugPrint('Creating connection and session'); + // It is currently safer to send chains approvals on optionalNamespaces + // but depending on Wallet implementation you may need to send some (for innstance eip155:1) as required final ConnectResponse res = await widget.web3App.connect( - optionalNamespaces: requiredNamespaces, + requiredNamespaces: { + 'eip155': const RequiredNamespace( + chains: [], + methods: MethodsConstants.requiredMethods, + events: EventsConstants.requiredEvents, + ), + }, + optionalNamespaces: { + 'eip155': RequiredNamespace( + chains: _selectedChains.map((c) => c.chainId).toList(), + methods: MethodsConstants.optionalMethods, + events: EventsConstants.optionalEvents, + ), + }, ); - // debugPrint('Connection created, connection response: ${res.uri}'); - // print(res.uri!.toString()); _showQrCode(res); try { debugPrint('Awaiting session proposal settlement'); final _ = await res.session.future; - // print(sessionData); showToast?.call(StringConstants.connectionEstablished); @@ -205,7 +185,7 @@ class ConnectPageState extends State { final AuthRequestResponse authRes = await widget.web3App.requestAuth( pairingTopic: res.pairingTopic, params: AuthRequestParams( - chainId: chains[0].chainId, + chainId: _selectedChains[0].chainId, domain: Constants.domain, aud: Constants.aud, // statement: 'Welcome to example flutter app', diff --git a/example/dapp/lib/utils/constants.dart b/example/dapp/lib/utils/constants.dart index 954fb4d2..14612b24 100644 --- a/example/dapp/lib/utils/constants.dart +++ b/example/dapp/lib/utils/constants.dart @@ -27,7 +27,7 @@ class StyleConstants { static const double magic10 = 10; static const double magic14 = 14; static const double magic20 = 20; - static const double magic40 = 40; + static const double magic40 = 28; static const double magic64 = 64; // Width diff --git a/example/dapp/lib/utils/crypto/chain_data.dart b/example/dapp/lib/utils/crypto/chain_data.dart index 5b775089..301af08d 100644 --- a/example/dapp/lib/utils/crypto/chain_data.dart +++ b/example/dapp/lib/utils/crypto/chain_data.dart @@ -19,27 +19,22 @@ class ChainData { color: Colors.purple.shade300, rpc: ['https://polygon-rpc.com/'], ), - // const ChainMetadata( - // type: ChainType.solana, - // chainId: 'solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ', - // name: 'Solana', - // logo: 'TODO', - // color: Colors.black, - // rpc: [ - // "https://api.mainnet-beta.solana.com", - // "https://solana-api.projectserum.com", - // ], - // ), - // ChainMetadata( - // type: ChainType.kadena, - // chainId: 'kadena:mainnet01', - // name: 'Kadena', - // logo: 'TODO', - // color: Colors.purple.shade600, - // rpc: [ - // "https://api.testnet.chainweb.com", - // ], - // ), + const ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:42161', + name: 'Arbitrum', + logo: '/chain-logos/eip155-42161.png', + color: Colors.black, + rpc: ['https://arbitrum.blockpi.network/v1/rpc/public'], + ), + const ChainMetadata( + type: ChainType.eip155, + chainId: 'eip155:43114', + name: 'Avalanche', + logo: '/chain-logos/eip155-43114.png', + color: Colors.red, + rpc: ['https://api.avax.network/ext/bc/C/rpc'], + ), ]; static final List testChains = [ @@ -61,15 +56,6 @@ class ChainData { isTestnet: true, rpc: ['https://matic-mumbai.chainstacklabs.com'], ), - // const ChainMetadata( - // type: ChainType.eip155, - // chainId: 'solana:8E9rvCKLFQia2Y35HXjjpWzj8weVo44K', - // name: 'Solana Devnet', - // logo: 'TODO', - // color: Colors.black, - // isTestnet: true, - // rpc: ["https://api.devnet.solana.com"], - // ), ChainMetadata( type: ChainType.kadena, chainId: 'kadena:testnet04', diff --git a/example/dapp/lib/utils/string_constants.dart b/example/dapp/lib/utils/string_constants.dart index 4cec305a..0d0396f1 100644 --- a/example/dapp/lib/utils/string_constants.dart +++ b/example/dapp/lib/utils/string_constants.dart @@ -6,7 +6,7 @@ class StringConstants { static const String delete = 'Delete'; // Main Page - static const String appTitle = 'Wallet Connect v2 Flutter dApp Demo'; + static const String appTitle = 'WalletConnect v2\nFlutter dApp Demo'; static const String connectPageTitle = 'Connect'; static const String pairingsPageTitle = 'Pairings'; static const String sessionsPageTitle = 'Sessions'; diff --git a/example/wallet/android/app/src/main/AndroidManifest.xml b/example/wallet/android/app/src/main/AndroidManifest.xml index 636a1ade..63e1f074 100644 --- a/example/wallet/android/app/src/main/AndroidManifest.xml +++ b/example/wallet/android/app/src/main/AndroidManifest.xml @@ -23,6 +23,12 @@ + + + + + + diff --git a/example/wallet/ios/Podfile.lock b/example/wallet/ios/Podfile.lock index db6c3de3..2a1d5c69 100644 --- a/example/wallet/ios/Podfile.lock +++ b/example/wallet/ios/Podfile.lock @@ -48,7 +48,7 @@ PODS: - GTMSessionFetcher/Core (< 3.0, >= 1.1) - MLImage (= 1.0.0-beta4) - MLKitCommon (~> 9.0) - - mobile_scanner (3.2.0): + - mobile_scanner (3.5.2): - Flutter - GoogleMLKit/BarcodeScanning (~> 4.0.0) - nanopb (2.30909.0): @@ -106,7 +106,7 @@ SPEC CHECKSUMS: MLKitBarcodeScanning: 04e264482c5f3810cb89ebc134ef6b61e67db505 MLKitCommon: c1b791c3e667091918d91bda4bba69a91011e390 MLKitVision: 8baa5f46ee3352614169b85250574fde38c36f49 - mobile_scanner: 47056db0c04027ea5f41a716385542da28574662 + mobile_scanner: 5090a13b7a35fc1c25b0d97e18e84f271a6eb605 nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431 package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef diff --git a/example/wallet/ios/Runner/Info.plist b/example/wallet/ios/Runner/Info.plist index 89968174..fe18a95e 100644 --- a/example/wallet/ios/Runner/Info.plist +++ b/example/wallet/ios/Runner/Info.plist @@ -49,5 +49,18 @@ UIViewControllerBasedStatusBarAppearance + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleURLSchemes + + myflutterwallet + + + diff --git a/example/wallet/lib/dependencies/chains/evm_service.dart b/example/wallet/lib/dependencies/chains/evm_service.dart index 233ff47a..d42c4ad2 100644 --- a/example/wallet/lib/dependencies/chains/evm_service.dart +++ b/example/wallet/lib/dependencies/chains/evm_service.dart @@ -7,8 +7,6 @@ import 'dart:typed_data'; import 'package:convert/convert.dart'; import 'package:eth_sig_util/eth_sig_util.dart'; import 'package:get_it/get_it.dart'; -import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; -import 'package:walletconnect_flutter_v2_wallet/dependencies/chains/i_chain.dart'; import 'package:walletconnect_flutter_v2_wallet/dependencies/bottom_sheet/i_bottom_sheet_service.dart'; import 'package:walletconnect_flutter_v2_wallet/dependencies/i_web3wallet_service.dart'; import 'package:walletconnect_flutter_v2_wallet/dependencies/key_service/chain_key.dart'; @@ -20,107 +18,87 @@ import 'package:walletconnect_flutter_v2_wallet/widgets/wc_connection_widget/wc_ import 'package:walletconnect_flutter_v2_wallet/widgets/wc_request_widget.dart/wc_request_widget.dart'; import 'package:web3dart/web3dart.dart'; -enum EVMChainId { +enum EVMChainsSupported { ethereum, polygon, + arbitrum, goerli, bsc, - mumbai, -} + mumbai; -extension KadenaChainIdX on EVMChainId { String chain() { - String name = ''; + String id = ''; switch (this) { - case EVMChainId.ethereum: - name = '1'; + case EVMChainsSupported.ethereum: + id = '1'; + break; + case EVMChainsSupported.polygon: + id = '137'; break; - case EVMChainId.polygon: - name = '137'; + case EVMChainsSupported.arbitrum: + id = '42161'; break; - case EVMChainId.goerli: - name = '5'; + case EVMChainsSupported.goerli: + id = '5'; break; - case EVMChainId.bsc: - name = '56'; + case EVMChainsSupported.bsc: + id = '56'; break; - case EVMChainId.mumbai: - name = '80001'; + case EVMChainsSupported.mumbai: + id = '80001'; break; } - return '${EVMService.namespace}:$name'; + return 'eip155:$id'; } } -class EVMService extends IChain { - static const namespace = 'eip155'; - static const pSign = 'personal_sign'; - static const eSign = 'eth_sign'; - static const eSignTransaction = 'eth_signTransaction'; - static const eSignTypedData = 'eth_signTypedData'; - static const eSendTransaction = 'eth_sendTransaction'; - +class EVMService { final IBottomSheetService _bottomSheetService = GetIt.I(); final IWeb3WalletService _web3WalletService = GetIt.I(); - final EVMChainId reference; + final EVMChainsSupported chainSupported; final Web3Client ethClient; - EVMService({ - required this.reference, - Web3Client? ethClient, - }) : ethClient = ethClient ?? + EVMService({required this.chainSupported, Web3Client? ethClient}) + : ethClient = ethClient ?? Web3Client( 'https://mainnet.infura.io/v3/51716d2096df4e73bec298680a51f0c5', http.Client()) { - final Web3Wallet wallet = _web3WalletService.getWeb3Wallet(); - for (final String event in getEvents()) { - wallet.registerEventEmitter(chainId: getChainId(), event: event); + final wallet = _web3WalletService.getWeb3Wallet(); + // Supported events + final supportedEvents = [ + 'chainChanged', + 'accountsChanged' + ]; // add whatever event you want to support + for (final String event in supportedEvents) { + print('Supported event ${chainSupported.chain()} $event'); + wallet.registerEventEmitter( + chainId: chainSupported.chain(), + event: event, + ); + } + // Supported methods + Map methodsHandlers = { + 'personal_sign': personalSign, + 'eth_sign': ethSign, + 'eth_signTransaction': ethSignTransaction, + 'eth_signTypedData': ethSignTypedData, + 'eth_sendTransaction': ethSignTransaction, + // add whatever method/handler you want to support + // 'eth_signTypedData_v4': ethSignTypedDataV4, + }; + + for (var handler in methodsHandlers.entries) { + wallet.registerRequestHandler( + chainId: chainSupported.chain(), + method: handler.key, + handler: handler.value, + ); } - wallet.registerRequestHandler( - chainId: getChainId(), - method: pSign, - handler: personalSign, - ); - wallet.registerRequestHandler( - chainId: getChainId(), - method: eSign, - handler: ethSign, - ); - wallet.registerRequestHandler( - chainId: getChainId(), - method: eSignTransaction, - handler: ethSignTransaction, - ); - wallet.registerRequestHandler( - chainId: getChainId(), - method: eSendTransaction, - handler: ethSignTransaction, - ); - wallet.registerRequestHandler( - chainId: getChainId(), - method: eSignTypedData, - handler: ethSignTypedData, - ); - } - - @override - String getNamespace() { - return namespace; - } - - @override - String getChainId() { - return reference.chain(); - } - - @override - List getEvents() { - return ['chainChanged', 'accountsChanged']; } Future requestAuthorization(String text) async { @@ -128,11 +106,7 @@ class EVMService extends IChain { widget: WCRequestWidget( child: WCConnectionWidget( title: 'Sign Transaction', - info: [ - WCConnectionModel( - text: text, - ), - ], + info: [WCConnectionModel(text: text)], ), ), ); @@ -157,7 +131,7 @@ class EVMService extends IChain { try { // Load the private key final List keys = GetIt.I().getKeysForChain( - getChainId(), + chainSupported.chain(), ); final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey); @@ -176,7 +150,7 @@ class EVMService extends IChain { } } - Future ethSign(String topic, dynamic parameters) async { + Future ethSign(String topic, dynamic parameters) async { print('received eth sign request: $parameters'); final String message = EthUtils.getUtf8Message(parameters[1]); @@ -189,7 +163,7 @@ class EVMService extends IChain { try { // Load the private key final List keys = GetIt.I().getKeysForChain( - getChainId(), + chainSupported.chain(), ); // print('private key'); // print(keys[0].privateKey); @@ -220,7 +194,7 @@ class EVMService extends IChain { } } - Future ethSignTransaction(String topic, dynamic parameters) async { + Future ethSignTransaction(String topic, dynamic parameters) async { print('received eth sign transaction request: $parameters'); final String? authAcquired = await requestAuthorization( jsonEncode( @@ -233,7 +207,7 @@ class EVMService extends IChain { // Load the private key final List keys = GetIt.I().getKeysForChain( - getChainId(), + chainSupported.chain(), ); final Credentials credentials = EthPrivateKey.fromHex( '0x${keys[0].privateKey}', @@ -294,7 +268,7 @@ class EVMService extends IChain { } } - Future ethSignTypedData(String topic, dynamic parameters) async { + Future ethSignTypedData(String topic, dynamic parameters) async { print('received eth sign typed data request: $parameters'); final String data = parameters[1]; final String? authAcquired = await requestAuthorization(data); @@ -303,7 +277,7 @@ class EVMService extends IChain { } final List keys = GetIt.I().getKeysForChain( - getChainId(), + chainSupported.chain(), ); // EthPrivateKey credentials = EthPrivateKey.fromHex(keys[0].privateKey); diff --git a/example/wallet/lib/dependencies/chains/i_chain.dart b/example/wallet/lib/dependencies/chains/i_chain.dart deleted file mode 100644 index f3a3e36b..00000000 --- a/example/wallet/lib/dependencies/chains/i_chain.dart +++ /dev/null @@ -1,5 +0,0 @@ -abstract class IChain { - String getNamespace(); - String getChainId(); - List getEvents(); -} diff --git a/example/wallet/lib/dependencies/key_service/key_service.dart b/example/wallet/lib/dependencies/key_service/key_service.dart index dce0c503..0bea000b 100644 --- a/example/wallet/lib/dependencies/key_service/key_service.dart +++ b/example/wallet/lib/dependencies/key_service/key_service.dart @@ -1,3 +1,4 @@ +import 'package:walletconnect_flutter_v2_wallet/dependencies/chains/evm_service.dart'; import 'package:walletconnect_flutter_v2_wallet/dependencies/key_service/chain_key.dart'; import 'package:walletconnect_flutter_v2_wallet/dependencies/key_service/i_key_service.dart'; import 'package:walletconnect_flutter_v2_wallet/utils/dart_defines.dart'; @@ -14,13 +15,7 @@ class KeyService extends IKeyService { publicKey: DartDefines.kadenaPublicKey, ), ChainKey( - chains: [ - 'eip155:1', - 'eip155:5', - 'eip155:56', - 'eip155:137', - 'eip155:80001', - ], + chains: EVMChainsSupported.values.map((e) => e.chain()).toList(), privateKey: '300851edb635b2dbb2d4e70615444925afeb60bf95c19365aff88740e09d7345', publicKey: diff --git a/example/wallet/lib/dependencies/web3wallet_service.dart b/example/wallet/lib/dependencies/web3wallet_service.dart index 8a570ae3..3cca879c 100644 --- a/example/wallet/lib/dependencies/web3wallet_service.dart +++ b/example/wallet/lib/dependencies/web3wallet_service.dart @@ -49,6 +49,10 @@ class Web3WalletService extends IWeb3WalletService { icons: [ 'https://github.com/WalletConnect/Web3ModalFlutter/blob/master/assets/png/logo_wc.png' ], + redirect: Redirect( + native: 'myflutterwallet://', + universal: 'https://walletconnect.com', + ), ), ); diff --git a/example/wallet/lib/main.dart b/example/wallet/lib/main.dart index 1e50c7f1..27f79c2d 100644 --- a/example/wallet/lib/main.dart +++ b/example/wallet/lib/main.dart @@ -2,9 +2,8 @@ import 'package:get_it/get_it.dart'; import 'package:get_it_mixin/get_it_mixin.dart'; import 'package:walletconnect_flutter_v2_wallet/dependencies/bottom_sheet/bottom_sheet_listener.dart'; import 'package:walletconnect_flutter_v2_wallet/dependencies/bottom_sheet/bottom_sheet_service.dart'; -import 'package:walletconnect_flutter_v2_wallet/dependencies/chains/evm_service.dart'; -import 'package:walletconnect_flutter_v2_wallet/dependencies/chains/i_chain.dart'; import 'package:walletconnect_flutter_v2_wallet/dependencies/bottom_sheet/i_bottom_sheet_service.dart'; +import 'package:walletconnect_flutter_v2_wallet/dependencies/chains/evm_service.dart'; import 'package:walletconnect_flutter_v2_wallet/dependencies/i_web3wallet_service.dart'; import 'package:walletconnect_flutter_v2_wallet/dependencies/key_service/i_key_service.dart'; import 'package:walletconnect_flutter_v2_wallet/dependencies/key_service/key_service.dart'; @@ -137,10 +136,10 @@ class _MyHomePageState extends State with GetItStateMixin { // ); // } - for (final cId in EVMChainId.values) { - GetIt.I.registerSingleton( - EVMService(reference: cId), - instanceName: cId.chain(), + for (final supportedChain in EVMChainsSupported.values) { + GetIt.I.registerSingleton( + EVMService(chainSupported: supportedChain), + instanceName: supportedChain.chain(), ); } diff --git a/example/wallet/lib/utils/namespace_model_builder.dart b/example/wallet/lib/utils/namespace_model_builder.dart index 28177e8e..890b3860 100644 --- a/example/wallet/lib/utils/namespace_model_builder.dart +++ b/example/wallet/lib/utils/namespace_model_builder.dart @@ -7,21 +7,21 @@ import 'package:walletconnect_flutter_v2_wallet/widgets/wc_connection_widget/wc_ class ConnectionWidgetBuilder { static List buildFromRequiredNamespaces( - Map requiredNamespaces, + Map generatedNamespaces, ) { final List views = []; - for (final key in requiredNamespaces.keys) { - RequiredNamespace ns = requiredNamespaces[key]!; + for (final key in generatedNamespaces.keys) { + Namespace ns = generatedNamespaces[key]!; final List models = []; // If the chains property is present, add the chain data to the models - if (ns.chains != null) { - models.add( - WCConnectionModel( - title: StringConstants.chains, - elements: ns.chains!, - ), - ); - } + models.add( + WCConnectionModel( + title: StringConstants.chains, + elements: ns.accounts.map((acc) { + return NamespaceUtils.getChainFromAccount(acc); + }).toList(), + ), + ); models.add(WCConnectionModel( title: StringConstants.methods, elements: ns.methods, diff --git a/example/wallet/lib/widgets/wc_connection_request/wc_connection_request_widget.dart b/example/wallet/lib/widgets/wc_connection_request/wc_connection_request_widget.dart index 690cee63..97957cf1 100644 --- a/example/wallet/lib/widgets/wc_connection_request/wc_connection_request_widget.dart +++ b/example/wallet/lib/widgets/wc_connection_request/wc_connection_request_widget.dart @@ -55,8 +55,8 @@ class WCConnectionRequestWidget extends StatelessWidget { ), const SizedBox(height: StyleConstants.linear8), authRequest != null - ? _buildAuthRequest() - : _buildSessionProposal(context), + ? _buildAuthRequestView() + : _buildSessionProposalView(context), ], ), ); @@ -70,7 +70,7 @@ class WCConnectionRequestWidget extends StatelessWidget { ); } - Widget _buildAuthRequest() { + Widget _buildAuthRequestView() { final model = WCConnectionModel( text: wallet.formatAuthMessage( iss: 'did:pkh:eip155:1:${authRequest!.iss}', @@ -86,26 +86,16 @@ class WCConnectionRequestWidget extends StatelessWidget { ); } - Widget _buildSessionProposal(BuildContext context) { + Widget _buildSessionProposalView(BuildContext context) { // Create the connection models using the required and optional namespaces provided by the proposal data // The key is the title and the list of values is the data - final List views = - ConnectionWidgetBuilder.buildFromRequiredNamespaces( - sessionProposal!.request.requiredNamespaces, + final views = ConnectionWidgetBuilder.buildFromRequiredNamespaces( + sessionProposal!.request.generatedNamespaces!, ); return Column( children: views, ); - // return Expanded( - // child: ListView.separated( - // itemBuilder: (context, index) => views[index], - // separatorBuilder: (context, index) => const SizedBox( - // height: StyleConstants.linear8, - // ), - // itemCount: views.length, - // ), - // ); } } diff --git a/lib/apis/utils/constants.dart b/lib/apis/utils/constants.dart index 739f3bd2..4ddb5b53 100644 --- a/lib/apis/utils/constants.dart +++ b/lib/apis/utils/constants.dart @@ -1,5 +1,5 @@ class WalletConnectConstants { - static const SDK_VERSION = '2.1.10'; + static const SDK_VERSION = '2.1.11'; static const CORE_PROTOCOL = 'wc'; static const CORE_VERSION = 2; @@ -64,3 +64,47 @@ class StoreVersions { static const CONTEXT_COMPLETE_REQUESTS = 'completeRequests'; static const VERSION_COMPLETE_REQUESTS = '2.1'; } + +class MethodsConstants { + static const walletSwitchEthChain = 'wallet_switchEthereumChain'; + static const walletAddEthChain = 'wallet_addEthereumChain'; + static const requiredMethods = [ + 'eth_sendTransaction', + 'personal_sign', + ]; + static const optionalMethods = [ + 'eth_accounts', + 'eth_requestAccounts', + 'eth_sendRawTransaction', + 'eth_sign', + 'eth_signTransaction', + 'eth_signTypedData', + 'eth_signTypedData_v3', + 'eth_signTypedData_v4', + 'eth_sendTransaction', + 'personal_sign', + walletSwitchEthChain, + walletAddEthChain, + 'wallet_getPermissions', + 'wallet_requestPermissions', + 'wallet_registerOnboarding', + 'wallet_watchAsset', + 'wallet_scanQRCode', + ]; + static const allMethods = [...requiredMethods, ...optionalMethods]; +} + +class EventsConstants { + static const chainChanged = 'chainChanged'; + static const accountsChanged = 'accountsChanged'; + static const requiredEvents = [ + chainChanged, + accountsChanged, + ]; + static const optionalEvents = [ + 'message', + 'disconnect', + 'connect', + ]; + static const allEvents = [...requiredEvents, ...optionalEvents]; +} diff --git a/lib/apis/utils/namespace_utils.dart b/lib/apis/utils/namespace_utils.dart index aee2d1da..fa8503cc 100644 --- a/lib/apis/utils/namespace_utils.dart +++ b/lib/apis/utils/namespace_utils.dart @@ -312,9 +312,8 @@ class NamespaceUtils { final List events = []; final List methods = []; final namespace = requiredNamespaces[namespaceOrChainId]!; - if (NamespaceUtils.isValidChainId(namespaceOrChainId) || - namespace.chains == null || - namespace.chains!.isEmpty) { + final chains = namespace.chains ?? []; + if (NamespaceUtils.isValidChainId(namespaceOrChainId) || chains.isEmpty) { // Add the chain specific availableAccounts accounts.addAll( _getMatching( @@ -340,10 +339,6 @@ class NamespaceUtils { ), ); } else { - final List chains = namespace.chains!; - // Add the namespace specific functions - final List> chainMethodSets = []; - final List> chainEventSets = []; // Loop through all of the chains for (final String chainId in chains) { // Add the chain specific availableAccounts @@ -351,10 +346,11 @@ class NamespaceUtils { _getMatching( namespaceOrChainId: chainId, available: availableAccounts, + takeLast: true, ).map((e) => '$chainId:$e'), ); // Add the chain specific events - chainEventSets.add( + events.addAll( _getMatching( namespaceOrChainId: chainId, available: availableEvents, @@ -362,7 +358,7 @@ class NamespaceUtils { ), ); // Add the chain specific methods - chainMethodSets.add( + methods.addAll( _getMatching( namespaceOrChainId: chainId, available: availableMethods, @@ -370,12 +366,7 @@ class NamespaceUtils { ), ); } - - methods.addAll(chainMethodSets.reduce((v, e) => v.intersection(e))); - events.addAll(chainEventSets.reduce((v, e) => v.intersection(e))); } - // print(availableAccounts); - // print(accounts); // Add the namespace to the list namespaces[namespaceOrChainId] = Namespace( diff --git a/lib/src/version.dart b/lib/src/version.dart index 71ae6bf6..f1c02a75 100644 --- a/lib/src/version.dart +++ b/lib/src/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '2.1.10'; +const packageVersion = '2.1.11'; diff --git a/pubspec.yaml b/pubspec.yaml index 184f8f84..c0889c94 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: walletconnect_flutter_v2 description: This repository contains oficial implementation of WalletConnect v2 protocols for Flutter applications. The communications protocol for web3. -version: 2.1.10 +version: 2.1.11 repository: https://github.com/WalletConnect/WalletConnectFlutterV2 environment: From 9b941fdf7d37015482202c6d79c720d74f0a8fca Mon Sep 17 00:00:00 2001 From: Alfreedom <00tango.bromine@icloud.com> Date: Thu, 7 Dec 2023 16:33:48 +0100 Subject: [PATCH 2/4] fixed an issue with generatedNamespaces during session proposal. Small enhancements in example wallet/dapp. Minor changes and bug fixes --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e4cf52e..0f683cde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ## 2.1.11 - Fixed an issue with `generatedNamespaces` during session proposal +- Small enhancements in example wallet/dapp. +- Minor changes and bug fixe ## 2.1.10 From 03c975803c3a16a457bc45ea22396ae994c6bf5d Mon Sep 17 00:00:00 2001 From: Alfreedom <00tango.bromine@icloud.com> Date: Thu, 7 Dec 2023 17:01:30 +0100 Subject: [PATCH 3/4] minor fix --- lib/apis/utils/namespace_utils.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/apis/utils/namespace_utils.dart b/lib/apis/utils/namespace_utils.dart index fa8503cc..9460c67e 100644 --- a/lib/apis/utils/namespace_utils.dart +++ b/lib/apis/utils/namespace_utils.dart @@ -346,7 +346,6 @@ class NamespaceUtils { _getMatching( namespaceOrChainId: chainId, available: availableAccounts, - takeLast: true, ).map((e) => '$chainId:$e'), ); // Add the chain specific events @@ -370,9 +369,9 @@ class NamespaceUtils { // Add the namespace to the list namespaces[namespaceOrChainId] = Namespace( - accounts: accounts, - events: events, - methods: methods, + accounts: accounts.toSet().toList(), + events: events.toSet().toList(), + methods: methods.toSet().toList(), ); } From 715d93d3c89341b1cc9b725ef12405e1113eabcc Mon Sep 17 00:00:00 2001 From: Alfreedom <00tango.bromine@icloud.com> Date: Thu, 7 Dec 2023 18:33:47 +0100 Subject: [PATCH 4/4] fixed tests and added new ones --- test/shared/namespace_utils_test.dart | 40 +++++++++++++++-- test/shared/shared_test_values.dart | 62 ++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 6 deletions(-) diff --git a/test/shared/namespace_utils_test.dart b/test/shared/namespace_utils_test.dart index 0fa7141e..ba767e89 100644 --- a/test/shared/namespace_utils_test.dart +++ b/test/shared/namespace_utils_test.dart @@ -312,6 +312,39 @@ void main() { ); }); + test('constructs namespaces with required and optional namespaces', () { + Map namespaces = NamespaceUtils.constructNamespaces( + availableAccounts: availableAccounts3, + availableMethods: availableMethods3, + availableEvents: availableEvents3, + requiredNamespaces: requiredNamespacesInAvailable3, + optionalNamespaces: optionalNamespacesInAvailable3, + ); + + expect(namespaces.length, 1); + expect( + namespaces['eip155']!.accounts, + availableAccounts3.toList(), + ); + expect( + namespaces['eip155']!.methods, + availableMethods3.map((m) => m.split(':').last).toList(), + ); + expect( + namespaces['eip155']!.events, + availableEvents3.map((m) => m.split(':').last).toList(), + ); + + expect( + SignApiValidatorUtils.isConformingNamespaces( + requiredNamespaces: requiredNamespacesInAvailable3, + namespaces: namespaces, + context: '', + ), + true, + ); + }); + test('constructNamespaces trims off unrequested', () { final reqNamespace = { 'eip155': const RequiredNamespace( @@ -449,7 +482,7 @@ void main() { Errors.getSdkError( Errors.UNSUPPORTED_METHODS, context: - " namespaces methods don't satisfy requiredNamespaces methods for namespace2. Requested: [method3, method4], Supported: [method3]", + " namespaces methods don't satisfy requiredNamespaces methods for namespace1:chain1. Requested: [method1, method2, method3], Supported: [method1, method2]", ).message, Errors.getSdkError( Errors.UNSUPPORTED_EVENTS, @@ -459,13 +492,12 @@ void main() { Errors.getSdkError( Errors.UNSUPPORTED_EVENTS, context: - " namespaces events don't satisfy requiredNamespaces events for namespace2. Requested: [event3, event4], Supported: [event3]", + " namespaces events don't satisfy requiredNamespaces events for namespace1:chain1. Requested: [event1, event2, event3], Supported: [event1, event2]", ).message, ]; for (int i = 0; i < nonconforming.length; i++) { - Map namespaces = - NamespaceUtils.constructNamespaces( + final namespaces = NamespaceUtils.constructNamespaces( availableAccounts: availableAccounts, availableMethods: availableMethods, availableEvents: availableEvents, diff --git a/test/shared/shared_test_values.dart b/test/shared/shared_test_values.dart index 616594bd..67bcfcc1 100644 --- a/test/shared/shared_test_values.dart +++ b/test/shared/shared_test_values.dart @@ -135,7 +135,7 @@ final Map requiredNamespacesNonconformingMethods1 = { final Map requiredNamespacesNonconformingMethods2 = { 'namespace1:chain1': const RequiredNamespace( - methods: ['method1', 'method2'], + methods: ['method1', 'method2', 'method3'], events: ['event1', 'event2'], ), 'namespace2': const RequiredNamespace( @@ -160,7 +160,7 @@ final Map requiredNamespacesNonconformingEvents1 = { final Map requiredNamespacesNonconformingEvents2 = { 'namespace1:chain1': const RequiredNamespace( methods: ['method1', 'method2'], - events: ['event1', 'event2'], + events: ['event1', 'event2', 'event3'], ), 'namespace2': const RequiredNamespace( chains: ['namespace2:chain1', 'namespace2:chain2'], @@ -175,3 +175,61 @@ Map optionalNamespaces = { events: ['event5', 'event2'], ), }; + +const sepolia = 'eip155:11155111'; + +final Set availableAccounts3 = { + '$sepolia:0x99999999999999999999999999', +}; + +final Set availableMethods3 = { + '$sepolia:eth_sendTransaction', + '$sepolia:personal_sign', + '$sepolia:eth_signTypedData', + '$sepolia:eth_signTypedData_v4', + '$sepolia:eth_sign', +}; + +final Set availableEvents3 = { + '$sepolia:chainChanged', + '$sepolia:accountsChanged', +}; + +final Map requiredNamespacesInAvailable3 = { + 'eip155': const RequiredNamespace( + chains: [sepolia], + methods: ['eth_sendTransaction', 'personal_sign'], + events: ['chainChanged', 'accountsChanged'], + ), +}; + +final Map optionalNamespacesInAvailable3 = { + 'eip155': const RequiredNamespace(chains: [ + 'eip155:1', + 'eip155:5', + sepolia, + 'eip155:137', + 'eip155:80001', + 'eip155:42220', + 'eip155:44787', + 'eip155:56', + 'eip155:43114', + 'eip155:42161', + 'eip155:421613', + 'eip155:10', + 'eip155:420', + 'eip155:8453' + ], methods: [ + 'eth_sendTransaction', + 'personal_sign', + 'eth_signTypedData', + 'eth_signTypedData_v4', + 'eth_sign' + ], events: [ + 'chainChanged', + 'accountsChanged', + 'message', + 'disconnect', + 'connect' + ]), +};