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>
This commit is contained in:
Adegoke David 2023-12-02 03:26:43 +01:00 committed by GitHub
parent 3b7f9a297c
commit b3d579c24a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
116 changed files with 2351 additions and 206 deletions

View file

@ -97,6 +97,7 @@ jobs:
cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_polygon && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
flutter packages pub run build_runner build --delete-conflicting-outputs
- name: Add secrets
@ -131,6 +132,7 @@ jobs:
echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart
echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart
echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart
echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart
echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart

1
.gitignore vendored
View file

@ -126,6 +126,7 @@ lib/haven/haven.dart
lib/ethereum/ethereum.dart
lib/bitcoin_cash/bitcoin_cash.dart
lib/nano/nano.dart
lib/polygon/polygon.dart
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png

View file

@ -62,6 +62,9 @@
<data android:scheme="bitcoincash" />
<data android:scheme="bitcoincash-wallet" />
<data android:scheme="bitcoincash_wallet" />
<data android:scheme="polygon" />
<data android:scheme="polygon-wallet" />
<data android:scheme="polygon_wallet" />
</intent-filter>
</activity>
<meta-data

View file

@ -0,0 +1,6 @@
-
uri: polygon-bor.publicnode.com
-
uri: polygon-rpc.com
-
uri: polygon.llamarpc.com

View file

@ -19,6 +19,8 @@ CryptoCurrency currencyForWalletType(WalletType type) {
return CryptoCurrency.nano;
case WalletType.banano:
return CryptoCurrency.banano;
case WalletType.polygon:
return CryptoCurrency.maticpoly;
default:
throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
}

View file

@ -58,6 +58,7 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin {
static const typeId = ERC20_TOKEN_TYPE_ID;
static const boxName = 'Erc20Tokens';
static const polygonBoxName = ' PolygonErc20Tokens';
@override
bool operator ==(other) => (other is Erc20Token && other.contractAddress == contractAddress) ||

View file

@ -88,6 +88,8 @@ class Node extends HiveObject with Keyable {
} else {
return Uri.http(uriRaw, '');
}
case WalletType.polygon:
return Uri.https(uriRaw, '');
default:
throw Exception('Unexpected type ${type.toString()} for Node uri');
}
@ -146,6 +148,8 @@ class Node extends HiveObject with Keyable {
case WalletType.nano:
case WalletType.banano:
return requestNanoNode();
case WalletType.polygon:
return requestElectrumServer();
default:
return false;
}

View file

@ -13,6 +13,7 @@ const walletTypes = [
WalletType.bitcoinCash,
WalletType.nano,
WalletType.banano,
WalletType.polygon,
];
@HiveType(typeId: WALLET_TYPE_TYPE_ID)
@ -44,6 +45,8 @@ enum WalletType {
@HiveField(8)
bitcoinCash,
@HiveField(9)
polygon
}
int serializeToInt(WalletType type) {
@ -64,6 +67,8 @@ int serializeToInt(WalletType type) {
return 6;
case WalletType.bitcoinCash:
return 7;
case WalletType.polygon:
return 8;
default:
return -1;
}
@ -87,6 +92,8 @@ WalletType deserializeFromInt(int raw) {
return WalletType.banano;
case 7:
return WalletType.bitcoinCash;
case 8:
return WalletType.polygon;
default:
throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
}
@ -110,6 +117,8 @@ String walletTypeToString(WalletType type) {
return 'Nano';
case WalletType.banano:
return 'Banano';
case WalletType.polygon:
return 'Polygon';
default:
return '';
}
@ -133,6 +142,8 @@ String walletTypeToDisplayName(WalletType type) {
return 'Nano (XNO)';
case WalletType.banano:
return 'Banano (BAN)';
case WalletType.polygon:
return 'Polygon (MATIC)';
default:
return '';
}
@ -156,7 +167,10 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
return CryptoCurrency.nano;
case WalletType.banano:
return CryptoCurrency.banano;
case WalletType.polygon:
return CryptoCurrency.maticpoly;
default:
throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
throw Exception(
'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
}
}

View file

@ -15,12 +15,12 @@ import 'package:cw_ethereum/ethereum_transaction_priority.dart';
import 'package:cw_ethereum/.secrets.g.dart' as secrets;
class EthereumClient {
final _httpClient = Client();
final httpClient = Client();
Web3Client? _client;
bool connect(Node node) {
try {
_client = Web3Client(node.uri.toString(), _httpClient);
_client = Web3Client(node.uri.toString(), httpClient);
return true;
} catch (e) {
@ -74,9 +74,11 @@ class EthereumClient {
required int exponent,
String? contractAddress,
}) async {
assert(currency == CryptoCurrency.eth || contractAddress != null);
assert(currency == CryptoCurrency.eth ||
currency == CryptoCurrency.maticpoly ||
contractAddress != null);
bool _isEthereum = currency == CryptoCurrency.eth;
bool _isEVMCompatibleChain = currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly;
final price = _client!.getGasPrice();
@ -84,19 +86,23 @@ class EthereumClient {
from: privateKey.address,
to: EthereumAddress.fromHex(toAddress),
maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
value: _isEthereum ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
value: _isEVMCompatibleChain ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
);
final signedTransaction = await _client!.signTransaction(privateKey, transaction);
final chainId = _getChainIdForCurrency(currency);
final signedTransaction =
await _client!.signTransaction(privateKey, transaction, chainId: chainId);
final Function _sendTransaction;
if (_isEthereum) {
if (_isEVMCompatibleChain) {
_sendTransaction = () async => await sendTransaction(signedTransaction);
} else {
final erc20 = ERC20(
client: _client!,
address: EthereumAddress.fromHex(contractAddress!),
chainId: chainId,
);
_sendTransaction = () async {
@ -118,6 +124,16 @@ class EthereumClient {
);
}
int _getChainIdForCurrency(CryptoCurrency currency) {
switch (currency) {
case CryptoCurrency.maticpoly:
return 137;
case CryptoCurrency.eth:
default:
return 1;
}
}
Future<String> sendTransaction(Uint8List signedTransaction) async =>
await _client!.sendRawTransaction(prependTransactionType(0x02, signedTransaction));
@ -198,7 +214,7 @@ I/flutter ( 4474): Gas Used: 53000
Future<List<EthereumTransactionModel>> fetchTransactions(String address,
{String? contractAddress}) async {
try {
final response = await _httpClient.get(Uri.https("api.etherscan.io", "/api", {
final response = await httpClient.get(Uri.https("api.etherscan.io", "/api", {
"module": "account",
"action": contractAddress != null ? "tokentx" : "txlist",
if (contractAddress != null) "contractaddress": contractAddress,

View file

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:cw_core/format_amount.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
@ -34,8 +36,10 @@ class EthereumTransactionInfo extends TransactionInfo {
final String? to;
@override
String amountFormatted() =>
'${formatAmount((ethAmount / BigInt.from(10).pow(exponent)).toString())} $tokenSymbol';
String amountFormatted() {
final amount = formatAmount((ethAmount / BigInt.from(10).pow(exponent)).toString());
return '${amount.substring(0, min(10, amount.length))} $tokenSymbol';
}
@override
String fiatAmount() => _fiatAmount ?? '';
@ -44,7 +48,10 @@ class EthereumTransactionInfo extends TransactionInfo {
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
@override
String feeFormatted() => '${(ethFee / BigInt.from(10).pow(18)).toString()} ETH';
String feeFormatted() {
final amount = (ethFee / BigInt.from(10).pow(18)).toString();
return '${amount.substring(0, min(10, amount.length))} ETH';
}
factory EthereumTransactionInfo.fromJson(Map<String, dynamic> data) {
return EthereumTransactionInfo(

View file

@ -1,3 +1,4 @@
//! Model used for in parsing transactions fetched using etherscan
class EthereumTransactionModel {
final DateTime date;
final String hash;

View file

@ -21,8 +21,8 @@ class PendingEthereumTransaction with PendingTransaction {
@override
String get amountFormatted {
final _amount = BigInt.parse(amount) / BigInt.from(pow(10, exponent));
return _amount.toStringAsFixed(min(15, _amount.toString().length));
final _amount = (BigInt.parse(amount) / BigInt.from(pow(10, exponent))).toString();
return _amount.substring(0, min(10, _amount.length));
}
@override
@ -30,8 +30,8 @@ class PendingEthereumTransaction with PendingTransaction {
@override
String get feeFormatted {
final _fee = fee / BigInt.from(pow(10, 18));
return _fee.toStringAsFixed(min(15, _fee.toString().length));
final _fee = (fee / BigInt.from(pow(10, 18))).toString();
return _fee.substring(0, min(10, _fee.length));
}
@override

30
cw_polygon/.gitignore vendored Normal file
View file

@ -0,0 +1,30 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.packages
build/

10
cw_polygon/.metadata Normal file
View file

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
channel: stable
project_type: package

3
cw_polygon/CHANGELOG.md Normal file
View file

@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.

1
cw_polygon/LICENSE Normal file
View file

@ -0,0 +1 @@
TODO: Add your license here.

39
cw_polygon/README.md Normal file
View file

@ -0,0 +1,39 @@
<!--
This README describes the package. If you publish this package to pub.dev,
this README's contents appear on the landing page for your package.
For information about how to write a good package README, see the guide for
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
For general information about developing packages, see the Dart guide for
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
and the Flutter guide for
[developing packages and plugins](https://flutter.dev/developing-packages).
-->
TODO: Put a short description of the package here that helps potential users
know whether this package might be useful for them.
## Features
TODO: List what your package can do. Maybe include images, gifs, or videos.
## Getting started
TODO: List prerequisites and provide or point to information on how to
start using the package.
## Usage
TODO: Include short and useful examples for package users. Add longer examples
to `/example` folder.
```dart
const like = 'sample';
```
## Additional information
TODO: Tell users more about the package: where to find more information, how to
contribute to the package, how to file issues, what response they can expect
from the package authors, and more.

View file

@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View file

@ -0,0 +1,7 @@
library cw_polygon;
/// A Calculator.
class Calculator {
/// Returns [value] plus 1.
int addOne(int value) => value + 1;
}

View file

@ -0,0 +1,86 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
class DefaultPolygonErc20Tokens {
final List<Erc20Token> _defaultTokens = [
Erc20Token(
name: "Wrapped Ether",
symbol: "WETH",
contractAddress: "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619",
decimal: 18,
enabled: false,
),
Erc20Token(
name: "Tether USD (PoS)",
symbol: "USDT",
contractAddress: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
decimal: 6,
enabled: true,
),
Erc20Token(
name: "USD Coin",
symbol: "USDC",
contractAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
decimal: 6,
enabled: true,
),
Erc20Token(
name: "USD Coin (POS)",
symbol: "USDC.e",
contractAddress: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
decimal: 6,
enabled: false,
),
Erc20Token(
name: "Avalanche Token",
symbol: "AVAX",
contractAddress: "0x2C89bbc92BD86F8075d1DEcc58C7F4E0107f286b",
decimal: 18,
enabled: false,
),
Erc20Token(
name: "Wrapped BTC (PoS)",
symbol: "WBTC",
contractAddress: "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6",
decimal: 8,
enabled: false,
),
Erc20Token(
name: "Dai (PoS)",
symbol: "DAI",
contractAddress: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
decimal: 18,
enabled: true,
),
Erc20Token(
name: "SHIBA INU (PoS)",
symbol: "SHIB",
contractAddress: "0x6f8a06447Ff6FcF75d803135a7de15CE88C1d4ec",
decimal: 18,
enabled: false,
),
Erc20Token(
name: "Uniswap (PoS)",
symbol: "UNI",
contractAddress: "0xb33EaAd8d922B1083446DC23f610c2567fB5180f",
decimal: 18,
enabled: false,
),
];
List<Erc20Token> get initialPolygonErc20Tokens => _defaultTokens.map((token) {
String? iconPath;
try {
iconPath = CryptoCurrency.all
.firstWhere((element) =>
element.title.toUpperCase() == token.symbol.toUpperCase())
.iconPath;
} catch (_) {}
if (iconPath != null) {
return Erc20Token.copyWith(token, iconPath);
}
return token;
}).toList();
}

View file

@ -0,0 +1,19 @@
import 'dart:typed_data';
import 'package:cw_ethereum/pending_ethereum_transaction.dart';
class PendingPolygonTransaction extends PendingEthereumTransaction {
PendingPolygonTransaction({
required Function sendTransaction,
required Uint8List signedTransaction,
required BigInt fee,
required String amount,
required int exponent,
}) : super(
amount: amount,
sendTransaction: sendTransaction,
signedTransaction: signedTransaction,
fee: fee,
exponent: exponent,
);
}

View file

@ -0,0 +1,34 @@
import 'dart:convert';
import 'package:cw_ethereum/ethereum_client.dart';
import 'package:cw_polygon/polygon_transaction_model.dart';
import 'package:cw_ethereum/.secrets.g.dart' as secrets;
class PolygonClient extends EthereumClient {
@override
Future<List<PolygonTransactionModel>> fetchTransactions(String address,
{String? contractAddress}) async {
try {
final response = await httpClient.get(Uri.https("api.polygonscan.com", "/api", {
"module": "account",
"action": contractAddress != null ? "tokentx" : "txlist",
if (contractAddress != null) "contractaddress": contractAddress,
"address": address,
"apikey": secrets.polygonScanApiKey,
}));
final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
return (jsonResponse['result'] as List)
.map((e) => PolygonTransactionModel.fromJson(e as Map<String, dynamic>))
.toList();
}
return [];
} catch (e) {
print(e);
return [];
}
}
}

View file

@ -0,0 +1,6 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_ethereum/ethereum_exceptions.dart';
class PolygonTransactionCreationException extends EthereumTransactionCreationException {
PolygonTransactionCreationException(CryptoCurrency currency) : super(currency);
}

View file

@ -0,0 +1,25 @@
import 'package:intl/intl.dart';
const polygonAmountLength = 12;
const polygonAmountDivider = 1000000000000;
final polygonAmountFormat = NumberFormat()
..maximumFractionDigits = polygonAmountLength
..minimumFractionDigits = 1;
class PolygonFormatter {
static int parsePolygonAmount(String amount) {
try {
return (double.parse(amount) * polygonAmountDivider).round();
} catch (_) {
return 0;
}
}
static double parsePolygonAmountToDouble(int amount) {
try {
return amount / polygonAmountDivider;
} catch (_) {
return 0;
}
}
}

View file

@ -0,0 +1,5 @@
class PolygonMnemonicIsIncorrectException implements Exception {
@override
String toString() =>
'Polygon mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
}

View file

@ -0,0 +1,18 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/output_info.dart';
import 'package:cw_ethereum/ethereum_transaction_credentials.dart';
import 'package:cw_polygon/polygon_transaction_priority.dart';
class PolygonTransactionCredentials extends EthereumTransactionCredentials {
PolygonTransactionCredentials(
List<OutputInfo> outputs, {
required PolygonTransactionPriority? priority,
required CryptoCurrency currency,
final int? feeRate,
}) : super(
outputs,
currency: currency,
priority: priority,
feeRate: feeRate,
);
}

View file

@ -0,0 +1,77 @@
import 'dart:convert';
import 'dart:core';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_ethereum/file.dart';
import 'package:cw_polygon/polygon_transaction_info.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/transaction_history.dart';
part 'polygon_transaction_history.g.dart';
const transactionsHistoryFileName = 'polygon_transactions.json';
class PolygonTransactionHistory = PolygonTransactionHistoryBase with _$PolygonTransactionHistory;
abstract class PolygonTransactionHistoryBase extends TransactionHistoryBase<PolygonTransactionInfo>
with Store {
PolygonTransactionHistoryBase({required this.walletInfo, required String password})
: _password = password {
transactions = ObservableMap<String, PolygonTransactionInfo>();
}
final WalletInfo walletInfo;
String _password;
Future<void> init() async => await _load();
@override
Future<void> save() async {
try {
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
final path = '$dirPath/$transactionsHistoryFileName';
final data = json.encode({'transactions': transactions});
await writeData(path: path, password: _password, data: data);
} catch (e, s) {
print('Error while saving polygon transaction history: ${e.toString()}');
print(s);
}
}
@override
void addOne(PolygonTransactionInfo transaction) => transactions[transaction.id] = transaction;
@override
void addMany(Map<String, PolygonTransactionInfo> transactions) =>
this.transactions.addAll(transactions);
Future<Map<String, dynamic>> _read() async {
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
final path = '$dirPath/$transactionsHistoryFileName';
final content = await read(path: path, password: _password);
if (content.isEmpty) {
return {};
}
return json.decode(content) as Map<String, dynamic>;
}
Future<void> _load() async {
try {
final content = await _read();
final txs = content['transactions'] as Map<String, dynamic>? ?? {};
txs.entries.forEach((entry) {
final val = entry.value;
if (val is Map<String, dynamic>) {
final tx = PolygonTransactionInfo.fromJson(val);
_update(tx);
}
});
} catch (e) {
print(e);
}
}
void _update(PolygonTransactionInfo transaction) => transactions[transaction.id] = transaction;
}

View file

@ -0,0 +1,49 @@
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_ethereum/ethereum_transaction_info.dart';
class PolygonTransactionInfo extends EthereumTransactionInfo {
PolygonTransactionInfo({
required String id,
required int height,
required BigInt ethAmount,
int exponent = 18,
required TransactionDirection direction,
required DateTime date,
required bool isPending,
required BigInt ethFee,
required int confirmations,
String tokenSymbol = "MATIC",
required String? to,
}) : super(
confirmations: confirmations,
id: id,
height: height,
ethAmount: ethAmount,
exponent: exponent,
direction: direction,
date: date,
isPending: isPending,
ethFee: ethFee,
to: to,
tokenSymbol: tokenSymbol,
);
factory PolygonTransactionInfo.fromJson(Map<String, dynamic> data) {
return PolygonTransactionInfo(
id: data['id'] as String,
height: data['height'] as int,
ethAmount: BigInt.parse(data['amount']),
exponent: data['exponent'] as int,
ethFee: BigInt.parse(data['fee']),
direction: parseTransactionDirectionFromInt(data['direction'] as int),
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
isPending: data['isPending'] as bool,
confirmations: data['confirmations'] as int,
tokenSymbol: data['tokenSymbol'] as String,
to: data['to'],
);
}
@override
String feeFormatted() => '${(ethFee / BigInt.from(10).pow(18)).toString()} MATIC';
}

View file

@ -0,0 +1,49 @@
import 'package:cw_ethereum/ethereum_transaction_model.dart';
class PolygonTransactionModel extends EthereumTransactionModel {
PolygonTransactionModel({
required DateTime date,
required String hash,
required String from,
required String to,
required BigInt amount,
required int gasUsed,
required BigInt gasPrice,
required String contractAddress,
required int confirmations,
required int blockNumber,
required String? tokenSymbol,
required int? tokenDecimal,
required bool isError,
}) : super(
amount: amount,
date: date,
hash: hash,
from: from,
to: to,
gasPrice: gasPrice,
gasUsed: gasUsed,
confirmations: confirmations,
contractAddress: contractAddress,
blockNumber: blockNumber,
tokenDecimal: tokenDecimal,
tokenSymbol: tokenSymbol,
isError: isError,
);
factory PolygonTransactionModel.fromJson(Map<String, dynamic> json) => PolygonTransactionModel(
date: DateTime.fromMillisecondsSinceEpoch(int.parse(json["timeStamp"]) * 1000),
hash: json["hash"],
from: json["from"],
to: json["to"],
amount: BigInt.parse(json["value"]),
gasUsed: int.parse(json["gasUsed"]),
gasPrice: BigInt.parse(json["gasPrice"]),
contractAddress: json["contractAddress"],
confirmations: int.parse(json["confirmations"]),
blockNumber: int.parse(json["blockNumber"]),
tokenSymbol: json["tokenSymbol"] ?? "MATIC",
tokenDecimal: int.tryParse(json["tokenDecimal"] ?? ""),
isError: json["isError"] == "1",
);
}

View file

@ -0,0 +1,51 @@
import 'package:cw_ethereum/ethereum_transaction_priority.dart';
class PolygonTransactionPriority extends EthereumTransactionPriority {
const PolygonTransactionPriority({required String title, required int raw, required int tip})
: super(title: title, raw: raw, tip: tip);
static const List<PolygonTransactionPriority> all = [fast, medium, slow];
static const PolygonTransactionPriority slow =
PolygonTransactionPriority(title: 'slow', raw: 0, tip: 1);
static const PolygonTransactionPriority medium =
PolygonTransactionPriority(title: 'Medium', raw: 1, tip: 2);
static const PolygonTransactionPriority fast =
PolygonTransactionPriority(title: 'Fast', raw: 2, tip: 4);
static PolygonTransactionPriority deserialize({required int raw}) {
switch (raw) {
case 0:
return slow;
case 1:
return medium;
case 2:
return fast;
default:
throw Exception('Unexpected token: $raw for PolygonTransactionPriority deserialize');
}
}
@override
String get units => 'gas';
@override
String toString() {
var label = '';
switch (this) {
case PolygonTransactionPriority.slow:
label = 'Slow';
break;
case PolygonTransactionPriority.medium:
label = 'Medium';
break;
case PolygonTransactionPriority.fast:
label = 'Fast';
break;
default:
break;
}
return label;
}
}

View file

@ -0,0 +1,540 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_addresses.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_ethereum/erc20_balance.dart';
import 'package:cw_ethereum/ethereum_formatter.dart';
import 'package:cw_ethereum/ethereum_transaction_model.dart';
import 'package:cw_ethereum/file.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_polygon/default_erc20_tokens.dart';
import 'package:cw_polygon/polygon_client.dart';
import 'package:cw_polygon/polygon_exceptions.dart';
import 'package:cw_polygon/polygon_formatter.dart';
import 'package:cw_polygon/polygon_transaction_credentials.dart';
import 'package:cw_polygon/polygon_transaction_history.dart';
import 'package:cw_polygon/polygon_transaction_info.dart';
import 'package:cw_polygon/polygon_transaction_model.dart';
import 'package:cw_polygon/polygon_transaction_priority.dart';
import 'package:cw_polygon/polygon_wallet_addresses.dart';
import 'package:hive/hive.dart';
import 'package:hex/hex.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:web3dart/crypto.dart';
import 'package:web3dart/web3dart.dart';
import 'package:bip39/bip39.dart' as bip39;
import 'package:bip32/bip32.dart' as bip32;
part 'polygon_wallet.g.dart';
class PolygonWallet = PolygonWalletBase with _$PolygonWallet;
abstract class PolygonWalletBase extends WalletBase<ERC20Balance,
PolygonTransactionHistory, PolygonTransactionInfo> with Store {
PolygonWalletBase({
required WalletInfo walletInfo,
String? mnemonic,
String? privateKey,
required String password,
ERC20Balance? initialBalance,
}) : syncStatus = NotConnectedSyncStatus(),
_password = password,
_mnemonic = mnemonic,
_hexPrivateKey = privateKey,
_isTransactionUpdating = false,
_client = PolygonClient(),
walletAddresses = PolygonWalletAddresses(walletInfo),
balance = ObservableMap<CryptoCurrency, ERC20Balance>.of({
CryptoCurrency.maticpoly: initialBalance ?? ERC20Balance(BigInt.zero)
}),
super(walletInfo) {
this.walletInfo = walletInfo;
transactionHistory =
PolygonTransactionHistory(walletInfo: walletInfo, password: password);
if (!CakeHive.isAdapterRegistered(Erc20Token.typeId)) {
CakeHive.registerAdapter(Erc20TokenAdapter());
}
_sharedPrefs.complete(SharedPreferences.getInstance());
}
final String? _mnemonic;
final String? _hexPrivateKey;
final String _password;
late final Box<Erc20Token> polygonErc20TokensBox;
late final EthPrivateKey _polygonPrivateKey;
EthPrivateKey get polygonPrivateKey => _polygonPrivateKey;
late PolygonClient _client;
int? _gasPrice;
int? _estimatedGas;
bool _isTransactionUpdating;
// TODO: remove after integrating our own node and having eth_newPendingTransactionFilter
Timer? _transactionsUpdateTimer;
@override
WalletAddresses walletAddresses;
@override
@observable
SyncStatus syncStatus;
@override
@observable
late ObservableMap<CryptoCurrency, ERC20Balance> balance;
Completer<SharedPreferences> _sharedPrefs = Completer();
Future<void> init() async {
polygonErc20TokensBox =
await CakeHive.openBox<Erc20Token>(Erc20Token.polygonBoxName);
await walletAddresses.init();
await transactionHistory.init();
_polygonPrivateKey = await getPrivateKey(
mnemonic: _mnemonic,
privateKey: _hexPrivateKey,
password: _password,
);
walletAddresses.address = _polygonPrivateKey.address.toString();
await save();
}
@override
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
try {
if (priority is PolygonTransactionPriority) {
final priorityFee =
EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
return (_gasPrice! + priorityFee) * (_estimatedGas ?? 0);
}
return 0;
} catch (e) {
return 0;
}
}
@override
Future<void> changePassword(String password) {
throw UnimplementedError("changePassword");
}
@override
void close() {
_client.stop();
_transactionsUpdateTimer?.cancel();
}
@action
@override
Future<void> connectToNode({required Node node}) async {
try {
syncStatus = ConnectingSyncStatus();
final isConnected = _client.connect(node);
if (!isConnected) {
throw Exception("Polygon Node connection failed");
}
_client.setListeners(_polygonPrivateKey.address, _onNewTransaction);
_setTransactionUpdateTimer();
syncStatus = ConnectedSyncStatus();
} catch (e) {
syncStatus = FailedSyncStatus();
}
}
@override
Future<PendingTransaction> createTransaction(Object credentials) async {
final _credentials = credentials as PolygonTransactionCredentials;
final outputs = _credentials.outputs;
final hasMultiDestination = outputs.length > 1;
final CryptoCurrency transactionCurrency = balance.keys
.firstWhere((element) => element.title == _credentials.currency.title);
final _erc20Balance = balance[transactionCurrency]!;
BigInt totalAmount = BigInt.zero;
int exponent =
transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18;
num amountToPolygonMultiplier = pow(10, exponent);
// so far this can not be made with Polygon as Polygon does not support multiple recipients
if (hasMultiDestination) {
if (outputs.any(
(item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
throw PolygonTransactionCreationException(transactionCurrency);
}
final totalOriginalAmount = PolygonFormatter.parsePolygonAmountToDouble(
outputs.fold(
0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)));
totalAmount =
BigInt.from(totalOriginalAmount * amountToPolygonMultiplier);
if (_erc20Balance.balance < totalAmount) {
throw PolygonTransactionCreationException(transactionCurrency);
}
} else {
final output = outputs.first;
// since the fees are taken from Ethereum
// then no need to subtract the fees from the amount if send all
final BigInt allAmount;
if (transactionCurrency is Erc20Token) {
allAmount = _erc20Balance.balance;
} else {
allAmount = _erc20Balance.balance -
BigInt.from(calculateEstimatedFee(_credentials.priority!, null));
}
final totalOriginalAmount = EthereumFormatter.parseEthereumAmountToDouble(
output.formattedCryptoAmount ?? 0);
totalAmount = output.sendAll
? allAmount
: BigInt.from(totalOriginalAmount * amountToPolygonMultiplier);
if (_erc20Balance.balance < totalAmount) {
throw PolygonTransactionCreationException(transactionCurrency);
}
}
final pendingPolygonTransaction = await _client.signTransaction(
privateKey: _polygonPrivateKey,
toAddress: _credentials.outputs.first.isParsedAddress
? _credentials.outputs.first.extractedAddress!
: _credentials.outputs.first.address,
amount: totalAmount.toString(),
gas: _estimatedGas!,
priority: _credentials.priority!,
currency: transactionCurrency,
exponent: exponent,
contractAddress: transactionCurrency is Erc20Token
? transactionCurrency.contractAddress
: null,
);
return pendingPolygonTransaction;
}
Future<void> _updateTransactions() async {
try {
if (_isTransactionUpdating) {
return;
}
bool isPolygonScanEnabled = (await _sharedPrefs.future).getBool("use_polygonscan") ?? true;
if (!isPolygonScanEnabled) {
return;
}
_isTransactionUpdating = true;
final transactions = await fetchTransactions();
transactionHistory.addMany(transactions);
await transactionHistory.save();
_isTransactionUpdating = false;
} catch (_) {
_isTransactionUpdating = false;
}
}
@override
Future<Map<String, PolygonTransactionInfo>> fetchTransactions() async {
final address = _polygonPrivateKey.address.hex;
final transactions = await _client.fetchTransactions(address);
final List<Future<List<PolygonTransactionModel>>> polygonErc20TokensTransactions =
[];
for (var token in balance.keys) {
if (token is Erc20Token) {
polygonErc20TokensTransactions.add(_client.fetchTransactions(
address,
contractAddress: token.contractAddress,
) as Future<List<PolygonTransactionModel>>);
}
}
final tokensTransaction = await Future.wait(polygonErc20TokensTransactions);
transactions.addAll(tokensTransaction.expand((element) => element));
final Map<String, PolygonTransactionInfo> result = {};
for (var transactionModel in transactions) {
if (transactionModel.isError) {
continue;
}
result[transactionModel.hash] = PolygonTransactionInfo(
id: transactionModel.hash,
height: transactionModel.blockNumber,
ethAmount: transactionModel.amount,
direction: transactionModel.from == address
? TransactionDirection.outgoing
: TransactionDirection.incoming,
isPending: false,
date: transactionModel.date,
confirmations: transactionModel.confirmations,
ethFee:
BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice,
exponent: transactionModel.tokenDecimal ?? 18,
tokenSymbol: transactionModel.tokenSymbol ?? "MATIC",
to: transactionModel.to,
);
}
return result;
}
@override
Object get keys => throw UnimplementedError("keys");
@override
Future<void> rescan({required int height}) {
throw UnimplementedError("rescan");
}
@override
Future<void> save() async {
await walletAddresses.updateAddressesInBox();
final path = await makePath();
await write(path: path, password: _password, data: toJSON());
await transactionHistory.save();
}
@override
String? get seed => _mnemonic;
@override
String get privateKey => HEX.encode(_polygonPrivateKey.privateKey);
@action
@override
Future<void> startSync() async {
try {
syncStatus = AttemptingSyncStatus();
await _updateBalance();
await _updateTransactions();
_gasPrice = await _client.getGasUnitPrice();
_estimatedGas = await _client.getEstimatedGas();
Timer.periodic(const Duration(minutes: 1),
(timer) async => _gasPrice = await _client.getGasUnitPrice());
Timer.periodic(const Duration(seconds: 10),
(timer) async => _estimatedGas = await _client.getEstimatedGas());
syncStatus = SyncedSyncStatus();
} catch (e) {
syncStatus = FailedSyncStatus();
}
}
Future<String> makePath() async =>
pathForWallet(name: walletInfo.name, type: walletInfo.type);
String toJSON() => json.encode({
'mnemonic': _mnemonic,
'private_key': privateKey,
'balance': balance[currency]!.toJSON(),
});
static Future<PolygonWallet> open({
required String name,
required String password,
required WalletInfo walletInfo,
}) async {
final path = await pathForWallet(name: name, type: walletInfo.type);
final jsonSource = await read(path: path, password: password);
final data = json.decode(jsonSource) as Map;
final mnemonic = data['mnemonic'] as String?;
final privateKey = data['private_key'] as String?;
final balance = ERC20Balance.fromJSON(data['balance'] as String) ??
ERC20Balance(BigInt.zero);
return PolygonWallet(
walletInfo: walletInfo,
password: password,
mnemonic: mnemonic,
privateKey: privateKey,
initialBalance: balance,
);
}
Future<void> _updateBalance() async {
balance[currency] = await _fetchMaticBalance();
await _fetchErc20Balances();
await save();
}
Future<ERC20Balance> _fetchMaticBalance() async {
final balance = await _client.getBalance(_polygonPrivateKey.address);
return ERC20Balance(balance.getInWei);
}
Future<void> _fetchErc20Balances() async {
for (var token in polygonErc20TokensBox.values) {
try {
if (token.enabled) {
balance[token] = await _client.fetchERC20Balances(
_polygonPrivateKey.address,
token.contractAddress,
);
} else {
balance.remove(token);
}
} catch (_) {}
}
}
Future<EthPrivateKey> getPrivateKey(
{String? mnemonic, String? privateKey, required String password}) async {
assert(mnemonic != null || privateKey != null);
if (privateKey != null) {
return EthPrivateKey.fromHex(privateKey);
}
final seed = bip39.mnemonicToSeed(mnemonic!);
final root = bip32.BIP32.fromSeed(seed);
const _hdPathPolygon = "m/44'/60'/0'/0";
const index = 0;
final addressAtIndex = root.derivePath("$_hdPathPolygon/$index");
return EthPrivateKey.fromHex(
HEX.encode(addressAtIndex.privateKey as List<int>));
}
Future<void>? updateBalance() async => await _updateBalance();
List<Erc20Token> get erc20Currencies => polygonErc20TokensBox.values.toList();
Future<void> addErc20Token(Erc20Token token) async {
String? iconPath;
try {
iconPath = CryptoCurrency.all
.firstWhere((element) =>
element.title.toUpperCase() == token.symbol.toUpperCase())
.iconPath;
} catch (_) {}
final _token = Erc20Token(
name: token.name,
symbol: token.symbol,
contractAddress: token.contractAddress,
decimal: token.decimal,
enabled: token.enabled,
iconPath: iconPath,
);
await polygonErc20TokensBox.put(_token.contractAddress, _token);
if (_token.enabled) {
balance[_token] = await _client.fetchERC20Balances(
_polygonPrivateKey.address,
_token.contractAddress,
);
} else {
balance.remove(_token);
}
}
Future<void> deleteErc20Token(Erc20Token token) async {
await token.delete();
balance.remove(token);
_updateBalance();
}
Future<Erc20Token?> getErc20Token(String contractAddress) async =>
await _client.getErc20Token(contractAddress);
void _onNewTransaction() {
_updateBalance();
_updateTransactions();
}
void addInitialTokens() {
final initialErc20Tokens =
DefaultPolygonErc20Tokens().initialPolygonErc20Tokens;
for (var token in initialErc20Tokens) {
polygonErc20TokensBox.put(token.contractAddress, token);
}
}
@override
Future<void> renameWalletFiles(String newWalletName) async {
final currentWalletPath =
await pathForWallet(name: walletInfo.name, type: type);
final currentWalletFile = File(currentWalletPath);
final currentDirPath =
await pathForWalletDir(name: walletInfo.name, type: type);
final currentTransactionsFile =
File('$currentDirPath/$transactionsHistoryFileName');
// Copies current wallet files into new wallet name's dir and files
if (currentWalletFile.existsSync()) {
final newWalletPath =
await pathForWallet(name: newWalletName, type: type);
await currentWalletFile.copy(newWalletPath);
}
if (currentTransactionsFile.existsSync()) {
final newDirPath =
await pathForWalletDir(name: newWalletName, type: type);
await currentTransactionsFile
.copy('$newDirPath/$transactionsHistoryFileName');
}
// Delete old name's dir and files
await Directory(currentDirPath).delete(recursive: true);
}
void _setTransactionUpdateTimer() {
if (_transactionsUpdateTimer?.isActive ?? false) {
_transactionsUpdateTimer!.cancel();
}
_transactionsUpdateTimer = Timer.periodic(Duration(seconds: 10), (_) {
_updateTransactions();
_updateBalance();
});
}
void updatePolygonScanUsageState(bool isEnabled) {
if (isEnabled) {
_updateTransactions();
_setTransactionUpdateTimer();
} else {
_transactionsUpdateTimer?.cancel();
}
}
@override
String signMessage(String message, {String? address = null}) => bytesToHex(
_polygonPrivateKey.signPersonalMessageToUint8List(ascii.encode(message)));
Web3Client? getWeb3Client() => _client.getWeb3Client();
}

View file

@ -0,0 +1,5 @@
import 'package:cw_ethereum/ethereum_wallet_addresses.dart';
class PolygonWalletAddresses extends EthereumWalletAddresses {
PolygonWalletAddresses(super.walletInfo);
}

View file

@ -0,0 +1,28 @@
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
class PolygonNewWalletCredentials extends WalletCredentials {
PolygonNewWalletCredentials({required String name, WalletInfo? walletInfo})
: super(name: name, walletInfo: walletInfo);
}
class PolygonRestoreWalletFromSeedCredentials extends WalletCredentials {
PolygonRestoreWalletFromSeedCredentials(
{required String name,
required String password,
required this.mnemonic,
WalletInfo? walletInfo})
: super(name: name, password: password, walletInfo: walletInfo);
final String mnemonic;
}
class PolygonRestoreWalletFromPrivateKey extends WalletCredentials {
PolygonRestoreWalletFromPrivateKey(
{required String name,
required String password,
required this.privateKey,
WalletInfo? walletInfo})
: super(name: name, password: password, walletInfo: walletInfo);
final String privateKey;
}

View file

@ -0,0 +1,123 @@
import 'dart:io';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_ethereum/ethereum_mnemonics.dart';
import 'package:cw_polygon/polygon_wallet.dart';
import 'package:bip39/bip39.dart' as bip39;
import 'package:hive/hive.dart';
import 'polygon_wallet_creation_credentials.dart';
import 'package:collection/collection.dart';
class PolygonWalletService extends WalletService<PolygonNewWalletCredentials,
PolygonRestoreWalletFromSeedCredentials, PolygonRestoreWalletFromPrivateKey> {
PolygonWalletService(this.walletInfoSource);
final Box<WalletInfo> walletInfoSource;
@override
Future<PolygonWallet> create(PolygonNewWalletCredentials credentials) async {
final strength = (credentials.seedPhraseLength == 12)
? 128
: (credentials.seedPhraseLength == 24)
? 256
: 128;
final mnemonic = bip39.generateMnemonic(strength: strength);
final wallet = PolygonWallet(
walletInfo: credentials.walletInfo!,
mnemonic: mnemonic,
password: credentials.password!,
);
await wallet.init();
wallet.addInitialTokens();
await wallet.save();
return wallet;
}
@override
WalletType getType() => WalletType.polygon;
@override
Future<bool> isWalletExit(String name) async =>
File(await pathForWallet(name: name, type: getType())).existsSync();
@override
Future<PolygonWallet> openWallet(String name, String password) async {
final walletInfo =
walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
final wallet = await PolygonWalletBase.open(
name: name,
password: password,
walletInfo: walletInfo,
);
await wallet.init();
await wallet.save();
return wallet;
}
@override
Future<void> remove(String wallet) async {
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
final walletInfo = walletInfoSource.values
.firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
await walletInfoSource.delete(walletInfo.key);
}
@override
Future<PolygonWallet> restoreFromKeys(PolygonRestoreWalletFromPrivateKey credentials) async {
final wallet = PolygonWallet(
password: credentials.password!,
privateKey: credentials.privateKey,
walletInfo: credentials.walletInfo!,
);
await wallet.init();
wallet.addInitialTokens();
await wallet.save();
return wallet;
}
@override
Future<PolygonWallet> restoreFromSeed(PolygonRestoreWalletFromSeedCredentials credentials) async {
if (!bip39.validateMnemonic(credentials.mnemonic)) {
throw EthereumMnemonicIsIncorrectException();
}
final wallet = PolygonWallet(
password: credentials.password!,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!,
);
await wallet.init();
wallet.addInitialTokens();
await wallet.save();
return wallet;
}
@override
Future<void> rename(String currentName, String password, String newName) async {
final currentWalletInfo = walletInfoSource.values
.firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
final currentWallet = await PolygonWalletBase.open(
password: password, name: currentName, walletInfo: currentWalletInfo);
await currentWallet.renameWalletFiles(newName);
final newWalletInfo = currentWalletInfo;
newWalletInfo.id = WalletBase.idFor(newName, getType());
newWalletInfo.name = newName;
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
}
}

73
cw_polygon/pubspec.yaml Normal file
View file

@ -0,0 +1,73 @@
name: cw_polygon
description: A new Flutter package project.
version: 0.0.1
publish_to: none
author: Cake Wallet
homepage: https://cakewallet.com
environment:
sdk: '>=3.0.6 <4.0.0'
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
cw_core:
path: ../cw_core
cw_ethereum:
path: ../cw_ethereum
mobx: ^2.0.7+4
intl: ^0.18.0
bip39: ^1.0.6
hive: ^2.2.3
collection: ^1.17.1
web3dart: ^2.7.1
bip32: ^2.0.0
hex: ^0.2.0
shared_preferences: ^2.0.15
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
build_runner: ^2.1.11
mobx_codegen: ^2.0.7
hive_generator: ^1.1.3
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# To add assets to your package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/assets-and-images/#from-packages
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
# To add custom fonts to your package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/custom-fonts/#from-packages

View file

@ -0,0 +1,12 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:cw_polygon/cw_polygon.dart';
void main() {
test('adds one to input values', () {
final calculator = Calculator();
expect(calculator.addOne(2), 3);
expect(calculator.addOne(-7), -6);
expect(calculator.addOne(0), 1);
});
}

View file

@ -160,6 +160,26 @@
<string>bitcoincash-wallet</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>polygon</string>
<key>CFBundleURLSchemes</key>
<array>
<string>polygon</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLName</key>
<string>polygon-wallet</string>
<key>CFBundleURLSchemes</key>
<array>
<string>polygon-wallet</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>

View file

@ -271,6 +271,8 @@ class AddressValidator extends TextValidator {
'|([^0-9a-zA-Z]|^)ltc[a-zA-Z0-9]{26,45}([^0-9a-zA-Z]|\$)';
case CryptoCurrency.eth:
return '0x[0-9a-zA-Z]{42}';
case CryptoCurrency.maticpoly:
return '0x[0-9a-zA-Z]{42}';
case CryptoCurrency.nano:
return 'nano_[0-9a-zA-Z]{60}';
case CryptoCurrency.banano:

View file

@ -16,7 +16,7 @@ Future<double> _fetchPrice(Map<String, dynamic> args) async {
final Map<String, String> queryParams = {
'interval_count': '1',
'base': crypto,
'base': crypto.split(".").first,
'quote': fiat,
'key': secrets.fiatApiKey,
};

View file

@ -3,6 +3,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/haven/haven.dart';
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/entities/mnemonic_item.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
@ -34,6 +35,8 @@ class SeedValidator extends Validator<MnemonicItem> {
case WalletType.nano:
case WalletType.banano:
return nano!.getNanoWordList(language);
case WalletType.polygon:
return polygon!.getPolygonWordList(language);
default:
return [];
}

View file

@ -6,6 +6,7 @@ import 'package:cake_wallet/core/wallet_connect/eth_transaction_model.dart';
import 'package:cake_wallet/core/wallet_connect/evm_chain_id.dart';
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart';
import 'package:cake_wallet/store/app_store.dart';
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
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart';
import 'package:cake_wallet/src/screens/wallet_connect/utils/string_parsing.dart';
import 'package:convert/convert.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:eth_sig_util/eth_sig_util.dart';
import 'package:eth_sig_util/util/utils.dart';
import 'package:http/http.dart' as http;
@ -46,13 +46,12 @@ class EvmChainServiceImpl implements ChainService {
required this.wcKeyService,
required this.bottomSheetService,
required this.wallet,
Web3Client? ethClient,
}) : ethClient = ethClient ??
Web3Client? web3Client,
}) : ethClient = web3Client ??
Web3Client(
appStore.settingsStore.getCurrentNode(WalletType.ethereum).uri.toString(),
appStore.settingsStore.getCurrentNode(appStore.wallet!.type).uri.toString(),
http.Client(),
) {
for (final String event in getEvents()) {
wallet.registerEventEmitter(chainId: getChainId(), event: event);
}
@ -138,7 +137,8 @@ class EvmChainServiceImpl implements ChainService {
try {
// Load the private key
final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
final List<ChainKeyModel> keys = wcKeyService
.getKeysForChain(getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type));
final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey);
@ -176,13 +176,15 @@ class EvmChainServiceImpl implements ChainService {
try {
// Load the private key
final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
final List<ChainKeyModel> keys = wcKeyService
.getKeysForChain(getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type));
final EthPrivateKey credentials = EthPrivateKey.fromHex(keys[0].privateKey);
final String signature = hex.encode(
credentials.signPersonalMessageToUint8List(
Uint8List.fromList(utf8.encode(message)),
chainId: getChainIdBasedOnWalletType(appStore.wallet!.type),
),
);
log(signature);
@ -212,7 +214,8 @@ class EvmChainServiceImpl implements ChainService {
}
// Load the private key
final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
final List<ChainKeyModel> keys = wcKeyService
.getKeysForChain(getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type));
final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey);
@ -232,7 +235,11 @@ class EvmChainServiceImpl implements ChainService {
);
try {
final result = await ethClient.sendTransaction(credentials, transaction);
final result = await ethClient.sendTransaction(
credentials,
transaction,
chainId: getChainIdBasedOnWalletType(appStore.wallet!.type),
);
log('Result: $result');
@ -267,7 +274,8 @@ class EvmChainServiceImpl implements ChainService {
return authError;
}
final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
final List<ChainKeyModel> keys = wcKeyService
.getKeysForChain(getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type));
return EthSigUtil.signTypedData(
privateKey: keys[0].privateKey,

View file

@ -1,9 +1,11 @@
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cw_core/balance.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
abstract class WalletConnectKeyService {
/// Returns a list of all the keys.
@ -32,16 +34,36 @@ class KeyServiceImpl implements WalletConnectKeyService {
'eip155:42161',
'eip155:80001',
],
privateKey: ethereum!.getPrivateKey(wallet),
publicKey: ethereum!.getPublicKey(wallet),
privateKey: _getPrivateKeyForWallet(wallet),
publicKey: _getPublicKeyForWallet(wallet),
),
];
late final WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
late final List<ChainKeyModel> _keys;
static String _getPrivateKeyForWallet(WalletBase wallet) {
switch (wallet.type) {
case WalletType.ethereum:
return ethereum!.getPrivateKey(wallet);
case WalletType.polygon:
return polygon!.getPrivateKey(wallet);
default:
return '';
}
}
static String _getPublicKeyForWallet(WalletBase wallet) {
switch (wallet.type) {
case WalletType.ethereum:
return ethereum!.getPublicKey(wallet);
case WalletType.polygon:
return polygon!.getPublicKey(wallet);
default:
return '';
}
}
@override
List<String> getChains() {
final List<String> chainIds = [];

View file

@ -9,6 +9,7 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/wallet_connect/models/auth_request_model.dart';
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
import 'package:cake_wallet/core/wallet_connect/models/session_request_model.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_request_widget.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart';
@ -164,8 +165,10 @@ abstract class Web3WalletServiceBase with Store {
void _onSessionProposal(SessionProposalEvent? args) async {
if (args != null) {
final chaindIdNamespace = getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type);
final Widget modalWidget = Web3RequestModal(
child: ConnectionRequestWidget(
chaindIdNamespace: chaindIdNamespace,
wallet: _web3Wallet,
sessionProposal: SessionRequestModel(request: args.params),
),
@ -232,12 +235,13 @@ abstract class Web3WalletServiceBase with Store {
@action
Future<void> _onAuthRequest(AuthRequest? args) async {
if (args != null) {
List<ChainKeyModel> chainKeys = walletKeyService.getKeysForChain('eip155:1');
final chaindIdNamespace = getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type);
List<ChainKeyModel> chainKeys = walletKeyService.getKeysForChain(chaindIdNamespace);
// Create the message to be signed
final String iss = 'did:pkh:eip155:1:${chainKeys.first.publicKey}';
final String iss = 'did:pkh:$chaindIdNamespace:${chainKeys.first.publicKey}';
final Widget modalWidget = Web3RequestModal(
child: ConnectionRequestWidget(
chaindIdNamespace: chaindIdNamespace,
wallet: _web3Wallet,
authRequest: AuthRequestModel(iss: iss, request: args),
),

View file

@ -18,6 +18,8 @@ import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/ionia/ionia_anypay.dart';
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
import 'package:cake_wallet/ionia/ionia_tip.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dart';
import 'package:cake_wallet/src/screens/buy/buy_options_page.dart';
@ -750,7 +752,7 @@ Future<void> setup({
final wallet = getIt.get<AppStore>().wallet;
return ConnectionSyncPage(
getIt.get<DashboardViewModel>(),
wallet?.type == WalletType.ethereum ? getIt.get<Web3WalletService>() : null,
isEVMCompatibleChain(wallet!.type) ? getIt.get<Web3WalletService>() : null,
);
});
@ -847,6 +849,8 @@ Future<void> setup({
.createBitcoinCashWalletService(_walletInfoSource, _unspentCoinsInfoSource!);
case WalletType.nano:
return nano!.createNanoWalletService(_walletInfoSource);
case WalletType.polygon:
return polygon!.createPolygonWalletService(_walletInfoSource);
default:
throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
}

View file

@ -27,6 +27,7 @@ const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
const havenDefaultNodeUri = 'nodes.havenprotocol.org:443';
const ethereumDefaultNodeUri = 'ethereum.publicnode.com';
const polygonDefaultNodeUri = 'polygon-bor.publicnode.com';
const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002';
const nanoDefaultNodeUri = 'rpc.nano.to';
const nanoDefaultPowNodeUri = 'rpc.nano.to';
@ -65,6 +66,8 @@ Future<void> defaultSettingsMigration(
final migrationVersions =
List<int>.generate(migrationVersionsLength, (i) => currentVersion + (i + 1));
/// When you add a new case, increase the initialMigrationVersion parameter in the main.dart file.
/// This ensures that this switch case runs the newly added case.
await Future.forEach(migrationVersions, (int version) async {
try {
switch (version) {
@ -175,6 +178,11 @@ Future<void> defaultSettingsMigration(
await changeBitcoinCurrentElectrumServerToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
break;
case 24:
await addPolygonNodeList(nodes: nodes);
await changePolygonCurrentNodeToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
break;
case 25:
await rewriteSecureStoragePin(secureStorage: secureStorage);
break;
@ -332,6 +340,11 @@ Node? getEthereumDefaultNode({required Box<Node> nodes}) {
nodes.values.firstWhereOrNull((node) => node.type == WalletType.ethereum);
}
Node? getPolygonDefaultNode({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == polygonDefaultNodeUri) ??
nodes.values.firstWhereOrNull((node) => node.type == WalletType.polygon);
}
Node? getNanoDefaultNode({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == nanoDefaultNodeUri) ??
nodes.values.firstWhereOrNull((node) => node.type == WalletType.nano);
@ -575,6 +588,7 @@ Future<void> checkCurrentNodes(
sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
final currentHavenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
final currentEthereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
final currentPolygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
final currentBitcoinCashNodeId =
@ -589,6 +603,8 @@ Future<void> checkCurrentNodes(
nodeSource.values.firstWhereOrNull((node) => node.key == currentHavenNodeId);
final currentEthereumNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentEthereumNodeId);
final currentPolygonNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentPolygonNodeId);
final currentNanoNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId);
final currentNanoPowNodeServer =
@ -648,6 +664,12 @@ Future<void> checkCurrentNodes(
await nodeSource.add(node);
await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int);
}
if (currentPolygonNodeServer == null) {
final node = Node(uri: polygonDefaultNodeUri, type: WalletType.polygon);
await nodeSource.add(node);
await sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, node.key as int);
}
}
Future<void> resetBitcoinElectrumServer(
@ -742,3 +764,20 @@ Future<void> changeNanoCurrentPowNodeToDefault(
final nodeId = node?.key as int? ?? 0;
await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, nodeId);
}
Future<void> addPolygonNodeList({required Box<Node> nodes}) async {
final nodeList = await loadDefaultPolygonNodes();
for (var node in nodeList) {
if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
await nodes.add(node);
}
}
}
Future<void> changePolygonCurrentNodeToDefault(
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final node = getPolygonDefaultNode(nodes: nodes);
final nodeId = node?.key as int? ?? 0;
await sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, nodeId);
}

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:ens_dart/ens_dart.dart';
@ -13,6 +14,10 @@ class EnsRecord {
_client = ethereum!.getWeb3Client(wallet);
}
if (wallet != null && wallet.type == WalletType.polygon) {
_client = polygon!.getWeb3Client(wallet);
}
if (_client == null) {
_client = Web3Client("https://ethereum.publicnode.com", Client());
}
@ -31,6 +36,7 @@ class EnsRecord {
case WalletType.haven:
return await ens.withName(name).getCoinAddress(CoinType.XHV);
case WalletType.ethereum:
case WalletType.polygon:
default:
return (await ens.withName(name).getAddress()).hex;
}

View file

@ -19,7 +19,8 @@ class MainActions {
final bool Function(DashboardViewModel viewModel)? isEnabled;
final bool Function(DashboardViewModel viewModel)? canShow;
final Future<void> Function(BuildContext context, DashboardViewModel viewModel) onTap;
final Future<void> Function(
BuildContext context, DashboardViewModel viewModel) onTap;
MainActions._({
required this.name,
@ -52,6 +53,7 @@ class MainActions {
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.bitcoinCash:
switch (defaultBuyProvider) {
case BuyProviderType.AskEachTime:
@ -124,6 +126,7 @@ class MainActions {
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.bitcoinCash:
if (viewModel.isEnabledSellAction) {
final moonPaySellProvider = MoonPaySellProvider();

View file

@ -133,6 +133,22 @@ Future<List<Node>> loadDefaultNanoPowNodes() async {
return nodes;
}
Future<List<Node>> loadDefaultPolygonNodes() async {
final nodesRaw = await rootBundle.loadString('assets/polygon_node_list.yml');
final loadedNodes = loadYaml(nodesRaw) as YamlList;
final nodes = <Node>[];
for (final raw in loadedNodes) {
if (raw is Map) {
final node = Node.fromMap(Map<String, Object>.from(raw));
node.type = WalletType.polygon;
nodes.add(node);
}
}
return nodes;
}
Future<void> resetToDefault(Box<Node> nodeSource) async {
final moneroNodes = await loadDefaultNodes();
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
@ -141,6 +157,8 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
final havenNodes = await loadDefaultHavenNodes();
final ethereumNodes = await loadDefaultEthereumNodes();
final nanoNodes = await loadDefaultNanoNodes();
final polygonNodes = await loadDefaultPolygonNodes();
final nodes = moneroNodes +
bitcoinElectrumServerList +
@ -148,7 +166,8 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
havenNodes +
ethereumNodes +
bitcoinCashElectrumServerList +
nanoNodes;
nanoNodes +
polygonNodes;
await nodeSource.clear();
await nodeSource.addAll(nodes);

View file

@ -6,6 +6,7 @@ class PreferencesKey {
static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc';
static const currentHavenNodeIdKey = 'current_node_id_xhv';
static const currentEthereumNodeIdKey = 'current_node_id_eth';
static const currentPolygonNodeIdKey = 'current_node_id_matic';
static const currentNanoNodeIdKey = 'current_node_id_nano';
static const currentNanoPowNodeIdKey = 'current_node_id_nano_pow';
static const currentBananoNodeIdKey = 'current_node_id_banano';
@ -37,6 +38,7 @@ class PreferencesKey {
static const havenTransactionPriority = 'current_fee_priority_haven';
static const litecoinTransactionPriority = 'current_fee_priority_litecoin';
static const ethereumTransactionPriority = 'current_fee_priority_ethereum';
static const polygonTransactionPriority = 'current_fee_priority_polygon';
static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash';
static const shouldShowReceiveWarning = 'should_show_receive_warning';
static const shouldShowYatPopup = 'should_show_yat_popup';
@ -50,6 +52,7 @@ class PreferencesKey {
static const sortBalanceBy = 'sort_balance_by';
static const pinNativeTokenAtTop = 'pin_native_token_at_top';
static const useEtherscan = 'use_etherscan';
static const usePolygonScan = 'use_polygonscan';
static const defaultNanoRep = 'default_nano_representative';
static const defaultBananoRep = 'default_banano_representative';
static const lookupsTwitter = 'looks_up_twitter';

View file

@ -3,6 +3,7 @@ import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/haven/haven.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_type.dart';
@ -24,6 +25,8 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
case WalletType.nano:
case WalletType.banano:
return [];
case WalletType.polygon:
return polygon!.getTransactionPriorities();
default:
return [];
}

156
lib/polygon/cw_polygon.dart Normal file
View file

@ -0,0 +1,156 @@
part of 'polygon.dart';
class CWPolygon extends Polygon {
@override
List<String> getPolygonWordList(String language) => EthereumMnemonics.englishWordlist;
WalletService createPolygonWalletService(Box<WalletInfo> walletInfoSource) =>
PolygonWalletService(walletInfoSource);
@override
WalletCredentials createPolygonNewWalletCredentials({
required String name,
WalletInfo? walletInfo,
}) =>
PolygonNewWalletCredentials(name: name, walletInfo: walletInfo);
@override
WalletCredentials createPolygonRestoreWalletFromSeedCredentials({
required String name,
required String mnemonic,
required String password,
}) =>
PolygonRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic);
@override
WalletCredentials createPolygonRestoreWalletFromPrivateKey({
required String name,
required String privateKey,
required String password,
}) =>
PolygonRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
@override
String getAddress(WalletBase wallet) => (wallet as PolygonWallet).walletAddresses.address;
@override
String getPrivateKey(WalletBase wallet) {
final privateKeyHolder = (wallet as PolygonWallet).polygonPrivateKey;
String stringKey = bytesToHex(privateKeyHolder.privateKey);
return stringKey;
}
@override
String getPublicKey(WalletBase wallet) {
final privateKeyInUnitInt = (wallet as PolygonWallet).polygonPrivateKey;
final publicKey = privateKeyInUnitInt.address.hex;
return publicKey;
}
@override
TransactionPriority getDefaultTransactionPriority() => PolygonTransactionPriority.medium;
@override
TransactionPriority getPolygonTransactionPrioritySlow() => PolygonTransactionPriority.slow;
@override
List<TransactionPriority> getTransactionPriorities() => PolygonTransactionPriority.all;
@override
TransactionPriority deserializePolygonTransactionPriority(int raw) =>
PolygonTransactionPriority.deserialize(raw: raw);
Object createPolygonTransactionCredentials(
List<Output> outputs, {
required TransactionPriority priority,
required CryptoCurrency currency,
int? feeRate,
}) =>
PolygonTransactionCredentials(
outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount))
.toList(),
priority: priority as PolygonTransactionPriority,
currency: currency,
feeRate: feeRate,
);
Object createPolygonTransactionCredentialsRaw(
List<OutputInfo> outputs, {
TransactionPriority? priority,
required CryptoCurrency currency,
required int feeRate,
}) =>
PolygonTransactionCredentials(
outputs,
priority: priority as PolygonTransactionPriority?,
currency: currency,
feeRate: feeRate,
);
@override
int formatterPolygonParseAmount(String amount) => PolygonFormatter.parsePolygonAmount(amount);
@override
double formatterPolygonAmountToDouble(
{TransactionInfo? transaction, BigInt? amount, int exponent = 18}) {
assert(transaction != null || amount != null);
if (transaction != null) {
transaction as PolygonTransactionInfo;
return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
} else {
return (amount!) / BigInt.from(10).pow(exponent);
}
}
@override
List<Erc20Token> getERC20Currencies(WalletBase wallet) {
final polygonWallet = wallet as PolygonWallet;
return polygonWallet.erc20Currencies;
}
@override
Future<void> addErc20Token(WalletBase wallet, Erc20Token token) async =>
await (wallet as PolygonWallet).addErc20Token(token);
@override
Future<void> deleteErc20Token(WalletBase wallet, Erc20Token token) async =>
await (wallet as PolygonWallet).deleteErc20Token(token);
@override
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) async {
final polygonWallet = wallet as PolygonWallet;
return await polygonWallet.getErc20Token(contractAddress);
}
@override
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
transaction as PolygonTransactionInfo;
if (transaction.tokenSymbol == CryptoCurrency.maticpoly.title) {
return CryptoCurrency.maticpoly;
}
wallet as PolygonWallet;
return wallet.erc20Currencies.firstWhere(
(element) => transaction.tokenSymbol.toLowerCase() == element.symbol.toLowerCase());
}
@override
void updatePolygonScanUsageState(WalletBase wallet, bool isEnabled) {
(wallet as PolygonWallet).updatePolygonScanUsageState(isEnabled);
}
@override
Web3Client? getWeb3Client(WalletBase wallet) {
return (wallet as PolygonWallet).getWeb3Client();
}
}

View file

@ -3,9 +3,11 @@ import 'package:cake_wallet/core/fiat_conversion_service.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/update_haven_rate.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
@ -33,10 +35,18 @@ Future<void> startFiatRateUpdate(
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
}
Iterable<Erc20Token>? currencies;
if (appStore.wallet!.type == WalletType.ethereum) {
final currencies =
currencies =
ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
}
if (appStore.wallet!.type == WalletType.polygon) {
currencies =
polygon!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
}
if (currencies != null) {
for (final currency in currencies) {
() async {
fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice(

View file

@ -2,7 +2,8 @@ import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/update_haven_rate.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/balance.dart';
import 'package:cw_core/transaction_info.dart';
@ -107,10 +108,17 @@ void startCurrentWalletChangeReaction(
fiat: settingsStore.fiatCurrency,
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
Iterable<Erc20Token>? currencies;
if (wallet.type == WalletType.ethereum) {
final currencies =
currencies =
ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
}
if (wallet.type == WalletType.polygon) {
currencies =
polygon!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
}
if (currencies != null) {
for (final currency in currencies) {
() async {
fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice(

View file

@ -0,0 +1,46 @@
import 'package:cake_wallet/core/wallet_connect/evm_chain_id.dart';
import 'package:cw_core/wallet_type.dart';
bool isEVMCompatibleChain(WalletType walletType) {
switch (walletType) {
case WalletType.polygon:
case WalletType.ethereum:
return true;
default:
return false;
}
}
String getChainNameSpaceAndIdBasedOnWalletType(WalletType walletType) {
switch (walletType) {
case WalletType.ethereum:
return EVMChainId.ethereum.chain();
case WalletType.polygon:
return EVMChainId.polygon.chain();
default:
return '';
}
}
int getChainIdBasedOnWalletType(WalletType walletType) {
switch (walletType) {
case WalletType.polygon:
return 137;
// For now, we return eth chain Id as the default, we'll modify as we add more wallets
case WalletType.ethereum:
default:
return 1;
}
}
String getChainNameBasedOnWalletType(WalletType walletType) {
switch (walletType) {
case WalletType.ethereum:
return 'eth';
case WalletType.polygon:
return 'polygon';
default:
return '';
}
}

View file

@ -33,6 +33,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
final polygonIcon = Image.asset('assets/images/matic_icon.png', height: 24, width: 24);
final bitcoinCashIcon = Image.asset('assets/images/bch_icon.png', height: 24, width: 24);
final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
final bananoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
@ -150,6 +151,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
return nanoIcon;
case WalletType.banano:
return bananoIcon;
case WalletType.polygon:
return polygonIcon;
default:
return nonWalletTypeIcon;
}

View file

@ -1,5 +1,6 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/nft_listing_page.dart';
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';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@ -32,12 +32,12 @@ class BalancePage extends StatelessWidget {
Widget build(BuildContext context) {
return Observer(
builder: (context) {
final isEthereumWallet = dashboardViewModel.type == WalletType.ethereum;
final isEVMCompatible = isEVMCompatibleChain(dashboardViewModel.type);
return DefaultTabController(
length: isEthereumWallet ? 2 : 1,
length: isEVMCompatible ? 2 : 1,
child: Column(
children: [
if (isEthereumWallet)
if (isEVMCompatible)
Align(
alignment: Alignment.centerLeft,
child: Padding(
@ -66,7 +66,7 @@ class BalancePage extends StatelessWidget {
physics: NeverScrollableScrollPhysics(),
children: [
CryptoBalanceWidget(dashboardViewModel: dashboardViewModel),
if (isEthereumWallet) NFTListingPage(nftViewModel: nftViewModel)
if (isEVMCompatible) NFTListingPage(nftViewModel: nftViewModel)
],
),
),

View file

@ -32,7 +32,8 @@ class MenuWidgetState extends State<MenuWidget> {
this.ethereumIcon = Image.asset('assets/images/eth_icon.png'),
this.nanoIcon = Image.asset('assets/images/nano_icon.png'),
this.bananoIcon = Image.asset('assets/images/nano_icon.png'),
this.bitcoinCashIcon = Image.asset('assets/images/bch_icon.png');
this.bitcoinCashIcon = Image.asset('assets/images/bch_icon.png'),
this.polygonIcon = Image.asset('assets/images/matic_icon.png');
final largeScreen = 731;
@ -54,6 +55,8 @@ class MenuWidgetState extends State<MenuWidget> {
Image bitcoinCashIcon;
Image nanoIcon;
Image bananoIcon;
Image polygonIcon;
@override
void initState() {
@ -219,6 +222,8 @@ class MenuWidgetState extends State<MenuWidget> {
return nanoIcon;
case WalletType.banano:
return bananoIcon;
case WalletType.polygon:
return polygonIcon;
default:
throw Exception('No icon for ${type.toString()}');
}

View file

@ -1,5 +1,6 @@
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -23,6 +24,13 @@ class CurrencyInputField extends StatelessWidget {
final TextEditingController controller;
final bool isLight;
String get _currencyName {
if (selectedCurrency is CryptoCurrency) {
return (selectedCurrency as CryptoCurrency).title.toUpperCase();
}
return selectedCurrency.name.toUpperCase();
}
@override
Widget build(BuildContext context) {
final arrowBottomPurple = Image.asset(
@ -74,7 +82,7 @@ class CurrencyInputField extends StatelessWidget {
child: arrowBottomPurple,
),
Text(
selectedCurrency.name.toUpperCase(),
_currencyName,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
@ -83,7 +91,7 @@ class CurrencyInputField extends StatelessWidget {
),
if (selectedCurrency.tag != null)
Padding(
padding: const EdgeInsets.only(right: 3.0),
padding: const EdgeInsets.symmetric(horizontal: 3.0),
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,

View file

@ -2,9 +2,9 @@ import 'dart:async';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/totp_request_details.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
@ -169,7 +169,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
);
launchUri = null;
} else if (isWalletConnectLink) {
if (widget.appStore.wallet!.type == WalletType.ethereum) {
if (isEVMCompatibleChain(widget.appStore.wallet!.type)) {
widget.navigatorKey.currentState?.pushNamed(
Routes.walletConnectConnectionsListing,
arguments: launchUri,
@ -205,7 +205,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
String? _getRouteToGo() {
if (isWalletConnectLink) {
if (widget.appStore.wallet!.type != WalletType.ethereum) {
if (isEVMCompatibleChain(widget.appStore.wallet!.type)) {
_nonETHWalletErrorToast(S.current.switchToETHWallet);
return null;
}

View file

@ -13,7 +13,8 @@ class PreSeedPage extends BasePage {
PreSeedPage(this.type, this.advancedPrivacySettingsViewModel)
: imageLight = Image.asset('assets/images/pre_seed_light.png'),
imageDark = Image.asset('assets/images/pre_seed_dark.png'),
seedPhraseLength = advancedPrivacySettingsViewModel.seedPhraseLength.value {
seedPhraseLength =
advancedPrivacySettingsViewModel.seedPhraseLength.value {
wordsCount = _wordsCount(type, seedPhraseLength);
}
@ -40,14 +41,14 @@ class PreSeedPage extends BasePage {
alignment: Alignment.center,
padding: EdgeInsets.all(24),
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
constraints: BoxConstraints(
maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.3
),
maxHeight: MediaQuery.of(context).size.height * 0.3),
child: AspectRatio(aspectRatio: 1, child: image),
),
Padding(
@ -58,12 +59,14 @@ class PreSeedPage extends BasePage {
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
color: Theme.of(context)
.extension<CakeTextTheme>()!
.secondaryTextColor),
),
),
PrimaryButton(
onPressed: () =>
Navigator.of(context).popAndPushNamed(Routes.seed, arguments: true),
onPressed: () => Navigator.of(context)
.popAndPushNamed(Routes.seed, arguments: true),
text: S.of(context).pre_seed_button_text,
color: Theme.of(context).primaryColor,
textColor: Colors.white)
@ -79,6 +82,7 @@ class PreSeedPage extends BasePage {
return 25;
case WalletType.ethereum:
case WalletType.bitcoinCash:
case WalletType.polygon:
return seedPhraseLength;
default:
return 24;

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
@ -8,7 +9,6 @@ import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/view_model/settings/sync_mode.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
@ -85,7 +85,7 @@ class ConnectionSyncPage extends BasePage {
);
},
),
if (dashboardViewModel.wallet.type == WalletType.ethereum) ...[
if (isEVMCompatibleChain(dashboardViewModel.wallet.type)) ...[
WalletConnectTile(
onTap: () => Navigator.of(context).pushNamed(Routes.walletConnectConnectionsListing),
),
@ -101,6 +101,7 @@ class ConnectionSyncPage extends BasePage {
);
}
Future<void> _presentReconnectAlert(BuildContext context) async {
await showPopUp<void>(
context: context,

View file

@ -87,6 +87,14 @@ class PrivacyPage extends BasePage {
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setUseEtherscan(value);
}),
if (_privacySettingsViewModel.canUsePolygonScan)
SettingsSwitcherCell(
title: S.current.polygonscan_history,
value: _privacySettingsViewModel.usePolygonScan,
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setUsePolygonScan(value);
},
),
SettingsCellWithArrow(
title: S.current.domain_looks_up,
handler: (context) => Navigator.of(context).pushNamed(Routes.domainLookupsPage),

View file

@ -14,12 +14,14 @@ import 'connection_widget.dart';
class ConnectionRequestWidget extends StatefulWidget {
const ConnectionRequestWidget({
required this.wallet,
required this.chaindIdNamespace,
this.authRequest,
this.sessionProposal,
Key? key,
}) : super(key: key);
final Web3Wallet wallet;
final String chaindIdNamespace;
final AuthRequestModel? authRequest;
final SessionRequestModel? sessionProposal;
@ -52,23 +54,26 @@ class _ConnectionRequestWidgetState extends State<ConnectionRequestWidget> {
return _ConnectionMetadataDisplayWidget(
metadata: metadata,
wallet: widget.wallet,
authRequest: widget.authRequest,
sessionProposal: widget.sessionProposal,
wallet: widget.wallet,
chaindIdNamespace: widget.chaindIdNamespace,
);
}
}
class _ConnectionMetadataDisplayWidget extends StatelessWidget {
const _ConnectionMetadataDisplayWidget({
required this.metadata,
required this.wallet,
this.authRequest,
required this.metadata,
required this.sessionProposal,
required this.chaindIdNamespace,
this.authRequest,
});
final ConnectionMetadata? metadata;
final Web3Wallet wallet;
final String chaindIdNamespace;
final AuthRequestModel? authRequest;
final SessionRequestModel? sessionProposal;
@ -114,7 +119,11 @@ class _ConnectionMetadataDisplayWidget extends StatelessWidget {
const SizedBox(height: 8),
Visibility(
visible: authRequest != null,
child: _AuthRequestWidget(wallet: wallet, authRequest: authRequest),
child: _AuthRequestWidget(
wallet: wallet,
authRequest: authRequest,
chaindIdNamespace: chaindIdNamespace,
),
//If authRequest is null, sessionProposal is not null.
replacement: _SessionProposalWidget(sessionProposal: sessionProposal!),
@ -126,16 +135,21 @@ class _ConnectionMetadataDisplayWidget extends StatelessWidget {
}
class _AuthRequestWidget extends StatelessWidget {
const _AuthRequestWidget({required this.wallet, this.authRequest});
const _AuthRequestWidget({
required this.wallet,
required this.chaindIdNamespace,
this.authRequest,
});
final Web3Wallet wallet;
final String chaindIdNamespace;
final AuthRequestModel? authRequest;
@override
Widget build(BuildContext context) {
final model = ConnectionModel(
text: wallet.formatAuthMessage(
iss: 'did:pkh:eip155:1:${authRequest!.iss}',
iss: 'did:pkh:$chaindIdNamespace:${authRequest!.iss}',
cacaoPayload: CacaoRequestPayload.fromPayloadParams(
authRequest!.request.payloadParams,
),

View file

@ -49,6 +49,7 @@ class WalletListBodyState extends State<WalletListBody> {
final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
final bitcoinCashIcon = Image.asset('assets/images/bch_icon.png', height: 24, width: 24);
final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
final polygonIcon = Image.asset('assets/images/matic_icon.png', height: 24, width: 24);
final scrollController = ScrollController();
final double tileHeight = 60;
Flushbar<void>? _progressBar;
@ -256,6 +257,8 @@ class WalletListBodyState extends State<WalletListBody> {
return bitcoinCashIcon;
case WalletType.nano:
return nanoIcon;
case WalletType.polygon:
return polygonIcon;
default:
return nonWalletTypeIcon;
}

View file

@ -1,8 +1,8 @@
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/balance.dart';
import 'package:cw_core/wallet_base.dart';
@ -44,7 +44,7 @@ abstract class AppStoreBase with Store {
this.wallet = wallet;
this.wallet!.setExceptionHandler(ExceptionHandler.onError);
if (wallet.type == WalletType.ethereum) {
if (isEVMCompatibleChain(wallet.type)) {
getIt.get<Web3WalletService>().init();
}
}

View file

@ -12,6 +12,7 @@ import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/seed_phrase_length.dart';
import 'package:cake_wallet/entities/seed_type.dart';
import 'package:cake_wallet/entities/sort_balance_types.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
import 'package:cake_wallet/view_model/settings/sync_mode.dart';
import 'package:cake_wallet/utils/device_info.dart';
@ -88,6 +89,7 @@ abstract class SettingsStoreBase with Store {
required this.sortBalanceBy,
required this.pinNativeTokenAtTop,
required this.useEtherscan,
required this.usePolygonScan,
required this.defaultNanoRep,
required this.defaultBananoRep,
required this.lookupsTwitter,
@ -101,6 +103,7 @@ abstract class SettingsStoreBase with Store {
TransactionPriority? initialHavenTransactionPriority,
TransactionPriority? initialLitecoinTransactionPriority,
TransactionPriority? initialEthereumTransactionPriority,
TransactionPriority? initialPolygonTransactionPriority,
TransactionPriority? initialBitcoinCashTransactionPriority})
: nodes = ObservableMap<WalletType, Node>.of(nodes),
powNodes = ObservableMap<WalletType, Node>.of(powNodes),
@ -165,6 +168,10 @@ abstract class SettingsStoreBase with Store {
priority[WalletType.ethereum] = initialEthereumTransactionPriority;
}
if (initialPolygonTransactionPriority != null) {
priority[WalletType.polygon] = initialPolygonTransactionPriority;
}
if (initialBitcoinCashTransactionPriority != null) {
priority[WalletType.bitcoinCash] = initialBitcoinCashTransactionPriority;
}
@ -202,6 +209,9 @@ abstract class SettingsStoreBase with Store {
case WalletType.bitcoinCash:
key = PreferencesKey.bitcoinCashTransactionPriority;
break;
case WalletType.polygon:
key = PreferencesKey.polygonTransactionPriority;
break;
default:
key = null;
}
@ -245,8 +255,8 @@ abstract class SettingsStoreBase with Store {
reaction(
(_) => moneroSeedType,
(SeedType moneroSeedType) => sharedPreferences.setInt(
PreferencesKey.moneroSeedType, moneroSeedType.raw));
(SeedType moneroSeedType) =>
sharedPreferences.setInt(PreferencesKey.moneroSeedType, moneroSeedType.raw));
reaction(
(_) => fiatApiMode,
@ -343,8 +353,8 @@ abstract class SettingsStoreBase with Store {
reaction(
(_) => seedPhraseLength,
(SeedPhraseLength seedPhraseWordCount) =>
sharedPreferences.setInt(PreferencesKey.currentSeedPhraseLength, seedPhraseWordCount.value));
(SeedPhraseLength seedPhraseWordCount) => sharedPreferences.setInt(
PreferencesKey.currentSeedPhraseLength, seedPhraseWordCount.value));
reaction(
(_) => pinTimeOutDuration,
@ -388,6 +398,11 @@ abstract class SettingsStoreBase with Store {
(bool useEtherscan) =>
_sharedPreferences.setBool(PreferencesKey.useEtherscan, useEtherscan));
reaction(
(_) => usePolygonScan,
(bool usePolygonScan) =>
_sharedPreferences.setBool(PreferencesKey.usePolygonScan, usePolygonScan));
reaction((_) => defaultNanoRep,
(String nanoRep) => _sharedPreferences.setString(PreferencesKey.defaultNanoRep, nanoRep));
@ -412,18 +427,16 @@ abstract class SettingsStoreBase with Store {
reaction(
(_) => lookupsUnstoppableDomains,
(bool looksUpUnstoppableDomains) =>
_sharedPreferences.setBool(PreferencesKey.lookupsUnstoppableDomains, looksUpUnstoppableDomains));
(bool looksUpUnstoppableDomains) => _sharedPreferences.setBool(
PreferencesKey.lookupsUnstoppableDomains, looksUpUnstoppableDomains));
reaction(
(_) => lookupsOpenAlias,
(bool looksUpOpenAlias) =>
_sharedPreferences.setBool(PreferencesKey.lookupsOpenAlias, looksUpOpenAlias));
reaction(
(_) => lookupsENS,
(bool looksUpENS) =>
_sharedPreferences.setBool(PreferencesKey.lookupsENS, looksUpENS));
reaction((_) => lookupsENS,
(bool looksUpENS) => _sharedPreferences.setBool(PreferencesKey.lookupsENS, looksUpENS));
this.nodes.observe((change) {
if (change.newValue != null && change.key != null) {
@ -562,6 +575,9 @@ abstract class SettingsStoreBase with Store {
@observable
bool useEtherscan;
@observable
bool usePolygonScan;
@observable
String defaultNanoRep;
@ -651,6 +667,7 @@ abstract class SettingsStoreBase with Store {
TransactionPriority? havenTransactionPriority;
TransactionPriority? litecoinTransactionPriority;
TransactionPriority? ethereumTransactionPriority;
TransactionPriority? polygonTransactionPriority;
TransactionPriority? bitcoinCashTransactionPriority;
if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) {
@ -662,9 +679,13 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!);
}
if (sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority) != null) {
ethereumTransactionPriority = bitcoin?.deserializeLitecoinTransactionPriority(
ethereumTransactionPriority = ethereum?.deserializeEthereumTransactionPriority(
sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!);
}
if (sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority) != null) {
polygonTransactionPriority = polygon?.deserializePolygonTransactionPriority(
sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority)!);
}
if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) {
bitcoinCashTransactionPriority = bitcoinCash?.deserializeBitcoinCashTransactionPriority(
sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!);
@ -676,6 +697,7 @@ abstract class SettingsStoreBase with Store {
litecoinTransactionPriority ??= bitcoin?.getLitecoinTransactionPriorityMedium();
ethereumTransactionPriority ??= ethereum?.getDefaultTransactionPriority();
bitcoinCashTransactionPriority ??= bitcoinCash?.getDefaultTransactionPriority();
polygonTransactionPriority ??= polygon?.getDefaultTransactionPriority();
final currentBalanceDisplayMode = BalanceDisplayMode.deserialize(
raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!);
@ -749,12 +771,14 @@ abstract class SettingsStoreBase with Store {
final pinNativeTokenAtTop =
sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true;
final useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
final usePolygonScan = sharedPreferences.getBool(PreferencesKey.usePolygonScan) ?? true;
final defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? "";
final defaultBananoRep = sharedPreferences.getString(PreferencesKey.defaultBananoRep) ?? "";
final lookupsTwitter = sharedPreferences.getBool(PreferencesKey.lookupsTwitter) ?? true;
final lookupsMastodon = sharedPreferences.getBool(PreferencesKey.lookupsMastodon) ?? true;
final lookupsYatService = sharedPreferences.getBool(PreferencesKey.lookupsYatService) ?? true;
final lookupsUnstoppableDomains = sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true;
final lookupsUnstoppableDomains =
sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true;
final lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
final lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
@ -774,6 +798,7 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
final moneroNode = nodeSource.get(nodeId);
@ -781,6 +806,7 @@ abstract class SettingsStoreBase with Store {
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
final havenNode = nodeSource.get(havenNodeId);
final ethereumNode = nodeSource.get(ethereumNodeId);
final polygonNode = nodeSource.get(polygonNodeId);
final bitcoinCashElectrumServer = nodeSource.get(bitcoinCashElectrumServerId);
final nanoNode = nodeSource.get(nanoNodeId);
final nanoPowNode = powNodeSource.get(nanoPowNodeId);
@ -824,6 +850,10 @@ abstract class SettingsStoreBase with Store {
nodes[WalletType.ethereum] = ethereumNode;
}
if (polygonNode != null) {
nodes[WalletType.polygon] = polygonNode;
}
if (bitcoinCashElectrumServer != null) {
nodes[WalletType.bitcoinCash] = bitcoinCashElectrumServer;
}
@ -869,10 +899,12 @@ abstract class SettingsStoreBase with Store {
actionlistDisplayMode: actionListDisplayMode,
initialPinLength: pinLength,
pinTimeOutDuration: pinCodeTimeOutDuration,
seedPhraseLength: seedPhraseWordCount,initialLanguageCode: savedLanguageCode,
seedPhraseLength: seedPhraseWordCount,
initialLanguageCode: savedLanguageCode,
sortBalanceBy: sortBalanceBy,
pinNativeTokenAtTop: pinNativeTokenAtTop,
useEtherscan: useEtherscan,
usePolygonScan: usePolygonScan,
defaultNanoRep: defaultNanoRep,
defaultBananoRep: defaultBananoRep,
lookupsTwitter: lookupsTwitter,
@ -900,6 +932,7 @@ abstract class SettingsStoreBase with Store {
initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
initialEthereumTransactionPriority: ethereumTransactionPriority,
initialPolygonTransactionPriority: polygonTransactionPriority,
backgroundTasks: backgroundTasks,
initialSyncMode: savedSyncMode,
initialSyncAll: savedSyncAll,
@ -934,6 +967,11 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!) ??
priority[WalletType.ethereum]!;
}
if (sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority) != null) {
priority[WalletType.polygon] = polygon?.deserializePolygonTransactionPriority(
sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority)!) ??
priority[WalletType.polygon]!;
}
if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) {
priority[WalletType.bitcoinCash] = bitcoinCash?.deserializeBitcoinCashTransactionPriority(
sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!) ??
@ -1027,12 +1065,14 @@ abstract class SettingsStoreBase with Store {
.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? sortBalanceBy.index];
pinNativeTokenAtTop = sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true;
useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
usePolygonScan = sharedPreferences.getBool(PreferencesKey.usePolygonScan) ?? true;
defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? "";
defaultBananoRep = sharedPreferences.getString(PreferencesKey.defaultBananoRep) ?? "";
lookupsTwitter = sharedPreferences.getBool(PreferencesKey.lookupsTwitter) ?? true;
lookupsMastodon = sharedPreferences.getBool(PreferencesKey.lookupsMastodon) ?? true;
lookupsYatService = sharedPreferences.getBool(PreferencesKey.lookupsYatService) ?? true;
lookupsUnstoppableDomains = sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true;
lookupsUnstoppableDomains =
sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true;
lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
@ -1045,6 +1085,7 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
final moneroNode = nodeSource.get(nodeId);
@ -1052,6 +1093,7 @@ abstract class SettingsStoreBase with Store {
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
final havenNode = nodeSource.get(havenNodeId);
final ethereumNode = nodeSource.get(ethereumNodeId);
final polygonNode = nodeSource.get(polygonNodeId);
final bitcoinCashNode = nodeSource.get(bitcoinCashElectrumServerId);
final nanoNode = nodeSource.get(nanoNodeId);
@ -1075,6 +1117,10 @@ abstract class SettingsStoreBase with Store {
nodes[WalletType.ethereum] = ethereumNode;
}
if (polygonNode != null) {
nodes[WalletType.polygon] = polygonNode;
}
if (bitcoinCashNode != null) {
nodes[WalletType.bitcoinCash] = bitcoinCashNode;
}
@ -1110,6 +1156,9 @@ abstract class SettingsStoreBase with Store {
case WalletType.nano:
await _sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, node.key as int);
break;
case WalletType.polygon:
await _sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, node.key as int);
break;
default:
break;
}
@ -1141,7 +1190,6 @@ abstract class SettingsStoreBase with Store {
trocadorProviderStates[providerName] = state;
}
static Future<String?> _getDeviceName() async {
String? deviceName = '';
final deviceInfoPlugin = DeviceInfoPlugin();

View file

@ -1,5 +1,6 @@
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/sort_balance_types.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/balance.dart';
@ -81,7 +82,7 @@ abstract class BalanceViewModelBase with Store {
bool get isFiatDisabled => settingsStore.fiatApiMode == FiatApiMode.disabled;
@computed
bool get isHomeScreenSettingsEnabled => wallet.type == WalletType.ethereum;
bool get isHomeScreenSettingsEnabled => isEVMCompatibleChain(wallet.type);
@computed
bool get hasAccounts => wallet.type == WalletType.monero;
@ -123,6 +124,7 @@ abstract class BalanceViewModelBase with Store {
case WalletType.monero:
case WalletType.haven:
case WalletType.ethereum:
case WalletType.polygon:
return S.current.xmr_available_balance;
default:
return S.current.confirmed;
@ -135,6 +137,7 @@ abstract class BalanceViewModelBase with Store {
case WalletType.monero:
case WalletType.haven:
case WalletType.ethereum:
case WalletType.polygon:
return S.current.xmr_full_balance;
default:
return S.current.unconfirmed;
@ -272,7 +275,8 @@ abstract class BalanceViewModelBase with Store {
}
@computed
bool get hasAdditionalBalance => wallet.type != WalletType.ethereum;
bool get hasAdditionalBalance => !isEVMCompatibleChain(wallet.type);
@computed
List<BalanceRecord> get formattedBalances {

View file

@ -2,10 +2,12 @@ import 'package:cake_wallet/core/fiat_conversion_service.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/sort_balance_types.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
part 'home_settings_view_model.g.dart';
@ -42,18 +44,41 @@ abstract class HomeSettingsViewModelBase with Store {
void setPinNativeToken(bool value) => _settingsStore.pinNativeTokenAtTop = value;
Future<void> addErc20Token(Erc20Token token) async {
if (_balanceViewModel.wallet.type == WalletType.ethereum) {
await ethereum!.addErc20Token(_balanceViewModel.wallet, token);
}
if (_balanceViewModel.wallet.type == WalletType.polygon) {
await polygon!.addErc20Token(_balanceViewModel.wallet, token);
}
_updateTokensList();
_updateFiatPrices(token);
}
Future<void> deleteErc20Token(Erc20Token token) async {
if (_balanceViewModel.wallet.type == WalletType.ethereum) {
await ethereum!.deleteErc20Token(_balanceViewModel.wallet, token);
}
if (_balanceViewModel.wallet.type == WalletType.polygon) {
await polygon!.deleteErc20Token(_balanceViewModel.wallet, token);
}
_updateTokensList();
}
Future<Erc20Token?> getErc20Token(String contractAddress) async =>
await ethereum!.getErc20Token(_balanceViewModel.wallet, contractAddress);
Future<Erc20Token?> getErc20Token(String contractAddress) async {
if (_balanceViewModel.wallet.type == WalletType.ethereum) {
return await ethereum!.getErc20Token(_balanceViewModel.wallet, contractAddress);
}
if (_balanceViewModel.wallet.type == WalletType.polygon) {
return await polygon!.getErc20Token(_balanceViewModel.wallet, contractAddress);
}
return null;
}
CryptoCurrency get nativeToken => _balanceViewModel.wallet.currency;
@ -69,7 +94,12 @@ abstract class HomeSettingsViewModelBase with Store {
void changeTokenAvailability(Erc20Token token, bool value) async {
token.enabled = value;
if (_balanceViewModel.wallet.type == WalletType.ethereum) {
ethereum!.addErc20Token(_balanceViewModel.wallet, token);
}
if (_balanceViewModel.wallet.type == WalletType.polygon) {
polygon!.addErc20Token(_balanceViewModel.wallet, token);
}
_refreshTokensList();
}
@ -83,7 +113,8 @@ abstract class HomeSettingsViewModelBase with Store {
return -1;
} else if (e2.enabled && !e1.enabled) {
return 1;
} else if (!e1.enabled && !e2.enabled) { // if both are disabled then sort alphabetically
} else if (!e1.enabled && !e2.enabled) {
// if both are disabled then sort alphabetically
return e1.name.compareTo(e2.name);
}
@ -92,6 +123,7 @@ abstract class HomeSettingsViewModelBase with Store {
tokens.clear();
if (_balanceViewModel.wallet.type == WalletType.ethereum) {
tokens.addAll(ethereum!
.getERC20Currencies(_balanceViewModel.wallet)
.where((element) => _matchesSearchText(element))
@ -99,6 +131,15 @@ abstract class HomeSettingsViewModelBase with Store {
..sort(_sortFunc));
}
if (_balanceViewModel.wallet.type == WalletType.polygon) {
tokens.addAll(polygon!
.getERC20Currencies(_balanceViewModel.wallet)
.where((element) => _matchesSearchText(element))
.toList()
..sort(_sortFunc));
}
}
@action
void _refreshTokensList() {
final _tokens = Set.of(tokens);

View file

@ -1,10 +1,9 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:convert';
import 'dart:developer';
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:http/http.dart' as http;
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
@ -39,23 +38,26 @@ abstract class NFTViewModelBase with Store {
@action
Future<void> getNFTAssetByWallet() async {
if (appStore.wallet!.type != WalletType.ethereum) return;
if (!isEVMCompatibleChain(appStore.wallet!.type)) return;
final walletAddress = appStore.wallet!.walletInfo.address;
log('Fetching wallet NFTs for $walletAddress');
final chainName = getChainNameBasedOnWalletType(appStore.wallet!.type);
// the [chain] refers to the chain network that the nft is on
// the [format] refers to the number format type of the responses
// the [normalizedMetadata] field is a boolean that determines if
// the response would include a json string of the NFT Metadata that can be decoded
// and used within the wallet
// the [excludeSpam] field is a boolean that determines if spam nfts be excluded from the response.
final uri = Uri.https(
'deep-index.moralis.io',
'/api/v2.2/$walletAddress/nft',
{
"chain": "eth",
"chain": chainName,
"format": "decimal",
"media_items": "false",
"exclude_spam": "true",
"normalizeMetadata": "true",
},
);
@ -94,7 +96,7 @@ abstract class NFTViewModelBase with Store {
@action
Future<void> importNFT(String tokenAddress, String tokenId) async {
final chainName = getChainNameBasedOnWalletType(appStore.wallet!.type);
// the [chain] refers to the chain network that the nft is on
// the [format] refers to the number format type of the responses
// the [normalizedMetadata] field is a boolean that determines if
@ -104,7 +106,7 @@ abstract class NFTViewModelBase with Store {
'deep-index.moralis.io',
'/api/v2.2/nft/$tokenAddress/$tokenId',
{
"chain": "eth",
"chain": chainName,
"format": "decimal",
"media_items": "false",
"normalizeMetadata": "true",

View file

@ -3,6 +3,7 @@ import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cake_wallet/store/settings_store.dart';
@ -91,6 +92,13 @@ class TransactionListItem extends ActionListItem with Keyable {
cryptoAmount: ethereum!.formatterEthereumAmountToDouble(transaction: transaction),
price: price);
break;
case WalletType.polygon:
final asset = polygon!.assetOfTransaction(balanceViewModel.wallet, transaction);
final price = balanceViewModel.fiatConvertationStore.prices[asset];
amount = calculateFiatAmountRaw(
cryptoAmount: polygon!.formatterPolygonAmountToDouble(transaction: transaction),
price: price);
break;
case WalletType.nano:
amount = calculateFiatAmountRaw(
cryptoAmount: double.parse(nanoUtil!.getRawAsDecimalString(

View file

@ -28,10 +28,7 @@ abstract class ExchangeTradeViewModelBase with Store {
required this.tradesStore,
required this.sendViewModel})
: trade = tradesStore.trade!,
isSendable = tradesStore.trade!.from == wallet.currency ||
tradesStore.trade!.provider == ExchangeProviderDescription.xmrto ||
(wallet.currency == CryptoCurrency.eth &&
tradesStore.trade!.from.tag == CryptoCurrency.eth.title),
isSendable = _checkIfCanSend(tradesStore, wallet),
items = ObservableList<ExchangeTradeItem>() {
switch (trade.provider) {
case ExchangeProviderDescription.changeNow:
@ -155,4 +152,19 @@ abstract class ExchangeTradeViewModelBase with Store {
isCopied: true),
]);
}
static bool _checkIfCanSend(TradesStore tradesStore, WalletBase wallet) {
bool _isEthToken() =>
wallet.currency == CryptoCurrency.eth &&
tradesStore.trade!.from.tag == CryptoCurrency.eth.title;
bool _isPolygonToken() =>
wallet.currency == CryptoCurrency.maticpoly &&
tradesStore.trade!.from.tag == CryptoCurrency.maticpoly.tag;
return tradesStore.trade!.from == wallet.currency ||
tradesStore.trade!.provider == ExchangeProviderDescription.xmrto ||
_isEthToken() ||
_isPolygonToken();
}
}

View file

@ -23,6 +23,7 @@ import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/dashboard/trades_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
@ -287,6 +288,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
return transactionPriority == ethereum!.getEthereumTransactionPrioritySlow();
case WalletType.bitcoinCash:
return transactionPriority == bitcoinCash!.getBitcoinCashTransactionPrioritySlow();
case WalletType.polygon:
return transactionPriority == polygon!.getPolygonTransactionPrioritySlow();
default:
return false;
}
@ -626,6 +629,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
depositCurrency = CryptoCurrency.nano;
receiveCurrency = CryptoCurrency.xmr;
break;
case WalletType.polygon:
depositCurrency = CryptoCurrency.maticpoly;
receiveCurrency = CryptoCurrency.xmr;
break;
default:
break;
}
@ -713,6 +720,9 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
case WalletType.bitcoinCash:
_settingsStore.priority[wallet.type] = bitcoinCash!.getDefaultTransactionPriority();
break;
case WalletType.polygon:
_settingsStore.priority[wallet.type] = polygon!.getDefaultTransactionPriority();
break;
default:
break;
}

View file

@ -72,6 +72,9 @@ abstract class NodeListViewModelBase with Store {
case WalletType.nano:
node = getNanoDefaultNode(nodes: _nodeSource)!;
break;
case WalletType.polygon:
node = getPolygonDefaultNode(nodes: _nodeSource)!;
break;
default:
throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}');
}

View file

@ -2,6 +2,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
import 'package:hive/hive.dart';
@ -71,6 +72,9 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
case WalletType.ethereum:
return ethereum!.createEthereumRestoreWalletFromPrivateKey(
name: name, password: password, privateKey: restoreWallet.privateKey!);
case WalletType.polygon:
return polygon!.createPolygonRestoreWalletFromPrivateKey(
name: name, password: password, privateKey: restoreWallet.privateKey!);
default:
throw Exception('Unexpected type: ${restoreWallet.type.toString()}');
}
@ -95,6 +99,9 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
case WalletType.nano:
return nano!.createNanoRestoreWalletFromSeedCredentials(
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
case WalletType.polygon:
return polygon!.createPolygonRestoreWalletFromSeedCredentials(
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
default:
throw Exception('Unexpected type: ${type.toString()}');
}

View file

@ -26,6 +26,7 @@ class WalletRestoreFromQRCode {
'litecoin-wallet': WalletType.litecoin,
'litecoin_wallet': WalletType.litecoin,
'ethereum-wallet': WalletType.ethereum,
'polygon-wallet': WalletType.polygon,
'nano-wallet': WalletType.nano,
'nano_wallet': WalletType.nano,
'bitcoincash': WalletType.bitcoinCash,
@ -157,7 +158,16 @@ class WalletRestoreFromQRCode {
return WalletRestoreMode.keys;
}
if ((type == WalletType.nano || type == WalletType.banano) && credentials.containsKey('hexSeed')) {
if (type == WalletType.polygon && credentials.containsKey('private_key')) {
final privateKey = credentials['private_key'] as String;
if (privateKey.isEmpty) {
throw Exception('Unexpected restore mode: private_key');
}
return WalletRestoreMode.keys;
}
if ((type == WalletType.nano || type == WalletType.banano) &&
credentials.containsKey('hexSeed')) {
final hexSeed = credentials['hexSeed'] as String;
if (hexSeed.isEmpty) {
throw Exception('Unexpected restore mode: hexSeed');

View file

@ -4,6 +4,8 @@ import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/entities/parsed_address.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/haven/haven.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
@ -27,7 +29,8 @@ const String cryptoNumberPattern = '0.0';
class Output = OutputBase with _$Output;
abstract class OutputBase with Store {
OutputBase(this._wallet, this._settingsStore, this._fiatConversationStore, this.cryptoCurrencyHandler)
OutputBase(
this._wallet, this._settingsStore, this._fiatConversationStore, this.cryptoCurrencyHandler)
: _cryptoNumberFormat = NumberFormat(cryptoNumberPattern),
key = UniqueKey(),
sendAll = false,
@ -65,8 +68,7 @@ abstract class OutputBase with Store {
@computed
bool get isParsedAddress =>
parsedAddress.parseFrom != ParseFrom.notParsed &&
parsedAddress.name.isNotEmpty;
parsedAddress.parseFrom != ParseFrom.notParsed && parsedAddress.name.isNotEmpty;
@computed
int get formattedCryptoAmount {
@ -83,8 +85,7 @@ abstract class OutputBase with Store {
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.bitcoinCash:
_amount =
bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount);
_amount = bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount);
break;
case WalletType.haven:
_amount = haven!.formatterMoneroParseAmount(amount: _cryptoAmount);
@ -92,6 +93,9 @@ abstract class OutputBase with Store {
case WalletType.ethereum:
_amount = ethereum!.formatterEthereumParseAmount(_cryptoAmount);
break;
case WalletType.polygon:
_amount = polygon!.formatterPolygonParseAmount(_cryptoAmount);
break;
default:
break;
}
@ -130,6 +134,10 @@ abstract class OutputBase with Store {
if (_wallet.type == WalletType.ethereum) {
return ethereum!.formatterEthereumAmountToDouble(amount: BigInt.from(fee));
}
if (_wallet.type == WalletType.polygon) {
return polygon!.formatterPolygonAmountToDouble(amount: BigInt.from(fee));
}
} catch (e) {
print(e.toString());
}
@ -140,10 +148,11 @@ abstract class OutputBase with Store {
@computed
String get estimatedFeeFiatAmount {
try {
final currency = _wallet.type == WalletType.ethereum ? _wallet.currency : cryptoCurrencyHandler();
final currency = isEVMCompatibleChain(_wallet.type)
? _wallet.currency
: cryptoCurrencyHandler();
final fiat = calculateFiatAmountRaw(
price: _fiatConversationStore.prices[currency]!,
cryptoAmount: estimatedFee);
price: _fiatConversationStore.prices[currency]!, cryptoAmount: estimatedFee);
return fiat;
} catch (_) {
return '0.00';
@ -240,6 +249,7 @@ abstract class OutputBase with Store {
maximumFractionDigits = 12;
break;
case WalletType.ethereum:
case WalletType.polygon:
maximumFractionDigits = 12;
break;
default:

View file

@ -50,7 +50,9 @@ abstract class SendTemplateViewModelBase with Store {
TemplateValidator get templateValidator => TemplateValidator();
bool get hasMultiRecipient =>
_wallet.type != WalletType.haven && _wallet.type != WalletType.ethereum;
_wallet.type != WalletType.haven &&
_wallet.type != WalletType.ethereum &&
_wallet.type != WalletType.polygon;
@computed
CryptoCurrency get cryptoCurrency => _wallet.currency;

View file

@ -5,6 +5,8 @@ import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/wallet_contact.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
@ -42,7 +44,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
void onWalletChange(wallet) {
currencies = wallet.balance.keys.toList();
selectedCryptoCurrency = wallet.currency;
hasMultipleTokens = wallet.type == WalletType.ethereum;
hasMultipleTokens = isEVMCompatibleChain(wallet.type);
}
SendViewModelBase(
@ -55,7 +57,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
) : state = InitialExecutionState(),
currencies = appStore.wallet!.balance.keys.toList(),
selectedCryptoCurrency = appStore.wallet!.currency,
hasMultipleTokens = appStore.wallet!.type == WalletType.ethereum,
hasMultipleTokens = isEVMCompatibleChain(appStore.wallet!.type),
outputs = ObservableList<Output>(),
_settingsStore = appStore.settingsStore,
fiatFromSettings = appStore.settingsStore.fiatCurrency,
@ -119,7 +121,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
try {
if (pendingTransaction != null) {
final currency =
walletType == WalletType.ethereum ? wallet.currency : selectedCryptoCurrency;
isEVMCompatibleChain(walletType) ? wallet.currency : selectedCryptoCurrency;
final fiat = calculateFiatAmount(
price: _fiatConversationStore.prices[currency]!,
cryptoAmount: pendingTransaction!.feeFormatted);
@ -372,6 +374,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
priority: priority!, currency: selectedCryptoCurrency);
case WalletType.nano:
return nano!.createNanoTransactionCredentials(outputs);
case WalletType.polygon:
return polygon!.createPolygonTransactionCredentials(outputs,
priority: priority!, currency: selectedCryptoCurrency);
default:
throw Exception('Unexpected wallet type: ${wallet.type}');
}
@ -412,11 +417,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
WalletType walletType,
CryptoCurrency currency,
) {
if (walletType == WalletType.ethereum || walletType == WalletType.haven) {
if (walletType == WalletType.ethereum ||
walletType == WalletType.polygon ||
walletType == WalletType.haven) {
if (error.contains('gas required exceeds allowance') ||
error.contains('insufficient funds for')) {
return S.current.do_not_have_enough_gas_asset(currency.toString());
}
return error;
}
return error;

View file

@ -1,6 +1,7 @@
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/balance.dart';
import 'package:cw_core/transaction_history.dart';
@ -12,8 +13,7 @@ import 'package:cake_wallet/entities/fiat_api_mode.dart';
part 'privacy_settings_view_model.g.dart';
class
PrivacySettingsViewModel = PrivacySettingsViewModelBase with _$PrivacySettingsViewModel;
class PrivacySettingsViewModel = PrivacySettingsViewModelBase with _$PrivacySettingsViewModel;
abstract class PrivacySettingsViewModelBase with Store {
PrivacySettingsViewModelBase(this._settingsStore, this._wallet);
@ -58,6 +58,9 @@ abstract class PrivacySettingsViewModelBase with Store {
@computed
bool get useEtherscan => _settingsStore.useEtherscan;
@computed
bool get usePolygonScan => _settingsStore.usePolygonScan;
@computed
bool get lookupTwitter => _settingsStore.lookupsTwitter;
@ -78,6 +81,8 @@ abstract class PrivacySettingsViewModelBase with Store {
bool get canUseEtherscan => _wallet.type == WalletType.ethereum;
bool get canUsePolygonScan => _wallet.type == WalletType.polygon;
@action
void setShouldSaveRecipientAddress(bool value) =>
_settingsStore.shouldSaveRecipientAddress = value;
@ -120,4 +125,10 @@ abstract class PrivacySettingsViewModelBase with Store {
_settingsStore.useEtherscan = value;
ethereum!.updateEtherscanUsageState(_wallet, value);
}
@action
void setUsePolygonScan(bool value) {
_settingsStore.usePolygonScan = value;
polygon!.updatePolygonScanUsageState(_wallet, value);
}
}

View file

@ -51,6 +51,9 @@ abstract class TransactionDetailsViewModelBase with Store {
case WalletType.nano:
_addNanoListItems(tx, dateFormat);
break;
case WalletType.polygon:
_addPolygonListItems(tx, dateFormat);
break;
default:
break;
}
@ -126,6 +129,8 @@ abstract class TransactionDetailsViewModelBase with Store {
return 'https://nanolooker.com/block/${txId}';
case WalletType.banano:
return 'https://bananolooker.com/block/${txId}';
case WalletType.polygon:
return 'https://polygonscan.com/tx/${txId}';
default:
return '';
}
@ -148,6 +153,8 @@ abstract class TransactionDetailsViewModelBase with Store {
return S.current.view_transaction_on + 'nanolooker.com';
case WalletType.banano:
return S.current.view_transaction_on + 'bananolooker.com';
case WalletType.polygon:
return S.current.view_transaction_on + 'polygonscan.com';
default:
return '';
}
@ -237,7 +244,6 @@ abstract class TransactionDetailsViewModelBase with Store {
items.addAll(_items);
}
void _addNanoListItems(TransactionInfo tx, DateFormat dateFormat) {
final _items = [
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
@ -250,4 +256,21 @@ abstract class TransactionDetailsViewModelBase with Store {
items.addAll(_items);
}
void _addPolygonListItems(TransactionInfo tx, DateFormat dateFormat) {
final _items = [
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
StandartListItem(
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
StandartListItem(title: S.current.confirmations, value: tx.confirmations.toString()),
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
if (tx.feeFormatted()?.isNotEmpty ?? false)
StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
if (showRecipientAddress && tx.to != null)
StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!),
];
items.addAll(_items);
}
}

View file

@ -1,6 +1,7 @@
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/yat/yat_store.dart';
import 'package:cw_core/currency.dart';
@ -139,6 +140,22 @@ class NanoURI extends PaymentURI {
}
}
class PolygonURI extends PaymentURI {
PolygonURI({required String amount, required String address})
: super(amount: amount, address: address);
@override
String toString() {
var base = 'polygon:' + address;
if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}';
}
return base;
}
}
abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store {
WalletAddressListViewModelBase({
required AppStore appStore,
@ -216,6 +233,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
return NanoURI(amount: amount, address: address.address);
}
if (wallet.type == WalletType.polygon) {
return PolygonURI(amount: amount, address: address.address);
}
throw Exception('Unexpected type: ${type.toString()}');
}
@ -272,6 +293,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
}
if (wallet.type == WalletType.polygon) {
final primaryAddress = polygon!.getAddress(wallet);
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
}
return addressList;
}

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
@ -19,7 +20,8 @@ abstract class WalletKeysViewModelBase with Store {
: title = _appStore.wallet!.type == WalletType.bitcoin ||
_appStore.wallet!.type == WalletType.litecoin ||
_appStore.wallet!.type == WalletType.bitcoinCash ||
_appStore.wallet!.type == WalletType.ethereum
_appStore.wallet!.type == WalletType.ethereum ||
_appStore.wallet!.type == WalletType.polygon
? S.current.wallet_seed
: S.current.wallet_keys,
_restoreHeight = _appStore.wallet!.walletInfo.restoreHeight,
@ -98,7 +100,7 @@ abstract class WalletKeysViewModelBase with Store {
]);
}
if (_appStore.wallet!.type == WalletType.ethereum) {
if (isEVMCompatibleChain(_appStore.wallet!.type)) {
items.addAll([
if (_appStore.wallet!.privateKey != null)
StandartListItem(title: S.current.private_key, value: _appStore.wallet!.privateKey!),
@ -151,6 +153,8 @@ abstract class WalletKeysViewModelBase with Store {
return 'nano-wallet';
case WalletType.banano:
return 'banano-wallet';
case WalletType.polygon:
return 'polygon-wallet';
default:
throw Exception('Unexpected wallet type: ${_appStore.wallet!.toString()}');
}

View file

@ -14,6 +14,8 @@ import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/haven/haven.dart';
import '../polygon/polygon.dart';
part 'wallet_new_vm.g.dart';
class WalletNewVM = WalletNewVMBase with _$WalletNewVM;
@ -52,6 +54,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
return bitcoinCash!.createBitcoinCashNewWalletCredentials(name: name);
case WalletType.nano:
return nano!.createNanoNewWalletCredentials(name: name);
case WalletType.polygon:
return polygon!.createPolygonNewWalletCredentials(name: name);
default:
throw Exception('Unexpected type: ${type.toString()}');
}

View file

@ -3,6 +3,7 @@ import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/store/app_store.dart';
@ -28,7 +29,10 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
: hasSeedLanguageSelector = type == WalletType.monero || type == WalletType.haven,
hasBlockchainHeightLanguageSelector = type == WalletType.monero || type == WalletType.haven,
hasRestoreFromPrivateKey =
type == WalletType.ethereum || type == WalletType.nano || type == WalletType.banano,
type == WalletType.ethereum ||
type == WalletType.polygon ||
type == WalletType.nano ||
type == WalletType.banano,
isButtonEnabled = false,
mode = WalletRestoreMode.seed,
super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true) {
@ -36,6 +40,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
case WalletType.monero:
case WalletType.haven:
case WalletType.ethereum:
case WalletType.polygon:
availableModes = WalletRestoreMode.values;
break;
case WalletType.nano:
@ -107,6 +112,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
mnemonic: seed,
password: password,
derivationType: derivationType);
case WalletType.polygon:
return polygon!.createPolygonRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
password: password,
);
default:
break;
}
@ -153,6 +164,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
password: password,
seedKey: options['private_key'] as String,
derivationType: options["derivationType"] as DerivationType);
case WalletType.polygon:
return polygon!.createPolygonRestoreWalletFromPrivateKey(
name: name,
password: password,
privateKey: options['private_key'] as String,
);
default:
break;
}

View file

@ -41,6 +41,7 @@ PODS:
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- tor (0.0.1)
- url_launcher_macos (0.0.1):
- FlutterMacOS
- wakelock_plus (0.0.1):
@ -59,6 +60,7 @@ DEPENDENCIES:
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- share_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- tor (from `Flutter/ephemeral/.symlinks/plugins/tor/macos`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
@ -91,6 +93,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
tor:
:path: Flutter/ephemeral/.symlinks/plugins/tor/macos
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
wakelock_plus:
@ -98,7 +102,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
connectivity_plus_macos: f6e86fd000e971d361e54b5afcadc8c8fa773308
cw_monero: ec03de55a19c4a2b174ea687e0f4202edc716fa4
cw_monero: f8b7f104508efba2591548e76b5c058d05cba3f0
device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
devicelocale: 9f0f36ac651cabae2c33f32dcff4f32b61c38225
flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea
@ -110,6 +114,7 @@ SPEC CHECKSUMS:
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
share_plus_macos: 853ee48e7dce06b633998ca0735d482dd671ade4
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
tor: 2138c48428e696b83eacdda404de6d5574932e26
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269

View file

@ -5,4 +5,5 @@ cd cw_haven && flutter pub get && flutter packages pub run build_runner build --
cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_polygon && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
flutter packages pub run build_runner build --delete-conflicting-outputs

View file

@ -147,6 +147,7 @@ flutter:
- assets/bitcoin_cash_electrum_server_list.yml
- assets/nano_node_list.yml
- assets/nano_pow_node_list.yml
- assets/polygon_node_list.yml
- assets/text/
- assets/faq/
- assets/animation/

View file

@ -745,5 +745,6 @@
"seedtype_polyseed": "بوليسيد (16 كلمة)",
"seed_language_czech": "التشيكية",
"seed_language_korean": "الكورية",
"seed_language_chinese_traditional": "تقاليد صينية)"
"seed_language_chinese_traditional": "تقاليد صينية)",
"polygonscan_history": "ﻥﺎﻜﺴﻧﻮﺠﻴﻟﻮﺑ ﺦﻳﺭﺎﺗ"
}

View file

@ -741,5 +741,6 @@
"seedtype_polyseed": "Поли семе (16 думи)",
"seed_language_czech": "Чех",
"seed_language_korean": "Корейски",
"seed_language_chinese_traditional": "Традиционен китайски)"
"seed_language_chinese_traditional": "Традиционен китайски)",
"polygonscan_history": "История на PolygonScan"
}

View file

@ -741,5 +741,6 @@
"seedtype_polyseed": "Polyseed (16 slov)",
"seed_language_czech": "čeština",
"seed_language_korean": "korejština",
"seed_language_chinese_traditional": "Číňan (tradiční)"
"seed_language_chinese_traditional": "Číňan (tradiční)",
"polygonscan_history": "Historie PolygonScan"
}

View file

@ -749,5 +749,6 @@
"seedtype_polyseed": "Polyseed (16 Wörter)",
"seed_language_czech": "Tschechisch",
"seed_language_korean": "Koreanisch",
"seed_language_chinese_traditional": "Chinesisch (Traditionell)"
"seed_language_chinese_traditional": "Chinesisch (Traditionell)",
"polygonscan_history": "PolygonScan-Verlauf"
}

View file

@ -750,5 +750,6 @@
"seedtype_polyseed": "Polyseed (16 words)",
"seed_language_czech": "Czech",
"seed_language_korean": "Korean",
"seed_language_chinese_traditional": "Chinese (Traditional)"
"seed_language_chinese_traditional": "Chinese (Traditional)",
"polygonscan_history": "PolygonScan history"
}

View file

@ -749,5 +749,6 @@
"seedtype_polyseed": "Polieta (16 palabras)",
"seed_language_czech": "checo",
"seed_language_korean": "coreano",
"seed_language_chinese_traditional": "Chino (tradicional)"
"seed_language_chinese_traditional": "Chino (tradicional)",
"polygonscan_history": "Historial de PolygonScan"
}

View file

@ -749,5 +749,6 @@
"seedtype_polyseed": "Polyseed (16 mots)",
"seed_language_czech": "tchèque",
"seed_language_korean": "coréen",
"seed_language_chinese_traditional": "Chinois (Traditionnel)"
"seed_language_chinese_traditional": "Chinois (Traditionnel)",
"polygonscan_history": "Historique de PolygonScan"
}

View file

@ -727,5 +727,6 @@
"seedtype_polyseed": "Polyseed (16 kalmomi)",
"seed_language_czech": "Czech",
"seed_language_korean": "Yaren Koriya",
"seed_language_chinese_traditional": "Sinanci (na gargajiya)"
"seed_language_chinese_traditional": "Sinanci (na gargajiya)",
"polygonscan_history": "PolygonScan tarihin kowane zamani"
}

View file

@ -749,5 +749,6 @@
"seedtype_polyseed": "पॉलीसीड (16 शब्द)",
"seed_language_czech": "चेक",
"seed_language_korean": "कोरियाई",
"seed_language_chinese_traditional": "चीनी पारंपरिक)"
"seed_language_chinese_traditional": "चीनी पारंपरिक)",
"polygonscan_history": "पॉलीगॉनस्कैन इतिहास"
}

View file

@ -747,5 +747,6 @@
"seedtype_polyseed": "Poliseed (16 riječi)",
"seed_language_czech": "češki",
"seed_language_korean": "korejski",
"seed_language_chinese_traditional": "Kinesko (tradicionalno)"
"seed_language_chinese_traditional": "Kinesko (tradicionalno)",
"polygonscan_history": "Povijest PolygonScan"
}

View file

@ -737,5 +737,6 @@
"seedtype_polyseed": "Polyseed (16 kata)",
"seed_language_czech": "Ceko",
"seed_language_korean": "Korea",
"seed_language_chinese_traditional": "Cina (tradisional)"
"seed_language_chinese_traditional": "Cina (tradisional)",
"polygonscan_history": "Sejarah PolygonScan"
}

View file

@ -749,5 +749,6 @@
"seedtype_polyseed": "Polyseed (16 parole)",
"seed_language_czech": "ceco",
"seed_language_korean": "coreano",
"seed_language_chinese_traditional": "Cinese tradizionale)"
"seed_language_chinese_traditional": "Cinese tradizionale)",
"polygonscan_history": "Cronologia PolygonScan"
}

View file

@ -749,5 +749,6 @@
"seedtype_polyseed": "ポリシード16語",
"seed_language_czech": "チェコ",
"seed_language_korean": "韓国語",
"seed_language_chinese_traditional": "中国の伝統的な)"
"seed_language_chinese_traditional": "中国の伝統的な)",
"polygonscan_history": "ポリゴンスキャン履歴"
}

Some files were not shown because too many files have changed in this diff Show more