Merge branch 'main' of https://github.com/cake-tech/cake_wallet into CW-519-tor

This commit is contained in:
fosse 2024-01-31 16:10:22 -05:00
commit 92b28ceacb
103 changed files with 1877 additions and 2111 deletions
.github/workflows
.gitignore
assets/text
configure_cake_wallet.sh
cw_ethereum
cw_evm
cw_polygon
lib
model_generator.sh
res/values
scripts
tool

View file

@ -105,21 +105,21 @@ jobs:
run: |
cd /opt/android/cake_wallet
cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_evm && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
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 ..
cd cw_ethereum && flutter pub get && cd ..
cd cw_polygon && flutter pub get && cd ..
flutter packages pub run build_runner build --delete-conflicting-outputs
- name: Add secrets
run: |
cd /opt/android/cake_wallet
touch lib/.secrets.g.dart
touch cw_ethereum/lib/.secrets.g.dart
touch cw_polygon/lib/.secrets.g.dart
touch cw_evm/lib/.secrets.g.dart
echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart
echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart
echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart
@ -146,14 +146,14 @@ jobs:
echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart
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 etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/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
echo "const robinhoodCIdApiSecret = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart
echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart
echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart
echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
- name: Rename app
run: echo -e "id=com.cakewallet.test\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties

3
.gitignore vendored
View file

@ -90,9 +90,10 @@ android/key.properties
**/tool/.secrets-prod.json
**/tool/.secrets-test.json
**/tool/.secrets-config.json
**/tool/.evm-secrets-config.json
**/tool/.ethereum-secrets-config.json
**/lib/.secrets.g.dart
**/cw_ethereum/lib/.secrets.g.dart
**/cw_evm/lib/.secrets.g.dart
vendor/

View file

@ -1,3 +1,3 @@
On-ramp flow fixes and enhancements
UI enhancements
Generic enhancements and bug fixes
Security and Privacy enhancements
Usability enhancements
Bug fixes

View file

@ -1,6 +1,4 @@
Add new Off-ramp providers (DFX, OnRamper)
On-ramp flow fixes and enhancements
Ethereum and WalletConnect fixes and improvements
Nano enhancements
UI enhancements
Generic enhancements and bug fixes
List previously used Bitcoin addresses
Security and Privacy enhancements
Usability enhancements
Bug fixes

View file

@ -24,11 +24,12 @@ source ./app_env.sh cakewallet
cd ../.. && flutter pub get
flutter packages pub run tool/generate_localization.dart
cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_evm && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
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 ..
cd cw_ethereum && flutter pub get && cd ..
cd cw_polygon && flutter pub get && cd ..
flutter packages pub run build_runner build --delete-conflicting-outputs

View file

@ -1,7 +1,7 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
class DefaultErc20Tokens {
class DefaultEthereumErc20Tokens {
final List<Erc20Token> _defaultTokens = [
Erc20Token(
name: "USD Coin",

View file

@ -1,229 +1,22 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'dart:typed_data';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_ethereum/erc20_balance.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_ethereum/ethereum_transaction_model.dart';
import 'package:cw_ethereum/pending_ethereum_transaction.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart';
import 'package:cw_evm/evm_chain_client.dart';
import 'package:cw_evm/.secrets.g.dart' as secrets;
import 'package:cw_evm/evm_chain_transaction_model.dart';
import 'package:web3dart/web3dart.dart';
import 'package:erc20/erc20.dart';
import 'package:cw_core/node.dart';
import 'package:cw_ethereum/ethereum_transaction_priority.dart';
import 'package:cw_ethereum/.secrets.g.dart' as secrets;
class EthereumClient {
final httpClient = Client();
Web3Client? _client;
bool connect(Node node) {
try {
_client = Web3Client(node.uri.toString(), httpClient);
return true;
} catch (e) {
return false;
}
}
void setListeners(EthereumAddress userAddress, Function() onNewTransaction) async {
// _client?.pendingTransactions().listen((transactionHash) async {
// final transaction = await _client!.getTransactionByHash(transactionHash);
//
// if (transaction.from.hex == userAddress || transaction.to?.hex == userAddress) {
// onNewTransaction();
// }
// });
}
Future<EtherAmount> getBalance(EthereumAddress address) async {
try {
return await _client!.getBalance(address);
} catch (_) {
return EtherAmount.zero();
}
}
Future<int> getGasUnitPrice() async {
try {
final gasPrice = await _client!.getGasPrice();
return gasPrice.getInWei.toInt();
} catch (_) {
return 0;
}
}
Future<int> getEstimatedGas() async {
try {
final estimatedGas = await _client!.estimateGas();
return estimatedGas.toInt();
} catch (_) {
return 0;
}
}
Future<PendingEthereumTransaction> signTransaction({
required EthPrivateKey privateKey,
required String toAddress,
required String amount,
required int gas,
required EthereumTransactionPriority priority,
required CryptoCurrency currency,
required int exponent,
String? contractAddress,
}) async {
assert(currency == CryptoCurrency.eth ||
currency == CryptoCurrency.maticpoly ||
contractAddress != null);
bool _isEVMCompatibleChain =
currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly;
final price = _client!.getGasPrice();
final Transaction transaction = createTransaction(
from: privateKey.address,
to: EthereumAddress.fromHex(toAddress),
maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
amount: _isEVMCompatibleChain ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
);
final signedTransaction =
await _client!.signTransaction(privateKey, transaction, chainId: chainId);
final Function _sendTransaction;
if (_isEVMCompatibleChain) {
_sendTransaction = () async => await sendTransaction(signedTransaction);
} else {
final erc20 = ERC20(
client: _client!,
address: EthereumAddress.fromHex(contractAddress!),
chainId: chainId,
);
_sendTransaction = () async {
await erc20.transfer(
EthereumAddress.fromHex(toAddress),
BigInt.parse(amount),
credentials: privateKey,
transaction: transaction,
);
};
}
return PendingEthereumTransaction(
signedTransaction: signedTransaction,
amount: amount,
fee: BigInt.from(gas) * (await price).getInWei,
sendTransaction: _sendTransaction,
exponent: exponent,
);
}
class EthereumClient extends EVMChainClient {
@override
int get chainId => 1;
Transaction createTransaction({
required EthereumAddress from,
required EthereumAddress to,
required EtherAmount amount,
EtherAmount? maxPriorityFeePerGas,
}) {
return Transaction(
from: from,
to: to,
maxPriorityFeePerGas: maxPriorityFeePerGas,
value: amount,
);
}
Future<String> sendTransaction(Uint8List signedTransaction) async =>
await _client!.sendRawTransaction(prepareSignedTransactionForSending(signedTransaction));
@override
Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) =>
prependTransactionType(0x02, signedTransaction);
Future getTransactionDetails(String transactionHash) async {
// Wait for the transaction receipt to become available
TransactionReceipt? receipt;
while (receipt == null) {
receipt = await _client!.getTransactionReceipt(transactionHash);
await Future.delayed(Duration(seconds: 1));
}
// Print the receipt information
print('Transaction Hash: ${receipt.transactionHash}');
print('Block Hash: ${receipt.blockHash}');
print('Block Number: ${receipt.blockNumber}');
print('Gas Used: ${receipt.gasUsed}');
/*
Transaction Hash: [112, 244, 4, 238, 89, 199, 171, 191, 210, 236, 110, 42, 185, 202, 220, 21, 27, 132, 123, 221, 137, 90, 77, 13, 23, 43, 12, 230, 93, 63, 221, 116]
I/flutter ( 4474): Block Hash: [149, 44, 250, 119, 111, 104, 82, 98, 17, 89, 30, 190, 25, 44, 218, 118, 127, 189, 241, 35, 213, 106, 25, 95, 195, 37, 55, 131, 185, 180, 246, 200]
I/flutter ( 4474): Block Number: 17120242
I/flutter ( 4474): Gas Used: 21000
*/
// Wait for the transaction receipt to become available
TransactionInformation? transactionInformation;
while (transactionInformation == null) {
print("********************************");
transactionInformation = await _client!.getTransactionByHash(transactionHash);
await Future.delayed(Duration(seconds: 1));
}
// Print the receipt information
print('Transaction Hash: ${transactionInformation.hash}');
print('Block Hash: ${transactionInformation.blockHash}');
print('Block Number: ${transactionInformation.blockNumber}');
print('Gas Used: ${transactionInformation.gas}');
/*
Transaction Hash: 0x70f404ee59c7abbfd2ec6e2ab9cadc151b847bdd895a4d0d172b0ce65d3fdd74
I/flutter ( 4474): Block Hash: 0x952cfa776f68526211591ebe192cda767fbdf123d56a195fc3253783b9b4f6c8
I/flutter ( 4474): Block Number: 17120242
I/flutter ( 4474): Gas Used: 53000
*/
}
Future<ERC20Balance> fetchERC20Balances(
EthereumAddress userAddress, String contractAddress) async {
final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
try {
final balance = await erc20.balanceOf(userAddress);
int exponent = (await erc20.decimals()).toInt();
return ERC20Balance(balance, exponent: exponent);
} catch (_) {
return ERC20Balance(BigInt.zero);
}
}
Future<Erc20Token?> getErc20Token(String contractAddress) async {
try {
final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
final name = await erc20.name();
final symbol = await erc20.symbol();
final decimal = await erc20.decimals();
return Erc20Token(
name: name,
symbol: symbol,
contractAddress: contractAddress,
decimal: decimal.toInt(),
);
} catch (e) {
return null;
}
}
void stop() {
_client?.dispose();
}
Future<List<EthereumTransactionModel>> fetchTransactions(String address,
@override
Future<List<EVMChainTransactionModel>> fetchTransactions(String address,
{String? contractAddress}) async {
try {
final response = await httpClient.get(Uri.https("api.etherscan.io", "/api", {
@ -234,41 +27,18 @@ I/flutter ( 4474): Gas Used: 53000
"apikey": secrets.etherScanApiKey,
}));
final _jsonResponse = json.decode(response.body) as Map<String, dynamic>;
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) => EthereumTransactionModel.fromJson(e as Map<String, dynamic>))
if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
return (jsonResponse['result'] as List)
.map((e) => EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, 'ETH'))
.toList();
}
return [];
} catch (e) {
print(e);
log(e.toString());
return [];
}
}
Web3Client? getWeb3Client() {
return _client;
}
// Future<int> _getDecimalPlacesForContract(DeployedContract contract) async {
// final String abi = await rootBundle.loadString("assets/abi_json/erc20_abi.json");
// final contractAbi = ContractAbi.fromJson(abi, "ERC20");
//
// final contract = DeployedContract(
// contractAbi,
// EthereumAddress.fromHex(_erc20Currencies[erc20Currency]!),
// );
// final decimalsFunction = contract.function('decimals');
// final decimals = await _client!.call(
// contract: contract,
// function: decimalsFunction,
// params: [],
// );
//
// int exponent = int.parse(decimals.first.toString());
// return exponent;
// }
}

View file

@ -1,11 +0,0 @@
import 'package:cw_core/crypto_currency.dart';
class EthereumTransactionCreationException implements Exception {
final String exceptionMessage;
EthereumTransactionCreationException(CryptoCurrency currency) :
this.exceptionMessage = 'Wrong balance. Not enough ${currency.title} on your balance.';
@override
String toString() => exceptionMessage;
}

View file

@ -1,25 +0,0 @@
import 'package:intl/intl.dart';
const ethereumAmountLength = 12;
const ethereumAmountDivider = 1000000000000;
final ethereumAmountFormat = NumberFormat()
..maximumFractionDigits = ethereumAmountLength
..minimumFractionDigits = 1;
class EthereumFormatter {
static int parseEthereumAmount(String amount) {
try {
return (double.parse(amount) * ethereumAmountDivider).round();
} catch (_) {
return 0;
}
}
static double parseEthereumAmountToDouble(int amount) {
try {
return amount / ethereumAmountDivider;
} catch (_) {
return 0;
}
}
}

View file

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

View file

@ -1,77 +1,18 @@
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:mobx/mobx.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_ethereum/ethereum_transaction_info.dart';
import 'package:cw_evm/evm_chain_transaction_history.dart';
import 'package:cw_evm/evm_chain_transaction_info.dart';
part 'ethereum_transaction_history.g.dart';
const transactionsHistoryFileName = 'transactions.json';
class EthereumTransactionHistory = EthereumTransactionHistoryBase with _$EthereumTransactionHistory;
abstract class EthereumTransactionHistoryBase
extends TransactionHistoryBase<EthereumTransactionInfo> with Store {
EthereumTransactionHistoryBase({required this.walletInfo, required String password})
: _password = password {
transactions = ObservableMap<String, EthereumTransactionInfo>();
}
final WalletInfo walletInfo;
String _password;
Future<void> init() async => await _load();
class EthereumTransactionHistory extends EVMChainTransactionHistory {
EthereumTransactionHistory({
required super.walletInfo,
required super.password,
});
@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 save ethereum transaction history: ${e.toString()}');
print(s);
}
}
String getTransactionHistoryFileName() => 'transactions.json';
@override
void addOne(EthereumTransactionInfo transaction) => transactions[transaction.id] = transaction;
@override
void addMany(Map<String, EthereumTransactionInfo> 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 = EthereumTransactionInfo.fromJson(val);
_update(tx);
}
});
} catch (e) {
print(e);
}
}
void _update(EthereumTransactionInfo transaction) => transactions[transaction.id] = transaction;
EVMChainTransactionInfo getTransactionInfo(Map<String, dynamic> val) =>
EthereumTransactionInfo.fromJson(val);
}

View file

@ -1,57 +1,21 @@
import 'dart:math';
import 'package:cw_core/format_amount.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_evm/evm_chain_transaction_info.dart';
class EthereumTransactionInfo extends TransactionInfo {
class EthereumTransactionInfo extends EVMChainTransactionInfo {
EthereumTransactionInfo({
required this.id,
required this.height,
required this.ethAmount,
required this.ethFee,
this.tokenSymbol = "ETH",
this.exponent = 18,
required this.direction,
required this.isPending,
required this.date,
required this.confirmations,
required this.to,
}) : this.amount = ethAmount.toInt(),
this.fee = ethFee.toInt();
final String id;
final int height;
final int amount;
final BigInt ethAmount;
final int exponent;
final TransactionDirection direction;
final DateTime date;
final bool isPending;
final int fee;
final BigInt ethFee;
final int confirmations;
final String tokenSymbol;
String? _fiatAmount;
final String? to;
@override
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 ?? '';
@override
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
@override
String feeFormatted() {
final amount = (ethFee / BigInt.from(10).pow(18)).toString();
return '${amount.substring(0, min(10, amount.length))} ETH';
}
required super.id,
required super.height,
required super.ethAmount,
required super.ethFee,
required super.tokenSymbol,
required super.direction,
required super.isPending,
required super.date,
required super.confirmations,
required super.to,
required super.from,
super.exponent,
});
factory EthereumTransactionInfo.fromJson(Map<String, dynamic> data) {
return EthereumTransactionInfo(
@ -66,20 +30,10 @@ class EthereumTransactionInfo extends TransactionInfo {
confirmations: data['confirmations'] as int,
tokenSymbol: data['tokenSymbol'] as String,
to: data['to'],
from: data['from'],
);
}
Map<String, dynamic> toJson() => {
'id': id,
'height': height,
'amount': ethAmount.toString(),
'exponent': exponent,
'fee': ethFee.toString(),
'direction': direction.index,
'date': date.millisecondsSinceEpoch,
'isPending': isPending,
'confirmations': confirmations,
'tokenSymbol': tokenSymbol,
'to': to,
};
@override
String get feeCurrency => 'ETH';
}

View file

@ -1,52 +0,0 @@
import 'package:cw_core/transaction_priority.dart';
class EthereumTransactionPriority extends TransactionPriority {
final int tip;
const EthereumTransactionPriority({required String title, required int raw, required this.tip})
: super(title: title, raw: raw);
static const List<EthereumTransactionPriority> all = [fast, medium, slow];
static const EthereumTransactionPriority slow =
EthereumTransactionPriority(title: 'slow', raw: 0, tip: 1);
static const EthereumTransactionPriority medium =
EthereumTransactionPriority(title: 'Medium', raw: 1, tip: 2);
static const EthereumTransactionPriority fast =
EthereumTransactionPriority(title: 'Fast', raw: 2, tip: 4);
static EthereumTransactionPriority 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 EthereumTransactionPriority deserialize');
}
}
String get units => 'gas';
@override
String toString() {
var label = '';
switch (this) {
case EthereumTransactionPriority.slow:
label = 'Slow';
break;
case EthereumTransactionPriority.medium:
label = 'Medium';
break;
case EthereumTransactionPriority.fast:
label = 'Fast';
break;
default:
break;
}
return label;
}
}

View file

@ -1,126 +1,58 @@
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/crypto_currency.dart';
import 'package:cw_core/erc20_token.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/default_ethereum_erc20_tokens.dart';
import 'package:cw_ethereum/erc20_balance.dart';
import 'package:cw_ethereum/ethereum_client.dart';
import 'package:cw_ethereum/ethereum_exceptions.dart';
import 'package:cw_ethereum/ethereum_formatter.dart';
import 'package:cw_ethereum/ethereum_transaction_credentials.dart';
import 'package:cw_ethereum/ethereum_transaction_history.dart';
import 'package:cw_ethereum/ethereum_transaction_info.dart';
import 'package:cw_ethereum/ethereum_transaction_model.dart';
import 'package:cw_ethereum/ethereum_transaction_priority.dart';
import 'package:cw_ethereum/ethereum_wallet_addresses.dart';
import 'package:cw_ethereum/file.dart';
import 'package:cw_core/erc20_token.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;
import 'package:cw_evm/evm_chain_transaction_history.dart';
import 'package:cw_evm/evm_chain_transaction_info.dart';
import 'package:cw_evm/evm_chain_transaction_model.dart';
import 'package:cw_evm/evm_chain_wallet.dart';
import 'package:cw_evm/evm_erc20_balance.dart';
import 'package:cw_evm/file.dart';
part 'ethereum_wallet.g.dart';
class EthereumWallet extends EVMChainWallet {
EthereumWallet({
required super.client,
required super.password,
required super.walletInfo,
super.mnemonic,
super.initialBalance,
super.privateKey,
}) : super(nativeCurrency: CryptoCurrency.eth);
class EthereumWallet = EthereumWalletBase with _$EthereumWallet;
@override
void addInitialTokens() {
final initialErc20Tokens = DefaultEthereumErc20Tokens().initialErc20Tokens;
abstract class EthereumWalletBase
extends WalletBase<ERC20Balance, EthereumTransactionHistory, EthereumTransactionInfo>
with Store {
EthereumWalletBase({
required WalletInfo walletInfo,
String? mnemonic,
String? privateKey,
required String password,
ERC20Balance? initialBalance,
}) : syncStatus = NotConnectedSyncStatus(),
_password = password,
_mnemonic = mnemonic,
_hexPrivateKey = privateKey,
_isTransactionUpdating = false,
_client = EthereumClient(),
walletAddresses = EthereumWalletAddresses(walletInfo),
balance = ObservableMap<CryptoCurrency, ERC20Balance>.of(
{CryptoCurrency.eth: initialBalance ?? ERC20Balance(BigInt.zero)}),
super(walletInfo) {
this.walletInfo = walletInfo;
transactionHistory = EthereumTransactionHistory(walletInfo: walletInfo, password: password);
if (!CakeHive.isAdapterRegistered(Erc20Token.typeId)) {
CakeHive.registerAdapter(Erc20TokenAdapter());
for (var token in initialErc20Tokens) {
evmChainErc20TokensBox.put(token.contractAddress, token);
}
_sharedPrefs.complete(SharedPreferences.getInstance());
}
final String? _mnemonic;
final String? _hexPrivateKey;
final String _password;
late final Box<Erc20Token> erc20TokensBox;
late final Box<Erc20Token> ethereumErc20TokensBox;
late final EthPrivateKey _ethPrivateKey;
EthPrivateKey get ethPrivateKey => _ethPrivateKey;
late EthereumClient _client;
int? _gasPrice;
int? _estimatedGas;
bool _isTransactionUpdating;
// TODO: remove after integrating our own node and having eth_newPendingTransactionFilter
Timer? _transactionsUpdateTimer;
@override
Future<bool> checkIfScanProviderIsEnabled() async {
bool isEtherscanEnabled = (await sharedPrefs.future).getBool("use_etherscan") ?? true;
return isEtherscanEnabled;
}
@override
WalletAddresses walletAddresses;
@override
@observable
SyncStatus syncStatus;
@override
@observable
late ObservableMap<CryptoCurrency, ERC20Balance> balance;
Completer<SharedPreferences> _sharedPrefs = Completer();
Future<void> init() async {
Future<void> initErc20TokensBox() async {
// This is for ethereum wallets,
// Other wallets would override and initialize their respective boxes with their boxNames.
await movePreviousErc20BoxConfigsToNewBox();
await walletAddresses.init();
await transactionHistory.init();
_ethPrivateKey = await getPrivateKey(
mnemonic: _mnemonic,
privateKey: _hexPrivateKey,
password: _password,
);
walletAddresses.address = _ethPrivateKey.address.toString();
await save();
}
/// Majorly for backward compatibility for previous configs that have been set.
Future<void> movePreviousErc20BoxConfigsToNewBox() async {
// Opens a box specific to this wallet
ethereumErc20TokensBox = await CakeHive.openBox<Erc20Token>(
evmChainErc20TokensBox = await CakeHive.openBox<Erc20Token>(
"${walletInfo.name.replaceAll(" ", "_")}_${Erc20Token.ethereumBoxName}");
//Open the previous token configs box
@ -130,7 +62,7 @@ abstract class EthereumWalletBase
if (erc20TokensBox.isEmpty) {
// If it's empty, but the new wallet specific box is also empty,
// we load the initial tokens to the new box.
if (ethereumErc20TokensBox.isEmpty) addInitialTokens();
if (evmChainErc20TokensBox.isEmpty) addInitialTokens();
return;
}
@ -141,319 +73,37 @@ abstract class EthereumWalletBase
await erc20TokensBox.deleteFromDisk();
// Add all the previous tokens with configs to the new box
ethereumErc20TokensBox.addAll(allValues);
evmChainErc20TokensBox.addAll(allValues);
}
@override
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
try {
if (priority is EthereumTransactionPriority) {
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("Ethereum Node connection failed");
}
_client.setListeners(_ethPrivateKey.address, _onNewTransaction);
_setTransactionUpdateTimer();
syncStatus = ConnectedSyncStatus();
} catch (e) {
syncStatus = FailedSyncStatus();
}
}
@override
Future<PendingTransaction> createTransaction(Object credentials) async {
final _credentials = credentials as EthereumTransactionCredentials;
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 amountToEthereumMultiplier = pow(10, exponent);
// so far this can not be made with Ethereum as Ethereum does not support multiple recipients
if (hasMultiDestination) {
if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
throw EthereumTransactionCreationException(transactionCurrency);
}
final totalOriginalAmount = EthereumFormatter.parseEthereumAmountToDouble(
outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)));
totalAmount = BigInt.from(totalOriginalAmount * amountToEthereumMultiplier);
if (_erc20Balance.balance < totalAmount) {
throw EthereumTransactionCreationException(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 * amountToEthereumMultiplier);
if (_erc20Balance.balance < totalAmount) {
throw EthereumTransactionCreationException(transactionCurrency);
}
}
final pendingEthereumTransaction = await _client.signTransaction(
privateKey: _ethPrivateKey,
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,
EVMChainTransactionInfo getTransactionInfo(
EVMChainTransactionModel transactionModel, String address) {
final model = EthereumTransactionInfo(
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 ?? "ETH",
to: transactionModel.to,
from: transactionModel.from,
);
return pendingEthereumTransaction;
}
Future<void> _updateTransactions() async {
try {
if (_isTransactionUpdating) {
return;
}
bool isEtherscanEnabled = (await _sharedPrefs.future).getBool("use_etherscan") ?? true;
if (!isEtherscanEnabled) {
return;
}
_isTransactionUpdating = true;
final transactions = await fetchTransactions();
transactionHistory.addMany(transactions);
await transactionHistory.save();
_isTransactionUpdating = false;
} catch (_) {
_isTransactionUpdating = false;
}
return model;
}
@override
Future<Map<String, EthereumTransactionInfo>> fetchTransactions() async {
final address = _ethPrivateKey.address.hex;
final transactions = await _client.fetchTransactions(address);
final List<Future<List<EthereumTransactionModel>>> erc20TokensTransactions = [];
for (var token in balance.keys) {
if (token is Erc20Token) {
erc20TokensTransactions.add(_client.fetchTransactions(
address,
contractAddress: token.contractAddress,
));
}
}
final tokensTransaction = await Future.wait(erc20TokensTransactions);
transactions.addAll(tokensTransaction.expand((element) => element));
final Map<String, EthereumTransactionInfo> result = {};
for (var transactionModel in transactions) {
if (transactionModel.isError) {
continue;
}
result[transactionModel.hash] = EthereumTransactionInfo(
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 ?? "ETH",
to: transactionModel.to,
);
}
return result;
}
String getTransactionHistoryFileName() => 'transactions.json';
@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(_ethPrivateKey.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<EthereumWallet> 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 EthereumWallet(
walletInfo: walletInfo,
password: password,
mnemonic: mnemonic,
privateKey: privateKey,
initialBalance: balance,
);
}
Future<void> _updateBalance() async {
balance[currency] = await _fetchEthBalance();
await _fetchErc20Balances();
await save();
}
Future<ERC20Balance> _fetchEthBalance() async {
final balance = await _client.getBalance(_ethPrivateKey.address);
return ERC20Balance(balance.getInWei);
}
Future<void> _fetchErc20Balances() async {
for (var token in ethereumErc20TokensBox.values) {
try {
if (token.enabled) {
balance[token] = await _client.fetchERC20Balances(
_ethPrivateKey.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 _hdPathEthereum = "m/44'/60'/0'/0";
const index = 0;
final addressAtIndex = root.derivePath("$_hdPathEthereum/$index");
return EthPrivateKey.fromHex(HEX.encode(addressAtIndex.privateKey as List<int>));
}
Future<void>? updateBalance() async => await _updateBalance();
List<Erc20Token> get erc20Currencies => ethereumErc20TokensBox.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(
Erc20Token createNewErc20TokenObject(Erc20Token token, String? iconPath) {
return Erc20Token(
name: token.name,
symbol: token.symbol,
contractAddress: token.contractAddress,
@ -462,85 +112,30 @@ abstract class EthereumWalletBase
tag: token.tag ?? "ETH",
iconPath: iconPath,
);
await ethereumErc20TokensBox.put(_token.contractAddress, _token);
if (_token.enabled) {
balance[_token] = await _client.fetchERC20Balances(
_ethPrivateKey.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 = DefaultErc20Tokens().initialErc20Tokens;
initialErc20Tokens.forEach((token) => ethereumErc20TokensBox.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);
EVMChainTransactionHistory setUpTransactionHistory(WalletInfo walletInfo, String password) {
return EthereumTransactionHistory(walletInfo: walletInfo, password: password);
}
void _setTransactionUpdateTimer() {
if (_transactionsUpdateTimer?.isActive ?? false) {
_transactionsUpdateTimer!.cancel();
}
static Future<EthereumWallet> 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 = EVMChainERC20Balance.fromJSON(data['balance'] as String) ??
EVMChainERC20Balance(BigInt.zero);
_transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 10), (_) {
_updateTransactions();
_updateBalance();
});
return EthereumWallet(
walletInfo: walletInfo,
password: password,
mnemonic: mnemonic,
privateKey: privateKey,
initialBalance: balance,
client: EthereumClient(),
);
}
void updateEtherscanUsageState(bool isEnabled) {
if (isEnabled) {
_updateTransactions();
_setTransactionUpdateTimer();
} else {
_transactionsUpdateTimer?.cancel();
}
}
@override
String signMessage(String message, {String? address}) =>
bytesToHex(_ethPrivateKey.signPersonalMessageToUint8List(ascii.encode(message)));
Web3Client? getWeb3Client() => _client.getWeb3Client();
}

View file

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

View file

@ -1,32 +1,31 @@
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_ethereum/ethereum_client.dart';
import 'package:cw_ethereum/ethereum_mnemonics_exception.dart';
import 'package:cw_ethereum/ethereum_wallet.dart';
import 'package:cw_ethereum/ethereum_wallet_creation_credentials.dart';
import 'package:hive/hive.dart';
import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
import 'package:cw_evm/evm_chain_wallet_service.dart';
import 'package:bip39/bip39.dart' as bip39;
import 'package:collection/collection.dart';
class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
EthereumRestoreWalletFromSeedCredentials, EthereumRestoreWalletFromPrivateKey> {
EthereumWalletService(this.walletInfoSource);
class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
EthereumWalletService(super.walletInfoSource, {required this.client});
final Box<WalletInfo> walletInfoSource;
late EthereumClient client;
@override
Future<EthereumWallet> create(EthereumNewWalletCredentials credentials) async {
WalletType getType() => WalletType.ethereum;
@override
Future<EthereumWallet> create(EVMChainNewWalletCredentials credentials) async {
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
final mnemonic = bip39.generateMnemonic(strength: strength);
final wallet = EthereumWallet(
walletInfo: credentials.walletInfo!,
mnemonic: mnemonic,
password: credentials.password!,
client: client,
);
await wallet.init();
@ -36,18 +35,11 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
return wallet;
}
@override
WalletType getType() => WalletType.ethereum;
@override
Future<bool> isWalletExit(String name) async =>
File(await pathForWallet(name: name, type: getType())).existsSync();
@override
Future<EthereumWallet> openWallet(String name, String password) async {
final walletInfo =
walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
final wallet = await EthereumWalletBase.open(
final wallet = await EthereumWallet.open(
name: name,
password: password,
walletInfo: walletInfo,
@ -60,19 +52,28 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
}
@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);
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 EthereumWallet.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);
}
@override
Future<EthereumWallet> restoreFromKeys(EthereumRestoreWalletFromPrivateKey credentials) async {
Future<EthereumWallet> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials) async {
final wallet = EthereumWallet(
password: credentials.password!,
privateKey: credentials.privateKey,
walletInfo: credentials.walletInfo!,
client: client,
);
await wallet.init();
@ -84,7 +85,7 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
@override
Future<EthereumWallet> restoreFromSeed(
EthereumRestoreWalletFromSeedCredentials credentials) async {
EVMChainRestoreWalletFromSeedCredentials credentials) async {
if (!bip39.validateMnemonic(credentials.mnemonic)) {
throw EthereumMnemonicIsIncorrectException();
}
@ -93,6 +94,7 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
password: credentials.password!,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!,
client: client,
);
await wallet.init();
@ -101,20 +103,4 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
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 EthereumWalletBase.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);
}
}

View file

@ -13,56 +13,23 @@ dependencies:
flutter:
sdk: flutter
web3dart: ^2.7.1
erc20: ^1.0.1
mobx: ^2.0.7+4
bip39: ^1.0.6
bip32: ^2.0.0
hex: ^0.2.0
http: ^1.1.0
shared_preferences: ^2.0.15
cw_core:
path: ../cw_core
cw_evm:
path: ../cw_evm
hive: ^2.2.3
dev_dependencies:
flutter_test:
sdk: flutter
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

30
cw_evm/.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_evm/.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_evm/CHANGELOG.md Normal file
View file

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

1
cw_evm/LICENSE Normal file
View file

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

39
cw_evm/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

7
cw_evm/lib/cw_evm.dart Normal file
View file

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

View file

@ -0,0 +1,251 @@
import 'dart:async';
import 'dart:developer';
import 'package:cw_core/node.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_evm/evm_erc20_balance.dart';
import 'package:cw_evm/evm_chain_transaction_model.dart';
import 'package:cw_evm/pending_evm_chain_transaction.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart';
import 'package:erc20/erc20.dart';
import 'package:web3dart/web3dart.dart';
abstract class EVMChainClient {
final httpClient = Client();
Web3Client? _client;
//! To be overridden by all child classes
int get chainId;
Future<List<EVMChainTransactionModel>> fetchTransactions(String address,
{String? contractAddress});
Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction);
//! Common methods across all child classes
bool connect(Node node) {
try {
_client = Web3Client(node.uri.toString(), httpClient);
return true;
} catch (e) {
return false;
}
}
void setListeners(EthereumAddress userAddress, Function() onNewTransaction) async {
// _client?.pendingTransactions().listen((transactionHash) async {
// final transaction = await _client!.getTransactionByHash(transactionHash);
//
// if (transaction.from.hex == userAddress || transaction.to?.hex == userAddress) {
// onNewTransaction();
// }
// });
}
Future<EtherAmount> getBalance(EthereumAddress address) async {
try {
return await _client!.getBalance(address);
} catch (_) {
return EtherAmount.zero();
}
}
Future<int> getGasUnitPrice() async {
try {
final gasPrice = await _client!.getGasPrice();
return gasPrice.getInWei.toInt();
} catch (_) {
return 0;
}
}
Future<int> getEstimatedGas() async {
try {
final estimatedGas = await _client!.estimateGas();
return estimatedGas.toInt();
} catch (_) {
return 0;
}
}
Future<PendingEVMChainTransaction> signTransaction({
required EthPrivateKey privateKey,
required String toAddress,
required String amount,
required int gas,
required EVMChainTransactionPriority priority,
required CryptoCurrency currency,
required int exponent,
String? contractAddress,
}) async {
assert(currency == CryptoCurrency.eth ||
currency == CryptoCurrency.maticpoly ||
contractAddress != null);
bool isEVMCompatibleChain =
currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly;
final price = _client!.getGasPrice();
final Transaction transaction = createTransaction(
from: privateKey.address,
to: EthereumAddress.fromHex(toAddress),
maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
amount: isEVMCompatibleChain ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
);
final signedTransaction =
await _client!.signTransaction(privateKey, transaction, chainId: chainId);
final Function _sendTransaction;
if (isEVMCompatibleChain) {
_sendTransaction = () async => await sendTransaction(signedTransaction);
} else {
final erc20 = ERC20(
client: _client!,
address: EthereumAddress.fromHex(contractAddress!),
chainId: chainId,
);
_sendTransaction = () async {
await erc20.transfer(
EthereumAddress.fromHex(toAddress),
BigInt.parse(amount),
credentials: privateKey,
transaction: transaction,
);
};
}
return PendingEVMChainTransaction(
signedTransaction: signedTransaction,
amount: amount,
fee: BigInt.from(gas) * (await price).getInWei,
sendTransaction: _sendTransaction,
exponent: exponent,
);
}
Transaction createTransaction({
required EthereumAddress from,
required EthereumAddress to,
required EtherAmount amount,
EtherAmount? maxPriorityFeePerGas,
}) {
return Transaction(
from: from,
to: to,
maxPriorityFeePerGas: maxPriorityFeePerGas,
value: amount,
);
}
Future<String> sendTransaction(Uint8List signedTransaction) async =>
await _client!.sendRawTransaction(prepareSignedTransactionForSending(signedTransaction));
Future getTransactionDetails(String transactionHash) async {
// Wait for the transaction receipt to become available
TransactionReceipt? receipt;
while (receipt == null) {
receipt = await _client!.getTransactionReceipt(transactionHash);
await Future.delayed(const Duration(seconds: 1));
}
// Print the receipt information
log('Transaction Hash: ${receipt.transactionHash}');
log('Block Hash: ${receipt.blockHash}');
log('Block Number: ${receipt.blockNumber}');
log('Gas Used: ${receipt.gasUsed}');
/*
Transaction Hash: [112, 244, 4, 238, 89, 199, 171, 191, 210, 236, 110, 42, 185, 202, 220, 21, 27, 132, 123, 221, 137, 90, 77, 13, 23, 43, 12, 230, 93, 63, 221, 116]
I/flutter ( 4474): Block Hash: [149, 44, 250, 119, 111, 104, 82, 98, 17, 89, 30, 190, 25, 44, 218, 118, 127, 189, 241, 35, 213, 106, 25, 95, 195, 37, 55, 131, 185, 180, 246, 200]
I/flutter ( 4474): Block Number: 17120242
I/flutter ( 4474): Gas Used: 21000
*/
// Wait for the transaction receipt to become available
TransactionInformation? transactionInformation;
while (transactionInformation == null) {
log("********************************");
transactionInformation = await _client!.getTransactionByHash(transactionHash);
await Future.delayed(const Duration(seconds: 1));
}
// Print the receipt information
log('Transaction Hash: ${transactionInformation.hash}');
log('Block Hash: ${transactionInformation.blockHash}');
log('Block Number: ${transactionInformation.blockNumber}');
log('Gas Used: ${transactionInformation.gas}');
/*
Transaction Hash: 0x70f404ee59c7abbfd2ec6e2ab9cadc151b847bdd895a4d0d172b0ce65d3fdd74
I/flutter ( 4474): Block Hash: 0x952cfa776f68526211591ebe192cda767fbdf123d56a195fc3253783b9b4f6c8
I/flutter ( 4474): Block Number: 17120242
I/flutter ( 4474): Gas Used: 53000
*/
}
Future<EVMChainERC20Balance> fetchERC20Balances(
EthereumAddress userAddress, String contractAddress) async {
final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
final balance = await erc20.balanceOf(userAddress);
int exponent = (await erc20.decimals()).toInt();
return EVMChainERC20Balance(balance, exponent: exponent);
}
Future<Erc20Token?> getErc20Token(String contractAddress) async {
try {
final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
final name = await erc20.name();
final symbol = await erc20.symbol();
final decimal = await erc20.decimals();
return Erc20Token(
name: name,
symbol: symbol,
contractAddress: contractAddress,
decimal: decimal.toInt(),
);
} catch (e) {
return null;
}
}
void stop() {
_client?.dispose();
}
Web3Client? getWeb3Client() {
return _client;
}
// Future<int> _getDecimalPlacesForContract(DeployedContract contract) async {
// final String abi = await rootBundle.loadString("assets/abi_json/erc20_abi.json");
// final contractAbi = ContractAbi.fromJson(abi, "ERC20");
//
// final contract = DeployedContract(
// contractAbi,
// EthereumAddress.fromHex(_erc20Currencies[erc20Currency]!),
// );
// final decimalsFunction = contract.function('decimals');
// final decimals = await _client!.call(
// contract: contract,
// function: decimalsFunction,
// params: [],
// );
//
// int exponent = int.parse(decimals.first.toString());
// return exponent;
// }
}

View file

@ -0,0 +1,11 @@
import 'package:cw_core/crypto_currency.dart';
class EVMChainTransactionCreationException implements Exception {
final String exceptionMessage;
EVMChainTransactionCreationException(CryptoCurrency currency)
: exceptionMessage = 'Wrong balance. Not enough ${currency.title} on your balance.';
@override
String toString() => exceptionMessage;
}

View file

@ -0,0 +1,25 @@
import 'package:intl/intl.dart';
const evmChainAmountLength = 12;
const evmChainAmountDivider = 1000000000000;
final evmChainAmountFormat = NumberFormat()
..maximumFractionDigits = evmChainAmountLength
..minimumFractionDigits = 1;
class EVMChainFormatter {
static int parseEVMChainAmount(String amount) {
try {
return (double.parse(amount) * evmChainAmountDivider).round();
} catch (_) {
return 0;
}
}
static double parseEVMChainAmountToDouble(int amount) {
try {
return amount / evmChainAmountDivider;
} catch (_) {
return 0;
}
}
}

View file

@ -1,10 +1,4 @@
class EthereumMnemonicIsIncorrectException implements Exception {
@override
String toString() =>
'Ethereum mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
}
class EthereumMnemonics {
class EVMChainMnemonics {
static const englishWordlist = <String>[
'abandon',
'ability',

View file

@ -1,9 +1,9 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/output_info.dart';
import 'package:cw_ethereum/ethereum_transaction_priority.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
class EthereumTransactionCredentials {
EthereumTransactionCredentials(
class EVMChainTransactionCredentials {
EVMChainTransactionCredentials(
this.outputs, {
required this.priority,
required this.currency,
@ -11,7 +11,7 @@ class EthereumTransactionCredentials {
});
final List<OutputInfo> outputs;
final EthereumTransactionPriority? priority;
final EVMChainTransactionPriority? priority;
final int? feeRate;
final CryptoCurrency currency;
}

View file

@ -0,0 +1,88 @@
import 'dart:convert';
import 'dart:core';
import 'dart:developer';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_evm/evm_chain_transaction_info.dart';
import 'package:cw_evm/file.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/transaction_history.dart';
part 'evm_chain_transaction_history.g.dart';
abstract class EVMChainTransactionHistory = EVMChainTransactionHistoryBase
with _$EVMChainTransactionHistory;
abstract class EVMChainTransactionHistoryBase
extends TransactionHistoryBase<EVMChainTransactionInfo> with Store {
EVMChainTransactionHistoryBase({required this.walletInfo, required String password})
: _password = password {
transactions = ObservableMap<String, EVMChainTransactionInfo>();
}
String _password;
final WalletInfo walletInfo;
//! Method to be overridden by all child classes
String getTransactionHistoryFileName();
EVMChainTransactionInfo getTransactionInfo(Map<String, dynamic> val);
//! Common methods across all child classes
Future<void> init() async => await _load();
@override
Future<void> save() async {
final transactionsHistoryFileNameForWallet = getTransactionHistoryFileName();
try {
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
String path = '$dirPath/$transactionsHistoryFileNameForWallet';
final data = json.encode({'transactions': transactions});
await writeData(path: path, password: _password, data: data);
} catch (e, s) {
log('Error while saving ${walletInfo.type.name} transaction history: ${e.toString()}');
log(s.toString());
}
}
@override
void addOne(EVMChainTransactionInfo transaction) => transactions[transaction.id] = transaction;
@override
void addMany(Map<String, EVMChainTransactionInfo> transactions) =>
this.transactions.addAll(transactions);
Future<Map<String, dynamic>> _read() async {
final transactionsHistoryFileNameForWallet = getTransactionHistoryFileName();
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
String path = '$dirPath/$transactionsHistoryFileNameForWallet';
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>? ?? {};
for (var entry in txs.entries) {
final val = entry.value;
if (val is Map<String, dynamic>) {
final tx = getTransactionInfo(val);
_update(tx);
}
}
} catch (e) {
log(e.toString());
}
}
void _update(EVMChainTransactionInfo transaction) => transactions[transaction.id] = transaction;
}

View file

@ -0,0 +1,77 @@
// ignore_for_file: overridden_fields, annotate_overrides
import 'dart:math';
import 'package:cw_core/format_amount.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
abstract class EVMChainTransactionInfo extends TransactionInfo {
EVMChainTransactionInfo({
required this.id,
required this.height,
required this.ethAmount,
required this.ethFee,
required this.tokenSymbol,
this.exponent = 18,
required this.direction,
required this.isPending,
required this.date,
required this.confirmations,
required this.to,
required this.from,
}) : amount = ethAmount.toInt(),
fee = ethFee.toInt();
final String id;
final int height;
final int amount;
final BigInt ethAmount;
final int exponent;
final TransactionDirection direction;
final DateTime date;
final bool isPending;
final int fee;
final BigInt ethFee;
final int confirmations;
final String tokenSymbol;
String? _fiatAmount;
final String? to;
final String? from;
//! Getter to be overridden in child classes
String get feeCurrency;
@override
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 ?? '';
@override
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
@override
String feeFormatted() {
final amount = (ethFee / BigInt.from(10).pow(18)).toString();
return '${amount.substring(0, min(10, amount.length))} $feeCurrency';
}
Map<String, dynamic> toJson() => {
'id': id,
'height': height,
'amount': ethAmount.toString(),
'exponent': exponent,
'fee': ethFee.toString(),
'direction': direction.index,
'date': date.millisecondsSinceEpoch,
'isPending': isPending,
'confirmations': confirmations,
'tokenSymbol': tokenSymbol,
'to': to,
'from': from,
};
}

View file

@ -1,5 +1,4 @@
//! Model used for in parsing transactions fetched using etherscan
class EthereumTransactionModel {
class EVMChainTransactionModel {
final DateTime date;
final String hash;
final String from;
@ -14,7 +13,7 @@ class EthereumTransactionModel {
final int? tokenDecimal;
final bool isError;
EthereumTransactionModel({
EVMChainTransactionModel({
required this.date,
required this.hash,
required this.from,
@ -30,7 +29,8 @@ class EthereumTransactionModel {
required this.isError,
});
factory EthereumTransactionModel.fromJson(Map<String, dynamic> json) => EthereumTransactionModel(
factory EVMChainTransactionModel.fromJson(Map<String, dynamic> json, String defaultSymbol) =>
EVMChainTransactionModel(
date: DateTime.fromMillisecondsSinceEpoch(int.parse(json["timeStamp"]) * 1000),
hash: json["hash"],
from: json["from"],
@ -41,7 +41,7 @@ class EthereumTransactionModel {
contractAddress: json["contractAddress"],
confirmations: int.parse(json["confirmations"]),
blockNumber: int.parse(json["blockNumber"]),
tokenSymbol: json["tokenSymbol"] ?? "ETH",
tokenSymbol: json["tokenSymbol"] ?? defaultSymbol,
tokenDecimal: int.tryParse(json["tokenDecimal"] ?? ""),
isError: json["isError"] == "1",
);

View file

@ -0,0 +1,52 @@
import 'package:cw_core/transaction_priority.dart';
class EVMChainTransactionPriority extends TransactionPriority {
final int tip;
const EVMChainTransactionPriority({required String title, required int raw, required this.tip})
: super(title: title, raw: raw);
static const List<EVMChainTransactionPriority> all = [fast, medium, slow];
static const EVMChainTransactionPriority slow =
EVMChainTransactionPriority(title: 'slow', raw: 0, tip: 1);
static const EVMChainTransactionPriority medium =
EVMChainTransactionPriority(title: 'Medium', raw: 1, tip: 2);
static const EVMChainTransactionPriority fast =
EVMChainTransactionPriority(title: 'Fast', raw: 2, tip: 4);
static EVMChainTransactionPriority 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 EVMChainTransactionPriority deserialize');
}
}
String get units => 'gas';
@override
String toString() {
var label = '';
switch (this) {
case EVMChainTransactionPriority.slow:
label = 'Slow';
break;
case EVMChainTransactionPriority.medium:
label = 'Medium';
break;
case EVMChainTransactionPriority.fast:
label = 'Fast';
break;
default:
break;
}
return label;
}
}

View file

@ -0,0 +1,512 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:bip32/bip32.dart' as bip32;
import 'package:bip39/bip39.dart' as bip39;
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.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_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_core/wallet_type.dart';
import 'package:cw_evm/evm_chain_client.dart';
import 'package:cw_evm/evm_chain_exceptions.dart';
import 'package:cw_evm/evm_chain_formatter.dart';
import 'package:cw_evm/evm_chain_transaction_credentials.dart';
import 'package:cw_evm/evm_chain_transaction_history.dart';
import 'package:cw_evm/evm_chain_transaction_model.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:cw_evm/evm_chain_wallet_addresses.dart';
import 'package:cw_evm/file.dart';
import 'package:hex/hex.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:web3dart/crypto.dart';
import 'package:web3dart/web3dart.dart';
import 'evm_chain_transaction_info.dart';
import 'evm_erc20_balance.dart';
part 'evm_chain_wallet.g.dart';
abstract class EVMChainWallet = EVMChainWalletBase with _$EVMChainWallet;
abstract class EVMChainWalletBase
extends WalletBase<EVMChainERC20Balance, EVMChainTransactionHistory, EVMChainTransactionInfo>
with Store {
EVMChainWalletBase({
required WalletInfo walletInfo,
required EVMChainClient client,
required CryptoCurrency nativeCurrency,
String? mnemonic,
String? privateKey,
required String password,
EVMChainERC20Balance? initialBalance,
}) : syncStatus = const NotConnectedSyncStatus(),
_password = password,
_mnemonic = mnemonic,
_hexPrivateKey = privateKey,
_isTransactionUpdating = false,
_client = client,
walletAddresses = EVMChainWalletAddresses(walletInfo),
balance = ObservableMap<CryptoCurrency, EVMChainERC20Balance>.of(
{
// Not sure of this yet, will it work? will it not?
nativeCurrency: initialBalance ?? EVMChainERC20Balance(BigInt.zero),
},
),
super(walletInfo) {
this.walletInfo = walletInfo;
transactionHistory = setUpTransactionHistory(walletInfo, 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> erc20TokensBox;
late final Box<Erc20Token> evmChainErc20TokensBox;
late final EthPrivateKey _evmChainPrivateKey;
EthPrivateKey get evmChainPrivateKey => _evmChainPrivateKey;
late EVMChainClient _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, EVMChainERC20Balance> balance;
Completer<SharedPreferences> sharedPrefs = Completer();
//! Methods to be overridden by every child
void addInitialTokens();
// Future<EVMChainWallet> open({
// required String name,
// required String password,
// required WalletInfo walletInfo,
// });
Future<void> initErc20TokensBox();
String getTransactionHistoryFileName();
Future<bool> checkIfScanProviderIsEnabled();
EVMChainTransactionInfo getTransactionInfo(
EVMChainTransactionModel transactionModel, String address);
Erc20Token createNewErc20TokenObject(Erc20Token token, String? iconPath);
EVMChainTransactionHistory setUpTransactionHistory(WalletInfo walletInfo, String password);
//! Common Methods across child classes
String idFor(String name, WalletType type) => '${walletTypeToString(type).toLowerCase()}_$name';
Future<void> init() async {
await initErc20TokensBox();
await walletAddresses.init();
await transactionHistory.init();
_evmChainPrivateKey = await getPrivateKey(
mnemonic: _mnemonic,
privateKey: _hexPrivateKey,
password: _password,
);
walletAddresses.address = _evmChainPrivateKey.address.toString();
await save();
}
@override
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
try {
if (priority is EVMChainTransactionPriority) {
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("${walletInfo.type.name.toUpperCase()} Node connection failed");
}
_client.setListeners(_evmChainPrivateKey.address, _onNewTransaction);
_setTransactionUpdateTimer();
syncStatus = ConnectedSyncStatus();
} catch (e) {
syncStatus = FailedSyncStatus();
}
}
@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();
}
}
@override
Future<PendingTransaction> createTransaction(Object credentials) async {
final _credentials = credentials as EVMChainTransactionCredentials;
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 amountToEVMChainMultiplier = pow(10, exponent);
// so far this can not be made with Ethereum as Ethereum does not support multiple recipients
if (hasMultiDestination) {
if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
throw EVMChainTransactionCreationException(transactionCurrency);
}
final totalOriginalAmount = EVMChainFormatter.parseEVMChainAmountToDouble(
outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)));
totalAmount = BigInt.from(totalOriginalAmount * amountToEVMChainMultiplier);
if (_erc20Balance.balance < totalAmount) {
throw EVMChainTransactionCreationException(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 =
EVMChainFormatter.parseEVMChainAmountToDouble(output.formattedCryptoAmount ?? 0);
totalAmount = output.sendAll
? allAmount
: BigInt.from(totalOriginalAmount * amountToEVMChainMultiplier);
if (_erc20Balance.balance < totalAmount) {
throw EVMChainTransactionCreationException(transactionCurrency);
}
}
final pendingEVMChainTransaction = await _client.signTransaction(
privateKey: _evmChainPrivateKey,
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 pendingEVMChainTransaction;
}
Future<void> _updateTransactions() async {
try {
if (_isTransactionUpdating) {
return;
}
final isProviderEnabled = await checkIfScanProviderIsEnabled();
if (!isProviderEnabled) {
return;
}
_isTransactionUpdating = true;
final transactions = await fetchTransactions();
transactionHistory.addMany(transactions);
await transactionHistory.save();
_isTransactionUpdating = false;
} catch (_) {
_isTransactionUpdating = false;
}
}
@override
Future<Map<String, EVMChainTransactionInfo>> fetchTransactions() async {
final address = _evmChainPrivateKey.address.hex;
final transactions = await _client.fetchTransactions(address);
final List<Future<List<EVMChainTransactionModel>>> erc20TokensTransactions = [];
for (var token in balance.keys) {
if (token is Erc20Token) {
erc20TokensTransactions.add(_client.fetchTransactions(
address,
contractAddress: token.contractAddress,
));
}
}
final tokensTransaction = await Future.wait(erc20TokensTransactions);
transactions.addAll(tokensTransaction.expand((element) => element));
final Map<String, EVMChainTransactionInfo> result = {};
for (var transactionModel in transactions) {
if (transactionModel.isError) {
continue;
}
result[transactionModel.hash] = getTransactionInfo(transactionModel, address);
}
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(_evmChainPrivateKey.privateKey);
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
String toJSON() => json.encode({
'mnemonic': _mnemonic,
'private_key': privateKey,
'balance': balance[currency]!.toJSON(),
});
Future<void> _updateBalance() async {
balance[currency] = await _fetchEVMChainBalance();
await _fetchErc20Balances();
await save();
}
Future<EVMChainERC20Balance> _fetchEVMChainBalance() async {
final balance = await _client.getBalance(_evmChainPrivateKey.address);
return EVMChainERC20Balance(balance.getInWei);
}
Future<void> _fetchErc20Balances() async {
for (var token in evmChainErc20TokensBox.values) {
try {
if (token.enabled) {
balance[token] = await _client.fetchERC20Balances(
_evmChainPrivateKey.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 hdPathEVMChain = "m/44'/60'/0'/0";
const index = 0;
final addressAtIndex = root.derivePath("$hdPathEVMChain/$index");
return EthPrivateKey.fromHex(HEX.encode(addressAtIndex.privateKey as List<int>));
}
Future<void>? updateBalance() async => await _updateBalance();
List<Erc20Token> get erc20Currencies => evmChainErc20TokensBox.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 newToken = createNewErc20TokenObject(token, iconPath);
await evmChainErc20TokensBox.put(newToken.contractAddress, newToken);
if (newToken.enabled) {
balance[newToken] = await _client.fetchERC20Balances(
_evmChainPrivateKey.address,
newToken.contractAddress,
);
} else {
balance.remove(newToken);
}
}
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();
}
@override
Future<void> renameWalletFiles(String newWalletName) async {
final transactionHistoryFileNameForWallet = getTransactionHistoryFileName();
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/$transactionHistoryFileNameForWallet');
// 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/$transactionHistoryFileNameForWallet');
}
// Delete old name's dir and files
await Directory(currentDirPath).delete(recursive: true);
}
void _setTransactionUpdateTimer() {
if (_transactionsUpdateTimer?.isActive ?? false) {
_transactionsUpdateTimer!.cancel();
}
_transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 10), (_) {
_updateTransactions();
_updateBalance();
});
}
/// Scan Providers:
///
/// EtherScan for Ethereum.
///
/// PolygonScan for Polygon.
void updateScanProviderUsageState(bool isEnabled) {
if (isEnabled) {
_updateTransactions();
_setTransactionUpdateTimer();
} else {
_transactionsUpdateTimer?.cancel();
}
}
@override
String signMessage(String message, {String? address}) =>
bytesToHex(_evmChainPrivateKey.signPersonalMessageToUint8List(ascii.encode(message)));
Web3Client? getWeb3Client() => _client.getWeb3Client();
}

View file

@ -1,13 +1,15 @@
import 'dart:developer';
import 'package:cw_core/wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:mobx/mobx.dart';
part 'ethereum_wallet_addresses.g.dart';
part 'evm_chain_wallet_addresses.g.dart';
class EthereumWalletAddresses = EthereumWalletAddressesBase with _$EthereumWalletAddresses;
class EVMChainWalletAddresses = EVMChainWalletAddressesBase with _$EVMChainWalletAddresses;
abstract class EthereumWalletAddressesBase extends WalletAddresses with Store {
EthereumWalletAddressesBase(WalletInfo walletInfo)
abstract class EVMChainWalletAddressesBase extends WalletAddresses with Store {
EVMChainWalletAddressesBase(WalletInfo walletInfo)
: address = '',
super(walletInfo);
@ -27,7 +29,7 @@ abstract class EthereumWalletAddressesBase extends WalletAddresses with Store {
addressesMap[address] = '';
await saveAddressesInBox();
} catch (e) {
print(e.toString());
log(e.toString());
}
}
}

View file

@ -0,0 +1,29 @@
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
class EVMChainNewWalletCredentials extends WalletCredentials {
EVMChainNewWalletCredentials({required String name, WalletInfo? walletInfo})
: super(name: name, walletInfo: walletInfo);
}
class EVMChainRestoreWalletFromSeedCredentials extends WalletCredentials {
EVMChainRestoreWalletFromSeedCredentials({
required String name,
required String password,
required this.mnemonic,
WalletInfo? walletInfo,
}) : super(name: name, password: password, walletInfo: walletInfo);
final String mnemonic;
}
class EVMChainRestoreWalletFromPrivateKey extends WalletCredentials {
EVMChainRestoreWalletFromPrivateKey({
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,50 @@
import 'dart:io';
import 'package:collection/collection.dart';
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_evm/evm_chain_wallet.dart';
import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
import 'package:hive/hive.dart';
abstract class EVMChainWalletService<T extends EVMChainWallet> extends WalletService<
EVMChainNewWalletCredentials,
EVMChainRestoreWalletFromSeedCredentials,
EVMChainRestoreWalletFromPrivateKey> {
EVMChainWalletService(this.walletInfoSource);
final Box<WalletInfo> walletInfoSource;
@override
WalletType getType();
@override
Future<T> create(EVMChainNewWalletCredentials credentials);
@override
Future<T> openWallet(String name, String password);
@override
Future<void> rename(String currentName, String password, String newName);
@override
Future<T> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials);
@override
Future<T> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials);
@override
Future<bool> isWalletExit(String name) async =>
File(await pathForWallet(name: name, type: getType())).existsSync();
@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);
}
}

View file

@ -3,10 +3,9 @@ import 'dart:math';
import 'package:cw_core/balance.dart';
class ERC20Balance extends Balance {
ERC20Balance(this.balance, {this.exponent = 18})
: super(balance.toInt(),
balance.toInt());
class EVMChainERC20Balance extends Balance {
EVMChainERC20Balance(this.balance, {this.exponent = 18})
: super(balance.toInt(), balance.toInt());
final BigInt balance;
final int exponent;
@ -28,7 +27,7 @@ class ERC20Balance extends Balance {
'exponent': exponent,
});
static ERC20Balance? fromJSON(String? jsonSource) {
static EVMChainERC20Balance? fromJSON(String? jsonSource) {
if (jsonSource == null) {
return null;
}
@ -36,12 +35,12 @@ class ERC20Balance extends Balance {
final decoded = json.decode(jsonSource) as Map;
try {
return ERC20Balance(
return EVMChainERC20Balance(
BigInt.parse(decoded['balanceInWei']),
exponent: decoded['exponent'],
);
} catch (e) {
return ERC20Balance(BigInt.zero);
return EVMChainERC20Balance(BigInt.zero);
}
}
}

View file

@ -4,14 +4,14 @@ import 'dart:typed_data';
import 'package:cw_core/pending_transaction.dart';
import 'package:web3dart/crypto.dart';
class PendingEthereumTransaction with PendingTransaction {
class PendingEVMChainTransaction with PendingTransaction {
final Function sendTransaction;
final Uint8List signedTransaction;
final BigInt fee;
final String amount;
final int exponent;
PendingEthereumTransaction({
PendingEVMChainTransaction({
required this.sendTransaction,
required this.signedTransaction,
required this.fee,

45
cw_evm/pubspec.yaml Normal file
View file

@ -0,0 +1,45 @@
name: cw_evm
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
web3dart: ^2.7.1
erc20: ^1.0.1
bip39: ^1.0.6
bip32: ^2.0.0
hex: ^0.2.0
http: ^1.1.0
hive: ^2.2.3
collection: ^1.17.1
shared_preferences: ^2.0.15
cw_core:
path: ../cw_core
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.1.11
mobx_codegen: ^2.0.7
hive_generator: ^1.1.3
flutter_lints: ^2.0.0
flutter:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic

View file

@ -0,0 +1,12 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:cw_evm/cw_evm.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

@ -1,19 +0,0 @@
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

@ -1,12 +1,12 @@
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;
import 'package:cw_evm/evm_chain_client.dart';
import 'package:cw_evm/.secrets.g.dart' as secrets;
import 'package:cw_evm/evm_chain_transaction_model.dart';
import 'package:flutter/foundation.dart';
import 'package:web3dart/web3dart.dart';
class PolygonClient extends EthereumClient {
class PolygonClient extends EVMChainClient {
@override
Transaction createTransaction({
required EthereumAddress from,
@ -28,7 +28,7 @@ class PolygonClient extends EthereumClient {
int get chainId => 137;
@override
Future<List<PolygonTransactionModel>> fetchTransactions(String address,
Future<List<EVMChainTransactionModel>> fetchTransactions(String address,
{String? contractAddress}) async {
try {
final response = await httpClient.get(Uri.https("api.polygonscan.com", "/api", {
@ -43,7 +43,9 @@ class PolygonClient extends EthereumClient {
if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
return (jsonResponse['result'] as List)
.map((e) => PolygonTransactionModel.fromJson(e as Map<String, dynamic>))
.map(
(e) => EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, 'MATIC'),
)
.toList();
}

View file

@ -1,6 +0,0 @@
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

@ -1,25 +0,0 @@
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

@ -1,18 +0,0 @@
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

@ -1,77 +1,19 @@
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_evm/evm_chain_transaction_history.dart';
import 'package:cw_evm/evm_chain_transaction_info.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();
class PolygonTransactionHistory extends EVMChainTransactionHistory {
PolygonTransactionHistory({
required super.walletInfo,
required super.password,
});
@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);
}
}
String getTransactionHistoryFileName() => 'polygon_transactions.json';
@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;
EVMChainTransactionInfo getTransactionInfo(Map<String, dynamic> val) =>
PolygonTransactionInfo.fromJson(val);
}

View file

@ -1,32 +1,21 @@
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_ethereum/ethereum_transaction_info.dart';
import 'package:cw_evm/evm_chain_transaction_info.dart';
class PolygonTransactionInfo extends EthereumTransactionInfo {
class PolygonTransactionInfo extends EVMChainTransactionInfo {
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,
);
required super.id,
required super.height,
required super.ethAmount,
required super.ethFee,
required super.tokenSymbol,
required super.direction,
required super.isPending,
required super.date,
required super.confirmations,
required super.to,
required super.from,
super.exponent,
});
factory PolygonTransactionInfo.fromJson(Map<String, dynamic> data) {
return PolygonTransactionInfo(
@ -41,9 +30,10 @@ class PolygonTransactionInfo extends EthereumTransactionInfo {
confirmations: data['confirmations'] as int,
tokenSymbol: data['tokenSymbol'] as String,
to: data['to'],
from: data['from'],
);
}
@override
String feeFormatted() => '${(ethFee / BigInt.from(10).pow(18)).toString()} MATIC';
String get feeCurrency => 'MATIC';
}

View file

@ -1,49 +0,0 @@
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

@ -1,51 +0,0 @@
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

@ -1,366 +1,109 @@
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/file.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_evm/evm_chain_transaction_history.dart';
import 'package:cw_evm/evm_chain_transaction_info.dart';
import 'package:cw_evm/evm_chain_transaction_model.dart';
import 'package:cw_evm/evm_chain_wallet.dart';
import 'package:cw_evm/evm_erc20_balance.dart';
import 'package:cw_evm/file.dart';
import 'package:cw_polygon/default_polygon_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;
import 'package:cw_polygon/polygon_client.dart';
import 'package:cw_polygon/polygon_transaction_history.dart';
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 = const 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;
late final PolygonClient _client;
EthPrivateKey get polygonPrivateKey => _polygonPrivateKey;
int? _gasPrice;
int? _estimatedGas;
bool _isTransactionUpdating;
// TODO: remove after integrating our own node and having eth_newPendingTransactionFilter
Timer? _transactionsUpdateTimer;
class PolygonWallet extends EVMChainWallet {
PolygonWallet({
required super.walletInfo,
required super.password,
super.mnemonic,
super.initialBalance,
super.privateKey,
required super.client,
}) : super(nativeCurrency: CryptoCurrency.maticpoly);
@override
WalletAddresses walletAddresses;
@override
@observable
SyncStatus syncStatus;
@override
@observable
late ObservableMap<CryptoCurrency, ERC20Balance> balance;
final Completer<SharedPreferences> _sharedPrefs = Completer();
Future<void> init() async {
Future<void> initErc20TokensBox() async {
final boxName = "${walletInfo.name.replaceAll(" ", "_")}_ ${Erc20Token.polygonBoxName}";
if (await CakeHive.boxExists(boxName)) {
polygonErc20TokensBox = await CakeHive.openBox<Erc20Token>(boxName);
evmChainErc20TokensBox = await CakeHive.openBox<Erc20Token>(boxName);
} else {
polygonErc20TokensBox = await CakeHive.openBox<Erc20Token>(boxName.replaceAll(" ", ""));
evmChainErc20TokensBox = await CakeHive.openBox<Erc20Token>(boxName.replaceAll(" ", ""));
}
await walletAddresses.init();
await transactionHistory.init();
_polygonPrivateKey = await getPrivateKey(
mnemonic: _mnemonic,
privateKey: _hexPrivateKey,
password: _password,
}
@override
void addInitialTokens() {
final initialErc20Tokens = DefaultPolygonErc20Tokens().initialPolygonErc20Tokens;
for (var token in initialErc20Tokens) {
evmChainErc20TokensBox.put(token.contractAddress, token);
}
}
@override
Future<bool> checkIfScanProviderIsEnabled() async {
bool isPolygonScanEnabled = (await sharedPrefs.future).getBool("use_polygonscan") ?? true;
return isPolygonScanEnabled;
}
@override
String getTransactionHistoryFileName() => 'polygon_transactions.json';
@override
Erc20Token createNewErc20TokenObject(Erc20Token token, String? iconPath) {
return Erc20Token(
name: token.name,
symbol: token.symbol,
contractAddress: token.contractAddress,
decimal: token.decimal,
enabled: token.enabled,
tag: token.tag ?? "MATIC",
iconPath: iconPath,
);
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 credentials0 = credentials as PolygonTransactionCredentials;
final outputs = credentials0.outputs;
final hasMultiDestination = outputs.length > 1;
final CryptoCurrency transactionCurrency =
balance.keys.firstWhere((element) => element.title == credentials0.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(credentials0.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: credentials0.outputs.first.isParsedAddress
? credentials0.outputs.first.extractedAddress!
: credentials0.outputs.first.address,
amount: totalAmount.toString(),
gas: _estimatedGas!,
priority: credentials0.priority!,
currency: transactionCurrency,
exponent: exponent,
contractAddress:
transactionCurrency is Erc20Token ? transactionCurrency.contractAddress : null,
EVMChainTransactionInfo getTransactionInfo(
EVMChainTransactionModel transactionModel, String address) {
final model = 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,
from: transactionModel.from,
);
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;
}
return model;
}
@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,
),
);
}
}
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;
EVMChainTransactionHistory setUpTransactionHistory(WalletInfo walletInfo, String password) {
return PolygonTransactionHistory(walletInfo: walletInfo, password: password);
}
@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 {
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);
final balance = EVMChainERC20Balance.fromJSON(data['balance'] as String) ??
EVMChainERC20Balance(BigInt.zero);
return PolygonWallet(
walletInfo: walletInfo,
@ -368,158 +111,7 @@ abstract class PolygonWalletBase
mnemonic: mnemonic,
privateKey: privateKey,
initialBalance: balance,
client: PolygonClient(),
);
}
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>));
}
@override
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 token0 = Erc20Token(
name: token.name,
symbol: token.symbol,
contractAddress: token.contractAddress,
decimal: token.decimal,
enabled: token.enabled,
tag: token.tag ?? "POLY",
iconPath: iconPath,
);
await polygonErc20TokensBox.put(token0.contractAddress, token0);
if (token0.enabled) {
balance[token0] = await _client.fetchERC20Balances(
_polygonPrivateKey.address,
token0.contractAddress,
);
} else {
balance.remove(token0);
}
}
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(const Duration(seconds: 10), (_) {
_updateTransactions();
_updateBalance();
});
}
void updatePolygonScanUsageState(bool isEnabled) {
if (isEnabled) {
_updateTransactions();
_setTransactionUpdateTimer();
} else {
_transactionsUpdateTimer?.cancel();
}
}
@override
String signMessage(String message, {String? address}) =>
bytesToHex(_polygonPrivateKey.signPersonalMessageToUint8List(ascii.encode(message)));
Web3Client? getWeb3Client() => _client.getWeb3Client();
}

View file

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

View file

@ -1,28 +0,0 @@
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

@ -1,32 +1,34 @@
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';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
import 'package:cw_evm/evm_chain_wallet_service.dart';
import 'package:cw_polygon/polygon_client.dart';
import 'package:cw_polygon/polygon_mnemonics_exception.dart';
import 'package:cw_polygon/polygon_wallet.dart';
class PolygonWalletService extends WalletService<PolygonNewWalletCredentials,
PolygonRestoreWalletFromSeedCredentials, PolygonRestoreWalletFromPrivateKey> {
PolygonWalletService(this.walletInfoSource);
class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
PolygonWalletService(
super.walletInfoSource, {
required this.client,
});
final Box<WalletInfo> walletInfoSource;
late PolygonClient client;
@override
Future<PolygonWallet> create(PolygonNewWalletCredentials credentials) async {
WalletType getType() => WalletType.polygon;
@override
Future<PolygonWallet> create(EVMChainNewWalletCredentials credentials) async {
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
final mnemonic = bip39.generateMnemonic(strength: strength);
final wallet = PolygonWallet(
walletInfo: credentials.walletInfo!,
mnemonic: mnemonic,
password: credentials.password!,
client: client,
);
await wallet.init();
@ -36,18 +38,11 @@ class PolygonWalletService extends WalletService<PolygonNewWalletCredentials,
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(
final wallet = await PolygonWallet.open(
name: name,
password: password,
walletInfo: walletInfo,
@ -60,19 +55,13 @@ class PolygonWalletService extends WalletService<PolygonNewWalletCredentials,
}
@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);
}
Future<PolygonWallet> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials) async {
@override
Future<PolygonWallet> restoreFromKeys(PolygonRestoreWalletFromPrivateKey credentials) async {
final wallet = PolygonWallet(
password: credentials.password!,
privateKey: credentials.privateKey,
walletInfo: credentials.walletInfo!,
client: client,
);
await wallet.init();
@ -83,15 +72,17 @@ class PolygonWalletService extends WalletService<PolygonNewWalletCredentials,
}
@override
Future<PolygonWallet> restoreFromSeed(PolygonRestoreWalletFromSeedCredentials credentials) async {
Future<PolygonWallet> restoreFromSeed(
EVMChainRestoreWalletFromSeedCredentials credentials) async {
if (!bip39.validateMnemonic(credentials.mnemonic)) {
throw EthereumMnemonicIsIncorrectException();
throw PolygonMnemonicIsIncorrectException();
}
final wallet = PolygonWallet(
password: credentials.password!,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!,
client: client,
);
await wallet.init();
@ -105,7 +96,7 @@ class PolygonWalletService extends WalletService<PolygonNewWalletCredentials,
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(
final currentWallet = await PolygonWallet.open(
password: password, name: currentName, walletInfo: currentWalletInfo);
await currentWallet.renameWalletFiles(newName);

View file

@ -16,15 +16,12 @@ dependencies:
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
cw_evm:
path: ../cw_evm
web3dart: ^2.7.1
bip32: ^2.0.0
hex: ^0.2.0
shared_preferences: ^2.0.15
hive: ^2.2.3
bip39: ^1.0.6
collection: ^1.17.1
dev_dependencies:
@ -32,8 +29,6 @@ dev_dependencies:
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

View file

@ -150,7 +150,7 @@ class CWBitcoin extends Bitcoin {
return bitcoinWallet.unspentCoins;
}
void updateUnspents(Object wallet) async {
Future<void> updateUnspents(Object wallet) async {
final bitcoinWallet = wallet as ElectrumWallet;
await bitcoinWallet.updateUnspent();
}

View file

@ -186,8 +186,10 @@ Future<void> defaultSettingsMigration(
await rewriteSecureStoragePin(secureStorage: secureStorage);
break;
case 26:
await insecureStorageMigration(
secureStorage: secureStorage, sharedPreferences: sharedPreferences);
/// commented out as it was a probable cause for some users to have white screen issues
/// maybe due to multiple access on Secure Storage at once
/// or long await time on start of the app
// await insecureStorageMigration(secureStorage: secureStorage, sharedPreferences: sharedPreferences);
break;
case 27:
await migrateTorPreferences(sharedPreferences: sharedPreferences);
@ -527,7 +529,7 @@ Future<void> changeLitecoinCurrentElectrumServerToDefault(
Future<void> changeBitcoinCashCurrentNodeToDefault(
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final server = getBitcoinCashDefaultElectrumServer(nodes: nodes);
final serverId = server?.key as int ?? 0;
final serverId = server?.key as int? ?? 0;
await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, serverId);
}

View file

@ -2,17 +2,17 @@ part of 'ethereum.dart';
class CWEthereum extends Ethereum {
@override
List<String> getEthereumWordList(String language) => EthereumMnemonics.englishWordlist;
List<String> getEthereumWordList(String language) => EVMChainMnemonics.englishWordlist;
WalletService createEthereumWalletService(Box<WalletInfo> walletInfoSource) =>
EthereumWalletService(walletInfoSource);
EthereumWalletService(walletInfoSource, client: EthereumClient());
@override
WalletCredentials createEthereumNewWalletCredentials({
required String name,
WalletInfo? walletInfo,
}) =>
EthereumNewWalletCredentials(name: name, walletInfo: walletInfo);
EVMChainNewWalletCredentials(name: name, walletInfo: walletInfo);
@override
WalletCredentials createEthereumRestoreWalletFromSeedCredentials({
@ -20,7 +20,7 @@ class CWEthereum extends Ethereum {
required String mnemonic,
required String password,
}) =>
EthereumRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic);
EVMChainRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic);
@override
WalletCredentials createEthereumRestoreWalletFromPrivateKey({
@ -28,37 +28,37 @@ class CWEthereum extends Ethereum {
required String privateKey,
required String password,
}) =>
EthereumRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
@override
String getAddress(WalletBase wallet) => (wallet as EthereumWallet).walletAddresses.address;
@override
String getPrivateKey(WalletBase wallet) {
final privateKeyHolder = (wallet as EthereumWallet).ethPrivateKey;
final privateKeyHolder = (wallet as EthereumWallet).evmChainPrivateKey;
String stringKey = bytesToHex(privateKeyHolder.privateKey);
return stringKey;
}
@override
String getPublicKey(WalletBase wallet) {
final privateKeyInUnitInt = (wallet as EthereumWallet).ethPrivateKey;
final privateKeyInUnitInt = (wallet as EthereumWallet).evmChainPrivateKey;
final publicKey = privateKeyInUnitInt.address.hex;
return publicKey;
}
@override
TransactionPriority getDefaultTransactionPriority() => EthereumTransactionPriority.medium;
TransactionPriority getDefaultTransactionPriority() => EVMChainTransactionPriority.medium;
@override
TransactionPriority getEthereumTransactionPrioritySlow() => EthereumTransactionPriority.slow;
TransactionPriority getEthereumTransactionPrioritySlow() => EVMChainTransactionPriority.slow;
@override
List<TransactionPriority> getTransactionPriorities() => EthereumTransactionPriority.all;
List<TransactionPriority> getTransactionPriorities() => EVMChainTransactionPriority.all;
@override
TransactionPriority deserializeEthereumTransactionPriority(int raw) =>
EthereumTransactionPriority.deserialize(raw: raw);
EVMChainTransactionPriority.deserialize(raw: raw);
Object createEthereumTransactionCredentials(
List<Output> outputs, {
@ -66,7 +66,7 @@ class CWEthereum extends Ethereum {
required CryptoCurrency currency,
int? feeRate,
}) =>
EthereumTransactionCredentials(
EVMChainTransactionCredentials(
outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
@ -78,7 +78,7 @@ class CWEthereum extends Ethereum {
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount))
.toList(),
priority: priority as EthereumTransactionPriority,
priority: priority as EVMChainTransactionPriority,
currency: currency,
feeRate: feeRate,
);
@ -89,15 +89,15 @@ class CWEthereum extends Ethereum {
required CryptoCurrency currency,
required int feeRate,
}) =>
EthereumTransactionCredentials(
EVMChainTransactionCredentials(
outputs,
priority: priority as EthereumTransactionPriority?,
priority: priority as EVMChainTransactionPriority?,
currency: currency,
feeRate: feeRate,
);
@override
int formatterEthereumParseAmount(String amount) => EthereumFormatter.parseEthereumAmount(amount);
int formatterEthereumParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
@override
double formatterEthereumAmountToDouble(
@ -105,7 +105,7 @@ class CWEthereum extends Ethereum {
assert(transaction != null || amount != null);
if (transaction != null) {
transaction as EthereumTransactionInfo;
transaction as EVMChainTransactionInfo;
return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
} else {
return (amount!) / BigInt.from(10).pow(exponent);
@ -134,7 +134,7 @@ class CWEthereum extends Ethereum {
@override
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
transaction as EthereumTransactionInfo;
transaction as EVMChainTransactionInfo;
if (transaction.tokenSymbol == CryptoCurrency.eth.title) {
return CryptoCurrency.eth;
}
@ -146,7 +146,7 @@ class CWEthereum extends Ethereum {
@override
void updateEtherscanUsageState(WalletBase wallet, bool isEnabled) {
(wallet as EthereumWallet).updateEtherscanUsageState(isEnabled);
(wallet as EthereumWallet).updateScanProviderUsageState(isEnabled);
}
@override

View file

@ -331,7 +331,7 @@ class CWMonero extends Monero {
}
@override
void updateUnspents(Object wallet) async {
Future<void> updateUnspents(Object wallet) async {
final moneroWallet = wallet as MoneroWallet;
await moneroWallet.updateUnspent();
}

View file

@ -2,17 +2,17 @@ part of 'polygon.dart';
class CWPolygon extends Polygon {
@override
List<String> getPolygonWordList(String language) => EthereumMnemonics.englishWordlist;
List<String> getPolygonWordList(String language) => EVMChainMnemonics.englishWordlist;
WalletService createPolygonWalletService(Box<WalletInfo> walletInfoSource) =>
PolygonWalletService(walletInfoSource);
PolygonWalletService(walletInfoSource, client: PolygonClient());
@override
WalletCredentials createPolygonNewWalletCredentials({
required String name,
WalletInfo? walletInfo,
}) =>
PolygonNewWalletCredentials(name: name, walletInfo: walletInfo);
EVMChainNewWalletCredentials(name: name, walletInfo: walletInfo);
@override
WalletCredentials createPolygonRestoreWalletFromSeedCredentials({
@ -20,7 +20,7 @@ class CWPolygon extends Polygon {
required String mnemonic,
required String password,
}) =>
PolygonRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic);
EVMChainRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic);
@override
WalletCredentials createPolygonRestoreWalletFromPrivateKey({
@ -28,37 +28,37 @@ class CWPolygon extends Polygon {
required String privateKey,
required String password,
}) =>
PolygonRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
EVMChainRestoreWalletFromPrivateKey(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;
final privateKeyHolder = (wallet as PolygonWallet).evmChainPrivateKey;
String stringKey = bytesToHex(privateKeyHolder.privateKey);
return stringKey;
}
@override
String getPublicKey(WalletBase wallet) {
final privateKeyInUnitInt = (wallet as PolygonWallet).polygonPrivateKey;
final privateKeyInUnitInt = (wallet as PolygonWallet).evmChainPrivateKey;
final publicKey = privateKeyInUnitInt.address.hex;
return publicKey;
}
@override
TransactionPriority getDefaultTransactionPriority() => PolygonTransactionPriority.medium;
TransactionPriority getDefaultTransactionPriority() => EVMChainTransactionPriority.medium;
@override
TransactionPriority getPolygonTransactionPrioritySlow() => PolygonTransactionPriority.slow;
TransactionPriority getPolygonTransactionPrioritySlow() => EVMChainTransactionPriority.slow;
@override
List<TransactionPriority> getTransactionPriorities() => PolygonTransactionPriority.all;
List<TransactionPriority> getTransactionPriorities() => EVMChainTransactionPriority.all;
@override
TransactionPriority deserializePolygonTransactionPriority(int raw) =>
PolygonTransactionPriority.deserialize(raw: raw);
EVMChainTransactionPriority.deserialize(raw: raw);
Object createPolygonTransactionCredentials(
List<Output> outputs, {
@ -66,7 +66,7 @@ class CWPolygon extends Polygon {
required CryptoCurrency currency,
int? feeRate,
}) =>
PolygonTransactionCredentials(
EVMChainTransactionCredentials(
outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
@ -78,7 +78,7 @@ class CWPolygon extends Polygon {
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount))
.toList(),
priority: priority as PolygonTransactionPriority,
priority: priority as EVMChainTransactionPriority,
currency: currency,
feeRate: feeRate,
);
@ -89,15 +89,15 @@ class CWPolygon extends Polygon {
required CryptoCurrency currency,
required int feeRate,
}) =>
PolygonTransactionCredentials(
EVMChainTransactionCredentials(
outputs,
priority: priority as PolygonTransactionPriority?,
priority: priority as EVMChainTransactionPriority?,
currency: currency,
feeRate: feeRate,
);
@override
int formatterPolygonParseAmount(String amount) => PolygonFormatter.parsePolygonAmount(amount);
int formatterPolygonParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
@override
double formatterPolygonAmountToDouble(
@ -105,7 +105,7 @@ class CWPolygon extends Polygon {
assert(transaction != null || amount != null);
if (transaction != null) {
transaction as PolygonTransactionInfo;
transaction as EVMChainTransactionInfo;
return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
} else {
return (amount!) / BigInt.from(10).pow(exponent);
@ -134,7 +134,7 @@ class CWPolygon extends Polygon {
@override
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
transaction as PolygonTransactionInfo;
transaction as EVMChainTransactionInfo;
if (transaction.tokenSymbol == CryptoCurrency.maticpoly.title) {
return CryptoCurrency.maticpoly;
}
@ -146,7 +146,7 @@ class CWPolygon extends Polygon {
@override
void updatePolygonScanUsageState(WalletBase wallet, bool isEnabled) {
(wallet as PolygonWallet).updatePolygonScanUsageState(isEnabled);
(wallet as PolygonWallet).updateScanProviderUsageState(isEnabled);
}
@override

View file

@ -101,26 +101,26 @@ class NFTDetailsPage extends BasePage {
SizedBox(height: 16),
_NFTSingleInfoTile(
infoType: S.current.name,
infoValue: nftAsset.normalizedMetadata?.name ?? '',
infoValue: nftAsset.normalizedMetadata?.name ?? '---',
),
if (nftAsset.normalizedMetadata?.description != null) ...[
SizedBox(height: 16),
_NFTSingleInfoTile(
infoType: 'Description',
infoValue: nftAsset.normalizedMetadata?.description ?? '',
infoType: S.current.description,
infoValue: nftAsset.normalizedMetadata?.description ?? '---',
),
],
SizedBox(height: 16),
_NFTSingleInfoTile(
infoType: 'Contract Name',
infoValue: nftAsset.name ?? '',
infoType: S.current.contractName,
infoValue: nftAsset.name ?? '---',
),
SizedBox(height: 8),
_NFTSingleInfoTile(
infoType: 'Contract Symbol',
infoValue: nftAsset.symbol ?? '',
infoType: S.current.contractSymbol,
infoValue: nftAsset.symbol ?? '---',
),
],
),

View file

@ -13,22 +13,15 @@ class NFTTileWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () => Navigator.pushNamed(context, Routes.nftDetailsPage,
arguments: nftAsset),
onTap: () => Navigator.pushNamed(context, Routes.nftDetailsPage, arguments: nftAsset),
child: Container(
width: double.infinity,
margin: const EdgeInsets.only(left: 16, right: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30.0),
border: Border.all(
color: Theme.of(context)
.extension<BalancePageTheme>()!
.cardBorderColor,
width: 1,
),
color: Theme.of(context)
.extension<SyncIndicatorTheme>()!
.syncedBackgroundColor,
color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor, width: 1),
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
),
child: Row(
children: [
@ -40,14 +33,10 @@ class NFTTileWidget extends StatelessWidget {
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.0),
border: Border.all(
color: Theme.of(context)
.extension<BalancePageTheme>()!
.cardBorderColor,
color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
width: 1,
),
color: Theme.of(context)
.extension<SyncIndicatorTheme>()!
.syncedBackgroundColor,
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
),
child: NFTImageWidget(
imageUrl: nftAsset.normalizedMetadata?.imageUrl,
@ -59,27 +48,23 @@ class NFTTileWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${nftAsset.name ?? ''} - ${nftAsset.symbol ?? ''}',
'${nftAsset.name ?? '---'} - ${nftAsset.symbol ?? '---'}',
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
color: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
height: 1,
),
),
SizedBox(height: 8),
Text(
nftAsset.normalizedMetadata?.name ?? nftAsset.name ?? "",
nftAsset.normalizedMetadata?.name ?? nftAsset.name ?? "---",
style: TextStyle(
fontSize: 20,
fontFamily: 'Lato',
fontWeight: FontWeight.w900,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
color: Theme.of(context).extension<BalancePageTheme>()!.assetTitleColor,
height: 1,
),
),
@ -92,4 +77,3 @@ class NFTTileWidget extends StatelessWidget {
);
}
}

View file

@ -283,15 +283,17 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
if (state is TransactionCommitted) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext popupContext) {
return AlertWithOneAction(
alertTitle: S.of(popupContext).sending,
alertContent: S.of(popupContext).transaction_sent,
buttonText: S.of(popupContext).ok,
buttonAction: () => Navigator.of(popupContext).pop());
});
if (context.mounted) {
showPopUp<void>(
context: context,
builder: (BuildContext popupContext) {
return AlertWithOneAction(
alertTitle: S.of(popupContext).sending,
alertContent: S.of(popupContext).transaction_sent,
buttonText: S.of(popupContext).ok,
buttonAction: () => Navigator.of(popupContext).pop());
});
}
});
}
});

View file

@ -113,13 +113,15 @@ class AddressCell extends StatelessWidget {
color: textColor,
),
),
AutoSizeText(
formattedAddress,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: isChange ? 10 : 14,
color: textColor,
Flexible(
child: AutoSizeText(
formattedAddress,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: isChange ? 10 : 14,
color: textColor,
),
),
),
],

View file

@ -53,16 +53,16 @@ class RootState extends State<Root> with WidgetsBindingObserver {
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) async {
bool value = await widget.authService.requireAuth();
setState(() {
_requestAuth = value;
WidgetsBinding.instance.addObserver(this);
widget.authService.requireAuth().then((value) {
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() => _requestAuth = value);
});
});
_isInactiveController = StreamController<bool>.broadcast();
_isInactive = false;
_postFrameCallback = false;
WidgetsBinding.instance.addObserver(this);
super.initState();
if (DeviceInfo.instance.isMobile) {
initUniLinks();

View file

@ -946,7 +946,7 @@ abstract class SettingsStoreBase with Store {
final allowBiometricalAuthentication = await SecureKey.getBool(
secureStorage: secureStorage,
sharedPreferences: sharedPreferences,
key: SecureKey.pinTimeOutDuration,
key: SecureKey.allowBiometricalAuthenticationKey,
) ??
false;
@ -1273,6 +1273,16 @@ abstract class SettingsStoreBase with Store {
) ??
totpSecretKey;
final timeOutDuration = await SecureKey.getInt(
secureStorage: _secureStorage,
sharedPreferences: sharedPreferences,
key: SecureKey.pinTimeOutDuration,
);
pinTimeOutDuration = timeOutDuration != null
? PinCodeRequiredDuration.deserialize(raw: timeOutDuration)
: defaultPinCodeTimeOutDuration;
allowBiometricalAuthentication = await SecureKey.getBool(
secureStorage: _secureStorage,
sharedPreferences: sharedPreferences,

View file

@ -239,6 +239,8 @@ abstract class TransactionDetailsViewModelBase with Store {
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!),
if (tx.direction == TransactionDirection.incoming && tx.from != null)
StandartListItem(title: S.current.transaction_details_source_address, value: tx.from!),
];
items.addAll(_items);
@ -271,8 +273,10 @@ abstract class TransactionDetailsViewModelBase with Store {
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)
if (showRecipientAddress && tx.to != null && tx.direction == TransactionDirection.outgoing)
StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!),
if (tx.direction == TransactionDirection.incoming && tx.from != null)
StandartListItem(title: S.current.transaction_details_source_address, value: tx.from!),
];
items.addAll(_items);

View file

@ -16,30 +16,17 @@ abstract class UnspentCoinsListViewModelBase with Store {
UnspentCoinsListViewModelBase(
{required this.wallet, required Box<UnspentCoinsInfo> unspentCoinsInfo})
: _unspentCoinsInfo = unspentCoinsInfo {
_updateUnspentCoinsInfo();
_updateUnspents();
}
WalletBase wallet;
final Box<UnspentCoinsInfo> _unspentCoinsInfo;
@computed
ObservableList<UnspentCoinsItem> get items => ObservableList.of(_getUnspents().map((elem) {
final info =
getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
final ObservableList<UnspentCoinsItem> _items = ObservableList();
return UnspentCoinsItem(
address: elem.address,
amount: '${formatAmountToString(elem.value)} ${wallet.currency.title}',
hash: elem.hash,
isFrozen: info.isFrozen,
note: info.note,
isSending: info.isSending,
amountRaw: elem.value,
vout: elem.vout,
keyImage: elem.keyImage,
isChange: elem.isChange,
);
}));
@computed
ObservableList<UnspentCoinsItem> get items => _items;
Future<void> saveUnspentCoinInfo(UnspentCoinsItem item) async {
try {
@ -77,9 +64,14 @@ abstract class UnspentCoinsListViewModelBase with Store {
}
Future<void> _updateUnspents() async {
if (wallet.type == WalletType.monero) return monero!.updateUnspents(wallet);
if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type))
return bitcoin!.updateUnspents(wallet);
if (wallet.type == WalletType.monero) {
await monero!.updateUnspents(wallet);
}
if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type)) {
await bitcoin!.updateUnspents(wallet);
}
_updateUnspentCoinsInfo();
}
List<Unspent> _getUnspents() {
@ -88,4 +80,26 @@ abstract class UnspentCoinsListViewModelBase with Store {
return bitcoin!.getUnspents(wallet);
return List.empty();
}
@action
void _updateUnspentCoinsInfo() {
_items.clear();
_items.addAll(_getUnspents().map((elem) {
final info =
getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
return UnspentCoinsItem(
address: elem.address,
amount: '${formatAmountToString(elem.value)} ${wallet.currency.title}',
hash: elem.hash,
isFrozen: info.isFrozen,
note: info.note,
isSending: info.isSending,
amountRaw: elem.value,
vout: elem.vout,
keyImage: elem.keyImage,
isChange: elem.isChange,
);
}));
}
}

View file

@ -1,9 +1,8 @@
cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_evm && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
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

@ -772,9 +772,12 @@
"pause_wallet_creation": ".ﺎﻴًﻟﺎﺣ ﺎﺘًﻗﺆﻣ ﺔﻔﻗﻮﺘﻣ Haven Wallet ءﺎﺸﻧﺇ ﻰﻠﻋ ﺓﺭﺪﻘﻟﺍ",
"tor_feature_disabled": "يتم تعطيل هذه الميزة بينما يتم تمكين وضع TOR فقط لحماية خصوصيتك لأن هذه الميزة لا تتصل عبر Tor",
"tor_only_warning": "قد يتم تعطيل بعض الميزات لحماية خصوصيتك عند استخدام وضع TOR فقط",
"contractName": "ﺪﻘﻌﻟﺍ ﻢﺳﺍ",
"contractSymbol": "ﺪﻘﻌﻟﺍ ﺰﻣﺭ",
"description": "ﻒﺻﻭ",
"camera_consent": ".ﻞﻴﺻﺎﻔﺘﻟﺍ ﻰﻠﻋ ﻝﻮﺼﺤﻠﻟ ﻢﻬﺑ ﺔﺻﺎﺨﻟﺍ ﺔﻴﺻﻮﺼﺨﻟﺍ ﺔﺳﺎﻴﺳ ﻦﻣ ﻖﻘﺤﺘﻟﺍ ﻰﺟﺮﻳ .${provider} ﻝﻮﻠ",
"no_relays": " ﺕﻼﺣﺮﻤﻟﺍ ﻻ",
"choose_relay": " ﻡﺍﺪﺨﺘﺳﻼﻟ ﻊﺑﺎﺘﺘﻟﺍ ﺭﺎﻴﺘﺧﺍ ءﺎﺟﺮﻟﺍ",
"no_relays": "ﺕﻼﺣﺮﻤﻟﺍ ﻻ",
"choose_relay": "ﻡﺍﺪﺨﺘﺳﻼﻟ ﻊﺑﺎﺘﺘﻟﺍ ﺭﺎﻴﺘﺧﺍ ءﺎﺟﺮﻟﺍ",
"no_relays_message": ".ﻪﺑ ﺹﺎﺨﻟﺍ Nostr ﻞﺠﺳ ﻰﻟﺇ ﺕﻼﺣﺮﻤﻟﺍ ﺔﻓﺎﺿﻹ ﻢﻠﺘﺴﻤﻟﺍ ﺩﺎﺷﺭﺇ ﻰﺟﺮﻳ .ﺕﻼﺣﺮﻣ ﻱﺃ ﻰﻠﻋ ﻱﻮﺘﺤﻳ ﻻ",
"no_relay_on_domain": ".ﻡﺍﺪﺨﺘﺳﻼﻟ ﻊﺑﺎﺘﺘﻟﺍ ﺭﺎﻴﺘﺧﺍ ءﺎﺟﺮﻟﺍ .ﺡﺎﺘﻣ ﺮﻴﻏ ﻞﻴﺣﺮﺘﻟﺍ ﻥﺃ ﻭﺃ ﻡﺪﺨﺘﺴﻤﻟﺍ ﻝﺎﺠﻤﻟ ﻞﻴﺣﺮﺗ ﺪ"
}

View file

@ -768,6 +768,9 @@
"pause_wallet_creation": "Възможността за създаване на Haven Wallet в момента е на пауза.",
"tor_feature_disabled": "Тази функция е деактивирана, докато само режимът на TOR е активиран да защити вашата поверителност, тъй като тази функция не се свързва над TOR",
"tor_only_warning": "Някои функции могат да бъдат деактивирани, за да защитят вашата поверителност, когато използвате само TOR режим",
"contractName": "Име на договора",
"contractSymbol": "Договор Символ",
"description": "Описание",
"camera_consent": "Вашият фотоапарат ще бъде използван за заснемане на изображение с цел идентификация от ${provider}. Моля, проверете тяхната политика за поверителност за подробности.",
"no_relays": "Без релета",
"choose_relay": "Моля, изберете реле, което да използвате",

View file

@ -768,6 +768,9 @@
"pause_wallet_creation": "Možnost vytvářet Haven Wallet je momentálně pozastavena.",
"tor_feature_disabled": "Tato funkce je zakázána, zatímco režim pouze TOR je povolen k ochraně vašeho soukromí, protože tato funkce se nepřipojuje přes tor",
"tor_only_warning": "Některé funkce mohou být deaktivovány k ochraně vašeho soukromí při používání režimu pouze TOR",
"contractName": "Název smlouvy",
"contractSymbol": "Symbol smlouvy",
"description": "Popis",
"camera_consent": "Váš fotoaparát použije k pořízení snímku pro účely identifikace ${provider}. Podrobnosti najdete v jejich Zásadách ochrany osobních údajů.",
"no_relays": "Žádná relé",
"choose_relay": "Vyberte relé, které chcete použít",

View file

@ -776,6 +776,9 @@
"pause_wallet_creation": "Die Möglichkeit, Haven Wallet zu erstellen, ist derzeit pausiert.",
"tor_feature_disabled": "Diese Funktion ist deaktiviert, während der TOR -Modus für den Schutz Ihrer Privatsphäre aktiviert ist, da diese Funktion nicht über TOR hergestellt wird",
"tor_only_warning": "Einige Funktionen können deaktiviert sein, um Ihre Privatsphäre zu schützen, wenn Sie nur den TOR -Modus verwenden",
"contractName": "Vertragsname",
"contractSymbol": "Vertragssymbol",
"description": "Beschreibung",
"camera_consent": "Mit Ihrer Kamera wird bis zum ${provider} ein Bild zur Identifizierung aufgenommen. Weitere Informationen finden Sie in deren Datenschutzbestimmungen.",
"no_relays": "Keine Relais",
"choose_relay": "Bitte wählen Sie ein zu verwendendes Relais aus",

View file

@ -777,6 +777,9 @@
"pause_wallet_creation": "Ability to create Haven Wallet is currently paused.",
"tor_feature_disabled": "This feature is disabled while Tor Only mode is enabled to protect your privacy as this feature doesn't connect over Tor",
"tor_only_warning": "Some features may be disabled to protect your privacy when using Tor only mode",
"contractName": "Contract Name",
"contractSymbol": "Contract Symbol",
"description": "Description",
"camera_consent": "Your camera will be used to capture an image for identification purposes by ${provider}. Please check their Privacy Policy for details.",
"no_relays": "No relays",
"choose_relay": "Please choose a relay to use",

View file

@ -776,6 +776,9 @@
"pause_wallet_creation": "La capacidad para crear Haven Wallet está actualmente pausada.",
"tor_feature_disabled": "Esta característica está deshabilitada, mientras que el modo de solo tor está habilitado para proteger su privacidad, ya que esta función no se conecta a través de Tor",
"tor_only_warning": "Algunas características pueden desactivarse para proteger su privacidad cuando se usa solo el modo Tor",
"contractName": "Nombre del contrato",
"contractSymbol": "Símbolo de contrato",
"description": "Descripción",
"camera_consent": "Su cámara será utilizada para capturar una imagen con fines de identificación por ${provider}. Consulte su Política de privacidad para obtener más detalles.",
"no_relays": "Sin relevos",
"choose_relay": "Por favor elija un relé para usar",

View file

@ -776,6 +776,9 @@
"pause_wallet_creation": "La possibilité de créer Haven Wallet est actuellement suspendue.",
"tor_feature_disabled": "Cette fonction est désactivée tandis que le mode Tor unique",
"tor_only_warning": "Certaines fonctionnalités peuvent être désactivées pour protéger votre vie privée lorsque vous utilisez le mode Tor uniquement",
"contractName": "Nom du contrat",
"contractSymbol": "Symbole du contrat",
"description": "Description",
"camera_consent": "Votre appareil photo sera utilisé pour capturer une image à des fins d'identification par ${provider}. Veuillez consulter leur politique de confidentialité pour plus de détails.",
"no_relays": "Pas de relais",
"choose_relay": "Veuillez choisir un relais à utiliser",

View file

@ -758,6 +758,9 @@
"pause_wallet_creation": "A halin yanzu an dakatar da ikon ƙirƙirar Haven Wallet.",
"tor_feature_disabled": "An kunna wannan fasalin kawai yayin da kawai ana kunna yanayin don kare sirrinka saboda wannan fasalin bashi da alaƙa da tor",
"tor_only_warning": "Ana iya kashe wasu fasaloli don kare sirrinka lokacin da kake amfani da tor kawai",
"contractName": "Sunan Kwangila",
"contractSymbol": "Alamar Kwangila",
"description": "Bayani",
"camera_consent": "Za a yi amfani da kyamarar ku don ɗaukar hoto don dalilai na tantancewa ta ${provider}. Da fatan za a duba Manufar Sirri don cikakkun bayanai.",
"no_relays": "Babu relays",
"choose_relay": "Da fatan za a zaɓi gudun ba da sanda don amfani",

View file

@ -776,6 +776,9 @@
"pause_wallet_creation": "हेवन वॉलेट बनाने की क्षमता फिलहाल रुकी हुई है।",
"tor_feature_disabled": "यह सुविधा अक्षम है, जबकि टॉर केवल मोड आपकी गोपनीयता की सुरक्षा के लिए सक्षम है क्योंकि यह सुविधा टीओआर से कनेक्ट नहीं होती है",
"tor_only_warning": "TOR केवल मोड का उपयोग करते समय आपकी गोपनीयता की सुरक्षा के लिए कुछ सुविधाएँ अक्षम हो सकती हैं",
"contractName": "अनुबंध का नाम",
"contractSymbol": "अनुबंध चिह्न",
"description": "विवरण",
"camera_consent": "आपके कैमरे का उपयोग ${provider} द्वारा पहचान उद्देश्यों के लिए एक छवि कैप्चर करने के लिए किया जाएगा। विवरण के लिए कृपया उनकी गोपनीयता नीति जांचें।",
"no_relays": "कोई रिले नहीं",
"choose_relay": "कृपया उपयोग करने के लिए एक रिले चुनें",

View file

@ -774,6 +774,9 @@
"pause_wallet_creation": "Mogućnost stvaranja novčanika Haven trenutno je pauzirana.",
"tor_feature_disabled": "Ova je značajka onemogućena dok je način samo TOR omogućen kako bi zaštitio vašu privatnost jer se ova značajka ne povezuje preko Tor -a",
"tor_only_warning": "Neke značajke mogu biti onemogućene za zaštitu vaše privatnosti kada koristite TOR način",
"contractName": "Naziv ugovora",
"contractSymbol": "Simbol ugovora",
"description": "Opis",
"camera_consent": "Vaš će fotoaparat koristiti za snimanje slike u svrhu identifikacije od strane ${provider}. Pojedinosti potražite u njihovoj politici privatnosti.",
"no_relays": "Nema releja",
"choose_relay": "Odaberite relej za korištenje",

View file

@ -764,6 +764,9 @@
"pause_wallet_creation": "Kemampuan untuk membuat Haven Wallet saat ini dijeda.",
"tor_feature_disabled": "Fitur ini dinonaktifkan sementara mode Tor Only diaktifkan untuk melindungi privasi Anda karena fitur ini tidak terhubung melalui Tor",
"tor_only_warning": "Beberapa fitur mungkin dinonaktifkan untuk melindungi privasi Anda saat menggunakan mode tor saja",
"contractName": "Nama Kontrak",
"contractSymbol": "Simbol Kontrak",
"description": "Keterangan",
"camera_consent": "Kamera Anda akan digunakan untuk mengambil gambar untuk tujuan identifikasi oleh ${provider}. Silakan periksa Kebijakan Privasi mereka untuk detailnya.",
"no_relays": "Tidak ada relay",
"choose_relay": "Silakan pilih relai yang akan digunakan",

View file

@ -776,6 +776,9 @@
"pause_wallet_creation": "La possibilità di creare Haven Wallet è attualmente sospesa.",
"tor_feature_disabled": "Questa funzione è disabilitata mentre la modalità solo TOR è abilitata per proteggere la tua privacy in quanto questa funzione non si collega a Tor",
"tor_only_warning": "Alcune funzionalità possono essere disabilitate per proteggere la tua privacy quando si utilizzano solo la modalità Tor",
"contractName": "Nome del contratto",
"contractSymbol": "Simbolo del contratto",
"description": "Descrizione",
"camera_consent": "La tua fotocamera verrà utilizzata per acquisire un'immagine a scopo identificativo da ${provider}. Si prega di controllare la loro Informativa sulla privacy per i dettagli.",
"no_relays": "Nessun relè",
"choose_relay": "Scegli un relè da utilizzare",

View file

@ -776,6 +776,9 @@
"pause_wallet_creation": "Haven Wallet を作成する機能は現在一時停止されています。",
"tor_feature_disabled": "この機能はプライバシーを保護するためにTORのみモードが有効になっている間、この機能は無効になります。この機能はTORに接続していないため",
"tor_only_warning": "TORのみのモードを使用する場合、プライバシーを保護するためにいくつかの機能が無効になる場合があります",
"contractName": "契約名",
"contractSymbol": "契約記号",
"description": "説明",
"camera_consent": "あなたのカメラは、${provider}_ までに識別目的で画像を撮影するために使用されます。詳細については、プライバシー ポリシーをご確認ください。",
"no_relays": "リレーなし",
"choose_relay": "使用するリレーを選択してください",

View file

@ -774,6 +774,9 @@
"pause_wallet_creation": "Haven Wallet 생성 기능이 현재 일시 중지되었습니다.",
"tor_feature_disabled": "이 기능은 TOR 전용 모드가 사용되지 않으므로이 기능은 TOR에 연결되지 않으므로 개인 정보를 보호 할 수 있습니다.",
"tor_only_warning": "Tor 전용 모드를 사용할 때 개인 정보를 보호하기 위해 일부 기능이 비활성화 될 수 있습니다.",
"contractName": "계약명",
"contractSymbol": "계약 기호",
"description": "설명",
"camera_consent": "귀하의 카메라는 ${provider}의 식별 목적으로 이미지를 캡처하는 데 사용됩니다. 자세한 내용은 해당 개인정보 보호정책을 확인하세요.",
"no_relays": "릴레이 없음",
"choose_relay": "사용할 릴레이를 선택해주세요",

View file

@ -774,6 +774,9 @@
"pause_wallet_creation": "Haven Wallet ဖန်တီးနိုင်မှုကို လောလောဆယ် ခေတ္တရပ်ထားသည်။",
"tor_feature_disabled": "ဤအင်္ဂါရပ်ကိုမသန်မစွမ်းဖြစ်သော်လည်း Tor တစ်ခုတည်းသော mode ကိုသင်၏ privacy ကိုကာကွယ်ရန်အတွက်ဤအင်္ဂါရပ်သည် Tor ကိုမချိတ်ဆက်ပါကကာကွယ်နိုင်သည်",
"tor_only_warning": "Tor တစ်ခုတည်းသော mode ကိုသုံးသောအခါသင်၏ privacy ကိုကာကွယ်ရန်အချို့သောအင်္ဂါရပ်များကိုပိတ်ထားနိုင်သည်",
"contractName": "စာချုပ်အမည်",
"contractSymbol": "စာချုပ်သင်္ကေတ",
"description": "ဖော်ပြချက်",
"camera_consent": "မှတ်ပုံတင်ခြင်းရည်ရွယ်ချက်များအတွက် ${provider} တွင် သင့်ကင်မရာကို အသုံးပြုပါမည်။ အသေးစိတ်အတွက် ၎င်းတို့၏ ကိုယ်ရေးကိုယ်တာမူဝါဒကို စစ်ဆေးပါ။",
"no_relays": "Relay မရှိပါ။",
"choose_relay": "အသုံးပြုရန် relay ကိုရွေးချယ်ပါ။",

View file

@ -776,6 +776,9 @@
"pause_wallet_creation": "De mogelijkheid om Haven Wallet te maken is momenteel onderbroken.",
"tor_feature_disabled": "Deze functie is uitgeschakeld, terwijl alleen TOR -modus is ingeschakeld om uw privacy te beschermen, omdat deze functie geen verbinding maakt via Tor",
"tor_only_warning": "Sommige functies kunnen worden uitgeschakeld om uw privacy te beschermen wanneer u alleen de Tor -modus gebruikt",
"contractName": "Contractnaam",
"contractSymbol": "Contractsymbool",
"description": "Beschrijving",
"camera_consent": "Uw camera wordt gebruikt om vóór ${provider} een beeld vast te leggen voor identificatiedoeleinden. Raadpleeg hun privacybeleid voor meer informatie.",
"no_relays": "Geen relais",
"choose_relay": "Kies een relais dat u wilt gebruiken",

View file

@ -776,6 +776,9 @@
"pause_wallet_creation": "Możliwość utworzenia Portfela Haven jest obecnie wstrzymana.",
"tor_feature_disabled": "Ta funkcja jest wyłączona, podczas gdy tryb TOR jest włączony do ochrony prywatności, ponieważ ta funkcja nie łączy się z Tor",
"tor_only_warning": "Niektóre funkcje mogą być wyłączone w celu ochrony prywatności podczas korzystania z trybu TOR",
"contractName": "Nazwa umowy",
"contractSymbol": "Symbol kontraktu",
"description": "Opis",
"camera_consent": "Twój aparat zostanie użyty do przechwycenia obrazu w celach identyfikacyjnych przez ${provider}. Aby uzyskać szczegółowe informacje, sprawdź ich Politykę prywatności.",
"no_relays": "Żadnych przekaźników",
"choose_relay": "Wybierz przekaźnik, którego chcesz użyć",

View file

@ -775,6 +775,9 @@
"pause_wallet_creation": "A capacidade de criar a Haven Wallet está atualmente pausada.",
"tor_feature_disabled": "Esse recurso está desativado, enquanto o modo apenas Tor está habilitado para proteger sua privacidade, pois esse recurso não se conecta",
"tor_only_warning": "Alguns recursos podem ser desativados para proteger sua privacidade ao usar apenas o modo Tor",
"contractName": "Nome do contrato",
"contractSymbol": "Símbolo do Contrato",
"description": "Descrição",
"camera_consent": "Sua câmera será usada para capturar uma imagem para fins de identificação por ${provider}. Por favor, verifique a Política de Privacidade para obter detalhes.",
"no_relays": "Sem relés",
"choose_relay": "Escolha um relé para usar",

View file

@ -776,6 +776,9 @@
"pause_wallet_creation": "Возможность создания Haven Wallet в настоящее время приостановлена.",
"tor_feature_disabled": "Эта функция отключена, в то время как режим только Tor включен для защиты вашей конфиденциальности, поскольку эта функция не подключается к Tor",
"tor_only_warning": "Некоторые функции могут быть отключены для защиты вашей конфиденциальности при использовании только режима Tor",
"contractName": "Название контракта",
"contractSymbol": "Символ контракта",
"description": "Описание",
"camera_consent": "Ваша камера будет использоваться для захвата изображения в целях идентификации ${provider}. Пожалуйста, ознакомьтесь с их Политикой конфиденциальности для получения подробной информации.",
"no_relays": "Нет реле",
"choose_relay": "Пожалуйста, выберите реле для использования",

View file

@ -774,6 +774,9 @@
"pause_wallet_creation": "ขณะนี้ความสามารถในการสร้าง Haven Wallet ถูกหยุดชั่วคราว",
"tor_feature_disabled": "คุณสมบัตินี้ถูกปิดใช้งานในขณะที่โหมด Tor Only เปิดใช้งานเพื่อป้องกันความเป็นส่วนตัวของคุณเนื่องจากคุณสมบัตินี้ไม่เชื่อมต่อกับ Tor",
"tor_only_warning": "คุณสมบัติบางอย่างอาจถูกปิดใช้งานเพื่อปกป้องความเป็นส่วนตัวของคุณเมื่อใช้โหมด TOR เท่านั้น",
"contractName": "ชื่อสัญญา",
"contractSymbol": "สัญลักษณ์สัญญา",
"description": "คำอธิบาย",
"camera_consent": "กล้องของคุณจะถูกนำมาใช้เพื่อจับภาพเพื่อวัตถุประสงค์ในการระบุตัวตนภายใน ${provider} โปรดตรวจสอบนโยบายความเป็นส่วนตัวเพื่อดูรายละเอียด",
"no_relays": "ไม่มีรีเลย์",
"choose_relay": "กรุณาเลือกรีเลย์ที่จะใช้",

View file

@ -770,6 +770,9 @@
"pause_wallet_creation": "Kasalukuyang naka-pause ang kakayahang gumawa ng Haven Wallet.",
"tor_feature_disabled": "Ang tampok na ito ay hindi pinagana habang ang mode lamang ay pinagana upang maprotektahan ang iyong privacy dahil ang tampok na ito ay hindi kumonekta sa tor",
"tor_only_warning": "Ang ilang mga tampok ay maaaring hindi pinagana upang maprotektahan ang iyong privacy kapag gumagamit lamang ng mode ng tor",
"contractName": "Pangalan ng Kontrata",
"contractSymbol": "Simbolo ng Kontrata",
"description": "Paglalarawan",
"camera_consent": "Gagamitin ang iyong camera upang kumuha ng larawan para sa mga layunin ng pagkakakilanlan sa pamamagitan ng ${provider}. Pakisuri ang kanilang Patakaran sa Privacy para sa mga detalye.",
"no_relays": "Walang mga relay",
"choose_relay": "Mangyaring pumili ng relay na gagamitin",

View file

@ -774,6 +774,9 @@
"pause_wallet_creation": "Haven Cüzdanı oluşturma yeteneği şu anda duraklatıldı.",
"tor_feature_disabled": "Bu özellik, gizliliğinizi korumak için yalnızca TOR modu etkinleştirilirken bu özellik devre dışı bırakılır, çünkü bu özellik TOR üzerinden bağlanmaz",
"tor_only_warning": "Yalnızca TOR modu kullanırken gizliliğinizi korumak için bazı özellikler devre dışı bırakılabilir",
"contractName": "Sözleşme Adı",
"contractSymbol": "Sözleşme Sembolü",
"description": "Tanım",
"camera_consent": "Kameranız ${provider} tarihine kadar tanımlama amacıyla bir görüntü yakalamak için kullanılacaktır. Ayrıntılar için lütfen Gizlilik Politikalarını kontrol edin.",
"no_relays": "Röle yok",
"choose_relay": "Lütfen kullanmak için bir röle seçin",

View file

@ -776,6 +776,9 @@
"pause_wallet_creation": "Можливість створення гаманця Haven зараз призупинено.",
"tor_feature_disabled": "Ця функція вимкнена, тоді як режим лише TOR увімкнено для захисту вашої конфіденційності, оскільки ця функція не з'єднується через TOR",
"tor_only_warning": "Деякі функції можуть бути відключені для захисту вашої конфіденційності при використанні лише режиму TOR",
"contractName": "Назва контракту",
"contractSymbol": "Контракт символ",
"description": "опис",
"camera_consent": "Ваша камера використовуватиметься для зйомки зображення з метою ідентифікації ${provider}. Будь ласка, ознайомтеся з їхньою політикою конфіденційності, щоб дізнатися більше.",
"no_relays": "Без реле",
"choose_relay": "Будь ласка, виберіть реле для використання",

View file

@ -768,9 +768,12 @@
"pause_wallet_creation": "Haven Wallet ۔ﮯﮨ ﻑﻮﻗﻮﻣ ﻝﺎﺤﻟﺍ ﯽﻓ ﺖﯿﻠﮨﺍ ﯽﮐ ﮯﻧﺎﻨﺑ",
"tor_feature_disabled": "یہ خصوصیت غیر فعال ہے جبکہ ٹور صرف موڈ آپ کی رازداری کے تحفظ کے لئے اہل ہے کیونکہ یہ خصوصیت ٹور سے زیادہ متصل نہیں ہے",
"tor_only_warning": "جب صرف ٹور صرف ٹور استعمال کرتے ہو تو آپ کی رازداری کے تحفظ کے لئے کچھ خصوصیات کو غیر فعال کیا جاسکتا ہے",
"contractName": "ﻡﺎﻧ ﺎﮐ ﮦﺪﮨﺎﻌﻣ",
"contractSymbol": "ﺖﻣﻼﻋ ﯽﮐ ﮦﺪﮨﺎﻌﻣ",
"description": "ﻞﯿﺼﻔﺗ",
"camera_consent": "۔ﮟﯿﮭﮑﯾﺩ ﯽﺴﯿﻟﺎﭘ ﯽﺴﯾﻮﯿﺋﺍﺮﭘ ﯽﮐ ﻥﺍ ﻡﺮﮐ ﮦﺍﺮﺑ ﮯﯿﻟ ﮯﮐ ﺕﻼ${provider}ﯿﺼﻔﺗ ۔ﺎﮔ ﮯﺋﺎﺟ ﺎﯿﮐ ﻝﺎﻤﻌﺘﺳﺍ ﮯﯿﻟ",
"no_relays": " ۔ﮟﯿﮩﻧ ﮯﻠﯾﺭ ﯽﺋﻮﮐ",
"choose_relay": " ۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﮯﻠﯾﺭ ﮯﯿﻟ ﮯﮐ ﮯﻧﺮﮐ ﻝﺎﻤﻌﺘﺳﺍ ﻡﺮﮐ ﮦﺍﺮﺑ",
"no_relays": "۔ﮟﯿﮩﻧ ﮯﻠﯾﺭ ﯽﺋﻮﮐ",
"choose_relay": "۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﮯﻠﯾﺭ ﮯﯿﻟ ﮯﮐ ﮯﻧﺮﮐ ﻝﺎﻤﻌﺘﺳﺍ ﻡﺮﮐ ﮦﺍﺮﺑ",
"no_relays_message": "۔ﮟﯾﺮﮐ ﻞﻣﺎﺷ ﮯﻠﯾﺭ ﮟﯿﻣ ﮈﺭﺎﮑﯾﺭ ﺮﭩﺳﻮﻧ ﮯﻨﭘﺍ ﮦﻭ ﮧﮐ ﮟﯾﺩ ﺖﯾﺍﺪﮨ ﻮﮐ ﮦﺪﻨﻨﮐ ﻝﻮﺻﻭ ﻡﺮﮐ ﮦﺍﺮﺑ ۔",
"no_relay_on_domain": "۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﮯﻠﯾﺭ ﮯﯿﻟ ﮯﮐ ﮯﻧﺮﮐ ﻝﺎﻤﻌﺘﺳﺍ ﻡﺮﮐ ﮦﺍﺮﺑ ۔ﮯﮨ ﮟﯿﮩﻧ ﺏﺎﯿﺘﺳﺩ ﮯﻠﯾﺭ ﺎﯾ ﮯﮨ ﮟ"
}

View file

@ -770,6 +770,9 @@
"pause_wallet_creation": "Agbara lati ṣẹda Haven Wallet ti wa ni idaduro lọwọlọwọ.",
"tor_feature_disabled": "Ẹya yii jẹ alaabo lakoko ti o ba jẹ pe o ṣiṣẹ nikan lati daabobo aṣiri rẹ bi ẹya yii ko sopọ mọra",
"tor_only_warning": "Diẹ ninu awọn ẹya le jẹ alaabo lati daabobo aṣiri rẹ nigbati o ba ni ọna to",
"contractName": "Orukọ adehun",
"contractSymbol": "Aami adehun",
"description": "Apejuwe",
"camera_consent": "Kamẹra rẹ yoo ṣee lo lati ya aworan kan fun awọn idi idanimọ nipasẹ ${provider}. Jọwọ ṣayẹwo Ilana Aṣiri wọn fun awọn alaye.",
"no_relays": "Ko si relays",
"choose_relay": "Jọwọ yan yii lati lo",

View file

@ -775,6 +775,9 @@
"pause_wallet_creation": "创建 Haven 钱包的功能当前已暂停。",
"tor_feature_disabled": "此功能在启用仅TOR模式的同时被禁用以保护您的隐私因为此功能无法通过TOR连接",
"tor_only_warning": "某些功能可能会被禁用以保护您的隐私时仅使用TOR模式",
"contractName": "合约名称",
"contractSymbol": "合约符号",
"description": "描述",
"camera_consent": "${provider} 将使用您的相机拍摄图像以供识别之用。请查看他们的隐私政策了解详情。",
"no_relays": "无继电器",
"choose_relay": "请选择要使用的继电器",

View file

@ -15,15 +15,15 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
APP_ANDROID_TYPE=$1
MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="1.10.0"
MONERO_COM_BUILD_NUMBER=72
MONERO_COM_VERSION="1.10.2"
MONERO_COM_BUILD_NUMBER=74
MONERO_COM_BUNDLE_ID="com.monero.app"
MONERO_COM_PACKAGE="com.monero.app"
MONERO_COM_SCHEME="monero.com"
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.13.0"
CAKEWALLET_BUILD_NUMBER=189
CAKEWALLET_VERSION="4.13.2"
CAKEWALLET_BUILD_NUMBER=191
CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"
CAKEWALLET_SCHEME="cakewallet"

View file

@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
APP_IOS_TYPE=$1
MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="1.10.0"
MONERO_COM_BUILD_NUMBER=70
MONERO_COM_VERSION="1.10.2"
MONERO_COM_BUILD_NUMBER=72
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.13.0"
CAKEWALLET_BUILD_NUMBER=208
CAKEWALLET_VERSION="4.13.2"
CAKEWALLET_BUILD_NUMBER=210
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
HAVEN_NAME="Haven"

View file

@ -16,13 +16,13 @@ if [ -n "$1" ]; then
fi
MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="1.0.0"
MONERO_COM_BUILD_NUMBER=1
MONERO_COM_VERSION="1.0.2"
MONERO_COM_BUILD_NUMBER=4
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="1.6.0"
CAKEWALLET_BUILD_NUMBER=50
CAKEWALLET_VERSION="1.6.2"
CAKEWALLET_BUILD_NUMBER=52
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then

View file

@ -127,7 +127,7 @@ abstract class Bitcoin {
String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate);
List<Unspent> getUnspents(Object wallet);
void updateUnspents(Object wallet);
Future<void> updateUnspents(Object wallet);
WalletService createBitcoinWalletService(Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource);
WalletService createLitecoinWalletService(Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource);
TransactionPriority getBitcoinTransactionPriorityMedium();
@ -271,7 +271,7 @@ abstract class Monero {
List<String> getMoneroWordList(String language);
List<Unspent> getUnspents(Object wallet);
void updateUnspents(Object wallet);
Future<void> updateUnspents(Object wallet);
WalletCredentials createMoneroRestoreWalletFromKeysCredentials({
required String name,
@ -526,19 +526,24 @@ import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_service.dart';
""";
const ethereumCWHeaders = """
import 'package:cw_evm/evm_chain_formatter.dart';
import 'package:cw_evm/evm_chain_mnemonics.dart';
import 'package:cw_evm/evm_chain_transaction_credentials.dart';
import 'package:cw_evm/evm_chain_transaction_info.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
import 'package:cw_ethereum/ethereum_client.dart';
import 'package:cw_ethereum/ethereum_wallet.dart';
import 'package:cw_ethereum/ethereum_wallet_service.dart';
import 'package:eth_sig_util/util/utils.dart';
import 'package:hive/hive.dart';
import 'package:web3dart/web3dart.dart';
""";
const ethereumCWHeaders = """
import 'package:cw_ethereum/ethereum_formatter.dart';
import 'package:cw_ethereum/ethereum_mnemonics.dart';
import 'package:cw_ethereum/ethereum_transaction_credentials.dart';
import 'package:cw_ethereum/ethereum_transaction_info.dart';
import 'package:cw_ethereum/ethereum_wallet.dart';
import 'package:cw_ethereum/ethereum_wallet_creation_credentials.dart';
import 'package:cw_ethereum/ethereum_wallet_service.dart';
import 'package:cw_ethereum/ethereum_transaction_priority.dart';
""";
const ethereumCwPart = "part 'cw_ethereum.dart';";
const ethereumContent = """
@ -613,19 +618,24 @@ import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:eth_sig_util/util/utils.dart';
import 'package:hive/hive.dart';
import 'package:web3dart/web3dart.dart';
""";
const polygonCWHeaders = """
import 'package:cw_polygon/polygon_formatter.dart';
import 'package:cw_polygon/polygon_transaction_credentials.dart';
import 'package:cw_polygon/polygon_transaction_info.dart';
import 'package:cw_evm/evm_chain_formatter.dart';
import 'package:cw_evm/evm_chain_mnemonics.dart';
import 'package:cw_evm/evm_chain_transaction_info.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:cw_evm/evm_chain_transaction_credentials.dart';
import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
import 'package:cw_polygon/polygon_client.dart';
import 'package:cw_polygon/polygon_wallet.dart';
import 'package:cw_polygon/polygon_wallet_creation_credentials.dart';
import 'package:cw_polygon/polygon_wallet_service.dart';
import 'package:cw_polygon/polygon_transaction_priority.dart';
import 'package:cw_ethereum/ethereum_mnemonics.dart';
import 'package:hive/hive.dart';
import 'package:web3dart/web3dart.dart';
import 'package:eth_sig_util/util/utils.dart';
""";
const polygonCwPart = "part 'cw_polygon.dart';";
const polygonContent = """
@ -925,6 +935,10 @@ Future<void> generatePubspec(
cw_polygon:
path: ./cw_polygon
""";
const cwEVM = """
cw_evm:
path: ./cw_evm
""";
final inputFile = File(pubspecOutputPath);
final inputText = await inputFile.readAsString();
final inputLines = inputText.split('\n');
@ -965,6 +979,10 @@ Future<void> generatePubspec(
output += '\n$cwHaven';
}
if (hasEthereum || hasPolygon) {
output += '\n$cwEVM';
}
final outputLines = output.split('\n');
inputLines.insertAll(dependenciesIndex + 1, outputLines);
final outputContent = inputLines.join('\n');

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