From b60f048e3d2bd91bd484f4e4575d7c7b42665883 Mon Sep 17 00:00:00 2001
From: Alfreedom <00tango.bromine@icloud.com>
Date: Wed, 31 Jan 2024 21:00:13 +0100
Subject: [PATCH] first approach: added custom credentials as extension and
enhanced examples
---
example/dapp/android/app/build.gradle | 4 +-
.../dapp/ios/Runner.xcodeproj/project.pbxproj | 9 +-
example/dapp/ios/Runner/Info.plist | 50 +-
example/dapp/lib/main.dart | 31 +-
example/dapp/lib/pages/connect_page.dart | 41 +-
example/dapp/lib/utils/crypto/eip155.dart | 153 +-
example/dapp/lib/utils/smart_contracts.dart | 279 +++
example/dapp/lib/widgets/session_widget.dart | 184 +-
.../macos/Runner/Configs/AppInfo.xcconfig | 2 +-
.../lib/dependencies/bip32/bip32_base.dart | 269 +++
.../lib/dependencies/bip32/utils/crypto.dart | 21 +
.../lib/dependencies/bip32/utils/ecurve.dart | 263 +++
.../lib/dependencies/bip32/utils/wif.dart | 56 +
.../lib/dependencies/bip39/bip39_base.dart | 153 ++
.../lib/dependencies/bip39/utils/pbkdf2.dart | 31 +
.../dependencies/bip39/wordlists/english.dart | 2052 +++++++++++++++++
.../lib/dependencies/chains/evm_service.dart | 218 +-
.../dependencies/key_service/chain_key.dart | 4 +-
.../key_service/i_key_service.dart | 15 +-
.../dependencies/key_service/key_service.dart | 133 +-
.../lib/dependencies/web3wallet_service.dart | 14 +-
example/wallet/lib/main.dart | 8 +-
example/wallet/lib/models/chain_data.dart | 72 +
example/wallet/lib/pages/app_detail_page.dart | 3 +-
example/wallet/lib/pages/apps_page.dart | 1 +
example/wallet/lib/pages/settings_page.dart | 235 ++
example/wallet/lib/utils/constants.dart | 2 +-
example/wallet/lib/utils/dart_defines.dart | 6 +-
.../lib/utils/namespace_model_builder.dart | 22 +-
example/wallet/lib/widgets/custom_button.dart | 8 +-
.../wallet/lib/widgets/recover_from_seed.dart | 88 +
.../wc_connection_widget.dart | 2 +-
.../wc_connection_widget_info.dart | 2 +-
example/wallet/pubspec.yaml | 1 -
lib/apis/utils/extensions.dart | 163 ++
lib/walletconnect_flutter_v2.dart | 2 +-
36 files changed, 4354 insertions(+), 243 deletions(-)
create mode 100644 example/dapp/lib/utils/smart_contracts.dart
create mode 100644 example/wallet/lib/dependencies/bip32/bip32_base.dart
create mode 100644 example/wallet/lib/dependencies/bip32/utils/crypto.dart
create mode 100644 example/wallet/lib/dependencies/bip32/utils/ecurve.dart
create mode 100644 example/wallet/lib/dependencies/bip32/utils/wif.dart
create mode 100644 example/wallet/lib/dependencies/bip39/bip39_base.dart
create mode 100644 example/wallet/lib/dependencies/bip39/utils/pbkdf2.dart
create mode 100644 example/wallet/lib/dependencies/bip39/wordlists/english.dart
create mode 100644 example/wallet/lib/models/chain_data.dart
create mode 100644 example/wallet/lib/pages/settings_page.dart
create mode 100644 example/wallet/lib/widgets/recover_from_seed.dart
create mode 100644 lib/apis/utils/extensions.dart
diff --git a/example/dapp/android/app/build.gradle b/example/dapp/android/app/build.gradle
index db2ca1aa..c3c083b9 100644
--- a/example/dapp/android/app/build.gradle
+++ b/example/dapp/android/app/build.gradle
@@ -44,7 +44,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
- applicationId "com.example.dapp"
+ applicationId "com.walletconnect.flutterdapp"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion flutter.minSdkVersion
@@ -60,7 +60,7 @@ android {
signingConfig signingConfigs.debug
}
}
- namespace 'com.example.dapp'
+ namespace 'com.walletconnect.flutterdapp'
}
flutter {
diff --git a/example/dapp/ios/Runner.xcodeproj/project.pbxproj b/example/dapp/ios/Runner.xcodeproj/project.pbxproj
index 36a22b63..a65a62da 100644
--- a/example/dapp/ios/Runner.xcodeproj/project.pbxproj
+++ b/example/dapp/ios/Runner.xcodeproj/project.pbxproj
@@ -361,11 +361,12 @@
DEVELOPMENT_TEAM = W5R8AG9K22;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = "Flutter Dapp";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
- PRODUCT_BUNDLE_IDENTIFIER = com.example.dapp;
+ PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.flutterdapp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@@ -490,11 +491,12 @@
DEVELOPMENT_TEAM = W5R8AG9K22;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = "Flutter Dapp";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
- PRODUCT_BUNDLE_IDENTIFIER = com.example.dapp;
+ PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.flutterdapp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -513,11 +515,12 @@
DEVELOPMENT_TEAM = W5R8AG9K22;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = "Flutter Dapp";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
- PRODUCT_BUNDLE_IDENTIFIER = com.example.dapp;
+ PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.flutterdapp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
diff --git a/example/dapp/ios/Runner/Info.plist b/example/dapp/ios/Runner/Info.plist
index 21b9a0ff..0627d218 100644
--- a/example/dapp/ios/Runner/Info.plist
+++ b/example/dapp/ios/Runner/Info.plist
@@ -2,10 +2,12 @@
+ CADisableMinimumFrameDurationOnPhone
+
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleDisplayName
- Dapp
+ Flutter Dapp
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
@@ -13,17 +15,38 @@
CFBundleInfoDictionaryVersion
6.0
CFBundleName
- dapp
+ Flutter Dapp
CFBundlePackageType
APPL
CFBundleShortVersionString
$(FLUTTER_BUILD_NAME)
CFBundleSignature
????
+ CFBundleURLTypes
+
+
+ CFBundleTypeRole
+ Editor
+ CFBundleURLName
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleURLSchemes
+
+ wcflutterdapp
+
+
+
CFBundleVersion
$(FLUTTER_BUILD_NUMBER)
+ LSApplicationCategoryType
+
+ LSApplicationQueriesSchemes
+
+ wcflutterwallet
+
LSRequiresIPhoneOS
+ UIApplicationSupportsIndirectInputEvents
+
UILaunchStoryboardName
LaunchScreen
UIMainStoryboardFile
@@ -43,28 +66,5 @@
UIViewControllerBasedStatusBarAppearance
- CADisableMinimumFrameDurationOnPhone
-
- UIApplicationSupportsIndirectInputEvents
-
- LSApplicationCategoryType
-
- CFBundleURLTypes
-
-
- CFBundleTypeRole
- Editor
- CFBundleURLName
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleURLSchemes
-
- wcflutterdapp
-
-
-
- LSApplicationQueriesSchemes
-
- wcflutterwallet
-
diff --git a/example/dapp/lib/main.dart b/example/dapp/lib/main.dart
index ce1706c2..3d3a7b1e 100644
--- a/example/dapp/lib/main.dart
+++ b/example/dapp/lib/main.dart
@@ -63,7 +63,7 @@ class _MyHomePageState extends State {
projectId: DartDefines.projectId,
logLevel: LogLevel.info,
metadata: const PairingMetadata(
- name: 'Sample dApp Fllutter',
+ name: 'Sample dApp Flutter',
description: 'WalletConnect\'s sample dapp with Flutter',
url: 'https://walletconnect.com/',
icons: [
@@ -86,12 +86,17 @@ class _MyHomePageState extends State {
}
// Register event handlers
+ _web3App!.onSessionConnect.subscribe(_onSessionConnect);
_web3App!.onSessionPing.subscribe(_onSessionPing);
_web3App!.onSessionEvent.subscribe(_onSessionEvent);
_web3App!.onSessionUpdate.subscribe(_onSessionUpdate);
+
_web3App!.core.relayClient.onRelayClientConnect.subscribe(_setState);
_web3App!.core.relayClient.onRelayClientDisconnect.subscribe(_setState);
- _web3App!.onSessionConnect.subscribe(_onSessionConnect);
+ _web3App!.core.relayClient.onRelayClientMessage.subscribe(_onRelayMessage);
+
+ _web3App!.signEngine.onSessionEvent.subscribe(_onSessionEvent);
+ _web3App!.signEngine.onSessionUpdate.subscribe(_onSessionUpdate);
setState(() {
_pageDatas = [
@@ -128,12 +133,19 @@ class _MyHomePageState extends State {
@override
void dispose() {
+ // Unregister event handlers
_web3App!.onSessionConnect.unsubscribe(_onSessionConnect);
- _web3App!.core.relayClient.onRelayClientConnect.unsubscribe(_setState);
- _web3App!.core.relayClient.onRelayClientDisconnect.unsubscribe(_setState);
_web3App!.onSessionPing.unsubscribe(_onSessionPing);
_web3App!.onSessionEvent.unsubscribe(_onSessionEvent);
_web3App!.onSessionUpdate.unsubscribe(_onSessionUpdate);
+
+ _web3App!.core.relayClient.onRelayClientConnect.unsubscribe(_setState);
+ _web3App!.core.relayClient.onRelayClientDisconnect.unsubscribe(_setState);
+ _web3App!.core.relayClient.onRelayClientMessage
+ .unsubscribe(_onRelayMessage);
+
+ _web3App!.signEngine.onSessionEvent.unsubscribe(_onSessionEvent);
+ _web3App!.signEngine.onSessionUpdate.unsubscribe(_onSessionUpdate);
super.dispose();
}
@@ -249,4 +261,15 @@ class _MyHomePageState extends State {
void _onSessionUpdate(SessionUpdate? args) {
debugPrint('[$runtimeType] _onSessionUpdate $args');
}
+
+ void _onRelayMessage(MessageEvent? args) async {
+ if (args != null) {
+ final payloadString = await _web3App!.core.crypto.decode(
+ args.topic,
+ args.message,
+ );
+ final data = jsonDecode(payloadString ?? '{}') as Map;
+ debugPrint(data.toString());
+ }
+ }
}
diff --git a/example/dapp/lib/pages/connect_page.dart b/example/dapp/lib/pages/connect_page.dart
index 843007fd..f6bf3cf6 100644
--- a/example/dapp/lib/pages/connect_page.dart
+++ b/example/dapp/lib/pages/connect_page.dart
@@ -107,7 +107,7 @@ class ConnectPageState extends State {
children: [
const Text(
StringConstants.appTitle,
- style: StyleConstants.titleText,
+ style: StyleConstants.subtitleText,
textAlign: TextAlign.center,
),
const SizedBox(
@@ -115,7 +115,7 @@ class ConnectPageState extends State {
),
const Text(
StringConstants.selectChains,
- style: StyleConstants.subtitleText,
+ style: StyleConstants.paragraph,
textAlign: TextAlign.center,
),
const SizedBox(
@@ -169,8 +169,32 @@ class ConnectPageState extends State {
final encodedUri = Uri.encodeComponent(res.uri.toString());
final uri = 'wcflutterwallet://wc?uri=$encodedUri';
+ // final uri = 'metamask://wc?uri=$encodedUri';
if (await canLaunchUrlString(uri)) {
- launchUrlString(uri, mode: LaunchMode.externalApplication);
+ // ignore: use_build_context_synchronously
+ final openApp = await showDialog(
+ context: context,
+ builder: (BuildContext context) {
+ return AlertDialog(
+ content: const Text('Do you want to open with Web3Wallet Flutter'),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.of(context).pop(false),
+ child: const Text('Show QR'),
+ ),
+ TextButton(
+ onPressed: () => Navigator.of(context).pop(true),
+ child: const Text('Open'),
+ ),
+ ],
+ );
+ },
+ );
+ if (openApp) {
+ launchUrlString(uri, mode: LaunchMode.externalApplication);
+ } else {
+ _showQrCode(res);
+ }
} else {
_showQrCode(res);
}
@@ -203,13 +227,14 @@ class ConnectPageState extends State {
showToast?.call(StringConstants.authSucceeded);
}
- if (_shouldDismissQrCode) {
+ // ignore: use_build_context_synchronously
+ if (_shouldDismissQrCode && Navigator.canPop(context)) {
// ignore: use_build_context_synchronously
Navigator.pop(context);
}
} catch (e) {
- // debugPrint(e.toString());
- if (_shouldDismissQrCode) {
+ // ignore: use_build_context_synchronously
+ if (_shouldDismissQrCode && Navigator.canPop(context)) {
// ignore: use_build_context_synchronously
Navigator.pop(context);
}
@@ -217,9 +242,7 @@ class ConnectPageState extends State {
}
}
- Future _showQrCode(
- ConnectResponse response,
- ) async {
+ Future _showQrCode(ConnectResponse response) async {
// Show the QR code
debugPrint('Showing QR Code: ${response.uri}');
diff --git a/example/dapp/lib/utils/crypto/eip155.dart b/example/dapp/lib/utils/crypto/eip155.dart
index 730c7677..cd6e6bcd 100644
--- a/example/dapp/lib/utils/crypto/eip155.dart
+++ b/example/dapp/lib/utils/crypto/eip155.dart
@@ -1,5 +1,10 @@
+import 'dart:convert';
+
+import 'package:intl/intl.dart';
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
-import 'package:walletconnect_flutter_v2_dapp/models/eth/ethereum_transaction.dart';
+import 'package:walletconnect_flutter_v2_dapp/models/chain_metadata.dart';
+import 'package:walletconnect_flutter_v2_dapp/utils/crypto/chain_data.dart';
+import 'package:walletconnect_flutter_v2_dapp/utils/smart_contracts.dart';
import 'package:walletconnect_flutter_v2_dapp/utils/test_data.dart';
enum EIP155Methods {
@@ -15,10 +20,6 @@ enum EIP155Events {
accountsChanged,
}
-extension EIP155MethodsX on EIP155Methods {
- String? get value => EIP155.methods[this];
-}
-
extension EIP155MethodsStringX on String {
EIP155Methods? toEip155Method() {
final entries = EIP155.methods.entries.where(
@@ -28,10 +29,6 @@ extension EIP155MethodsStringX on String {
}
}
-extension EIP155EventsX on EIP155Events {
- String? get value => EIP155.events[this];
-}
-
extension EIP155EventsStringX on String {
EIP155Events? toEip155Event() {
final entries = EIP155.events.entries.where(
@@ -59,7 +56,7 @@ class EIP155 {
required Web3App web3App,
required String topic,
required EIP155Methods method,
- required String chainId,
+ required ChainMetadata chainData,
required String address,
}) {
switch (method) {
@@ -67,7 +64,7 @@ class EIP155 {
return personalSign(
web3App: web3App,
topic: topic,
- chainId: chainId,
+ chainId: chainData.chainId,
address: address,
data: testSignData,
);
@@ -75,7 +72,7 @@ class EIP155 {
return ethSign(
web3App: web3App,
topic: topic,
- chainId: chainId,
+ chainId: chainData.chainId,
address: address,
data: testSignData,
);
@@ -83,7 +80,7 @@ class EIP155 {
return ethSignTypedData(
web3App: web3App,
topic: topic,
- chainId: chainId,
+ chainId: chainData.chainId,
address: address,
data: typedData,
);
@@ -91,24 +88,69 @@ class EIP155 {
return ethSignTransaction(
web3App: web3App,
topic: topic,
- chainId: chainId,
- transaction: EthereumTransaction(
- from: address,
- to: address,
- value: '0x01',
+ chainId: chainData.chainId,
+ transaction: Transaction(
+ from: EthereumAddress.fromHex(address),
+ to: EthereumAddress.fromHex(
+ '0x59e2f66C0E96803206B6486cDb39029abAE834c0'),
+ value: EtherAmount.fromInt(EtherUnit.finney, 12), // == 0.012
),
);
case EIP155Methods.ethSendTransaction:
return ethSendTransaction(
web3App: web3App,
topic: topic,
- chainId: chainId,
- transaction: EthereumTransaction(
- from: address,
- to: address,
- value: '0x01',
+ chainId: chainData.chainId,
+ transaction: Transaction(
+ from: EthereumAddress.fromHex(address),
+ to: EthereumAddress.fromHex(
+ '0x59e2f66C0E96803206B6486cDb39029abAE834c0'),
+ value: EtherAmount.fromInt(EtherUnit.finney, 11), // == 0.011
+ ),
+ );
+ }
+ }
+
+ static Future callSmartContract({
+ required Web3App web3App,
+ required String topic,
+ required String address,
+ required String action,
+ }) {
+ // Create DeployedContract object using contract's ABI and address
+ final deployedContract = DeployedContract(
+ ContractAbi.fromJson(
+ jsonEncode(SepoliaTestContract.readContractAbi),
+ 'Alfreedoms',
+ ),
+ EthereumAddress.fromHex(SepoliaTestContract.contractAddress),
+ );
+
+ switch (action) {
+ case 'read':
+ return readSmartContract(
+ web3App: web3App,
+ rpcUrl: ChainData.testChains.first.rpc.first,
+ contract: deployedContract,
+ address: address,
+ );
+ case 'write':
+ return writeToSmartContract(
+ web3App: web3App,
+ rpcUrl: ChainData.testChains.first.rpc.first,
+ address: address,
+ topic: topic,
+ chainId: ChainData.testChains.first.chainId,
+ contract: deployedContract,
+ transaction: Transaction(
+ from: EthereumAddress.fromHex(address),
+ to: EthereumAddress.fromHex(
+ '0x59e2f66C0E96803206B6486cDb39029abAE834c0'),
+ value: EtherAmount.fromInt(EtherUnit.finney, 10), // == 0.010
),
);
+ default:
+ return Future.value();
}
}
@@ -167,7 +209,7 @@ class EIP155 {
required Web3App web3App,
required String topic,
required String chainId,
- required EthereumTransaction transaction,
+ required Transaction transaction,
}) async {
return await web3App.request(
topic: topic,
@@ -183,7 +225,7 @@ class EIP155 {
required Web3App web3App,
required String topic,
required String chainId,
- required EthereumTransaction transaction,
+ required Transaction transaction,
}) async {
return await web3App.request(
topic: topic,
@@ -194,4 +236,65 @@ class EIP155 {
),
);
}
+
+ static Future readSmartContract({
+ required Web3App web3App,
+ required String rpcUrl,
+ required String address,
+ required DeployedContract contract,
+ }) async {
+ final results = await Future.wait([
+ // results[0]
+ web3App.readContractCall(
+ deployedContract: contract,
+ functionName: 'name',
+ rpcUrl: rpcUrl,
+ ),
+ // results[1]
+ web3App.readContractCall(
+ deployedContract: contract,
+ functionName: 'totalSupply',
+ rpcUrl: rpcUrl,
+ ),
+ // results[2]
+ web3App.readContractCall(
+ deployedContract: contract,
+ functionName: 'balanceOf',
+ rpcUrl: rpcUrl,
+ parameters: [
+ EthereumAddress.fromHex(address),
+ ],
+ ),
+ ]);
+
+ final oCcy = NumberFormat("#,##0.00", "en_US");
+ final name = results[0].toString();
+ final total = results[1] / BigInt.from(1000000000000000000);
+ final balance = results[2] / BigInt.from(1000000000000000000);
+
+ return {
+ 'name': name,
+ 'totalSupply': oCcy.format(total),
+ 'balance': oCcy.format(balance),
+ };
+ }
+
+ static Future writeToSmartContract({
+ required Web3App web3App,
+ required String rpcUrl,
+ required String topic,
+ required String chainId,
+ required String address,
+ required DeployedContract contract,
+ required Transaction transaction,
+ }) async {
+ return await web3App.writeContractCall(
+ topic: topic,
+ chainId: chainId,
+ rpcUrl: rpcUrl,
+ deployedContract: contract,
+ functionName: 'transfer',
+ transaction: transaction,
+ );
+ }
}
diff --git a/example/dapp/lib/utils/smart_contracts.dart b/example/dapp/lib/utils/smart_contracts.dart
new file mode 100644
index 00000000..ca128c39
--- /dev/null
+++ b/example/dapp/lib/utils/smart_contracts.dart
@@ -0,0 +1,279 @@
+class SepoliaTestContract {
+ // Alfreedoms2 ALF2 in Sepolia
+ // DEPLOY https://sepolia.etherscan.io/tx/0xebf287281abbc976b7cf6956a7f5f66338935d324c6453a350e3bb42ff7bd4e2
+ // MINT https://sepolia.etherscan.io/tx/0x04a015504be7420a40a59936bfcca9302e55700fd00129059444539770fed5e7
+ // CONTRACT https://sepolia.etherscan.io/address/0xBe60D05C11BD1C365849C824E0C2D880d2466eAF
+ // TRANSFERS https://sepolia.etherscan.io/token/0xbe60d05c11bd1c365849c824e0c2d880d2466eaf?a=0x59e2f66C0E96803206B6486cDb39029abAE834c0
+ // SOURCIFY https://repo.sourcify.dev/contracts/full_match/11155111/0xBe60D05C11BD1C365849C824E0C2D880d2466eAF/
+ static const contractAddress = '0xBe60D05C11BD1C365849C824E0C2D880d2466eAF';
+
+ static const readContractAbi = [
+ {
+ "inputs": [
+ {"internalType": "address", "name": "initialOwner", "type": "address"}
+ ],
+ "stateMutability": "nonpayable",
+ "type": "constructor"
+ },
+ {
+ "inputs": [
+ {"internalType": "address", "name": "spender", "type": "address"},
+ {"internalType": "uint256", "name": "allowance", "type": "uint256"},
+ {"internalType": "uint256", "name": "needed", "type": "uint256"}
+ ],
+ "name": "ERC20InsufficientAllowance",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {"internalType": "address", "name": "sender", "type": "address"},
+ {"internalType": "uint256", "name": "balance", "type": "uint256"},
+ {"internalType": "uint256", "name": "needed", "type": "uint256"}
+ ],
+ "name": "ERC20InsufficientBalance",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {"internalType": "address", "name": "approver", "type": "address"}
+ ],
+ "name": "ERC20InvalidApprover",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {"internalType": "address", "name": "receiver", "type": "address"}
+ ],
+ "name": "ERC20InvalidReceiver",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {"internalType": "address", "name": "sender", "type": "address"}
+ ],
+ "name": "ERC20InvalidSender",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {"internalType": "address", "name": "spender", "type": "address"}
+ ],
+ "name": "ERC20InvalidSpender",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {"internalType": "address", "name": "owner", "type": "address"}
+ ],
+ "name": "OwnableInvalidOwner",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {"internalType": "address", "name": "account", "type": "address"}
+ ],
+ "name": "OwnableUnauthorizedAccount",
+ "type": "error"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "spender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Approval",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "previousOwner",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "name": "OwnershipTransferred",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "from",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Transfer",
+ "type": "event"
+ },
+ {
+ "inputs": [
+ {"internalType": "address", "name": "owner", "type": "address"},
+ {"internalType": "address", "name": "spender", "type": "address"}
+ ],
+ "name": "allowance",
+ "outputs": [
+ {"internalType": "uint256", "name": "", "type": "uint256"}
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {"internalType": "address", "name": "spender", "type": "address"},
+ {"internalType": "uint256", "name": "value", "type": "uint256"}
+ ],
+ "name": "approve",
+ "outputs": [
+ {"internalType": "bool", "name": "", "type": "bool"}
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {"internalType": "address", "name": "account", "type": "address"}
+ ],
+ "name": "balanceOf",
+ "outputs": [
+ {"internalType": "uint256", "name": "", "type": "uint256"}
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "decimals",
+ "outputs": [
+ {"internalType": "uint8", "name": "", "type": "uint8"}
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {"internalType": "address", "name": "to", "type": "address"},
+ {"internalType": "uint256", "name": "amount", "type": "uint256"}
+ ],
+ "name": "mint",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "name",
+ "outputs": [
+ {"internalType": "string", "name": "", "type": "string"}
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "owner",
+ "outputs": [
+ {"internalType": "address", "name": "", "type": "address"}
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "renounceOwnership",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "symbol",
+ "outputs": [
+ {"internalType": "string", "name": "", "type": "string"}
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "totalSupply",
+ "outputs": [
+ {"internalType": "uint256", "name": "", "type": "uint256"}
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {"internalType": "address", "name": "to", "type": "address"},
+ {"internalType": "uint256", "name": "value", "type": "uint256"}
+ ],
+ "name": "transfer",
+ "outputs": [
+ {"internalType": "bool", "name": "", "type": "bool"}
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {"internalType": "address", "name": "from", "type": "address"},
+ {"internalType": "address", "name": "to", "type": "address"},
+ {"internalType": "uint256", "name": "value", "type": "uint256"}
+ ],
+ "name": "transferFrom",
+ "outputs": [
+ {"internalType": "bool", "name": "", "type": "bool"}
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {"internalType": "address", "name": "newOwner", "type": "address"}
+ ],
+ "name": "transferOwnership",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ }
+ ];
+}
diff --git a/example/dapp/lib/widgets/session_widget.dart b/example/dapp/lib/widgets/session_widget.dart
index 9f60318f..7532033f 100644
--- a/example/dapp/lib/widgets/session_widget.dart
+++ b/example/dapp/lib/widgets/session_widget.dart
@@ -3,6 +3,7 @@ import 'package:url_launcher/url_launcher_string.dart';
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/eip155.dart';
import 'package:walletconnect_flutter_v2_dapp/utils/crypto/helpers.dart';
import 'package:walletconnect_flutter_v2_dapp/utils/string_constants.dart';
@@ -26,14 +27,6 @@ class SessionWidgetState extends State {
@override
Widget build(BuildContext context) {
final List children = [
- Text(
- widget.session.peer.metadata.name,
- style: StyleConstants.titleText,
- textAlign: TextAlign.center,
- ),
- const SizedBox(
- height: StyleConstants.linear16,
- ),
Text(
'${StringConstants.sessionTopic}${widget.session.topic}',
),
@@ -85,19 +78,16 @@ class SessionWidgetState extends State {
),
);
+ children.add(const SizedBox(height: 20.0));
return ListView(
children: children,
);
}
Widget _buildAccountWidget(String namespaceAccount) {
- final String chainId = NamespaceUtils.getChainFromAccount(
- namespaceAccount,
- );
- final String account = NamespaceUtils.getAccount(
- namespaceAccount,
- );
- final ChainMetadata chainMetadata = getChainMetadataFromChain(chainId);
+ final chainId = NamespaceUtils.getChainFromAccount(namespaceAccount);
+ final account = NamespaceUtils.getAccount(namespaceAccount);
+ final chainMetadata = getChainMetadataFromChain(chainId);
final List children = [
Text(
@@ -120,12 +110,13 @@ class SessionWidgetState extends State {
),
];
- children.addAll(
- _buildChainMethodButtons(
- chainMetadata,
- account,
- ),
- );
+ children.addAll(_buildChainMethodButtons(chainMetadata, account));
+
+ children.add(const Divider());
+ if (chainId != ChainData.testChains.first.chainId) {
+ children.add(const Text('Connect to Sepolia to Test'));
+ }
+ children.addAll(_buildSepoliaButtons(account, chainId));
children.addAll([
const SizedBox(
@@ -184,20 +175,15 @@ class SessionWidgetState extends State {
),
child: ElevatedButton(
onPressed: () async {
- final walletUrl = widget.session.peer.metadata.redirect?.native;
- if ((walletUrl ?? '').isNotEmpty) {
- launchUrlString(
- walletUrl!,
- mode: LaunchMode.externalApplication,
- );
- }
- Future future = callChainMethod(
- chainMetadata.type,
- method,
- chainMetadata,
- address,
+ final future = EIP155.callMethod(
+ web3App: widget.web3App,
+ topic: widget.session.topic,
+ method: method.toEip155Method()!,
+ chainData: chainMetadata,
+ address: address.toLowerCase(),
);
MethodDialog.show(context, method, future);
+ _launchWallet();
},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
@@ -224,9 +210,111 @@ class SessionWidgetState extends State {
return buttons;
}
+ void _launchWallet() {
+ final walletUrl = widget.session.peer.metadata.redirect?.native;
+ if ((walletUrl ?? '').isNotEmpty) {
+ launchUrlString(
+ walletUrl!,
+ mode: LaunchMode.externalApplication,
+ );
+ }
+ }
+
+ List _buildSepoliaButtons(String address, String chainId) {
+ final List buttons = [];
+ final enabled = chainId == ChainData.testChains.first.chainId;
+ buttons.add(
+ Container(
+ width: double.infinity,
+ height: StyleConstants.linear48,
+ margin: const EdgeInsets.symmetric(
+ vertical: StyleConstants.linear8,
+ ),
+ child: ElevatedButton(
+ onPressed: enabled
+ ? () async {
+ final future = EIP155.callSmartContract(
+ web3App: widget.web3App,
+ topic: widget.session.topic,
+ address: address,
+ action: 'read',
+ );
+ MethodDialog.show(context, 'Test Contract (Read)', future);
+ }
+ : null,
+ style: ButtonStyle(
+ backgroundColor: MaterialStateProperty.resolveWith((states) {
+ if (states.contains(MaterialState.disabled)) {
+ return StyleConstants.grayColor;
+ }
+ return Colors.orange;
+ }),
+ shape: MaterialStateProperty.all(
+ RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(
+ StyleConstants.linear8,
+ ),
+ ),
+ ),
+ ),
+ child: const Text(
+ 'Test Contract (Read)',
+ style: StyleConstants.buttonText,
+ textAlign: TextAlign.center,
+ ),
+ ),
+ ),
+ );
+ buttons.add(
+ Container(
+ width: double.infinity,
+ height: StyleConstants.linear48,
+ margin: const EdgeInsets.symmetric(
+ vertical: StyleConstants.linear8,
+ ),
+ child: ElevatedButton(
+ onPressed: enabled
+ ? () async {
+ final future = EIP155.callSmartContract(
+ web3App: widget.web3App,
+ topic: widget.session.topic,
+ address: address,
+ action: 'write',
+ );
+ MethodDialog.show(context, 'Test Contract (Write)', future);
+ _launchWallet();
+ }
+ : null,
+ style: ButtonStyle(
+ backgroundColor: MaterialStateProperty.resolveWith((states) {
+ if (states.contains(MaterialState.disabled)) {
+ return StyleConstants.grayColor;
+ }
+ return Colors.orange;
+ }),
+ shape: MaterialStateProperty.all(
+ RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(
+ StyleConstants.linear8,
+ ),
+ ),
+ ),
+ ),
+ child: const Text(
+ 'Test Contract (Write)',
+ style: StyleConstants.buttonText,
+ textAlign: TextAlign.center,
+ ),
+ ),
+ ),
+ );
+
+ return buttons;
+ }
+
List _buildChainEventsTiles(ChainMetadata chainMetadata) {
final List values = [];
- // Add Methods
+
for (final String event in getChainEvents(chainMetadata.type)) {
values.add(
Container(
@@ -258,32 +346,4 @@ class SessionWidgetState extends State {
return values;
}
-
- Future callChainMethod(
- ChainType type,
- String method,
- ChainMetadata chainMetadata,
- String address,
- ) {
- switch (type) {
- case ChainType.eip155:
- return EIP155.callMethod(
- web3App: widget.web3App,
- topic: widget.session.topic,
- method: method.toEip155Method()!,
- chainId: chainMetadata.chainId,
- address: address.toLowerCase(),
- );
- // case ChainType.kadena:
- // return Kadena.callMethod(
- // web3App: widget.web3App,
- // topic: widget.session.topic,
- // method: method.toKadenaMethod()!,
- // chainId: chainMetadata.chainId,
- // address: address.toLowerCase(),
- // );
- default:
- return Future.value();
- }
- }
}
diff --git a/example/dapp/macos/Runner/Configs/AppInfo.xcconfig b/example/dapp/macos/Runner/Configs/AppInfo.xcconfig
index ef570649..e7eb0032 100644
--- a/example/dapp/macos/Runner/Configs/AppInfo.xcconfig
+++ b/example/dapp/macos/Runner/Configs/AppInfo.xcconfig
@@ -8,7 +8,7 @@
PRODUCT_NAME = dapp
// The application's bundle identifier
-PRODUCT_BUNDLE_IDENTIFIER = com.example.dapp
+PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.flutterdapp
// The copyright displayed in application information
PRODUCT_COPYRIGHT = Copyright © 2023 com.example. All rights reserved.
diff --git a/example/wallet/lib/dependencies/bip32/bip32_base.dart b/example/wallet/lib/dependencies/bip32/bip32_base.dart
new file mode 100644
index 00000000..caabd0d5
--- /dev/null
+++ b/example/wallet/lib/dependencies/bip32/bip32_base.dart
@@ -0,0 +1,269 @@
+// ignore_for_file: non_constant_identifier_names, depend_on_referenced_packages, constant_identifier_names
+
+import 'dart:typed_data';
+import 'package:bs58/bs58.dart';
+
+import 'utils/crypto.dart';
+import 'utils/ecurve.dart' as ecc;
+import 'utils/wif.dart' as wif;
+import 'dart:convert';
+
+class Bip32Type {
+ int public;
+ int private;
+ Bip32Type({required this.public, required this.private});
+}
+
+class NetworkType {
+ int wif;
+ Bip32Type bip32;
+ NetworkType({required this.wif, required this.bip32});
+}
+
+final BITCOIN = NetworkType(
+ wif: 0x80,
+ bip32: Bip32Type(
+ public: 0x0488b21e,
+ private: 0x0488ade4,
+ ),
+);
+const HIGHEST_BIT = 0x80000000;
+const UINT31_MAX = 2147483647; // 2^31 - 1
+const UINT32_MAX = 4294967295; // 2^32 - 1
+
+/// Checks if you are awesome. Spoiler: you are.
+class BIP32 {
+ Uint8List? _d;
+ Uint8List? _Q;
+ Uint8List chainCode;
+ int depth = 0;
+ int index = 0;
+ NetworkType network;
+ int parentFingerprint = 0x00000000;
+ BIP32(this._d, this._Q, this.chainCode, this.network);
+
+ Uint8List get publicKey {
+ _Q ??= ecc.pointFromScalar(_d!, true)!;
+ return _Q!;
+ }
+
+ Uint8List? get privateKey => _d;
+ Uint8List get identifier => hash160(publicKey);
+ Uint8List get fingerprint => identifier.sublist(0, 4);
+
+ bool isNeutered() {
+ return _d == null;
+ }
+
+ BIP32 neutered() {
+ final neutered = BIP32.fromPublicKey(publicKey, chainCode, network);
+ neutered.depth = depth;
+ neutered.index = index;
+ neutered.parentFingerprint = parentFingerprint;
+ return neutered;
+ }
+
+ String toBase58() {
+ final version =
+ (!isNeutered()) ? network.bip32.private : network.bip32.public;
+ Uint8List buffer = Uint8List(78);
+ ByteData bytes = buffer.buffer.asByteData();
+ bytes.setUint32(0, version);
+ bytes.setUint8(4, depth);
+ bytes.setUint32(5, parentFingerprint);
+ bytes.setUint32(9, index);
+ buffer.setRange(13, 45, chainCode);
+ if (!isNeutered()) {
+ bytes.setUint8(45, 0);
+ buffer.setRange(46, 78, privateKey!);
+ } else {
+ buffer.setRange(45, 78, publicKey);
+ }
+ return base58.encode(buffer);
+ }
+
+ String toWIF() {
+ if (privateKey == null) {
+ throw ArgumentError('Missing private key');
+ }
+ return wif.encode(
+ wif.WIF(
+ version: network.wif,
+ privateKey: privateKey!,
+ compressed: true,
+ ),
+ );
+ }
+
+ BIP32 derive(int index) {
+ if (index > UINT32_MAX || index < 0) {
+ throw ArgumentError('Expected UInt32');
+ }
+ final isHardened = index >= HIGHEST_BIT;
+ Uint8List data = Uint8List(37);
+ if (isHardened) {
+ if (isNeutered()) {
+ throw ArgumentError('Missing private key for hardened child key');
+ }
+ data[0] = 0x00;
+ data.setRange(1, 33, privateKey!);
+ data.buffer.asByteData().setUint32(33, index);
+ } else {
+ data.setRange(0, 33, publicKey);
+ data.buffer.asByteData().setUint32(33, index);
+ }
+ final I = hmacSHA512(chainCode, data);
+ final IL = I.sublist(0, 32);
+ final IR = I.sublist(32);
+ if (!ecc.isPrivate(IL)) {
+ return derive(index + 1);
+ }
+ BIP32 hd;
+ if (!isNeutered()) {
+ final ki = ecc.privateAdd(privateKey!, IL);
+ if (ki == null) return derive(index + 1);
+ hd = BIP32.fromPrivateKey(ki, IR, network);
+ } else {
+ final ki = ecc.pointAddScalar(publicKey, IL, true);
+ if (ki == null) return derive(index + 1);
+ hd = BIP32.fromPublicKey(ki, IR, network);
+ }
+ hd.depth = depth + 1;
+ hd.index = index;
+ hd.parentFingerprint = fingerprint.buffer.asByteData().getUint32(0);
+ return hd;
+ }
+
+ BIP32 deriveHardened(int index) {
+ if (index > UINT31_MAX || index < 0) {
+ throw ArgumentError('Expected UInt31');
+ }
+ return derive(index + HIGHEST_BIT);
+ }
+
+ BIP32 derivePath(String path) {
+ final regex = RegExp(r"^(m\/)?(\d+'?\/)*\d+'?$");
+ if (!regex.hasMatch(path)) {
+ throw ArgumentError('Expected BIP32 Path');
+ }
+ List splitPath = path.split('/');
+ if (splitPath[0] == 'm') {
+ if (parentFingerprint != 0) {
+ throw ArgumentError('Expected master, got child');
+ }
+ splitPath = splitPath.sublist(1);
+ }
+ return splitPath.fold(this, (BIP32 prevHd, String indexStr) {
+ int index;
+ if (indexStr.substring(indexStr.length - 1) == "'") {
+ index = int.parse(indexStr.substring(0, indexStr.length - 1));
+ return prevHd.deriveHardened(index);
+ } else {
+ index = int.parse(indexStr);
+ return prevHd.derive(index);
+ }
+ });
+ }
+
+ sign(Uint8List hash) {
+ return ecc.sign(hash, privateKey!);
+ }
+
+ verify(Uint8List hash, Uint8List signature) {
+ return ecc.verify(hash, publicKey, signature);
+ }
+
+ factory BIP32.fromBase58(String string, [NetworkType? nw]) {
+ Uint8List buffer = base58.decode(string);
+ if (buffer.length != 78) {
+ throw ArgumentError('Invalid buffer length');
+ }
+ NetworkType network = nw ?? BITCOIN;
+ ByteData bytes = buffer.buffer.asByteData();
+ // 4 bytes: version bytes
+ var version = bytes.getUint32(0);
+ if (version != network.bip32.private && version != network.bip32.public) {
+ throw ArgumentError('Invalid network version');
+ }
+ // 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ...
+ var depth = buffer[4];
+
+ // 4 bytes: the fingerprint of the parent's key (0x00000000 if master key)
+ var parentFingerprint = bytes.getUint32(5);
+ if (depth == 0) {
+ if (parentFingerprint != 0x00000000) {
+ throw ArgumentError('Invalid parent fingerprint');
+ }
+ }
+
+ // 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized.
+ // This is encoded in MSB order. (0x00000000 if master key)
+ var index = bytes.getUint32(9);
+ if (depth == 0 && index != 0) {
+ throw ArgumentError('Invalid index');
+ }
+
+ // 32 bytes: the chain code
+ Uint8List chainCode = buffer.sublist(13, 45);
+ BIP32 hd;
+
+ // 33 bytes: private key data (0x00 + k)
+ if (version == network.bip32.private) {
+ if (bytes.getUint8(45) != 0x00) {
+ throw ArgumentError('Invalid private key');
+ }
+ Uint8List k = buffer.sublist(46, 78);
+ hd = BIP32.fromPrivateKey(k, chainCode, network);
+ } else {
+ // 33 bytes: public key data (0x02 + X or 0x03 + X)
+ Uint8List X = buffer.sublist(45, 78);
+ hd = BIP32.fromPublicKey(X, chainCode, network);
+ }
+ hd.depth = depth;
+ hd.index = index;
+ hd.parentFingerprint = parentFingerprint;
+ return hd;
+ }
+
+ factory BIP32.fromPublicKey(
+ Uint8List publicKey,
+ Uint8List chainCode, [
+ NetworkType? nw,
+ ]) {
+ NetworkType network = nw ?? BITCOIN;
+ if (!ecc.isPoint(publicKey)) {
+ throw ArgumentError('Point is not on the curve');
+ }
+ return BIP32(null, publicKey, chainCode, network);
+ }
+
+ factory BIP32.fromPrivateKey(
+ Uint8List privateKey,
+ Uint8List chainCode, [
+ NetworkType? nw,
+ ]) {
+ NetworkType network = nw ?? BITCOIN;
+ if (privateKey.length != 32) {
+ throw ArgumentError(
+ 'Expected property privateKey of type Buffer(Length: 32)');
+ }
+ if (!ecc.isPrivate(privateKey)) {
+ throw ArgumentError('Private key not in range [1, n]');
+ }
+ return BIP32(privateKey, null, chainCode, network);
+ }
+
+ factory BIP32.fromSeed(Uint8List seed, [NetworkType? nw]) {
+ if (seed.length < 16) {
+ throw ArgumentError('Seed should be at least 128 bits');
+ }
+ if (seed.length > 64) {
+ throw ArgumentError('Seed should be at most 512 bits');
+ }
+ NetworkType network = nw ?? BITCOIN;
+ final I = hmacSHA512(utf8.encode('Bitcoin seed'), seed);
+ final IL = I.sublist(0, 32);
+ final IR = I.sublist(32);
+ return BIP32.fromPrivateKey(IL, IR, network);
+ }
+}
diff --git a/example/wallet/lib/dependencies/bip32/utils/crypto.dart b/example/wallet/lib/dependencies/bip32/utils/crypto.dart
new file mode 100644
index 00000000..3df8a41d
--- /dev/null
+++ b/example/wallet/lib/dependencies/bip32/utils/crypto.dart
@@ -0,0 +1,21 @@
+// ignore_for_file: depend_on_referenced_packages, non_constant_identifier_names
+
+import 'dart:typed_data';
+import 'package:pointycastle/digests/sha512.dart';
+import 'package:pointycastle/api.dart';
+import 'package:pointycastle/macs/hmac.dart';
+import 'package:pointycastle/digests/ripemd160.dart';
+import 'package:pointycastle/digests/sha256.dart';
+
+final ONE1 = Uint8List.fromList([1]);
+final ZERO1 = Uint8List.fromList([0]);
+
+Uint8List hash160(Uint8List buffer) {
+ Uint8List tmp = SHA256Digest().process(buffer);
+ return RIPEMD160Digest().process(tmp);
+}
+
+Uint8List hmacSHA512(Uint8List key, Uint8List data) {
+ final tmp = HMac(SHA512Digest(), 128)..init(KeyParameter(key));
+ return tmp.process(data);
+}
diff --git a/example/wallet/lib/dependencies/bip32/utils/ecurve.dart b/example/wallet/lib/dependencies/bip32/utils/ecurve.dart
new file mode 100644
index 00000000..39b5cbad
--- /dev/null
+++ b/example/wallet/lib/dependencies/bip32/utils/ecurve.dart
@@ -0,0 +1,263 @@
+// ignore_for_file: constant_identifier_names, non_constant_identifier_names, depend_on_referenced_packages, implementation_imports
+
+import 'dart:typed_data';
+import 'package:convert/convert.dart';
+import 'package:pointycastle/ecc/curves/secp256k1.dart';
+import 'package:pointycastle/api.dart';
+import 'package:pointycastle/ecc/api.dart';
+import 'package:pointycastle/signers/ecdsa_signer.dart';
+import 'package:pointycastle/macs/hmac.dart';
+import 'package:pointycastle/digests/sha256.dart';
+import 'package:pointycastle/src/utils.dart';
+
+final ZERO32 = Uint8List.fromList(List.generate(32, (index) => 0));
+final EC_GROUP_ORDER = hex
+ .decode('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141');
+final EC_P = hex
+ .decode('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f');
+final secp256k1 = ECCurve_secp256k1();
+final n = secp256k1.n;
+final G = secp256k1.G;
+BigInt nDiv2 = n >> 1;
+const THROW_BAD_PRIVATE = 'Expected Private';
+const THROW_BAD_POINT = 'Expected Point';
+const THROW_BAD_TWEAK = 'Expected Tweak';
+const THROW_BAD_HASH = 'Expected Hash';
+const THROW_BAD_SIGNATURE = 'Expected Signature';
+
+bool isPrivate(Uint8List x) {
+ if (!isScalar(x)) return false;
+ return _compare(x, ZERO32) > 0 && // > 0
+ _compare(x, EC_GROUP_ORDER as Uint8List) < 0; // < G
+}
+
+bool isPoint(Uint8List p) {
+ if (p.length < 33) {
+ return false;
+ }
+ var t = p[0];
+ var x = p.sublist(1, 33);
+
+ if (_compare(x, ZERO32) == 0) {
+ return false;
+ }
+ if (_compare(x, EC_P as Uint8List) == 1) {
+ return false;
+ }
+ try {
+ decodeFrom(p);
+ } catch (err) {
+ return false;
+ }
+ if ((t == 0x02 || t == 0x03) && p.length == 33) {
+ return true;
+ }
+ var y = p.sublist(33);
+ if (_compare(y, ZERO32) == 0) {
+ return false;
+ }
+ if (_compare(y, EC_P as Uint8List) == 1) {
+ return false;
+ }
+ if (t == 0x04 && p.length == 65) {
+ return true;
+ }
+ return false;
+}
+
+bool isScalar(Uint8List x) {
+ return x.length == 32;
+}
+
+bool isOrderScalar(x) {
+ if (!isScalar(x)) return false;
+ return _compare(x, EC_GROUP_ORDER as Uint8List) < 0; // < G
+}
+
+bool isSignature(Uint8List value) {
+ Uint8List r = value.sublist(0, 32);
+ Uint8List s = value.sublist(32, 64);
+
+ return value.length == 64 &&
+ _compare(r, EC_GROUP_ORDER as Uint8List) < 0 &&
+ _compare(s, EC_GROUP_ORDER as Uint8List) < 0;
+}
+
+bool _isPointCompressed(Uint8List p) {
+ return p[0] != 0x04;
+}
+
+bool assumeCompression(bool? value, Uint8List? pubkey) {
+ if (value == null && pubkey != null) return _isPointCompressed(pubkey);
+ if (value == null) return true;
+ return value;
+}
+
+Uint8List? pointFromScalar(Uint8List d, bool compressed) {
+ if (!isPrivate(d)) {
+ throw ArgumentError(THROW_BAD_PRIVATE);
+ }
+ BigInt dd = fromBuffer(d);
+ ECPoint pp = (G * dd) as ECPoint;
+ if (pp.isInfinity) return null;
+ return getEncoded(pp, compressed);
+}
+
+Uint8List? pointAddScalar(Uint8List p, Uint8List tweak, bool comprsd) {
+ if (!isPoint(p)) throw ArgumentError(THROW_BAD_POINT);
+ if (!isOrderScalar(tweak)) throw ArgumentError(THROW_BAD_TWEAK);
+ bool compressed = assumeCompression(comprsd, p);
+ ECPoint? pp = decodeFrom(p);
+ if (_compare(tweak, ZERO32) == 0) return getEncoded(pp, compressed);
+ BigInt tt = fromBuffer(tweak);
+ ECPoint qq = (G * tt) as ECPoint;
+ ECPoint uu = (pp! + qq) as ECPoint;
+ if (uu.isInfinity) return null;
+ return getEncoded(uu, compressed);
+}
+
+Uint8List? privateAdd(Uint8List d, Uint8List tweak) {
+ if (!isPrivate(d)) throw ArgumentError(THROW_BAD_PRIVATE);
+ if (!isOrderScalar(tweak)) throw ArgumentError(THROW_BAD_TWEAK);
+ BigInt dd = fromBuffer(d);
+ BigInt tt = fromBuffer(tweak);
+ Uint8List dt = toBuffer((dd + tt) % n);
+
+ if (dt.length < 32) {
+ Uint8List padLeadingZero = Uint8List(32 - dt.length);
+ dt = Uint8List.fromList(padLeadingZero + dt);
+ }
+
+ if (!isPrivate(dt)) return null;
+ return dt;
+}
+
+Uint8List sign(Uint8List hash, Uint8List x) {
+ if (!isScalar(hash)) throw ArgumentError(THROW_BAD_HASH);
+ if (!isPrivate(x)) throw ArgumentError(THROW_BAD_PRIVATE);
+ ECSignature sig = deterministicGenerateK(hash, x);
+ Uint8List buffer = Uint8List(64);
+ buffer.setRange(0, 32, _encodeBigInt(sig.r));
+ BigInt s;
+ if (sig.s.compareTo(nDiv2) > 0) {
+ s = n - sig.s;
+ } else {
+ s = sig.s;
+ }
+ buffer.setRange(32, 64, _encodeBigInt(s));
+ return buffer;
+}
+
+bool verify(Uint8List hash, Uint8List q, Uint8List signature) {
+ if (!isScalar(hash)) throw ArgumentError(THROW_BAD_HASH);
+ if (!isPoint(q)) throw ArgumentError(THROW_BAD_POINT);
+ // 1.4.1 Enforce r and s are both integers in the interval [1, n − 1] (1, isSignature enforces '< n - 1')
+ if (!isSignature(signature)) throw ArgumentError(THROW_BAD_SIGNATURE);
+
+ ECPoint? Q = decodeFrom(q);
+ BigInt r = fromBuffer(signature.sublist(0, 32));
+ BigInt s = fromBuffer(signature.sublist(32, 64));
+
+ final signer = ECDSASigner(null, HMac(SHA256Digest(), 64));
+ signer.init(false, PublicKeyParameter(ECPublicKey(Q, secp256k1)));
+ return signer.verifySignature(hash, ECSignature(r, s));
+ /* STEP BY STEP
+ // 1.4.1 Enforce r and s are both integers in the interval [1, n − 1] (2, enforces '> 0')
+ if (r.compareTo(n) >= 0) return false;
+ if (s.compareTo(n) >= 0) return false;
+
+ // 1.4.2 H = Hash(M), already done by the user
+ // 1.4.3 e = H
+ BigInt e = fromBuffer(hash);
+
+ BigInt sInv = s.modInverse(n);
+ BigInt u1 = (e * sInv) % n;
+ BigInt u2 = (r * sInv) % n;
+
+ // 1.4.5 Compute R = (xR, yR)
+ // R = u1G + u2Q
+ ECPoint R = G * u1 + Q * u2;
+
+ // 1.4.5 (cont.) Enforce R is not at infinity
+ if (R.isInfinity) return false;
+
+ // 1.4.6 Convert the field element R.x to an integer
+ BigInt xR = R.x.toBigInteger();
+
+ // 1.4.7 Set v = xR mod n
+ BigInt v = xR % n;
+
+ // 1.4.8 If v = r, output "valid", and if v != r, output "invalid"
+ return v.compareTo(r) == 0;
+ */
+}
+
+/// Decode a BigInt from bytes in big-endian encoding.
+BigInt _decodeBigInt(List bytes) {
+ BigInt result = BigInt.from(0);
+ for (int i = 0; i < bytes.length; i++) {
+ result += BigInt.from(bytes[bytes.length - i - 1]) << (8 * i);
+ }
+ return result;
+}
+
+var _byteMask = BigInt.from(0xff);
+
+/// Encode a BigInt into bytes using big-endian encoding.
+Uint8List _encodeBigInt(BigInt number) {
+ int needsPaddingByte;
+ int rawSize;
+
+ if (number > BigInt.zero) {
+ rawSize = (number.bitLength + 7) >> 3;
+ needsPaddingByte =
+ ((number >> (rawSize - 1) * 8) & negativeFlag) == negativeFlag ? 1 : 0;
+
+ if (rawSize < 32) {
+ needsPaddingByte = 1;
+ }
+ } else {
+ needsPaddingByte = 0;
+ rawSize = (number.bitLength + 8) >> 3;
+ }
+
+ final size = rawSize < 32 ? rawSize + needsPaddingByte : rawSize;
+ var result = Uint8List(size);
+ for (int i = 0; i < size; i++) {
+ result[size - i - 1] = (number & _byteMask).toInt();
+ number = number >> 8;
+ }
+ return result;
+}
+
+BigInt fromBuffer(Uint8List d) {
+ return _decodeBigInt(d);
+}
+
+Uint8List toBuffer(BigInt d) {
+ return _encodeBigInt(d);
+}
+
+ECPoint? decodeFrom(Uint8List P) {
+ return secp256k1.curve.decodePoint(P);
+}
+
+Uint8List getEncoded(ECPoint? P, compressed) {
+ return P!.getEncoded(compressed);
+}
+
+ECSignature deterministicGenerateK(Uint8List hash, Uint8List x) {
+ final signer = ECDSASigner(null, HMac(SHA256Digest(), 64));
+ var pkp = PrivateKeyParameter(ECPrivateKey(_decodeBigInt(x), secp256k1));
+ signer.init(true, pkp);
+// signer.init(false, new PublicKeyParameter(new ECPublicKey(secp256k1.curve.decodePoint(x), secp256k1)));
+ return signer.generateSignature(hash) as ECSignature;
+}
+
+int _compare(Uint8List a, Uint8List b) {
+ BigInt aa = fromBuffer(a);
+ BigInt bb = fromBuffer(b);
+ if (aa == bb) return 0;
+ if (aa > bb) return 1;
+ return -1;
+}
diff --git a/example/wallet/lib/dependencies/bip32/utils/wif.dart b/example/wallet/lib/dependencies/bip32/utils/wif.dart
new file mode 100644
index 00000000..cba7cf50
--- /dev/null
+++ b/example/wallet/lib/dependencies/bip32/utils/wif.dart
@@ -0,0 +1,56 @@
+// ignore_for_file: depend_on_referenced_packages
+
+import 'dart:typed_data';
+import 'package:bs58/bs58.dart';
+
+class WIF {
+ int version;
+ Uint8List privateKey;
+ bool compressed;
+ WIF(
+ {required this.version,
+ required this.privateKey,
+ required this.compressed});
+}
+
+WIF decodeRaw(Uint8List buffer, [int? version]) {
+ if (version != null && buffer[0] != version) {
+ throw ArgumentError('Invalid network version');
+ }
+ if (buffer.length == 33) {
+ return WIF(
+ version: buffer[0],
+ privateKey: buffer.sublist(1, 33),
+ compressed: false);
+ }
+ if (buffer.length != 34) {
+ throw ArgumentError('Invalid WIF length');
+ }
+ if (buffer[33] != 0x01) {
+ throw ArgumentError('Invalid compression flag');
+ }
+ return WIF(
+ version: buffer[0], privateKey: buffer.sublist(1, 33), compressed: true);
+}
+
+Uint8List encodeRaw(int version, Uint8List privateKey, bool compressed) {
+ if (privateKey.length != 32) {
+ throw ArgumentError('Invalid privateKey length');
+ }
+ Uint8List result = Uint8List(compressed ? 34 : 33);
+ ByteData bytes = result.buffer.asByteData();
+ bytes.setUint8(0, version);
+ result.setRange(1, 33, privateKey);
+ if (compressed) {
+ result[33] = 0x01;
+ }
+ return result;
+}
+
+WIF decode(String string, [int? version]) {
+ return decodeRaw(base58.decode(string), version);
+}
+
+String encode(WIF wif) {
+ return base58.encode(encodeRaw(wif.version, wif.privateKey, wif.compressed));
+}
diff --git a/example/wallet/lib/dependencies/bip39/bip39_base.dart b/example/wallet/lib/dependencies/bip39/bip39_base.dart
new file mode 100644
index 00000000..f446a987
--- /dev/null
+++ b/example/wallet/lib/dependencies/bip39/bip39_base.dart
@@ -0,0 +1,153 @@
+// ignore_for_file: depend_on_referenced_packages, constant_identifier_names, non_constant_identifier_names
+
+import 'dart:math';
+import 'dart:typed_data';
+
+// import 'package:crypto/crypto.dart' show sha256;
+// import 'package:hex/hex.dart';
+
+import 'package:convert/convert.dart';
+import 'package:pointycastle/digests/sha256.dart';
+
+import 'utils/pbkdf2.dart';
+import 'wordlists/english.dart';
+
+const int _SIZE_BYTE = 255;
+const _INVALID_MNEMONIC = 'Invalid mnemonic';
+const _INVALID_ENTROPY = 'Invalid entropy';
+const _INVALID_CHECKSUM = 'Invalid mnemonic checksum';
+
+typedef RandomBytes = Uint8List Function(int size);
+
+int _binaryToByte(String binary) {
+ return int.parse(binary, radix: 2);
+}
+
+String _bytesToBinary(Uint8List bytes) {
+ return bytes.map((byte) => byte.toRadixString(2).padLeft(8, '0')).join('');
+}
+
+//Uint8List _createUint8ListFromString( String s ) {
+// var ret = new Uint8List(s.length);
+// for( var i=0 ; i 32) {
+ throw ArgumentError(_INVALID_ENTROPY);
+ }
+ if (entropy.length % 4 != 0) {
+ throw ArgumentError(_INVALID_ENTROPY);
+ }
+ final entropyBits = _bytesToBinary(entropy);
+ final checksumBits = _deriveChecksumBits(entropy);
+ final bits = entropyBits + checksumBits;
+ final regex = RegExp(r'.{1,11}', caseSensitive: false, multiLine: false);
+ final chunks = regex
+ .allMatches(bits)
+ .map((match) => match.group(0)!)
+ .toList(growable: false);
+ List wordlist = WORDLIST;
+ String words =
+ chunks.map((binary) => wordlist[_binaryToByte(binary)]).join(' ');
+ return words;
+}
+
+Uint8List mnemonicToSeed(String mnemonic, {String passphrase = ''}) {
+ final pbkdf2 = PBKDF2();
+ return pbkdf2.process(mnemonic, passphrase: passphrase);
+}
+
+String mnemonicToSeedHex(String mnemonic, {String passphrase = ''}) {
+ return mnemonicToSeed(mnemonic, passphrase: passphrase).map((byte) {
+ return byte.toRadixString(16).padLeft(2, '0');
+ }).join('');
+}
+
+bool validateMnemonic(String mnemonic) {
+ try {
+ mnemonicToEntropy(mnemonic);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
+
+String mnemonicToEntropy(mnemonic) {
+ var words = mnemonic.split(' ');
+ if (words.length % 3 != 0) {
+ throw ArgumentError(_INVALID_MNEMONIC);
+ }
+ // convert word indices to 11 bit binary strings
+ final bits = words.map((word) {
+ final index = WORDLIST.indexOf(word);
+ if (index == -1) {
+ throw ArgumentError(_INVALID_MNEMONIC);
+ }
+ return index.toRadixString(2).padLeft(11, '0');
+ }).join('');
+ // split the binary string into ENT/CS
+ final dividerIndex = (bits.length / 33).floor() * 32;
+ final entropyBits = bits.substring(0, dividerIndex);
+ final checksumBits = bits.substring(dividerIndex);
+
+ // calculate the checksum and compare
+ final regex = RegExp(r'.{1,8}');
+ final entropyBytes = Uint8List.fromList(regex
+ .allMatches(entropyBits)
+ .map((match) => _binaryToByte(match.group(0)!))
+ .toList(growable: false));
+ if (entropyBytes.length < 16) {
+ throw StateError(_INVALID_ENTROPY);
+ }
+ if (entropyBytes.length > 32) {
+ throw StateError(_INVALID_ENTROPY);
+ }
+ if (entropyBytes.length % 4 != 0) {
+ throw StateError(_INVALID_ENTROPY);
+ }
+ final newChecksum = _deriveChecksumBits(entropyBytes);
+ if (newChecksum != checksumBits) {
+ throw StateError(_INVALID_CHECKSUM);
+ }
+ return entropyBytes.map((byte) {
+ return byte.toRadixString(16).padLeft(2, '0');
+ }).join('');
+}
+// List> _loadWordList() {
+// final res = new Resource('package:bip39/src/wordlists/english.json').readAsString();
+// List words = (json.decode(res) as List).map((e) => e.toString()).toList();
+// return words;
+// }
diff --git a/example/wallet/lib/dependencies/bip39/utils/pbkdf2.dart b/example/wallet/lib/dependencies/bip39/utils/pbkdf2.dart
new file mode 100644
index 00000000..474c0fc1
--- /dev/null
+++ b/example/wallet/lib/dependencies/bip39/utils/pbkdf2.dart
@@ -0,0 +1,31 @@
+// ignore_for_file: depend_on_referenced_packages
+
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:pointycastle/digests/sha512.dart';
+import 'package:pointycastle/key_derivators/api.dart';
+import 'package:pointycastle/key_derivators/pbkdf2.dart';
+import 'package:pointycastle/macs/hmac.dart';
+
+class PBKDF2 {
+ final int blockLength;
+ final int iterationCount;
+ final int desiredKeyLength;
+ final String saltPrefix = 'mnemonic';
+
+ final PBKDF2KeyDerivator _derivator;
+
+ PBKDF2({
+ this.blockLength = 128,
+ this.iterationCount = 2048,
+ this.desiredKeyLength = 64,
+ }) : _derivator = PBKDF2KeyDerivator(HMac(SHA512Digest(), blockLength));
+
+ Uint8List process(String mnemonic, {passphrase = ''}) {
+ final salt = Uint8List.fromList(utf8.encode(saltPrefix + passphrase));
+ _derivator.reset();
+ _derivator.init(Pbkdf2Parameters(salt, iterationCount, desiredKeyLength));
+ return _derivator.process(Uint8List.fromList(mnemonic.codeUnits));
+ }
+}
diff --git a/example/wallet/lib/dependencies/bip39/wordlists/english.dart b/example/wallet/lib/dependencies/bip39/wordlists/english.dart
new file mode 100644
index 00000000..8be30b62
--- /dev/null
+++ b/example/wallet/lib/dependencies/bip39/wordlists/english.dart
@@ -0,0 +1,2052 @@
+// ignore_for_file: constant_identifier_names
+
+const WORDLIST = [
+ 'abandon',
+ 'ability',
+ 'able',
+ 'about',
+ 'above',
+ 'absent',
+ 'absorb',
+ 'abstract',
+ 'absurd',
+ 'abuse',
+ 'access',
+ 'accident',
+ 'account',
+ 'accuse',
+ 'achieve',
+ 'acid',
+ 'acoustic',
+ 'acquire',
+ 'across',
+ 'act',
+ 'action',
+ 'actor',
+ 'actress',
+ 'actual',
+ 'adapt',
+ 'add',
+ 'addict',
+ 'address',
+ 'adjust',
+ 'admit',
+ 'adult',
+ 'advance',
+ 'advice',
+ 'aerobic',
+ 'affair',
+ 'afford',
+ 'afraid',
+ 'again',
+ 'age',
+ 'agent',
+ 'agree',
+ 'ahead',
+ 'aim',
+ 'air',
+ 'airport',
+ 'aisle',
+ 'alarm',
+ 'album',
+ 'alcohol',
+ 'alert',
+ 'alien',
+ 'all',
+ 'alley',
+ 'allow',
+ 'almost',
+ 'alone',
+ 'alpha',
+ 'already',
+ 'also',
+ 'alter',
+ 'always',
+ 'amateur',
+ 'amazing',
+ 'among',
+ 'amount',
+ 'amused',
+ 'analyst',
+ 'anchor',
+ 'ancient',
+ 'anger',
+ 'angle',
+ 'angry',
+ 'animal',
+ 'ankle',
+ 'announce',
+ 'annual',
+ 'another',
+ 'answer',
+ 'antenna',
+ 'antique',
+ 'anxiety',
+ 'any',
+ 'apart',
+ 'apology',
+ 'appear',
+ 'apple',
+ 'approve',
+ 'april',
+ 'arch',
+ 'arctic',
+ 'area',
+ 'arena',
+ 'argue',
+ 'arm',
+ 'armed',
+ 'armor',
+ 'army',
+ 'around',
+ 'arrange',
+ 'arrest',
+ 'arrive',
+ 'arrow',
+ 'art',
+ 'artefact',
+ 'artist',
+ 'artwork',
+ 'ask',
+ 'aspect',
+ 'assault',
+ 'asset',
+ 'assist',
+ 'assume',
+ 'asthma',
+ 'athlete',
+ 'atom',
+ 'attack',
+ 'attend',
+ 'attitude',
+ 'attract',
+ 'auction',
+ 'audit',
+ 'august',
+ 'aunt',
+ 'author',
+ 'auto',
+ 'autumn',
+ 'average',
+ 'avocado',
+ 'avoid',
+ 'awake',
+ 'aware',
+ 'away',
+ 'awesome',
+ 'awful',
+ 'awkward',
+ 'axis',
+ 'baby',
+ 'bachelor',
+ 'bacon',
+ 'badge',
+ 'bag',
+ 'balance',
+ 'balcony',
+ 'ball',
+ 'bamboo',
+ 'banana',
+ 'banner',
+ 'bar',
+ 'barely',
+ 'bargain',
+ 'barrel',
+ 'base',
+ 'basic',
+ 'basket',
+ 'battle',
+ 'beach',
+ 'bean',
+ 'beauty',
+ 'because',
+ 'become',
+ 'beef',
+ 'before',
+ 'begin',
+ 'behave',
+ 'behind',
+ 'believe',
+ 'below',
+ 'belt',
+ 'bench',
+ 'benefit',
+ 'best',
+ 'betray',
+ 'better',
+ 'between',
+ 'beyond',
+ 'bicycle',
+ 'bid',
+ 'bike',
+ 'bind',
+ 'biology',
+ 'bird',
+ 'birth',
+ 'bitter',
+ 'black',
+ 'blade',
+ 'blame',
+ 'blanket',
+ 'blast',
+ 'bleak',
+ 'bless',
+ 'blind',
+ 'blood',
+ 'blossom',
+ 'blouse',
+ 'blue',
+ 'blur',
+ 'blush',
+ 'board',
+ 'boat',
+ 'body',
+ 'boil',
+ 'bomb',
+ 'bone',
+ 'bonus',
+ 'book',
+ 'boost',
+ 'border',
+ 'boring',
+ 'borrow',
+ 'boss',
+ 'bottom',
+ 'bounce',
+ 'box',
+ 'boy',
+ 'bracket',
+ 'brain',
+ 'brand',
+ 'brass',
+ 'brave',
+ 'bread',
+ 'breeze',
+ 'brick',
+ 'bridge',
+ 'brief',
+ 'bright',
+ 'bring',
+ 'brisk',
+ 'broccoli',
+ 'broken',
+ 'bronze',
+ 'broom',
+ 'brother',
+ 'brown',
+ 'brush',
+ 'bubble',
+ 'buddy',
+ 'budget',
+ 'buffalo',
+ 'build',
+ 'bulb',
+ 'bulk',
+ 'bullet',
+ 'bundle',
+ 'bunker',
+ 'burden',
+ 'burger',
+ 'burst',
+ 'bus',
+ 'business',
+ 'busy',
+ 'butter',
+ 'buyer',
+ 'buzz',
+ 'cabbage',
+ 'cabin',
+ 'cable',
+ 'cactus',
+ 'cage',
+ 'cake',
+ 'call',
+ 'calm',
+ 'camera',
+ 'camp',
+ 'can',
+ 'canal',
+ 'cancel',
+ 'candy',
+ 'cannon',
+ 'canoe',
+ 'canvas',
+ 'canyon',
+ 'capable',
+ 'capital',
+ 'captain',
+ 'car',
+ 'carbon',
+ 'card',
+ 'cargo',
+ 'carpet',
+ 'carry',
+ 'cart',
+ 'case',
+ 'cash',
+ 'casino',
+ 'castle',
+ 'casual',
+ 'cat',
+ 'catalog',
+ 'catch',
+ 'category',
+ 'cattle',
+ 'caught',
+ 'cause',
+ 'caution',
+ 'cave',
+ 'ceiling',
+ 'celery',
+ 'cement',
+ 'census',
+ 'century',
+ 'cereal',
+ 'certain',
+ 'chair',
+ 'chalk',
+ 'champion',
+ 'change',
+ 'chaos',
+ 'chapter',
+ 'charge',
+ 'chase',
+ 'chat',
+ 'cheap',
+ 'check',
+ 'cheese',
+ 'chef',
+ 'cherry',
+ 'chest',
+ 'chicken',
+ 'chief',
+ 'child',
+ 'chimney',
+ 'choice',
+ 'choose',
+ 'chronic',
+ 'chuckle',
+ 'chunk',
+ 'churn',
+ 'cigar',
+ 'cinnamon',
+ 'circle',
+ 'citizen',
+ 'city',
+ 'civil',
+ 'claim',
+ 'clap',
+ 'clarify',
+ 'claw',
+ 'clay',
+ 'clean',
+ 'clerk',
+ 'clever',
+ 'click',
+ 'client',
+ 'cliff',
+ 'climb',
+ 'clinic',
+ 'clip',
+ 'clock',
+ 'clog',
+ 'close',
+ 'cloth',
+ 'cloud',
+ 'clown',
+ 'club',
+ 'clump',
+ 'cluster',
+ 'clutch',
+ 'coach',
+ 'coast',
+ 'coconut',
+ 'code',
+ 'coffee',
+ 'coil',
+ 'coin',
+ 'collect',
+ 'color',
+ 'column',
+ 'combine',
+ 'come',
+ 'comfort',
+ 'comic',
+ 'common',
+ 'company',
+ 'concert',
+ 'conduct',
+ 'confirm',
+ 'congress',
+ 'connect',
+ 'consider',
+ 'control',
+ 'convince',
+ 'cook',
+ 'cool',
+ 'copper',
+ 'copy',
+ 'coral',
+ 'core',
+ 'corn',
+ 'correct',
+ 'cost',
+ 'cotton',
+ 'couch',
+ 'country',
+ 'couple',
+ 'course',
+ 'cousin',
+ 'cover',
+ 'coyote',
+ 'crack',
+ 'cradle',
+ 'craft',
+ 'cram',
+ 'crane',
+ 'crash',
+ 'crater',
+ 'crawl',
+ 'crazy',
+ 'cream',
+ 'credit',
+ 'creek',
+ 'crew',
+ 'cricket',
+ 'crime',
+ 'crisp',
+ 'critic',
+ 'crop',
+ 'cross',
+ 'crouch',
+ 'crowd',
+ 'crucial',
+ 'cruel',
+ 'cruise',
+ 'crumble',
+ 'crunch',
+ 'crush',
+ 'cry',
+ 'crystal',
+ 'cube',
+ 'culture',
+ 'cup',
+ 'cupboard',
+ 'curious',
+ 'current',
+ 'curtain',
+ 'curve',
+ 'cushion',
+ 'custom',
+ 'cute',
+ 'cycle',
+ 'dad',
+ 'damage',
+ 'damp',
+ 'dance',
+ 'danger',
+ 'daring',
+ 'dash',
+ 'daughter',
+ 'dawn',
+ 'day',
+ 'deal',
+ 'debate',
+ 'debris',
+ 'decade',
+ 'december',
+ 'decide',
+ 'decline',
+ 'decorate',
+ 'decrease',
+ 'deer',
+ 'defense',
+ 'define',
+ 'defy',
+ 'degree',
+ 'delay',
+ 'deliver',
+ 'demand',
+ 'demise',
+ 'denial',
+ 'dentist',
+ 'deny',
+ 'depart',
+ 'depend',
+ 'deposit',
+ 'depth',
+ 'deputy',
+ 'derive',
+ 'describe',
+ 'desert',
+ 'design',
+ 'desk',
+ 'despair',
+ 'destroy',
+ 'detail',
+ 'detect',
+ 'develop',
+ 'device',
+ 'devote',
+ 'diagram',
+ 'dial',
+ 'diamond',
+ 'diary',
+ 'dice',
+ 'diesel',
+ 'diet',
+ 'differ',
+ 'digital',
+ 'dignity',
+ 'dilemma',
+ 'dinner',
+ 'dinosaur',
+ 'direct',
+ 'dirt',
+ 'disagree',
+ 'discover',
+ 'disease',
+ 'dish',
+ 'dismiss',
+ 'disorder',
+ 'display',
+ 'distance',
+ 'divert',
+ 'divide',
+ 'divorce',
+ 'dizzy',
+ 'doctor',
+ 'document',
+ 'dog',
+ 'doll',
+ 'dolphin',
+ 'domain',
+ 'donate',
+ 'donkey',
+ 'donor',
+ 'door',
+ 'dose',
+ 'double',
+ 'dove',
+ 'draft',
+ 'dragon',
+ 'drama',
+ 'drastic',
+ 'draw',
+ 'dream',
+ 'dress',
+ 'drift',
+ 'drill',
+ 'drink',
+ 'drip',
+ 'drive',
+ 'drop',
+ 'drum',
+ 'dry',
+ 'duck',
+ 'dumb',
+ 'dune',
+ 'during',
+ 'dust',
+ 'dutch',
+ 'duty',
+ 'dwarf',
+ 'dynamic',
+ 'eager',
+ 'eagle',
+ 'early',
+ 'earn',
+ 'earth',
+ 'easily',
+ 'east',
+ 'easy',
+ 'echo',
+ 'ecology',
+ 'economy',
+ 'edge',
+ 'edit',
+ 'educate',
+ 'effort',
+ 'egg',
+ 'eight',
+ 'either',
+ 'elbow',
+ 'elder',
+ 'electric',
+ 'elegant',
+ 'element',
+ 'elephant',
+ 'elevator',
+ 'elite',
+ 'else',
+ 'embark',
+ 'embody',
+ 'embrace',
+ 'emerge',
+ 'emotion',
+ 'employ',
+ 'empower',
+ 'empty',
+ 'enable',
+ 'enact',
+ 'end',
+ 'endless',
+ 'endorse',
+ 'enemy',
+ 'energy',
+ 'enforce',
+ 'engage',
+ 'engine',
+ 'enhance',
+ 'enjoy',
+ 'enlist',
+ 'enough',
+ 'enrich',
+ 'enroll',
+ 'ensure',
+ 'enter',
+ 'entire',
+ 'entry',
+ 'envelope',
+ 'episode',
+ 'equal',
+ 'equip',
+ 'era',
+ 'erase',
+ 'erode',
+ 'erosion',
+ 'error',
+ 'erupt',
+ 'escape',
+ 'essay',
+ 'essence',
+ 'estate',
+ 'eternal',
+ 'ethics',
+ 'evidence',
+ 'evil',
+ 'evoke',
+ 'evolve',
+ 'exact',
+ 'example',
+ 'excess',
+ 'exchange',
+ 'excite',
+ 'exclude',
+ 'excuse',
+ 'execute',
+ 'exercise',
+ 'exhaust',
+ 'exhibit',
+ 'exile',
+ 'exist',
+ 'exit',
+ 'exotic',
+ 'expand',
+ 'expect',
+ 'expire',
+ 'explain',
+ 'expose',
+ 'express',
+ 'extend',
+ 'extra',
+ 'eye',
+ 'eyebrow',
+ 'fabric',
+ 'face',
+ 'faculty',
+ 'fade',
+ 'faint',
+ 'faith',
+ 'fall',
+ 'false',
+ 'fame',
+ 'family',
+ 'famous',
+ 'fan',
+ 'fancy',
+ 'fantasy',
+ 'farm',
+ 'fashion',
+ 'fat',
+ 'fatal',
+ 'father',
+ 'fatigue',
+ 'fault',
+ 'favorite',
+ 'feature',
+ 'february',
+ 'federal',
+ 'fee',
+ 'feed',
+ 'feel',
+ 'female',
+ 'fence',
+ 'festival',
+ 'fetch',
+ 'fever',
+ 'few',
+ 'fiber',
+ 'fiction',
+ 'field',
+ 'figure',
+ 'file',
+ 'film',
+ 'filter',
+ 'final',
+ 'find',
+ 'fine',
+ 'finger',
+ 'finish',
+ 'fire',
+ 'firm',
+ 'first',
+ 'fiscal',
+ 'fish',
+ 'fit',
+ 'fitness',
+ 'fix',
+ 'flag',
+ 'flame',
+ 'flash',
+ 'flat',
+ 'flavor',
+ 'flee',
+ 'flight',
+ 'flip',
+ 'float',
+ 'flock',
+ 'floor',
+ 'flower',
+ 'fluid',
+ 'flush',
+ 'fly',
+ 'foam',
+ 'focus',
+ 'fog',
+ 'foil',
+ 'fold',
+ 'follow',
+ 'food',
+ 'foot',
+ 'force',
+ 'forest',
+ 'forget',
+ 'fork',
+ 'fortune',
+ 'forum',
+ 'forward',
+ 'fossil',
+ 'foster',
+ 'found',
+ 'fox',
+ 'fragile',
+ 'frame',
+ 'frequent',
+ 'fresh',
+ 'friend',
+ 'fringe',
+ 'frog',
+ 'front',
+ 'frost',
+ 'frown',
+ 'frozen',
+ 'fruit',
+ 'fuel',
+ 'fun',
+ 'funny',
+ 'furnace',
+ 'fury',
+ 'future',
+ 'gadget',
+ 'gain',
+ 'galaxy',
+ 'gallery',
+ 'game',
+ 'gap',
+ 'garage',
+ 'garbage',
+ 'garden',
+ 'garlic',
+ 'garment',
+ 'gas',
+ 'gasp',
+ 'gate',
+ 'gather',
+ 'gauge',
+ 'gaze',
+ 'general',
+ 'genius',
+ 'genre',
+ 'gentle',
+ 'genuine',
+ 'gesture',
+ 'ghost',
+ 'giant',
+ 'gift',
+ 'giggle',
+ 'ginger',
+ 'giraffe',
+ 'girl',
+ 'give',
+ 'glad',
+ 'glance',
+ 'glare',
+ 'glass',
+ 'glide',
+ 'glimpse',
+ 'globe',
+ 'gloom',
+ 'glory',
+ 'glove',
+ 'glow',
+ 'glue',
+ 'goat',
+ 'goddess',
+ 'gold',
+ 'good',
+ 'goose',
+ 'gorilla',
+ 'gospel',
+ 'gossip',
+ 'govern',
+ 'gown',
+ 'grab',
+ 'grace',
+ 'grain',
+ 'grant',
+ 'grape',
+ 'grass',
+ 'gravity',
+ 'great',
+ 'green',
+ 'grid',
+ 'grief',
+ 'grit',
+ 'grocery',
+ 'group',
+ 'grow',
+ 'grunt',
+ 'guard',
+ 'guess',
+ 'guide',
+ 'guilt',
+ 'guitar',
+ 'gun',
+ 'gym',
+ 'habit',
+ 'hair',
+ 'half',
+ 'hammer',
+ 'hamster',
+ 'hand',
+ 'happy',
+ 'harbor',
+ 'hard',
+ 'harsh',
+ 'harvest',
+ 'hat',
+ 'have',
+ 'hawk',
+ 'hazard',
+ 'head',
+ 'health',
+ 'heart',
+ 'heavy',
+ 'hedgehog',
+ 'height',
+ 'hello',
+ 'helmet',
+ 'help',
+ 'hen',
+ 'hero',
+ 'hidden',
+ 'high',
+ 'hill',
+ 'hint',
+ 'hip',
+ 'hire',
+ 'history',
+ 'hobby',
+ 'hockey',
+ 'hold',
+ 'hole',
+ 'holiday',
+ 'hollow',
+ 'home',
+ 'honey',
+ 'hood',
+ 'hope',
+ 'horn',
+ 'horror',
+ 'horse',
+ 'hospital',
+ 'host',
+ 'hotel',
+ 'hour',
+ 'hover',
+ 'hub',
+ 'huge',
+ 'human',
+ 'humble',
+ 'humor',
+ 'hundred',
+ 'hungry',
+ 'hunt',
+ 'hurdle',
+ 'hurry',
+ 'hurt',
+ 'husband',
+ 'hybrid',
+ 'ice',
+ 'icon',
+ 'idea',
+ 'identify',
+ 'idle',
+ 'ignore',
+ 'ill',
+ 'illegal',
+ 'illness',
+ 'image',
+ 'imitate',
+ 'immense',
+ 'immune',
+ 'impact',
+ 'impose',
+ 'improve',
+ 'impulse',
+ 'inch',
+ 'include',
+ 'income',
+ 'increase',
+ 'index',
+ 'indicate',
+ 'indoor',
+ 'industry',
+ 'infant',
+ 'inflict',
+ 'inform',
+ 'inhale',
+ 'inherit',
+ 'initial',
+ 'inject',
+ 'injury',
+ 'inmate',
+ 'inner',
+ 'innocent',
+ 'input',
+ 'inquiry',
+ 'insane',
+ 'insect',
+ 'inside',
+ 'inspire',
+ 'install',
+ 'intact',
+ 'interest',
+ 'into',
+ 'invest',
+ 'invite',
+ 'involve',
+ 'iron',
+ 'island',
+ 'isolate',
+ 'issue',
+ 'item',
+ 'ivory',
+ 'jacket',
+ 'jaguar',
+ 'jar',
+ 'jazz',
+ 'jealous',
+ 'jeans',
+ 'jelly',
+ 'jewel',
+ 'job',
+ 'join',
+ 'joke',
+ 'journey',
+ 'joy',
+ 'judge',
+ 'juice',
+ 'jump',
+ 'jungle',
+ 'junior',
+ 'junk',
+ 'just',
+ 'kangaroo',
+ 'keen',
+ 'keep',
+ 'ketchup',
+ 'key',
+ 'kick',
+ 'kid',
+ 'kidney',
+ 'kind',
+ 'kingdom',
+ 'kiss',
+ 'kit',
+ 'kitchen',
+ 'kite',
+ 'kitten',
+ 'kiwi',
+ 'knee',
+ 'knife',
+ 'knock',
+ 'know',
+ 'lab',
+ 'label',
+ 'labor',
+ 'ladder',
+ 'lady',
+ 'lake',
+ 'lamp',
+ 'language',
+ 'laptop',
+ 'large',
+ 'later',
+ 'latin',
+ 'laugh',
+ 'laundry',
+ 'lava',
+ 'law',
+ 'lawn',
+ 'lawsuit',
+ 'layer',
+ 'lazy',
+ 'leader',
+ 'leaf',
+ 'learn',
+ 'leave',
+ 'lecture',
+ 'left',
+ 'leg',
+ 'legal',
+ 'legend',
+ 'leisure',
+ 'lemon',
+ 'lend',
+ 'length',
+ 'lens',
+ 'leopard',
+ 'lesson',
+ 'letter',
+ 'level',
+ 'liar',
+ 'liberty',
+ 'library',
+ 'license',
+ 'life',
+ 'lift',
+ 'light',
+ 'like',
+ 'limb',
+ 'limit',
+ 'link',
+ 'lion',
+ 'liquid',
+ 'list',
+ 'little',
+ 'live',
+ 'lizard',
+ 'load',
+ 'loan',
+ 'lobster',
+ 'local',
+ 'lock',
+ 'logic',
+ 'lonely',
+ 'long',
+ 'loop',
+ 'lottery',
+ 'loud',
+ 'lounge',
+ 'love',
+ 'loyal',
+ 'lucky',
+ 'luggage',
+ 'lumber',
+ 'lunar',
+ 'lunch',
+ 'luxury',
+ 'lyrics',
+ 'machine',
+ 'mad',
+ 'magic',
+ 'magnet',
+ 'maid',
+ 'mail',
+ 'main',
+ 'major',
+ 'make',
+ 'mammal',
+ 'man',
+ 'manage',
+ 'mandate',
+ 'mango',
+ 'mansion',
+ 'manual',
+ 'maple',
+ 'marble',
+ 'march',
+ 'margin',
+ 'marine',
+ 'market',
+ 'marriage',
+ 'mask',
+ 'mass',
+ 'master',
+ 'match',
+ 'material',
+ 'math',
+ 'matrix',
+ 'matter',
+ 'maximum',
+ 'maze',
+ 'meadow',
+ 'mean',
+ 'measure',
+ 'meat',
+ 'mechanic',
+ 'medal',
+ 'media',
+ 'melody',
+ 'melt',
+ 'member',
+ 'memory',
+ 'mention',
+ 'menu',
+ 'mercy',
+ 'merge',
+ 'merit',
+ 'merry',
+ 'mesh',
+ 'message',
+ 'metal',
+ 'method',
+ 'middle',
+ 'midnight',
+ 'milk',
+ 'million',
+ 'mimic',
+ 'mind',
+ 'minimum',
+ 'minor',
+ 'minute',
+ 'miracle',
+ 'mirror',
+ 'misery',
+ 'miss',
+ 'mistake',
+ 'mix',
+ 'mixed',
+ 'mixture',
+ 'mobile',
+ 'model',
+ 'modify',
+ 'mom',
+ 'moment',
+ 'monitor',
+ 'monkey',
+ 'monster',
+ 'month',
+ 'moon',
+ 'moral',
+ 'more',
+ 'morning',
+ 'mosquito',
+ 'mother',
+ 'motion',
+ 'motor',
+ 'mountain',
+ 'mouse',
+ 'move',
+ 'movie',
+ 'much',
+ 'muffin',
+ 'mule',
+ 'multiply',
+ 'muscle',
+ 'museum',
+ 'mushroom',
+ 'music',
+ 'must',
+ 'mutual',
+ 'myself',
+ 'mystery',
+ 'myth',
+ 'naive',
+ 'name',
+ 'napkin',
+ 'narrow',
+ 'nasty',
+ 'nation',
+ 'nature',
+ 'near',
+ 'neck',
+ 'need',
+ 'negative',
+ 'neglect',
+ 'neither',
+ 'nephew',
+ 'nerve',
+ 'nest',
+ 'net',
+ 'network',
+ 'neutral',
+ 'never',
+ 'news',
+ 'next',
+ 'nice',
+ 'night',
+ 'noble',
+ 'noise',
+ 'nominee',
+ 'noodle',
+ 'normal',
+ 'north',
+ 'nose',
+ 'notable',
+ 'note',
+ 'nothing',
+ 'notice',
+ 'novel',
+ 'now',
+ 'nuclear',
+ 'number',
+ 'nurse',
+ 'nut',
+ 'oak',
+ 'obey',
+ 'object',
+ 'oblige',
+ 'obscure',
+ 'observe',
+ 'obtain',
+ 'obvious',
+ 'occur',
+ 'ocean',
+ 'october',
+ 'odor',
+ 'off',
+ 'offer',
+ 'office',
+ 'often',
+ 'oil',
+ 'okay',
+ 'old',
+ 'olive',
+ 'olympic',
+ 'omit',
+ 'once',
+ 'one',
+ 'onion',
+ 'online',
+ 'only',
+ 'open',
+ 'opera',
+ 'opinion',
+ 'oppose',
+ 'option',
+ 'orange',
+ 'orbit',
+ 'orchard',
+ 'order',
+ 'ordinary',
+ 'organ',
+ 'orient',
+ 'original',
+ 'orphan',
+ 'ostrich',
+ 'other',
+ 'outdoor',
+ 'outer',
+ 'output',
+ 'outside',
+ 'oval',
+ 'oven',
+ 'over',
+ 'own',
+ 'owner',
+ 'oxygen',
+ 'oyster',
+ 'ozone',
+ 'pact',
+ 'paddle',
+ 'page',
+ 'pair',
+ 'palace',
+ 'palm',
+ 'panda',
+ 'panel',
+ 'panic',
+ 'panther',
+ 'paper',
+ 'parade',
+ 'parent',
+ 'park',
+ 'parrot',
+ 'party',
+ 'pass',
+ 'patch',
+ 'path',
+ 'patient',
+ 'patrol',
+ 'pattern',
+ 'pause',
+ 'pave',
+ 'payment',
+ 'peace',
+ 'peanut',
+ 'pear',
+ 'peasant',
+ 'pelican',
+ 'pen',
+ 'penalty',
+ 'pencil',
+ 'people',
+ 'pepper',
+ 'perfect',
+ 'permit',
+ 'person',
+ 'pet',
+ 'phone',
+ 'photo',
+ 'phrase',
+ 'physical',
+ 'piano',
+ 'picnic',
+ 'picture',
+ 'piece',
+ 'pig',
+ 'pigeon',
+ 'pill',
+ 'pilot',
+ 'pink',
+ 'pioneer',
+ 'pipe',
+ 'pistol',
+ 'pitch',
+ 'pizza',
+ 'place',
+ 'planet',
+ 'plastic',
+ 'plate',
+ 'play',
+ 'please',
+ 'pledge',
+ 'pluck',
+ 'plug',
+ 'plunge',
+ 'poem',
+ 'poet',
+ 'point',
+ 'polar',
+ 'pole',
+ 'police',
+ 'pond',
+ 'pony',
+ 'pool',
+ 'popular',
+ 'portion',
+ 'position',
+ 'possible',
+ 'post',
+ 'potato',
+ 'pottery',
+ 'poverty',
+ 'powder',
+ 'power',
+ 'practice',
+ 'praise',
+ 'predict',
+ 'prefer',
+ 'prepare',
+ 'present',
+ 'pretty',
+ 'prevent',
+ 'price',
+ 'pride',
+ 'primary',
+ 'print',
+ 'priority',
+ 'prison',
+ 'private',
+ 'prize',
+ 'problem',
+ 'process',
+ 'produce',
+ 'profit',
+ 'program',
+ 'project',
+ 'promote',
+ 'proof',
+ 'property',
+ 'prosper',
+ 'protect',
+ 'proud',
+ 'provide',
+ 'public',
+ 'pudding',
+ 'pull',
+ 'pulp',
+ 'pulse',
+ 'pumpkin',
+ 'punch',
+ 'pupil',
+ 'puppy',
+ 'purchase',
+ 'purity',
+ 'purpose',
+ 'purse',
+ 'push',
+ 'put',
+ 'puzzle',
+ 'pyramid',
+ 'quality',
+ 'quantum',
+ 'quarter',
+ 'question',
+ 'quick',
+ 'quit',
+ 'quiz',
+ 'quote',
+ 'rabbit',
+ 'raccoon',
+ 'race',
+ 'rack',
+ 'radar',
+ 'radio',
+ 'rail',
+ 'rain',
+ 'raise',
+ 'rally',
+ 'ramp',
+ 'ranch',
+ 'random',
+ 'range',
+ 'rapid',
+ 'rare',
+ 'rate',
+ 'rather',
+ 'raven',
+ 'raw',
+ 'razor',
+ 'ready',
+ 'real',
+ 'reason',
+ 'rebel',
+ 'rebuild',
+ 'recall',
+ 'receive',
+ 'recipe',
+ 'record',
+ 'recycle',
+ 'reduce',
+ 'reflect',
+ 'reform',
+ 'refuse',
+ 'region',
+ 'regret',
+ 'regular',
+ 'reject',
+ 'relax',
+ 'release',
+ 'relief',
+ 'rely',
+ 'remain',
+ 'remember',
+ 'remind',
+ 'remove',
+ 'render',
+ 'renew',
+ 'rent',
+ 'reopen',
+ 'repair',
+ 'repeat',
+ 'replace',
+ 'report',
+ 'require',
+ 'rescue',
+ 'resemble',
+ 'resist',
+ 'resource',
+ 'response',
+ 'result',
+ 'retire',
+ 'retreat',
+ 'return',
+ 'reunion',
+ 'reveal',
+ 'review',
+ 'reward',
+ 'rhythm',
+ 'rib',
+ 'ribbon',
+ 'rice',
+ 'rich',
+ 'ride',
+ 'ridge',
+ 'rifle',
+ 'right',
+ 'rigid',
+ 'ring',
+ 'riot',
+ 'ripple',
+ 'risk',
+ 'ritual',
+ 'rival',
+ 'river',
+ 'road',
+ 'roast',
+ 'robot',
+ 'robust',
+ 'rocket',
+ 'romance',
+ 'roof',
+ 'rookie',
+ 'room',
+ 'rose',
+ 'rotate',
+ 'rough',
+ 'round',
+ 'route',
+ 'royal',
+ 'rubber',
+ 'rude',
+ 'rug',
+ 'rule',
+ 'run',
+ 'runway',
+ 'rural',
+ 'sad',
+ 'saddle',
+ 'sadness',
+ 'safe',
+ 'sail',
+ 'salad',
+ 'salmon',
+ 'salon',
+ 'salt',
+ 'salute',
+ 'same',
+ 'sample',
+ 'sand',
+ 'satisfy',
+ 'satoshi',
+ 'sauce',
+ 'sausage',
+ 'save',
+ 'say',
+ 'scale',
+ 'scan',
+ 'scare',
+ 'scatter',
+ 'scene',
+ 'scheme',
+ 'school',
+ 'science',
+ 'scissors',
+ 'scorpion',
+ 'scout',
+ 'scrap',
+ 'screen',
+ 'script',
+ 'scrub',
+ 'sea',
+ 'search',
+ 'season',
+ 'seat',
+ 'second',
+ 'secret',
+ 'section',
+ 'security',
+ 'seed',
+ 'seek',
+ 'segment',
+ 'select',
+ 'sell',
+ 'seminar',
+ 'senior',
+ 'sense',
+ 'sentence',
+ 'series',
+ 'service',
+ 'session',
+ 'settle',
+ 'setup',
+ 'seven',
+ 'shadow',
+ 'shaft',
+ 'shallow',
+ 'share',
+ 'shed',
+ 'shell',
+ 'sheriff',
+ 'shield',
+ 'shift',
+ 'shine',
+ 'ship',
+ 'shiver',
+ 'shock',
+ 'shoe',
+ 'shoot',
+ 'shop',
+ 'short',
+ 'shoulder',
+ 'shove',
+ 'shrimp',
+ 'shrug',
+ 'shuffle',
+ 'shy',
+ 'sibling',
+ 'sick',
+ 'side',
+ 'siege',
+ 'sight',
+ 'sign',
+ 'silent',
+ 'silk',
+ 'silly',
+ 'silver',
+ 'similar',
+ 'simple',
+ 'since',
+ 'sing',
+ 'siren',
+ 'sister',
+ 'situate',
+ 'six',
+ 'size',
+ 'skate',
+ 'sketch',
+ 'ski',
+ 'skill',
+ 'skin',
+ 'skirt',
+ 'skull',
+ 'slab',
+ 'slam',
+ 'sleep',
+ 'slender',
+ 'slice',
+ 'slide',
+ 'slight',
+ 'slim',
+ 'slogan',
+ 'slot',
+ 'slow',
+ 'slush',
+ 'small',
+ 'smart',
+ 'smile',
+ 'smoke',
+ 'smooth',
+ 'snack',
+ 'snake',
+ 'snap',
+ 'sniff',
+ 'snow',
+ 'soap',
+ 'soccer',
+ 'social',
+ 'sock',
+ 'soda',
+ 'soft',
+ 'solar',
+ 'soldier',
+ 'solid',
+ 'solution',
+ 'solve',
+ 'someone',
+ 'song',
+ 'soon',
+ 'sorry',
+ 'sort',
+ 'soul',
+ 'sound',
+ 'soup',
+ 'source',
+ 'south',
+ 'space',
+ 'spare',
+ 'spatial',
+ 'spawn',
+ 'speak',
+ 'special',
+ 'speed',
+ 'spell',
+ 'spend',
+ 'sphere',
+ 'spice',
+ 'spider',
+ 'spike',
+ 'spin',
+ 'spirit',
+ 'split',
+ 'spoil',
+ 'sponsor',
+ 'spoon',
+ 'sport',
+ 'spot',
+ 'spray',
+ 'spread',
+ 'spring',
+ 'spy',
+ 'square',
+ 'squeeze',
+ 'squirrel',
+ 'stable',
+ 'stadium',
+ 'staff',
+ 'stage',
+ 'stairs',
+ 'stamp',
+ 'stand',
+ 'start',
+ 'state',
+ 'stay',
+ 'steak',
+ 'steel',
+ 'stem',
+ 'step',
+ 'stereo',
+ 'stick',
+ 'still',
+ 'sting',
+ 'stock',
+ 'stomach',
+ 'stone',
+ 'stool',
+ 'story',
+ 'stove',
+ 'strategy',
+ 'street',
+ 'strike',
+ 'strong',
+ 'struggle',
+ 'student',
+ 'stuff',
+ 'stumble',
+ 'style',
+ 'subject',
+ 'submit',
+ 'subway',
+ 'success',
+ 'such',
+ 'sudden',
+ 'suffer',
+ 'sugar',
+ 'suggest',
+ 'suit',
+ 'summer',
+ 'sun',
+ 'sunny',
+ 'sunset',
+ 'super',
+ 'supply',
+ 'supreme',
+ 'sure',
+ 'surface',
+ 'surge',
+ 'surprise',
+ 'surround',
+ 'survey',
+ 'suspect',
+ 'sustain',
+ 'swallow',
+ 'swamp',
+ 'swap',
+ 'swarm',
+ 'swear',
+ 'sweet',
+ 'swift',
+ 'swim',
+ 'swing',
+ 'switch',
+ 'sword',
+ 'symbol',
+ 'symptom',
+ 'syrup',
+ 'system',
+ 'table',
+ 'tackle',
+ 'tag',
+ 'tail',
+ 'talent',
+ 'talk',
+ 'tank',
+ 'tape',
+ 'target',
+ 'task',
+ 'taste',
+ 'tattoo',
+ 'taxi',
+ 'teach',
+ 'team',
+ 'tell',
+ 'ten',
+ 'tenant',
+ 'tennis',
+ 'tent',
+ 'term',
+ 'test',
+ 'text',
+ 'thank',
+ 'that',
+ 'theme',
+ 'then',
+ 'theory',
+ 'there',
+ 'they',
+ 'thing',
+ 'this',
+ 'thought',
+ 'three',
+ 'thrive',
+ 'throw',
+ 'thumb',
+ 'thunder',
+ 'ticket',
+ 'tide',
+ 'tiger',
+ 'tilt',
+ 'timber',
+ 'time',
+ 'tiny',
+ 'tip',
+ 'tired',
+ 'tissue',
+ 'title',
+ 'toast',
+ 'tobacco',
+ 'today',
+ 'toddler',
+ 'toe',
+ 'together',
+ 'toilet',
+ 'token',
+ 'tomato',
+ 'tomorrow',
+ 'tone',
+ 'tongue',
+ 'tonight',
+ 'tool',
+ 'tooth',
+ 'top',
+ 'topic',
+ 'topple',
+ 'torch',
+ 'tornado',
+ 'tortoise',
+ 'toss',
+ 'total',
+ 'tourist',
+ 'toward',
+ 'tower',
+ 'town',
+ 'toy',
+ 'track',
+ 'trade',
+ 'traffic',
+ 'tragic',
+ 'train',
+ 'transfer',
+ 'trap',
+ 'trash',
+ 'travel',
+ 'tray',
+ 'treat',
+ 'tree',
+ 'trend',
+ 'trial',
+ 'tribe',
+ 'trick',
+ 'trigger',
+ 'trim',
+ 'trip',
+ 'trophy',
+ 'trouble',
+ 'truck',
+ 'true',
+ 'truly',
+ 'trumpet',
+ 'trust',
+ 'truth',
+ 'try',
+ 'tube',
+ 'tuition',
+ 'tumble',
+ 'tuna',
+ 'tunnel',
+ 'turkey',
+ 'turn',
+ 'turtle',
+ 'twelve',
+ 'twenty',
+ 'twice',
+ 'twin',
+ 'twist',
+ 'two',
+ 'type',
+ 'typical',
+ 'ugly',
+ 'umbrella',
+ 'unable',
+ 'unaware',
+ 'uncle',
+ 'uncover',
+ 'under',
+ 'undo',
+ 'unfair',
+ 'unfold',
+ 'unhappy',
+ 'uniform',
+ 'unique',
+ 'unit',
+ 'universe',
+ 'unknown',
+ 'unlock',
+ 'until',
+ 'unusual',
+ 'unveil',
+ 'update',
+ 'upgrade',
+ 'uphold',
+ 'upon',
+ 'upper',
+ 'upset',
+ 'urban',
+ 'urge',
+ 'usage',
+ 'use',
+ 'used',
+ 'useful',
+ 'useless',
+ 'usual',
+ 'utility',
+ 'vacant',
+ 'vacuum',
+ 'vague',
+ 'valid',
+ 'valley',
+ 'valve',
+ 'van',
+ 'vanish',
+ 'vapor',
+ 'various',
+ 'vast',
+ 'vault',
+ 'vehicle',
+ 'velvet',
+ 'vendor',
+ 'venture',
+ 'venue',
+ 'verb',
+ 'verify',
+ 'version',
+ 'very',
+ 'vessel',
+ 'veteran',
+ 'viable',
+ 'vibrant',
+ 'vicious',
+ 'victory',
+ 'video',
+ 'view',
+ 'village',
+ 'vintage',
+ 'violin',
+ 'virtual',
+ 'virus',
+ 'visa',
+ 'visit',
+ 'visual',
+ 'vital',
+ 'vivid',
+ 'vocal',
+ 'voice',
+ 'void',
+ 'volcano',
+ 'volume',
+ 'vote',
+ 'voyage',
+ 'wage',
+ 'wagon',
+ 'wait',
+ 'walk',
+ 'wall',
+ 'walnut',
+ 'want',
+ 'warfare',
+ 'warm',
+ 'warrior',
+ 'wash',
+ 'wasp',
+ 'waste',
+ 'water',
+ 'wave',
+ 'way',
+ 'wealth',
+ 'weapon',
+ 'wear',
+ 'weasel',
+ 'weather',
+ 'web',
+ 'wedding',
+ 'weekend',
+ 'weird',
+ 'welcome',
+ 'west',
+ 'wet',
+ 'whale',
+ 'what',
+ 'wheat',
+ 'wheel',
+ 'when',
+ 'where',
+ 'whip',
+ 'whisper',
+ 'wide',
+ 'width',
+ 'wife',
+ 'wild',
+ 'will',
+ 'win',
+ 'window',
+ 'wine',
+ 'wing',
+ 'wink',
+ 'winner',
+ 'winter',
+ 'wire',
+ 'wisdom',
+ 'wise',
+ 'wish',
+ 'witness',
+ 'wolf',
+ 'woman',
+ 'wonder',
+ 'wood',
+ 'wool',
+ 'word',
+ 'work',
+ 'world',
+ 'worry',
+ 'worth',
+ 'wrap',
+ 'wreck',
+ 'wrestle',
+ 'wrist',
+ 'write',
+ 'wrong',
+ 'yard',
+ 'year',
+ 'yellow',
+ 'you',
+ 'young',
+ 'youth',
+ 'zebra',
+ 'zero',
+ 'zone',
+ 'zoo'
+];
diff --git a/example/wallet/lib/dependencies/chains/evm_service.dart b/example/wallet/lib/dependencies/chains/evm_service.dart
index 3889b674..cfbbcca5 100644
--- a/example/wallet/lib/dependencies/chains/evm_service.dart
+++ b/example/wallet/lib/dependencies/chains/evm_service.dart
@@ -10,7 +10,7 @@ import 'package:walletconnect_flutter_v2_wallet/dependencies/bottom_sheet/i_bott
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';
-import 'package:walletconnect_flutter_v2_wallet/models/eth/ethereum_transaction.dart';
+import 'package:walletconnect_flutter_v2_wallet/models/chain_data.dart';
import 'package:walletconnect_flutter_v2_wallet/utils/eth_utils.dart';
import 'package:walletconnect_flutter_v2_wallet/widgets/wc_connection_widget/wc_connection_model.dart';
import 'package:walletconnect_flutter_v2_wallet/widgets/wc_connection_widget/wc_connection_widget.dart';
@@ -21,7 +21,7 @@ enum EVMChainsSupported {
polygon,
arbitrum,
sepolia,
- bsc,
+ // bsc,
mumbai;
String chain() {
@@ -40,9 +40,9 @@ enum EVMChainsSupported {
case EVMChainsSupported.sepolia:
id = '11155111';
break;
- case EVMChainsSupported.bsc:
- id = '56';
- break;
+ // case EVMChainsSupported.bsc:
+ // id = '56';
+ // break;
case EVMChainsSupported.mumbai:
id = '80001';
break;
@@ -58,14 +58,16 @@ class EVMService {
final _web3Wallet = GetIt.I().getWeb3Wallet();
final EVMChainsSupported chainSupported;
- final Web3Client ethClient;
-
- EVMService({required this.chainSupported, Web3Client? ethClient})
- : ethClient = ethClient ??
- Web3Client(
- 'https://mainnet.infura.io/v3/51716d2096df4e73bec298680a51f0c5',
- http.Client()) {
- // Supported events
+ late final Web3Client ethClient;
+
+ EVMService({required this.chainSupported}) {
+ final supportedId = chainSupported.chain();
+ final chainMetadata = ChainData.allChains.firstWhere(
+ (c) => c.chainId == supportedId,
+ );
+ debugPrint('supportedId $supportedId - ${chainMetadata.rpc.first}');
+ ethClient = Web3Client(chainMetadata.rpc.first, http.Client());
+
const supportedEvents = EventsConstants.requiredEvents;
for (final String event in supportedEvents) {
debugPrint('Supported event ${chainSupported.chain()} $event');
@@ -80,7 +82,7 @@ class EVMService {
'eth_sign': ethSign,
'eth_signTransaction': ethSignTransaction,
'eth_signTypedData': ethSignTypedData,
- 'eth_sendTransaction': ethSignTransaction,
+ 'eth_sendTransaction': ethSendTransaction,
'eth_signTypedData_v4': ethSignTypedData,
'wallet_switchEthereumChain': switchChain,
'wallet_addEthereumChain': addChain,
@@ -121,6 +123,7 @@ class EVMService {
} catch (e) {
debugPrint('[$runtimeType] personalSign error $e');
result = e.toString();
+ // _web3Wallet.respondSessionRequest(topic: topic, response: response)
}
}
@@ -170,54 +173,23 @@ class EVMService {
debugPrint('[$runtimeType] ethSignTransaction request: $parameters');
String? result;
- result = await requestApproval(jsonEncode(parameters[0]));
- if (result == null) {
- // Load the private key
- final keys = GetIt.I().getKeysForChain(
- chainSupported.chain(),
- );
- final credentials = EthPrivateKey.fromHex('0x${keys[0].privateKey}');
-
- final ethTransaction = EthereumTransaction.fromJson(parameters[0]);
+ final tJson = parameters[0] as Map;
+ final transaction = await approveTransaction(jsonEncode(tJson));
+ if (transaction != null) {
+ try {
+ // Load the private key
+ final keys = GetIt.I().getKeysForChain(
+ chainSupported.chain(),
+ );
+ final credentials = EthPrivateKey.fromHex('0x${keys[0].privateKey}');
- // Construct a transaction from the EthereumTransaction object
- final transaction = Transaction(
- from: EthereumAddress.fromHex(ethTransaction.from),
- to: EthereumAddress.fromHex(ethTransaction.to),
- value: EtherAmount.fromBigInt(
- EtherUnit.wei,
- BigInt.tryParse(ethTransaction.value) ?? BigInt.zero,
- ),
- gasPrice: ethTransaction.gasPrice != null
- ? EtherAmount.fromBigInt(
- EtherUnit.wei,
- BigInt.tryParse(ethTransaction.gasPrice!) ?? BigInt.zero,
- )
- : null,
- maxFeePerGas: ethTransaction.maxFeePerGas != null
- ? EtherAmount.fromBigInt(
- EtherUnit.wei,
- BigInt.tryParse(ethTransaction.maxFeePerGas!) ?? BigInt.zero,
- )
- : null,
- maxPriorityFeePerGas: ethTransaction.maxPriorityFeePerGas != null
- ? EtherAmount.fromBigInt(
- EtherUnit.wei,
- BigInt.tryParse(ethTransaction.maxPriorityFeePerGas!) ??
- BigInt.zero,
- )
- : null,
- maxGas: int.tryParse(ethTransaction.gasLimit ?? ''),
- nonce: int.tryParse(ethTransaction.nonce ?? ''),
- data: (ethTransaction.data != null && ethTransaction.data != '0x')
- ? Uint8List.fromList(hex.decode(ethTransaction.data!))
- : null,
- );
+ final chainId = chainSupported.chain().split(':').last;
+ debugPrint('[$runtimeType] ethSignTransaction chainId: $chainId');
- try {
final signature = await ethClient.signTransaction(
credentials,
transaction,
+ chainId: int.parse(chainId),
);
// Sign the transaction
@@ -225,8 +197,47 @@ class EVMService {
// Return the signed transaction as a hexadecimal string
result = '0x$signedTx';
- } catch (e) {
+ } catch (e, s) {
debugPrint('[$runtimeType] ethSignTransaction error $e');
+ print(s);
+ result = e.toString();
+ }
+ }
+
+ final session = _web3Wallet.sessions.get(topic);
+ final scheme = session?.peer.metadata.redirect?.native ?? '';
+ DeepLinkHandler.goTo(scheme, delay: 300, modalTitle: 'Success');
+
+ return result;
+ }
+
+ Future ethSendTransaction(String topic, dynamic parameters) async {
+ debugPrint('[$runtimeType] ethSendTransaction request: $parameters');
+ String? result;
+
+ final tJson = parameters[0] as Map;
+ final transaction = await approveTransaction(jsonEncode(tJson));
+ if (transaction != null) {
+ try {
+ // Load the private key
+ final keys = GetIt.I().getKeysForChain(
+ chainSupported.chain(),
+ );
+ final credentials = EthPrivateKey.fromHex('0x${keys[0].privateKey}');
+
+ final chainId = chainSupported.chain().split(':').last;
+ debugPrint('[$runtimeType] ethSendTransaction chainId: $chainId');
+
+ final signedTx = await ethClient.sendTransaction(
+ credentials,
+ transaction,
+ chainId: int.parse(chainId),
+ );
+
+ result = '0x$signedTx';
+ } catch (e, s) {
+ debugPrint('[$runtimeType] ethSendTransaction error $e');
+ print(s);
result = e.toString();
}
}
@@ -306,4 +317,95 @@ class EVMService {
return null;
}
+
+ Future approveTransaction(String text) async {
+ final tJson = jsonDecode(text) as Map;
+ String? tValue = tJson['value'];
+ debugPrint('tValue $tValue');
+ EtherAmount? value;
+ if (tValue != null) {
+ tValue = tValue.replaceFirst('0x', '');
+ value = EtherAmount.fromBigInt(
+ EtherUnit.wei,
+ BigInt.from(int.parse(tValue, radix: 16)),
+ );
+ }
+ Transaction transaction = Transaction(
+ from: EthereumAddress.fromHex(tJson['from']),
+ to: EthereumAddress.fromHex(tJson['to']),
+ value: value,
+ gasPrice: tJson['gasPrice'],
+ maxFeePerGas: tJson['maxFeePerGas'],
+ maxPriorityFeePerGas: tJson['maxPriorityFeePerGas'],
+ maxGas: tJson['maxGas'],
+ nonce: tJson['nonce'],
+ data: (tJson['data'] != null && tJson['data'] != '0x')
+ ? Uint8List.fromList(hex.decode(tJson['data']!))
+ : null,
+ );
+
+ // Gas limit
+ final gasLimit = await ethClient.estimateGas(
+ sender: transaction.from,
+ to: transaction.to,
+ value: transaction.value,
+ data: transaction.data,
+ );
+ print(gasLimit.toInt().toString());
+ // BigInt estimateGas;
+ // BigInt maxFee;
+
+ final gasPrice = await ethClient.getGasPrice();
+ print(gasPrice.getInWei);
+
+ transaction = transaction.copyWith(
+ maxGas: gasLimit.toInt(),
+ gasPrice: gasPrice,
+ );
+
+ final estimateGas = gasLimit * gasPrice.getInWei;
+ final maxFee = estimateGas;
+ final trxValue = transaction.value ?? EtherAmount.zero();
+
+ BigInt total = estimateGas + trxValue.getInWei;
+ BigInt maxAmount = maxFee + trxValue.getInWei;
+
+ // Adjust the amount if it exceeds the balance
+ if (trxValue.getInWei > BigInt.zero) {
+ final chainId = await ethClient.getNetworkId();
+ debugPrint(chainId.toString());
+ final balance = await ethClient.getBalance(transaction.from!);
+ if (maxAmount > balance.getInWei) {
+ final amountLeft = balance.getInWei - maxFee;
+ if (amountLeft <= BigInt.zero) {
+ throw Exception(
+ 'Insufficient funds for transfer, maybe it needs gas fee.',
+ );
+ }
+
+ transaction = transaction.copyWith(
+ value: EtherAmount.inWei(amountLeft),
+ );
+ total = estimateGas + trxValue.getInWei;
+ maxAmount = maxFee + trxValue.getInWei;
+ }
+ }
+
+ final approved = await _bottomSheetService.queueBottomSheet(
+ widget: WCRequestWidget(
+ child: WCConnectionWidget(
+ title: 'Sign Transaction',
+ info: [
+ WCConnectionModel(text: text),
+ ],
+ ),
+ ),
+ );
+
+ if (approved != null && approved == false) {
+ return null;
+ }
+
+ return transaction;
+ }
}
diff --git a/example/wallet/lib/dependencies/key_service/chain_key.dart b/example/wallet/lib/dependencies/key_service/chain_key.dart
index 8918a7ed..8db7da01 100644
--- a/example/wallet/lib/dependencies/key_service/chain_key.dart
+++ b/example/wallet/lib/dependencies/key_service/chain_key.dart
@@ -2,15 +2,17 @@ class ChainKey {
final List chains;
final String privateKey;
final String publicKey;
+ final String address;
ChainKey({
required this.chains,
required this.privateKey,
required this.publicKey,
+ required this.address,
});
@override
String toString() {
- return 'ChainKey(chains: $chains, privateKey: $privateKey, publicKey: $publicKey)';
+ return 'ChainKey(chains: $chains, privateKey: $privateKey, publicKey: $publicKey, address: $address)';
}
}
diff --git a/example/wallet/lib/dependencies/key_service/i_key_service.dart b/example/wallet/lib/dependencies/key_service/i_key_service.dart
index 28406ccf..4c7aa19b 100644
--- a/example/wallet/lib/dependencies/key_service/i_key_service.dart
+++ b/example/wallet/lib/dependencies/key_service/i_key_service.dart
@@ -1,8 +1,9 @@
+import 'package:walletconnect_flutter_v2/apis/core/crypto/crypto_models.dart';
import 'package:walletconnect_flutter_v2_wallet/dependencies/key_service/chain_key.dart';
abstract class IKeyService {
/// Returns a list of all the keys.
- List getKeys();
+ Future> setKeys();
/// Returns a list of all the chain ids.
List getChains();
@@ -14,4 +15,16 @@ abstract class IKeyService {
/// Returns a list of all the accounts in namespace:chainId:address format.
List getAllAccounts();
+
+ String generateMnemonic();
+
+ CryptoKeyPair keyPairFromMnemonic(String mnemonic);
+
+ String getAddressFromPrivateKey(String privateKey);
+
+ Future createWallet();
+
+ Future restoreWallet({required String mnemonic});
+
+ Future deleteWallet();
}
diff --git a/example/wallet/lib/dependencies/key_service/key_service.dart b/example/wallet/lib/dependencies/key_service/key_service.dart
index 0bea000b..f831ffe4 100644
--- a/example/wallet/lib/dependencies/key_service/key_service.dart
+++ b/example/wallet/lib/dependencies/key_service/key_service.dart
@@ -1,27 +1,53 @@
+import 'package:convert/convert.dart';
+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';
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';
+import 'package:walletconnect_flutter_v2_wallet/dependencies/bip39/bip39_base.dart'
+ as bip39;
+import 'package:walletconnect_flutter_v2_wallet/dependencies/bip32/bip32_base.dart'
+ as bip32;
class KeyService extends IKeyService {
- final List keys = [
- ChainKey(
+ final List keys = [];
+
+ @override
+ Future> setKeys() async {
+ // WARNING: SharedPreferences is not the best way to store your keys!
+ final prefs = await SharedPreferences.getInstance();
+ final privateKey = prefs.getString('privateKey') ?? '';
+ if (privateKey.isEmpty) {
+ return [];
+ }
+ final publicKey = prefs.getString('publicKey') ?? '';
+ final address = getAddressFromPrivateKey(privateKey);
+ final evmChainKey = ChainKey(
+ chains: EVMChainsSupported.values.map((e) => e.chain()).toList(),
+ privateKey: privateKey,
+ publicKey: publicKey,
+ address: address,
+ );
+ debugPrint(evmChainKey.toString());
+ final kadenaChainKey = ChainKey(
chains: [
'kadena:mainnet01',
'kadena:testnet04',
'kadena:development',
],
privateKey: DartDefines.kadenaPrivateKey,
- publicKey: DartDefines.kadenaPublicKey,
- ),
- ChainKey(
- chains: EVMChainsSupported.values.map((e) => e.chain()).toList(),
- privateKey:
- '300851edb635b2dbb2d4e70615444925afeb60bf95c19365aff88740e09d7345',
- publicKey:
- '0xeaa05f75445a4beacc73e8fbf07ddb3a76a80a0c', // Eth Address, not actual public key
- )
- ];
+ publicKey: '',
+ address: DartDefines.kadenaAddress,
+ );
+ keys
+ ..clear()
+ ..addAll(
+ [kadenaChainKey, evmChainKey],
+ );
+ return keys;
+ }
@override
List getChains() {
@@ -32,11 +58,6 @@ class KeyService extends IKeyService {
return chainIds;
}
- @override
- List getKeys() {
- return keys;
- }
-
@override
List getKeysForChain(String chain) {
return keys.where((e) => e.chains.contains(chain)).toList();
@@ -47,9 +68,85 @@ class KeyService extends IKeyService {
final List accounts = [];
for (final ChainKey key in keys) {
for (final String chain in key.chains) {
- accounts.add('$chain:${key.publicKey}');
+ accounts.add('$chain:${key.address}');
}
}
return accounts;
}
+
+ @override
+ String generateMnemonic() => bip39.generateMnemonic();
+
+ @override
+ CryptoKeyPair keyPairFromMnemonic(String mnemonic) {
+ final isValidMnemonic = bip39.validateMnemonic(mnemonic);
+ if (!isValidMnemonic) {
+ throw 'Invalid mnemonic';
+ }
+
+ final seed = bip39.mnemonicToSeed(mnemonic);
+ final root = bip32.BIP32.fromSeed(seed);
+
+ final firstChild = root.derivePath("m/44'/60'/0'/0/0");
+ final private = hex.encode(firstChild.privateKey as List);
+ final public = hex.encode(firstChild.publicKey);
+ return CryptoKeyPair(private, public);
+ }
+
+ @override
+ String getAddressFromPrivateKey(String privateKey) {
+ final private = EthPrivateKey.fromHex(privateKey);
+ return private.address.hex;
+ }
+
+ @override
+ Future createWallet() async {
+ final mnemonic = generateMnemonic();
+ return await restoreWallet(mnemonic: mnemonic);
+ }
+
+ @override
+ Future restoreWallet({required String mnemonic}) async {
+ print(mnemonic);
+ final keyPair = keyPairFromMnemonic(mnemonic);
+ final address = getAddressFromPrivateKey(keyPair.privateKey);
+ final evmChainKey = ChainKey(
+ chains: EVMChainsSupported.values.map((e) => e.chain()).toList(),
+ privateKey: keyPair.privateKey,
+ publicKey: keyPair.publicKey,
+ address: address,
+ );
+ debugPrint(evmChainKey.toString());
+ final kadenaChainKey = ChainKey(
+ chains: [
+ 'kadena:mainnet01',
+ 'kadena:testnet04',
+ 'kadena:development',
+ ],
+ privateKey: DartDefines.kadenaPrivateKey,
+ publicKey: '',
+ address: DartDefines.kadenaAddress,
+ );
+
+ keys
+ ..clear()
+ ..addAll(
+ [kadenaChainKey, evmChainKey],
+ );
+
+ // WARNING: SharedPreferences is not the best way to store your keys!
+ final prefs = await SharedPreferences.getInstance();
+ await prefs.setString('privateKey', keyPair.privateKey);
+ await prefs.setString('publicKey', keyPair.publicKey);
+ await prefs.setString('mnemonic', mnemonic);
+ return;
+ }
+
+ @override
+ Future deleteWallet() async {
+ final prefs = await SharedPreferences.getInstance();
+ await prefs.clear();
+ keys.clear();
+ return;
+ }
}
diff --git a/example/wallet/lib/dependencies/web3wallet_service.dart b/example/wallet/lib/dependencies/web3wallet_service.dart
index 4a3e0258..e898b79d 100644
--- a/example/wallet/lib/dependencies/web3wallet_service.dart
+++ b/example/wallet/lib/dependencies/web3wallet_service.dart
@@ -34,7 +34,7 @@ class Web3WalletService extends IWeb3WalletService {
ValueNotifier> auth = ValueNotifier>([]);
@override
- void create() {
+ void create() async {
// Create the web3wallet
_web3Wallet = Web3Wallet(
core: Core(
@@ -56,18 +56,22 @@ class Web3WalletService extends IWeb3WalletService {
);
// Setup our accounts
- List chainKeys = GetIt.I().getKeys();
+ List chainKeys = await GetIt.I().setKeys();
+ if (chainKeys.isEmpty) {
+ await GetIt.I().createWallet();
+ chainKeys = await GetIt.I().setKeys();
+ }
for (final chainKey in chainKeys) {
for (final chainId in chainKey.chains) {
if (chainId.startsWith('kadena')) {
_web3Wallet!.registerAccount(
chainId: chainId,
- accountAddress: 'k**${chainKey.publicKey}',
+ accountAddress: 'k**${chainKey.address}',
);
} else {
_web3Wallet!.registerAccount(
chainId: chainId,
- accountAddress: chainKey.publicKey,
+ accountAddress: chainKey.address,
);
}
}
@@ -172,7 +176,7 @@ class Web3WalletService extends IWeb3WalletService {
'eip155:1',
);
// Create the message to be signed
- final String iss = 'did:pkh:eip155:1:${chainKeys.first.publicKey}';
+ final String iss = 'did:pkh:eip155:1:${chainKeys.first.address}';
final bool? auth = await _bottomSheetHandler.queueBottomSheet(
widget: WCRequestWidget(
diff --git a/example/wallet/lib/main.dart b/example/wallet/lib/main.dart
index decb1010..f6006faf 100644
--- a/example/wallet/lib/main.dart
+++ b/example/wallet/lib/main.dart
@@ -11,6 +11,7 @@ import 'package:walletconnect_flutter_v2_wallet/dependencies/key_service/key_ser
import 'package:walletconnect_flutter_v2_wallet/dependencies/web3wallet_service.dart';
import 'package:walletconnect_flutter_v2_wallet/models/page_data.dart';
import 'package:walletconnect_flutter_v2_wallet/pages/apps_page.dart';
+import 'package:walletconnect_flutter_v2_wallet/pages/settings_page.dart';
import 'package:walletconnect_flutter_v2_wallet/utils/constants.dart';
import 'package:flutter/material.dart';
import 'package:walletconnect_flutter_v2_wallet/utils/string_constants.dart';
@@ -88,12 +89,7 @@ class _MyHomePageState extends State with GetItStateMixin {
icon: Icons.inbox_rounded,
),
PageData(
- page: const Center(
- child: Text(
- 'Settings (Not Implemented)',
- style: StyleConstants.bodyText,
- ),
- ),
+ page: const SettingsPage(),
title: 'Settings',
icon: Icons.settings_outlined,
),
diff --git a/example/wallet/lib/models/chain_data.dart b/example/wallet/lib/models/chain_data.dart
new file mode 100644
index 00000000..edaef5a4
--- /dev/null
+++ b/example/wallet/lib/models/chain_data.dart
@@ -0,0 +1,72 @@
+import 'package:flutter/material.dart';
+import 'package:walletconnect_flutter_v2_wallet/models/chain_metadata.dart';
+
+class ChainData {
+ static final List mainChains = [
+ ChainMetadata(
+ type: ChainType.eip155,
+ chainId: 'eip155:1',
+ name: 'Ethereum',
+ logo: '/chain-logos/eip155-1.png',
+ color: Colors.blue.shade300,
+ rpc: ['https://eth.drpc.org'],
+ ),
+ ChainMetadata(
+ type: ChainType.eip155,
+ chainId: 'eip155:137',
+ name: 'Polygon',
+ logo: '/chain-logos/eip155-137.png',
+ color: Colors.purple.shade300,
+ rpc: ['https://polygon-rpc.com/'],
+ ),
+ const ChainMetadata(
+ type: ChainType.eip155,
+ chainId: 'eip155:42161',
+ name: 'Arbitrum',
+ logo: '/chain-logos/eip155-42161.png',
+ color: Colors.blue,
+ 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 = [
+ ChainMetadata(
+ type: ChainType.eip155,
+ chainId: 'eip155:11155111',
+ name: 'Sepolia',
+ logo: '/chain-logos/eip155-1.png',
+ color: Colors.blue.shade300,
+ isTestnet: true,
+ rpc: ['https://ethereum-sepolia.publicnode.com'],
+ ),
+ ChainMetadata(
+ type: ChainType.eip155,
+ chainId: 'eip155:80001',
+ name: 'Polygon Mumbai',
+ logo: '/chain-logos/eip155-137.png',
+ color: Colors.purple.shade300,
+ isTestnet: true,
+ rpc: ['https://matic-mumbai.chainstacklabs.com'],
+ ),
+ ChainMetadata(
+ type: ChainType.kadena,
+ chainId: 'kadena:testnet04',
+ name: 'Kadena',
+ logo: 'TODO',
+ color: Colors.purple.shade600,
+ rpc: [
+ 'https://api.testnet.chainweb.com',
+ ],
+ ),
+ ];
+
+ static final List allChains = [...mainChains, ...testChains];
+}
diff --git a/example/wallet/lib/pages/app_detail_page.dart b/example/wallet/lib/pages/app_detail_page.dart
index 6453827b..f78393b5 100644
--- a/example/wallet/lib/pages/app_detail_page.dart
+++ b/example/wallet/lib/pages/app_detail_page.dart
@@ -42,8 +42,7 @@ class AppDetailPageState extends State {
List sessionWidgets = [];
for (final SessionData session in sessions) {
- List namespaceWidget =
- ConnectionWidgetBuilder.buildFromNamespaces(
+ final namespaceWidget = ConnectionWidgetBuilder.buildFromNamespaces(
session.topic,
session.namespaces,
);
diff --git a/example/wallet/lib/pages/apps_page.dart b/example/wallet/lib/pages/apps_page.dart
index 8a11db0f..5baacd59 100644
--- a/example/wallet/lib/pages/apps_page.dart
+++ b/example/wallet/lib/pages/apps_page.dart
@@ -31,6 +31,7 @@ class AppsPageState extends State with GetItStateMixin {
_pairings = web3Wallet.pairings.getAll();
web3Wallet.core.pairing.onPairingDelete.subscribe(_onPairingDelete);
web3Wallet.core.pairing.onPairingExpire.subscribe(_onPairingDelete);
+ // TODO web3Wallet.core.echo.register(firebaseAccessToken);
DeepLinkHandler.onLink.listen(_deepLinkListener);
DeepLinkHandler.checkInitialLink();
}
diff --git a/example/wallet/lib/pages/settings_page.dart b/example/wallet/lib/pages/settings_page.dart
new file mode 100644
index 00000000..466450e5
--- /dev/null
+++ b/example/wallet/lib/pages/settings_page.dart
@@ -0,0 +1,235 @@
+import 'package:flutter/material.dart';
+import 'package:get_it/get_it.dart';
+import 'package:walletconnect_flutter_v2_wallet/dependencies/bottom_sheet/i_bottom_sheet_service.dart';
+import 'package:walletconnect_flutter_v2_wallet/dependencies/key_service/i_key_service.dart';
+import 'package:walletconnect_flutter_v2_wallet/utils/constants.dart';
+import 'package:walletconnect_flutter_v2_wallet/widgets/custom_button.dart';
+import 'package:walletconnect_flutter_v2_wallet/widgets/recover_from_seed.dart';
+
+class SettingsPage extends StatefulWidget {
+ const SettingsPage({super.key});
+
+ @override
+ State createState() => _SettingsPageState();
+}
+
+class _SettingsPageState extends State {
+ @override
+ Widget build(BuildContext context) {
+ final keysService = GetIt.I();
+ final chainKeys = keysService.getKeysForChain('eip155:1');
+ return Padding(
+ padding: const EdgeInsets.all(12.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const Padding(
+ padding: EdgeInsets.only(left: 8.0, bottom: 8.0),
+ child: Text(
+ 'Account',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 16.0,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ Container(
+ decoration: BoxDecoration(
+ color: StyleConstants.lightGray,
+ borderRadius: BorderRadius.circular(
+ StyleConstants.linear16,
+ ),
+ ),
+ padding: const EdgeInsets.all(12.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const Row(
+ children: [
+ Text(
+ 'CAIP-10',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 14.0,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ SizedBox(
+ width: 8.0,
+ ),
+ Icon(
+ Icons.copy,
+ size: 14.0,
+ ),
+ ],
+ ),
+ Text(
+ 'eip155:1:${chainKeys.first.address}',
+ style: const TextStyle(
+ color: Colors.black87,
+ fontSize: 13.0,
+ fontWeight: FontWeight.w400,
+ ),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 12.0),
+ Container(
+ decoration: BoxDecoration(
+ color: StyleConstants.lightGray,
+ borderRadius: BorderRadius.circular(
+ StyleConstants.linear16,
+ ),
+ ),
+ padding: const EdgeInsets.all(12.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const Row(
+ children: [
+ Text(
+ 'Private key',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 14.0,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ SizedBox(
+ width: 8.0,
+ ),
+ Icon(
+ Icons.copy,
+ size: 14.0,
+ ),
+ ],
+ ),
+ Text(
+ chainKeys.first.privateKey,
+ style: const TextStyle(
+ color: Colors.black87,
+ fontSize: 13.0,
+ fontWeight: FontWeight.w400,
+ ),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 12.0),
+ Container(
+ decoration: BoxDecoration(
+ color: StyleConstants.lightGray,
+ borderRadius: BorderRadius.circular(
+ StyleConstants.linear16,
+ ),
+ ),
+ padding: const EdgeInsets.all(12.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const Row(
+ children: [
+ Text(
+ 'Public key',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 14.0,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ SizedBox(
+ width: 8.0,
+ ),
+ Icon(
+ Icons.copy,
+ size: 14.0,
+ ),
+ ],
+ ),
+ Text(
+ chainKeys.first.publicKey,
+ style: const TextStyle(
+ color: Colors.black87,
+ fontSize: 13.0,
+ fontWeight: FontWeight.w400,
+ ),
+ ),
+ ],
+ ),
+ ),
+ const Expanded(child: SizedBox()),
+ Row(
+ children: [
+ CustomButton(
+ onTap: () async {
+ final mnemonic =
+ await GetIt.I().queueBottomSheet(
+ widget: RecoverFromSeed(),
+ );
+ if (mnemonic is String) {
+ debugPrint(mnemonic);
+ await keysService.restoreWallet(mnemonic: mnemonic);
+ setState(() {});
+ }
+ },
+ child: const Center(
+ child: Text(
+ 'Import account',
+ style: TextStyle(
+ color: Colors.white,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ // const SizedBox(height: 12.0),
+ // Row(
+ // children: [
+ // CustomButton(
+ // type: CustomButtonType.normal,
+ // onTap: () async {
+ // await keysService.createWallet();
+ // setState(() {});
+ // },
+ // child: const Center(
+ // child: Text(
+ // 'Restart account',
+ // style: TextStyle(
+ // color: Colors.white,
+ // fontWeight: FontWeight.bold,
+ // ),
+ // ),
+ // ),
+ // ),
+ // ],
+ // ),
+ const SizedBox(height: 12.0),
+ Row(
+ children: [
+ CustomButton(
+ type: CustomButtonType.invalid,
+ onTap: () async {
+ await keysService.createWallet();
+ setState(() {});
+ },
+ child: const Center(
+ child: Text(
+ 'Delete and create new',
+ style: TextStyle(
+ color: Colors.white,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/example/wallet/lib/utils/constants.dart b/example/wallet/lib/utils/constants.dart
index 1c67448c..8326aefe 100644
--- a/example/wallet/lib/utils/constants.dart
+++ b/example/wallet/lib/utils/constants.dart
@@ -12,7 +12,7 @@ class StyleConstants {
static const Color primaryColor = Color(0xFF3396FF);
static const Color darkGray = Color(0xFF141414);
- static const Color lightGray = Color.fromARGB(255, 196, 196, 196);
+ static const Color lightGray = Color.fromARGB(255, 227, 227, 227);
static const Color clear = Color.fromARGB(0, 0, 0, 0);
static const Color layerColor0 = Color(0xFF000000);
diff --git a/example/wallet/lib/utils/dart_defines.dart b/example/wallet/lib/utils/dart_defines.dart
index e0acc1a1..1f2ecd21 100644
--- a/example/wallet/lib/utils/dart_defines.dart
+++ b/example/wallet/lib/utils/dart_defines.dart
@@ -2,13 +2,13 @@ class DartDefines {
static const String projectId = String.fromEnvironment(
'PROJECT_ID',
);
- static const String kadenaPrivateKey = String.fromEnvironment(
+ static const kadenaPrivateKey = String.fromEnvironment(
'KADENA_PRIVATE_KEY',
defaultValue:
'7d54a79feeb95ac4efdc6cfd4b702da5ee5dc1c31781b76ea092301c266e2451',
);
- static const String kadenaPublicKey = String.fromEnvironment(
- 'KADENA_PUBLIC_KEY',
+ static const kadenaAddress = String.fromEnvironment(
+ 'KADENA_ADDRESS',
defaultValue:
'af242a8d963f184eca742271a5134ee3d3e006f0377d667510e15f6fc18e41d9',
);
diff --git a/example/wallet/lib/utils/namespace_model_builder.dart b/example/wallet/lib/utils/namespace_model_builder.dart
index 890b3860..b0b3728e 100644
--- a/example/wallet/lib/utils/namespace_model_builder.dart
+++ b/example/wallet/lib/utils/namespace_model_builder.dart
@@ -57,10 +57,12 @@ class ConnectionWidgetBuilder {
elements: ns.accounts,
),
);
- models.add(WCConnectionModel(
- title: StringConstants.methods,
- elements: ns.methods,
- ));
+ models.add(
+ WCConnectionModel(
+ title: StringConstants.methods,
+ elements: ns.methods,
+ ),
+ );
Map actions = {};
for (final String event in ns.events) {
@@ -78,11 +80,13 @@ class ConnectionWidgetBuilder {
);
};
}
- models.add(WCConnectionModel(
- title: StringConstants.events,
- elements: ns.events,
- elementActions: actions,
- ));
+ models.add(
+ WCConnectionModel(
+ title: StringConstants.events,
+ elements: ns.events,
+ elementActions: actions,
+ ),
+ );
views.add(
WCConnectionWidget(
diff --git a/example/wallet/lib/widgets/custom_button.dart b/example/wallet/lib/widgets/custom_button.dart
index 7cc19284..d7c9cf94 100644
--- a/example/wallet/lib/widgets/custom_button.dart
+++ b/example/wallet/lib/widgets/custom_button.dart
@@ -5,17 +5,17 @@ enum CustomButtonType { normal, valid, invalid }
class CustomButton extends StatelessWidget {
final Widget child;
- final CustomButtonType type;
+ final CustomButtonType? type;
final VoidCallback onTap;
const CustomButton({
super.key,
required this.child,
- required this.type,
required this.onTap,
+ this.type,
});
- Color _getBackgroundColor(CustomButtonType type) {
+ Color _getBackgroundColor(CustomButtonType? type) {
switch (type) {
case CustomButtonType.normal:
return Colors.blue;
@@ -24,7 +24,7 @@ class CustomButton extends StatelessWidget {
case CustomButtonType.invalid:
return StyleConstants.errorColor;
default:
- return Colors.blue;
+ return Colors.blue[200]!;
}
}
diff --git a/example/wallet/lib/widgets/recover_from_seed.dart b/example/wallet/lib/widgets/recover_from_seed.dart
new file mode 100644
index 00000000..0f959e3b
--- /dev/null
+++ b/example/wallet/lib/widgets/recover_from_seed.dart
@@ -0,0 +1,88 @@
+import 'package:flutter/material.dart';
+import 'package:walletconnect_flutter_v2_wallet/utils/constants.dart';
+
+class RecoverFromSeed extends StatelessWidget {
+ RecoverFromSeed({
+ Key? key,
+ }) : super(key: key);
+
+ final controller = TextEditingController();
+
+ @override
+ Widget build(BuildContext context) {
+ final unfocusedBorder = OutlineInputBorder(
+ borderSide: BorderSide(color: Colors.grey.shade300, width: 1.0),
+ borderRadius: BorderRadius.circular(12.0),
+ );
+ final focusedBorder = unfocusedBorder.copyWith(
+ borderSide: const BorderSide(color: Colors.blue, width: 1.0),
+ );
+ return Container(
+ color: Colors.white,
+ height: 282.0,
+ width: double.infinity,
+ padding: const EdgeInsets.symmetric(vertical: 20.0),
+ child: Column(
+ children: [
+ Text(
+ 'Insert Seed Phrase',
+ style: StyleConstants.subtitleText.copyWith(
+ color: Colors.black,
+ fontSize: 18.0,
+ ),
+ ),
+ const SizedBox(height: StyleConstants.magic10),
+ SizedBox(
+ height: 90.0,
+ // padding: const EdgeInsets.all(3.0),
+ child: TextFormField(
+ controller: controller,
+ maxLines: 4,
+ textAlignVertical: TextAlignVertical.center,
+ cursorColor: Colors.blue,
+ enableSuggestions: false,
+ autocorrect: false,
+ cursorHeight: 16.0,
+ decoration: InputDecoration(
+ isDense: true,
+ hintText: 'your seed phrase here',
+ hintStyle: const TextStyle(color: Colors.grey),
+ border: unfocusedBorder,
+ errorBorder: unfocusedBorder,
+ enabledBorder: unfocusedBorder,
+ disabledBorder: unfocusedBorder,
+ focusedBorder: focusedBorder,
+ filled: true,
+ fillColor: Colors.grey[200],
+ contentPadding: const EdgeInsets.all(8.0),
+ ),
+ ),
+ ),
+ const SizedBox(height: StyleConstants.magic10),
+ SizedBox(
+ width: double.infinity,
+ child: ElevatedButton(
+ onPressed: () => Navigator.of(context).pop(controller.text),
+ style: ButtonStyle(
+ backgroundColor: MaterialStateProperty.all(Colors.blue),
+ foregroundColor: MaterialStateProperty.all(Colors.white),
+ ),
+ child: const Text('Recover'),
+ ),
+ ),
+ const SizedBox(height: StyleConstants.magic10),
+ SizedBox(
+ width: double.infinity,
+ child: TextButton(
+ onPressed: () => Navigator.of(context).pop(null),
+ child: const Text(
+ 'Cancel',
+ style: TextStyle(color: Colors.grey),
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/example/wallet/lib/widgets/wc_connection_widget/wc_connection_widget.dart b/example/wallet/lib/widgets/wc_connection_widget/wc_connection_widget.dart
index f2ff8845..9d7a81e4 100644
--- a/example/wallet/lib/widgets/wc_connection_widget/wc_connection_widget.dart
+++ b/example/wallet/lib/widgets/wc_connection_widget/wc_connection_widget.dart
@@ -43,7 +43,7 @@ class WCConnectionWidget extends StatelessWidget {
Widget _buildTitle(String text) {
return Container(
decoration: BoxDecoration(
- color: Colors.grey,
+ color: Colors.black12,
borderRadius: BorderRadius.circular(
StyleConstants.linear16,
),
diff --git a/example/wallet/lib/widgets/wc_connection_widget/wc_connection_widget_info.dart b/example/wallet/lib/widgets/wc_connection_widget/wc_connection_widget_info.dart
index 237a2b55..da3060d2 100644
--- a/example/wallet/lib/widgets/wc_connection_widget/wc_connection_widget_info.dart
+++ b/example/wallet/lib/widgets/wc_connection_widget/wc_connection_widget_info.dart
@@ -15,7 +15,7 @@ class WCConnectionWidgetInfo extends StatelessWidget {
return Container(
width: double.infinity,
decoration: BoxDecoration(
- color: Colors.grey,
+ color: Colors.black12,
borderRadius: BorderRadius.circular(
StyleConstants.linear16,
),
diff --git a/example/wallet/pubspec.yaml b/example/wallet/pubspec.yaml
index 28823db2..7a0760a8 100644
--- a/example/wallet/pubspec.yaml
+++ b/example/wallet/pubspec.yaml
@@ -16,7 +16,6 @@ dependencies:
qr_flutter: ^4.0.0
json_annotation: ^4.8.1
fl_toast: ^3.1.0
-
mobile_scanner: ^3.0.0
get_it: ^7.2.0
eth_sig_util: ^0.0.9
diff --git a/lib/apis/utils/extensions.dart b/lib/apis/utils/extensions.dart
new file mode 100644
index 00000000..2c2858f3
--- /dev/null
+++ b/lib/apis/utils/extensions.dart
@@ -0,0 +1,163 @@
+import 'dart:typed_data';
+
+import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
+import 'package:web3dart/crypto.dart' as crypto;
+import 'package:http/http.dart' as http;
+
+extension TransactionExtension on Transaction {
+ // https://github.com/WalletConnect/WalletConnectFlutterV2/issues/246#issuecomment-1905455072
+ Map toJson() {
+ return {
+ if (from != null) 'from': from!.hex,
+ if (to != null) 'to': to!.hex,
+ if (maxGas != null) 'gas': '0x${maxGas!.toRadixString(16)}',
+ if (gasPrice != null)
+ 'gasPrice': '0x${gasPrice!.getInWei.toRadixString(16)}',
+ if (value != null) 'value': '0x${value!.getInWei.toRadixString(16)}',
+ if (data != null) 'data': crypto.bytesToHex(data!),
+ if (nonce != null) 'nonce': nonce,
+ if (maxFeePerGas != null)
+ 'maxFeePerGas': '0x${maxFeePerGas!.getInWei.toRadixString(16)}',
+ if (maxPriorityFeePerGas != null)
+ 'maxPriorityFeePerGas':
+ '0x${maxPriorityFeePerGas!.getInWei.toRadixString(16)}',
+ };
+ }
+}
+
+extension ContractsInteractionExtension on Web3App {
+ Future readContractCall({
+ required DeployedContract deployedContract,
+ required String functionName,
+ required String rpcUrl,
+ List parameters = const [],
+ }) async {
+ try {
+ core.logger.i('readContractCall: with function $functionName');
+ final result = await Web3Client(rpcUrl, http.Client()).call(
+ contract: deployedContract,
+ function: deployedContract.function(functionName),
+ params: parameters,
+ );
+
+ core.logger.i(
+ 'readContractCall - function: $functionName - result: ${result.first}');
+ return result.first;
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future writeContractCall({
+ required String topic,
+ required String chainId,
+ required String rpcUrl,
+ required DeployedContract deployedContract,
+ required String functionName,
+ required Transaction transaction,
+ String? method,
+ List parameters = const [],
+ }) async {
+ try {
+ final credentials = _CustomCredentialsSender(
+ topic: topic,
+ chainId: chainId,
+ signEngine: signEngine,
+ address: transaction.from!,
+ method: method,
+ );
+ final trx = Transaction.callContract(
+ contract: deployedContract,
+ function: deployedContract.function(functionName),
+ from: credentials.address,
+ parameters: [
+ if (transaction.to != null) transaction.to,
+ if (transaction.value != null) transaction.value!.getInWei,
+ ...parameters,
+ ],
+ );
+
+ if (chainId.contains(':')) {
+ chainId = chainId.split(':').last;
+ }
+ return await Web3Client(rpcUrl, http.Client()).sendTransaction(
+ credentials,
+ trx,
+ chainId: int.parse(chainId),
+ );
+ } catch (e) {
+ rethrow;
+ }
+ }
+}
+
+class _CustomCredentialsSender extends CustomTransactionSender {
+ _CustomCredentialsSender({
+ required ISignEngine signEngine,
+ required String topic,
+ required String chainId,
+ required EthereumAddress address,
+ String? method,
+ }) : _signEngine = signEngine,
+ _topic = topic,
+ _chainId = chainId,
+ _address = address,
+ _method = method;
+
+ final ISignEngine _signEngine;
+ final String _topic;
+ final String _chainId;
+ final EthereumAddress _address;
+ final String? _method;
+
+ @override
+ EthereumAddress get address => _address;
+
+ @override
+ Future sendTransaction(Transaction transaction) async {
+ try {
+ final sessionRequestParams = SessionRequestParams(
+ method: _method ?? MethodsConstants.ethSendTransaction,
+ params: [
+ transaction.toJson(),
+ ],
+ );
+
+ final result = await _signEngine.request(
+ topic: _topic,
+ chainId: _chainId,
+ request: sessionRequestParams,
+ );
+ return result;
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ @override
+ Future extractAddress() => Future.value(_address);
+
+ @override
+ Future signToSignature(
+ Uint8List payload, {
+ int? chainId,
+ bool isEIP1559 = false,
+ }) {
+ final signature = signToEcSignature(
+ payload,
+ chainId: chainId,
+ isEIP1559: isEIP1559,
+ );
+ return Future.value(signature);
+ }
+
+ @override
+ crypto.MsgSignature signToEcSignature(
+ Uint8List payload, {
+ int? chainId,
+ bool isEIP1559 = false,
+ }) {
+ // TODO: implement signToEcSignature
+ throw UnimplementedError();
+ }
+}
diff --git a/lib/walletconnect_flutter_v2.dart b/lib/walletconnect_flutter_v2.dart
index 7b1f133b..0c4d6767 100644
--- a/lib/walletconnect_flutter_v2.dart
+++ b/lib/walletconnect_flutter_v2.dart
@@ -19,6 +19,7 @@ export 'apis/models/uri_parse_result.dart';
export 'apis/utils/method_constants.dart';
export 'apis/utils/namespace_utils.dart';
export 'apis/utils/log_level.dart';
+export 'apis/utils/extensions.dart';
// Sign API
export 'apis/sign_api/i_sign_client.dart';
@@ -57,4 +58,3 @@ export 'package:logger/logger.dart';
export 'package:shared_preferences/shared_preferences.dart';
export 'package:universal_io/io.dart';
export 'package:web3dart/web3dart.dart';
-export 'package:web3dart/crypto.dart';