diff --git a/.gitignore b/.gitignore index 354a615e..8ab5c4da 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,9 @@ app.*.map.json # fvm .fvm/ +**/secrets.properties +**/*.keystore + # vscode .vscode/ diff --git a/example/dapp/lib/main.dart b/example/dapp/lib/main.dart index 47b74c4a..4a18c327 100644 --- a/example/dapp/lib/main.dart +++ b/example/dapp/lib/main.dart @@ -124,21 +124,11 @@ class _MyHomePageState extends State { _web3App!.onSessionAuthResponse.subscribe(_onSessionAuthResponse); await _web3App!.init(); + await _registerEventHandlers(); DeepLinkHandler.init(_web3App!); DeepLinkHandler.checkInitialLink(); - // Loop through all the chain data - for (final ChainMetadata chain in ChainData.allChains) { - // Loop through the events for that chain - for (final event in getChainEvents(chain.type)) { - _web3App!.registerEventHandler( - chainId: chain.chainId, - event: event, - ); - } - } - setState(() { _pageDatas = [ PageData( @@ -167,6 +157,25 @@ class _MyHomePageState extends State { }); } + Future _registerEventHandlers() async { + if (!_web3App!.core.connectivity.isOnline.value) { + await Future.delayed(const Duration(milliseconds: 500)); + _registerEventHandlers(); + return; + } + + // Loop through all the chain data + for (final ChainMetadata chain in ChainData.allChains) { + // Loop through the events for that chain + for (final event in getChainEvents(chain.type)) { + _web3App!.registerEventHandler( + chainId: chain.chainId, + event: event, + ); + } + } + } + void _onSessionConnect(SessionConnect? event) { debugPrint('[SampleDapp] _onSessionConnect $event'); Future.delayed(const Duration(milliseconds: 500), () { diff --git a/example/dapp/lib/models/chain_metadata.dart b/example/dapp/lib/models/chain_metadata.dart index 834a0ba6..d5d0f995 100644 --- a/example/dapp/lib/models/chain_metadata.dart +++ b/example/dapp/lib/models/chain_metadata.dart @@ -7,6 +7,7 @@ enum ChainType { kadena, cosmos, polkadot, + bip122, } class ChainMetadata { diff --git a/example/dapp/lib/pages/connect_page.dart b/example/dapp/lib/pages/connect_page.dart index 17ca2429..832d95e6 100644 --- a/example/dapp/lib/pages/connect_page.dart +++ b/example/dapp/lib/pages/connect_page.dart @@ -11,6 +11,7 @@ import 'package:package_info_plus/package_info_plus.dart'; import 'package:qr_flutter/qr_flutter.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/bitcoin.dart'; import 'package:walletconnect_flutter_v2_dapp/utils/crypto/chain_data.dart'; import 'package:walletconnect_flutter_v2_dapp/utils/crypto/eip155.dart'; import 'package:walletconnect_flutter_v2_dapp/utils/crypto/polkadot.dart'; @@ -91,6 +92,16 @@ class ConnectPageState extends State { ); } + final btcChains = + _selectedChains.where((e) => e.type == ChainType.bip122).toList(); + if (btcChains.isNotEmpty) { + optionalNamespaces['bip122'] = RequiredNamespace( + chains: btcChains.map((c) => c.chainId).toList(), + methods: Bitcoin.methods.values.toList(), + events: Bitcoin.events.values.toList(), + ); + } + final solanaChains = _selectedChains.where((e) => e.type == ChainType.solana).toList(); if (solanaChains.isNotEmpty) { diff --git a/example/dapp/lib/utils/crypto/bitcoin.dart b/example/dapp/lib/utils/crypto/bitcoin.dart new file mode 100644 index 00000000..8241e6e8 --- /dev/null +++ b/example/dapp/lib/utils/crypto/bitcoin.dart @@ -0,0 +1,86 @@ +import 'package:walletconnect_flutter_v2_dapp/models/chain_metadata.dart'; +import 'package:walletconnect_flutter_v2_dapp/imports.dart'; + +enum BitcoinMethods { + bitcoinSignTransaction, + bitcoinSignMessage, +} + +enum BitcoinEvents { + none, +} + +class Bitcoin { + static final Map methods = { + BitcoinMethods.bitcoinSignTransaction: 'btc_sendTransaction', + BitcoinMethods.bitcoinSignMessage: 'btc_signMessage' + }; + + static final Map events = {}; + + static Future callMethod({ + required Web3App web3App, + required String topic, + required String method, + required ChainMetadata chainData, + required String address, + bool isV0 = false, + }) async { + switch (method) { + case 'btc_signMessage': + const message = "This is a message to be signed for BIP122"; + final result = await web3App.request( + topic: topic, + chainId: chainData.chainId, + request: SessionRequestParams( + method: method, + params: [message, address], + ), + ); + // final checkSegwitAlways = (result.segwitType == "p2wpkh") || + // (result.segwitType == 'p2sh(p2wpkh)'); + return { + 'method': method, + 'address': address, + // 'valid': verifyBitcoinMessage( + // message, + // result.signature, + // address, + // // undefined, + // // checkSegwitAlways + // ), + 'result': '' + 'signature: ${result["signature"]}\n' + 'segwitType: ${result["segwitType"]}', + }; + case 'btc_sendTransaction': + // final utxos = await apiGetAddressUtxos(address, chainId); + // final availableBalance = getAvailableBalanceFromUtxos(utxos); // in satoshis + return web3App.request( + topic: topic, + chainId: chainData.chainId, + request: SessionRequestParams( + method: method, + params: { + 'address': address, + 'value': 0.0000001, // availableBalance, + 'transactionType': 'p2wpkh', + }, + ), + ); + default: + throw 'Method unimplemented'; + } + } +} + +// Future apiGetAddressUtxos(String address, String chainId) { +// return await (await fetch(`https://mempool.space/signet/api/address/${address}/utxo`)).json(); +// } + +// int getAvailableBalanceFromUtxos(List utxos) { +// if (!utxos || !utxos.length) { +// return 0; +// } +// return utxos.reduce((acc, { value }) => acc + value, 0); +// } diff --git a/example/dapp/lib/utils/crypto/chain_data.dart b/example/dapp/lib/utils/crypto/chain_data.dart index 14eafe58..d0213bf6 100644 --- a/example/dapp/lib/utils/crypto/chain_data.dart +++ b/example/dapp/lib/utils/crypto/chain_data.dart @@ -98,25 +98,19 @@ class ChainData { static final List solanaChains = [ const ChainMetadata( type: ChainType.solana, - chainId: 'solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ', - name: 'Solana Mainnet 1', + chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + name: 'Solana Mainnet', logo: '/chain-logos/solana.png', color: Color.fromARGB(255, 247, 0, 255), - rpc: [ - 'https://rpc.ankr.com/solana', - 'https://api.tatum.io/v3/blockchain/node/solana-mainnet', - ], + rpc: ['https://api.mainnet-beta.solana.com'], ), const ChainMetadata( type: ChainType.solana, - chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', - name: 'Solana Mainnet 2', + chainId: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1', + name: 'Solana Devnet', logo: '/chain-logos/solana.png', color: Color.fromARGB(255, 247, 0, 255), - rpc: [ - 'https://rpc.ankr.com/solana', - 'https://api.tatum.io/v3/blockchain/node/solana-mainnet', - ], + rpc: ['https://api.devnet.solana.com'], ), const ChainMetadata( type: ChainType.solana, @@ -125,9 +119,7 @@ class ChainData { logo: '/chain-logos/solana.png', color: Colors.black, isTestnet: true, - rpc: [ - 'https://api.testnet.solana.com', - ], + rpc: ['https://api.testnet.solana.com'], ), ]; @@ -197,10 +189,34 @@ class ChainData { ), ]; + static final List bitcoinChains = [ + const ChainMetadata( + type: ChainType.bip122, + chainId: + 'bip122:000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f', + name: 'Bitcoin Mainnet', + logo: '/chain-logos/bitcoin.png', + color: Color.fromARGB(255, 255, 161, 9), + rpc: ['https://bitcoin.drpc.org/'], + ), + const ChainMetadata( + type: ChainType.bip122, + chainId: + 'bip122:000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943', + name: 'Bitcoin Signet', + logo: '/chain-logos/bitcoin.png', + color: Color.fromARGB(255, 255, 161, 9), + rpc: ['https://signet.bitcoinrpc.org'], + // https://bitcoin-testnet.drpc.org + isTestnet: true, + ), + ]; + static final List allChains = [ ...eip155Chains, ...solanaChains, ...polkadotChains, + ...bitcoinChains, // ...kadenaChains, // ...cosmosChains, ]; diff --git a/example/dapp/lib/utils/crypto/helpers.dart b/example/dapp/lib/utils/crypto/helpers.dart index a37dfbbb..aaa51875 100644 --- a/example/dapp/lib/utils/crypto/helpers.dart +++ b/example/dapp/lib/utils/crypto/helpers.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:walletconnect_flutter_v2_dapp/models/chain_metadata.dart'; +import 'package:walletconnect_flutter_v2_dapp/utils/crypto/bitcoin.dart'; import 'package:walletconnect_flutter_v2_dapp/utils/crypto/chain_data.dart'; import 'package:walletconnect_flutter_v2_dapp/utils/crypto/eip155.dart'; import 'package:walletconnect_flutter_v2_dapp/utils/crypto/polkadot.dart'; @@ -36,6 +37,8 @@ List getChainMethods(ChainType value) { return Solana.methods.values.toList(); case ChainType.polkadot: return Polkadot.methods.values.toList(); + case ChainType.bip122: + return Bitcoin.methods.values.toList(); default: return []; } diff --git a/example/dapp/lib/utils/crypto/solana.dart b/example/dapp/lib/utils/crypto/solana.dart index 241309f3..412a5e60 100644 --- a/example/dapp/lib/utils/crypto/solana.dart +++ b/example/dapp/lib/utils/crypto/solana.dart @@ -2,6 +2,11 @@ import 'dart:convert'; // ignore: depend_on_referenced_packages import 'package:bs58/bs58.dart'; +import 'package:solana_web3/solana_web3.dart' as solana; +// import 'package:solana_web3/programs.dart'; +// import 'package:solana_web3/src/encodings/lamports.dart'; +// import 'package:solana_web3/src/rpc/models/blockhash_with_expiry_block_height.dart'; + import 'package:walletconnect_flutter_v2_dapp/models/chain_metadata.dart'; import 'package:walletconnect_flutter_v2_dapp/imports.dart'; @@ -28,13 +33,14 @@ class Solana { required String method, required ChainMetadata chainData, required String address, - }) { - final bytes = utf8.encode( - 'This is an example message to be signed - ${DateTime.now()}', - ); - final message = base58.encode(bytes); + bool isV0 = false, + }) async { switch (method) { case 'solana_signMessage': + final bytes = utf8.encode( + 'This is an example message to be signed - ${DateTime.now()}', + ); + final message = base58.encode(bytes); return web3App.request( topic: topic, chainId: chainData.chainId, @@ -47,32 +53,63 @@ class Solana { ), ); case 'solana_signTransaction': - return web3App.request( + // Create a connection to the devnet cluster. + final cluster = solana.Cluster.https( + Uri.parse(chainData.rpc.first).authority, + ); + // final cluster = solana.Cluster.devnet; + final connection = solana.Connection(cluster); + + // Fetch the latest blockhash. + final blockhash = await connection.getLatestBlockhash(); + + // Create a System Program instruction to transfer 0.5 SOL from [address1] to [address2]. + final transactionv0 = solana.Transaction.v0( + payer: solana.Pubkey.fromBase58(address), + recentBlockhash: blockhash.blockhash, + instructions: [ + solana.TransactionInstruction.fromJson({ + "programId": "11111111111111111111111111111111", + "data": [2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], + "keys": [ + { + "isSigner": true, + "isWritable": true, + "pubkey": address, + }, + { + "isSigner": false, + "isWritable": true, + "pubkey": "8vCyX7oB6Pc3pbWMGYYZF5pbSnAdQ7Gyr32JqxqCy8ZR" + } + ] + }), + // SystemProgram.transfer( + // fromPubkey: solana.Pubkey.fromBase58(address), + // toPubkey: solana.Pubkey.fromBase58( + // '8vCyX7oB6Pc3pbWMGYYZF5pbSnAdQ7Gyr32JqxqCy8ZR', + // ), + // lamports: solana.solToLamports(0.5), + // ), + ], + ); + + const config = solana.TransactionSerializableConfig( + verifySignatures: false, + ); + final bytes = transactionv0.serialize(config).asUint8List(); + final encodedV0Trx = base64.encode(bytes); + + return web3App.signEngine.request( topic: topic, chainId: chainData.chainId, request: SessionRequestParams( method: method, params: { - "feePayer": address, - "recentBlockhash": "H32Ss1hxpP2ZJM4whREVNyUWRgzFLVA97UXJUjBrEsgx", - "instructions": [ - { - "programId": "11111111111111111111111111111111", - "data": [2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], - "keys": [ - { - "isSigner": true, - "isWritable": true, - "pubkey": "EbdEmCpKGvEwfwV4ACmVYHFRkwvXdogJhMZeEekDFVVJ" - }, - { - "isSigner": false, - "isWritable": true, - "pubkey": "4SzUq9NNYSYGp41ED5NgSDoCrEh9MoD7zSvmtkwseW8s" - } - ] - } - ] + 'transaction': encodedV0Trx, + 'pubkey': address, + 'feePayer': address, + ...transactionv0.message.toJson(), }, ), ); diff --git a/example/dapp/lib/widgets/session_widget.dart b/example/dapp/lib/widgets/session_widget.dart index 520e308c..923e4cbf 100644 --- a/example/dapp/lib/widgets/session_widget.dart +++ b/example/dapp/lib/widgets/session_widget.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.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/bitcoin.dart'; import 'package:walletconnect_flutter_v2_dapp/utils/crypto/eip155.dart'; import 'package:walletconnect_flutter_v2_dapp/utils/crypto/helpers.dart'; import 'package:walletconnect_flutter_v2_dapp/utils/crypto/polkadot.dart'; @@ -170,7 +171,8 @@ class SessionWidgetState extends State { ) { final List buttons = []; // Add Methods - for (final String method in getChainMethods(chainMetadata.type)) { + final chainMethods = getChainMethods(chainMetadata.type); + for (final method in chainMethods) { final namespaces = widget.session.namespaces[chainMetadata.type.name]; final supported = namespaces?.methods.contains(method) ?? false; buttons.add( @@ -233,6 +235,14 @@ class SessionWidgetState extends State { chainData: chainMetadata, address: address, ); + case ChainType.bip122: + return Bitcoin.callMethod( + web3App: widget.web3App, + topic: widget.session.topic, + method: method, + chainData: chainMetadata, + address: address, + ); case ChainType.polkadot: return Polkadot.callMethod( web3App: widget.web3App, @@ -248,7 +258,9 @@ class SessionWidgetState extends State { method: method, chainData: chainMetadata, address: address, + isV0: true, ); + // case ChainType.kadena: // return Kadena.callMethod( // web3App: widget.web3App, diff --git a/example/dapp/pubspec.yaml b/example/dapp/pubspec.yaml index d788f4ab..51209128 100644 --- a/example/dapp/pubspec.yaml +++ b/example/dapp/pubspec.yaml @@ -9,6 +9,9 @@ environment: sdk: ">=2.18.6 <3.0.0" dependencies: + bitcoin_base: ^4.7.0 + pointycastle: ^3.5.2 + hex: ^0.2.0 flutter: sdk: flutter @@ -17,6 +20,7 @@ dependencies: json_annotation: ^4.8.1 fl_toast: ^3.1.0 package_info_plus: ^7.0.0 + solana_web3: ^0.1.3 walletconnect_modal_flutter: ^2.1.20 dependency_overrides: diff --git a/example/wallet/lib/dependencies/chain_services/bitcoin_service.dart b/example/wallet/lib/dependencies/chain_services/bitcoin_service.dart new file mode 100644 index 00000000..ec308171 --- /dev/null +++ b/example/wallet/lib/dependencies/chain_services/bitcoin_service.dart @@ -0,0 +1,206 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; + +import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.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/models/chain_metadata.dart'; +import 'package:walletconnect_flutter_v2_wallet/utils/methods_utils.dart'; + +import 'package:bitcoin_base/bitcoin_base.dart'; +// import 'package:bitcoin_message_signer/bitcoin_message_signer.dart'; + +class BitcoinService { + Map get bitcoinRequestHandlers => { + 'btc_signMessage': bitcoinSignMessage, + 'btc_sendTransaction': bitcoinSendTransaction, + }; + + final _web3wallet = GetIt.I().web3wallet; + final ChainMetadata chainSupported; + + BitcoinService({required this.chainSupported}) { + for (var handler in bitcoinRequestHandlers.entries) { + _web3wallet.registerRequestHandler( + chainId: chainSupported.chainId, + method: handler.key, + handler: handler.value, + ); + } + } + + Future bitcoinSignMessage(String topic, dynamic parameters) async { + debugPrint('[SampleWallet] bitcoinSignMessage request: $parameters'); + final pRequest = _web3wallet.pendingRequests.getAll().last; + var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0'); + + try { + final params = parameters as List; + final message = params.first.toString(); + + final keys = GetIt.I().getKeysForChain( + chainSupported.chainId, + ); + + final privateKey = ECPrivate.fromWif( + keys.first.privateKey, + netVersion: BitcoinNetwork.mainnet.wifNetVer, + ); + + if (await MethodsUtils.requestApproval( + message, + method: pRequest.method, + chainId: pRequest.chainId, + address: keys.first.address, + transportType: pRequest.transportType.name, + )) { + // final messageSigner = BitcoinMessageSigner( + // privateKey: Uint8List.fromList(privateKey.toBytes()), + // scriptType: P2WPKH(), + // ); + + final messageBytes = utf8.encode(message); + + final signature = privateKey.signMessage( + messageBytes, + messagePrefix: '\x18Bitcoin Signed Message:\n', + ); + debugPrint('[$runtimeType] signature: $signature'); + + // final signature2 = messageSigner.signMessage( + // message: message, + // messagePrefix: '\x18Bitcoin Signed Message:\n', + // ); + + // debugPrint('[$runtimeType] signature2: $signature2'); + + final publicKey = privateKey.getPublic(); + + final isValid = publicKey.verify( + messageBytes, + base64.decode(signature).sublist(0, 64), + messagePrefix: '\x18Bitcoin Signed Message:\n', + ); + + debugPrint('[$runtimeType] signature: $signature, valid: $isValid'); + + response = response.copyWith( + result: { + 'signature': '0x$signature', + // 'segwitType': 'p2wsh', + }, + ); + } else { + response = response.copyWith( + error: const JsonRpcError(code: 5001, message: 'User rejected'), + ); + } + // + } catch (e) { + debugPrint('[SampleWallet] bitcoinSignMessage error $e'); + response = response.copyWith( + error: JsonRpcError(code: 0, message: e.toString()), + ); + } + + _handleResponseForTopic(topic, response); + } + + Future bitcoinSendTransaction(String topic, dynamic parameters) async { + debugPrint( + '[SampleWallet] bitcoinSendTransaction: ${jsonEncode(parameters)}'); + final pRequest = _web3wallet.pendingRequests.getAll().last; + var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0'); + + try { + final params = parameters as Map; + final beautifiedTrx = const JsonEncoder.withIndent(' ').convert(params); + + final keys = GetIt.I().getKeysForChain( + chainSupported.chainId, + ); + + final privateKey = ECPrivate.fromWif( + keys.first.privateKey, + netVersion: BitcoinNetwork.mainnet.wifNetVer, + ); + + if (await MethodsUtils.requestApproval( + // Show Approval Modal + beautifiedTrx, + method: pRequest.method, + chainId: pRequest.chainId, + address: keys.first.address, + transportType: pRequest.transportType.name, + )) { + // Sign the transaction. + // else we parse the other key/values, see https://docs.walletconnect.com/advanced/multichain/rpc-reference/solana-rpc#solana_signtransaction + final address = params['address'].toString(); + final value = params['value'] as num; + final transactionType = params['transactionType'].toString(); + + final List utxos = + []; //await this.getUtXOs(this.address); + + final List outPuts = []; + + BitcoinTransactionBuilder( + outPuts: outPuts, + fee: BigInt.zero, + network: BitcoinNetwork.mainnet, + utxos: utxos, + ); + + // response = response.copyWith( + // result: { + // 'signature': signature.toBase58(), + // }, + // ); + } else { + response = response.copyWith( + error: const JsonRpcError(code: 5001, message: 'User rejected'), + ); + } + } catch (e, s) { + debugPrint('[SampleWallet] bitcoinSendTransaction error $e, $s'); + response = response.copyWith( + error: JsonRpcError(code: 0, message: e.toString()), + ); + } + + _handleResponseForTopic(topic, response); + } + + void _handleResponseForTopic(String topic, JsonRpcResponse response) async { + final session = _web3wallet.sessions.get(topic); + + try { + await _web3wallet.respondSessionRequest( + topic: topic, + response: response, + ); + MethodsUtils.handleRedirect( + topic, + session!.peer.metadata.redirect, + response.error?.message, + ); + } on WalletConnectError catch (error) { + MethodsUtils.handleRedirect( + topic, + session!.peer.metadata.redirect, + error.message, + ); + } + } +} + +// String _padEncodeIfNeeded(String encoded) { +// final padding = encoded.length % 4; +// if (padding > 0) { +// encoded += '=' * (4 - padding); +// } +// return encoded; +// } diff --git a/example/wallet/lib/dependencies/chain_services/polkadot_service.dart b/example/wallet/lib/dependencies/chain_services/polkadot_service.dart index acc2b421..6ec9b5a9 100644 --- a/example/wallet/lib/dependencies/chain_services/polkadot_service.dart +++ b/example/wallet/lib/dependencies/chain_services/polkadot_service.dart @@ -1,9 +1,9 @@ import 'dart:convert'; -import 'package:convert/convert.dart'; import 'package:flutter/foundation.dart'; import 'package:get_it/get_it.dart'; import 'package:polkadart/scale_codec.dart'; +import 'package:solana_web3/solana_web3.dart' show hex; import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; import 'package:walletconnect_flutter_v2_wallet/dependencies/i_web3wallet_service.dart'; diff --git a/example/wallet/lib/dependencies/chain_services/solana_service.dart b/example/wallet/lib/dependencies/chain_services/solana_service.dart index a8eb461f..d14a9764 100644 --- a/example/wallet/lib/dependencies/chain_services/solana_service.dart +++ b/example/wallet/lib/dependencies/chain_services/solana_service.dart @@ -9,24 +9,27 @@ import 'package:walletconnect_flutter_v2_wallet/dependencies/i_web3wallet_servic import 'package:walletconnect_flutter_v2_wallet/dependencies/key_service/i_key_service.dart'; import 'package:walletconnect_flutter_v2_wallet/models/chain_metadata.dart'; -import 'package:solana/solana.dart'; -import 'package:solana/encoder.dart'; +import 'package:solana/solana.dart' as solana; +import 'package:solana/encoder.dart' as solana_encoder; // ignore: depend_on_referenced_packages import 'package:bs58/bs58.dart'; import 'package:walletconnect_flutter_v2_wallet/utils/methods_utils.dart'; +/// +/// Uses solana: ^0.30.4 +/// class SolanaService { Map get solanaRequestHandlers => { 'solana_signMessage': solanaSignMessage, 'solana_signTransaction': solanaSignTransaction, }; - final _web3Wallet = GetIt.I().web3wallet; + final _web3wallet = GetIt.I().web3wallet; final ChainMetadata chainSupported; SolanaService({required this.chainSupported}) { for (var handler in solanaRequestHandlers.entries) { - _web3Wallet.registerRequestHandler( + _web3wallet.registerRequestHandler( chainId: chainSupported.chainId, method: handler.key, handler: handler.value, @@ -36,29 +39,23 @@ class SolanaService { Future solanaSignMessage(String topic, dynamic parameters) async { debugPrint('[SampleWallet] solanaSignMessage request: $parameters'); - const method = 'solana_signMessage'; - final pRequest = _web3Wallet.pendingRequests.getAll().last; + final pRequest = _web3wallet.pendingRequests.getAll().last; var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0'); try { final params = parameters as Map; final message = params['message'].toString(); - final keys = GetIt.I().getKeysForChain( - chainSupported.chainId, - ); - final secKeyBytes = keys[0].privateKey.parse32Bytes(); - - final keyPair = await Ed25519HDKeyPair.fromPrivateKeyBytes( - privateKey: secKeyBytes, - ); + final keyPair = await _getKeyPair(); // it's being sent encoded from dapp final base58Decoded = base58.decode(message); final decodedMessage = utf8.decode(base58Decoded); if (await MethodsUtils.requestApproval( decodedMessage, - title: method, + method: pRequest.method, + chainId: pRequest.chainId, + address: keyPair.address, transportType: pRequest.transportType.name, )) { final signature = await keyPair.sign(base58Decoded.toList()); @@ -81,80 +78,115 @@ class SolanaService { ); } + await _web3wallet.respondSessionRequest( + topic: topic, + response: response, + ); + _handleResponseForTopic(topic, response); } Future solanaSignTransaction(String topic, dynamic parameters) async { debugPrint( '[SampleWallet] solanaSignTransaction: ${jsonEncode(parameters)}'); - const method = 'solana_signTransaction'; - final pRequest = _web3Wallet.pendingRequests.getAll().last; + final pRequest = _web3wallet.pendingRequests.getAll().last; var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0'); try { final params = parameters as Map; - final feePayer = params['feePayer'].toString(); - final recentBlockHash = params['recentBlockhash'].toString(); - final instructionsList = params['instructions'] as List; + final beautifiedTrx = const JsonEncoder.withIndent(' ').convert(params); - final keys = GetIt.I().getKeysForChain( - chainSupported.chainId, - ); - final secKeyBytes = keys[0].privateKey.parse32Bytes(); + final keyPair = await _getKeyPair(); - final keyPair = await Ed25519HDKeyPair.fromPrivateKeyBytes( - privateKey: secKeyBytes, - ); - - if (keyPair.address != feePayer) { - throw Exception('Error'); - } - - const encoder = JsonEncoder.withIndent(' '); - final transaction = encoder.convert(params); if (await MethodsUtils.requestApproval( - transaction, - title: method, + // Show Approval Modal + beautifiedTrx, + method: pRequest.method, + chainId: pRequest.chainId, + address: keyPair.address, transportType: pRequest.transportType.name, )) { // Sign the transaction. - final instructions = instructionsList.map((json) { - return (json as Map).toInstruction(); - }).toList(); - - final message = Message(instructions: instructions); - final compiledMessage = message.compile( - recentBlockhash: recentBlockHash, - feePayer: Ed25519HDPublicKey.fromBase58(feePayer), - ); - - final signature = await keyPair.sign(compiledMessage.toByteArray()); - - response = response.copyWith( - result: { - 'signature': signature.toBase58(), - }, - ); + // if params contains `transaction` key we should parse that one and disregard the rest + if (params.containsKey('transaction')) { + final transaction = params['transaction'] as String; + final transactionBytes = base64.decode(transaction); + final signedTx = solana_encoder.SignedTx.fromBytes( + transactionBytes, + ); + + // Sign the transaction. + final signature = await keyPair.sign( + signedTx.compiledMessage.toByteArray(), + ); + + response = response.copyWith( + result: { + 'signature': signature.toBase58(), + }, + ); + } else { + // else we parse the other key/values, see https://docs.walletconnect.com/advanced/multichain/rpc-reference/solana-rpc#solana_signtransaction + final feePayer = params['feePayer'].toString(); + final recentBlockHash = params['recentBlockhash'].toString(); + final instructionsList = params['instructions'] as List; + + final instructions = instructionsList.map((json) { + return (json as Map).toInstruction(); + }).toList(); + + final message = solana.Message(instructions: instructions); + final compiledMessage = message.compile( + recentBlockhash: recentBlockHash, + feePayer: solana.Ed25519HDPublicKey.fromBase58(feePayer), + ); + + // Sign the transaction. + final signature = await keyPair.sign( + compiledMessage.toByteArray(), + ); + + response = response.copyWith( + result: { + 'signature': signature.toBase58(), + }, + ); + } } else { response = response.copyWith( error: const JsonRpcError(code: 5001, message: 'User rejected'), ); } - } catch (e) { - debugPrint('[SampleWallet] solanaSignTransaction error $e'); + } catch (e, s) { + debugPrint('[SampleWallet] solanaSignTransaction error $e, $s'); response = response.copyWith( error: JsonRpcError(code: 0, message: e.toString()), ); } + await _web3wallet.respondSessionRequest( + topic: topic, + response: response, + ); + _handleResponseForTopic(topic, response); } + Future _getKeyPair() async { + final keys = GetIt.I().getKeysForChain( + chainSupported.chainId, + ); + final secKeyBytes = keys[0].privateKey.parse32Bytes(); + return await solana.Ed25519HDKeyPair.fromPrivateKeyBytes( + privateKey: secKeyBytes, + ); + } + void _handleResponseForTopic(String topic, JsonRpcResponse response) async { - final session = _web3Wallet.sessions.get(topic); + final session = _web3wallet.sessions.get(topic); try { - await _web3Wallet.respondSessionRequest( + await _web3wallet.respondSessionRequest( topic: topic, response: response, ); @@ -180,28 +212,30 @@ extension on String { final List secBytes = split(',').map((e) => int.parse(e)).toList(); return Uint8List.fromList(secBytes.sublist(0, 32)); } catch (e) { - rethrow; + final secKeyBytes = base58.decode(this); + return Uint8List.fromList(secKeyBytes.sublist(0, 32)); } } } extension on Map { - Instruction toInstruction() { + solana_encoder.Instruction toInstruction() { final programId = this['programId'] as String; - final programKey = Ed25519HDPublicKey(base58.decode(programId).toList()); + final programKey = + solana.Ed25519HDPublicKey(base58.decode(programId).toList()); final data = (this['data'] as List).map((e) => e as int).toList(); final data58 = base58.encode(Uint8List.fromList(data)); - final dataBytes = ByteArray.fromBase58(data58); + final dataBytes = solana_encoder.ByteArray.fromBase58(data58); final keys = this['keys'] as List; - return Instruction( + return solana_encoder.Instruction( programId: programKey, data: dataBytes, accounts: keys.map((k) { final kParams = (k as Map); - return AccountMeta( - pubKey: Ed25519HDPublicKey.fromBase58(kParams['pubkey']), + return solana_encoder.AccountMeta( + pubKey: solana.Ed25519HDPublicKey.fromBase58(kParams['pubkey']), isWriteable: kParams['isWritable'] as bool, isSigner: kParams['isSigner'] as bool, ); diff --git a/example/wallet/lib/dependencies/chain_services/solana_service_2.dart b/example/wallet/lib/dependencies/chain_services/solana_service_2.dart new file mode 100644 index 00000000..2da351db --- /dev/null +++ b/example/wallet/lib/dependencies/chain_services/solana_service_2.dart @@ -0,0 +1,233 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; + +import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.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/models/chain_metadata.dart'; + +import 'package:solana_web3/solana_web3.dart' as solana; +// ignore: implementation_imports +import 'package:solana_web3/src/crypto/nacl.dart' as nacl; +// ignore: depend_on_referenced_packages +import 'package:bs58/bs58.dart'; + +import 'package:walletconnect_flutter_v2_wallet/utils/methods_utils.dart'; + +/// +/// Uses solana_web3: ^0.1.3 +/// +class SolanaService2 { + Map get solanaRequestHandlers => { + 'solana_signMessage': solanaSignMessage, + 'solana_signTransaction': solanaSignTransaction, + }; + + final _web3wallet = GetIt.I().web3wallet; + final ChainMetadata chainSupported; + + SolanaService2({required this.chainSupported}) { + for (var handler in solanaRequestHandlers.entries) { + _web3wallet.registerRequestHandler( + chainId: chainSupported.chainId, + method: handler.key, + handler: handler.value, + ); + } + } + + Future solanaSignMessage(String topic, dynamic parameters) async { + debugPrint('[SampleWallet] solanaSignMessage request: $parameters'); + final pRequest = _web3wallet.pendingRequests.getAll().last; + var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0'); + + try { + final params = parameters as Map; + final message = params['message'].toString(); + + final keyPair = await _getKeyPair(); + + // it's being sent encoded from dapp + final base58Decoded = base58.decode(message); + final decodedMessage = utf8.decode(base58Decoded); + if (await MethodsUtils.requestApproval( + decodedMessage, + method: pRequest.method, + chainId: pRequest.chainId, + address: keyPair.pubkey.toBase58(), + transportType: pRequest.transportType.name, + )) { + final signature = await nacl.sign.detached( + base58Decoded, + keyPair.seckey, + ); + + response = response.copyWith( + result: { + 'signature': signature.toBase58(), + }, + ); + } else { + response = response.copyWith( + error: const JsonRpcError(code: 5001, message: 'User rejected'), + ); + } + // + } catch (e) { + debugPrint('[SampleWallet] polkadotSignMessage error $e'); + response = response.copyWith( + error: JsonRpcError(code: 0, message: e.toString()), + ); + } + + _handleResponseForTopic(topic, response); + } + + Future solanaSignTransaction(String topic, dynamic parameters) async { + debugPrint( + '[SampleWallet] solanaSignTransaction: ${jsonEncode(parameters)}'); + final pRequest = _web3wallet.pendingRequests.getAll().last; + var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0'); + + try { + final params = parameters as Map; + final beautifiedTrx = const JsonEncoder.withIndent(' ').convert(params); + + final keyPair = await _getKeyPair(); + + if (await MethodsUtils.requestApproval( + // Show Approval Modal + beautifiedTrx, + method: pRequest.method, + chainId: pRequest.chainId, + address: keyPair.pubkey.toBase58(), + transportType: pRequest.transportType.name, + )) { + // Sign the transaction. + // if params contains `transaction` key we should parse that one and disregard the rest, see https://docs.walletconnect.com/advanced/multichain/rpc-reference/solana-rpc#solana_signtransaction + if (params.containsKey('transaction')) { + final encodedTx = params['transaction'] as String; + final decodedTx = solana.Transaction.fromBase64(encodedTx); + + // Sign the transaction. + decodedTx.sign([keyPair]); + + response = response.copyWith( + result: { + 'signature': decodedTx.signatures.first.toBase58(), + }, + ); + } else { + // else we parse the other key/values, see https://docs.walletconnect.com/advanced/multichain/rpc-reference/solana-rpc#solana_signtransaction + final feePayer = params['feePayer'].toString(); + final recentBlockHash = params['recentBlockhash'].toString(); + final instructionsList = params['instructions'] as List; + + final instructions = instructionsList.map((json) { + return (json as Map).toInstruction(); + }).toList(); + + final decodedTx = solana.Transaction.v0( + payer: solana.Pubkey.fromBase58(feePayer), + instructions: instructions, + recentBlockhash: recentBlockHash, + ); + + // Sign the transaction. + decodedTx.sign([keyPair]); + + response = response.copyWith( + result: { + 'signature': decodedTx.signatures.first.toBase58(), + }, + ); + } + } else { + response = response.copyWith( + error: const JsonRpcError(code: 5001, message: 'User rejected'), + ); + } + } catch (e, s) { + debugPrint('[SampleWallet] solanaSignTransaction error $e, $s'); + response = response.copyWith( + error: JsonRpcError(code: 0, message: e.toString()), + ); + } + + _handleResponseForTopic(topic, response); + } + + Future _getKeyPair() async { + final keys = GetIt.I().getKeysForChain( + chainSupported.chainId, + ); + try { + final secKeyBytes = keys[0].privateKey.parse32Bytes(); + return solana.Keypair.fromSeedSync(secKeyBytes); + } catch (e) { + final secKeyBytes = base58.decode(keys[0].privateKey); + // final bytes = Uint8List.fromList(secKeyBytes.sublist(0, 32)); + return solana.Keypair.fromSeckeySync(secKeyBytes); + } + } + + void _handleResponseForTopic(String topic, JsonRpcResponse response) async { + final session = _web3wallet.sessions.get(topic); + + try { + await _web3wallet.respondSessionRequest( + topic: topic, + response: response, + ); + MethodsUtils.handleRedirect( + topic, + session!.peer.metadata.redirect, + response.error?.message, + ); + } on WalletConnectError catch (error) { + MethodsUtils.handleRedirect( + topic, + session!.peer.metadata.redirect, + error.message, + ); + } + } +} + +extension on Map { + solana.TransactionInstruction toInstruction() { + final programId = this['programId'] as String; + + final data = (this['data'] as String); + final dataBytes = base64.decode(data); + + final keys = this['keys'] as List; + return solana.TransactionInstruction( + programId: solana.Pubkey.fromBase58(programId), + data: dataBytes, + keys: keys.map((k) { + final kParams = (k as Map); + return solana.AccountMeta( + solana.Pubkey.fromBase58(kParams['pubkey']), + isSigner: kParams['isSigner'] as bool, + isWritable: kParams['isWritable'] as bool, + ); + }).toList(), + ); + } +} + +extension on String { + // SigningKey used by solana package requires a 32 bytes key + Uint8List parse32Bytes() { + final List secBytes = split(',').map((e) => int.parse(e)).toList(); + return Uint8List.fromList(secBytes.sublist(0, 32)); + } +} + +extension on Uint8List { + String toBase58() => base58.encode(this); +} diff --git a/example/wallet/lib/dependencies/key_service/key_service.dart b/example/wallet/lib/dependencies/key_service/key_service.dart index 69c4ef66..fd8bd6d1 100644 --- a/example/wallet/lib/dependencies/key_service/key_service.dart +++ b/example/wallet/lib/dependencies/key_service/key_service.dart @@ -1,6 +1,7 @@ import 'dart:convert'; -import 'package:convert/convert.dart'; +import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:solana_web3/solana_web3.dart' show hex; import 'package:flutter/foundation.dart'; import 'package:walletconnect_flutter_v2/apis/core/crypto/crypto_models.dart'; import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; @@ -83,8 +84,7 @@ class KeyService extends IKeyService { @override Future loadDefaultWallet() async { - const mnemonic = - 'spoil video deputy round immense setup wasp secret maze slight bag what'; + const mnemonic = DartDefines.ethereumSecretKey; await restoreWalletFromSeed(mnemonic: mnemonic); } @@ -136,10 +136,10 @@ class KeyService extends IKeyService { } final seed = bip39.mnemonicToSeed(mnemonic); - final root = bip32.BIP32.fromSeed(seed); + final node = bip32.BIP32.fromSeed(seed); + final child = node.derivePath("m/44'/60'/0'/0/$index"); - final child = root.derivePath("m/44'/60'/0'/0/$index"); - final private = hex.encode(child.privateKey as List); + final private = hex.encode(child.privateKey!); final public = hex.encode(child.publicKey); return CryptoKeyPair(private, public); } @@ -164,11 +164,13 @@ class KeyService extends IKeyService { final kadenaChainKey = _kadenaChainKey(); final polkadotChainKey = _polkadotChainKey(); final solanaChainKeys = _solanaChainKey(); + final bitcoinChainKeys = await _bitcoinChainKey(); // return [ kadenaChainKey, polkadotChainKey, solanaChainKeys, + bitcoinChainKeys, ]; } @@ -198,4 +200,52 @@ class KeyService extends IKeyService { address: DartDefines.solanaAddress, ); } + + Future _bitcoinChainKey() async { + final mnemonic = await getMnemonic(); + final seed = bip39.mnemonicToSeed(mnemonic); + final node = bip32.BIP32.fromSeed(seed); + final child = node.derivePath("m/84'/0'/0'/0/0"); + final strng = child.toBase58(); + final restored = bip32.BIP32.fromBase58(strng); + final privateKey = ECPrivate.fromBytes(restored.privateKey!.toList()); + + const network = BitcoinNetwork.mainnet; + final wif = privateKey.toWif(network: network); + final publicKey = privateKey.getPublic(); + + // Generate a Pay-to-Public-Key-Hash (P2PKH) address from the public key. + final p2pkh = publicKey.toAddress(); + debugPrint('[$runtimeType] p2pkh ${p2pkh.toAddress(network)}'); + // Generate a Pay-to-Witness-Public-Key-Hash (P2WPKH) Segregated Witness (SegWit) address from the public key. + final p2wpkh = publicKey.toSegwitAddress(); + debugPrint('[$runtimeType] p2wpkh ${p2wpkh.toAddress(network)}'); + // // Generate a Pay-to-Witness-Script-Hash (P2WSH) Segregated Witness (SegWit) address from the public key. + // final p2wsh = publicKey.toP2wshAddress(); + // debugPrint('[$runtimeType] p2wsh ${p2wsh.toAddress(network)}'); + // // Generate a Taproot address from the public key. + // final p2tr = publicKey.toTaprootAddress(); + // debugPrint('[$runtimeType] p2tr ${p2tr.toAddress(network)}'); + // // Generate a Pay-to-Public-Key-Hash (P2PKH) inside Pay-to-Script-Hash (P2SH) address from the public key. + // final p2pkhInP2sh = publicKey.toP2pkhInP2sh(); + // debugPrint('[$runtimeType] p2pkhInP2sh ${p2pkhInP2sh.toAddress(network)}'); + // // Generate a Pay-to-Witness-Public-Key-Hash (P2WPKH) inside Pay-to-Script-Hash (P2SH) address from the public key. + // final p2wpkhInP2sh = publicKey.toP2wpkhInP2sh(); + // debugPrint( + // '[$runtimeType] p2wpkhInP2sh ${p2wpkhInP2sh.toAddress(network)}'); + // // Generate a Pay-to-Witness-Script-Hash (P2WSH) inside Pay-to-Script-Hash (P2SH) address from the public key. + // final p2wshInP2sh = publicKey.toP2wshInP2sh(); + // debugPrint('[$runtimeType] p2wshInP2sh ${p2wshInP2sh.toAddress(network)}'); + // // Generate a Pay-to-Public-Key (P2PK) inside Pay-to-Script-Hash (P2SH) address from the public key. + // final p2pkInP2sh = publicKey.toP2pkInP2sh(); + // debugPrint('[$runtimeType] p2pkInP2sh ${p2pkInP2sh.toAddress(network)}'); + + // + return ChainKey( + chains: ChainData.bitcoinChains.map((e) => e.chainId).toList(), + privateKey: wif, + publicKey: p2pkh.toAddress(network), + address: p2wpkh.toAddress(network), + ); + } } diff --git a/example/wallet/lib/dependencies/web3wallet_service.dart b/example/wallet/lib/dependencies/web3wallet_service.dart index a715f6ee..3943a7e6 100644 --- a/example/wallet/lib/dependencies/web3wallet_service.dart +++ b/example/wallet/lib/dependencies/web3wallet_service.dart @@ -119,6 +119,36 @@ class Web3WalletService extends IWeb3WalletService { Future init() async { // Await the initialization of the web3wallet await _web3Wallet!.init(); + await _emitEvent(); + } + + Future _emitEvent() async { + if (!_web3Wallet!.core.connectivity.isOnline.value) { + await Future.delayed(const Duration(milliseconds: 500)); + _emitEvent(); + return; + } + + final sessions = _web3Wallet!.sessions.getAll(); + for (var session in sessions) { + try { + final events = NamespaceUtils.getNamespacesEventsForChain( + chainId: 'eip155:1', + namespaces: session.namespaces, + ); + if (events.contains('accountsChanged')) { + final chainKeys = GetIt.I().getKeysForChain('eip155'); + _web3Wallet!.emitSessionEvent( + topic: session.topic, + chainId: 'eip155:1', + event: SessionEventParams( + name: 'accountsChanged', + data: [chainKeys.first.address], + ), + ); + } + } catch (_) {} + } _web3Wallet!.core.connectivity.isOnline.addListener(() { if (_web3Wallet!.core.connectivity.isOnline.value) { final sessions = _web3Wallet!.sessions.getAll(); diff --git a/example/wallet/lib/main.dart b/example/wallet/lib/main.dart index af37875c..ecc111f3 100644 --- a/example/wallet/lib/main.dart +++ b/example/wallet/lib/main.dart @@ -3,11 +3,14 @@ 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/bottom_sheet/i_bottom_sheet_service.dart'; +import 'package:walletconnect_flutter_v2_wallet/dependencies/chain_services/bitcoin_service.dart'; +// ignore: unused_import +import 'package:walletconnect_flutter_v2_wallet/dependencies/chain_services/solana_service.dart'; +import 'package:walletconnect_flutter_v2_wallet/dependencies/chain_services/solana_service_2.dart'; import 'package:walletconnect_flutter_v2_wallet/dependencies/chain_services/cosmos_service.dart'; import 'package:walletconnect_flutter_v2_wallet/dependencies/chain_services/evm_service.dart'; import 'package:walletconnect_flutter_v2_wallet/dependencies/chain_services/kadena_service.dart'; import 'package:walletconnect_flutter_v2_wallet/dependencies/chain_services/polkadot_service.dart'; -import 'package:walletconnect_flutter_v2_wallet/dependencies/chain_services/solana_service.dart'; import 'package:walletconnect_flutter_v2_wallet/dependencies/deep_link_handler.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'; @@ -75,6 +78,14 @@ class _MyHomePageState extends State with GetItStateMixin { ); } + // Support Bitcoin Chains + for (final chainData in ChainData.bitcoinChains) { + GetIt.I.registerSingleton( + BitcoinService(chainSupported: chainData), + instanceName: chainData.chainId, + ); + } + // Support Kadena Chains for (final chainData in ChainData.kadenaChains) { GetIt.I.registerSingleton( @@ -92,9 +103,10 @@ class _MyHomePageState extends State with GetItStateMixin { } // Support Solana Chains + // Change SolanaService2 to SolanaService to switch between solana_web3: ^0.1.3 to solana: ^0.30.4 for (final chainData in ChainData.solanaChains) { - GetIt.I.registerSingleton( - SolanaService(chainSupported: chainData), + GetIt.I.registerSingleton( + SolanaService2(chainSupported: chainData), instanceName: chainData.chainId, ); } diff --git a/example/wallet/lib/models/chain_data.dart b/example/wallet/lib/models/chain_data.dart index f69380ca..4cffb8f0 100644 --- a/example/wallet/lib/models/chain_data.dart +++ b/example/wallet/lib/models/chain_data.dart @@ -98,25 +98,19 @@ class ChainData { static final List solanaChains = [ const ChainMetadata( type: ChainType.solana, - chainId: 'solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ', - name: 'Solana Mainnet 1', + chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + name: 'Solana Mainnet', logo: '/chain-logos/solana.png', color: Color.fromARGB(255, 247, 0, 255), - rpc: [ - 'https://rpc.ankr.com/solana', - 'https://api.tatum.io/v3/blockchain/node/solana-mainnet', - ], + rpc: ['https://api.mainnet-beta.solana.com'], ), const ChainMetadata( type: ChainType.solana, - chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', - name: 'Solana Mainnet 2', + chainId: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1', + name: 'Solana Devnet', logo: '/chain-logos/solana.png', color: Color.fromARGB(255, 247, 0, 255), - rpc: [ - 'https://rpc.ankr.com/solana', - 'https://api.tatum.io/v3/blockchain/node/solana-mainnet', - ], + rpc: ['https://api.devnet.solana.com'], ), const ChainMetadata( type: ChainType.solana, @@ -125,9 +119,7 @@ class ChainData { logo: '/chain-logos/solana.png', color: Colors.black, isTestnet: true, - rpc: [ - 'https://api.testnet.solana.com', - ], + rpc: ['https://api.testnet.solana.com'], ), ]; @@ -195,4 +187,27 @@ class ChainData { ], ), ]; + + static final List bitcoinChains = [ + const ChainMetadata( + type: ChainType.bitcoin, + chainId: + 'bip122:000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f', + name: 'Bitcoin Mainnet', + logo: '/chain-logos/bitcoin.png', + color: Color.fromARGB(255, 255, 161, 9), + rpc: ['https://bitcoin.drpc.org/'], + ), + const ChainMetadata( + type: ChainType.bitcoin, + chainId: + 'bip122:000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943', + name: 'Bitcoin Signet', + logo: '/chain-logos/bitcoin.png', + color: Color.fromARGB(255, 255, 161, 9), + rpc: ['https://signet.bitcoinrpc.org'], + // https://bitcoin-testnet.drpc.org + isTestnet: true, + ), + ]; } diff --git a/example/wallet/lib/models/chain_metadata.dart b/example/wallet/lib/models/chain_metadata.dart index 38aee3de..f6b99db5 100644 --- a/example/wallet/lib/models/chain_metadata.dart +++ b/example/wallet/lib/models/chain_metadata.dart @@ -7,6 +7,7 @@ enum ChainType { cosmos, kadena, polkadot, + bitcoin, } class ChainMetadata { diff --git a/example/wallet/lib/pages/settings_page.dart b/example/wallet/lib/pages/settings_page.dart index 82d15a57..07d0d918 100644 --- a/example/wallet/lib/pages/settings_page.dart +++ b/example/wallet/lib/pages/settings_page.dart @@ -61,6 +61,9 @@ class _SettingsPageState extends State { // const SizedBox(height: 20.0), const Divider(height: 1.0), + _BitcoinAccounts(), + const SizedBox(height: 20.0), + const Divider(height: 1.0), _SolanaAccounts(), const SizedBox(height: 20.0), const Divider(height: 1.0), @@ -468,6 +471,54 @@ class _KadenaAccounts extends StatelessWidget { } } +class _BitcoinAccounts extends StatelessWidget { + @override + Widget build(BuildContext context) { + final keysService = GetIt.I(); + final chainKeys = keysService.getKeysForChain('bip122'); + if (chainKeys.isEmpty) return const SizedBox.shrink(); + return Column( + children: [ + const Padding( + padding: EdgeInsets.all(12.0), + child: Row( + children: [ + SizedBox.square(dimension: 8.0), + Expanded( + child: Text( + 'Bitcoin Account', + style: TextStyle( + color: Colors.black, + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Column( + children: [ + _DataContainer( + title: 'Address', + data: chainKeys.first.address, + ), + const SizedBox(height: 12.0), + _DataContainer( + title: 'Secret key', + data: chainKeys.first.privateKey, + blurred: true, + ), + ], + ), + ) + ], + ); + } +} + class _DeviceData extends StatelessWidget { @override Widget build(BuildContext context) { diff --git a/example/wallet/lib/utils/dart_defines.dart b/example/wallet/lib/utils/dart_defines.dart index 6c5964e8..33b3eec9 100644 --- a/example/wallet/lib/utils/dart_defines.dart +++ b/example/wallet/lib/utils/dart_defines.dart @@ -1,8 +1,12 @@ class DartDefines { - static const String projectId = String.fromEnvironment( - 'PROJECT_ID', - ); + static const projectId = String.fromEnvironment('PROJECT_ID'); // HARDCODED TEST KEYS + // ETHEREUM + static const ethereumSecretKey = String.fromEnvironment( + 'ETH_SECRET_KEY', + defaultValue: + 'spoil video deputy round immense setup wasp secret maze slight bag what', + ); // KADENA static const kadenaSecretKey = String.fromEnvironment( 'KADENA_SECRET_KEY', @@ -28,7 +32,7 @@ class DartDefines { static const polkadotMnemonic = String.fromEnvironment( 'POLKADOT_MNEMONIC', defaultValue: - 'shove trumpet draw priority either tonight million worry dust vivid twelve solid', + 'spoil video deputy round immense setup wasp secret maze slight bag what', ); static const polkadotAddress = String.fromEnvironment( 'POLKADOT_ADDRESS', diff --git a/example/wallet/lib/utils/eth_utils.dart b/example/wallet/lib/utils/eth_utils.dart index 244c4b3a..d8cb5af8 100644 --- a/example/wallet/lib/utils/eth_utils.dart +++ b/example/wallet/lib/utils/eth_utils.dart @@ -1,8 +1,8 @@ import 'dart:convert'; -import 'package:convert/convert.dart'; import 'package:flutter/foundation.dart'; import 'package:get_it/get_it.dart'; +import 'package:solana_web3/solana_web3.dart' show hex; import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; import 'package:walletconnect_flutter_v2_wallet/dependencies/i_web3wallet_service.dart'; diff --git a/example/wallet/lib/utils/methods_utils.dart b/example/wallet/lib/utils/methods_utils.dart index c6fbcf35..5dbe89c7 100644 --- a/example/wallet/lib/utils/methods_utils.dart +++ b/example/wallet/lib/utils/methods_utils.dart @@ -57,6 +57,7 @@ class MethodsUtils { debugPrint( '[SampleWallet] handleRedirect topic: $topic, redirect: $redirect, error: $error'); openApp(topic, redirect, onFail: (e) { + debugPrint('[SampleWallet] handleRedirect error $e'); goBackModal( title: 'Error', message: error, @@ -93,7 +94,7 @@ class MethodsUtils { closeAfter: success ? 3 : 0, widget: Container( color: Colors.white, - height: 210.0, + height: 300.0, width: double.infinity, padding: const EdgeInsets.all(20.0), child: Column( diff --git a/example/wallet/pubspec.yaml b/example/wallet/pubspec.yaml index e3238d70..7a4d54bc 100644 --- a/example/wallet/pubspec.yaml +++ b/example/wallet/pubspec.yaml @@ -9,6 +9,9 @@ environment: sdk: ">=2.19.0 <4.0.0" dependencies: + bitcoin_base: ^4.2.1 + # flutter_bitcoin: ^1.0.1 + # bitcoin_message_signer: ^1.0.2 flutter: sdk: flutter @@ -21,11 +24,12 @@ dependencies: eth_sig_util: ^0.0.9 get_it_mixin: ^4.0.0 package_info_plus: ^7.0.0 - convert: ^3.0.1 + # convert: ^3.0.1 # CHECK WEB SUPPORT kadena_dart_sdk: ^2.3.2 solana: ^0.30.4 + solana_web3: ^0.1.3 polkadart_keyring: ^0.4.3 polkadart: ^0.4.6 # diff --git a/example/wallet/test/widget_test.dart b/example/wallet/test/widget_test.dart index 990fad29..aee7c630 100644 --- a/example/wallet/test/widget_test.dart +++ b/example/wallet/test/widget_test.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:convert/convert.dart'; +import 'package:solana_web3/solana_web3.dart' show hex; import 'package:flutter_test/flutter_test.dart'; import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';