CW-527-Add-Polygon-MATIC-Wallet (#1179)

* chore: Initial setup for polygon package * feat: Add polygon node urls * feat: Add Polygon(MATIC) wallet WIP * feat: Add Polygon(MATIC) wallet WIP * feat: Add Polygon MATIC wallet [skip ci] * fix: Issue with create/restore wallet for polygon * feat: Add erc20 tokens for polygon * feat: Adding Polygon MATIC Wallet * fix: Add build command for polygon to workflow file to fix failing action * fix: Switch evm to not display additional balance * chore: Sync with remote * fix: Revert change to inject app script * feat: Add polygon erc20 tokens * feat: Increase migration version * fix: Restore from QR address validator fix * fix: Adjust wallet connect connection flow to adapt to wallet type * fix: Make wallet fetch nfts based on the current wallet type * fix: Make wallet fetch nfts based on the current wallet type * fix: Try fetching transactions with moralis * fix: Requested review changes * fix: Error creating new wallet * fix: Revert script * fix: Exclude spam NFTs from nft listing API response * Update default_erc20_tokens.dart * replace matic with matic poly * Add polygon wallet scheme to app links * style: reformat default_settings_migration.dart * minor enhancement * fix using different wallet function for setting the transaction priorities * fix: Add chain to calls * Add USDC.e to initial coins * Add other default polygon node * Use Polygon scan some UI fixes * Add polygon scan api key to secrets generation code --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

Adegoke David committed Dec 2, 2023 at 03:26 UTC b3d579c24aa81b9e34a3a2710edec6618114c7be
116 files changed +2351 -206
.github/workflows/pr_test_build.yml
+2
@@ -97,6 +97,7 @@ jobs:
97 cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
98 cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
99 cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
100 + cd cw_polygon && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
101 flutter packages pub run build_runner build --delete-conflicting-outputs
102
103 - name: Add secrets
@@ -131,6 +132,7 @@ jobs:
132 echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart
133 echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
134 echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart
135 + echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart
136 echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart
137 echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
138 echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart
.gitignore
+1
@@ -126,6 +126,7 @@ lib/haven/haven.dart
126 lib/ethereum/ethereum.dart
127 lib/bitcoin_cash/bitcoin_cash.dart
128 lib/nano/nano.dart
129 +lib/polygon/polygon.dart
130
131 ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
132 ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png
android/app/src/main/AndroidManifestBase.xml
+3
@@ -62,6 +62,9 @@
62 <data android:scheme="bitcoincash" />
63 <data android:scheme="bitcoincash-wallet" />
64 <data android:scheme="bitcoincash_wallet" />
65 + <data android:scheme="polygon" />
66 + <data android:scheme="polygon-wallet" />
67 + <data android:scheme="polygon_wallet" />
68 </intent-filter>
69 </activity>
70 <meta-data
assets/polygon_node_list.yml new
+6
@@ -0,0 +1,6 @@
1 +-
2 + uri: polygon-bor.publicnode.com
3 +-
4 + uri: polygon-rpc.com
5 +-
6 + uri: polygon.llamarpc.com
\ No newline at end of file
cw_core/lib/currency_for_wallet_type.dart
+2
@@ -19,6 +19,8 @@ CryptoCurrency currencyForWalletType(WalletType type) {
19 return CryptoCurrency.nano;
20 case WalletType.banano:
21 return CryptoCurrency.banano;
22 + case WalletType.polygon:
23 + return CryptoCurrency.maticpoly;
24 default:
25 throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
26 }
cw_core/lib/erc20_token.dart
+1
@@ -58,6 +58,7 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin {
58
59 static const typeId = ERC20_TOKEN_TYPE_ID;
60 static const boxName = 'Erc20Tokens';
61 + static const polygonBoxName = ' PolygonErc20Tokens';
62
63 @override
64 bool operator ==(other) => (other is Erc20Token && other.contractAddress == contractAddress) ||
cw_core/lib/node.dart
+4
@@ -88,6 +88,8 @@ class Node extends HiveObject with Keyable {
88 } else {
89 return Uri.http(uriRaw, '');
90 }
91 + case WalletType.polygon:
92 + return Uri.https(uriRaw, '');
93 default:
94 throw Exception('Unexpected type ${type.toString()} for Node uri');
95 }
@@ -146,6 +148,8 @@ class Node extends HiveObject with Keyable {
148 case WalletType.nano:
149 case WalletType.banano:
150 return requestNanoNode();
151 + case WalletType.polygon:
152 + return requestElectrumServer();
153 default:
154 return false;
155 }
cw_core/lib/wallet_type.dart
+15 -1
@@ -13,6 +13,7 @@ const walletTypes = [
13 WalletType.bitcoinCash,
14 WalletType.nano,
15 WalletType.banano,
16 + WalletType.polygon,
17 ];
18
19 @HiveType(typeId: WALLET_TYPE_TYPE_ID)
@@ -44,6 +45,8 @@ enum WalletType {
45 @HiveField(8)
46 bitcoinCash,
47
48 + @HiveField(9)
49 + polygon
50 }
51
52 int serializeToInt(WalletType type) {
@@ -64,6 +67,8 @@ int serializeToInt(WalletType type) {
67 return 6;
68 case WalletType.bitcoinCash:
69 return 7;
70 + case WalletType.polygon:
71 + return 8;
72 default:
73 return -1;
74 }
@@ -87,6 +92,8 @@ WalletType deserializeFromInt(int raw) {
92 return WalletType.banano;
93 case 7:
94 return WalletType.bitcoinCash;
95 + case 8:
96 + return WalletType.polygon;
97 default:
98 throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
99 }
@@ -110,6 +117,8 @@ String walletTypeToString(WalletType type) {
117 return 'Nano';
118 case WalletType.banano:
119 return 'Banano';
120 + case WalletType.polygon:
121 + return 'Polygon';
122 default:
123 return '';
124 }
@@ -133,6 +142,8 @@ String walletTypeToDisplayName(WalletType type) {
142 return 'Nano (XNO)';
143 case WalletType.banano:
144 return 'Banano (BAN)';
145 + case WalletType.polygon:
146 + return 'Polygon (MATIC)';
147 default:
148 return '';
149 }
@@ -156,7 +167,10 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
167 return CryptoCurrency.nano;
168 case WalletType.banano:
169 return CryptoCurrency.banano;
170 + case WalletType.polygon:
171 + return CryptoCurrency.maticpoly;
172 default:
160 - throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
173 + throw Exception(
174 + 'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
175 }
176 }
cw_ethereum/lib/ethereum_client.dart
+24 -8
@@ -15,12 +15,12 @@ import 'package:cw_ethereum/ethereum_transaction_priority.dart';
15 import 'package:cw_ethereum/.secrets.g.dart' as secrets;
16
17 class EthereumClient {
18 - final _httpClient = Client();
18 + final httpClient = Client();
19 Web3Client? _client;
20
21 bool connect(Node node) {
22 try {
23 - _client = Web3Client(node.uri.toString(), _httpClient);
23 + _client = Web3Client(node.uri.toString(), httpClient);
24
25 return true;
26 } catch (e) {
@@ -74,9 +74,11 @@ class EthereumClient {
74 required int exponent,
75 String? contractAddress,
76 }) async {
77 - assert(currency == CryptoCurrency.eth || contractAddress != null);
77 + assert(currency == CryptoCurrency.eth ||
78 + currency == CryptoCurrency.maticpoly ||
79 + contractAddress != null);
80
79 - bool _isEthereum = currency == CryptoCurrency.eth;
81 + bool _isEVMCompatibleChain = currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly;
82
83 final price = _client!.getGasPrice();
84
@@ -84,19 +86,23 @@ class EthereumClient {
86 from: privateKey.address,
87 to: EthereumAddress.fromHex(toAddress),
88 maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
87 - value: _isEthereum ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
89 + value: _isEVMCompatibleChain ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
90 );
91
90 - final signedTransaction = await _client!.signTransaction(privateKey, transaction);
92 + final chainId = _getChainIdForCurrency(currency);
93 +
94 + final signedTransaction =
95 + await _client!.signTransaction(privateKey, transaction, chainId: chainId);
96
97 final Function _sendTransaction;
98
94 - if (_isEthereum) {
99 + if (_isEVMCompatibleChain) {
100 _sendTransaction = () async => await sendTransaction(signedTransaction);
101 } else {
102 final erc20 = ERC20(
103 client: _client!,
104 address: EthereumAddress.fromHex(contractAddress!),
105 + chainId: chainId,
106 );
107
108 _sendTransaction = () async {
@@ -118,6 +124,16 @@ class EthereumClient {
124 );
125 }
126
127 + int _getChainIdForCurrency(CryptoCurrency currency) {
128 + switch (currency) {
129 + case CryptoCurrency.maticpoly:
130 + return 137;
131 + case CryptoCurrency.eth:
132 + default:
133 + return 1;
134 + }
135 + }
136 +
137 Future<String> sendTransaction(Uint8List signedTransaction) async =>
138 await _client!.sendRawTransaction(prependTransactionType(0x02, signedTransaction));
139
@@ -198,7 +214,7 @@ I/flutter ( 4474): Gas Used: 53000
214 Future<List<EthereumTransactionModel>> fetchTransactions(String address,
215 {String? contractAddress}) async {
216 try {
201 - final response = await _httpClient.get(Uri.https("api.etherscan.io", "/api", {
217 + final response = await httpClient.get(Uri.https("api.etherscan.io", "/api", {
218 "module": "account",
219 "action": contractAddress != null ? "tokentx" : "txlist",
220 if (contractAddress != null) "contractaddress": contractAddress,
cw_ethereum/lib/ethereum_transaction_info.dart
+10 -3
@@ -1,3 +1,5 @@
1 +import 'dart:math';
2 +
3 import 'package:cw_core/format_amount.dart';
4 import 'package:cw_core/transaction_direction.dart';
5 import 'package:cw_core/transaction_info.dart';
@@ -34,8 +36,10 @@ class EthereumTransactionInfo extends TransactionInfo {
36 final String? to;
37
38 @override
37 - String amountFormatted() =>
38 - '${formatAmount((ethAmount / BigInt.from(10).pow(exponent)).toString())} $tokenSymbol';
39 + String amountFormatted() {
40 + final amount = formatAmount((ethAmount / BigInt.from(10).pow(exponent)).toString());
41 + return '${amount.substring(0, min(10, amount.length))} $tokenSymbol';
42 + }
43
44 @override
45 String fiatAmount() => _fiatAmount ?? '';
@@ -44,7 +48,10 @@ class EthereumTransactionInfo extends TransactionInfo {
48 void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
49
50 @override
47 - String feeFormatted() => '${(ethFee / BigInt.from(10).pow(18)).toString()} ETH';
51 + String feeFormatted() {
52 + final amount = (ethFee / BigInt.from(10).pow(18)).toString();
53 + return '${amount.substring(0, min(10, amount.length))} ETH';
54 + }
55
56 factory EthereumTransactionInfo.fromJson(Map<String, dynamic> data) {
57 return EthereumTransactionInfo(
cw_ethereum/lib/ethereum_transaction_model.dart
+1
@@ -1,3 +1,4 @@
1 +//! Model used for in parsing transactions fetched using etherscan
2 class EthereumTransactionModel {
3 final DateTime date;
4 final String hash;
cw_ethereum/lib/pending_ethereum_transaction.dart
+4 -4
@@ -21,8 +21,8 @@ class PendingEthereumTransaction with PendingTransaction {
21
22 @override
23 String get amountFormatted {
24 - final _amount = BigInt.parse(amount) / BigInt.from(pow(10, exponent));
25 - return _amount.toStringAsFixed(min(15, _amount.toString().length));
24 + final _amount = (BigInt.parse(amount) / BigInt.from(pow(10, exponent))).toString();
25 + return _amount.substring(0, min(10, _amount.length));
26 }
27
28 @override
@@ -30,8 +30,8 @@ class PendingEthereumTransaction with PendingTransaction {
30
31 @override
32 String get feeFormatted {
33 - final _fee = fee / BigInt.from(pow(10, 18));
34 - return _fee.toStringAsFixed(min(15, _fee.toString().length));
33 + final _fee = (fee / BigInt.from(pow(10, 18))).toString();
34 + return _fee.substring(0, min(10, _fee.length));
35 }
36
37 @override
cw_polygon/.gitignore new
+30
@@ -0,0 +1,30 @@
1 +# Miscellaneous
2 +*.class
3 +*.log
4 +*.pyc
5 +*.swp
6 +.DS_Store
7 +.atom/
8 +.buildlog/
9 +.history
10 +.svn/
11 +migrate_working_dir/
12 +
13 +# IntelliJ related
14 +*.iml
15 +*.ipr
16 +*.iws
17 +.idea/
18 +
19 +# The .vscode folder contains launch configuration and tasks you configure in
20 +# VS Code which you may wish to be included in version control, so this line
21 +# is commented out by default.
22 +#.vscode/
23 +
24 +# Flutter/Dart/Pub related
25 +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
26 +/pubspec.lock
27 +**/doc/api/
28 +.dart_tool/
29 +.packages
30 +build/
cw_polygon/.metadata new
+10
@@ -0,0 +1,10 @@
1 +# This file tracks properties of this Flutter project.
2 +# Used by Flutter tool to assess capabilities and perform upgrades etc.
3 +#
4 +# This file should be version controlled and should not be manually edited.
5 +
6 +version:
7 + revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
8 + channel: stable
9 +
10 +project_type: package
cw_polygon/CHANGELOG.md new
+3
@@ -0,0 +1,3 @@
1 +## 0.0.1
2 +
3 +* TODO: Describe initial release.
cw_polygon/LICENSE new
+1
@@ -0,0 +1 @@
1 +TODO: Add your license here.
cw_polygon/README.md new
+39
@@ -0,0 +1,39 @@
1 +<!--
2 +This README describes the package. If you publish this package to pub.dev,
3 +this README's contents appear on the landing page for your package.
4 +
5 +For information about how to write a good package README, see the guide for
6 +[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
7 +
8 +For general information about developing packages, see the Dart guide for
9 +[creating packages](https://dart.dev/guides/libraries/create-library-packages)
10 +and the Flutter guide for
11 +[developing packages and plugins](https://flutter.dev/developing-packages).
12 +-->
13 +
14 +TODO: Put a short description of the package here that helps potential users
15 +know whether this package might be useful for them.
16 +
17 +## Features
18 +
19 +TODO: List what your package can do. Maybe include images, gifs, or videos.
20 +
21 +## Getting started
22 +
23 +TODO: List prerequisites and provide or point to information on how to
24 +start using the package.
25 +
26 +## Usage
27 +
28 +TODO: Include short and useful examples for package users. Add longer examples
29 +to `/example` folder.
30 +
31 +```dart
32 +const like = 'sample';
33 +```
34 +
35 +## Additional information
36 +
37 +TODO: Tell users more about the package: where to find more information, how to
38 +contribute to the package, how to file issues, what response they can expect
39 +from the package authors, and more.
cw_polygon/analysis_options.yaml new
+4
@@ -0,0 +1,4 @@
1 +include: package:flutter_lints/flutter.yaml
2 +
3 +# Additional information about this file can be found at
4 +# https://dart.dev/guides/language/analysis-options
cw_polygon/lib/cw_polygon.dart new
+7
@@ -0,0 +1,7 @@
1 +library cw_polygon;
2 +
3 +/// A Calculator.
4 +class Calculator {
5 + /// Returns [value] plus 1.
6 + int addOne(int value) => value + 1;
7 +}
cw_polygon/lib/default_erc20_tokens.dart new
+86
@@ -0,0 +1,86 @@
1 +import 'package:cw_core/crypto_currency.dart';
2 +import 'package:cw_core/erc20_token.dart';
3 +
4 +class DefaultPolygonErc20Tokens {
5 + final List<Erc20Token> _defaultTokens = [
6 + Erc20Token(
7 + name: "Wrapped Ether",
8 + symbol: "WETH",
9 + contractAddress: "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619",
10 + decimal: 18,
11 + enabled: false,
12 + ),
13 + Erc20Token(
14 + name: "Tether USD (PoS)",
15 + symbol: "USDT",
16 + contractAddress: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
17 + decimal: 6,
18 + enabled: true,
19 + ),
20 + Erc20Token(
21 + name: "USD Coin",
22 + symbol: "USDC",
23 + contractAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
24 + decimal: 6,
25 + enabled: true,
26 + ),
27 + Erc20Token(
28 + name: "USD Coin (POS)",
29 + symbol: "USDC.e",
30 + contractAddress: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
31 + decimal: 6,
32 + enabled: false,
33 + ),
34 + Erc20Token(
35 + name: "Avalanche Token",
36 + symbol: "AVAX",
37 + contractAddress: "0x2C89bbc92BD86F8075d1DEcc58C7F4E0107f286b",
38 + decimal: 18,
39 + enabled: false,
40 + ),
41 + Erc20Token(
42 + name: "Wrapped BTC (PoS)",
43 + symbol: "WBTC",
44 + contractAddress: "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6",
45 + decimal: 8,
46 + enabled: false,
47 + ),
48 + Erc20Token(
49 + name: "Dai (PoS)",
50 + symbol: "DAI",
51 + contractAddress: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
52 + decimal: 18,
53 + enabled: true,
54 + ),
55 + Erc20Token(
56 + name: "SHIBA INU (PoS)",
57 + symbol: "SHIB",
58 + contractAddress: "0x6f8a06447Ff6FcF75d803135a7de15CE88C1d4ec",
59 + decimal: 18,
60 + enabled: false,
61 + ),
62 + Erc20Token(
63 + name: "Uniswap (PoS)",
64 + symbol: "UNI",
65 + contractAddress: "0xb33EaAd8d922B1083446DC23f610c2567fB5180f",
66 + decimal: 18,
67 + enabled: false,
68 + ),
69 + ];
70 +
71 + List<Erc20Token> get initialPolygonErc20Tokens => _defaultTokens.map((token) {
72 + String? iconPath;
73 + try {
74 + iconPath = CryptoCurrency.all
75 + .firstWhere((element) =>
76 + element.title.toUpperCase() == token.symbol.toUpperCase())
77 + .iconPath;
78 + } catch (_) {}
79 +
80 + if (iconPath != null) {
81 + return Erc20Token.copyWith(token, iconPath);
82 + }
83 +
84 + return token;
85 + }).toList();
86 +}
cw_polygon/lib/pending_polygon_transaction.dart new
+19
@@ -0,0 +1,19 @@
1 +import 'dart:typed_data';
2 +
3 +import 'package:cw_ethereum/pending_ethereum_transaction.dart';
4 +
5 +class PendingPolygonTransaction extends PendingEthereumTransaction {
6 + PendingPolygonTransaction({
7 + required Function sendTransaction,
8 + required Uint8List signedTransaction,
9 + required BigInt fee,
10 + required String amount,
11 + required int exponent,
12 + }) : super(
13 + amount: amount,
14 + sendTransaction: sendTransaction,
15 + signedTransaction: signedTransaction,
16 + fee: fee,
17 + exponent: exponent,
18 + );
19 +}
cw_polygon/lib/polygon_client.dart new
+34
@@ -0,0 +1,34 @@
1 +import 'dart:convert';
2 +
3 +import 'package:cw_ethereum/ethereum_client.dart';
4 +import 'package:cw_polygon/polygon_transaction_model.dart';
5 +import 'package:cw_ethereum/.secrets.g.dart' as secrets;
6 +
7 +class PolygonClient extends EthereumClient {
8 + @override
9 + Future<List<PolygonTransactionModel>> fetchTransactions(String address,
10 + {String? contractAddress}) async {
11 + try {
12 + final response = await httpClient.get(Uri.https("api.polygonscan.com", "/api", {
13 + "module": "account",
14 + "action": contractAddress != null ? "tokentx" : "txlist",
15 + if (contractAddress != null) "contractaddress": contractAddress,
16 + "address": address,
17 + "apikey": secrets.polygonScanApiKey,
18 + }));
19 +
20 + final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
21 +
22 + if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
23 + return (jsonResponse['result'] as List)
24 + .map((e) => PolygonTransactionModel.fromJson(e as Map<String, dynamic>))
25 + .toList();
26 + }
27 +
28 + return [];
29 + } catch (e) {
30 + print(e);
31 + return [];
32 + }
33 + }
34 +}
cw_polygon/lib/polygon_exceptions.dart new
+6
@@ -0,0 +1,6 @@
1 +import 'package:cw_core/crypto_currency.dart';
2 +import 'package:cw_ethereum/ethereum_exceptions.dart';
3 +
4 +class PolygonTransactionCreationException extends EthereumTransactionCreationException {
5 + PolygonTransactionCreationException(CryptoCurrency currency) : super(currency);
6 +}
cw_polygon/lib/polygon_formatter.dart new
+25
@@ -0,0 +1,25 @@
1 +import 'package:intl/intl.dart';
2 +
3 +const polygonAmountLength = 12;
4 +const polygonAmountDivider = 1000000000000;
5 +final polygonAmountFormat = NumberFormat()
6 + ..maximumFractionDigits = polygonAmountLength
7 + ..minimumFractionDigits = 1;
8 +
9 +class PolygonFormatter {
10 + static int parsePolygonAmount(String amount) {
11 + try {
12 + return (double.parse(amount) * polygonAmountDivider).round();
13 + } catch (_) {
14 + return 0;
15 + }
16 + }
17 +
18 + static double parsePolygonAmountToDouble(int amount) {
19 + try {
20 + return amount / polygonAmountDivider;
21 + } catch (_) {
22 + return 0;
23 + }
24 + }
25 +}
cw_polygon/lib/polygon_mnemonics_exception.dart new
+5
@@ -0,0 +1,5 @@
1 +class PolygonMnemonicIsIncorrectException implements Exception {
2 + @override
3 + String toString() =>
4 + 'Polygon mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
5 +}
cw_polygon/lib/polygon_transaction_credentials.dart new
+18
@@ -0,0 +1,18 @@
1 +import 'package:cw_core/crypto_currency.dart';
2 +import 'package:cw_core/output_info.dart';
3 +import 'package:cw_ethereum/ethereum_transaction_credentials.dart';
4 +import 'package:cw_polygon/polygon_transaction_priority.dart';
5 +
6 +class PolygonTransactionCredentials extends EthereumTransactionCredentials {
7 + PolygonTransactionCredentials(
8 + List<OutputInfo> outputs, {
9 + required PolygonTransactionPriority? priority,
10 + required CryptoCurrency currency,
11 + final int? feeRate,
12 + }) : super(
13 + outputs,
14 + currency: currency,
15 + priority: priority,
16 + feeRate: feeRate,
17 + );
18 +}
cw_polygon/lib/polygon_transaction_history.dart new
+77
@@ -0,0 +1,77 @@
1 +import 'dart:convert';
2 +import 'dart:core';
3 +import 'package:cw_core/pathForWallet.dart';
4 +import 'package:cw_core/wallet_info.dart';
5 +import 'package:cw_ethereum/file.dart';
6 +import 'package:cw_polygon/polygon_transaction_info.dart';
7 +import 'package:mobx/mobx.dart';
8 +import 'package:cw_core/transaction_history.dart';
9 +
10 +part 'polygon_transaction_history.g.dart';
11 +
12 +const transactionsHistoryFileName = 'polygon_transactions.json';
13 +
14 +class PolygonTransactionHistory = PolygonTransactionHistoryBase with _$PolygonTransactionHistory;
15 +
16 +abstract class PolygonTransactionHistoryBase extends TransactionHistoryBase<PolygonTransactionInfo>
17 + with Store {
18 + PolygonTransactionHistoryBase({required this.walletInfo, required String password})
19 + : _password = password {
20 + transactions = ObservableMap<String, PolygonTransactionInfo>();
21 + }
22 +
23 + final WalletInfo walletInfo;
24 + String _password;
25 +
26 + Future<void> init() async => await _load();
27 +
28 + @override
29 + Future<void> save() async {
30 + try {
31 + final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
32 + final path = '$dirPath/$transactionsHistoryFileName';
33 + final data = json.encode({'transactions': transactions});
34 + await writeData(path: path, password: _password, data: data);
35 + } catch (e, s) {
36 + print('Error while saving polygon transaction history: ${e.toString()}');
37 + print(s);
38 + }
39 + }
40 +
41 + @override
42 + void addOne(PolygonTransactionInfo transaction) => transactions[transaction.id] = transaction;
43 +
44 + @override
45 + void addMany(Map<String, PolygonTransactionInfo> transactions) =>
46 + this.transactions.addAll(transactions);
47 +
48 + Future<Map<String, dynamic>> _read() async {
49 + final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
50 + final path = '$dirPath/$transactionsHistoryFileName';
51 + final content = await read(path: path, password: _password);
52 + if (content.isEmpty) {
53 + return {};
54 + }
55 + return json.decode(content) as Map<String, dynamic>;
56 + }
57 +
58 + Future<void> _load() async {
59 + try {
60 + final content = await _read();
61 + final txs = content['transactions'] as Map<String, dynamic>? ?? {};
62 +
63 + txs.entries.forEach((entry) {
64 + final val = entry.value;
65 +
66 + if (val is Map<String, dynamic>) {
67 + final tx = PolygonTransactionInfo.fromJson(val);
68 + _update(tx);
69 + }
70 + });
71 + } catch (e) {
72 + print(e);
73 + }
74 + }
75 +
76 + void _update(PolygonTransactionInfo transaction) => transactions[transaction.id] = transaction;
77 +}
cw_polygon/lib/polygon_transaction_info.dart new
+49
@@ -0,0 +1,49 @@
1 +import 'package:cw_core/transaction_direction.dart';
2 +import 'package:cw_ethereum/ethereum_transaction_info.dart';
3 +
4 +class PolygonTransactionInfo extends EthereumTransactionInfo {
5 + PolygonTransactionInfo({
6 + required String id,
7 + required int height,
8 + required BigInt ethAmount,
9 + int exponent = 18,
10 + required TransactionDirection direction,
11 + required DateTime date,
12 + required bool isPending,
13 + required BigInt ethFee,
14 + required int confirmations,
15 + String tokenSymbol = "MATIC",
16 + required String? to,
17 + }) : super(
18 + confirmations: confirmations,
19 + id: id,
20 + height: height,
21 + ethAmount: ethAmount,
22 + exponent: exponent,
23 + direction: direction,
24 + date: date,
25 + isPending: isPending,
26 + ethFee: ethFee,
27 + to: to,
28 + tokenSymbol: tokenSymbol,
29 + );
30 +
31 + factory PolygonTransactionInfo.fromJson(Map<String, dynamic> data) {
32 + return PolygonTransactionInfo(
33 + id: data['id'] as String,
34 + height: data['height'] as int,
35 + ethAmount: BigInt.parse(data['amount']),
36 + exponent: data['exponent'] as int,
37 + ethFee: BigInt.parse(data['fee']),
38 + direction: parseTransactionDirectionFromInt(data['direction'] as int),
39 + date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
40 + isPending: data['isPending'] as bool,
41 + confirmations: data['confirmations'] as int,
42 + tokenSymbol: data['tokenSymbol'] as String,
43 + to: data['to'],
44 + );
45 + }
46 +
47 + @override
48 + String feeFormatted() => '${(ethFee / BigInt.from(10).pow(18)).toString()} MATIC';
49 +}
cw_polygon/lib/polygon_transaction_model.dart new
+49
@@ -0,0 +1,49 @@
1 +import 'package:cw_ethereum/ethereum_transaction_model.dart';
2 +
3 +class PolygonTransactionModel extends EthereumTransactionModel {
4 + PolygonTransactionModel({
5 + required DateTime date,
6 + required String hash,
7 + required String from,
8 + required String to,
9 + required BigInt amount,
10 + required int gasUsed,
11 + required BigInt gasPrice,
12 + required String contractAddress,
13 + required int confirmations,
14 + required int blockNumber,
15 + required String? tokenSymbol,
16 + required int? tokenDecimal,
17 + required bool isError,
18 + }) : super(
19 + amount: amount,
20 + date: date,
21 + hash: hash,
22 + from: from,
23 + to: to,
24 + gasPrice: gasPrice,
25 + gasUsed: gasUsed,
26 + confirmations: confirmations,
27 + contractAddress: contractAddress,
28 + blockNumber: blockNumber,
29 + tokenDecimal: tokenDecimal,
30 + tokenSymbol: tokenSymbol,
31 + isError: isError,
32 + );
33 +
34 + factory PolygonTransactionModel.fromJson(Map<String, dynamic> json) => PolygonTransactionModel(
35 + date: DateTime.fromMillisecondsSinceEpoch(int.parse(json["timeStamp"]) * 1000),
36 + hash: json["hash"],
37 + from: json["from"],
38 + to: json["to"],
39 + amount: BigInt.parse(json["value"]),
40 + gasUsed: int.parse(json["gasUsed"]),
41 + gasPrice: BigInt.parse(json["gasPrice"]),
42 + contractAddress: json["contractAddress"],
43 + confirmations: int.parse(json["confirmations"]),
44 + blockNumber: int.parse(json["blockNumber"]),
45 + tokenSymbol: json["tokenSymbol"] ?? "MATIC",
46 + tokenDecimal: int.tryParse(json["tokenDecimal"] ?? ""),
47 + isError: json["isError"] == "1",
48 + );
49 +}
cw_polygon/lib/polygon_transaction_priority.dart new
+51
@@ -0,0 +1,51 @@
1 +import 'package:cw_ethereum/ethereum_transaction_priority.dart';
2 +
3 +class PolygonTransactionPriority extends EthereumTransactionPriority {
4 + const PolygonTransactionPriority({required String title, required int raw, required int tip})
5 + : super(title: title, raw: raw, tip: tip);
6 +
7 + static const List<PolygonTransactionPriority> all = [fast, medium, slow];
8 + static const PolygonTransactionPriority slow =
9 + PolygonTransactionPriority(title: 'slow', raw: 0, tip: 1);
10 + static const PolygonTransactionPriority medium =
11 + PolygonTransactionPriority(title: 'Medium', raw: 1, tip: 2);
12 + static const PolygonTransactionPriority fast =
13 + PolygonTransactionPriority(title: 'Fast', raw: 2, tip: 4);
14 +
15 + static PolygonTransactionPriority deserialize({required int raw}) {
16 + switch (raw) {
17 + case 0:
18 + return slow;
19 + case 1:
20 + return medium;
21 + case 2:
22 + return fast;
23 + default:
24 + throw Exception('Unexpected token: $raw for PolygonTransactionPriority deserialize');
25 + }
26 + }
27 +
28 + @override
29 + String get units => 'gas';
30 +
31 + @override
32 + String toString() {
33 + var label = '';
34 +
35 + switch (this) {
36 + case PolygonTransactionPriority.slow:
37 + label = 'Slow';
38 + break;
39 + case PolygonTransactionPriority.medium:
40 + label = 'Medium';
41 + break;
42 + case PolygonTransactionPriority.fast:
43 + label = 'Fast';
44 + break;
45 + default:
46 + break;
47 + }
48 +
49 + return label;
50 + }
51 +}
cw_polygon/lib/polygon_wallet.dart new
+540
@@ -0,0 +1,540 @@
1 +import 'dart:async';
2 +import 'dart:convert';
3 +import 'dart:io';
4 +import 'dart:math';
5 +
6 +import 'package:cw_core/crypto_currency.dart';
7 +import 'package:cw_core/cake_hive.dart';
8 +import 'package:cw_core/node.dart';
9 +import 'package:cw_core/pathForWallet.dart';
10 +import 'package:cw_core/pending_transaction.dart';
11 +import 'package:cw_core/sync_status.dart';
12 +import 'package:cw_core/transaction_direction.dart';
13 +import 'package:cw_core/transaction_priority.dart';
14 +import 'package:cw_core/wallet_addresses.dart';
15 +import 'package:cw_core/wallet_base.dart';
16 +import 'package:cw_core/wallet_info.dart';
17 +import 'package:cw_ethereum/erc20_balance.dart';
18 +import 'package:cw_ethereum/ethereum_formatter.dart';
19 +import 'package:cw_ethereum/ethereum_transaction_model.dart';
20 +import 'package:cw_ethereum/file.dart';
21 +import 'package:cw_core/erc20_token.dart';
22 +import 'package:cw_polygon/default_erc20_tokens.dart';
23 +import 'package:cw_polygon/polygon_client.dart';
24 +import 'package:cw_polygon/polygon_exceptions.dart';
25 +import 'package:cw_polygon/polygon_formatter.dart';
26 +import 'package:cw_polygon/polygon_transaction_credentials.dart';
27 +import 'package:cw_polygon/polygon_transaction_history.dart';
28 +import 'package:cw_polygon/polygon_transaction_info.dart';
29 +import 'package:cw_polygon/polygon_transaction_model.dart';
30 +import 'package:cw_polygon/polygon_transaction_priority.dart';
31 +import 'package:cw_polygon/polygon_wallet_addresses.dart';
32 +import 'package:hive/hive.dart';
33 +import 'package:hex/hex.dart';
34 +import 'package:mobx/mobx.dart';
35 +import 'package:shared_preferences/shared_preferences.dart';
36 +import 'package:web3dart/crypto.dart';
37 +import 'package:web3dart/web3dart.dart';
38 +import 'package:bip39/bip39.dart' as bip39;
39 +import 'package:bip32/bip32.dart' as bip32;
40 +
41 +part 'polygon_wallet.g.dart';
42 +
43 +class PolygonWallet = PolygonWalletBase with _$PolygonWallet;
44 +
45 +abstract class PolygonWalletBase extends WalletBase<ERC20Balance,
46 + PolygonTransactionHistory, PolygonTransactionInfo> with Store {
47 + PolygonWalletBase({
48 + required WalletInfo walletInfo,
49 + String? mnemonic,
50 + String? privateKey,
51 + required String password,
52 + ERC20Balance? initialBalance,
53 + }) : syncStatus = NotConnectedSyncStatus(),
54 + _password = password,
55 + _mnemonic = mnemonic,
56 + _hexPrivateKey = privateKey,
57 + _isTransactionUpdating = false,
58 + _client = PolygonClient(),
59 + walletAddresses = PolygonWalletAddresses(walletInfo),
60 + balance = ObservableMap<CryptoCurrency, ERC20Balance>.of({
61 + CryptoCurrency.maticpoly: initialBalance ?? ERC20Balance(BigInt.zero)
62 + }),
63 + super(walletInfo) {
64 + this.walletInfo = walletInfo;
65 + transactionHistory =
66 + PolygonTransactionHistory(walletInfo: walletInfo, password: password);
67 +
68 + if (!CakeHive.isAdapterRegistered(Erc20Token.typeId)) {
69 + CakeHive.registerAdapter(Erc20TokenAdapter());
70 + }
71 +
72 + _sharedPrefs.complete(SharedPreferences.getInstance());
73 + }
74 +
75 + final String? _mnemonic;
76 + final String? _hexPrivateKey;
77 + final String _password;
78 +
79 + late final Box<Erc20Token> polygonErc20TokensBox;
80 +
81 + late final EthPrivateKey _polygonPrivateKey;
82 +
83 + EthPrivateKey get polygonPrivateKey => _polygonPrivateKey;
84 +
85 + late PolygonClient _client;
86 +
87 + int? _gasPrice;
88 + int? _estimatedGas;
89 + bool _isTransactionUpdating;
90 +
91 + // TODO: remove after integrating our own node and having eth_newPendingTransactionFilter
92 + Timer? _transactionsUpdateTimer;
93 +
94 + @override
95 + WalletAddresses walletAddresses;
96 +
97 + @override
98 + @observable
99 + SyncStatus syncStatus;
100 +
101 + @override
102 + @observable
103 + late ObservableMap<CryptoCurrency, ERC20Balance> balance;
104 +
105 + Completer<SharedPreferences> _sharedPrefs = Completer();
106 +
107 + Future<void> init() async {
108 + polygonErc20TokensBox =
109 + await CakeHive.openBox<Erc20Token>(Erc20Token.polygonBoxName);
110 + await walletAddresses.init();
111 + await transactionHistory.init();
112 + _polygonPrivateKey = await getPrivateKey(
113 + mnemonic: _mnemonic,
114 + privateKey: _hexPrivateKey,
115 + password: _password,
116 + );
117 + walletAddresses.address = _polygonPrivateKey.address.toString();
118 + await save();
119 + }
120 +
121 + @override
122 + int calculateEstimatedFee(TransactionPriority priority, int? amount) {
123 + try {
124 + if (priority is PolygonTransactionPriority) {
125 + final priorityFee =
126 + EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
127 + return (_gasPrice! + priorityFee) * (_estimatedGas ?? 0);
128 + }
129 +
130 + return 0;
131 + } catch (e) {
132 + return 0;
133 + }
134 + }
135 +
136 + @override
137 + Future<void> changePassword(String password) {
138 + throw UnimplementedError("changePassword");
139 + }
140 +
141 + @override
142 + void close() {
143 + _client.stop();
144 + _transactionsUpdateTimer?.cancel();
145 + }
146 +
147 + @action
148 + @override
149 + Future<void> connectToNode({required Node node}) async {
150 + try {
151 + syncStatus = ConnectingSyncStatus();
152 +
153 + final isConnected = _client.connect(node);
154 +
155 + if (!isConnected) {
156 + throw Exception("Polygon Node connection failed");
157 + }
158 +
159 + _client.setListeners(_polygonPrivateKey.address, _onNewTransaction);
160 +
161 + _setTransactionUpdateTimer();
162 +
163 + syncStatus = ConnectedSyncStatus();
164 + } catch (e) {
165 + syncStatus = FailedSyncStatus();
166 + }
167 + }
168 +
169 + @override
170 + Future<PendingTransaction> createTransaction(Object credentials) async {
171 + final _credentials = credentials as PolygonTransactionCredentials;
172 + final outputs = _credentials.outputs;
173 + final hasMultiDestination = outputs.length > 1;
174 +
175 + final CryptoCurrency transactionCurrency = balance.keys
176 + .firstWhere((element) => element.title == _credentials.currency.title);
177 +
178 + final _erc20Balance = balance[transactionCurrency]!;
179 + BigInt totalAmount = BigInt.zero;
180 + int exponent =
181 + transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18;
182 + num amountToPolygonMultiplier = pow(10, exponent);
183 +
184 + // so far this can not be made with Polygon as Polygon does not support multiple recipients
185 + if (hasMultiDestination) {
186 + if (outputs.any(
187 + (item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
188 + throw PolygonTransactionCreationException(transactionCurrency);
189 + }
190 +
191 + final totalOriginalAmount = PolygonFormatter.parsePolygonAmountToDouble(
192 + outputs.fold(
193 + 0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)));
194 + totalAmount =
195 + BigInt.from(totalOriginalAmount * amountToPolygonMultiplier);
196 +
197 + if (_erc20Balance.balance < totalAmount) {
198 + throw PolygonTransactionCreationException(transactionCurrency);
199 + }
200 + } else {
201 + final output = outputs.first;
202 + // since the fees are taken from Ethereum
203 + // then no need to subtract the fees from the amount if send all
204 + final BigInt allAmount;
205 + if (transactionCurrency is Erc20Token) {
206 + allAmount = _erc20Balance.balance;
207 + } else {
208 + allAmount = _erc20Balance.balance -
209 + BigInt.from(calculateEstimatedFee(_credentials.priority!, null));
210 + }
211 + final totalOriginalAmount = EthereumFormatter.parseEthereumAmountToDouble(
212 + output.formattedCryptoAmount ?? 0);
213 + totalAmount = output.sendAll
214 + ? allAmount
215 + : BigInt.from(totalOriginalAmount * amountToPolygonMultiplier);
216 +
217 + if (_erc20Balance.balance < totalAmount) {
218 + throw PolygonTransactionCreationException(transactionCurrency);
219 + }
220 + }
221 +
222 + final pendingPolygonTransaction = await _client.signTransaction(
223 + privateKey: _polygonPrivateKey,
224 + toAddress: _credentials.outputs.first.isParsedAddress
225 + ? _credentials.outputs.first.extractedAddress!
226 + : _credentials.outputs.first.address,
227 + amount: totalAmount.toString(),
228 + gas: _estimatedGas!,
229 + priority: _credentials.priority!,
230 + currency: transactionCurrency,
231 + exponent: exponent,
232 + contractAddress: transactionCurrency is Erc20Token
233 + ? transactionCurrency.contractAddress
234 + : null,
235 + );
236 +
237 + return pendingPolygonTransaction;
238 + }
239 +
240 + Future<void> _updateTransactions() async {
241 + try {
242 + if (_isTransactionUpdating) {
243 + return;
244 + }
245 + bool isPolygonScanEnabled = (await _sharedPrefs.future).getBool("use_polygonscan") ?? true;
246 + if (!isPolygonScanEnabled) {
247 + return;
248 + }
249 +
250 + _isTransactionUpdating = true;
251 + final transactions = await fetchTransactions();
252 + transactionHistory.addMany(transactions);
253 + await transactionHistory.save();
254 + _isTransactionUpdating = false;
255 + } catch (_) {
256 + _isTransactionUpdating = false;
257 + }
258 + }
259 +
260 + @override
261 + Future<Map<String, PolygonTransactionInfo>> fetchTransactions() async {
262 + final address = _polygonPrivateKey.address.hex;
263 + final transactions = await _client.fetchTransactions(address);
264 +
265 + final List<Future<List<PolygonTransactionModel>>> polygonErc20TokensTransactions =
266 + [];
267 +
268 + for (var token in balance.keys) {
269 + if (token is Erc20Token) {
270 + polygonErc20TokensTransactions.add(_client.fetchTransactions(
271 + address,
272 + contractAddress: token.contractAddress,
273 + ) as Future<List<PolygonTransactionModel>>);
274 + }
275 + }
276 +
277 + final tokensTransaction = await Future.wait(polygonErc20TokensTransactions);
278 + transactions.addAll(tokensTransaction.expand((element) => element));
279 +
280 + final Map<String, PolygonTransactionInfo> result = {};
281 +
282 + for (var transactionModel in transactions) {
283 + if (transactionModel.isError) {
284 + continue;
285 + }
286 +
287 + result[transactionModel.hash] = PolygonTransactionInfo(
288 + id: transactionModel.hash,
289 + height: transactionModel.blockNumber,
290 + ethAmount: transactionModel.amount,
291 + direction: transactionModel.from == address
292 + ? TransactionDirection.outgoing
293 + : TransactionDirection.incoming,
294 + isPending: false,
295 + date: transactionModel.date,
296 + confirmations: transactionModel.confirmations,
297 + ethFee:
298 + BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice,
299 + exponent: transactionModel.tokenDecimal ?? 18,
300 + tokenSymbol: transactionModel.tokenSymbol ?? "MATIC",
301 + to: transactionModel.to,
302 + );
303 + }
304 +
305 + return result;
306 + }
307 +
308 + @override
309 + Object get keys => throw UnimplementedError("keys");
310 +
311 + @override
312 + Future<void> rescan({required int height}) {
313 + throw UnimplementedError("rescan");
314 + }
315 +
316 + @override
317 + Future<void> save() async {
318 + await walletAddresses.updateAddressesInBox();
319 + final path = await makePath();
320 + await write(path: path, password: _password, data: toJSON());
321 + await transactionHistory.save();
322 + }
323 +
324 + @override
325 + String? get seed => _mnemonic;
326 +
327 + @override
328 + String get privateKey => HEX.encode(_polygonPrivateKey.privateKey);
329 +
330 + @action
331 + @override
332 + Future<void> startSync() async {
333 + try {
334 + syncStatus = AttemptingSyncStatus();
335 + await _updateBalance();
336 + await _updateTransactions();
337 + _gasPrice = await _client.getGasUnitPrice();
338 + _estimatedGas = await _client.getEstimatedGas();
339 +
340 + Timer.periodic(const Duration(minutes: 1),
341 + (timer) async => _gasPrice = await _client.getGasUnitPrice());
342 + Timer.periodic(const Duration(seconds: 10),
343 + (timer) async => _estimatedGas = await _client.getEstimatedGas());
344 +
345 + syncStatus = SyncedSyncStatus();
346 + } catch (e) {
347 + syncStatus = FailedSyncStatus();
348 + }
349 + }
350 +
351 + Future<String> makePath() async =>
352 + pathForWallet(name: walletInfo.name, type: walletInfo.type);
353 +
354 + String toJSON() => json.encode({
355 + 'mnemonic': _mnemonic,
356 + 'private_key': privateKey,
357 + 'balance': balance[currency]!.toJSON(),
358 + });
359 +
360 + static Future<PolygonWallet> open({
361 + required String name,
362 + required String password,
363 + required WalletInfo walletInfo,
364 + }) async {
365 + final path = await pathForWallet(name: name, type: walletInfo.type);
366 + final jsonSource = await read(path: path, password: password);
367 + final data = json.decode(jsonSource) as Map;
368 + final mnemonic = data['mnemonic'] as String?;
369 + final privateKey = data['private_key'] as String?;
370 + final balance = ERC20Balance.fromJSON(data['balance'] as String) ??
371 + ERC20Balance(BigInt.zero);
372 +
373 + return PolygonWallet(
374 + walletInfo: walletInfo,
375 + password: password,
376 + mnemonic: mnemonic,
377 + privateKey: privateKey,
378 + initialBalance: balance,
379 + );
380 + }
381 +
382 + Future<void> _updateBalance() async {
383 + balance[currency] = await _fetchMaticBalance();
384 +
385 + await _fetchErc20Balances();
386 + await save();
387 + }
388 +
389 + Future<ERC20Balance> _fetchMaticBalance() async {
390 + final balance = await _client.getBalance(_polygonPrivateKey.address);
391 + return ERC20Balance(balance.getInWei);
392 + }
393 +
394 + Future<void> _fetchErc20Balances() async {
395 + for (var token in polygonErc20TokensBox.values) {
396 + try {
397 + if (token.enabled) {
398 + balance[token] = await _client.fetchERC20Balances(
399 + _polygonPrivateKey.address,
400 + token.contractAddress,
401 + );
402 + } else {
403 + balance.remove(token);
404 + }
405 + } catch (_) {}
406 + }
407 + }
408 +
409 + Future<EthPrivateKey> getPrivateKey(
410 + {String? mnemonic, String? privateKey, required String password}) async {
411 + assert(mnemonic != null || privateKey != null);
412 +
413 + if (privateKey != null) {
414 + return EthPrivateKey.fromHex(privateKey);
415 + }
416 +
417 + final seed = bip39.mnemonicToSeed(mnemonic!);
418 +
419 + final root = bip32.BIP32.fromSeed(seed);
420 +
421 + const _hdPathPolygon = "m/44'/60'/0'/0";
422 + const index = 0;
423 + final addressAtIndex = root.derivePath("$_hdPathPolygon/$index");
424 +
425 + return EthPrivateKey.fromHex(
426 + HEX.encode(addressAtIndex.privateKey as List<int>));
427 + }
428 +
429 + Future<void>? updateBalance() async => await _updateBalance();
430 +
431 + List<Erc20Token> get erc20Currencies => polygonErc20TokensBox.values.toList();
432 +
433 + Future<void> addErc20Token(Erc20Token token) async {
434 + String? iconPath;
435 + try {
436 + iconPath = CryptoCurrency.all
437 + .firstWhere((element) =>
438 + element.title.toUpperCase() == token.symbol.toUpperCase())
439 + .iconPath;
440 + } catch (_) {}
441 +
442 + final _token = Erc20Token(
443 + name: token.name,
444 + symbol: token.symbol,
445 + contractAddress: token.contractAddress,
446 + decimal: token.decimal,
447 + enabled: token.enabled,
448 + iconPath: iconPath,
449 + );
450 +
451 + await polygonErc20TokensBox.put(_token.contractAddress, _token);
452 +
453 + if (_token.enabled) {
454 + balance[_token] = await _client.fetchERC20Balances(
455 + _polygonPrivateKey.address,
456 + _token.contractAddress,
457 + );
458 + } else {
459 + balance.remove(_token);
460 + }
461 + }
462 +
463 + Future<void> deleteErc20Token(Erc20Token token) async {
464 + await token.delete();
465 +
466 + balance.remove(token);
467 + _updateBalance();
468 + }
469 +
470 + Future<Erc20Token?> getErc20Token(String contractAddress) async =>
471 + await _client.getErc20Token(contractAddress);
472 +
473 + void _onNewTransaction() {
474 + _updateBalance();
475 + _updateTransactions();
476 + }
477 +
478 + void addInitialTokens() {
479 + final initialErc20Tokens =
480 + DefaultPolygonErc20Tokens().initialPolygonErc20Tokens;
481 +
482 + for (var token in initialErc20Tokens) {
483 + polygonErc20TokensBox.put(token.contractAddress, token);
484 + }
485 + }
486 +
487 + @override
488 + Future<void> renameWalletFiles(String newWalletName) async {
489 + final currentWalletPath =
490 + await pathForWallet(name: walletInfo.name, type: type);
491 + final currentWalletFile = File(currentWalletPath);
492 +
493 + final currentDirPath =
494 + await pathForWalletDir(name: walletInfo.name, type: type);
495 + final currentTransactionsFile =
496 + File('$currentDirPath/$transactionsHistoryFileName');
497 +
498 + // Copies current wallet files into new wallet name's dir and files
499 + if (currentWalletFile.existsSync()) {
500 + final newWalletPath =
501 + await pathForWallet(name: newWalletName, type: type);
502 + await currentWalletFile.copy(newWalletPath);
503 + }
504 + if (currentTransactionsFile.existsSync()) {
505 + final newDirPath =
506 + await pathForWalletDir(name: newWalletName, type: type);
507 + await currentTransactionsFile
508 + .copy('$newDirPath/$transactionsHistoryFileName');
509 + }
510 +
511 + // Delete old name's dir and files
512 + await Directory(currentDirPath).delete(recursive: true);
513 + }
514 +
515 + void _setTransactionUpdateTimer() {
516 + if (_transactionsUpdateTimer?.isActive ?? false) {
517 + _transactionsUpdateTimer!.cancel();
518 + }
519 +
520 + _transactionsUpdateTimer = Timer.periodic(Duration(seconds: 10), (_) {
521 + _updateTransactions();
522 + _updateBalance();
523 + });
524 + }
525 +
526 + void updatePolygonScanUsageState(bool isEnabled) {
527 + if (isEnabled) {
528 + _updateTransactions();
529 + _setTransactionUpdateTimer();
530 + } else {
531 + _transactionsUpdateTimer?.cancel();
532 + }
533 + }
534 +
535 + @override
536 + String signMessage(String message, {String? address = null}) => bytesToHex(
537 + _polygonPrivateKey.signPersonalMessageToUint8List(ascii.encode(message)));
538 +
539 + Web3Client? getWeb3Client() => _client.getWeb3Client();
540 +}
cw_polygon/lib/polygon_wallet_addresses.dart new
+5
@@ -0,0 +1,5 @@
1 +import 'package:cw_ethereum/ethereum_wallet_addresses.dart';
2 +
3 +class PolygonWalletAddresses extends EthereumWalletAddresses {
4 + PolygonWalletAddresses(super.walletInfo);
5 +}
cw_polygon/lib/polygon_wallet_creation_credentials.dart new
+28
@@ -0,0 +1,28 @@
1 +import 'package:cw_core/wallet_credentials.dart';
2 +import 'package:cw_core/wallet_info.dart';
3 +class PolygonNewWalletCredentials extends WalletCredentials {
4 + PolygonNewWalletCredentials({required String name, WalletInfo? walletInfo})
5 + : super(name: name, walletInfo: walletInfo);
6 +}
7 +
8 +class PolygonRestoreWalletFromSeedCredentials extends WalletCredentials {
9 + PolygonRestoreWalletFromSeedCredentials(
10 + {required String name,
11 + required String password,
12 + required this.mnemonic,
13 + WalletInfo? walletInfo})
14 + : super(name: name, password: password, walletInfo: walletInfo);
15 +
16 + final String mnemonic;
17 +}
18 +
19 +class PolygonRestoreWalletFromPrivateKey extends WalletCredentials {
20 + PolygonRestoreWalletFromPrivateKey(
21 + {required String name,
22 + required String password,
23 + required this.privateKey,
24 + WalletInfo? walletInfo})
25 + : super(name: name, password: password, walletInfo: walletInfo);
26 +
27 + final String privateKey;
28 +}
cw_polygon/lib/polygon_wallet_service.dart new
+123
@@ -0,0 +1,123 @@
1 +import 'dart:io';
2 +
3 +import 'package:cw_core/pathForWallet.dart';
4 +import 'package:cw_core/wallet_base.dart';
5 +import 'package:cw_core/wallet_info.dart';
6 +import 'package:cw_core/wallet_service.dart';
7 +import 'package:cw_core/wallet_type.dart';
8 +import 'package:cw_ethereum/ethereum_mnemonics.dart';
9 +import 'package:cw_polygon/polygon_wallet.dart';
10 +import 'package:bip39/bip39.dart' as bip39;
11 +import 'package:hive/hive.dart';
12 +import 'polygon_wallet_creation_credentials.dart';
13 +import 'package:collection/collection.dart';
14 +
15 +class PolygonWalletService extends WalletService<PolygonNewWalletCredentials,
16 + PolygonRestoreWalletFromSeedCredentials, PolygonRestoreWalletFromPrivateKey> {
17 + PolygonWalletService(this.walletInfoSource);
18 +
19 + final Box<WalletInfo> walletInfoSource;
20 +
21 + @override
22 + Future<PolygonWallet> create(PolygonNewWalletCredentials credentials) async {
23 + final strength = (credentials.seedPhraseLength == 12)
24 + ? 128
25 + : (credentials.seedPhraseLength == 24)
26 + ? 256
27 + : 128;
28 +
29 + final mnemonic = bip39.generateMnemonic(strength: strength);
30 + final wallet = PolygonWallet(
31 + walletInfo: credentials.walletInfo!,
32 + mnemonic: mnemonic,
33 + password: credentials.password!,
34 + );
35 +
36 + await wallet.init();
37 + wallet.addInitialTokens();
38 + await wallet.save();
39 +
40 + return wallet;
41 + }
42 +
43 + @override
44 + WalletType getType() => WalletType.polygon;
45 +
46 + @override
47 + Future<bool> isWalletExit(String name) async =>
48 + File(await pathForWallet(name: name, type: getType())).existsSync();
49 +
50 + @override
51 + Future<PolygonWallet> openWallet(String name, String password) async {
52 + final walletInfo =
53 + walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
54 + final wallet = await PolygonWalletBase.open(
55 + name: name,
56 + password: password,
57 + walletInfo: walletInfo,
58 + );
59 +
60 + await wallet.init();
61 + await wallet.save();
62 +
63 + return wallet;
64 + }
65 +
66 + @override
67 + Future<void> remove(String wallet) async {
68 + File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
69 + final walletInfo = walletInfoSource.values
70 + .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
71 + await walletInfoSource.delete(walletInfo.key);
72 + }
73 +
74 + @override
75 + Future<PolygonWallet> restoreFromKeys(PolygonRestoreWalletFromPrivateKey credentials) async {
76 + final wallet = PolygonWallet(
77 + password: credentials.password!,
78 + privateKey: credentials.privateKey,
79 + walletInfo: credentials.walletInfo!,
80 + );
81 +
82 + await wallet.init();
83 + wallet.addInitialTokens();
84 + await wallet.save();
85 +
86 + return wallet;
87 + }
88 +
89 + @override
90 + Future<PolygonWallet> restoreFromSeed(PolygonRestoreWalletFromSeedCredentials credentials) async {
91 + if (!bip39.validateMnemonic(credentials.mnemonic)) {
92 + throw EthereumMnemonicIsIncorrectException();
93 + }
94 +
95 + final wallet = PolygonWallet(
96 + password: credentials.password!,
97 + mnemonic: credentials.mnemonic,
98 + walletInfo: credentials.walletInfo!,
99 + );
100 +
101 + await wallet.init();
102 + wallet.addInitialTokens();
103 + await wallet.save();
104 +
105 + return wallet;
106 + }
107 +
108 + @override
109 + Future<void> rename(String currentName, String password, String newName) async {
110 + final currentWalletInfo = walletInfoSource.values
111 + .firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
112 + final currentWallet = await PolygonWalletBase.open(
113 + password: password, name: currentName, walletInfo: currentWalletInfo);
114 +
115 + await currentWallet.renameWalletFiles(newName);
116 +
117 + final newWalletInfo = currentWalletInfo;
118 + newWalletInfo.id = WalletBase.idFor(newName, getType());
119 + newWalletInfo.name = newName;
120 +
121 + await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
122 + }
123 +}
cw_polygon/pubspec.yaml new
+73
@@ -0,0 +1,73 @@
1 +name: cw_polygon
2 +description: A new Flutter package project.
3 +version: 0.0.1
4 +publish_to: none
5 +author: Cake Wallet
6 +homepage: https://cakewallet.com
7 +
8 +environment:
9 + sdk: '>=3.0.6 <4.0.0'
10 + flutter: ">=1.17.0"
11 +
12 +dependencies:
13 + flutter:
14 + sdk: flutter
15 + cw_core:
16 + path: ../cw_core
17 + cw_ethereum:
18 + path: ../cw_ethereum
19 + mobx: ^2.0.7+4
20 + intl: ^0.18.0
21 + bip39: ^1.0.6
22 + hive: ^2.2.3
23 + collection: ^1.17.1
24 + web3dart: ^2.7.1
25 + bip32: ^2.0.0
26 + hex: ^0.2.0
27 + shared_preferences: ^2.0.15
28 +
29 +
30 +dev_dependencies:
31 + flutter_test:
32 + sdk: flutter
33 + flutter_lints: ^2.0.0
34 + build_runner: ^2.1.11
35 + mobx_codegen: ^2.0.7
36 + hive_generator: ^1.1.3
37 +
38 +# For information on the generic Dart part of this file, see the
39 +# following page: https://dart.dev/tools/pub/pubspec
40 +
41 +# The following section is specific to Flutter packages.
42 +flutter:
43 +
44 + # To add assets to your package, add an assets section, like this:
45 + # assets:
46 + # - images/a_dot_burr.jpeg
47 + # - images/a_dot_ham.jpeg
48 + #
49 + # For details regarding assets in packages, see
50 + # https://flutter.dev/assets-and-images/#from-packages
51 + #
52 + # An image asset can refer to one or more resolution-specific "variants", see
53 + # https://flutter.dev/assets-and-images/#resolution-aware
54 +
55 + # To add custom fonts to your package, add a fonts section here,
56 + # in this "flutter" section. Each entry in this list should have a
57 + # "family" key with the font family name, and a "fonts" key with a
58 + # list giving the asset and other descriptors for the font. For
59 + # example:
60 + # fonts:
61 + # - family: Schyler
62 + # fonts:
63 + # - asset: fonts/Schyler-Regular.ttf
64 + # - asset: fonts/Schyler-Italic.ttf
65 + # style: italic
66 + # - family: Trajan Pro
67 + # fonts:
68 + # - asset: fonts/TrajanPro.ttf
69 + # - asset: fonts/TrajanPro_Bold.ttf
70 + # weight: 700
71 + #
72 + # For details regarding fonts in packages, see
73 + # https://flutter.dev/custom-fonts/#from-packages
cw_polygon/test/cw_polygon_test.dart new
+12
@@ -0,0 +1,12 @@
1 +import 'package:flutter_test/flutter_test.dart';
2 +
3 +import 'package:cw_polygon/cw_polygon.dart';
4 +
5 +void main() {
6 + test('adds one to input values', () {
7 + final calculator = Calculator();
8 + expect(calculator.addOne(2), 3);
9 + expect(calculator.addOne(-7), -6);
10 + expect(calculator.addOne(0), 1);
11 + });
12 +}
ios/Runner/InfoBase.plist
+20
@@ -160,6 +160,26 @@
160 <string>bitcoincash-wallet</string>
161 </array>
162 </dict>
163 + <dict>
164 + <key>CFBundleTypeRole</key>
165 + <string>Editor</string>
166 + <key>CFBundleURLName</key>
167 + <string>polygon</string>
168 + <key>CFBundleURLSchemes</key>
169 + <array>
170 + <string>polygon</string>
171 + </array>
172 + </dict>
173 + <dict>
174 + <key>CFBundleTypeRole</key>
175 + <string>Viewer</string>
176 + <key>CFBundleURLName</key>
177 + <string>polygon-wallet</string>
178 + <key>CFBundleURLSchemes</key>
179 + <array>
180 + <string>polygon-wallet</string>
181 + </array>
182 + </dict>
183 </array>
184 <key>CFBundleVersion</key>
185 <string>$(CURRENT_PROJECT_VERSION)</string>
lib/core/address_validator.dart
+2
@@ -271,6 +271,8 @@ class AddressValidator extends TextValidator {
271 '|([^0-9a-zA-Z]|^)ltc[a-zA-Z0-9]{26,45}([^0-9a-zA-Z]|\$)';
272 case CryptoCurrency.eth:
273 return '0x[0-9a-zA-Z]{42}';
274 + case CryptoCurrency.maticpoly:
275 + return '0x[0-9a-zA-Z]{42}';
276 case CryptoCurrency.nano:
277 return 'nano_[0-9a-zA-Z]{60}';
278 case CryptoCurrency.banano:
lib/core/fiat_conversion_service.dart
+1 -1
@@ -16,7 +16,7 @@ Future<double> _fetchPrice(Map<String, dynamic> args) async {
16
17 final Map<String, String> queryParams = {
18 'interval_count': '1',
19 - 'base': crypto,
19 + 'base': crypto.split(".").first,
20 'quote': fiat,
21 'key': secrets.fiatApiKey,
22 };
lib/core/seed_validator.dart
+3
@@ -3,6 +3,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart';
3 import 'package:cake_wallet/haven/haven.dart';
4 import 'package:cake_wallet/core/validator.dart';
5 import 'package:cake_wallet/entities/mnemonic_item.dart';
6 +import 'package:cake_wallet/polygon/polygon.dart';
7 import 'package:cw_core/wallet_type.dart';
8 import 'package:cake_wallet/monero/monero.dart';
9 import 'package:cake_wallet/nano/nano.dart';
@@ -34,6 +35,8 @@ class SeedValidator extends Validator<MnemonicItem> {
35 case WalletType.nano:
36 case WalletType.banano:
37 return nano!.getNanoWordList(language);
38 + case WalletType.polygon:
39 + return polygon!.getPolygonWordList(language);
40 default:
41 return [];
42 }
lib/core/wallet_connect/evm_chain_service.dart
+18 -10
@@ -6,6 +6,7 @@ import 'package:cake_wallet/core/wallet_connect/eth_transaction_model.dart';
6 import 'package:cake_wallet/core/wallet_connect/evm_chain_id.dart';
7 import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
8 import 'package:cake_wallet/generated/i18n.dart';
9 +import 'package:cake_wallet/reactions/wallet_connect.dart';
10 import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart';
11 import 'package:cake_wallet/store/app_store.dart';
12 import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
@@ -14,7 +15,6 @@ import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_widget
15 import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart';
16 import 'package:cake_wallet/src/screens/wallet_connect/utils/string_parsing.dart';
17 import 'package:convert/convert.dart';
17 -import 'package:cw_core/wallet_type.dart';
18 import 'package:eth_sig_util/eth_sig_util.dart';
19 import 'package:eth_sig_util/util/utils.dart';
20 import 'package:http/http.dart' as http;
@@ -46,13 +46,12 @@ class EvmChainServiceImpl implements ChainService {
46 required this.wcKeyService,
47 required this.bottomSheetService,
48 required this.wallet,
49 - Web3Client? ethClient,
50 - }) : ethClient = ethClient ??
49 + Web3Client? web3Client,
50 + }) : ethClient = web3Client ??
51 Web3Client(
52 - appStore.settingsStore.getCurrentNode(WalletType.ethereum).uri.toString(),
52 + appStore.settingsStore.getCurrentNode(appStore.wallet!.type).uri.toString(),
53 http.Client(),
54 ) {
55 -
55 for (final String event in getEvents()) {
56 wallet.registerEventEmitter(chainId: getChainId(), event: event);
57 }
@@ -138,7 +137,8 @@ class EvmChainServiceImpl implements ChainService {
137
138 try {
139 // Load the private key
141 - final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
140 + final List<ChainKeyModel> keys = wcKeyService
141 + .getKeysForChain(getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type));
142
143 final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey);
144
@@ -176,13 +176,15 @@ class EvmChainServiceImpl implements ChainService {
176
177 try {
178 // Load the private key
179 - final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
179 + final List<ChainKeyModel> keys = wcKeyService
180 + .getKeysForChain(getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type));
181
182 final EthPrivateKey credentials = EthPrivateKey.fromHex(keys[0].privateKey);
183
184 final String signature = hex.encode(
185 credentials.signPersonalMessageToUint8List(
186 Uint8List.fromList(utf8.encode(message)),
187 + chainId: getChainIdBasedOnWalletType(appStore.wallet!.type),
188 ),
189 );
190 log(signature);
@@ -212,7 +214,8 @@ class EvmChainServiceImpl implements ChainService {
214 }
215
216 // Load the private key
215 - final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
217 + final List<ChainKeyModel> keys = wcKeyService
218 + .getKeysForChain(getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type));
219
220 final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey);
221
@@ -232,7 +235,11 @@ class EvmChainServiceImpl implements ChainService {
235 );
236
237 try {
235 - final result = await ethClient.sendTransaction(credentials, transaction);
238 + final result = await ethClient.sendTransaction(
239 + credentials,
240 + transaction,
241 + chainId: getChainIdBasedOnWalletType(appStore.wallet!.type),
242 + );
243
244 log('Result: $result');
245
@@ -267,7 +274,8 @@ class EvmChainServiceImpl implements ChainService {
274 return authError;
275 }
276
270 - final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
277 + final List<ChainKeyModel> keys = wcKeyService
278 + .getKeysForChain(getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type));
279
280 return EthSigUtil.signTypedData(
281 privateKey: keys[0].privateKey,
lib/core/wallet_connect/wallet_connect_key_service.dart
+25 -3
@@ -1,9 +1,11 @@
1 import 'package:cake_wallet/ethereum/ethereum.dart';
2 import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
3 +import 'package:cake_wallet/polygon/polygon.dart';
4 import 'package:cw_core/balance.dart';
5 import 'package:cw_core/transaction_history.dart';
6 import 'package:cw_core/transaction_info.dart';
7 import 'package:cw_core/wallet_base.dart';
8 +import 'package:cw_core/wallet_type.dart';
9
10 abstract class WalletConnectKeyService {
11 /// Returns a list of all the keys.
@@ -32,16 +34,36 @@ class KeyServiceImpl implements WalletConnectKeyService {
34 'eip155:42161',
35 'eip155:80001',
36 ],
35 - privateKey: ethereum!.getPrivateKey(wallet),
36 - publicKey: ethereum!.getPublicKey(wallet),
37 + privateKey: _getPrivateKeyForWallet(wallet),
38 + publicKey: _getPublicKeyForWallet(wallet),
39 ),
38 -
40 ];
41
42 late final WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
43
44 late final List<ChainKeyModel> _keys;
45
46 + static String _getPrivateKeyForWallet(WalletBase wallet) {
47 + switch (wallet.type) {
48 + case WalletType.ethereum:
49 + return ethereum!.getPrivateKey(wallet);
50 + case WalletType.polygon:
51 + return polygon!.getPrivateKey(wallet);
52 + default:
53 + return '';
54 + }
55 + }
56 +
57 + static String _getPublicKeyForWallet(WalletBase wallet) {
58 + switch (wallet.type) {
59 + case WalletType.ethereum:
60 + return ethereum!.getPublicKey(wallet);
61 + case WalletType.polygon:
62 + return polygon!.getPublicKey(wallet);
63 + default:
64 + return '';
65 + }
66 + }
67 @override
68 List<String> getChains() {
69 final List<String> chainIds = [];
lib/core/wallet_connect/web3wallet_service.dart
+7 -3
@@ -9,6 +9,7 @@ import 'package:cake_wallet/generated/i18n.dart';
9 import 'package:cake_wallet/core/wallet_connect/models/auth_request_model.dart';
10 import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
11 import 'package:cake_wallet/core/wallet_connect/models/session_request_model.dart';
12 +import 'package:cake_wallet/reactions/wallet_connect.dart';
13 import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_request_widget.dart';
14 import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart';
15 import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart';
@@ -164,8 +165,10 @@ abstract class Web3WalletServiceBase with Store {
165
166 void _onSessionProposal(SessionProposalEvent? args) async {
167 if (args != null) {
168 + final chaindIdNamespace = getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type);
169 final Widget modalWidget = Web3RequestModal(
170 child: ConnectionRequestWidget(
171 + chaindIdNamespace: chaindIdNamespace,
172 wallet: _web3Wallet,
173 sessionProposal: SessionRequestModel(request: args.params),
174 ),
@@ -232,12 +235,13 @@ abstract class Web3WalletServiceBase with Store {
235 @action
236 Future<void> _onAuthRequest(AuthRequest? args) async {
237 if (args != null) {
235 - List<ChainKeyModel> chainKeys = walletKeyService.getKeysForChain('eip155:1');
238 + final chaindIdNamespace = getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type);
239 + List<ChainKeyModel> chainKeys = walletKeyService.getKeysForChain(chaindIdNamespace);
240 // Create the message to be signed
237 - final String iss = 'did:pkh:eip155:1:${chainKeys.first.publicKey}';
238 -
241 + final String iss = 'did:pkh:$chaindIdNamespace:${chainKeys.first.publicKey}';
242 final Widget modalWidget = Web3RequestModal(
243 child: ConnectionRequestWidget(
244 + chaindIdNamespace: chaindIdNamespace,
245 wallet: _web3Wallet,
246 authRequest: AuthRequestModel(iss: iss, request: args),
247 ),
lib/di.dart
+5 -1
@@ -18,6 +18,8 @@ import 'package:cake_wallet/nano/nano.dart';
18 import 'package:cake_wallet/ionia/ionia_anypay.dart';
19 import 'package:cake_wallet/ionia/ionia_gift_card.dart';
20 import 'package:cake_wallet/ionia/ionia_tip.dart';
21 +import 'package:cake_wallet/polygon/polygon.dart';
22 +import 'package:cake_wallet/reactions/wallet_connect.dart';
23 import 'package:cake_wallet/routes.dart';
24 import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dart';
25 import 'package:cake_wallet/src/screens/buy/buy_options_page.dart';
@@ -750,7 +752,7 @@ Future<void> setup({
752 final wallet = getIt.get<AppStore>().wallet;
753 return ConnectionSyncPage(
754 getIt.get<DashboardViewModel>(),
753 - wallet?.type == WalletType.ethereum ? getIt.get<Web3WalletService>() : null,
755 + isEVMCompatibleChain(wallet!.type) ? getIt.get<Web3WalletService>() : null,
756 );
757 });
758
@@ -847,6 +849,8 @@ Future<void> setup({
849 .createBitcoinCashWalletService(_walletInfoSource, _unspentCoinsInfoSource!);
850 case WalletType.nano:
851 return nano!.createNanoWalletService(_walletInfoSource);
852 + case WalletType.polygon:
853 + return polygon!.createPolygonWalletService(_walletInfoSource);
854 default:
855 throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
856 }
lib/entities/default_settings_migration.dart
+39
@@ -27,6 +27,7 @@ const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
27 const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
28 const havenDefaultNodeUri = 'nodes.havenprotocol.org:443';
29 const ethereumDefaultNodeUri = 'ethereum.publicnode.com';
30 +const polygonDefaultNodeUri = 'polygon-bor.publicnode.com';
31 const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002';
32 const nanoDefaultNodeUri = 'rpc.nano.to';
33 const nanoDefaultPowNodeUri = 'rpc.nano.to';
@@ -65,6 +66,8 @@ Future<void> defaultSettingsMigration(
66 final migrationVersions =
67 List<int>.generate(migrationVersionsLength, (i) => currentVersion + (i + 1));
68
69 + /// When you add a new case, increase the initialMigrationVersion parameter in the main.dart file.
70 + /// This ensures that this switch case runs the newly added case.
71 await Future.forEach(migrationVersions, (int version) async {
72 try {
73 switch (version) {
@@ -175,6 +178,11 @@ Future<void> defaultSettingsMigration(
178 await changeBitcoinCurrentElectrumServerToDefault(
179 sharedPreferences: sharedPreferences, nodes: nodes);
180 break;
181 + case 24:
182 + await addPolygonNodeList(nodes: nodes);
183 + await changePolygonCurrentNodeToDefault(
184 + sharedPreferences: sharedPreferences, nodes: nodes);
185 + break;
186 case 25:
187 await rewriteSecureStoragePin(secureStorage: secureStorage);
188 break;
@@ -332,6 +340,11 @@ Node? getEthereumDefaultNode({required Box<Node> nodes}) {
340 nodes.values.firstWhereOrNull((node) => node.type == WalletType.ethereum);
341 }
342
343 +Node? getPolygonDefaultNode({required Box<Node> nodes}) {
344 + return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == polygonDefaultNodeUri) ??
345 + nodes.values.firstWhereOrNull((node) => node.type == WalletType.polygon);
346 +}
347 +
348 Node? getNanoDefaultNode({required Box<Node> nodes}) {
349 return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == nanoDefaultNodeUri) ??
350 nodes.values.firstWhereOrNull((node) => node.type == WalletType.nano);
@@ -575,6 +588,7 @@ Future<void> checkCurrentNodes(
588 sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
589 final currentHavenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
590 final currentEthereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
591 + final currentPolygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
592 final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
593 final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
594 final currentBitcoinCashNodeId =
@@ -589,6 +603,8 @@ Future<void> checkCurrentNodes(
603 nodeSource.values.firstWhereOrNull((node) => node.key == currentHavenNodeId);
604 final currentEthereumNodeServer =
605 nodeSource.values.firstWhereOrNull((node) => node.key == currentEthereumNodeId);
606 + final currentPolygonNodeServer =
607 + nodeSource.values.firstWhereOrNull((node) => node.key == currentPolygonNodeId);
608 final currentNanoNodeServer =
609 nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId);
610 final currentNanoPowNodeServer =
@@ -648,6 +664,12 @@ Future<void> checkCurrentNodes(
664 await nodeSource.add(node);
665 await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int);
666 }
667 +
668 + if (currentPolygonNodeServer == null) {
669 + final node = Node(uri: polygonDefaultNodeUri, type: WalletType.polygon);
670 + await nodeSource.add(node);
671 + await sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, node.key as int);
672 + }
673 }
674
675 Future<void> resetBitcoinElectrumServer(
@@ -742,3 +764,20 @@ Future<void> changeNanoCurrentPowNodeToDefault(
764 final nodeId = node?.key as int? ?? 0;
765 await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, nodeId);
766 }
767 +
768 +Future<void> addPolygonNodeList({required Box<Node> nodes}) async {
769 + final nodeList = await loadDefaultPolygonNodes();
770 + for (var node in nodeList) {
771 + if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
772 + await nodes.add(node);
773 + }
774 + }
775 +}
776 +
777 +Future<void> changePolygonCurrentNodeToDefault(
778 + {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
779 + final node = getPolygonDefaultNode(nodes: nodes);
780 + final nodeId = node?.key as int? ?? 0;
781 +
782 + await sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, nodeId);
783 +}
lib/entities/ens_record.dart
+6
@@ -1,4 +1,5 @@
1 import 'package:cake_wallet/ethereum/ethereum.dart';
2 +import 'package:cake_wallet/polygon/polygon.dart';
3 import 'package:cw_core/wallet_base.dart';
4 import 'package:cw_core/wallet_type.dart';
5 import 'package:ens_dart/ens_dart.dart';
@@ -12,6 +13,10 @@ class EnsRecord {
13 if (wallet != null && wallet.type == WalletType.ethereum) {
14 _client = ethereum!.getWeb3Client(wallet);
15 }
16 +
17 + if (wallet != null && wallet.type == WalletType.polygon) {
18 + _client = polygon!.getWeb3Client(wallet);
19 + }
20
21 if (_client == null) {
22 _client = Web3Client("https://ethereum.publicnode.com", Client());
@@ -31,6 +36,7 @@ class EnsRecord {
36 case WalletType.haven:
37 return await ens.withName(name).getCoinAddress(CoinType.XHV);
38 case WalletType.ethereum:
39 + case WalletType.polygon:
40 default:
41 return (await ens.withName(name).getAddress()).hex;
42 }
lib/entities/main_actions.dart
+4 -1
@@ -19,7 +19,8 @@ class MainActions {
19
20 final bool Function(DashboardViewModel viewModel)? isEnabled;
21 final bool Function(DashboardViewModel viewModel)? canShow;
22 - final Future<void> Function(BuildContext context, DashboardViewModel viewModel) onTap;
22 + final Future<void> Function(
23 + BuildContext context, DashboardViewModel viewModel) onTap;
24
25 MainActions._({
26 required this.name,
@@ -52,6 +53,7 @@ class MainActions {
53 case WalletType.bitcoin:
54 case WalletType.litecoin:
55 case WalletType.ethereum:
56 + case WalletType.polygon:
57 case WalletType.bitcoinCash:
58 switch (defaultBuyProvider) {
59 case BuyProviderType.AskEachTime:
@@ -124,6 +126,7 @@ class MainActions {
126 case WalletType.bitcoin:
127 case WalletType.litecoin:
128 case WalletType.ethereum:
129 + case WalletType.polygon:
130 case WalletType.bitcoinCash:
131 if (viewModel.isEnabledSellAction) {
132 final moonPaySellProvider = MoonPaySellProvider();
lib/entities/node_list.dart
+20 -1
@@ -133,6 +133,22 @@ Future<List<Node>> loadDefaultNanoPowNodes() async {
133 return nodes;
134 }
135
136 +Future<List<Node>> loadDefaultPolygonNodes() async {
137 + final nodesRaw = await rootBundle.loadString('assets/polygon_node_list.yml');
138 + final loadedNodes = loadYaml(nodesRaw) as YamlList;
139 + final nodes = <Node>[];
140 +
141 + for (final raw in loadedNodes) {
142 + if (raw is Map) {
143 + final node = Node.fromMap(Map<String, Object>.from(raw));
144 + node.type = WalletType.polygon;
145 + nodes.add(node);
146 + }
147 + }
148 +
149 + return nodes;
150 +}
151 +
152 Future<void> resetToDefault(Box<Node> nodeSource) async {
153 final moneroNodes = await loadDefaultNodes();
154 final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
@@ -141,6 +157,8 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
157 final havenNodes = await loadDefaultHavenNodes();
158 final ethereumNodes = await loadDefaultEthereumNodes();
159 final nanoNodes = await loadDefaultNanoNodes();
160 + final polygonNodes = await loadDefaultPolygonNodes();
161 +
162
163 final nodes = moneroNodes +
164 bitcoinElectrumServerList +
@@ -148,7 +166,8 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
166 havenNodes +
167 ethereumNodes +
168 bitcoinCashElectrumServerList +
151 - nanoNodes;
169 + nanoNodes +
170 + polygonNodes;
171
172 await nodeSource.clear();
173 await nodeSource.addAll(nodes);
lib/entities/preferences_key.dart
+3
@@ -6,6 +6,7 @@ class PreferencesKey {
6 static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc';
7 static const currentHavenNodeIdKey = 'current_node_id_xhv';
8 static const currentEthereumNodeIdKey = 'current_node_id_eth';
9 + static const currentPolygonNodeIdKey = 'current_node_id_matic';
10 static const currentNanoNodeIdKey = 'current_node_id_nano';
11 static const currentNanoPowNodeIdKey = 'current_node_id_nano_pow';
12 static const currentBananoNodeIdKey = 'current_node_id_banano';
@@ -37,6 +38,7 @@ class PreferencesKey {
38 static const havenTransactionPriority = 'current_fee_priority_haven';
39 static const litecoinTransactionPriority = 'current_fee_priority_litecoin';
40 static const ethereumTransactionPriority = 'current_fee_priority_ethereum';
41 + static const polygonTransactionPriority = 'current_fee_priority_polygon';
42 static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash';
43 static const shouldShowReceiveWarning = 'should_show_receive_warning';
44 static const shouldShowYatPopup = 'should_show_yat_popup';
@@ -50,6 +52,7 @@ class PreferencesKey {
52 static const sortBalanceBy = 'sort_balance_by';
53 static const pinNativeTokenAtTop = 'pin_native_token_at_top';
54 static const useEtherscan = 'use_etherscan';
55 + static const usePolygonScan = 'use_polygonscan';
56 static const defaultNanoRep = 'default_nano_representative';
57 static const defaultBananoRep = 'default_banano_representative';
58 static const lookupsTwitter = 'looks_up_twitter';
lib/entities/priority_for_wallet_type.dart
+3
@@ -3,6 +3,7 @@ import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
3 import 'package:cake_wallet/ethereum/ethereum.dart';
4 import 'package:cake_wallet/haven/haven.dart';
5 import 'package:cake_wallet/monero/monero.dart';
6 +import 'package:cake_wallet/polygon/polygon.dart';
7 import 'package:cw_core/transaction_priority.dart';
8 import 'package:cw_core/wallet_type.dart';
9
@@ -24,6 +25,8 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
25 case WalletType.nano:
26 case WalletType.banano:
27 return [];
28 + case WalletType.polygon:
29 + return polygon!.getTransactionPriorities();
30 default:
31 return [];
32 }
lib/polygon/cw_polygon.dart new
+156
@@ -0,0 +1,156 @@
1 +part of 'polygon.dart';
2 +
3 +class CWPolygon extends Polygon {
4 + @override
5 + List<String> getPolygonWordList(String language) => EthereumMnemonics.englishWordlist;
6 +
7 + WalletService createPolygonWalletService(Box<WalletInfo> walletInfoSource) =>
8 + PolygonWalletService(walletInfoSource);
9 +
10 + @override
11 + WalletCredentials createPolygonNewWalletCredentials({
12 + required String name,
13 + WalletInfo? walletInfo,
14 + }) =>
15 + PolygonNewWalletCredentials(name: name, walletInfo: walletInfo);
16 +
17 + @override
18 + WalletCredentials createPolygonRestoreWalletFromSeedCredentials({
19 + required String name,
20 + required String mnemonic,
21 + required String password,
22 + }) =>
23 + PolygonRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic);
24 +
25 + @override
26 + WalletCredentials createPolygonRestoreWalletFromPrivateKey({
27 + required String name,
28 + required String privateKey,
29 + required String password,
30 + }) =>
31 + PolygonRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
32 +
33 + @override
34 + String getAddress(WalletBase wallet) => (wallet as PolygonWallet).walletAddresses.address;
35 +
36 + @override
37 + String getPrivateKey(WalletBase wallet) {
38 + final privateKeyHolder = (wallet as PolygonWallet).polygonPrivateKey;
39 + String stringKey = bytesToHex(privateKeyHolder.privateKey);
40 + return stringKey;
41 + }
42 +
43 + @override
44 + String getPublicKey(WalletBase wallet) {
45 + final privateKeyInUnitInt = (wallet as PolygonWallet).polygonPrivateKey;
46 + final publicKey = privateKeyInUnitInt.address.hex;
47 + return publicKey;
48 + }
49 +
50 + @override
51 + TransactionPriority getDefaultTransactionPriority() => PolygonTransactionPriority.medium;
52 +
53 + @override
54 + TransactionPriority getPolygonTransactionPrioritySlow() => PolygonTransactionPriority.slow;
55 +
56 + @override
57 + List<TransactionPriority> getTransactionPriorities() => PolygonTransactionPriority.all;
58 +
59 + @override
60 + TransactionPriority deserializePolygonTransactionPriority(int raw) =>
61 + PolygonTransactionPriority.deserialize(raw: raw);
62 +
63 + Object createPolygonTransactionCredentials(
64 + List<Output> outputs, {
65 + required TransactionPriority priority,
66 + required CryptoCurrency currency,
67 + int? feeRate,
68 + }) =>
69 + PolygonTransactionCredentials(
70 + outputs
71 + .map((out) => OutputInfo(
72 + fiatAmount: out.fiatAmount,
73 + cryptoAmount: out.cryptoAmount,
74 + address: out.address,
75 + note: out.note,
76 + sendAll: out.sendAll,
77 + extractedAddress: out.extractedAddress,
78 + isParsedAddress: out.isParsedAddress,
79 + formattedCryptoAmount: out.formattedCryptoAmount))
80 + .toList(),
81 + priority: priority as PolygonTransactionPriority,
82 + currency: currency,
83 + feeRate: feeRate,
84 + );
85 +
86 + Object createPolygonTransactionCredentialsRaw(
87 + List<OutputInfo> outputs, {
88 + TransactionPriority? priority,
89 + required CryptoCurrency currency,
90 + required int feeRate,
91 + }) =>
92 + PolygonTransactionCredentials(
93 + outputs,
94 + priority: priority as PolygonTransactionPriority?,
95 + currency: currency,
96 + feeRate: feeRate,
97 + );
98 +
99 + @override
100 + int formatterPolygonParseAmount(String amount) => PolygonFormatter.parsePolygonAmount(amount);
101 +
102 + @override
103 + double formatterPolygonAmountToDouble(
104 + {TransactionInfo? transaction, BigInt? amount, int exponent = 18}) {
105 + assert(transaction != null || amount != null);
106 +
107 + if (transaction != null) {
108 + transaction as PolygonTransactionInfo;
109 + return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
110 + } else {
111 + return (amount!) / BigInt.from(10).pow(exponent);
112 + }
113 + }
114 +
115 + @override
116 + List<Erc20Token> getERC20Currencies(WalletBase wallet) {
117 + final polygonWallet = wallet as PolygonWallet;
118 + return polygonWallet.erc20Currencies;
119 + }
120 +
121 + @override
122 + Future<void> addErc20Token(WalletBase wallet, Erc20Token token) async =>
123 + await (wallet as PolygonWallet).addErc20Token(token);
124 +
125 + @override
126 + Future<void> deleteErc20Token(WalletBase wallet, Erc20Token token) async =>
127 + await (wallet as PolygonWallet).deleteErc20Token(token);
128 +
129 + @override
130 + Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) async {
131 + final polygonWallet = wallet as PolygonWallet;
132 + return await polygonWallet.getErc20Token(contractAddress);
133 + }
134 +
135 + @override
136 + CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
137 + transaction as PolygonTransactionInfo;
138 + if (transaction.tokenSymbol == CryptoCurrency.maticpoly.title) {
139 + return CryptoCurrency.maticpoly;
140 + }
141 +
142 + wallet as PolygonWallet;
143 + return wallet.erc20Currencies.firstWhere(
144 + (element) => transaction.tokenSymbol.toLowerCase() == element.symbol.toLowerCase());
145 + }
146 +
147 + @override
148 + void updatePolygonScanUsageState(WalletBase wallet, bool isEnabled) {
149 + (wallet as PolygonWallet).updatePolygonScanUsageState(isEnabled);
150 + }
151 +
152 + @override
153 + Web3Client? getWeb3Client(WalletBase wallet) {
154 + return (wallet as PolygonWallet).getWeb3Client();
155 + }
156 +}
lib/reactions/fiat_rate_update.dart
+12 -2
@@ -3,9 +3,11 @@ import 'package:cake_wallet/core/fiat_conversion_service.dart';
3 import 'package:cake_wallet/entities/fiat_api_mode.dart';
4 import 'package:cake_wallet/entities/update_haven_rate.dart';
5 import 'package:cake_wallet/ethereum/ethereum.dart';
6 +import 'package:cake_wallet/polygon/polygon.dart';
7 import 'package:cake_wallet/store/app_store.dart';
8 import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
9 import 'package:cake_wallet/store/settings_store.dart';
10 +import 'package:cw_core/erc20_token.dart';
11 import 'package:cw_core/wallet_type.dart';
12 import 'package:mobx/mobx.dart';
13
@@ -33,10 +35,18 @@ Future<void> startFiatRateUpdate(
35 torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
36 }
37
38 + Iterable<Erc20Token>? currencies;
39 if (appStore.wallet!.type == WalletType.ethereum) {
37 - final currencies =
38 - ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
40 + currencies =
41 + ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
42 + }
43 +
44 + if (appStore.wallet!.type == WalletType.polygon) {
45 + currencies =
46 + polygon!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
47 + }
48
49 + if (currencies != null) {
50 for (final currency in currencies) {
51 () async {
52 fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice(
lib/reactions/on_current_wallet_change.dart
+10 -2
@@ -2,7 +2,8 @@ import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
2 import 'package:cake_wallet/entities/fiat_api_mode.dart';
3 import 'package:cake_wallet/entities/update_haven_rate.dart';
4 import 'package:cake_wallet/ethereum/ethereum.dart';
5 -import 'package:cake_wallet/nano/nano.dart';
5 +import 'package:cake_wallet/polygon/polygon.dart';
6 +import 'package:cw_core/erc20_token.dart';
7 import 'package:cw_core/transaction_history.dart';
8 import 'package:cw_core/balance.dart';
9 import 'package:cw_core/transaction_info.dart';
@@ -107,10 +108,17 @@ void startCurrentWalletChangeReaction(
108 fiat: settingsStore.fiatCurrency,
109 torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
110
111 + Iterable<Erc20Token>? currencies;
112 if (wallet.type == WalletType.ethereum) {
111 - final currencies =
113 + currencies =
114 ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
115 + }
116 + if (wallet.type == WalletType.polygon) {
117 + currencies =
118 + polygon!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
119 + }
120
121 + if (currencies != null) {
122 for (final currency in currencies) {
123 () async {
124 fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice(
lib/reactions/wallet_connect.dart new
+46
@@ -0,0 +1,46 @@
1 +import 'package:cake_wallet/core/wallet_connect/evm_chain_id.dart';
2 +import 'package:cw_core/wallet_type.dart';
3 +
4 +bool isEVMCompatibleChain(WalletType walletType) {
5 + switch (walletType) {
6 + case WalletType.polygon:
7 + case WalletType.ethereum:
8 + return true;
9 + default:
10 + return false;
11 + }
12 +}
13 +
14 +String getChainNameSpaceAndIdBasedOnWalletType(WalletType walletType) {
15 + switch (walletType) {
16 + case WalletType.ethereum:
17 + return EVMChainId.ethereum.chain();
18 + case WalletType.polygon:
19 + return EVMChainId.polygon.chain();
20 + default:
21 + return '';
22 + }
23 +}
24 +
25 +int getChainIdBasedOnWalletType(WalletType walletType) {
26 + switch (walletType) {
27 + case WalletType.polygon:
28 + return 137;
29 +
30 + // For now, we return eth chain Id as the default, we'll modify as we add more wallets
31 + case WalletType.ethereum:
32 + default:
33 + return 1;
34 + }
35 +}
36 +
37 +String getChainNameBasedOnWalletType(WalletType walletType) {
38 + switch (walletType) {
39 + case WalletType.ethereum:
40 + return 'eth';
41 + case WalletType.polygon:
42 + return 'polygon';
43 + default:
44 + return '';
45 + }
46 +}
lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart
+3
@@ -33,6 +33,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
33 final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
34 final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
35 final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
36 + final polygonIcon = Image.asset('assets/images/matic_icon.png', height: 24, width: 24);
37 final bitcoinCashIcon = Image.asset('assets/images/bch_icon.png', height: 24, width: 24);
38 final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
39 final bananoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
@@ -150,6 +151,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
151 return nanoIcon;
152 case WalletType.banano:
153 return bananoIcon;
154 + case WalletType.polygon:
155 + return polygonIcon;
156 default:
157 return nonWalletTypeIcon;
158 }
lib/src/screens/dashboard/pages/balance_page.dart
+5 -5
@@ -1,5 +1,6 @@
1 import 'package:auto_size_text/auto_size_text.dart';
2 import 'package:cake_wallet/generated/i18n.dart';
3 +import 'package:cake_wallet/reactions/wallet_connect.dart';
4 import 'package:cake_wallet/routes.dart';
5 import 'package:cake_wallet/src/screens/dashboard/pages/nft_listing_page.dart';
6 import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.dart';
@@ -13,7 +14,6 @@ import 'package:cake_wallet/utils/feature_flag.dart';
14 import 'package:cake_wallet/utils/show_pop_up.dart';
15 import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
16 import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
16 -import 'package:cw_core/wallet_type.dart';
17 import 'package:flutter/material.dart';
18 import 'package:flutter_mobx/flutter_mobx.dart';
19
@@ -32,12 +32,12 @@ class BalancePage extends StatelessWidget {
32 Widget build(BuildContext context) {
33 return Observer(
34 builder: (context) {
35 - final isEthereumWallet = dashboardViewModel.type == WalletType.ethereum;
35 + final isEVMCompatible = isEVMCompatibleChain(dashboardViewModel.type);
36 return DefaultTabController(
37 - length: isEthereumWallet ? 2 : 1,
37 + length: isEVMCompatible ? 2 : 1,
38 child: Column(
39 children: [
40 - if (isEthereumWallet)
40 + if (isEVMCompatible)
41 Align(
42 alignment: Alignment.centerLeft,
43 child: Padding(
@@ -66,7 +66,7 @@ class BalancePage extends StatelessWidget {
66 physics: NeverScrollableScrollPhysics(),
67 children: [
68 CryptoBalanceWidget(dashboardViewModel: dashboardViewModel),
69 - if (isEthereumWallet) NFTListingPage(nftViewModel: nftViewModel)
69 + if (isEVMCompatible) NFTListingPage(nftViewModel: nftViewModel)
70 ],
71 ),
72 ),
lib/src/screens/dashboard/widgets/menu_widget.dart
+6 -1
@@ -32,7 +32,8 @@ class MenuWidgetState extends State<MenuWidget> {
32 this.ethereumIcon = Image.asset('assets/images/eth_icon.png'),
33 this.nanoIcon = Image.asset('assets/images/nano_icon.png'),
34 this.bananoIcon = Image.asset('assets/images/nano_icon.png'),
35 - this.bitcoinCashIcon = Image.asset('assets/images/bch_icon.png');
35 + this.bitcoinCashIcon = Image.asset('assets/images/bch_icon.png'),
36 + this.polygonIcon = Image.asset('assets/images/matic_icon.png');
37
38
39 final largeScreen = 731;
@@ -54,6 +55,8 @@ class MenuWidgetState extends State<MenuWidget> {
55 Image bitcoinCashIcon;
56 Image nanoIcon;
57 Image bananoIcon;
58 + Image polygonIcon;
59 +
60
61 @override
62 void initState() {
@@ -219,6 +222,8 @@ class MenuWidgetState extends State<MenuWidget> {
222 return nanoIcon;
223 case WalletType.banano:
224 return bananoIcon;
225 + case WalletType.polygon:
226 + return polygonIcon;
227 default:
228 throw Exception('No icon for ${type.toString()}');
229 }
lib/src/screens/receive/widgets/currency_input_field.dart
+10 -2
@@ -1,5 +1,6 @@
1 import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
2 import 'package:cake_wallet/utils/responsive_layout_util.dart';
3 +import 'package:cw_core/crypto_currency.dart';
4 import 'package:cw_core/currency.dart';
5 import 'package:flutter/material.dart';
6 import 'package:flutter/services.dart';
@@ -23,6 +24,13 @@ class CurrencyInputField extends StatelessWidget {
24 final TextEditingController controller;
25 final bool isLight;
26
27 + String get _currencyName {
28 + if (selectedCurrency is CryptoCurrency) {
29 + return (selectedCurrency as CryptoCurrency).title.toUpperCase();
30 + }
31 + return selectedCurrency.name.toUpperCase();
32 + }
33 +
34 @override
35 Widget build(BuildContext context) {
36 final arrowBottomPurple = Image.asset(
@@ -74,7 +82,7 @@ class CurrencyInputField extends StatelessWidget {
82 child: arrowBottomPurple,
83 ),
84 Text(
77 - selectedCurrency.name.toUpperCase(),
85 + _currencyName,
86 style: TextStyle(
87 fontWeight: FontWeight.w600,
88 fontSize: 16,
@@ -83,7 +91,7 @@ class CurrencyInputField extends StatelessWidget {
91 ),
92 if (selectedCurrency.tag != null)
93 Padding(
86 - padding: const EdgeInsets.only(right: 3.0),
94 + padding: const EdgeInsets.symmetric(horizontal: 3.0),
95 child: Container(
96 decoration: BoxDecoration(
97 color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
lib/src/screens/root/root.dart
+4 -4
@@ -2,9 +2,9 @@ import 'dart:async';
2 import 'package:cake_wallet/core/auth_service.dart';
3 import 'package:cake_wallet/core/totp_request_details.dart';
4 import 'package:cake_wallet/generated/i18n.dart';
5 +import 'package:cake_wallet/reactions/wallet_connect.dart';
6 import 'package:cake_wallet/utils/device_info.dart';
7 import 'package:cake_wallet/utils/payment_request.dart';
7 -import 'package:cw_core/wallet_type.dart';
8 import 'package:flutter/material.dart';
9 import 'package:cake_wallet/routes.dart';
10 import 'package:cake_wallet/src/screens/auth/auth_page.dart';
@@ -169,7 +169,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
169 );
170 launchUri = null;
171 } else if (isWalletConnectLink) {
172 - if (widget.appStore.wallet!.type == WalletType.ethereum) {
172 + if (isEVMCompatibleChain(widget.appStore.wallet!.type)) {
173 widget.navigatorKey.currentState?.pushNamed(
174 Routes.walletConnectConnectionsListing,
175 arguments: launchUri,
@@ -179,7 +179,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
179 _nonETHWalletErrorToast(S.current.switchToETHWallet);
180 }
181 }
182 -
182 +
183 launchUri = null;
184 return WillPopScope(
185 onWillPop: () async => false,
@@ -205,7 +205,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
205
206 String? _getRouteToGo() {
207 if (isWalletConnectLink) {
208 - if (widget.appStore.wallet!.type != WalletType.ethereum) {
208 + if (isEVMCompatibleChain(widget.appStore.wallet!.type)) {
209 _nonETHWalletErrorToast(S.current.switchToETHWallet);
210 return null;
211 }
lib/src/screens/seed/pre_seed_page.dart
+11 -7
@@ -13,7 +13,8 @@ class PreSeedPage extends BasePage {
13 PreSeedPage(this.type, this.advancedPrivacySettingsViewModel)
14 : imageLight = Image.asset('assets/images/pre_seed_light.png'),
15 imageDark = Image.asset('assets/images/pre_seed_dark.png'),
16 - seedPhraseLength = advancedPrivacySettingsViewModel.seedPhraseLength.value {
16 + seedPhraseLength =
17 + advancedPrivacySettingsViewModel.seedPhraseLength.value {
18 wordsCount = _wordsCount(type, seedPhraseLength);
19 }
20
@@ -40,14 +41,14 @@ class PreSeedPage extends BasePage {
41 alignment: Alignment.center,
42 padding: EdgeInsets.all(24),
43 child: ConstrainedBox(
43 - constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
44 + constraints: BoxConstraints(
45 + maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
46 child: Column(
47 mainAxisAlignment: MainAxisAlignment.spaceBetween,
48 children: <Widget>[
49 ConstrainedBox(
50 constraints: BoxConstraints(
49 - maxHeight: MediaQuery.of(context).size.height * 0.3
50 - ),
51 + maxHeight: MediaQuery.of(context).size.height * 0.3),
52 child: AspectRatio(aspectRatio: 1, child: image),
53 ),
54 Padding(
@@ -58,12 +59,14 @@ class PreSeedPage extends BasePage {
59 style: TextStyle(
60 fontSize: 14,
61 fontWeight: FontWeight.normal,
61 - color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
62 + color: Theme.of(context)
63 + .extension<CakeTextTheme>()!
64 + .secondaryTextColor),
65 ),
66 ),
67 PrimaryButton(
65 - onPressed: () =>
66 - Navigator.of(context).popAndPushNamed(Routes.seed, arguments: true),
68 + onPressed: () => Navigator.of(context)
69 + .popAndPushNamed(Routes.seed, arguments: true),
70 text: S.of(context).pre_seed_button_text,
71 color: Theme.of(context).primaryColor,
72 textColor: Colors.white)
@@ -79,6 +82,7 @@ class PreSeedPage extends BasePage {
82 return 25;
83 case WalletType.ethereum:
84 case WalletType.bitcoinCash:
85 + case WalletType.polygon:
86 return seedPhraseLength;
87 default:
88 return 24;
lib/src/screens/settings/connection_sync_page.dart
+3 -2
@@ -1,4 +1,5 @@
1 import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
2 +import 'package:cake_wallet/reactions/wallet_connect.dart';
3 import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
4 import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
5 import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
@@ -8,7 +9,6 @@ import 'package:cake_wallet/utils/feature_flag.dart';
9 import 'package:cake_wallet/utils/show_pop_up.dart';
10 import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
11 import 'package:cake_wallet/view_model/settings/sync_mode.dart';
11 -import 'package:cw_core/wallet_type.dart';
12 import 'package:flutter/material.dart';
13 import 'package:cake_wallet/routes.dart';
14 import 'package:cake_wallet/generated/i18n.dart';
@@ -85,7 +85,7 @@ class ConnectionSyncPage extends BasePage {
85 );
86 },
87 ),
88 - if (dashboardViewModel.wallet.type == WalletType.ethereum) ...[
88 + if (isEVMCompatibleChain(dashboardViewModel.wallet.type)) ...[
89 WalletConnectTile(
90 onTap: () => Navigator.of(context).pushNamed(Routes.walletConnectConnectionsListing),
91 ),
@@ -101,6 +101,7 @@ class ConnectionSyncPage extends BasePage {
101 );
102 }
103
104 +
105 Future<void> _presentReconnectAlert(BuildContext context) async {
106 await showPopUp<void>(
107 context: context,
lib/src/screens/settings/privacy_page.dart
+8
@@ -87,6 +87,14 @@ class PrivacyPage extends BasePage {
87 onValueChange: (BuildContext _, bool value) {
88 _privacySettingsViewModel.setUseEtherscan(value);
89 }),
90 + if (_privacySettingsViewModel.canUsePolygonScan)
91 + SettingsSwitcherCell(
92 + title: S.current.polygonscan_history,
93 + value: _privacySettingsViewModel.usePolygonScan,
94 + onValueChange: (BuildContext _, bool value) {
95 + _privacySettingsViewModel.setUsePolygonScan(value);
96 + },
97 + ),
98 SettingsCellWithArrow(
99 title: S.current.domain_looks_up,
100 handler: (context) => Navigator.of(context).pushNamed(Routes.domainLookupsPage),
lib/src/screens/wallet_connect/widgets/connection_request_widget.dart
+20 -6
@@ -14,12 +14,14 @@ import 'connection_widget.dart';
14 class ConnectionRequestWidget extends StatefulWidget {
15 const ConnectionRequestWidget({
16 required this.wallet,
17 + required this.chaindIdNamespace,
18 this.authRequest,
19 this.sessionProposal,
20 Key? key,
21 }) : super(key: key);
22
23 final Web3Wallet wallet;
24 + final String chaindIdNamespace;
25 final AuthRequestModel? authRequest;
26 final SessionRequestModel? sessionProposal;
27
@@ -52,23 +54,26 @@ class _ConnectionRequestWidgetState extends State<ConnectionRequestWidget> {
54
55 return _ConnectionMetadataDisplayWidget(
56 metadata: metadata,
57 + wallet: widget.wallet,
58 authRequest: widget.authRequest,
59 sessionProposal: widget.sessionProposal,
57 - wallet: widget.wallet,
60 + chaindIdNamespace: widget.chaindIdNamespace,
61 );
62 }
63 }
64
65 class _ConnectionMetadataDisplayWidget extends StatelessWidget {
66 const _ConnectionMetadataDisplayWidget({
64 - required this.metadata,
67 required this.wallet,
66 - this.authRequest,
68 + required this.metadata,
69 required this.sessionProposal,
70 + required this.chaindIdNamespace,
71 + this.authRequest,
72 });
73
74 final ConnectionMetadata? metadata;
75 final Web3Wallet wallet;
76 + final String chaindIdNamespace;
77 final AuthRequestModel? authRequest;
78 final SessionRequestModel? sessionProposal;
79
@@ -114,7 +119,11 @@ class _ConnectionMetadataDisplayWidget extends StatelessWidget {
119 const SizedBox(height: 8),
120 Visibility(
121 visible: authRequest != null,
117 - child: _AuthRequestWidget(wallet: wallet, authRequest: authRequest),
122 + child: _AuthRequestWidget(
123 + wallet: wallet,
124 + authRequest: authRequest,
125 + chaindIdNamespace: chaindIdNamespace,
126 + ),
127
128 //If authRequest is null, sessionProposal is not null.
129 replacement: _SessionProposalWidget(sessionProposal: sessionProposal!),
@@ -126,16 +135,21 @@ class _ConnectionMetadataDisplayWidget extends StatelessWidget {
135 }
136
137 class _AuthRequestWidget extends StatelessWidget {
129 - const _AuthRequestWidget({required this.wallet, this.authRequest});
138 + const _AuthRequestWidget({
139 + required this.wallet,
140 + required this.chaindIdNamespace,
141 + this.authRequest,
142 + });
143
144 final Web3Wallet wallet;
145 + final String chaindIdNamespace;
146 final AuthRequestModel? authRequest;
147
148 @override
149 Widget build(BuildContext context) {
150 final model = ConnectionModel(
151 text: wallet.formatAuthMessage(
138 - iss: 'did:pkh:eip155:1:${authRequest!.iss}',
152 + iss: 'did:pkh:$chaindIdNamespace:${authRequest!.iss}',
153 cacaoPayload: CacaoRequestPayload.fromPayloadParams(
154 authRequest!.request.payloadParams,
155 ),
lib/src/screens/wallet_list/wallet_list_page.dart
+3
@@ -49,6 +49,7 @@ class WalletListBodyState extends State<WalletListBody> {
49 final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
50 final bitcoinCashIcon = Image.asset('assets/images/bch_icon.png', height: 24, width: 24);
51 final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
52 + final polygonIcon = Image.asset('assets/images/matic_icon.png', height: 24, width: 24);
53 final scrollController = ScrollController();
54 final double tileHeight = 60;
55 Flushbar<void>? _progressBar;
@@ -256,6 +257,8 @@ class WalletListBodyState extends State<WalletListBody> {
257 return bitcoinCashIcon;
258 case WalletType.nano:
259 return nanoIcon;
260 + case WalletType.polygon:
261 + return polygonIcon;
262 default:
263 return nonWalletTypeIcon;
264 }
lib/store/app_store.dart
+2 -2
@@ -1,8 +1,8 @@
1 import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
2 import 'package:cake_wallet/di.dart';
3 +import 'package:cake_wallet/reactions/wallet_connect.dart';
4 import 'package:cake_wallet/utils/exception_handler.dart';
5 import 'package:cw_core/transaction_info.dart';
5 -import 'package:cw_core/wallet_type.dart';
6 import 'package:mobx/mobx.dart';
7 import 'package:cw_core/balance.dart';
8 import 'package:cw_core/wallet_base.dart';
@@ -44,7 +44,7 @@ abstract class AppStoreBase with Store {
44 this.wallet = wallet;
45 this.wallet!.setExceptionHandler(ExceptionHandler.onError);
46
47 - if (wallet.type == WalletType.ethereum) {
47 + if (isEVMCompatibleChain(wallet.type)) {
48 getIt.get<Web3WalletService>().init();
49 }
50 }
lib/store/settings_store.dart
+111 -63
@@ -12,6 +12,7 @@ import 'package:cake_wallet/entities/preferences_key.dart';
12 import 'package:cake_wallet/entities/seed_phrase_length.dart';
13 import 'package:cake_wallet/entities/seed_type.dart';
14 import 'package:cake_wallet/entities/sort_balance_types.dart';
15 +import 'package:cake_wallet/polygon/polygon.dart';
16 import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
17 import 'package:cake_wallet/view_model/settings/sync_mode.dart';
18 import 'package:cake_wallet/utils/device_info.dart';
@@ -88,6 +89,7 @@ abstract class SettingsStoreBase with Store {
89 required this.sortBalanceBy,
90 required this.pinNativeTokenAtTop,
91 required this.useEtherscan,
92 + required this.usePolygonScan,
93 required this.defaultNanoRep,
94 required this.defaultBananoRep,
95 required this.lookupsTwitter,
@@ -101,6 +103,7 @@ abstract class SettingsStoreBase with Store {
103 TransactionPriority? initialHavenTransactionPriority,
104 TransactionPriority? initialLitecoinTransactionPriority,
105 TransactionPriority? initialEthereumTransactionPriority,
106 + TransactionPriority? initialPolygonTransactionPriority,
107 TransactionPriority? initialBitcoinCashTransactionPriority})
108 : nodes = ObservableMap<WalletType, Node>.of(nodes),
109 powNodes = ObservableMap<WalletType, Node>.of(powNodes),
@@ -165,6 +168,10 @@ abstract class SettingsStoreBase with Store {
168 priority[WalletType.ethereum] = initialEthereumTransactionPriority;
169 }
170
171 + if (initialPolygonTransactionPriority != null) {
172 + priority[WalletType.polygon] = initialPolygonTransactionPriority;
173 + }
174 +
175 if (initialBitcoinCashTransactionPriority != null) {
176 priority[WalletType.bitcoinCash] = initialBitcoinCashTransactionPriority;
177 }
@@ -202,6 +209,9 @@ abstract class SettingsStoreBase with Store {
209 case WalletType.bitcoinCash:
210 key = PreferencesKey.bitcoinCashTransactionPriority;
211 break;
212 + case WalletType.polygon:
213 + key = PreferencesKey.polygonTransactionPriority;
214 + break;
215 default:
216 key = null;
217 }
@@ -245,8 +255,8 @@ abstract class SettingsStoreBase with Store {
255
256 reaction(
257 (_) => moneroSeedType,
248 - (SeedType moneroSeedType) => sharedPreferences.setInt(
249 - PreferencesKey.moneroSeedType, moneroSeedType.raw));
258 + (SeedType moneroSeedType) =>
259 + sharedPreferences.setInt(PreferencesKey.moneroSeedType, moneroSeedType.raw));
260
261 reaction(
262 (_) => fiatApiMode,
@@ -342,9 +352,9 @@ abstract class SettingsStoreBase with Store {
352 sharedPreferences.setString(PreferencesKey.currentLanguageCode, languageCode));
353
354 reaction(
345 - (_) => seedPhraseLength,
346 - (SeedPhraseLength seedPhraseWordCount) =>
347 - sharedPreferences.setInt(PreferencesKey.currentSeedPhraseLength, seedPhraseWordCount.value));
355 + (_) => seedPhraseLength,
356 + (SeedPhraseLength seedPhraseWordCount) => sharedPreferences.setInt(
357 + PreferencesKey.currentSeedPhraseLength, seedPhraseWordCount.value));
358
359 reaction(
360 (_) => pinTimeOutDuration,
@@ -388,6 +398,11 @@ abstract class SettingsStoreBase with Store {
398 (bool useEtherscan) =>
399 _sharedPreferences.setBool(PreferencesKey.useEtherscan, useEtherscan));
400
401 + reaction(
402 + (_) => usePolygonScan,
403 + (bool usePolygonScan) =>
404 + _sharedPreferences.setBool(PreferencesKey.usePolygonScan, usePolygonScan));
405 +
406 reaction((_) => defaultNanoRep,
407 (String nanoRep) => _sharedPreferences.setString(PreferencesKey.defaultNanoRep, nanoRep));
408
@@ -396,34 +411,32 @@ abstract class SettingsStoreBase with Store {
411 (String bananoRep) =>
412 _sharedPreferences.setString(PreferencesKey.defaultBananoRep, bananoRep));
413 reaction(
399 - (_) => lookupsTwitter,
400 - (bool looksUpTwitter) =>
414 + (_) => lookupsTwitter,
415 + (bool looksUpTwitter) =>
416 _sharedPreferences.setBool(PreferencesKey.lookupsTwitter, looksUpTwitter));
417
418 reaction(
404 - (_) => lookupsMastodon,
405 - (bool looksUpMastodon) =>
419 + (_) => lookupsMastodon,
420 + (bool looksUpMastodon) =>
421 _sharedPreferences.setBool(PreferencesKey.lookupsMastodon, looksUpMastodon));
422
423 reaction(
409 - (_) => lookupsYatService,
410 - (bool looksUpYatService) =>
424 + (_) => lookupsYatService,
425 + (bool looksUpYatService) =>
426 _sharedPreferences.setBool(PreferencesKey.lookupsYatService, looksUpYatService));
427
428 reaction(
414 - (_) => lookupsUnstoppableDomains,
415 - (bool looksUpUnstoppableDomains) =>
416 - _sharedPreferences.setBool(PreferencesKey.lookupsUnstoppableDomains, looksUpUnstoppableDomains));
429 + (_) => lookupsUnstoppableDomains,
430 + (bool looksUpUnstoppableDomains) => _sharedPreferences.setBool(
431 + PreferencesKey.lookupsUnstoppableDomains, looksUpUnstoppableDomains));
432
433 reaction(
419 - (_) => lookupsOpenAlias,
420 - (bool looksUpOpenAlias) =>
434 + (_) => lookupsOpenAlias,
435 + (bool looksUpOpenAlias) =>
436 _sharedPreferences.setBool(PreferencesKey.lookupsOpenAlias, looksUpOpenAlias));
437
423 - reaction(
424 - (_) => lookupsENS,
425 - (bool looksUpENS) =>
426 - _sharedPreferences.setBool(PreferencesKey.lookupsENS, looksUpENS));
438 + reaction((_) => lookupsENS,
439 + (bool looksUpENS) => _sharedPreferences.setBool(PreferencesKey.lookupsENS, looksUpENS));
440
441 this.nodes.observe((change) {
442 if (change.newValue != null && change.key != null) {
@@ -562,6 +575,9 @@ abstract class SettingsStoreBase with Store {
575 @observable
576 bool useEtherscan;
577
578 + @observable
579 + bool usePolygonScan;
580 +
581 @observable
582 String defaultNanoRep;
583
@@ -651,6 +667,7 @@ abstract class SettingsStoreBase with Store {
667 TransactionPriority? havenTransactionPriority;
668 TransactionPriority? litecoinTransactionPriority;
669 TransactionPriority? ethereumTransactionPriority;
670 + TransactionPriority? polygonTransactionPriority;
671 TransactionPriority? bitcoinCashTransactionPriority;
672
673 if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) {
@@ -662,9 +679,13 @@ abstract class SettingsStoreBase with Store {
679 sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!);
680 }
681 if (sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority) != null) {
665 - ethereumTransactionPriority = bitcoin?.deserializeLitecoinTransactionPriority(
682 + ethereumTransactionPriority = ethereum?.deserializeEthereumTransactionPriority(
683 sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!);
684 }
685 + if (sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority) != null) {
686 + polygonTransactionPriority = polygon?.deserializePolygonTransactionPriority(
687 + sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority)!);
688 + }
689 if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) {
690 bitcoinCashTransactionPriority = bitcoinCash?.deserializeBitcoinCashTransactionPriority(
691 sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!);
@@ -676,6 +697,7 @@ abstract class SettingsStoreBase with Store {
697 litecoinTransactionPriority ??= bitcoin?.getLitecoinTransactionPriorityMedium();
698 ethereumTransactionPriority ??= ethereum?.getDefaultTransactionPriority();
699 bitcoinCashTransactionPriority ??= bitcoinCash?.getDefaultTransactionPriority();
700 + polygonTransactionPriority ??= polygon?.getDefaultTransactionPriority();
701
702 final currentBalanceDisplayMode = BalanceDisplayMode.deserialize(
703 raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!);
@@ -749,12 +771,14 @@ abstract class SettingsStoreBase with Store {
771 final pinNativeTokenAtTop =
772 sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true;
773 final useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
774 + final usePolygonScan = sharedPreferences.getBool(PreferencesKey.usePolygonScan) ?? true;
775 final defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? "";
776 final defaultBananoRep = sharedPreferences.getString(PreferencesKey.defaultBananoRep) ?? "";
777 final lookupsTwitter = sharedPreferences.getBool(PreferencesKey.lookupsTwitter) ?? true;
778 final lookupsMastodon = sharedPreferences.getBool(PreferencesKey.lookupsMastodon) ?? true;
779 final lookupsYatService = sharedPreferences.getBool(PreferencesKey.lookupsYatService) ?? true;
757 - final lookupsUnstoppableDomains = sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true;
780 + final lookupsUnstoppableDomains =
781 + sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true;
782 final lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
783 final lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
784
@@ -774,6 +798,7 @@ abstract class SettingsStoreBase with Store {
798 sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
799 final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
800 final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
801 + final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
802 final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
803 final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
804 final moneroNode = nodeSource.get(nodeId);
@@ -781,6 +806,7 @@ abstract class SettingsStoreBase with Store {
806 final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
807 final havenNode = nodeSource.get(havenNodeId);
808 final ethereumNode = nodeSource.get(ethereumNodeId);
809 + final polygonNode = nodeSource.get(polygonNodeId);
810 final bitcoinCashElectrumServer = nodeSource.get(bitcoinCashElectrumServerId);
811 final nanoNode = nodeSource.get(nanoNodeId);
812 final nanoPowNode = powNodeSource.get(nanoPowNodeId);
@@ -824,6 +850,10 @@ abstract class SettingsStoreBase with Store {
850 nodes[WalletType.ethereum] = ethereumNode;
851 }
852
853 + if (polygonNode != null) {
854 + nodes[WalletType.polygon] = polygonNode;
855 + }
856 +
857 if (bitcoinCashElectrumServer != null) {
858 nodes[WalletType.bitcoinCash] = bitcoinCashElectrumServer;
859 }
@@ -841,19 +871,19 @@ abstract class SettingsStoreBase with Store {
871 });
872 final savedSyncAll = sharedPreferences.getBool(PreferencesKey.syncAllKey) ?? true;
873
844 - return SettingsStore(
845 - sharedPreferences: sharedPreferences,
846 - initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
847 - nodes: nodes,
848 - powNodes: powNodes,
849 - appVersion: packageInfo.version,
850 - deviceName: deviceName,
851 - isBitcoinBuyEnabled: isBitcoinBuyEnabled,
852 - initialFiatCurrency: currentFiatCurrency,
853 - initialBalanceDisplayMode: currentBalanceDisplayMode,
854 - initialSaveRecipientAddress: shouldSaveRecipientAddress,
855 - initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus,
856 - initialMoneroSeedType: moneroSeedType,
874 + return SettingsStore(
875 + sharedPreferences: sharedPreferences,
876 + initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
877 + nodes: nodes,
878 + powNodes: powNodes,
879 + appVersion: packageInfo.version,
880 + deviceName: deviceName,
881 + isBitcoinBuyEnabled: isBitcoinBuyEnabled,
882 + initialFiatCurrency: currentFiatCurrency,
883 + initialBalanceDisplayMode: currentBalanceDisplayMode,
884 + initialSaveRecipientAddress: shouldSaveRecipientAddress,
885 + initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus,
886 + initialMoneroSeedType: moneroSeedType,
887 initialAppSecure: isAppSecure,
888 initialDisableBuy: disableBuy,
889 initialDisableSell: disableSell,
@@ -869,42 +899,45 @@ abstract class SettingsStoreBase with Store {
899 actionlistDisplayMode: actionListDisplayMode,
900 initialPinLength: pinLength,
901 pinTimeOutDuration: pinCodeTimeOutDuration,
872 - seedPhraseLength: seedPhraseWordCount,initialLanguageCode: savedLanguageCode,
902 + seedPhraseLength: seedPhraseWordCount,
903 + initialLanguageCode: savedLanguageCode,
904 sortBalanceBy: sortBalanceBy,
905 pinNativeTokenAtTop: pinNativeTokenAtTop,
906 useEtherscan: useEtherscan,
907 + usePolygonScan: usePolygonScan,
908 defaultNanoRep: defaultNanoRep,
877 - defaultBananoRep: defaultBananoRep,
878 - lookupsTwitter: lookupsTwitter,
879 - lookupsMastodon: lookupsMastodon,
880 - lookupsYatService: lookupsYatService,
881 - lookupsUnstoppableDomains: lookupsUnstoppableDomains,
882 - lookupsOpenAlias: lookupsOpenAlias,
883 - lookupsENS: lookupsENS,
884 - initialMoneroTransactionPriority: moneroTransactionPriority,
909 + defaultBananoRep: defaultBananoRep,
910 + lookupsTwitter: lookupsTwitter,
911 + lookupsMastodon: lookupsMastodon,
912 + lookupsYatService: lookupsYatService,
913 + lookupsUnstoppableDomains: lookupsUnstoppableDomains,
914 + lookupsOpenAlias: lookupsOpenAlias,
915 + lookupsENS: lookupsENS,
916 + initialMoneroTransactionPriority: moneroTransactionPriority,
917 initialBitcoinTransactionPriority: bitcoinTransactionPriority,
918 initialHavenTransactionPriority: havenTransactionPriority,
919 initialLitecoinTransactionPriority: litecoinTransactionPriority,
920 initialBitcoinCashTransactionPriority: bitcoinCashTransactionPriority,
889 - initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet,
890 - initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact,
891 - initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact,
892 - initialShouldRequireTOTP2FAForSendsToInternalWallets:
893 - shouldRequireTOTP2FAForSendsToInternalWallets,
894 - initialShouldRequireTOTP2FAForExchangesToInternalWallets:
895 - shouldRequireTOTP2FAForExchangesToInternalWallets,
921 + initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet,
922 + initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact,
923 + initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact,
924 + initialShouldRequireTOTP2FAForSendsToInternalWallets:
925 + shouldRequireTOTP2FAForSendsToInternalWallets,
926 + initialShouldRequireTOTP2FAForExchangesToInternalWallets:
927 + shouldRequireTOTP2FAForExchangesToInternalWallets,
928 initialShouldRequireTOTP2FAForExchangesToExternalWallets:
929 shouldRequireTOTP2FAForExchangesToExternalWallets,
898 - initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts,
899 - initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets,
900 - initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
901 - shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
902 - initialEthereumTransactionPriority: ethereumTransactionPriority,
903 - backgroundTasks: backgroundTasks,
904 - initialSyncMode: savedSyncMode,
905 - initialSyncAll: savedSyncAll,
906 - shouldShowYatPopup: shouldShowYatPopup);
907 - }
930 + initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts,
931 + initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets,
932 + initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
933 + shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
934 + initialEthereumTransactionPriority: ethereumTransactionPriority,
935 + initialPolygonTransactionPriority: polygonTransactionPriority,
936 + backgroundTasks: backgroundTasks,
937 + initialSyncMode: savedSyncMode,
938 + initialSyncAll: savedSyncAll,
939 + shouldShowYatPopup: shouldShowYatPopup);
940 + }
941
942 Future<void> reload({required Box<Node> nodeSource}) async {
943 final sharedPreferences = await getIt.getAsync<SharedPreferences>();
@@ -934,6 +967,11 @@ abstract class SettingsStoreBase with Store {
967 sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!) ??
968 priority[WalletType.ethereum]!;
969 }
970 + if (sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority) != null) {
971 + priority[WalletType.polygon] = polygon?.deserializePolygonTransactionPriority(
972 + sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority)!) ??
973 + priority[WalletType.polygon]!;
974 + }
975 if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) {
976 priority[WalletType.bitcoinCash] = bitcoinCash?.deserializeBitcoinCashTransactionPriority(
977 sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!) ??
@@ -1027,12 +1065,14 @@ abstract class SettingsStoreBase with Store {
1065 .values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? sortBalanceBy.index];
1066 pinNativeTokenAtTop = sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true;
1067 useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
1068 + usePolygonScan = sharedPreferences.getBool(PreferencesKey.usePolygonScan) ?? true;
1069 defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? "";
1070 defaultBananoRep = sharedPreferences.getString(PreferencesKey.defaultBananoRep) ?? "";
1071 lookupsTwitter = sharedPreferences.getBool(PreferencesKey.lookupsTwitter) ?? true;
1072 lookupsMastodon = sharedPreferences.getBool(PreferencesKey.lookupsMastodon) ?? true;
1073 lookupsYatService = sharedPreferences.getBool(PreferencesKey.lookupsYatService) ?? true;
1035 - lookupsUnstoppableDomains = sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true;
1074 + lookupsUnstoppableDomains =
1075 + sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true;
1076 lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
1077 lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
1078
@@ -1045,6 +1085,7 @@ abstract class SettingsStoreBase with Store {
1085 sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
1086 final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
1087 final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
1088 + final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
1089 final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
1090 final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
1091 final moneroNode = nodeSource.get(nodeId);
@@ -1052,6 +1093,7 @@ abstract class SettingsStoreBase with Store {
1093 final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
1094 final havenNode = nodeSource.get(havenNodeId);
1095 final ethereumNode = nodeSource.get(ethereumNodeId);
1096 + final polygonNode = nodeSource.get(polygonNodeId);
1097 final bitcoinCashNode = nodeSource.get(bitcoinCashElectrumServerId);
1098 final nanoNode = nodeSource.get(nanoNodeId);
1099
@@ -1075,6 +1117,10 @@ abstract class SettingsStoreBase with Store {
1117 nodes[WalletType.ethereum] = ethereumNode;
1118 }
1119
1120 + if (polygonNode != null) {
1121 + nodes[WalletType.polygon] = polygonNode;
1122 + }
1123 +
1124 if (bitcoinCashNode != null) {
1125 nodes[WalletType.bitcoinCash] = bitcoinCashNode;
1126 }
@@ -1110,6 +1156,9 @@ abstract class SettingsStoreBase with Store {
1156 case WalletType.nano:
1157 await _sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, node.key as int);
1158 break;
1159 + case WalletType.polygon:
1160 + await _sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, node.key as int);
1161 + break;
1162 default:
1163 break;
1164 }
@@ -1141,7 +1190,6 @@ abstract class SettingsStoreBase with Store {
1190 trocadorProviderStates[providerName] = state;
1191 }
1192
1144 -
1193 static Future<String?> _getDeviceName() async {
1194 String? deviceName = '';
1195 final deviceInfoPlugin = DeviceInfoPlugin();
lib/view_model/dashboard/balance_view_model.dart
+6 -2
@@ -1,5 +1,6 @@
1 import 'package:cake_wallet/entities/fiat_api_mode.dart';
2 import 'package:cake_wallet/entities/sort_balance_types.dart';
3 +import 'package:cake_wallet/reactions/wallet_connect.dart';
4 import 'package:cw_core/transaction_history.dart';
5 import 'package:cw_core/wallet_base.dart';
6 import 'package:cw_core/balance.dart';
@@ -81,7 +82,7 @@ abstract class BalanceViewModelBase with Store {
82 bool get isFiatDisabled => settingsStore.fiatApiMode == FiatApiMode.disabled;
83
84 @computed
84 - bool get isHomeScreenSettingsEnabled => wallet.type == WalletType.ethereum;
85 + bool get isHomeScreenSettingsEnabled => isEVMCompatibleChain(wallet.type);
86
87 @computed
88 bool get hasAccounts => wallet.type == WalletType.monero;
@@ -123,6 +124,7 @@ abstract class BalanceViewModelBase with Store {
124 case WalletType.monero:
125 case WalletType.haven:
126 case WalletType.ethereum:
127 + case WalletType.polygon:
128 return S.current.xmr_available_balance;
129 default:
130 return S.current.confirmed;
@@ -135,6 +137,7 @@ abstract class BalanceViewModelBase with Store {
137 case WalletType.monero:
138 case WalletType.haven:
139 case WalletType.ethereum:
140 + case WalletType.polygon:
141 return S.current.xmr_full_balance;
142 default:
143 return S.current.unconfirmed;
@@ -272,7 +275,8 @@ abstract class BalanceViewModelBase with Store {
275 }
276
277 @computed
275 - bool get hasAdditionalBalance => wallet.type != WalletType.ethereum;
278 + bool get hasAdditionalBalance => !isEVMCompatibleChain(wallet.type);
279 +
280
281 @computed
282 List<BalanceRecord> get formattedBalances {
lib/view_model/dashboard/home_settings_view_model.dart
+52 -11
@@ -2,10 +2,12 @@ import 'package:cake_wallet/core/fiat_conversion_service.dart';
2 import 'package:cake_wallet/entities/fiat_api_mode.dart';
3 import 'package:cake_wallet/entities/sort_balance_types.dart';
4 import 'package:cake_wallet/ethereum/ethereum.dart';
5 +import 'package:cake_wallet/polygon/polygon.dart';
6 import 'package:cake_wallet/store/settings_store.dart';
7 import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
8 import 'package:cw_core/crypto_currency.dart';
9 import 'package:cw_core/erc20_token.dart';
10 +import 'package:cw_core/wallet_type.dart';
11 import 'package:mobx/mobx.dart';
12
13 part 'home_settings_view_model.g.dart';
@@ -42,18 +44,41 @@ abstract class HomeSettingsViewModelBase with Store {
44 void setPinNativeToken(bool value) => _settingsStore.pinNativeTokenAtTop = value;
45
46 Future<void> addErc20Token(Erc20Token token) async {
45 - await ethereum!.addErc20Token(_balanceViewModel.wallet, token);
47 + if (_balanceViewModel.wallet.type == WalletType.ethereum) {
48 + await ethereum!.addErc20Token(_balanceViewModel.wallet, token);
49 + }
50 +
51 + if (_balanceViewModel.wallet.type == WalletType.polygon) {
52 + await polygon!.addErc20Token(_balanceViewModel.wallet, token);
53 + }
54 +
55 _updateTokensList();
56 _updateFiatPrices(token);
57 }
58
59 Future<void> deleteErc20Token(Erc20Token token) async {
51 - await ethereum!.deleteErc20Token(_balanceViewModel.wallet, token);
60 + if (_balanceViewModel.wallet.type == WalletType.ethereum) {
61 + await ethereum!.deleteErc20Token(_balanceViewModel.wallet, token);
62 + }
63 +
64 + if (_balanceViewModel.wallet.type == WalletType.polygon) {
65 + await polygon!.deleteErc20Token(_balanceViewModel.wallet, token);
66 + }
67 +
68 _updateTokensList();
69 }
70
55 - Future<Erc20Token?> getErc20Token(String contractAddress) async =>
56 - await ethereum!.getErc20Token(_balanceViewModel.wallet, contractAddress);
71 + Future<Erc20Token?> getErc20Token(String contractAddress) async {
72 + if (_balanceViewModel.wallet.type == WalletType.ethereum) {
73 + return await ethereum!.getErc20Token(_balanceViewModel.wallet, contractAddress);
74 + }
75 +
76 + if (_balanceViewModel.wallet.type == WalletType.polygon) {
77 + return await polygon!.getErc20Token(_balanceViewModel.wallet, contractAddress);
78 + }
79 +
80 + return null;
81 + }
82
83 CryptoCurrency get nativeToken => _balanceViewModel.wallet.currency;
84
@@ -69,7 +94,12 @@ abstract class HomeSettingsViewModelBase with Store {
94
95 void changeTokenAvailability(Erc20Token token, bool value) async {
96 token.enabled = value;
72 - ethereum!.addErc20Token(_balanceViewModel.wallet, token);
97 + if (_balanceViewModel.wallet.type == WalletType.ethereum) {
98 + ethereum!.addErc20Token(_balanceViewModel.wallet, token);
99 + }
100 + if (_balanceViewModel.wallet.type == WalletType.polygon) {
101 + polygon!.addErc20Token(_balanceViewModel.wallet, token);
102 + }
103 _refreshTokensList();
104 }
105
@@ -83,7 +113,8 @@ abstract class HomeSettingsViewModelBase with Store {
113 return -1;
114 } else if (e2.enabled && !e1.enabled) {
115 return 1;
86 - } else if (!e1.enabled && !e2.enabled) { // if both are disabled then sort alphabetically
116 + } else if (!e1.enabled && !e2.enabled) {
117 + // if both are disabled then sort alphabetically
118 return e1.name.compareTo(e2.name);
119 }
120
@@ -92,11 +123,21 @@ abstract class HomeSettingsViewModelBase with Store {
123
124 tokens.clear();
125
95 - tokens.addAll(ethereum!
96 - .getERC20Currencies(_balanceViewModel.wallet)
97 - .where((element) => _matchesSearchText(element))
98 - .toList()
99 - ..sort(_sortFunc));
126 + if (_balanceViewModel.wallet.type == WalletType.ethereum) {
127 + tokens.addAll(ethereum!
128 + .getERC20Currencies(_balanceViewModel.wallet)
129 + .where((element) => _matchesSearchText(element))
130 + .toList()
131 + ..sort(_sortFunc));
132 + }
133 +
134 + if (_balanceViewModel.wallet.type == WalletType.polygon) {
135 + tokens.addAll(polygon!
136 + .getERC20Currencies(_balanceViewModel.wallet)
137 + .where((element) => _matchesSearchText(element))
138 + .toList()
139 + ..sort(_sortFunc));
140 + }
141 }
142
143 @action
lib/view_model/dashboard/nft_view_model.dart
+8 -6
@@ -1,10 +1,9 @@
1 -// ignore_for_file: public_member_api_docs, sort_constructors_first
1 import 'dart:convert';
2 import 'dart:developer';
3
4 import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
5 +import 'package:cake_wallet/reactions/wallet_connect.dart';
6 import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart';
7 -import 'package:cw_core/wallet_type.dart';
7 import 'package:http/http.dart' as http;
8 import 'package:mobx/mobx.dart';
9 import 'package:cake_wallet/.secrets.g.dart' as secrets;
@@ -39,23 +38,26 @@ abstract class NFTViewModelBase with Store {
38
39 @action
40 Future<void> getNFTAssetByWallet() async {
42 - if (appStore.wallet!.type != WalletType.ethereum) return;
41 + if (!isEVMCompatibleChain(appStore.wallet!.type)) return;
42
43 final walletAddress = appStore.wallet!.walletInfo.address;
44 log('Fetching wallet NFTs for $walletAddress');
45
46 + final chainName = getChainNameBasedOnWalletType(appStore.wallet!.type);
47 // the [chain] refers to the chain network that the nft is on
48 // the [format] refers to the number format type of the responses
49 // the [normalizedMetadata] field is a boolean that determines if
50 // the response would include a json string of the NFT Metadata that can be decoded
51 // and used within the wallet
52 + // the [excludeSpam] field is a boolean that determines if spam nfts be excluded from the response.
53 final uri = Uri.https(
54 'deep-index.moralis.io',
55 '/api/v2.2/$walletAddress/nft',
56 {
56 - "chain": "eth",
57 + "chain": chainName,
58 "format": "decimal",
59 "media_items": "false",
60 + "exclude_spam": "true",
61 "normalizeMetadata": "true",
62 },
63 );
@@ -94,7 +96,7 @@ abstract class NFTViewModelBase with Store {
96
97 @action
98 Future<void> importNFT(String tokenAddress, String tokenId) async {
97 -
99 + final chainName = getChainNameBasedOnWalletType(appStore.wallet!.type);
100 // the [chain] refers to the chain network that the nft is on
101 // the [format] refers to the number format type of the responses
102 // the [normalizedMetadata] field is a boolean that determines if
@@ -104,7 +106,7 @@ abstract class NFTViewModelBase with Store {
106 'deep-index.moralis.io',
107 '/api/v2.2/nft/$tokenAddress/$tokenId',
108 {
107 - "chain": "eth",
109 + "chain": chainName,
110 "format": "decimal",
111 "media_items": "false",
112 "normalizeMetadata": "true",
lib/view_model/dashboard/transaction_list_item.dart
+8
@@ -3,6 +3,7 @@ import 'package:cake_wallet/entities/fiat_currency.dart';
3 import 'package:cake_wallet/ethereum/ethereum.dart';
4 import 'package:cake_wallet/generated/i18n.dart';
5 import 'package:cake_wallet/nano/nano.dart';
6 +import 'package:cake_wallet/polygon/polygon.dart';
7 import 'package:cw_core/transaction_direction.dart';
8 import 'package:cw_core/transaction_info.dart';
9 import 'package:cake_wallet/store/settings_store.dart';
@@ -91,6 +92,13 @@ class TransactionListItem extends ActionListItem with Keyable {
92 cryptoAmount: ethereum!.formatterEthereumAmountToDouble(transaction: transaction),
93 price: price);
94 break;
95 + case WalletType.polygon:
96 + final asset = polygon!.assetOfTransaction(balanceViewModel.wallet, transaction);
97 + final price = balanceViewModel.fiatConvertationStore.prices[asset];
98 + amount = calculateFiatAmountRaw(
99 + cryptoAmount: polygon!.formatterPolygonAmountToDouble(transaction: transaction),
100 + price: price);
101 + break;
102 case WalletType.nano:
103 amount = calculateFiatAmountRaw(
104 cryptoAmount: double.parse(nanoUtil!.getRawAsDecimalString(
lib/view_model/exchange/exchange_trade_view_model.dart
+16 -4
@@ -28,10 +28,7 @@ abstract class ExchangeTradeViewModelBase with Store {
28 required this.tradesStore,
29 required this.sendViewModel})
30 : trade = tradesStore.trade!,
31 - isSendable = tradesStore.trade!.from == wallet.currency ||
32 - tradesStore.trade!.provider == ExchangeProviderDescription.xmrto ||
33 - (wallet.currency == CryptoCurrency.eth &&
34 - tradesStore.trade!.from.tag == CryptoCurrency.eth.title),
31 + isSendable = _checkIfCanSend(tradesStore, wallet),
32 items = ObservableList<ExchangeTradeItem>() {
33 switch (trade.provider) {
34 case ExchangeProviderDescription.changeNow:
@@ -155,4 +152,19 @@ abstract class ExchangeTradeViewModelBase with Store {
152 isCopied: true),
153 ]);
154 }
155 +
156 + static bool _checkIfCanSend(TradesStore tradesStore, WalletBase wallet) {
157 + bool _isEthToken() =>
158 + wallet.currency == CryptoCurrency.eth &&
159 + tradesStore.trade!.from.tag == CryptoCurrency.eth.title;
160 +
161 + bool _isPolygonToken() =>
162 + wallet.currency == CryptoCurrency.maticpoly &&
163 + tradesStore.trade!.from.tag == CryptoCurrency.maticpoly.tag;
164 +
165 + return tradesStore.trade!.from == wallet.currency ||
166 + tradesStore.trade!.provider == ExchangeProviderDescription.xmrto ||
167 + _isEthToken() ||
168 + _isPolygonToken();
169 + }
170 }
lib/view_model/exchange/exchange_view_model.dart
+10
@@ -23,6 +23,7 @@ import 'package:cake_wallet/exchange/trade.dart';
23 import 'package:cake_wallet/exchange/trade_request.dart';
24 import 'package:cake_wallet/generated/i18n.dart';
25 import 'package:cake_wallet/monero/monero.dart';
26 +import 'package:cake_wallet/polygon/polygon.dart';
27 import 'package:cake_wallet/store/app_store.dart';
28 import 'package:cake_wallet/store/dashboard/trades_store.dart';
29 import 'package:cake_wallet/store/settings_store.dart';
@@ -287,6 +288,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
288 return transactionPriority == ethereum!.getEthereumTransactionPrioritySlow();
289 case WalletType.bitcoinCash:
290 return transactionPriority == bitcoinCash!.getBitcoinCashTransactionPrioritySlow();
291 + case WalletType.polygon:
292 + return transactionPriority == polygon!.getPolygonTransactionPrioritySlow();
293 default:
294 return false;
295 }
@@ -626,6 +629,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
629 depositCurrency = CryptoCurrency.nano;
630 receiveCurrency = CryptoCurrency.xmr;
631 break;
632 + case WalletType.polygon:
633 + depositCurrency = CryptoCurrency.maticpoly;
634 + receiveCurrency = CryptoCurrency.xmr;
635 + break;
636 default:
637 break;
638 }
@@ -713,6 +720,9 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
720 case WalletType.bitcoinCash:
721 _settingsStore.priority[wallet.type] = bitcoinCash!.getDefaultTransactionPriority();
722 break;
723 + case WalletType.polygon:
724 + _settingsStore.priority[wallet.type] = polygon!.getDefaultTransactionPriority();
725 + break;
726 default:
727 break;
728 }
lib/view_model/node_list/node_list_view_model.dart
+3
@@ -72,6 +72,9 @@ abstract class NodeListViewModelBase with Store {
72 case WalletType.nano:
73 node = getNanoDefaultNode(nodes: _nodeSource)!;
74 break;
75 + case WalletType.polygon:
76 + node = getPolygonDefaultNode(nodes: _nodeSource)!;
77 + break;
78 default:
79 throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}');
80 }
lib/view_model/restore/restore_from_qr_vm.dart
+7
@@ -2,6 +2,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
3 import 'package:cake_wallet/ethereum/ethereum.dart';
4 import 'package:cake_wallet/nano/nano.dart';
5 +import 'package:cake_wallet/polygon/polygon.dart';
6 import 'package:cake_wallet/view_model/restore/restore_mode.dart';
7 import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
8 import 'package:hive/hive.dart';
@@ -71,6 +72,9 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
72 case WalletType.ethereum:
73 return ethereum!.createEthereumRestoreWalletFromPrivateKey(
74 name: name, password: password, privateKey: restoreWallet.privateKey!);
75 + case WalletType.polygon:
76 + return polygon!.createPolygonRestoreWalletFromPrivateKey(
77 + name: name, password: password, privateKey: restoreWallet.privateKey!);
78 default:
79 throw Exception('Unexpected type: ${restoreWallet.type.toString()}');
80 }
@@ -95,6 +99,9 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
99 case WalletType.nano:
100 return nano!.createNanoRestoreWalletFromSeedCredentials(
101 name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
102 + case WalletType.polygon:
103 + return polygon!.createPolygonRestoreWalletFromSeedCredentials(
104 + name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
105 default:
106 throw Exception('Unexpected type: ${type.toString()}');
107 }
lib/view_model/restore/wallet_restore_from_qr_code.dart
+11 -1
@@ -26,6 +26,7 @@ class WalletRestoreFromQRCode {
26 'litecoin-wallet': WalletType.litecoin,
27 'litecoin_wallet': WalletType.litecoin,
28 'ethereum-wallet': WalletType.ethereum,
29 + 'polygon-wallet': WalletType.polygon,
30 'nano-wallet': WalletType.nano,
31 'nano_wallet': WalletType.nano,
32 'bitcoincash': WalletType.bitcoinCash,
@@ -157,7 +158,16 @@ class WalletRestoreFromQRCode {
158 return WalletRestoreMode.keys;
159 }
160
160 - if ((type == WalletType.nano || type == WalletType.banano) && credentials.containsKey('hexSeed')) {
161 + if (type == WalletType.polygon && credentials.containsKey('private_key')) {
162 + final privateKey = credentials['private_key'] as String;
163 + if (privateKey.isEmpty) {
164 + throw Exception('Unexpected restore mode: private_key');
165 + }
166 + return WalletRestoreMode.keys;
167 + }
168 +
169 + if ((type == WalletType.nano || type == WalletType.banano) &&
170 + credentials.containsKey('hexSeed')) {
171 final hexSeed = credentials['hexSeed'] as String;
172 if (hexSeed.isEmpty) {
173 throw Exception('Unexpected restore mode: hexSeed');
lib/view_model/send/output.dart
+18 -8
@@ -4,6 +4,8 @@ import 'package:cake_wallet/entities/parse_address_from_domain.dart';
4 import 'package:cake_wallet/entities/parsed_address.dart';
5 import 'package:cake_wallet/ethereum/ethereum.dart';
6 import 'package:cake_wallet/haven/haven.dart';
7 +import 'package:cake_wallet/polygon/polygon.dart';
8 +import 'package:cake_wallet/reactions/wallet_connect.dart';
9 import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart';
10 import 'package:cw_core/crypto_currency.dart';
11 import 'package:flutter/material.dart';
@@ -27,7 +29,8 @@ const String cryptoNumberPattern = '0.0';
29 class Output = OutputBase with _$Output;
30
31 abstract class OutputBase with Store {
30 - OutputBase(this._wallet, this._settingsStore, this._fiatConversationStore, this.cryptoCurrencyHandler)
32 + OutputBase(
33 + this._wallet, this._settingsStore, this._fiatConversationStore, this.cryptoCurrencyHandler)
34 : _cryptoNumberFormat = NumberFormat(cryptoNumberPattern),
35 key = UniqueKey(),
36 sendAll = false,
@@ -65,8 +68,7 @@ abstract class OutputBase with Store {
68
69 @computed
70 bool get isParsedAddress =>
68 - parsedAddress.parseFrom != ParseFrom.notParsed &&
69 - parsedAddress.name.isNotEmpty;
71 + parsedAddress.parseFrom != ParseFrom.notParsed && parsedAddress.name.isNotEmpty;
72
73 @computed
74 int get formattedCryptoAmount {
@@ -83,8 +85,7 @@ abstract class OutputBase with Store {
85 case WalletType.bitcoin:
86 case WalletType.litecoin:
87 case WalletType.bitcoinCash:
86 - _amount =
87 - bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount);
88 + _amount = bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount);
89 break;
90 case WalletType.haven:
91 _amount = haven!.formatterMoneroParseAmount(amount: _cryptoAmount);
@@ -92,6 +93,9 @@ abstract class OutputBase with Store {
93 case WalletType.ethereum:
94 _amount = ethereum!.formatterEthereumParseAmount(_cryptoAmount);
95 break;
96 + case WalletType.polygon:
97 + _amount = polygon!.formatterPolygonParseAmount(_cryptoAmount);
98 + break;
99 default:
100 break;
101 }
@@ -130,6 +134,10 @@ abstract class OutputBase with Store {
134 if (_wallet.type == WalletType.ethereum) {
135 return ethereum!.formatterEthereumAmountToDouble(amount: BigInt.from(fee));
136 }
137 +
138 + if (_wallet.type == WalletType.polygon) {
139 + return polygon!.formatterPolygonAmountToDouble(amount: BigInt.from(fee));
140 + }
141 } catch (e) {
142 print(e.toString());
143 }
@@ -140,10 +148,11 @@ abstract class OutputBase with Store {
148 @computed
149 String get estimatedFeeFiatAmount {
150 try {
143 - final currency = _wallet.type == WalletType.ethereum ? _wallet.currency : cryptoCurrencyHandler();
151 + final currency = isEVMCompatibleChain(_wallet.type)
152 + ? _wallet.currency
153 + : cryptoCurrencyHandler();
154 final fiat = calculateFiatAmountRaw(
145 - price: _fiatConversationStore.prices[currency]!,
146 - cryptoAmount: estimatedFee);
155 + price: _fiatConversationStore.prices[currency]!, cryptoAmount: estimatedFee);
156 return fiat;
157 } catch (_) {
158 return '0.00';
@@ -240,6 +249,7 @@ abstract class OutputBase with Store {
249 maximumFractionDigits = 12;
250 break;
251 case WalletType.ethereum:
252 + case WalletType.polygon:
253 maximumFractionDigits = 12;
254 break;
255 default:
lib/view_model/send/send_template_view_model.dart
+3 -1
@@ -50,7 +50,9 @@ abstract class SendTemplateViewModelBase with Store {
50 TemplateValidator get templateValidator => TemplateValidator();
51
52 bool get hasMultiRecipient =>
53 - _wallet.type != WalletType.haven && _wallet.type != WalletType.ethereum;
53 + _wallet.type != WalletType.haven &&
54 + _wallet.type != WalletType.ethereum &&
55 + _wallet.type != WalletType.polygon;
56
57 @computed
58 CryptoCurrency get cryptoCurrency => _wallet.currency;
lib/view_model/send/send_view_model.dart
+13 -4
@@ -5,6 +5,8 @@ import 'package:cake_wallet/nano/nano.dart';
5 import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
6 import 'package:cake_wallet/entities/contact_record.dart';
7 import 'package:cake_wallet/entities/wallet_contact.dart';
8 +import 'package:cake_wallet/polygon/polygon.dart';
9 +import 'package:cake_wallet/reactions/wallet_connect.dart';
10 import 'package:cake_wallet/store/app_store.dart';
11 import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
12 import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
@@ -42,7 +44,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
44 void onWalletChange(wallet) {
45 currencies = wallet.balance.keys.toList();
46 selectedCryptoCurrency = wallet.currency;
45 - hasMultipleTokens = wallet.type == WalletType.ethereum;
47 + hasMultipleTokens = isEVMCompatibleChain(wallet.type);
48 }
49
50 SendViewModelBase(
@@ -55,7 +57,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
57 ) : state = InitialExecutionState(),
58 currencies = appStore.wallet!.balance.keys.toList(),
59 selectedCryptoCurrency = appStore.wallet!.currency,
58 - hasMultipleTokens = appStore.wallet!.type == WalletType.ethereum,
60 + hasMultipleTokens = isEVMCompatibleChain(appStore.wallet!.type),
61 outputs = ObservableList<Output>(),
62 _settingsStore = appStore.settingsStore,
63 fiatFromSettings = appStore.settingsStore.fiatCurrency,
@@ -119,7 +121,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
121 try {
122 if (pendingTransaction != null) {
123 final currency =
122 - walletType == WalletType.ethereum ? wallet.currency : selectedCryptoCurrency;
124 + isEVMCompatibleChain(walletType) ? wallet.currency : selectedCryptoCurrency;
125 final fiat = calculateFiatAmount(
126 price: _fiatConversationStore.prices[currency]!,
127 cryptoAmount: pendingTransaction!.feeFormatted);
@@ -372,6 +374,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
374 priority: priority!, currency: selectedCryptoCurrency);
375 case WalletType.nano:
376 return nano!.createNanoTransactionCredentials(outputs);
377 + case WalletType.polygon:
378 + return polygon!.createPolygonTransactionCredentials(outputs,
379 + priority: priority!, currency: selectedCryptoCurrency);
380 default:
381 throw Exception('Unexpected wallet type: ${wallet.type}');
382 }
@@ -412,11 +417,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
417 WalletType walletType,
418 CryptoCurrency currency,
419 ) {
415 - if (walletType == WalletType.ethereum || walletType == WalletType.haven) {
420 + if (walletType == WalletType.ethereum ||
421 + walletType == WalletType.polygon ||
422 + walletType == WalletType.haven) {
423 if (error.contains('gas required exceeds allowance') ||
424 error.contains('insufficient funds for')) {
425 return S.current.do_not_have_enough_gas_asset(currency.toString());
426 }
427 +
428 + return error;
429 }
430
431 return error;
lib/view_model/settings/privacy_settings_view_model.dart
+13 -2
@@ -1,6 +1,7 @@
1 import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
2 import 'package:cake_wallet/entities/exchange_api_mode.dart';
3 import 'package:cake_wallet/ethereum/ethereum.dart';
4 +import 'package:cake_wallet/polygon/polygon.dart';
5 import 'package:cake_wallet/store/settings_store.dart';
6 import 'package:cw_core/balance.dart';
7 import 'package:cw_core/transaction_history.dart';
@@ -12,8 +13,7 @@ import 'package:cake_wallet/entities/fiat_api_mode.dart';
13
14 part 'privacy_settings_view_model.g.dart';
15
15 -class
16 -PrivacySettingsViewModel = PrivacySettingsViewModelBase with _$PrivacySettingsViewModel;
16 +class PrivacySettingsViewModel = PrivacySettingsViewModelBase with _$PrivacySettingsViewModel;
17
18 abstract class PrivacySettingsViewModelBase with Store {
19 PrivacySettingsViewModelBase(this._settingsStore, this._wallet);
@@ -58,6 +58,9 @@ abstract class PrivacySettingsViewModelBase with Store {
58 @computed
59 bool get useEtherscan => _settingsStore.useEtherscan;
60
61 + @computed
62 + bool get usePolygonScan => _settingsStore.usePolygonScan;
63 +
64 @computed
65 bool get lookupTwitter => _settingsStore.lookupsTwitter;
66
@@ -78,6 +81,8 @@ abstract class PrivacySettingsViewModelBase with Store {
81
82 bool get canUseEtherscan => _wallet.type == WalletType.ethereum;
83
84 + bool get canUsePolygonScan => _wallet.type == WalletType.polygon;
85 +
86 @action
87 void setShouldSaveRecipientAddress(bool value) =>
88 _settingsStore.shouldSaveRecipientAddress = value;
@@ -120,4 +125,10 @@ abstract class PrivacySettingsViewModelBase with Store {
125 _settingsStore.useEtherscan = value;
126 ethereum!.updateEtherscanUsageState(_wallet, value);
127 }
128 +
129 + @action
130 + void setUsePolygonScan(bool value) {
131 + _settingsStore.usePolygonScan = value;
132 + polygon!.updatePolygonScanUsageState(_wallet, value);
133 + }
134 }
lib/view_model/transaction_details_view_model.dart
+25 -2
@@ -51,6 +51,9 @@ abstract class TransactionDetailsViewModelBase with Store {
51 case WalletType.nano:
52 _addNanoListItems(tx, dateFormat);
53 break;
54 + case WalletType.polygon:
55 + _addPolygonListItems(tx, dateFormat);
56 + break;
57 default:
58 break;
59 }
@@ -125,7 +128,9 @@ abstract class TransactionDetailsViewModelBase with Store {
128 case WalletType.nano:
129 return 'https://nanolooker.com/block/${txId}';
130 case WalletType.banano:
128 - return 'https://bananolooker.com/block/${txId}';
131 + return 'https://bananolooker.com/block/${txId}';
132 + case WalletType.polygon:
133 + return 'https://polygonscan.com/tx/${txId}';
134 default:
135 return '';
136 }
@@ -148,6 +153,8 @@ abstract class TransactionDetailsViewModelBase with Store {
153 return S.current.view_transaction_on + 'nanolooker.com';
154 case WalletType.banano:
155 return S.current.view_transaction_on + 'bananolooker.com';
156 + case WalletType.polygon:
157 + return S.current.view_transaction_on + 'polygonscan.com';
158 default:
159 return '';
160 }
@@ -237,7 +244,6 @@ abstract class TransactionDetailsViewModelBase with Store {
244 items.addAll(_items);
245 }
246
240 -
247 void _addNanoListItems(TransactionInfo tx, DateFormat dateFormat) {
248 final _items = [
249 StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
@@ -250,4 +256,21 @@ abstract class TransactionDetailsViewModelBase with Store {
256
257 items.addAll(_items);
258 }
259 +
260 + void _addPolygonListItems(TransactionInfo tx, DateFormat dateFormat) {
261 + final _items = [
262 + StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
263 + StandartListItem(
264 + title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
265 + StandartListItem(title: S.current.confirmations, value: tx.confirmations.toString()),
266 + StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
267 + StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
268 + if (tx.feeFormatted()?.isNotEmpty ?? false)
269 + StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
270 + if (showRecipientAddress && tx.to != null)
271 + StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!),
272 + ];
273 +
274 + items.addAll(_items);
275 + }
276 }
lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+27
@@ -1,6 +1,7 @@
1 import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
2 import 'package:cake_wallet/ethereum/ethereum.dart';
3 import 'package:cake_wallet/entities/fiat_currency.dart';
4 +import 'package:cake_wallet/polygon/polygon.dart';
5 import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
6 import 'package:cake_wallet/store/yat/yat_store.dart';
7 import 'package:cw_core/currency.dart';
@@ -139,6 +140,22 @@ class NanoURI extends PaymentURI {
140 }
141 }
142
143 +class PolygonURI extends PaymentURI {
144 + PolygonURI({required String amount, required String address})
145 + : super(amount: amount, address: address);
146 +
147 + @override
148 + String toString() {
149 + var base = 'polygon:' + address;
150 +
151 + if (amount.isNotEmpty) {
152 + base += '?amount=${amount.replaceAll(',', '.')}';
153 + }
154 +
155 + return base;
156 + }
157 +}
158 +
159 abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store {
160 WalletAddressListViewModelBase({
161 required AppStore appStore,
@@ -216,6 +233,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
233 return NanoURI(amount: amount, address: address.address);
234 }
235
236 + if (wallet.type == WalletType.polygon) {
237 + return PolygonURI(amount: amount, address: address.address);
238 + }
239 +
240 throw Exception('Unexpected type: ${type.toString()}');
241 }
242
@@ -272,6 +293,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
293 addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
294 }
295
296 + if (wallet.type == WalletType.polygon) {
297 + final primaryAddress = polygon!.getAddress(wallet);
298 +
299 + addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
300 + }
301 +
302 return addressList;
303 }
304
lib/view_model/wallet_keys_view_model.dart
+6 -2
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/reactions/wallet_connect.dart';
2 import 'package:cake_wallet/store/app_store.dart';
3 import 'package:cw_core/transaction_direction.dart';
4 import 'package:cw_core/transaction_info.dart';
@@ -19,7 +20,8 @@ abstract class WalletKeysViewModelBase with Store {
20 : title = _appStore.wallet!.type == WalletType.bitcoin ||
21 _appStore.wallet!.type == WalletType.litecoin ||
22 _appStore.wallet!.type == WalletType.bitcoinCash ||
22 - _appStore.wallet!.type == WalletType.ethereum
23 + _appStore.wallet!.type == WalletType.ethereum ||
24 + _appStore.wallet!.type == WalletType.polygon
25 ? S.current.wallet_seed
26 : S.current.wallet_keys,
27 _restoreHeight = _appStore.wallet!.walletInfo.restoreHeight,
@@ -98,7 +100,7 @@ abstract class WalletKeysViewModelBase with Store {
100 ]);
101 }
102
101 - if (_appStore.wallet!.type == WalletType.ethereum) {
103 + if (isEVMCompatibleChain(_appStore.wallet!.type)) {
104 items.addAll([
105 if (_appStore.wallet!.privateKey != null)
106 StandartListItem(title: S.current.private_key, value: _appStore.wallet!.privateKey!),
@@ -151,6 +153,8 @@ abstract class WalletKeysViewModelBase with Store {
153 return 'nano-wallet';
154 case WalletType.banano:
155 return 'banano-wallet';
156 + case WalletType.polygon:
157 + return 'polygon-wallet';
158 default:
159 throw Exception('Unexpected wallet type: ${_appStore.wallet!.toString()}');
160 }
lib/view_model/wallet_new_vm.dart
+4
@@ -14,6 +14,8 @@ import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
14 import 'package:cake_wallet/bitcoin/bitcoin.dart';
15 import 'package:cake_wallet/haven/haven.dart';
16
17 +import '../polygon/polygon.dart';
18 +
19 part 'wallet_new_vm.g.dart';
20
21 class WalletNewVM = WalletNewVMBase with _$WalletNewVM;
@@ -52,6 +54,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
54 return bitcoinCash!.createBitcoinCashNewWalletCredentials(name: name);
55 case WalletType.nano:
56 return nano!.createNanoNewWalletCredentials(name: name);
57 + case WalletType.polygon:
58 + return polygon!.createPolygonNewWalletCredentials(name: name);
59 default:
60 throw Exception('Unexpected type: ${type.toString()}');
61 }
lib/view_model/wallet_restore_view_model.dart
+18 -1
@@ -3,6 +3,7 @@ import 'package:cake_wallet/di.dart';
3 import 'package:cake_wallet/nano/nano.dart';
4 import 'package:cake_wallet/ethereum/ethereum.dart';
5 import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
6 +import 'package:cake_wallet/polygon/polygon.dart';
7 import 'package:hive/hive.dart';
8 import 'package:mobx/mobx.dart';
9 import 'package:cake_wallet/store/app_store.dart';
@@ -28,7 +29,10 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
29 : hasSeedLanguageSelector = type == WalletType.monero || type == WalletType.haven,
30 hasBlockchainHeightLanguageSelector = type == WalletType.monero || type == WalletType.haven,
31 hasRestoreFromPrivateKey =
31 - type == WalletType.ethereum || type == WalletType.nano || type == WalletType.banano,
32 + type == WalletType.ethereum ||
33 + type == WalletType.polygon ||
34 + type == WalletType.nano ||
35 + type == WalletType.banano,
36 isButtonEnabled = false,
37 mode = WalletRestoreMode.seed,
38 super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true) {
@@ -36,6 +40,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
40 case WalletType.monero:
41 case WalletType.haven:
42 case WalletType.ethereum:
43 + case WalletType.polygon:
44 availableModes = WalletRestoreMode.values;
45 break;
46 case WalletType.nano:
@@ -107,6 +112,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
112 mnemonic: seed,
113 password: password,
114 derivationType: derivationType);
115 + case WalletType.polygon:
116 + return polygon!.createPolygonRestoreWalletFromSeedCredentials(
117 + name: name,
118 + mnemonic: seed,
119 + password: password,
120 + );
121 default:
122 break;
123 }
@@ -153,6 +164,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
164 password: password,
165 seedKey: options['private_key'] as String,
166 derivationType: options["derivationType"] as DerivationType);
167 + case WalletType.polygon:
168 + return polygon!.createPolygonRestoreWalletFromPrivateKey(
169 + name: name,
170 + password: password,
171 + privateKey: options['private_key'] as String,
172 + );
173 default:
174 break;
175 }
macos/Podfile.lock
+6 -1
@@ -41,6 +41,7 @@ PODS:
41 - shared_preferences_foundation (0.0.1):
42 - Flutter
43 - FlutterMacOS
44 + - tor (0.0.1)
45 - url_launcher_macos (0.0.1):
46 - FlutterMacOS
47 - wakelock_plus (0.0.1):
@@ -59,6 +60,7 @@ DEPENDENCIES:
60 - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
61 - share_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos`)
62 - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
63 + - tor (from `Flutter/ephemeral/.symlinks/plugins/tor/macos`)
64 - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
65 - wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
66
@@ -91,6 +93,8 @@ EXTERNAL SOURCES:
93 :path: Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos
94 shared_preferences_foundation:
95 :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
96 + tor:
97 + :path: Flutter/ephemeral/.symlinks/plugins/tor/macos
98 url_launcher_macos:
99 :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
100 wakelock_plus:
@@ -98,7 +102,7 @@ EXTERNAL SOURCES:
102
103 SPEC CHECKSUMS:
104 connectivity_plus_macos: f6e86fd000e971d361e54b5afcadc8c8fa773308
101 - cw_monero: ec03de55a19c4a2b174ea687e0f4202edc716fa4
105 + cw_monero: f8b7f104508efba2591548e76b5c058d05cba3f0
106 device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
107 devicelocale: 9f0f36ac651cabae2c33f32dcff4f32b61c38225
108 flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea
@@ -110,6 +114,7 @@ SPEC CHECKSUMS:
114 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
115 share_plus_macos: 853ee48e7dce06b633998ca0735d482dd671ade4
116 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
117 + tor: 2138c48428e696b83eacdda404de6d5574932e26
118 url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
119 wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269
120
model_generator.sh
+1
@@ -5,4 +5,5 @@ cd cw_haven && flutter pub get && flutter packages pub run build_runner build --
5 cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
6 cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
7 cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
8 +cd cw_polygon && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
9 flutter packages pub run build_runner build --delete-conflicting-outputs
\ No newline at end of file
pubspec_base.yaml
+1
@@ -147,6 +147,7 @@ flutter:
147 - assets/bitcoin_cash_electrum_server_list.yml
148 - assets/nano_node_list.yml
149 - assets/nano_pow_node_list.yml
150 + - assets/polygon_node_list.yml
151 - assets/text/
152 - assets/faq/
153 - assets/animation/
res/values/strings_ar.arb
+2 -1
@@ -745,5 +745,6 @@
745 "seedtype_polyseed": "بوليسيد (16 كلمة)",
746 "seed_language_czech": "التشيكية",
747 "seed_language_korean": "الكورية",
748 - "seed_language_chinese_traditional": "تقاليد صينية)"
748 + "seed_language_chinese_traditional": "تقاليد صينية)",
749 + "polygonscan_history": "ﻥﺎﻜﺴﻧﻮﺠﻴﻟﻮﺑ ﺦﻳﺭﺎﺗ"
750 }
res/values/strings_bg.arb
+2 -1
@@ -741,5 +741,6 @@
741 "seedtype_polyseed": "Поли семе (16 думи)",
742 "seed_language_czech": "Чех",
743 "seed_language_korean": "Корейски",
744 - "seed_language_chinese_traditional": "Традиционен китайски)"
744 + "seed_language_chinese_traditional": "Традиционен китайски)",
745 + "polygonscan_history": "История на PolygonScan"
746 }
res/values/strings_cs.arb
+2 -1
@@ -741,5 +741,6 @@
741 "seedtype_polyseed": "Polyseed (16 slov)",
742 "seed_language_czech": "čeština",
743 "seed_language_korean": "korejština",
744 - "seed_language_chinese_traditional": "Číňan (tradiční)"
744 + "seed_language_chinese_traditional": "Číňan (tradiční)",
745 + "polygonscan_history": "Historie PolygonScan"
746 }
res/values/strings_de.arb
+2 -1
@@ -749,5 +749,6 @@
749 "seedtype_polyseed": "Polyseed (16 Wörter)",
750 "seed_language_czech": "Tschechisch",
751 "seed_language_korean": "Koreanisch",
752 - "seed_language_chinese_traditional": "Chinesisch (Traditionell)"
752 + "seed_language_chinese_traditional": "Chinesisch (Traditionell)",
753 + "polygonscan_history": "PolygonScan-Verlauf"
754 }
res/values/strings_en.arb
+2 -1
@@ -750,5 +750,6 @@
750 "seedtype_polyseed": "Polyseed (16 words)",
751 "seed_language_czech": "Czech",
752 "seed_language_korean": "Korean",
753 - "seed_language_chinese_traditional": "Chinese (Traditional)"
753 + "seed_language_chinese_traditional": "Chinese (Traditional)",
754 + "polygonscan_history": "PolygonScan history"
755 }
res/values/strings_es.arb
+2 -1
@@ -749,5 +749,6 @@
749 "seedtype_polyseed": "Polieta (16 palabras)",
750 "seed_language_czech": "checo",
751 "seed_language_korean": "coreano",
752 - "seed_language_chinese_traditional": "Chino (tradicional)"
752 + "seed_language_chinese_traditional": "Chino (tradicional)",
753 + "polygonscan_history": "Historial de PolygonScan"
754 }
res/values/strings_fr.arb
+2 -1
@@ -749,5 +749,6 @@
749 "seedtype_polyseed": "Polyseed (16 mots)",
750 "seed_language_czech": "tchèque",
751 "seed_language_korean": "coréen",
752 - "seed_language_chinese_traditional": "Chinois (Traditionnel)"
752 + "seed_language_chinese_traditional": "Chinois (Traditionnel)",
753 + "polygonscan_history": "Historique de PolygonScan"
754 }
res/values/strings_ha.arb
+2 -1
@@ -727,5 +727,6 @@
727 "seedtype_polyseed": "Polyseed (16 kalmomi)",
728 "seed_language_czech": "Czech",
729 "seed_language_korean": "Yaren Koriya",
730 - "seed_language_chinese_traditional": "Sinanci (na gargajiya)"
730 + "seed_language_chinese_traditional": "Sinanci (na gargajiya)",
731 + "polygonscan_history": "PolygonScan tarihin kowane zamani"
732 }
res/values/strings_hi.arb
+2 -1
@@ -749,5 +749,6 @@
749 "seedtype_polyseed": "पॉलीसीड (16 शब्द)",
750 "seed_language_czech": "चेक",
751 "seed_language_korean": "कोरियाई",
752 - "seed_language_chinese_traditional": "चीनी पारंपरिक)"
752 + "seed_language_chinese_traditional": "चीनी पारंपरिक)",
753 + "polygonscan_history": "पॉलीगॉनस्कैन इतिहास"
754 }
res/values/strings_hr.arb
+2 -1
@@ -747,5 +747,6 @@
747 "seedtype_polyseed": "Poliseed (16 riječi)",
748 "seed_language_czech": "češki",
749 "seed_language_korean": "korejski",
750 - "seed_language_chinese_traditional": "Kinesko (tradicionalno)"
750 + "seed_language_chinese_traditional": "Kinesko (tradicionalno)",
751 + "polygonscan_history": "Povijest PolygonScan"
752 }
res/values/strings_id.arb
+2 -1
@@ -737,5 +737,6 @@
737 "seedtype_polyseed": "Polyseed (16 kata)",
738 "seed_language_czech": "Ceko",
739 "seed_language_korean": "Korea",
740 - "seed_language_chinese_traditional": "Cina (tradisional)"
740 + "seed_language_chinese_traditional": "Cina (tradisional)",
741 + "polygonscan_history": "Sejarah PolygonScan"
742 }
res/values/strings_it.arb
+2 -1
@@ -749,5 +749,6 @@
749 "seedtype_polyseed": "Polyseed (16 parole)",
750 "seed_language_czech": "ceco",
751 "seed_language_korean": "coreano",
752 - "seed_language_chinese_traditional": "Cinese tradizionale)"
752 + "seed_language_chinese_traditional": "Cinese tradizionale)",
753 + "polygonscan_history": "Cronologia PolygonScan"
754 }
res/values/strings_ja.arb
+2 -1
@@ -749,5 +749,6 @@
749 "seedtype_polyseed": "ポリシード(16語)",
750 "seed_language_czech": "チェコ",
751 "seed_language_korean": "韓国語",
752 - "seed_language_chinese_traditional": "中国の伝統的な)"
752 + "seed_language_chinese_traditional": "中国の伝統的な)",
753 + "polygonscan_history": "ポリゴンスキャン履歴"
754 }
res/values/strings_ko.arb
+2 -1
@@ -747,5 +747,6 @@
747 "seedtype_polyseed": "다문 (16 단어)",
748 "seed_language_czech": "체코 사람",
749 "seed_language_korean": "한국인",
750 - "seed_language_chinese_traditional": "중국 전통)"
750 + "seed_language_chinese_traditional": "중국 전통)",
751 + "polygonscan_history": "다각형 스캔 기록"
752 }
res/values/strings_my.arb
+2 -1
@@ -747,5 +747,6 @@
747 "seedtype_polyseed": "polyseed (စကားလုံး 16 လုံး)",
748 "seed_language_czech": "ချက်",
749 "seed_language_korean": "ကိုးရီးယား",
750 - "seed_language_chinese_traditional": "တရုတ်ရိုးရာ)"
750 + "seed_language_chinese_traditional": "တရုတ်ရိုးရာ)",
751 + "polygonscan_history": "PolygonScan မှတ်တမ်း"
752 }
res/values/strings_nl.arb
+2 -1
@@ -749,5 +749,6 @@
749 "seedtype_polyseed": "Polyseed (16 woorden)",
750 "seed_language_czech": "Tsjechisch",
751 "seed_language_korean": "Koreaans",
752 - "seed_language_chinese_traditional": "Chinese (traditionele)"
752 + "seed_language_chinese_traditional": "Chinese (traditionele)",
753 + "polygonscan_history": "PolygonScan-geschiedenis"
754 }
res/values/strings_pl.arb
+2 -1
@@ -749,5 +749,6 @@
749 "seedtype_polyseed": "Poliqueed (16 słów)",
750 "seed_language_czech": "Czech",
751 "seed_language_korean": "koreański",
752 - "seed_language_chinese_traditional": "Chiński tradycyjny)"
752 + "seed_language_chinese_traditional": "Chiński tradycyjny)",
753 + "polygonscan_history": "Historia PolygonScan"
754 }
res/values/strings_pt.arb
+2 -1
@@ -748,5 +748,6 @@
748 "seedtype_polyseed": "Polyseed (16 palavras)",
749 "seed_language_czech": "Tcheco",
750 "seed_language_korean": "coreano",
751 - "seed_language_chinese_traditional": "Chinês tradicional)"
751 + "seed_language_chinese_traditional": "Chinês tradicional)",
752 + "polygonscan_history": "História do PolygonScan"
753 }
res/values/strings_ru.arb
+2 -1
@@ -749,5 +749,6 @@
749 "seedtype_polyseed": "Полиса (16 слов)",
750 "seed_language_czech": "Чешский",
751 "seed_language_korean": "Корейский",
752 - "seed_language_chinese_traditional": "Китайский традиционный)"
752 + "seed_language_chinese_traditional": "Китайский традиционный)",
753 + "polygonscan_history": "История PolygonScan"
754 }
res/values/strings_th.arb
+2 -1
@@ -747,5 +747,6 @@
747 "seedtype_polyseed": "โพลีส (16 คำ)",
748 "seed_language_czech": "ภาษาเช็ก",
749 "seed_language_korean": "เกาหลี",
750 - "seed_language_chinese_traditional": "จีน (ดั้งเดิม)"
750 + "seed_language_chinese_traditional": "จีน (ดั้งเดิม)",
751 + "polygonscan_history": "ประวัติ PolygonScan"
752 }
res/values/strings_tl.arb
+2 -1
@@ -743,5 +743,6 @@
743 "seedtype_polyseed": "Polyseed (16 na salita)",
744 "seed_language_czech": "Czech",
745 "seed_language_korean": "Korean",
746 - "seed_language_chinese_traditional": "Intsik (tradisyonal)"
746 + "seed_language_chinese_traditional": "Intsik (tradisyonal)",
747 + "polygonscan_history": "Kasaysayan ng PolygonScan"
748 }
res/values/strings_tr.arb
+2 -1
@@ -747,5 +747,6 @@
747 "seedtype_polyseed": "Polyseed (16 kelime)",
748 "seed_language_czech": "Çek",
749 "seed_language_korean": "Koreli",
750 - "seed_language_chinese_traditional": "Çin geleneği)"
750 + "seed_language_chinese_traditional": "Çin geleneği)",
751 + "polygonscan_history": "PolygonScan geçmişi"
752 }
res/values/strings_uk.arb
+2 -1
@@ -749,5 +749,6 @@
749 "seedtype_polyseed": "Полісей (16 слів)",
750 "seed_language_czech": "Чеський",
751 "seed_language_korean": "Корейський",
752 - "seed_language_chinese_traditional": "Китайський (традиційний)"
752 + "seed_language_chinese_traditional": "Китайський (традиційний)",
753 + "polygonscan_history": "Історія PolygonScan"
754 }
res/values/strings_ur.arb
+2 -1
@@ -741,5 +741,6 @@
741 "seedtype_polyseed": "پالیسیڈ (16 الفاظ)",
742 "seed_language_czech": "چیک",
743 "seed_language_korean": "کورین",
744 - "seed_language_chinese_traditional": "چینی (روایتی)"
744 + "seed_language_chinese_traditional": "چینی (روایتی)",
745 + "polygonscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﻥﻮﮔ ﯽﻟﻮﭘ"
746 }
res/values/strings_yo.arb
+2 -1
@@ -743,5 +743,6 @@
743 "seedtype_polyseed": "Polyseed (awọn ọrọ 16)",
744 "seed_language_czech": "Czech",
745 "seed_language_korean": "Ara ẹni",
746 - "seed_language_chinese_traditional": "Kannada (ibile)"
746 + "seed_language_chinese_traditional": "Kannada (ibile)",
747 + "polygonscan_history": "PolygonScan itan"
748 }
res/values/strings_zh.arb
+2 -1
@@ -748,5 +748,6 @@
748 "seedtype_polyseed": "多种物品(16个单词)",
749 "seed_language_czech": "捷克",
750 "seed_language_korean": "韩国人",
751 - "seed_language_chinese_traditional": "中国传统的)"
751 + "seed_language_chinese_traditional": "中国传统的)",
752 + "polygonscan_history": "多边形扫描历史"
753 }
scripts/android/pubspec_gen.sh
+1 -1
@@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in
10 CONFIG_ARGS="--monero"
11 ;;
12 $CAKEWALLET)
13 - CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano --bitcoinCash"
13 + CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano --bitcoinCash --polygon"
14 ;;
15 $HAVEN)
16 CONFIG_ARGS="--haven"
tool/configure.dart
+108 -2
@@ -6,6 +6,7 @@ const havenOutputPath = 'lib/haven/haven.dart';
6 const ethereumOutputPath = 'lib/ethereum/ethereum.dart';
7 const bitcoinCashOutputPath = 'lib/bitcoin_cash/bitcoin_cash.dart';
8 const nanoOutputPath = 'lib/nano/nano.dart';
9 +const polygonOutputPath = 'lib/polygon/polygon.dart';
10 const walletTypesPath = 'lib/wallet_types.g.dart';
11 const pubspecDefaultPath = 'pubspec_default.yaml';
12 const pubspecOutputPath = 'pubspec.yaml';
@@ -19,6 +20,7 @@ Future<void> main(List<String> args) async {
20 final hasBitcoinCash = args.contains('${prefix}bitcoinCash');
21 final hasNano = args.contains('${prefix}nano');
22 final hasBanano = args.contains('${prefix}banano');
23 + final hasPolygon = args.contains('${prefix}polygon');
24
25 await generateBitcoin(hasBitcoin);
26 await generateMonero(hasMonero);
@@ -26,6 +28,7 @@ Future<void> main(List<String> args) async {
28 await generateEthereum(hasEthereum);
29 await generateBitcoinCash(hasBitcoinCash);
30 await generateNano(hasNano);
31 + await generatePolygon(hasPolygon);
32 // await generateBanano(hasEthereum);
33
34 await generatePubspec(
@@ -36,6 +39,7 @@ Future<void> main(List<String> args) async {
39 hasNano: hasNano,
40 hasBanano: hasBanano,
41 hasBitcoinCash: hasBitcoinCash,
42 + hasPolygon: hasPolygon,
43 );
44 await generateWalletTypes(
45 hasMonero: hasMonero,
@@ -45,6 +49,7 @@ Future<void> main(List<String> args) async {
49 hasNano: hasNano,
50 hasBanano: hasBanano,
51 hasBitcoinCash: hasBitcoinCash,
52 + hasPolygon: hasPolygon,
53 );
54 }
55
@@ -572,6 +577,93 @@ abstract class Ethereum {
577 await outputFile.writeAsString(output);
578 }
579
580 +Future<void> generatePolygon(bool hasImplementation) async {
581 + final outputFile = File(polygonOutputPath);
582 + const polygonCommonHeaders = """
583 +import 'package:cake_wallet/view_model/send/output.dart';
584 +import 'package:cw_core/crypto_currency.dart';
585 +import 'package:cw_core/erc20_token.dart';
586 +import 'package:cw_core/output_info.dart';
587 +import 'package:cw_core/transaction_info.dart';
588 +import 'package:cw_core/transaction_priority.dart';
589 +import 'package:cw_core/wallet_base.dart';
590 +import 'package:cw_core/wallet_credentials.dart';
591 +import 'package:cw_core/wallet_info.dart';
592 +import 'package:cw_core/wallet_service.dart';
593 +import 'package:cw_ethereum/ethereum_mnemonics.dart';
594 +import 'package:eth_sig_util/util/utils.dart';
595 +import 'package:hive/hive.dart';
596 +import 'package:web3dart/web3dart.dart';
597 +""";
598 + const polygonCWHeaders = """
599 +import 'package:cw_polygon/polygon_formatter.dart';
600 +import 'package:cw_polygon/polygon_transaction_credentials.dart';
601 +import 'package:cw_polygon/polygon_transaction_info.dart';
602 +import 'package:cw_polygon/polygon_wallet.dart';
603 +import 'package:cw_polygon/polygon_wallet_creation_credentials.dart';
604 +import 'package:cw_polygon/polygon_wallet_service.dart';
605 +import 'package:cw_polygon/polygon_transaction_priority.dart';
606 +""";
607 + const polygonCwPart = "part 'cw_polygon.dart';";
608 + const polygonContent = """
609 +abstract class Polygon {
610 + List<String> getPolygonWordList(String language);
611 + WalletService createPolygonWalletService(Box<WalletInfo> walletInfoSource);
612 + WalletCredentials createPolygonNewWalletCredentials({required String name, WalletInfo? walletInfo});
613 + WalletCredentials createPolygonRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password});
614 + WalletCredentials createPolygonRestoreWalletFromPrivateKey({required String name, required String privateKey, required String password});
615 + String getAddress(WalletBase wallet);
616 + String getPrivateKey(WalletBase wallet);
617 + String getPublicKey(WalletBase wallet);
618 + TransactionPriority getDefaultTransactionPriority();
619 + TransactionPriority getPolygonTransactionPrioritySlow();
620 + List<TransactionPriority> getTransactionPriorities();
621 + TransactionPriority deserializePolygonTransactionPriority(int raw);
622 +
623 + Object createPolygonTransactionCredentials(
624 + List<Output> outputs, {
625 + required TransactionPriority priority,
626 + required CryptoCurrency currency,
627 + int? feeRate,
628 + });
629 +
630 + Object createPolygonTransactionCredentialsRaw(
631 + List<OutputInfo> outputs, {
632 + TransactionPriority? priority,
633 + required CryptoCurrency currency,
634 + required int feeRate,
635 + });
636 +
637 + int formatterPolygonParseAmount(String amount);
638 + double formatterPolygonAmountToDouble({TransactionInfo? transaction, BigInt? amount, int exponent = 18});
639 + List<Erc20Token> getERC20Currencies(WalletBase wallet);
640 + Future<void> addErc20Token(WalletBase wallet, Erc20Token token);
641 + Future<void> deleteErc20Token(WalletBase wallet, Erc20Token token);
642 + Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress);
643 +
644 + CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction);
645 + void updatePolygonScanUsageState(WalletBase wallet, bool isEnabled);
646 + Web3Client? getWeb3Client(WalletBase wallet);
647 +}
648 + """;
649 +
650 + const polygonEmptyDefinition = 'Polygon? polygon;\n';
651 + const polygonCWDefinition = 'Polygon? polygon = CWPolygon();\n';
652 +
653 + final output = '$polygonCommonHeaders\n' +
654 + (hasImplementation ? '$polygonCWHeaders\n' : '\n') +
655 + (hasImplementation ? '$polygonCwPart\n\n' : '\n') +
656 + (hasImplementation ? polygonCWDefinition : polygonEmptyDefinition) +
657 + '\n' +
658 + polygonContent;
659 +
660 + if (outputFile.existsSync()) {
661 + await outputFile.delete();
662 + }
663 +
664 + await outputFile.writeAsString(output);
665 +}
666 +
667 Future<void> generateBitcoinCash(bool hasImplementation) async {
668 final outputFile = File(bitcoinCashOutputPath);
669 const bitcoinCashCommonHeaders = """
@@ -783,7 +875,8 @@ Future<void> generatePubspec(
875 required bool hasEthereum,
876 required bool hasNano,
877 required bool hasBanano,
786 - required bool hasBitcoinCash}) async {
878 + required bool hasBitcoinCash,
879 + required bool hasPolygon}) async {
880 const cwCore = """
881 cw_core:
882 path: ./cw_core
@@ -820,6 +913,10 @@ Future<void> generatePubspec(
913 cw_banano:
914 path: ./cw_banano
915 """;
916 + const cwPolygon = """
917 + cw_polygon:
918 + path: ./cw_polygon
919 + """;
920 final inputFile = File(pubspecOutputPath);
921 final inputText = await inputFile.readAsString();
922 final inputLines = inputText.split('\n');
@@ -850,6 +947,10 @@ Future<void> generatePubspec(
947 output += '\n$cwBitcoinCash';
948 }
949
950 + if (hasPolygon) {
951 + output += '\n$cwPolygon';
952 + }
953 +
954 if (hasHaven && !hasMonero) {
955 output += '\n$cwSharedExternal\n$cwHaven';
956 } else if (hasHaven) {
@@ -875,7 +976,8 @@ Future<void> generateWalletTypes(
976 required bool hasEthereum,
977 required bool hasNano,
978 required bool hasBanano,
878 - required bool hasBitcoinCash}) async {
979 + required bool hasBitcoinCash,
980 + required bool hasPolygon}) async {
981 final walletTypesFile = File(walletTypesPath);
982
983 if (walletTypesFile.existsSync()) {
@@ -906,6 +1008,10 @@ Future<void> generateWalletTypes(
1008 outputContent += '\tWalletType.bitcoinCash,\n';
1009 }
1010
1011 + if (hasPolygon) {
1012 + outputContent += '\tWalletType.polygon,\n';
1013 + }
1014 +
1015 if (hasNano) {
1016 outputContent += '\tWalletType.nano,\n';
1017 }
tool/utils/secret_key.dart
+1
@@ -41,6 +41,7 @@ class SecretKey {
41
42 static final ethereumSecrets = [
43 SecretKey('etherScanApiKey', () => ''),
44 + SecretKey('polygonScanApiKey', () => ''),
45 ];
46
47 final String name;