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

 Conflicts:
	cw_bitcoin/lib/bitcoin_wallet.dart
	cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart
	cw_bitcoin/lib/electrum_wallet.dart
	cw_bitcoin/lib/litecoin_wallet.dart
	cw_bitcoin/pubspec.lock
	cw_monero/example/pubspec.lock
	lib/core/wallet_loading_service.dart
	lib/src/screens/restore/wallet_restore_from_seed_form.dart
	lib/view_model/wallet_restore_view_model.dart
This commit is contained in:
OmarHatem 2024-04-30 04:16:48 +03:00
commit 6b0170e69a
110 changed files with 1506 additions and 652 deletions

View file

@ -6,4 +6,4 @@
uri: workers.perish.co uri: workers.perish.co
- -
uri: worker.nanoriver.cc uri: worker.nanoriver.cc
useSSL: true useSSL: true

View file

@ -1,2 +1 @@
UI enhancements Generic bug fixes and enhancements
Bug fixes

View file

@ -1,7 +1,3 @@
Add Replace-By-Fee to boost pending Bitcoin transactions Support restoring Non-Electrum Bitcoin Wallets (check supported derivation paths https://github.com/cake-tech/cake_wallet/blob/main/cw_bitcoin/lib/bitcoin_derivations.dart)
Enable WalletConnect for Solana Bitcoin enhancements and bug fixes
WalletConnect Enhancements Generic bug fixes and enhancements
Enhancements for ERC-20 tokens and Solana tokens
Enhancements for Nano wallet
UI enhancements
Bug fixes

View file

@ -23,14 +23,4 @@ source ./app_env.sh cakewallet
./app_config.sh ./app_config.sh
cd ../.. && flutter pub get cd ../.. && flutter pub get
flutter packages pub run tool/generate_localization.dart 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 .. ./model_generator.sh
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_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_solana && 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

@ -90,8 +90,7 @@ List<bool> prefixMatches(String source, List<String> prefixes) {
return prefixes.map((prefix) => hx.startsWith(prefix.toLowerCase())).toList(); return prefixes.map((prefix) => hx.startsWith(prefix.toLowerCase())).toList();
} }
Future<String> generateMnemonic( Future<String> generateElectrumMnemonic({int strength = 264, String prefix = segwit}) async {
{int strength = 264, String prefix = segwit}) async {
final wordBitlen = logBase(wordlist.length, 2).ceil(); final wordBitlen = logBase(wordlist.length, 2).ceil();
final wordCount = strength / wordBitlen; final wordCount = strength / wordBitlen;
final byteCount = ((wordCount * wordBitlen).ceil() / 8).ceil(); final byteCount = ((wordCount * wordBitlen).ceil() / 8).ceil();
@ -106,22 +105,29 @@ Future<String> generateMnemonic(
return result; return result;
} }
Future<bool> checkIfMnemonicIsElectrum2(String mnemonic) async {
return prefixMatches(mnemonic, [segwit]).first;
}
Future<String> getMnemonicHash(String mnemonic) async {
final hmacSha512 = Hmac(sha512, utf8.encode('Seed version'));
final digest = hmacSha512.convert(utf8.encode(normalizeText(mnemonic)));
final hx = digest.toString();
return hx;
}
Future<Uint8List> mnemonicToSeedBytes(String mnemonic, {String prefix = segwit}) async { Future<Uint8List> mnemonicToSeedBytes(String mnemonic, {String prefix = segwit}) async {
final pbkdf2 = cryptography.Pbkdf2( final pbkdf2 =
macAlgorithm: cryptography.Hmac.sha512(), cryptography.Pbkdf2(macAlgorithm: cryptography.Hmac.sha512(), iterations: 2048, bits: 512);
iterations: 2048,
bits: 512);
final text = normalizeText(mnemonic); final text = normalizeText(mnemonic);
// pbkdf2.deriveKey(secretKey: secretKey, nonce: nonce) // pbkdf2.deriveKey(secretKey: secretKey, nonce: nonce)
final key = await pbkdf2.deriveKey( final key = await pbkdf2.deriveKey(
secretKey: cryptography.SecretKey(text.codeUnits), secretKey: cryptography.SecretKey(text.codeUnits), nonce: 'electrum'.codeUnits);
nonce: 'electrum'.codeUnits);
final bytes = await key.extractBytes(); final bytes = await key.extractBytes();
return Uint8List.fromList(bytes); return Uint8List.fromList(bytes);
} }
bool matchesAnyPrefix(String mnemonic) => bool matchesAnyPrefix(String mnemonic) => prefixMatches(mnemonic, [segwit]).any((el) => el);
prefixMatches(mnemonic, [segwit]).any((el) => el);
bool validateMnemonic(String mnemonic, {String prefix = segwit}) { bool validateMnemonic(String mnemonic, {String prefix = segwit}) {
try { try {
@ -208,10 +214,8 @@ String removeCJKSpaces(String source) {
} }
String normalizeText(String source) { String normalizeText(String source) {
final res = removeCombiningCharacters(unorm.nfkd(source).toLowerCase()) final res =
.trim() removeCombiningCharacters(unorm.nfkd(source).toLowerCase()).trim().split('/\s+/').join(' ');
.split('/\s+/')
.join(' ');
return removeCJKSpaces(res); return removeCJKSpaces(res);
} }

View file

@ -37,7 +37,7 @@ class BitcoinTransactionPriority extends TransactionPriority {
switch (this) { switch (this) {
case BitcoinTransactionPriority.slow: case BitcoinTransactionPriority.slow:
label = 'Slow ~24hrs'; // '${S.current.transaction_priority_slow} ~24hrs'; label = 'Slow ~24hrs+'; // '${S.current.transaction_priority_slow} ~24hrs';
break; break;
case BitcoinTransactionPriority.medium: case BitcoinTransactionPriority.medium:
label = 'Medium'; // S.current.transaction_priority_medium; label = 'Medium'; // S.current.transaction_priority_medium;

View file

@ -13,6 +13,7 @@ import 'package:cw_core/wallet_info.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart'; import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
import 'package:bip39/bip39.dart' as bip39;
part 'bitcoin_wallet.g.dart'; part 'bitcoin_wallet.g.dart';
@ -32,8 +33,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
ElectrumBalance? initialBalance, ElectrumBalance? initialBalance,
Map<String, int>? initialRegularAddressIndex, Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex, Map<String, int>? initialChangeAddressIndex,
String? passphrase,
}) : super( }) : super(
mnemonic: mnemonic, mnemonic: mnemonic,
passphrase: passphrase,
password: password, password: password,
walletInfo: walletInfo, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
@ -47,14 +50,19 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
seedBytes: seedBytes, seedBytes: seedBytes,
currency: CryptoCurrency.btc, currency: CryptoCurrency.btc,
encryptionFileUtils: encryptionFileUtils) { encryptionFileUtils: encryptionFileUtils) {
// in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here)
// the sideHd derivation path = m/84'/0'/0'/1 (account 1, index unspecified here)
String derivationPath = walletInfo.derivationInfo!.derivationPath!;
String sideDerivationPath = derivationPath.substring(0, derivationPath.length - 1) + "1";
final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType);
walletAddresses = BitcoinWalletAddresses( walletAddresses = BitcoinWalletAddresses(
walletInfo, walletInfo,
electrumClient: electrumClient, electrumClient: electrumClient,
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex, initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: hd, mainHd: hd.derivePath(derivationPath),
sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"), sideHd: hd.derivePath(sideDerivationPath),
network: networkParam ?? network, network: networkParam ?? network,
); );
autorun((_) { autorun((_) {
@ -68,6 +76,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
required WalletInfo walletInfo, required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo,
required EncryptionFileUtils encryptionFileUtils, required EncryptionFileUtils encryptionFileUtils,
String? passphrase,
String? addressPageType, String? addressPageType,
BasedUtxoNetwork? network, BasedUtxoNetwork? network,
List<BitcoinAddressRecord>? initialAddresses, List<BitcoinAddressRecord>? initialAddresses,
@ -75,15 +84,30 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
Map<String, int>? initialRegularAddressIndex, Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex, Map<String, int>? initialChangeAddressIndex,
}) async { }) async {
late Uint8List seedBytes;
switch (walletInfo.derivationInfo?.derivationType) {
case DerivationType.bip39:
seedBytes = await bip39.mnemonicToSeed(
mnemonic,
passphrase: passphrase ?? "",
);
break;
case DerivationType.electrum:
default:
seedBytes = await mnemonicToSeedBytes(mnemonic);
break;
}
return BitcoinWallet( return BitcoinWallet(
mnemonic: mnemonic, mnemonic: mnemonic,
passphrase: passphrase ?? "",
password: password, password: password,
walletInfo: walletInfo, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
initialBalance: initialBalance, initialBalance: initialBalance,
encryptionFileUtils: encryptionFileUtils, encryptionFileUtils: encryptionFileUtils,
seedBytes: await mnemonicToSeedBytes(mnemonic), seedBytes: seedBytes,
initialRegularAddressIndex: initialRegularAddressIndex, initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex,
addressPageType: addressPageType, addressPageType: addressPageType,
@ -103,15 +127,39 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
: BitcoinNetwork.mainnet; : BitcoinNetwork.mainnet;
final snp = await ElectrumWalletSnapshot.load(encryptionFileUtils, name, walletInfo.type, password, network); final snp = await ElectrumWalletSnapshot.load(encryptionFileUtils, name, walletInfo.type, password, network);
walletInfo.derivationInfo ??= DerivationInfo(
derivationType: snp.derivationType ?? DerivationType.electrum,
derivationPath: snp.derivationPath,
);
// set the default if not present:
walletInfo.derivationInfo!.derivationPath = snp.derivationPath ?? "m/0'/1";
late Uint8List seedBytes;
switch (walletInfo.derivationInfo!.derivationType) {
case DerivationType.electrum:
seedBytes = await mnemonicToSeedBytes(snp.mnemonic);
break;
case DerivationType.bip39:
default:
seedBytes = await bip39.mnemonicToSeed(
snp.mnemonic,
passphrase: snp.passphrase ?? '',
);
break;
}
return BitcoinWallet( return BitcoinWallet(
mnemonic: snp.mnemonic, mnemonic: snp.mnemonic,
password: password, password: password,
passphrase: snp.passphrase,
walletInfo: walletInfo, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses, initialAddresses: snp.addresses,
initialBalance: snp.balance, initialBalance: snp.balance,
seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
encryptionFileUtils: encryptionFileUtils, encryptionFileUtils: encryptionFileUtils,
seedBytes: seedBytes,
initialRegularAddressIndex: snp.regularAddressIndex, initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex, initialChangeAddressIndex: snp.changeAddressIndex,
addressPageType: snp.addressPageType, addressPageType: snp.addressPageType,

View file

@ -2,13 +2,25 @@ import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
class BitcoinNewWalletCredentials extends WalletCredentials { class BitcoinNewWalletCredentials extends WalletCredentials {
BitcoinNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password}) BitcoinNewWalletCredentials(
: super(name: name, walletInfo: walletInfo, password: password); {required String name,
WalletInfo? walletInfo,
String? password,
DerivationType? derivationType,
String? derivationPath})
: super(
name: name,
walletInfo: walletInfo,
password: password,
);
} }
class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials { class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials {
BitcoinRestoreWalletFromSeedCredentials( BitcoinRestoreWalletFromSeedCredentials(
{required String name, required String password, required this.mnemonic, WalletInfo? walletInfo}) {required String name,
required String password,
required this.mnemonic,
WalletInfo? walletInfo})
: super(name: name, password: password, walletInfo: walletInfo); : super(name: name, password: password, walletInfo: walletInfo);
final String mnemonic; final String mnemonic;
@ -20,4 +32,22 @@ class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials {
: super(name: name, password: password, walletInfo: walletInfo); : super(name: name, password: password, walletInfo: walletInfo);
final String wif; final String wif;
} }
BitcoinRestoreWalletFromSeedCredentials({
required String name,
required String password,
required this.mnemonic,
WalletInfo? walletInfo,
required DerivationType derivationType,
required String derivationPath,
String? passphrase,
}) : super(
name: name,
password: password,
passphrase: passphrase,
walletInfo: walletInfo,
derivationInfo: DerivationInfo(
derivationType: derivationType,
derivationPath: derivationPath,
));

View file

@ -13,6 +13,7 @@ import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:bip39/bip39.dart' as bip39;
class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials, class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
BitcoinRestoreWalletFromSeedCredentials, BitcoinRestoreWalletFromWIFCredentials> { BitcoinRestoreWalletFromSeedCredentials, BitcoinRestoreWalletFromWIFCredentials> {
@ -31,8 +32,9 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
credentials.walletInfo?.network = network.value; credentials.walletInfo?.network = network.value;
final wallet = await BitcoinWalletBase.create( final wallet = await BitcoinWalletBase.create(
mnemonic: await generateMnemonic(), mnemonic: await generateElectrumMnemonic(),
password: credentials.password!, password: credentials.password!,
passphrase: credentials.passphrase,
walletInfo: credentials.walletInfo!, walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource, unspentCoinsInfo: unspentCoinsInfoSource,
network: network, network: network,
@ -111,7 +113,7 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
@override @override
Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials, Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials,
{bool? isTestnet}) async { {bool? isTestnet}) async {
if (!validateMnemonic(credentials.mnemonic)) { if (!validateMnemonic(credentials.mnemonic) && !bip39.validateMnemonic(credentials.mnemonic)) {
throw BitcoinMnemonicIsIncorrectException(); throw BitcoinMnemonicIsIncorrectException();
} }
@ -120,6 +122,7 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
final wallet = await BitcoinWalletBase.create( final wallet = await BitcoinWalletBase.create(
password: credentials.password!, password: credentials.password!,
passphrase: credentials.passphrase,
mnemonic: credentials.mnemonic, mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!, walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource, unspentCoinsInfo: unspentCoinsInfoSource,

View file

@ -0,0 +1,104 @@
import 'package:cw_core/wallet_info.dart';
Map<DerivationType, List<DerivationInfo>> electrum_derivations = {
DerivationType.electrum: [
DerivationInfo(
derivationType: DerivationType.electrum,
derivationPath: "m/0'/0",
description: "Electrum",
scriptType: "p2wpkh",
),
],
DerivationType.bip39: [
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/44'/0'/0'",
description: "Standard BIP44",
scriptType: "p2pkh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/49'/0'/0'",
description: "Standard BIP49 compatibility segwit",
scriptType: "p2wpkh-p2sh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/84'/0'/0'",
description: "Standard BIP84 native segwit",
scriptType: "p2wpkh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/0'",
description: "Non-standard legacy",
scriptType: "p2pkh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/0'",
description: "Non-standard compatibility segwit",
scriptType: "p2wpkh-p2sh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/0'",
description: "Non-standard native segwit",
scriptType: "p2wpkh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/44'/0'/0'",
description: "Samourai Deposit",
scriptType: "p2wpkh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/49'/0'/0'",
description: "Samourai Deposit",
scriptType: "p2wpkh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/84'/0'/2147483644'",
description: "Samourai Bad Bank (toxic change)",
scriptType: "p2wpkh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/84'/0'/2147483645'",
description: "Samourai Whirlpool Pre Mix",
scriptType: "p2wpkh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/84'/0'/2147483646'",
description: "Samourai Whirlpool Post Mix",
scriptType: "p2wpkh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/44'/0'/2147483647'",
description: "Samourai Ricochet legacy",
scriptType: "p2pkh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/49'/0'/2147483647'",
description: "Samourai Ricochet compatibility segwit",
scriptType: "p2wpkh-p2sh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/84'/0'/2147483647'",
description: "Samourai Ricochet native segwit",
scriptType: "p2wpkh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/84'/2'/0'",
description: "Default Litecoin",
scriptType: "p2wpkh",
),
],
};

View file

@ -57,13 +57,15 @@ abstract class ElectrumWalletBase
required this.mnemonic, required this.mnemonic,
required Uint8List seedBytes, required Uint8List seedBytes,
required this.encryptionFileUtils, required this.encryptionFileUtils,
this.passphrase,
List<BitcoinAddressRecord>? initialAddresses, List<BitcoinAddressRecord>? initialAddresses,
ElectrumClient? electrumClient, ElectrumClient? electrumClient,
ElectrumBalance? initialBalance, ElectrumBalance? initialBalance,
CryptoCurrency? currency}) CryptoCurrency? currency})
: hd = currency == CryptoCurrency.bch : hd = currency == CryptoCurrency.bch
? bitcoinCashHDWallet(seedBytes) ? bitcoinCashHDWallet(seedBytes)
: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/0"), : bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
.derivePath(walletInfo.derivationInfo?.derivationPath ?? "m/0'/0"),
syncStatus = NotConnectedSyncStatus(), syncStatus = NotConnectedSyncStatus(),
_password = password, _password = password,
_feeRates = <int>[], _feeRates = <int>[],
@ -99,6 +101,7 @@ abstract class ElectrumWalletBase
final bitcoin.HDWallet hd; final bitcoin.HDWallet hd;
final String mnemonic; final String mnemonic;
final EncryptionFileUtils encryptionFileUtils; final EncryptionFileUtils encryptionFileUtils;
final String? passphrase;
@override @override
@observable @observable
@ -213,10 +216,14 @@ abstract class ElectrumWalletBase
List<ECPrivate> privateKeys = []; List<ECPrivate> privateKeys = [];
int allInputsAmount = 0; int allInputsAmount = 0;
bool spendsUnconfirmedTX = false;
for (int i = 0; i < unspentCoins.length; i++) { for (int i = 0; i < unspentCoins.length; i++) {
final utx = unspentCoins[i]; final utx = unspentCoins[i];
if (utx.isSending) { if (utx.isSending && !utx.isFrozen) {
if (!spendsUnconfirmedTX) spendsUnconfirmedTX = utx.confirmations == 0;
allInputsAmount += utx.value; allInputsAmount += utx.value;
final address = addressTypeFromStr(utx.address, network); final address = addressTypeFromStr(utx.address, network);
@ -274,6 +281,10 @@ abstract class ElectrumWalletBase
// Here, when sending all, the output amount equals to the input value - fee to fully spend every input on the transaction and have no amount left for change // Here, when sending all, the output amount equals to the input value - fee to fully spend every input on the transaction and have no amount left for change
int amount = allInputsAmount - fee; int amount = allInputsAmount - fee;
if (amount <= 0) {
throw BitcoinTransactionWrongBalanceException();
}
// Attempting to send less than the dust limit // Attempting to send less than the dust limit
if (_isBelowDust(amount)) { if (_isBelowDust(amount)) {
throw BitcoinTransactionNoDustException(); throw BitcoinTransactionNoDustException();
@ -298,6 +309,7 @@ abstract class ElectrumWalletBase
isSendAll: true, isSendAll: true,
hasChange: false, hasChange: false,
memo: memo, memo: memo,
spendsUnconfirmedTX: spendsUnconfirmedTX,
); );
} }
@ -307,17 +319,25 @@ abstract class ElectrumWalletBase
int feeRate, { int feeRate, {
int? inputsCount, int? inputsCount,
String? memo, String? memo,
bool? useUnconfirmed,
}) async { }) async {
final utxos = <UtxoWithAddress>[]; final utxos = <UtxoWithAddress>[];
List<ECPrivate> privateKeys = []; List<ECPrivate> privateKeys = [];
int allInputsAmount = 0; int allInputsAmount = 0;
bool spendsUnconfirmedTX = false;
int leftAmount = credentialsAmount; int leftAmount = credentialsAmount;
final sendingCoins = unspentCoins.where((utx) => utx.isSending).toList(); final sendingCoins = unspentCoins.where((utx) => utx.isSending && !utx.isFrozen).toList();
final unconfirmedCoins = sendingCoins.where((utx) => utx.confirmations == 0).toList();
for (int i = 0; i < sendingCoins.length; i++) { for (int i = 0; i < sendingCoins.length; i++) {
final utx = sendingCoins[i]; final utx = sendingCoins[i];
final isUncormirmed = utx.confirmations == 0;
if (useUnconfirmed != true && isUncormirmed) continue;
if (!spendsUnconfirmedTX) spendsUnconfirmedTX = isUncormirmed;
allInputsAmount += utx.value; allInputsAmount += utx.value;
leftAmount = leftAmount - utx.value; leftAmount = leftAmount - utx.value;
@ -355,11 +375,23 @@ abstract class ElectrumWalletBase
} }
final spendingAllCoins = sendingCoins.length == utxos.length; final spendingAllCoins = sendingCoins.length == utxos.length;
final spendingAllConfirmedCoins =
!spendsUnconfirmedTX && utxos.length == sendingCoins.length - unconfirmedCoins.length;
// How much is being spent - how much is being sent // How much is being spent - how much is being sent
int amountLeftForChangeAndFee = allInputsAmount - credentialsAmount; int amountLeftForChangeAndFee = allInputsAmount - credentialsAmount;
if (amountLeftForChangeAndFee <= 0) { if (amountLeftForChangeAndFee <= 0) {
if (!spendingAllCoins) {
return estimateTxForAmount(
credentialsAmount,
outputs,
feeRate,
inputsCount: utxos.length + 1,
memo: memo,
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
);
}
throw BitcoinTransactionWrongBalanceException(); throw BitcoinTransactionWrongBalanceException();
} }
@ -413,6 +445,7 @@ abstract class ElectrumWalletBase
feeRate, feeRate,
inputsCount: utxos.length + 1, inputsCount: utxos.length + 1,
memo: memo, memo: memo,
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
); );
} }
@ -459,6 +492,7 @@ abstract class ElectrumWalletBase
feeRate, feeRate,
inputsCount: utxos.length + 1, inputsCount: utxos.length + 1,
memo: memo, memo: memo,
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
); );
} }
} }
@ -471,6 +505,7 @@ abstract class ElectrumWalletBase
hasChange: true, hasChange: true,
isSendAll: false, isSendAll: false,
memo: memo, memo: memo,
spendsUnconfirmedTX: spendsUnconfirmedTX,
); );
} }
@ -541,7 +576,7 @@ abstract class ElectrumWalletBase
network: network, network: network,
memo: estimatedTx.memo, memo: estimatedTx.memo,
outputOrdering: BitcoinOrdering.none, outputOrdering: BitcoinOrdering.none,
enableRBF: true, enableRBF: !estimatedTx.spendsUnconfirmedTX,
); );
} else { } else {
txb = BitcoinTransactionBuilder( txb = BitcoinTransactionBuilder(
@ -551,7 +586,7 @@ abstract class ElectrumWalletBase
network: network, network: network,
memo: estimatedTx.memo, memo: estimatedTx.memo,
outputOrdering: BitcoinOrdering.none, outputOrdering: BitcoinOrdering.none,
enableRBF: true, enableRBF: !estimatedTx.spendsUnconfirmedTX,
); );
} }
@ -595,6 +630,7 @@ abstract class ElectrumWalletBase
String toJSON() => json.encode({ String toJSON() => json.encode({
'mnemonic': mnemonic, 'mnemonic': mnemonic,
'passphrase': passphrase ?? '',
'account_index': walletAddresses.currentReceiveAddressIndexByType, 'account_index': walletAddresses.currentReceiveAddressIndexByType,
'change_address_index': walletAddresses.currentChangeAddressIndexByType, 'change_address_index': walletAddresses.currentChangeAddressIndexByType,
'addresses': walletAddresses.allAddresses.map((addr) => addr.toJSON()).toList(), 'addresses': walletAddresses.allAddresses.map((addr) => addr.toJSON()).toList(),
@ -602,6 +638,8 @@ abstract class ElectrumWalletBase
? SegwitAddresType.p2wpkh.toString() ? SegwitAddresType.p2wpkh.toString()
: walletInfo.addressPageType.toString(), : walletInfo.addressPageType.toString(),
'balance': balance[currency]?.toJSON(), 'balance': balance[currency]?.toJSON(),
'derivationTypeIndex': walletInfo.derivationInfo?.derivationType?.index,
'derivationPath': walletInfo.derivationInfo?.derivationPath,
}); });
int feeRate(TransactionPriority priority) { int feeRate(TransactionPriority priority) {
@ -731,6 +769,7 @@ abstract class ElectrumWalletBase
final tx = await fetchTransactionInfo( final tx = await fetchTransactionInfo(
hash: coin.hash, height: 0, myAddresses: addressesSet); hash: coin.hash, height: 0, myAddresses: addressesSet);
coin.isChange = tx?.direction == TransactionDirection.outgoing; coin.isChange = tx?.direction == TransactionDirection.outgoing;
coin.confirmations = tx?.confirmations;
updatedUnspentCoins.add(coin); updatedUnspentCoins.add(coin);
} catch (_) {} } catch (_) {}
})))); }))));
@ -755,6 +794,7 @@ abstract class ElectrumWalletBase
coin.isFrozen = coinInfo.isFrozen; coin.isFrozen = coinInfo.isFrozen;
coin.isSending = coinInfo.isSending; coin.isSending = coinInfo.isSending;
coin.note = coinInfo.note; coin.note = coinInfo.note;
coin.bitcoinAddressRecord.balance += coinInfo.value;
} else { } else {
_addCoinInfo(coin); _addCoinInfo(coin);
} }
@ -1047,9 +1087,11 @@ abstract class ElectrumWalletBase
return Future.wait(addressesByType.map((addressRecord) async { return Future.wait(addressesByType.map((addressRecord) async {
final history = await _fetchAddressHistory(addressRecord, addressesSet, currentHeight); final history = await _fetchAddressHistory(addressRecord, addressesSet, currentHeight);
final balance = await electrumClient.getBalance(addressRecord.scriptHash!);
if (history.isNotEmpty) { if (history.isNotEmpty) {
addressRecord.txCount = history.length; addressRecord.txCount = history.length;
addressRecord.balance = balance['confirmed'] as int? ?? 0;
historiesWithDetails.addAll(history); historiesWithDetails.addAll(history);
final matchedAddresses = final matchedAddresses =
@ -1280,6 +1322,7 @@ class EstimatedTxResult {
required this.hasChange, required this.hasChange,
required this.isSendAll, required this.isSendAll,
this.memo, this.memo,
required this.spendsUnconfirmedTX,
}); });
final List<UtxoWithAddress> utxos; final List<UtxoWithAddress> utxos;
@ -1289,6 +1332,7 @@ class EstimatedTxResult {
final bool hasChange; final bool hasChange;
final bool isSendAll; final bool isSendAll;
final String? memo; final String? memo;
final bool spendsUnconfirmedTX;
} }
BitcoinBaseAddress addressTypeFromStr(String address, BasedUtxoNetwork network) { BitcoinBaseAddress addressTypeFromStr(String address, BasedUtxoNetwork network) {

View file

@ -4,6 +4,7 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/utils/file.dart'; import 'package:cw_core/utils/file.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
@ -18,6 +19,9 @@ class ElectrumWalletSnapshot {
required this.regularAddressIndex, required this.regularAddressIndex,
required this.changeAddressIndex, required this.changeAddressIndex,
required this.addressPageType, required this.addressPageType,
this.passphrase,
this.derivationType,
this.derivationPath,
}); });
final String name; final String name;
@ -30,6 +34,9 @@ class ElectrumWalletSnapshot {
ElectrumBalance balance; ElectrumBalance balance;
Map<String, int> regularAddressIndex; Map<String, int> regularAddressIndex;
Map<String, int> changeAddressIndex; Map<String, int> changeAddressIndex;
String? passphrase;
DerivationType? derivationType;
String? derivationPath;
static Future<ElectrumWalletSnapshot> load( static Future<ElectrumWalletSnapshot> load(
EncryptionFileUtils encryptionFileUtils, String name, WalletType type, String password, BasedUtxoNetwork network) async { EncryptionFileUtils encryptionFileUtils, String name, WalletType type, String password, BasedUtxoNetwork network) async {
@ -38,6 +45,7 @@ class ElectrumWalletSnapshot {
final data = json.decode(jsonSource) as Map; final data = json.decode(jsonSource) as Map;
final addressesTmp = data['addresses'] as List? ?? <Object>[]; final addressesTmp = data['addresses'] as List? ?? <Object>[];
final mnemonic = data['mnemonic'] as String; final mnemonic = data['mnemonic'] as String;
final passphrase = data['passphrase'] as String? ?? '';
final addresses = addressesTmp final addresses = addressesTmp
.whereType<String>() .whereType<String>()
.map((addr) => BitcoinAddressRecord.fromJSON(addr, network)) .map((addr) => BitcoinAddressRecord.fromJSON(addr, network))
@ -47,6 +55,10 @@ class ElectrumWalletSnapshot {
var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0}; var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0}; var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
final derivationType =
DerivationType.values[(data['derivationTypeIndex'] as int?) ?? DerivationType.electrum.index];
final derivationPath = data['derivationPath'] as String? ?? "m/0'/0";
try { try {
regularAddressIndexByType = { regularAddressIndexByType = {
SegwitAddresType.p2wpkh.toString(): int.parse(data['account_index'] as String? ?? '0') SegwitAddresType.p2wpkh.toString(): int.parse(data['account_index'] as String? ?? '0')
@ -66,12 +78,15 @@ class ElectrumWalletSnapshot {
name: name, name: name,
type: type, type: type,
password: password, password: password,
passphrase: passphrase,
mnemonic: mnemonic, mnemonic: mnemonic,
addresses: addresses, addresses: addresses,
balance: balance, balance: balance,
regularAddressIndex: regularAddressIndexByType, regularAddressIndex: regularAddressIndexByType,
changeAddressIndex: changeAddressIndexByType, changeAddressIndex: changeAddressIndexByType,
addressPageType: data['address_page_type'] as String?, addressPageType: data['address_page_type'] as String?,
derivationType: derivationType,
derivationPath: derivationPath,
); );
} }
} }

View file

@ -15,7 +15,9 @@ class BitcoinTransactionNoDustOnChangeException extends TransactionNoDustOnChang
BitcoinTransactionNoDustOnChangeException(super.max, super.min); BitcoinTransactionNoDustOnChangeException(super.max, super.min);
} }
class BitcoinTransactionCommitFailed extends TransactionCommitFailed {} class BitcoinTransactionCommitFailed extends TransactionCommitFailed {
BitcoinTransactionCommitFailed({super.errorMessage});
}
class BitcoinTransactionCommitFailedDustChange extends TransactionCommitFailedDustChange {} class BitcoinTransactionCommitFailedDustChange extends TransactionCommitFailedDustChange {}

View file

@ -16,6 +16,7 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/litecoin_network.dart'; import 'package:cw_bitcoin/litecoin_network.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bip39/bip39.dart' as bip39;
part 'litecoin_wallet.g.dart'; part 'litecoin_wallet.g.dart';
@ -66,11 +67,26 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
required WalletInfo walletInfo, required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo,
required EncryptionFileUtils encryptionFileUtils, required EncryptionFileUtils encryptionFileUtils,
String? passphrase,
String? addressPageType, String? addressPageType,
List<BitcoinAddressRecord>? initialAddresses, List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance, ElectrumBalance? initialBalance,
Map<String, int>? initialRegularAddressIndex, Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex}) async { Map<String, int>? initialChangeAddressIndex}) async {
late Uint8List seedBytes;
switch (walletInfo.derivationInfo?.derivationType) {
case DerivationType.bip39:
seedBytes = await bip39.mnemonicToSeed(
mnemonic,
passphrase: passphrase ?? "",
);
break;
case DerivationType.electrum:
default:
seedBytes = await mnemonicToSeedBytes(mnemonic);
break;
}
return LitecoinWallet( return LitecoinWallet(
mnemonic: mnemonic, mnemonic: mnemonic,
password: password, password: password,
@ -78,8 +94,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
initialBalance: initialBalance, initialBalance: initialBalance,
seedBytes: await mnemonicToSeedBytes(mnemonic),
encryptionFileUtils: encryptionFileUtils, encryptionFileUtils: encryptionFileUtils,
seedBytes: seedBytes,
initialRegularAddressIndex: initialRegularAddressIndex, initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex,
addressPageType: addressPageType, addressPageType: addressPageType,

View file

@ -12,6 +12,7 @@ import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:bip39/bip39.dart' as bip39;
class LitecoinWalletService extends WalletService< class LitecoinWalletService extends WalletService<
BitcoinNewWalletCredentials, BitcoinNewWalletCredentials,
@ -29,8 +30,9 @@ class LitecoinWalletService extends WalletService<
@override @override
Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async { Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
final wallet = await LitecoinWalletBase.create( final wallet = await LitecoinWalletBase.create(
mnemonic: await generateMnemonic(), mnemonic: await generateElectrumMnemonic(),
password: credentials.password!, password: credentials.password!,
passphrase: credentials.passphrase,
walletInfo: credentials.walletInfo!, walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource, unspentCoinsInfo: unspentCoinsInfoSource,
encryptionFileUtils: encryptionFileUtilsFor(isDirect)); encryptionFileUtils: encryptionFileUtilsFor(isDirect));
@ -106,12 +108,13 @@ class LitecoinWalletService extends WalletService<
@override @override
Future<LitecoinWallet> restoreFromSeed( Future<LitecoinWallet> restoreFromSeed(
BitcoinRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async { BitcoinRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
if (!validateMnemonic(credentials.mnemonic)) { if (!validateMnemonic(credentials.mnemonic) && !bip39.validateMnemonic(credentials.mnemonic)) {
throw LitecoinMnemonicIsIncorrectException(); throw LitecoinMnemonicIsIncorrectException();
} }
final wallet = await LitecoinWalletBase.create( final wallet = await LitecoinWalletBase.create(
password: credentials.password!, password: credentials.password!,
passphrase: credentials.passphrase,
mnemonic: credentials.mnemonic, mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!, walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource, unspentCoinsInfo: unspentCoinsInfoSource,

View file

@ -73,7 +73,9 @@ class PendingBitcoinTransaction with PendingTransaction {
if (error.contains("bad-txns-vout-negative")) { if (error.contains("bad-txns-vout-negative")) {
throw BitcoinTransactionCommitFailedVoutNegative(); throw BitcoinTransactionCommitFailedVoutNegative();
} }
throw BitcoinTransactionCommitFailed(errorMessage: error);
} }
throw BitcoinTransactionCommitFailed(); throw BitcoinTransactionCommitFailed();
} }

View file

@ -5,29 +5,58 @@ import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData; import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
import 'package:hex/hex.dart'; import 'package:hex/hex.dart';
bitcoin.PaymentData generatePaymentData({required bitcoin.HDWallet hd, required int index}) => bitcoin.PaymentData generatePaymentData({required bitcoin.HDWallet hd, int? index}) {
PaymentData(pubkey: Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!))); final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
return PaymentData(pubkey: Uint8List.fromList(HEX.decode(pubKey)));
}
ECPrivate generateECPrivate( ECPrivate generateECPrivate(
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) => {required bitcoin.HDWallet hd, required BasedUtxoNetwork network, int? index}) {
ECPrivate.fromWif(hd.derive(index).wif!, netVersion: network.wifNetVer); final wif = index != null ? hd.derive(index).wif! : hd.wif!;
return ECPrivate.fromWif(wif, netVersion: network.wifNetVer);
}
String generateP2WPKHAddress( String generateP2WPKHAddress({
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) => required bitcoin.HDWallet hd,
ECPublic.fromHex(hd.derive(index).pubKey!).toP2wpkhAddress().toAddress(network); required BasedUtxoNetwork network,
int? index,
}) {
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
return ECPublic.fromHex(pubKey).toP2wpkhAddress().toAddress(network);
}
String generateP2SHAddress( String generateP2SHAddress({
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) => required bitcoin.HDWallet hd,
ECPublic.fromHex(hd.derive(index).pubKey!).toP2wpkhInP2sh().toAddress(network); required BasedUtxoNetwork network,
int? index,
}) {
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
return ECPublic.fromHex(pubKey).toP2wpkhInP2sh().toAddress(network);
}
String generateP2WSHAddress( String generateP2WSHAddress({
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) => required bitcoin.HDWallet hd,
ECPublic.fromHex(hd.derive(index).pubKey!).toP2wshAddress().toAddress(network); required BasedUtxoNetwork network,
int? index,
}) {
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
return ECPublic.fromHex(pubKey).toP2wshAddress().toAddress(network);
}
String generateP2PKHAddress( String generateP2PKHAddress({
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) => required bitcoin.HDWallet hd,
ECPublic.fromHex(hd.derive(index).pubKey!).toP2pkhAddress().toAddress(network); required BasedUtxoNetwork network,
int? index,
}) {
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
return ECPublic.fromHex(pubKey).toP2pkhAddress().toAddress(network);
}
String generateP2TRAddress( String generateP2TRAddress({
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) => required bitcoin.HDWallet hd,
ECPublic.fromHex(hd.derive(index).pubKey!).toTaprootAddress().toAddress(network); required BasedUtxoNetwork network,
int? index,
}) {
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
return ECPublic.fromHex(pubKey).toTaprootAddress().toAddress(network);
}

View file

@ -21,10 +21,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: args name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2" version: "2.5.0"
asn1lib: asn1lib:
dependency: transitive dependency: transitive
description: description:
@ -80,7 +80,7 @@ packages:
description: description:
path: "." path: "."
ref: cake-update-v2 ref: cake-update-v2
resolved-ref: "3fd81d238b990bb767fc7a4fdd5053a22a142e2e" resolved-ref: "01d844a5f5a520a31df5254e34169af4664aa769"
url: "https://github.com/cake-tech/bitcoin_base.git" url: "https://github.com/cake-tech/bitcoin_base.git"
source: git source: git
version: "4.2.0" version: "4.2.0"
@ -153,10 +153,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.8" version: "2.4.9"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
@ -177,19 +177,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: built_value name: built_value
sha256: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6 sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.9.0" version: "8.9.2"
cake_backup:
dependency: transitive
description:
path: "."
ref: main
resolved-ref: "3aba867dcab6737f6707782f5db15d71f303db38"
url: "https://github.com/cake-tech/cake_backup.git"
source: git
version: "1.0.0+1"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@ -254,14 +245,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.0" version: "2.5.0"
cupertino_icons:
dependency: transitive
description:
name: cupertino_icons
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
url: "https://pub.dev"
source: hosted
version: "1.0.6"
cw_core: cw_core:
dependency: "direct main" dependency: "direct main"
description: description:
@ -326,10 +309,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_mobx name: flutter_mobx
sha256: "4a5d062ff85ed3759f4aac6410ff0ffae32e324b2e71ca722ae1b37b32e865f4" sha256: "859fbf452fa9c2519d2700b125dd7fb14c508bbdd7fb65e26ca8ff6c92280e2e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0+2" version: "2.2.1+1"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -339,10 +322,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: frontend_server_client name: frontend_server_client
sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.2.0" version: "4.0.0"
glob: glob:
dependency: transitive dependency: transitive
description: description:
@ -483,10 +466,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: mobx name: mobx
sha256: "74ee54012dc7c1b3276eaa960a600a7418ef5f9997565deb8fca1fd88fb36b78" sha256: "63920b27b32ad1910adfe767ab1750e4c212e8923232a1f891597b362074ea5e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0+1" version: "2.3.3+2"
mobx_codegen: mobx_codegen:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -587,10 +570,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: pointycastle name: pointycastle
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" sha256: "70fe966348fe08c34bf929582f1d8247d9d9408130723206472b4687227e4333"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.7.4" version: "3.8.0"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@ -603,10 +586,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: provider name: provider
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.1" version: "6.1.2"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
@ -740,14 +723,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.1" version: "1.0.1"
tuple:
dependency: transitive
description:
name: tuple
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
url: "https://pub.dev"
source: hosted
version: "2.0.2"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:

View file

@ -62,7 +62,9 @@ class PendingBitcoinCashTransaction with PendingTransaction {
if (error.contains("bad-txns-vout-negative")) { if (error.contains("bad-txns-vout-negative")) {
throw BitcoinTransactionCommitFailedVoutNegative(); throw BitcoinTransactionCommitFailedVoutNegative();
} }
throw BitcoinTransactionCommitFailed(errorMessage: error);
} }
throw BitcoinTransactionCommitFailed(); throw BitcoinTransactionCommitFailed();
} }

View file

@ -19,7 +19,11 @@ class TransactionNoDustOnChangeException implements Exception {
final String min; final String min;
} }
class TransactionCommitFailed implements Exception {} class TransactionCommitFailed implements Exception {
final String? errorMessage;
TransactionCommitFailed({this.errorMessage});
}
class TransactionCommitFailedDustChange implements Exception {} class TransactionCommitFailedDustChange implements Exception {}

View file

@ -14,4 +14,5 @@ const ERC20_TOKEN_TYPE_ID = 12;
const NANO_ACCOUNT_TYPE_ID = 13; const NANO_ACCOUNT_TYPE_ID = 13;
const POW_NODE_TYPE_ID = 14; const POW_NODE_TYPE_ID = 14;
const DERIVATION_TYPE_TYPE_ID = 15; const DERIVATION_TYPE_TYPE_ID = 15;
const SPL_TOKEN_TYPE_ID = 16; const SPL_TOKEN_TYPE_ID = 16;
const DERIVATION_INFO_TYPE_ID = 17;

View file

@ -10,7 +10,7 @@ import 'package:http/io_client.dart' as ioc;
part 'node.g.dart'; part 'node.g.dart';
Uri createUriFromElectrumAddress(String address) => Uri.tryParse('tcp://$address')!; Uri createUriFromElectrumAddress(String address, String path) => Uri.tryParse('tcp://$address$path')!;
@HiveType(typeId: Node.typeId) @HiveType(typeId: Node.typeId)
class Node extends HiveObject with Keyable { class Node extends HiveObject with Keyable {
@ -83,7 +83,7 @@ class Node extends HiveObject with Keyable {
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin: case WalletType.litecoin:
case WalletType.bitcoinCash: case WalletType.bitcoinCash:
return createUriFromElectrumAddress(uriRaw); return createUriFromElectrumAddress(uriRaw, path ?? '');
case WalletType.nano: case WalletType.nano:
case WalletType.banano: case WalletType.banano:
if (isSSL) { if (isSSL) {
@ -94,7 +94,7 @@ class Node extends HiveObject with Keyable {
case WalletType.ethereum: case WalletType.ethereum:
case WalletType.polygon: case WalletType.polygon:
case WalletType.solana: case WalletType.solana:
return Uri.https(uriRaw, ''); return Uri.https(uriRaw, path ?? '');
default: default:
throw Exception('Unexpected type ${type.toString()} for Node uri'); throw Exception('Unexpected type ${type.toString()} for Node uri');
} }

View file

@ -14,6 +14,7 @@ class Unspent {
bool isChange; bool isChange;
bool isSending; bool isSending;
bool isFrozen; bool isFrozen;
int? confirmations;
String note; String note;
bool get isP2wpkh => address.startsWith('bc') || address.startsWith('ltc'); bool get isP2wpkh => address.startsWith('bc') || address.startsWith('ltc');

View file

@ -7,15 +7,19 @@ abstract class WalletCredentials {
this.seedPhraseLength, this.seedPhraseLength,
this.walletInfo, this.walletInfo,
this.password, this.password,
this.derivationType, this.passphrase,
this.derivationPath, this.derivationInfo,
}); }) {
if (this.walletInfo != null && derivationInfo != null) {
this.walletInfo!.derivationInfo = derivationInfo;
}
}
final String name; final String name;
final int? height; final int? height;
int? seedPhraseLength; int? seedPhraseLength;
String? password; String? password;
DerivationType? derivationType; String? passphrase;
String? derivationPath;
WalletInfo? walletInfo; WalletInfo? walletInfo;
DerivationInfo? derivationInfo;
} }

View file

@ -17,28 +17,42 @@ enum DerivationType {
@HiveField(3) @HiveField(3)
bip39, bip39,
@HiveField(4) @HiveField(4)
electrum1, electrum,
@HiveField(5)
electrum2,
} }
class DerivationInfo { @HiveType(typeId: DerivationInfo.typeId)
class DerivationInfo extends HiveObject {
DerivationInfo({ DerivationInfo({
required this.derivationType, this.derivationType,
this.derivationPath, this.derivationPath,
this.balance = "", this.balance = "",
this.address = "", this.address = "",
this.height = 0, this.transactionsCount = 0,
this.script_type, this.scriptType,
this.description, this.description,
}); });
String balance; static const typeId = DERIVATION_INFO_TYPE_ID;
@HiveField(0, defaultValue: '')
String address; String address;
int height;
final DerivationType derivationType; @HiveField(1, defaultValue: '')
final String? derivationPath; String balance;
final String? script_type;
@HiveField(2)
int transactionsCount;
@HiveField(3)
DerivationType? derivationType;
@HiveField(4)
String? derivationPath;
@HiveField(5)
final String? scriptType;
@HiveField(6)
final String? description; final String? description;
} }
@ -57,8 +71,7 @@ class WalletInfo extends HiveObject {
this.yatEid, this.yatEid,
this.yatLastUsedAddressRaw, this.yatLastUsedAddressRaw,
this.showIntroCakePayCard, this.showIntroCakePayCard,
this.derivationType, this.derivationInfo)
this.derivationPath)
: _yatLastUsedAddressController = StreamController<String>.broadcast(); : _yatLastUsedAddressController = StreamController<String>.broadcast();
factory WalletInfo.external({ factory WalletInfo.external({
@ -74,24 +87,23 @@ class WalletInfo extends HiveObject {
bool? showIntroCakePayCard, bool? showIntroCakePayCard,
String yatEid = '', String yatEid = '',
String yatLastUsedAddressRaw = '', String yatLastUsedAddressRaw = '',
DerivationType? derivationType, DerivationInfo? derivationInfo,
String? derivationPath,
}) { }) {
return WalletInfo( return WalletInfo(
id, id,
name, name,
type, type,
isRecovery, isRecovery,
restoreHeight, restoreHeight,
date.millisecondsSinceEpoch, date.millisecondsSinceEpoch,
dirPath, dirPath,
path, path,
address, address,
yatEid, yatEid,
yatLastUsedAddressRaw, yatLastUsedAddressRaw,
showIntroCakePayCard, showIntroCakePayCard,
derivationType, derivationInfo,
derivationPath); );
} }
static const typeId = WALLET_INFO_TYPE_ID; static const typeId = WALLET_INFO_TYPE_ID;
@ -143,10 +155,10 @@ class WalletInfo extends HiveObject {
List<String>? usedAddresses; List<String>? usedAddresses;
@HiveField(16) @HiveField(16)
DerivationType? derivationType; DerivationType? derivationType; // no longer used
@HiveField(17) @HiveField(17)
String? derivationPath; String? derivationPath; // no longer used
@HiveField(18) @HiveField(18)
String? addressPageType; String? addressPageType;
@ -154,6 +166,9 @@ class WalletInfo extends HiveObject {
@HiveField(19) @HiveField(19)
String? network; String? network;
@HiveField(20)
DerivationInfo? derivationInfo;
String get yatLastUsedAddress => yatLastUsedAddressRaw ?? ''; String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
set yatLastUsedAddress(String address) { set yatLastUsedAddress(String address) {

View file

@ -38,6 +38,7 @@ dev_dependencies:
mobx_codegen: ^2.0.7 mobx_codegen: ^2.0.7
hive_generator: ^2.0.1 hive_generator: ^2.0.1
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec

View file

@ -234,14 +234,17 @@ abstract class EVMChainClient {
final decodedResponse = jsonDecode(response.body)[0] as Map<String, dynamic>; final decodedResponse = jsonDecode(response.body)[0] as Map<String, dynamic>;
final symbol = (decodedResponse['symbol'] ?? '') as String;
String filteredSymbol = symbol.replaceFirst(RegExp('^\\\$'), '');
final name = decodedResponse['name'] ?? ''; final name = decodedResponse['name'] ?? '';
final symbol = decodedResponse['symbol'] ?? '';
final decimal = decodedResponse['decimals'] ?? '0'; final decimal = decodedResponse['decimals'] ?? '0';
final iconPath = decodedResponse['logo'] ?? ''; final iconPath = decodedResponse['logo'] ?? '';
return Erc20Token( return Erc20Token(
name: name, name: name,
symbol: symbol, symbol: filteredSymbol,
contractAddress: contractAddress, contractAddress: contractAddress,
decimal: int.tryParse(decimal) ?? 0, decimal: int.tryParse(decimal) ?? 0,
iconPath: iconPath, iconPath: iconPath,

View file

@ -474,9 +474,15 @@ abstract class EVMChainWalletBase
await token.delete(); await token.delete();
balance.remove(token); balance.remove(token);
await _removeTokenTransactionsInHistory(token);
_updateBalance(); _updateBalance();
} }
Future<void> _removeTokenTransactionsInHistory(Erc20Token token) async {
transactionHistory.transactions.removeWhere((key, value) => value.tokenSymbol == token.title);
await transactionHistory.save();
}
Future<Erc20Token?> getErc20Token(String contractAddress, String chainName) async => Future<Erc20Token?> getErc20Token(String contractAddress, String chainName) async =>
await _client.getErc20Token(contractAddress, chainName); await _client.getErc20Token(contractAddress, chainName);

View file

@ -69,10 +69,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build_daemon name: build_daemon
sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf" sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "4.0.0"
build_resolvers: build_resolvers:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -85,10 +85,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.3" version: "2.4.9"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
@ -158,10 +158,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.1" version: "1.17.2"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -375,18 +375,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.15" version: "0.12.16"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.0" version: "0.5.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@ -588,10 +588,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_span name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.10.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -636,10 +636,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.1" version: "0.6.0"
timing: timing:
dependency: transitive dependency: transitive
description: description:
@ -673,13 +673,21 @@ packages:
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
watcher: watcher:
dependency: transitive dependency: "direct overridden"
description: description:
name: watcher name: watcher
sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.1.0"
web:
dependency: transitive
description:
name: web
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
@ -713,5 +721,5 @@ packages:
source: hosted source: hosted
version: "3.1.1" version: "3.1.1"
sdks: sdks:
dart: ">=3.0.0 <4.0.0" dart: ">=3.1.0-185.0.dev <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.7.0"

View file

@ -33,15 +33,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.1"
cake_backup:
dependency: transitive
description:
path: "."
ref: main
resolved-ref: "3aba867dcab6737f6707782f5db15d71f303db38"
url: "https://github.com/cake-tech/cake_backup.git"
source: git
version: "1.0.0+1"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@ -62,10 +53,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.1" version: "1.17.2"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -82,14 +73,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.2" version: "3.0.2"
cryptography:
dependency: transitive
description:
name: cryptography
sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35
url: "https://pub.dev"
source: hosted
version: "2.5.0"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@ -230,18 +213,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.15" version: "0.12.16"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.0" version: "0.5.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@ -371,10 +354,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_span name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.10.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -411,18 +394,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.1" version: "0.6.0"
tuple:
dependency: transitive
description:
name: tuple
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
url: "https://pub.dev"
source: hosted
version: "2.0.2"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -439,6 +414,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
web:
dependency: transitive
description:
name: web
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
win32: win32:
dependency: transitive dependency: transitive
description: description:
@ -456,5 +439,5 @@ packages:
source: hosted source: hosted
version: "0.2.0+3" version: "0.2.0+3"
sdks: sdks:
dart: ">=3.0.6 <4.0.0" dart: ">=3.1.0-185.0.dev <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.7.0"

View file

@ -69,10 +69,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build_daemon name: build_daemon
sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf" sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "4.0.0"
build_resolvers: build_resolvers:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -85,10 +85,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.3" version: "2.4.9"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
@ -158,10 +158,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.1" version: "1.17.2"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -391,18 +391,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.15" version: "0.12.16"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.0" version: "0.5.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@ -612,10 +612,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_span name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.10.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -660,10 +660,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.1" version: "0.6.0"
timing: timing:
dependency: transitive dependency: transitive
description: description:
@ -697,13 +697,21 @@ packages:
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
watcher: watcher:
dependency: transitive dependency: "direct overridden"
description: description:
name: watcher name: watcher
sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.1.0"
web:
dependency: transitive
description:
name: web
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
@ -737,5 +745,5 @@ packages:
source: hosted source: hosted
version: "3.1.1" version: "3.1.1"
sdks: sdks:
dart: ">=3.0.6 <4.0.0" dart: ">=3.1.0-185.0.dev <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.7.0"

View file

@ -44,7 +44,7 @@ abstract class NanoWalletBase
}) : syncStatus = NotConnectedSyncStatus(), }) : syncStatus = NotConnectedSyncStatus(),
_password = password, _password = password,
_mnemonic = mnemonic, _mnemonic = mnemonic,
_derivationType = walletInfo.derivationType!, _derivationType = walletInfo.derivationInfo!.derivationType!,
_isTransactionUpdating = false, _isTransactionUpdating = false,
_encryptionFileUtils = encryptionFileUtils, _encryptionFileUtils = encryptionFileUtils,
_client = NanoClient(), _client = NanoClient(),
@ -401,7 +401,10 @@ abstract class NanoWalletBase
derivationType = DerivationType.bip39; derivationType = DerivationType.bip39;
} }
walletInfo.derivationType = derivationType; walletInfo.derivationInfo ??= DerivationInfo(derivationType: derivationType);
if (walletInfo.derivationInfo!.derivationType == null) {
walletInfo.derivationInfo!.derivationType = derivationType;
}
return NanoWallet( return NanoWallet(
walletInfo: walletInfo, walletInfo: walletInfo,

View file

@ -2,8 +2,15 @@ import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
class NanoNewWalletCredentials extends WalletCredentials { class NanoNewWalletCredentials extends WalletCredentials {
NanoNewWalletCredentials({required String name, String? password}) NanoNewWalletCredentials({
: super(name: name, password: password); required String name,
String? password,
DerivationType? derivationType,
}) : super(
name: name,
password: password,
derivationInfo: DerivationInfo(derivationType: derivationType),
);
} }
class NanoRestoreWalletFromSeedCredentials extends WalletCredentials { class NanoRestoreWalletFromSeedCredentials extends WalletCredentials {
@ -11,11 +18,11 @@ class NanoRestoreWalletFromSeedCredentials extends WalletCredentials {
required String name, required String name,
required this.mnemonic, required this.mnemonic,
String? password, String? password,
DerivationType? derivationType, required DerivationType derivationType,
}) : super( }) : super(
name: name, name: name,
password: password, password: password,
derivationType: derivationType, derivationInfo: DerivationInfo(derivationType: derivationType),
); );
final String mnemonic; final String mnemonic;
@ -30,12 +37,12 @@ class NanoRestoreWalletFromKeysCredentials extends WalletCredentials {
NanoRestoreWalletFromKeysCredentials({ NanoRestoreWalletFromKeysCredentials({
required String name, required String name,
required String password, required String password,
required DerivationType derivationType,
required this.seedKey, required this.seedKey,
DerivationType? derivationType,
}) : super( }) : super(
name: name, name: name,
password: password, password: password,
derivationType: derivationType, derivationInfo: DerivationInfo(derivationType: derivationType),
); );
final String seedKey; final String seedKey;

View file

@ -30,11 +30,11 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
@override @override
Future<WalletBase> create(NanoNewWalletCredentials credentials, {bool? isTestnet}) async { Future<WalletBase> create(NanoNewWalletCredentials credentials, {bool? isTestnet}) async {
// nano standard: // nano standard:
DerivationType derivationType = DerivationType.nano;
String seedKey = NanoSeeds.generateSeed(); String seedKey = NanoSeeds.generateSeed();
String mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey); String mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey);
credentials.walletInfo!.derivationType = derivationType; // ensure default if not present:
credentials.walletInfo!.derivationInfo ??= DerivationInfo(derivationType: DerivationType.nano);
final wallet = NanoWallet( final wallet = NanoWallet(
walletInfo: credentials.walletInfo!, walletInfo: credentials.walletInfo!,
@ -95,9 +95,6 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
} }
} }
DerivationType derivationType = credentials.derivationType ?? DerivationType.nano;
credentials.walletInfo!.derivationType = derivationType;
String? mnemonic; String? mnemonic;
// we can't derive the mnemonic from the key in all cases, only if it's a "nano" seed // we can't derive the mnemonic from the key in all cases, only if it's a "nano" seed
@ -136,9 +133,10 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
} }
} }
DerivationType derivationType = credentials.derivationType ?? DerivationType.nano; DerivationType derivationType =
credentials.walletInfo?.derivationInfo?.derivationType ?? DerivationType.nano;
credentials.walletInfo!.derivationType = derivationType; credentials.walletInfo!.derivationInfo ??= DerivationInfo(derivationType: derivationType);
final wallet = await NanoWallet( final wallet = await NanoWallet(
password: credentials.password!, password: credentials.password!,

View file

@ -93,10 +93,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build_daemon name: build_daemon
sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169" sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.1" version: "4.0.1"
build_resolvers: build_resolvers:
dependency: transitive dependency: transitive
description: description:
@ -109,10 +109,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.3" version: "2.4.9"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
@ -182,10 +182,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.1" version: "1.17.2"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -444,18 +444,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.15" version: "0.12.16"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.0" version: "0.5.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@ -738,10 +738,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_span name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.10.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -786,10 +786,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.1" version: "0.6.0"
timing: timing:
dependency: transitive dependency: transitive
description: description:
@ -823,13 +823,21 @@ packages:
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
watcher: watcher:
dependency: transitive dependency: "direct overridden"
description: description:
name: watcher name: watcher
sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.1.0"
web:
dependency: transitive
description:
name: web
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
@ -863,5 +871,5 @@ packages:
source: hosted source: hosted
version: "3.1.2" version: "3.1.2"
sdks: sdks:
dart: ">=3.0.0 <4.0.0" dart: ">=3.1.0-185.0.dev <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.7.0"

View file

@ -34,10 +34,7 @@ class SolanaTransactionInfo extends TransactionInfo {
@override @override
String amountFormatted() { String amountFormatted() {
String stringBalance = solAmount.toString(); String stringBalance = solAmount.toString();
if (stringBalance.toString().length >= 6) {
stringBalance = stringBalance.substring(0, 6);
}
return '$stringBalance $tokenSymbol'; return '$stringBalance $tokenSymbol';
} }

View file

@ -27,6 +27,7 @@ import 'package:hex/hex.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:solana/base58.dart';
import 'package:solana/metaplex.dart' as metaplex; import 'package:solana/metaplex.dart' as metaplex;
import 'package:solana/solana.dart'; import 'package:solana/solana.dart';
@ -114,7 +115,17 @@ abstract class SolanaWalletBase
String? get seed => _mnemonic; String? get seed => _mnemonic;
@override @override
String get privateKey => HEX.encode(_keyPairData!.bytes); String get privateKey {
final privateKeyBytes = _keyPairData!.bytes;
final publicKeyBytes = _keyPairData!.publicKey.bytes;
final encodedBytes = privateKeyBytes + publicKeyBytes;
final privateKey = base58encode(encodedBytes);
return privateKey;
}
Future<void> init() async { Future<void> init() async {
final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${SPLToken.boxName}"; final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${SPLToken.boxName}";
@ -141,8 +152,8 @@ abstract class SolanaWalletBase
assert(mnemonic != null || privateKey != null); assert(mnemonic != null || privateKey != null);
if (privateKey != null) { if (privateKey != null) {
final privateKeyBytes = HEX.decode(privateKey); final privateKeyBytes = base58decode(privateKey);
return await Wallet.fromPrivateKeyBytes(privateKey: privateKeyBytes); return await Wallet.fromPrivateKeyBytes(privateKey: privateKeyBytes.take(32).toList());
} }
return Wallet.fromMnemonic(mnemonic!, account: 0, change: 0); return Wallet.fromMnemonic(mnemonic!, account: 0, change: 0);
@ -268,32 +279,12 @@ abstract class SolanaWalletBase
final transactions = await _client.fetchTransactions(address); final transactions = await _client.fetchTransactions(address);
final Map<String, SolanaTransactionInfo> result = {}; await _addTransactionsToTransactionHistory(transactions);
for (var transactionModel in transactions) {
result[transactionModel.id] = SolanaTransactionInfo(
id: transactionModel.id,
to: transactionModel.to,
from: transactionModel.from,
blockTime: transactionModel.blockTime,
direction: transactionModel.isOutgoingTx
? TransactionDirection.outgoing
: TransactionDirection.incoming,
solAmount: transactionModel.amount,
isPending: false,
txFee: transactionModel.fee,
tokenSymbol: transactionModel.tokenSymbol,
);
}
transactionHistory.addMany(result);
await transactionHistory.save();
} }
/// Fetches the SPL Tokens transactions linked to the token account Public Key /// Fetches the SPL Tokens transactions linked to the token account Public Key
Future<void> _updateSPLTokenTransactions() async { Future<void> _updateSPLTokenTransactions() async {
List<SolanaTransactionModel> splTokenTransactions = []; // List<SolanaTransactionModel> splTokenTransactions = [];
// Make a copy of keys to avoid concurrent modification // Make a copy of keys to avoid concurrent modification
var tokenKeys = List<CryptoCurrency>.from(balance.keys); var tokenKeys = List<CryptoCurrency>.from(balance.keys);
@ -307,13 +298,20 @@ abstract class SolanaWalletBase
_walletKeyPair!, _walletKeyPair!,
); );
splTokenTransactions.addAll(tokenTxs); // splTokenTransactions.addAll(tokenTxs);
await _addTransactionsToTransactionHistory(tokenTxs);
} }
} }
// await _addTransactionsToTransactionHistory(splTokenTransactions);
}
Future<void> _addTransactionsToTransactionHistory(
List<SolanaTransactionModel> transactions,
) async {
final Map<String, SolanaTransactionInfo> result = {}; final Map<String, SolanaTransactionInfo> result = {};
for (var transactionModel in splTokenTransactions) { for (var transactionModel in transactions) {
result[transactionModel.id] = SolanaTransactionInfo( result[transactionModel.id] = SolanaTransactionInfo(
id: transactionModel.id, id: transactionModel.id,
to: transactionModel.to, to: transactionModel.to,
@ -457,12 +455,23 @@ abstract class SolanaWalletBase
await token.delete(); await token.delete();
balance.remove(token); balance.remove(token);
await _removeTokenTransactionsInHistory(token);
_updateBalance(); _updateBalance();
} }
Future<void> _removeTokenTransactionsInHistory(SPLToken token) async {
transactionHistory.transactions.removeWhere((key, value) => value.tokenSymbol == token.title);
await transactionHistory.save();
}
Future<SPLToken?> getSPLToken(String mintAddress) async { Future<SPLToken?> getSPLToken(String mintAddress) async {
// Convert SPL token mint address to public key // Convert SPL token mint address to public key
final mintPublicKey = Ed25519HDPublicKey.fromBase58(mintAddress); final Ed25519HDPublicKey mintPublicKey;
try {
mintPublicKey = Ed25519HDPublicKey.fromBase58(mintAddress);
} catch (_) {
return null;
}
// Fetch token's metadata account // Fetch token's metadata account
try { try {
@ -477,10 +486,12 @@ abstract class SolanaWalletBase
iconPath = await _client.getIconImageFromTokenUri(token.uri); iconPath = await _client.getIconImageFromTokenUri(token.uri);
} catch (_) {} } catch (_) {}
String filteredTokenSymbol = token.symbol.replaceFirst(RegExp('^\\\$'), '');
return SPLToken.fromMetadata( return SPLToken.fromMetadata(
name: token.name, name: token.name,
mint: token.mint, mint: token.mint,
symbol: token.symbol, symbol: filteredTokenSymbol,
mintAddress: mintAddress, mintAddress: mintAddress,
iconPath: iconPath, iconPath: iconPath,
); );

View file

@ -19,7 +19,7 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin {
@HiveField(3) @HiveField(3)
final int decimal; final int decimal;
@HiveField(4, defaultValue: false) @HiveField(4, defaultValue: true)
bool _enabled; bool _enabled;
@HiveField(5) @HiveField(5)
@ -39,7 +39,7 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin {
required this.mint, required this.mint,
this.iconPath, this.iconPath,
this.tag = 'SOL', this.tag = 'SOL',
bool enabled = false, bool enabled = true,
}) : _enabled = enabled, }) : _enabled = enabled,
super( super(
name: mint.toLowerCase(), name: mint.toLowerCase(),

View file

@ -1,13 +1,22 @@
part of 'bitcoin.dart'; part of 'bitcoin.dart';
class CWBitcoin extends Bitcoin { class CWBitcoin extends Bitcoin {
@override WalletCredentials createBitcoinRestoreWalletFromSeedCredentials({
TransactionPriority getMediumTransactionPriority() => BitcoinTransactionPriority.medium; required String name,
required String mnemonic,
@override required String password,
WalletCredentials createBitcoinRestoreWalletFromSeedCredentials( required DerivationType derivationType,
{required String name, required String mnemonic, required String password}) => required String derivationPath,
BitcoinRestoreWalletFromSeedCredentials(name: name, mnemonic: mnemonic, password: password); String? passphrase,
}) =>
BitcoinRestoreWalletFromSeedCredentials(
name: name,
mnemonic: mnemonic,
password: password,
derivationType: derivationType,
derivationPath: derivationPath,
passphrase: passphrase,
);
@override @override
WalletCredentials createBitcoinRestoreWalletFromWIFCredentials( WalletCredentials createBitcoinRestoreWalletFromWIFCredentials(
@ -23,6 +32,9 @@ class CWBitcoin extends Bitcoin {
{required String name, WalletInfo? walletInfo, String? password}) => {required String name, WalletInfo? walletInfo, String? password}) =>
BitcoinNewWalletCredentials(name: name, walletInfo: walletInfo, password: password); BitcoinNewWalletCredentials(name: name, walletInfo: walletInfo, password: password);
@override
TransactionPriority getMediumTransactionPriority() => BitcoinTransactionPriority.medium;
@override @override
List<String> getWordList() => wordlist; List<String> getWordList() => wordlist;
@ -78,21 +90,20 @@ class CWBitcoin extends Bitcoin {
final bitcoinFeeRate = final bitcoinFeeRate =
priority == BitcoinTransactionPriority.custom && feeRate != null ? feeRate : null; priority == BitcoinTransactionPriority.custom && feeRate != null ? feeRate : null;
return BitcoinTransactionCredentials( return BitcoinTransactionCredentials(
outputs outputs
.map((out) => OutputInfo( .map((out) => OutputInfo(
fiatAmount: out.fiatAmount, fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount, cryptoAmount: out.cryptoAmount,
address: out.address, address: out.address,
note: out.note, note: out.note,
sendAll: out.sendAll, sendAll: out.sendAll,
extractedAddress: out.extractedAddress, extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress, isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount, formattedCryptoAmount: out.formattedCryptoAmount,
memo: out.memo)) memo: out.memo))
.toList(), .toList(),
priority: priority as BitcoinTransactionPriority, priority: priority as BitcoinTransactionPriority,
feeRate: bitcoinFeeRate feeRate: bitcoinFeeRate);
);
} }
@override @override
@ -248,6 +259,137 @@ class CWBitcoin extends Bitcoin {
} }
} }
@override
Future<List<DerivationType>> compareDerivationMethods(
{required String mnemonic, required Node node}) async {
if (await checkIfMnemonicIsElectrum2(mnemonic)) {
return [DerivationType.electrum];
}
return [DerivationType.bip39, DerivationType.electrum];
}
int _countOccurrences(String str, String charToCount) {
int count = 0;
for (int i = 0; i < str.length; i++) {
if (str[i] == charToCount) {
count++;
}
}
return count;
}
@override
Future<List<DerivationInfo>> getDerivationsFromMnemonic({
required String mnemonic,
required Node node,
String? passphrase,
}) async {
List<DerivationInfo> list = [];
List<DerivationType> types = await compareDerivationMethods(mnemonic: mnemonic, node: node);
if (types.length == 1 && types.first == DerivationType.electrum) {
return [
DerivationInfo(
derivationType: DerivationType.electrum,
derivationPath: "m/0'/0",
description: "Electrum",
scriptType: "p2wpkh",
)
];
}
final electrumClient = ElectrumClient();
await electrumClient.connectToUri(node.uri);
late BasedUtxoNetwork network;
btc.NetworkType networkType;
switch (node.type) {
case WalletType.litecoin:
network = LitecoinNetwork.mainnet;
networkType = litecoinNetwork;
break;
case WalletType.bitcoin:
default:
network = BitcoinNetwork.mainnet;
networkType = btc.bitcoin;
break;
}
for (DerivationType dType in electrum_derivations.keys) {
late Uint8List seedBytes;
if (dType == DerivationType.electrum) {
seedBytes = await mnemonicToSeedBytes(mnemonic);
} else if (dType == DerivationType.bip39) {
seedBytes = bip39.mnemonicToSeed(mnemonic, passphrase: passphrase ?? '');
}
for (DerivationInfo dInfo in electrum_derivations[dType]!) {
try {
DerivationInfo dInfoCopy = DerivationInfo(
derivationType: dInfo.derivationType,
derivationPath: dInfo.derivationPath,
description: dInfo.description,
scriptType: dInfo.scriptType,
);
String derivationPath = dInfoCopy.derivationPath!;
int derivationDepth = _countOccurrences(derivationPath, "/");
// the correct derivation depth is dependant on the derivation type:
// the derivation paths defined in electrum_derivations are at the ROOT level, i.e.:
// electrum's format doesn't specify subaddresses, just subaccounts:
// for BIP44
if (derivationDepth == 3) {
// we add "/0/0" so that we generate account 0, index 0 and correctly get balance
derivationPath += "/0/0";
// we don't support sub-ACCOUNTS in bitcoin like we do monero, and so the path dInfoCopy
// expects should be ACCOUNT 0, index unspecified:
dInfoCopy.derivationPath = dInfoCopy.derivationPath! + "/0";
}
// var hd = bip32.BIP32.fromSeed(seedBytes).derivePath(derivationPath);
final hd = btc.HDWallet.fromSeed(
seedBytes,
network: networkType,
).derivePath(derivationPath);
String? address;
switch (dInfoCopy.scriptType) {
case "p2wpkh":
address = generateP2WPKHAddress(hd: hd, network: network);
break;
case "p2pkh":
address = generateP2PKHAddress(hd: hd, network: network);
break;
case "p2wpkh-p2sh":
address = generateP2SHAddress(hd: hd, network: network);
break;
default:
continue;
}
final sh = scriptHash(address, network: network);
final history = await electrumClient.getHistory(sh);
final balance = await electrumClient.getBalance(sh);
dInfoCopy.balance = balance.entries.first.value.toString();
dInfoCopy.address = address;
dInfoCopy.transactionsCount = history.length;
list.add(dInfoCopy);
} catch (e) {
print(e);
}
}
}
// sort the list such that derivations with the most transactions are first:
list.sort((a, b) => b.transactionsCount.compareTo(a.transactionsCount));
return list;
}
@override @override
bool hasTaprootInput(PendingTransaction pendingTransaction) { bool hasTaprootInput(PendingTransaction pendingTransaction) {
return (pendingTransaction as PendingBitcoinTransaction).hasTaprootInputs; return (pendingTransaction as PendingBitcoinTransaction).hasTaprootInputs;
@ -282,13 +424,20 @@ class CWBitcoin extends Bitcoin {
} }
@override @override
int getFeeAmountWithFeeRate(Object wallet, int feeRate, int inputsCount, int outputsCount, int getEstimatedFeeWithFeeRate(Object wallet, int feeRate, int? amount,
{int? size}) { {int? outputsCount, int? size}) {
final bitcoinWallet = wallet as ElectrumWallet; final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.feeAmountWithFeeRate( return bitcoinWallet.calculateEstimatedFeeWithFeeRate(
feeRate, feeRate,
inputsCount, amount,
outputsCount, outputsCount: outputsCount,
size: size,
); );
} }
@override
int getMaxCustomFeeRate(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return (bitcoinWallet.feeRate(BitcoinTransactionPriority.fast) * 1.1).round();
}
} }

View file

@ -17,13 +17,12 @@ class DFXBuyProvider extends BuyProvider {
: super(wallet: wallet, isTestEnvironment: isTestEnvironment); : super(wallet: wallet, isTestEnvironment: isTestEnvironment);
static const _baseUrl = 'api.dfx.swiss'; static const _baseUrl = 'api.dfx.swiss';
static const _authPath = '/v1/auth/signMessage'; // static const _signMessagePath = '/v1/auth/signMessage';
static const _signUpPath = '/v1/auth/signUp'; static const _authPath = '/v1/auth';
static const _signInPath = '/v1/auth/signIn';
static const walletName = 'CakeWallet'; static const walletName = 'CakeWallet';
@override @override
String get title => 'DFX Connect'; String get title => 'DFX.swiss';
@override @override
String get providerDescription => S.current.dfx_option_description; String get providerDescription => S.current.dfx_option_description;
@ -73,21 +72,25 @@ class DFXBuyProvider extends BuyProvider {
String get walletAddress => String get walletAddress =>
wallet.walletAddresses.primaryAddress ?? wallet.walletAddresses.address; wallet.walletAddresses.primaryAddress ?? wallet.walletAddresses.address;
Future<String> getSignMessage() async { Future<String> getSignMessage() async =>
final uri = Uri.https(_baseUrl, _authPath, {'address': walletAddress}); "By_signing_this_message,_you_confirm_that_you_are_the_sole_owner_of_the_provided_Blockchain_address._Your_ID:_$walletAddress";
var response = await http.get(uri, headers: {'accept': 'application/json'}); // // Lets keep this just in case, but we can avoid this API Call
// Future<String> getSignMessage() async {
// final uri = Uri.https(_baseUrl, _signMessagePath, {'address': walletAddress});
//
// final response = await http.get(uri, headers: {'accept': 'application/json'});
//
// if (response.statusCode == 200) {
// final responseBody = jsonDecode(response.body);
// return responseBody['message'] as String;
// } else {
// throw Exception(
// 'Failed to get sign message. Status: ${response.statusCode} ${response.body}');
// }
// }
if (response.statusCode == 200) { Future<String> auth() async {
final responseBody = jsonDecode(response.body);
return responseBody['message'] as String;
} else {
throw Exception(
'Failed to get sign message. Status: ${response.statusCode} ${response.body}');
}
}
Future<String> signUp() async {
final signMessage = getSignature(await getSignMessage()); final signMessage = getSignature(await getSignMessage());
final requestBody = jsonEncode({ final requestBody = jsonEncode({
@ -96,7 +99,7 @@ class DFXBuyProvider extends BuyProvider {
'signature': signMessage, 'signature': signMessage,
}); });
final uri = Uri.https(_baseUrl, _signUpPath); final uri = Uri.https(_baseUrl, _authPath);
var response = await http.post( var response = await http.post(
uri, uri,
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
@ -115,33 +118,6 @@ class DFXBuyProvider extends BuyProvider {
} }
} }
Future<String> signIn() async {
final signMessage = getSignature(await getSignMessage());
final requestBody = jsonEncode({
'address': walletAddress,
'signature': signMessage,
});
final uri = Uri.https(_baseUrl, _signInPath);
var response = await http.post(
uri,
headers: {'Content-Type': 'application/json'},
body: requestBody,
);
if (response.statusCode == 201) {
final responseBody = jsonDecode(response.body);
return responseBody['accessToken'] as String;
} else if (response.statusCode == 403) {
final responseBody = jsonDecode(response.body);
final message = responseBody['message'] ?? 'Service unavailable in your country';
throw Exception(message);
} else {
throw Exception('Failed to sign in. Status: ${response.statusCode} ${response.body}');
}
}
String getSignature(String message) { String getSignature(String message) {
switch (wallet.type) { switch (wallet.type) {
case WalletType.ethereum: case WalletType.ethereum:
@ -164,17 +140,7 @@ class DFXBuyProvider extends BuyProvider {
final blockchain = this.blockchain; final blockchain = this.blockchain;
final actionType = isBuyAction == true ? '/buy' : '/sell'; final actionType = isBuyAction == true ? '/buy' : '/sell';
String accessToken; final accessToken = await auth();
try {
accessToken = await signUp();
} on Exception catch (e) {
if (e.toString().contains('409')) {
accessToken = await signIn();
} else {
rethrow;
}
}
final uri = Uri.https('services.dfx.swiss', actionType, { final uri = Uri.https('services.dfx.swiss', actionType, {
'session': accessToken, 'session': accessToken,
@ -198,7 +164,7 @@ class DFXBuyProvider extends BuyProvider {
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return AlertWithOneAction( return AlertWithOneAction(
alertTitle: "DFX Connect", alertTitle: "DFX.swiss",
alertContent: S.of(context).buy_provider_unavailable + ': $e', alertContent: S.of(context).buy_provider_unavailable + ': $e',
buttonText: S.of(context).ok, buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop()); buttonAction: () => Navigator.of(context).pop());

View file

@ -0,0 +1,9 @@
class CreateTradeResult {
bool result;
String? errorMessage;
CreateTradeResult({
required this.result,
this.errorMessage,
});
}

View file

@ -11,5 +11,9 @@ class NodeAddressValidator extends TextValidator {
class NodePathValidator extends TextValidator { class NodePathValidator extends TextValidator {
NodePathValidator() NodePathValidator()
: super(errorMessage: S.current.error_text_node_address, pattern: '^([/0-9a-zA-Z.\-]+)?\$'); : super(
errorMessage: S.current.error_text_node_address,
pattern: '^([/0-9a-zA-Z.\-]+)?\$',
isAutovalidate: true,
);
} }

View file

@ -1,9 +1,13 @@
import 'package:cake_wallet/core/generate_wallet_password.dart'; import 'package:cake_wallet/core/generate_wallet_password.dart';
import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/wallet_base.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_service.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
class WalletLoadingService { class WalletLoadingService {
@ -36,15 +40,43 @@ class WalletLoadingService {
} }
Future<WalletBase> load(WalletType type, String name, {String? password}) async { Future<WalletBase> load(WalletType type, String name, {String? password}) async {
final walletService = walletServiceFactory.call(type); try {
final walletPassword = password ?? (await keyService.getWalletPassword(walletName: name)); final walletService = walletServiceFactory.call(type);
final wallet = await walletService.openWallet(name, walletPassword); final walletPassword = password ?? (await keyService.getWalletPassword(walletName: name));
final wallet = await walletService.openWallet(name, walletPassword);
if (type == WalletType.monero) { if (type == WalletType.monero) {
await updateMoneroWalletPassword(wallet); await updateMoneroWalletPassword(wallet);
}
return wallet;
} catch (error, stack) {
ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack));
// try opening another wallet that is not corrupted to give user access to the app
final walletInfoSource = await CakeHive.openBox<WalletInfo>(WalletInfo.boxName);
for (var walletInfo in walletInfoSource.values) {
try {
final walletService = walletServiceFactory.call(walletInfo.type);
final walletPassword = password ?? (await keyService.getWalletPassword(walletName: name));
final wallet = await walletService.openWallet(walletInfo.name, walletPassword);
if (walletInfo.type == WalletType.monero) {
await updateMoneroWalletPassword(wallet);
}
await sharedPreferences.setString(PreferencesKey.currentWalletName, wallet.name);
await sharedPreferences.setInt(
PreferencesKey.currentWalletType, serializeToInt(wallet.type));
return wallet;
} catch (_) {}
}
// if all user's wallets are corrupted throw exception
throw error;
} }
return wallet;
} }
Future<void> updateMoneroWalletPassword(WalletBase wallet) async { Future<void> updateMoneroWalletPassword(WalletBase wallet) async {

View file

@ -29,4 +29,4 @@ class BiometricAuth {
return false; return false;
} }
} }

View file

@ -3,6 +3,8 @@ String calculateFiatAmount({double? price, String? cryptoAmount}) {
return '0.00'; return '0.00';
} }
cryptoAmount = cryptoAmount.replaceAll(',', '.');
final _amount = double.parse(cryptoAmount); final _amount = double.parse(cryptoAmount);
final _result = price * _amount; final _result = price * _amount;
final result = _result < 0 ? _result * -1 : _result; final result = _result < 0 ? _result * -1 : _result;

View file

@ -220,6 +220,10 @@ Future<void> defaultSettingsMigration(
await updateNanoNodeList(nodes: nodes); await updateNanoNodeList(nodes: nodes);
break; break;
case 32:
await updateBtcNanoWalletInfos(walletInfoSource);
break;
default: default:
break; break;
} }
@ -757,6 +761,20 @@ Future<void> changeDefaultMoneroNode(
} }
} }
Future<void> updateBtcNanoWalletInfos(Box<WalletInfo> walletsInfoSource) async {
for (WalletInfo walletInfo in walletsInfoSource.values) {
if (walletInfo.type == WalletType.nano || walletInfo.type == WalletType.bitcoin) {
walletInfo.derivationInfo = DerivationInfo(
derivationPath: walletInfo.derivationPath,
derivationType: walletInfo.derivationType,
address: walletInfo.address,
transactionsCount: walletInfo.restoreHeight,
);
await walletInfo.save();
}
}
}
Future<void> changeDefaultBitcoinNode( Future<void> changeDefaultBitcoinNode(
Box<Node> nodeSource, SharedPreferences sharedPreferences) async { Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
const cakeWalletBitcoinNodeUriPattern = '.cakewallet.com'; const cakeWalletBitcoinNodeUriPattern = '.cakewallet.com';

View file

@ -51,10 +51,12 @@ class AddressResolver {
} }
final match = RegExp(addressPattern).firstMatch(raw); final match = RegExp(addressPattern).firstMatch(raw);
return match?.group(0)?.replaceAllMapped(RegExp('[^0-9a-zA-Z]|bitcoincash:|nano_'), return match?.group(0)?.replaceAllMapped(RegExp('[^0-9a-zA-Z]|bitcoincash:|nano_|ban_'),
(Match match) { (Match match) {
String group = match.group(0)!; String group = match.group(0)!;
if (group.startsWith('bitcoincash:') || group.startsWith('nano_')) { if (group.startsWith('bitcoincash:') ||
group.startsWith('nano_') ||
group.startsWith('ban_')) {
return group; return group;
} }
return ''; return '';

View file

@ -22,7 +22,7 @@ extension ProviderTypeName on ProviderType {
case ProviderType.robinhood: case ProviderType.robinhood:
return 'Robinhood Connect'; return 'Robinhood Connect';
case ProviderType.dfx: case ProviderType.dfx:
return 'DFX Connect'; return 'DFX.swiss';
case ProviderType.onramper: case ProviderType.onramper:
return 'Onramper'; return 'Onramper';
case ProviderType.moonpay: case ProviderType.moonpay:

View file

@ -143,8 +143,10 @@ class CWEthereum extends Ethereum {
} }
wallet as EthereumWallet; wallet as EthereumWallet;
return wallet.erc20Currencies
.firstWhere((element) => transaction.tokenSymbol == element.symbol); return wallet.erc20Currencies.firstWhere(
(element) => transaction.tokenSymbol == element.symbol,
);
} }
@override @override

View file

@ -955,4 +955,4 @@ String get todayLabel => 'Oyọ';
@override @override
String get noSpellCheckReplacementsLabel => ""; String get noSpellCheckReplacementsLabel => "";
} }

View file

@ -106,6 +106,10 @@ Future<void> initializeAppConfigs() async {
CakeHive.registerAdapter(DerivationTypeAdapter()); CakeHive.registerAdapter(DerivationTypeAdapter());
} }
if (!CakeHive.isAdapterRegistered(DERIVATION_INFO_TYPE_ID)) {
CakeHive.registerAdapter(DerivationInfoAdapter());
}
if (!CakeHive.isAdapterRegistered(WALLET_TYPE_TYPE_ID)) { if (!CakeHive.isAdapterRegistered(WALLET_TYPE_TYPE_ID)) {
CakeHive.registerAdapter(WalletTypeAdapter()); CakeHive.registerAdapter(WalletTypeAdapter());
} }
@ -165,7 +169,7 @@ Future<void> initializeAppConfigs() async {
transactionDescriptions: transactionDescriptions, transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage, secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo, anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 31, initialMigrationVersion: 32,
); );
} }

View file

@ -96,6 +96,7 @@ class CWNano extends Nano {
NanoNewWalletCredentials( NanoNewWalletCredentials(
name: name, name: name,
password: password, password: password,
derivationType: DerivationType.nano,
); );
@override @override
@ -103,15 +104,10 @@ class CWNano extends Nano {
required String name, required String name,
required String password, required String password,
required String mnemonic, required String mnemonic,
DerivationType? derivationType, required DerivationType derivationType,
}) { }) {
if (derivationType == null) { if (mnemonic.split(" ").length == 12 && derivationType != DerivationType.bip39) {
// figure out the derivation type as best we can, otherwise set it to "unknown" throw Exception("Invalid mnemonic for derivation type!");
if (mnemonic.split(" ").length == 12) {
derivationType = DerivationType.bip39;
} else {
derivationType = DerivationType.unknown;
}
} }
return NanoRestoreWalletFromSeedCredentials( return NanoRestoreWalletFromSeedCredentials(
@ -127,15 +123,10 @@ class CWNano extends Nano {
required String name, required String name,
required String password, required String password,
required String seedKey, required String seedKey,
DerivationType? derivationType, required DerivationType derivationType,
}) { }) {
if (derivationType == null) { if (seedKey.length == 128 && derivationType != DerivationType.bip39) {
// figure out the derivation type as best we can, otherwise set it to "unknown" throw Exception("Invalid seed key length for derivation type!");
if (seedKey.length == 64) {
derivationType = DerivationType.nano;
} else {
derivationType = DerivationType.unknown;
}
} }
return NanoRestoreWalletFromKeysCredentials( return NanoRestoreWalletFromKeysCredentials(
@ -199,7 +190,6 @@ class CWNano extends Nano {
} }
class CWNanoUtil extends NanoUtil { class CWNanoUtil extends NanoUtil {
@override @override
bool isValidBip39Seed(String seed) { bool isValidBip39Seed(String seed) {
return NanoDerivations.isValidBip39Seed(seed); return NanoDerivations.isValidBip39Seed(seed);
@ -353,4 +343,54 @@ class CWNanoUtil extends NanoUtil {
return [DerivationType.nano, DerivationType.bip39]; return [DerivationType.nano, DerivationType.bip39];
} }
} }
@override
Future<List<DerivationInfo>> getDerivationsFromMnemonic({
String? mnemonic,
String? seedKey,
required Node node,
}) async {
List<DerivationInfo> list = [];
List<DerivationType> possibleDerivationTypes = await compareDerivationMethods(
mnemonic: mnemonic,
privateKey: seedKey,
node: node,
);
if (possibleDerivationTypes.length == 1) {
return [DerivationInfo(derivationType: possibleDerivationTypes.first)];
}
AccountInfoResponse? bip39Info = await nanoUtil!.getInfoFromSeedOrMnemonic(
DerivationType.bip39,
mnemonic: mnemonic,
seedKey: seedKey,
node: node,
);
AccountInfoResponse? standardInfo = await nanoUtil!.getInfoFromSeedOrMnemonic(
DerivationType.nano,
mnemonic: mnemonic,
seedKey: seedKey,
node: node,
);
if (standardInfo?.confirmationHeight != null && standardInfo!.confirmationHeight > 0) {
list.add(DerivationInfo(
derivationType: DerivationType.nano,
balance: nanoUtil!.getRawAsUsableString(standardInfo.balance, nanoUtil!.rawPerNano),
address: standardInfo.address!,
transactionsCount: standardInfo.confirmationHeight,
));
}
if (bip39Info?.confirmationHeight != null && bip39Info!.confirmationHeight > 0) {
list.add(DerivationInfo(
derivationType: DerivationType.bip39,
balance: nanoUtil!.getRawAsUsableString(bip39Info.balance, nanoUtil!.rawPerNano),
address: bip39Info.address!,
transactionsCount: bip39Info.confirmationHeight,
));
}
return list;
}
} }

View file

@ -141,8 +141,10 @@ class CWPolygon extends Polygon {
} }
wallet as PolygonWallet; wallet as PolygonWallet;
return wallet.erc20Currencies.firstWhere( return wallet.erc20Currencies.firstWhere(
(element) => transaction.tokenSymbol.toLowerCase() == element.symbol.toLowerCase()); (element) => transaction.tokenSymbol.toLowerCase() == element.symbol.toLowerCase(),
);
} }
@override @override

View file

@ -111,8 +111,10 @@ class CWSolana extends Solana {
} }
wallet as SolanaWallet; wallet as SolanaWallet;
return wallet.splTokenCurrencies
.firstWhere((element) => transaction.tokenSymbol == element.symbol); return wallet.splTokenCurrencies.firstWhere(
(element) => transaction.tokenSymbol == element.symbol,
);
} }
@override @override

View file

@ -96,18 +96,20 @@ class NodeForm extends StatelessWidget {
], ],
), ),
SizedBox(height: 10.0), SizedBox(height: 10.0),
Row( if (nodeViewModel.hasPathSupport) ...[
children: <Widget>[ Row(
Expanded( children: <Widget>[
child: BaseTextFormField( Expanded(
controller: _pathController, child: BaseTextFormField(
hintText: "/path", controller: _pathController,
validator: NodePathValidator(), hintText: "/path",
), validator: NodePathValidator(),
) ),
], )
), ],
SizedBox(height: 10.0), ),
SizedBox(height: 10.0),
],
Row( Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(

View file

@ -1,8 +1,5 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart'; import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -13,15 +10,14 @@ class WalletRestoreChooseDerivationPage extends BasePage {
WalletRestoreChooseDerivationPage(this.walletRestoreChooseDerivationViewModel) {} WalletRestoreChooseDerivationPage(this.walletRestoreChooseDerivationViewModel) {}
@override @override
Widget middle(BuildContext context) => Observer( Widget middle(BuildContext context) => Text(
builder: (_) => Text( S.current.choose_derivation,
S.current.choose_derivation, style: TextStyle(
style: TextStyle( fontSize: 18.0,
fontSize: 18.0, fontWeight: FontWeight.bold,
fontWeight: FontWeight.bold, fontFamily: 'Lato',
fontFamily: 'Lato', color: titleColor(context)),
color: titleColor(context)), );
));
final WalletRestoreChooseDerivationViewModel walletRestoreChooseDerivationViewModel; final WalletRestoreChooseDerivationViewModel walletRestoreChooseDerivationViewModel;
DerivationType derivationType = DerivationType.unknown; DerivationType derivationType = DerivationType.unknown;
@ -105,7 +101,7 @@ class WalletRestoreChooseDerivationPage extends BasePage {
), ),
), ),
Text( Text(
"${S.current.transactions}: ${derivation.height}", "${S.current.transactions}: ${derivation.transactionsCount}",
style: Theme.of(context).primaryTextTheme.labelMedium!.copyWith( style: Theme.of(context).primaryTextTheme.labelMedium!.copyWith(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,

View file

@ -20,6 +20,7 @@ class WalletRestoreFromSeedForm extends StatefulWidget {
{Key? key, {Key? key,
required this.displayLanguageSelector, required this.displayLanguageSelector,
required this.displayBlockHeightSelector, required this.displayBlockHeightSelector,
required this.displayPassphrase,
required this.type, required this.type,
required this.displayWalletPassword, required this.displayWalletPassword,
required this.seedTypeViewModel, required this.seedTypeViewModel,
@ -35,6 +36,7 @@ class WalletRestoreFromSeedForm extends StatefulWidget {
final bool displayLanguageSelector; final bool displayLanguageSelector;
final bool displayBlockHeightSelector; final bool displayBlockHeightSelector;
final bool displayWalletPassword; final bool displayWalletPassword;
final bool displayPassphrase;
final SeedTypeViewModel seedTypeViewModel; final SeedTypeViewModel seedTypeViewModel;
final FocusNode? blockHeightFocusNode; final FocusNode? blockHeightFocusNode;
final Function(bool)? onHeightOrDateEntered; final Function(bool)? onHeightOrDateEntered;
@ -57,6 +59,7 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
nameTextEditingController = TextEditingController(), nameTextEditingController = TextEditingController(),
passwordTextEditingController = displayWalletPassword ? TextEditingController() : null, passwordTextEditingController = displayWalletPassword ? TextEditingController() : null,
repeatedPasswordTextEditingController = displayWalletPassword ? TextEditingController() : null, repeatedPasswordTextEditingController = displayWalletPassword ? TextEditingController() : null,
passphraseController = TextEditingController(),
seedTypeController = TextEditingController(); seedTypeController = TextEditingController();
final GlobalKey<SeedWidgetState> seedWidgetStateKey; final GlobalKey<SeedWidgetState> seedWidgetStateKey;
@ -66,6 +69,7 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
final TextEditingController? passwordTextEditingController; final TextEditingController? passwordTextEditingController;
final TextEditingController? repeatedPasswordTextEditingController; final TextEditingController? repeatedPasswordTextEditingController;
final TextEditingController seedTypeController; final TextEditingController seedTypeController;
final TextEditingController passphraseController;
final GlobalKey<FormState> formKey; final GlobalKey<FormState> formKey;
late ReactionDisposer moneroSeedTypeReaction; late ReactionDisposer moneroSeedTypeReaction;
String language; String language;
@ -206,15 +210,15 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
obscureText: true)], obscureText: true)],
if (widget.displayLanguageSelector) if (widget.displayLanguageSelector)
GestureDetector( GestureDetector(
onTap: () async { onTap: () async {
await showPopUp<void>( await showPopUp<void>(
context: context, context: context,
builder: (_) => SeedLanguagePicker( builder: (_) => SeedLanguagePicker(
selected: language, selected: language,
onItemSelected: _changeLanguage, onItemSelected: _changeLanguage,
seedType: isPolyseed ? SeedType.polyseed : SeedType.legacy, seedType: isPolyseed ? SeedType.polyseed : SeedType.legacy,
)); ));
}, },
child: Container( child: Container(
color: Colors.transparent, color: Colors.transparent,
padding: EdgeInsets.only(top: 20.0), padding: EdgeInsets.only(top: 20.0),
@ -234,6 +238,14 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
key: blockchainHeightKey, key: blockchainHeightKey,
onHeightOrDateEntered: widget.onHeightOrDateEntered, onHeightOrDateEntered: widget.onHeightOrDateEntered,
hasDatePicker: widget.type == WalletType.monero), hasDatePicker: widget.type == WalletType.monero),
if (widget.displayPassphrase) ...[
const SizedBox(height: 10),
BaseTextFormField(
hintText: S.current.passphrase,
controller: passphraseController,
obscureText: true,
),
]
])); ]));
} }

View file

@ -1,7 +1,5 @@
import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_from_keys_form.dart'; import 'package:cake_wallet/src/screens/restore/wallet_restore_from_keys_form.dart';
@ -9,7 +7,6 @@ import 'package:cake_wallet/src/screens/restore/wallet_restore_from_seed_form.da
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart'; import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart';
@ -17,7 +14,6 @@ import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/restore/restore_mode.dart'; import 'package:cake_wallet/view_model/restore/restore_mode.dart';
import 'package:cake_wallet/view_model/seed_type_view_model.dart'; import 'package:cake_wallet/view_model/seed_type_view_model.dart';
import 'package:cake_wallet/view_model/wallet_restore_view_model.dart'; import 'package:cake_wallet/view_model/wallet_restore_view_model.dart';
import 'package:cw_core/nano_account_info_response.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -42,6 +38,7 @@ class WalletRestorePage extends BasePage {
displayBlockHeightSelector: displayBlockHeightSelector:
walletRestoreViewModel.hasBlockchainHeightLanguageSelector, walletRestoreViewModel.hasBlockchainHeightLanguageSelector,
displayLanguageSelector: walletRestoreViewModel.hasSeedLanguageSelector, displayLanguageSelector: walletRestoreViewModel.hasSeedLanguageSelector,
displayPassphrase: walletRestoreViewModel.hasPassphrase,
type: walletRestoreViewModel.type, type: walletRestoreViewModel.type,
key: walletRestoreFromSeedFormKey, key: walletRestoreFromSeedFormKey,
blockHeightFocusNode: _blockHeightFocusNode, blockHeightFocusNode: _blockHeightFocusNode,
@ -105,8 +102,10 @@ class WalletRestorePage extends BasePage {
final GlobalKey<WalletRestoreFromSeedFormState> walletRestoreFromSeedFormKey; final GlobalKey<WalletRestoreFromSeedFormState> walletRestoreFromSeedFormKey;
final GlobalKey<WalletRestoreFromKeysFromState> walletRestoreFromKeysFormKey; final GlobalKey<WalletRestoreFromKeysFromState> walletRestoreFromKeysFormKey;
final FocusNode _blockHeightFocusNode; final FocusNode _blockHeightFocusNode;
DerivationType derivationType = DerivationType.unknown;
String? derivationPath = null; // DerivationType derivationType = DerivationType.unknown;
// String? derivationPath = null;
DerivationInfo? derivationInfo;
@override @override
Widget body(BuildContext context) { Widget body(BuildContext context) {
@ -306,6 +305,11 @@ class WalletRestorePage extends BasePage {
-1; -1;
} }
if (walletRestoreViewModel.hasPassphrase) {
credentials['passphrase'] =
walletRestoreFromSeedFormKey.currentState!.passphraseController.text;
}
credentials['name'] = credentials['name'] =
walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.text; walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.text;
} else if (walletRestoreViewModel.mode == WalletRestoreMode.keys) { } else if (walletRestoreViewModel.mode == WalletRestoreMode.keys) {
@ -326,58 +330,11 @@ class WalletRestorePage extends BasePage {
} }
} }
credentials['derivationType'] = this.derivationType; credentials['derivationInfo'] = this.derivationInfo;
credentials['derivationPath'] = this.derivationPath;
credentials['walletType'] = walletRestoreViewModel.type; credentials['walletType'] = walletRestoreViewModel.type;
return credentials; return credentials;
} }
Future<List<DerivationInfo>> getDerivationInfo(dynamic credentials) async {
var list = <DerivationInfo>[];
var walletType = credentials["walletType"] as WalletType;
var appStore = getIt.get<AppStore>();
var node = appStore.settingsStore.getCurrentNode(walletType);
switch (walletType) {
case WalletType.nano:
String? mnemonic = credentials['seed'] as String?;
String? seedKey = credentials['private_key'] as String?;
AccountInfoResponse? bip39Info = await nanoUtil!.getInfoFromSeedOrMnemonic(
DerivationType.bip39,
mnemonic: mnemonic,
seedKey: seedKey,
node: node);
AccountInfoResponse? standardInfo = await nanoUtil!.getInfoFromSeedOrMnemonic(
DerivationType.nano,
mnemonic: mnemonic,
seedKey: seedKey,
node: node,
);
if (standardInfo?.balance != null) {
list.add(DerivationInfo(
derivationType: DerivationType.nano,
balance: nanoUtil!.getRawAsUsableString(standardInfo!.balance, nanoUtil!.rawPerNano),
address: standardInfo.address!,
height: standardInfo.confirmationHeight,
));
}
if (bip39Info?.balance != null) {
list.add(DerivationInfo(
derivationType: DerivationType.bip39,
balance: nanoUtil!.getRawAsUsableString(bip39Info!.balance, nanoUtil!.rawPerNano),
address: bip39Info.address!,
height: bip39Info.confirmationHeight,
));
}
break;
default:
break;
}
return list;
}
Future<void> _confirmForm(BuildContext context) async { Future<void> _confirmForm(BuildContext context) async {
// Dismissing all visible keyboard to provide context for navigation // Dismissing all visible keyboard to provide context for navigation
FocusManager.instance.primaryFocus?.unfocus(); FocusManager.instance.primaryFocus?.unfocus();
@ -406,51 +363,46 @@ class WalletRestorePage extends BasePage {
walletRestoreViewModel.state = IsExecutingState(); walletRestoreViewModel.state = IsExecutingState();
List<DerivationType> derivationTypes = DerivationInfo? dInfo;
await walletRestoreViewModel.getDerivationTypes(_credentials());
if (derivationTypes[0] == DerivationType.unknown || derivationTypes.length > 1) { // get info about the different derivations:
// push screen to choose the derivation type: List<DerivationInfo> derivations =
List<DerivationInfo> derivations = await getDerivationInfo(_credentials()); await walletRestoreViewModel.getDerivationInfo(_credentials());
int derivationsWithHistory = 0; int derivationsWithHistory = 0;
int derivationWithHistoryIndex = 0; int derivationWithHistoryIndex = 0;
for (int i = 0; i < derivations.length; i++) { for (int i = 0; i < derivations.length; i++) {
if (derivations[i].height > 0) { if (derivations[i].transactionsCount > 0) {
derivationsWithHistory++; derivationsWithHistory++;
derivationWithHistoryIndex = i; derivationWithHistoryIndex = i;
}
} }
DerivationInfo? derivationInfo;
if (derivationsWithHistory > 1) {
derivationInfo = await Navigator.of(context).pushNamed(Routes.restoreWalletChooseDerivation,
arguments: derivations) as DerivationInfo?;
} else if (derivationsWithHistory == 1) {
derivationInfo = derivations[derivationWithHistoryIndex];
} else if (derivationsWithHistory == 0) {
// default derivation:
derivationInfo = DerivationInfo(
derivationType: derivationTypes[0],
derivationPath: "m/0'/1",
height: 0,
);
}
if (derivationInfo == null) {
walletRestoreViewModel.state = InitialExecutionState();
return;
}
this.derivationType = derivationInfo.derivationType;
this.derivationPath = derivationInfo.derivationPath;
} else {
// electrum derivation:
this.derivationType = derivationTypes[0];
this.derivationPath = "m/0'/1";
} }
walletRestoreViewModel.state = InitialExecutionState(); if (derivationsWithHistory > 1) {
dInfo = await Navigator.of(context).pushNamed(
Routes.restoreWalletChooseDerivation,
arguments: derivations,
) as DerivationInfo?;
} else if (derivationsWithHistory == 1) {
dInfo = derivations[derivationWithHistoryIndex];
}
// get the default derivation for this wallet type:
if (dInfo == null) {
// we only return 1 derivation if we're pretty sure we know which one to use:
if (derivations.length == 1) {
dInfo = derivations.first;
} else {
// if we have multiple possible derivations, and none have histories
// we just default to the most common one:
dInfo = walletRestoreViewModel.getCommonRestoreDerivation();
}
}
this.derivationInfo = dInfo;
if (this.derivationInfo == null) {
this.derivationInfo = walletRestoreViewModel.getDefaultDerivation();
}
walletRestoreViewModel.create(options: _credentials()); walletRestoreViewModel.create(options: _credentials());
} }

View file

@ -100,9 +100,9 @@ class SendPage extends BasePage {
AppBarStyle get appBarStyle => AppBarStyle.transparent; AppBarStyle get appBarStyle => AppBarStyle.transparent;
double _sendCardHeight(BuildContext context) { double _sendCardHeight(BuildContext context) {
double initialHeight = 450; double initialHeight = 480;
if (sendViewModel.hasCoinControl) { if (sendViewModel.hasCoinControl) {
initialHeight += 35; initialHeight += 55;
} }
if (!responsiveLayoutUtil.shouldRenderMobileUI) { if (!responsiveLayoutUtil.shouldRenderMobileUI) {

View file

@ -675,6 +675,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
final selectedItem = items.indexOf(sendViewModel.transactionPriority); final selectedItem = items.indexOf(sendViewModel.transactionPriority);
final customItemIndex = sendViewModel.getCustomPriorityIndex(items); final customItemIndex = sendViewModel.getCustomPriorityIndex(items);
final isBitcoinWallet = sendViewModel.walletType == WalletType.bitcoin; final isBitcoinWallet = sendViewModel.walletType == WalletType.bitcoin;
final maxCustomFeeRate = sendViewModel.maxCustomFeeRate?.toDouble();
double? customFeeRate = isBitcoinWallet ? sendViewModel.customBitcoinFeeRate.toDouble() : null; double? customFeeRate = isBitcoinWallet ? sendViewModel.customBitcoinFeeRate.toDouble() : null;
await showPopUp<void>( await showPopUp<void>(
@ -689,6 +690,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
sendViewModel.displayFeeRate(priority, customFeeRate?.round()), sendViewModel.displayFeeRate(priority, customFeeRate?.round()),
selectedAtIndex: selectedIdx, selectedAtIndex: selectedIdx,
customItemIndex: customItemIndex, customItemIndex: customItemIndex,
maxValue: maxCustomFeeRate,
title: S.of(context).please_select, title: S.of(context).please_select,
headerEnabled: !isBitcoinWallet, headerEnabled: !isBitcoinWallet,
closeOnItemSelected: !isBitcoinWallet, closeOnItemSelected: !isBitcoinWallet,

View file

@ -37,6 +37,7 @@ class OtherSettingsPage extends BasePage {
customItemIndex: _otherSettingsViewModel.customPriorityItemIndex, customItemIndex: _otherSettingsViewModel.customPriorityItemIndex,
onItemSelected: _otherSettingsViewModel.onDisplayBitcoinPrioritySelected, onItemSelected: _otherSettingsViewModel.onDisplayBitcoinPrioritySelected,
customValue: _otherSettingsViewModel.customBitcoinFeeRate, customValue: _otherSettingsViewModel.customBitcoinFeeRate,
maxValue: _otherSettingsViewModel.maxCustomFeeRate?.toDouble(),
) : ) :
SettingsPickerCell( SettingsPickerCell(
title: S.current.settings_fee_priority, title: S.current.settings_fee_priority,

View file

@ -15,6 +15,7 @@ class SettingsPriorityPickerCell<ItemType> extends StandardListRow {
this.isGridView = false, this.isGridView = false,
this.matchingCriteria, this.matchingCriteria,
this.customValue, this.customValue,
this.maxValue,
this.customItemIndex, this.customItemIndex,
this.onItemSelected}) this.onItemSelected})
: super( : super(
@ -34,6 +35,7 @@ class SettingsPriorityPickerCell<ItemType> extends StandardListRow {
displayItem: (ItemType item) => displayItem!(item, sliderValue.round()), displayItem: (ItemType item) => displayItem!(item, sliderValue.round()),
selectedAtIndex: selectedAtIndex, selectedAtIndex: selectedAtIndex,
customItemIndex: customItemIndex, customItemIndex: customItemIndex,
maxValue: maxValue,
headerEnabled: false, headerEnabled: false,
closeOnItemSelected: false, closeOnItemSelected: false,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -61,6 +63,7 @@ class SettingsPriorityPickerCell<ItemType> extends StandardListRow {
final bool isGridView; final bool isGridView;
final bool Function(ItemType, String)? matchingCriteria; final bool Function(ItemType, String)? matchingCriteria;
double? customValue; double? customValue;
double? maxValue;
int? customItemIndex; int? customItemIndex;
@override @override

View file

@ -10,6 +10,7 @@ class StandardPickerListItem<T> extends TransactionDetailsListItem {
required this.onItemSelected, required this.onItemSelected,
required this.selectedIdx, required this.selectedIdx,
required this.customItemIndex, required this.customItemIndex,
this.maxValue,
required this.customValue}) required this.customValue})
: super(title: title, value: value); : super(title: title, value: value);
@ -18,6 +19,7 @@ class StandardPickerListItem<T> extends TransactionDetailsListItem {
final Function(double) onSliderChanged; final Function(double) onSliderChanged;
final Function(T) onItemSelected; final Function(T) onItemSelected;
final int selectedIdx; final int selectedIdx;
final double? maxValue;
final int customItemIndex; final int customItemIndex;
double customValue; double customValue;
} }

View file

@ -74,6 +74,7 @@ class RBFDetailsPage extends BasePage {
selectedIdx: item.selectedIdx, selectedIdx: item.selectedIdx,
customItemIndex: item.customItemIndex, customItemIndex: item.customItemIndex,
customValue: item.customValue, customValue: item.customValue,
maxValue: item.maxValue,
); );
} }

View file

@ -360,7 +360,9 @@ class WalletListBodyState extends State<WalletListBody> {
}); });
} }
} catch (e) { } catch (e) {
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString())); if (this.mounted) {
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
}
} }
}, },
conditionToDetermineIfToUse2FA: conditionToDetermineIfToUse2FA:

View file

@ -22,6 +22,7 @@ class BaseTextFormField extends StatelessWidget {
this.enabled = true, this.enabled = true,
this.readOnly = false, this.readOnly = false,
this.enableInteractiveSelection = true, this.enableInteractiveSelection = true,
this.obscureText = false,
this.validator, this.validator,
this.textStyle, this.textStyle,
this.placeholderTextStyle, this.placeholderTextStyle,
@ -72,6 +73,7 @@ class BaseTextFormField extends StatelessWidget {
textInputAction: textInputAction, textInputAction: textInputAction,
textAlign: textAlign, textAlign: textAlign,
autovalidateMode: autovalidateMode, autovalidateMode: autovalidateMode,
obscureText: obscureText,
maxLines: maxLines, maxLines: maxLines,
inputFormatters: inputFormatters, inputFormatters: inputFormatters,
enabled: enabled, enabled: enabled,

View file

@ -27,14 +27,21 @@ class Picker<Item> extends StatefulWidget {
this.headerEnabled = true, this.headerEnabled = true,
this.closeOnItemSelected = true, this.closeOnItemSelected = true,
this.sliderValue, this.sliderValue,
this.minValue,
this.maxValue,
this.customItemIndex, this.customItemIndex,
this.isWrapped = true, this.isWrapped = true,
this.borderColor, this.borderColor,
this.onSliderChanged, this.onSliderChanged,
this.matchingCriteria, this.matchingCriteria,
}) : assert(hintText == null || }) : assert(hintText == null || matchingCriteria != null) {
matchingCriteria != // make sure that if the search field is enabled then there is a searching criteria provided
null); // make sure that if the search field is enabled then there is a searching criteria provided if (sliderValue != null && maxValue != null) {
if (sliderValue! > maxValue!) {
sliderValue = maxValue;
}
}
}
final int selectedAtIndex; final int selectedAtIndex;
final List<Item> items; final List<Item> items;
@ -49,12 +56,14 @@ class Picker<Item> extends StatefulWidget {
final String? hintText; final String? hintText;
final bool headerEnabled; final bool headerEnabled;
final bool closeOnItemSelected; final bool closeOnItemSelected;
final double? sliderValue; double? sliderValue;
final double? minValue;
final int? customItemIndex; final int? customItemIndex;
final bool isWrapped; final bool isWrapped;
final Color? borderColor; final Color? borderColor;
final Function(double)? onSliderChanged; final Function(double)? onSliderChanged;
final bool Function(Item, String)? matchingCriteria; final bool Function(Item, String)? matchingCriteria;
final double? maxValue;
@override @override
_PickerState<Item> createState() => _PickerState<Item>(items, images, onItemSelected); _PickerState<Item> createState() => _PickerState<Item>(items, images, onItemSelected);
@ -138,7 +147,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
containerHeight = height * 0.75; containerHeight = height * 0.75;
} }
final content = Column ( final content = Column(
children: [ children: [
if (widget.title?.isNotEmpty ?? false) if (widget.title?.isNotEmpty ?? false)
Container( Container(
@ -211,8 +220,9 @@ class _PickerState<Item> extends State<Picker<Item>> {
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
fontFamily: 'Lato', fontFamily: 'Lato',
decoration: TextDecoration.none, decoration: TextDecoration.none,
color: color: Theme.of(context)
Theme.of(context).extension<CakeTextTheme>()!.titleColor, .extension<CakeTextTheme>()!
.titleColor,
), ),
), ),
) )
@ -491,8 +501,8 @@ class _PickerState<Item> extends State<Picker<Item>> {
child: Slider( child: Slider(
value: widget.sliderValue ?? 1, value: widget.sliderValue ?? 1,
onChanged: isActivated ? widget.onSliderChanged : null, onChanged: isActivated ? widget.onSliderChanged : null,
min: 1, min: widget.minValue ?? 1,
max: 100, max: widget.maxValue ?? 100,
divisions: 100, divisions: 100,
), ),
), ),

View file

@ -15,6 +15,7 @@ class StandardPickerList<T> extends StatefulWidget {
required this.selectedIdx, required this.selectedIdx,
required this.customItemIndex, required this.customItemIndex,
required this.customValue, required this.customValue,
this.maxValue,
}) : super(key: key); }) : super(key: key);
final String title; final String title;
@ -26,6 +27,7 @@ class StandardPickerList<T> extends StatefulWidget {
final String value; final String value;
final int selectedIdx; final int selectedIdx;
final double customValue; final double customValue;
final double? maxValue;
@override @override
_StandardPickerListState<T> createState() => _StandardPickerListState<T>(); _StandardPickerListState<T> createState() => _StandardPickerListState<T>();
@ -59,6 +61,7 @@ class _StandardPickerListState<T> extends State<StandardPickerList<T>> {
displayItem: adaptedDisplayItem, displayItem: adaptedDisplayItem,
selectedAtIndex: selectedIdx, selectedAtIndex: selectedIdx,
customItemIndex: widget.customItemIndex, customItemIndex: widget.customItemIndex,
maxValue: widget.maxValue,
headerEnabled: false, headerEnabled: false,
closeOnItemSelected: false, closeOnItemSelected: false,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,

View file

@ -167,4 +167,4 @@ class ValidatableAnnotatedEditableTextState extends EditableTextState {
return TextSpan(style: widget.style, text: text); return TextSpan(style: widget.style, text: text);
} }
} }

View file

@ -125,4 +125,4 @@ abstract class AuthViewModelBase with Store {
_authService.saveLastAuthTime(); _authService.saveLastAuthTime();
} }
} }
} }

View file

@ -3,8 +3,20 @@ import 'dart:collection';
import 'dart:convert'; import 'dart:convert';
import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/core/create_trade_result.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/preferences_key.dart';
@ -33,14 +45,6 @@ import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/store/templates/exchange_template_store.dart'; import 'package:cake_wallet/store/templates/exchange_template_store.dart';
import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart'; import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart';
import 'package:intl/intl.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'exchange_view_model.g.dart'; part 'exchange_view_model.g.dart';
@ -516,10 +520,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
trade.walletId = wallet.id; trade.walletId = wallet.id;
trade.fromWalletAddress = wallet.walletAddresses.address; trade.fromWalletAddress = wallet.walletAddresses.address;
if (!isCanCreateTrade(trade)) { final canCreateTrade = await isCanCreateTrade(trade);
if (!canCreateTrade.result) {
tradeState = TradeIsCreatedFailure( tradeState = TradeIsCreatedFailure(
title: S.current.trade_not_created, title: S.current.trade_not_created,
error: S.current.thorchain_taproot_address_not_supported); error: canCreateTrade.errorMessage ?? '',
);
return; return;
} }
@ -776,16 +782,100 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
int get receiveMaxDigits => receiveCurrency.decimals; int get receiveMaxDigits => receiveCurrency.decimals;
bool isCanCreateTrade(Trade trade) { Future<CreateTradeResult> isCanCreateTrade(Trade trade) async {
if (trade.provider == ExchangeProviderDescription.thorChain) { if (trade.provider == ExchangeProviderDescription.thorChain) {
final payoutAddress = trade.payoutAddress ?? ''; final payoutAddress = trade.payoutAddress ?? '';
final fromWalletAddress = trade.fromWalletAddress ?? ''; final fromWalletAddress = trade.fromWalletAddress ?? '';
final tapRootPattern = RegExp(P2trAddress.regex.pattern); final tapRootPattern = RegExp(P2trAddress.regex.pattern);
if (tapRootPattern.hasMatch(payoutAddress) || tapRootPattern.hasMatch(fromWalletAddress)) { if (tapRootPattern.hasMatch(payoutAddress) || tapRootPattern.hasMatch(fromWalletAddress)) {
return false; return CreateTradeResult(
result: false,
errorMessage: S.current.thorchain_taproot_address_not_supported,
);
}
final currenciesToCheckPattern = RegExp('0x[0-9a-zA-Z]');
// Perform checks for payOutAddress
final isPayOutAddressAccordingToPattern = currenciesToCheckPattern.hasMatch(payoutAddress);
if (isPayOutAddressAccordingToPattern) {
final isPayOutAddressEOA = await _isExternallyOwnedAccountAddress(payoutAddress);
return CreateTradeResult(
result: isPayOutAddressEOA,
errorMessage:
!isPayOutAddressEOA ? S.current.thorchain_contract_address_not_supported : null,
);
}
// Perform checks for fromWalletAddress
final isFromWalletAddressAddressAccordingToPattern =
currenciesToCheckPattern.hasMatch(fromWalletAddress);
if (isFromWalletAddressAddressAccordingToPattern) {
final isFromWalletAddressEOA = await _isExternallyOwnedAccountAddress(fromWalletAddress);
return CreateTradeResult(
result: isFromWalletAddressEOA,
errorMessage:
!isFromWalletAddressEOA ? S.current.thorchain_contract_address_not_supported : null,
);
} }
} }
return true; return CreateTradeResult(result: true);
}
String _normalizeReceiveCurrency(CryptoCurrency receiveCurrency) {
switch (receiveCurrency) {
case CryptoCurrency.eth:
return 'eth';
case CryptoCurrency.maticpoly:
return 'polygon';
default:
return receiveCurrency.tag ?? '';
}
}
Future<bool> _isExternallyOwnedAccountAddress(String receivingAddress) async {
final normalizedReceiveCurrency = _normalizeReceiveCurrency(receiveCurrency);
final isEOAAddress = !(await _isContractAddress(normalizedReceiveCurrency, receivingAddress));
return isEOAAddress;
}
Future<bool> _isContractAddress(String chainName, String contractAddress) async {
final httpClient = http.Client();
final uri = Uri.https(
'deep-index.moralis.io',
'/api/v2.2/erc20/metadata',
{
"chain": chainName,
"addresses": contractAddress,
},
);
try {
final response = await httpClient.get(
uri,
headers: {
"Accept": "application/json",
"X-API-Key": secrets.moralisApiKey,
},
);
final decodedResponse = jsonDecode(response.body)[0] as Map<String, dynamic>;
final name = decodedResponse['name'] as String?;
bool isContractAddress = name!.isNotEmpty;
return isContractAddress;
} catch (e) {
print(e);
return false;
}
} }
} }

View file

@ -69,6 +69,24 @@ abstract class NodeCreateOrEditViewModelBase with Store {
bool get hasTestnetSupport => _walletType == WalletType.bitcoin; bool get hasTestnetSupport => _walletType == WalletType.bitcoin;
bool get hasPathSupport {
switch (_walletType) {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.solana:
case WalletType.banano:
case WalletType.nano:
return true;
case WalletType.none:
case WalletType.monero:
case WalletType.haven:
case WalletType.litecoin:
case WalletType.bitcoinCash:
case WalletType.bitcoin:
return false;
}
}
String get uri { String get uri {
var uri = address; var uri = address;
@ -217,7 +235,6 @@ abstract class NodeCreateOrEditViewModelBase with Store {
final port = uri.port.toString(); final port = uri.port.toString();
final path = uri.path; final path = uri.path;
setAddress(ipAddress); setAddress(ipAddress);
setPath(path); setPath(path);
setPassword(rpcPassword); setPassword(rpcPassword);

View file

@ -30,8 +30,7 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
spendKey = '', spendKey = '',
wif = '', wif = '',
address = '', address = '',
super(appStore, walletInfoSource, walletCreationService, super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true);
type: type, isRecovery: true);
@observable @observable
int height; int height;
@ -51,8 +50,16 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
bool get hasRestorationHeight => type == WalletType.monero; bool get hasRestorationHeight => type == WalletType.monero;
@override @override
WalletCredentials getCredentialsFromRestoredWallet(dynamic options, RestoredWallet restoreWallet) { WalletCredentials getCredentialsFromRestoredWallet(
dynamic options, RestoredWallet restoreWallet) {
final password = generateWalletPassword(); final password = generateWalletPassword();
String? passphrase;
DerivationInfo? derivationInfo;
if (options != null) {
derivationInfo = options["derivationInfo"] as DerivationInfo?;
passphrase = options["passphrase"] as String?;
}
derivationInfo ??= getDefaultDerivation();
switch (restoreWallet.restoreMode) { switch (restoreWallet.restoreMode) {
case WalletRestoreMode.keys: case WalletRestoreMode.keys:
@ -86,23 +93,37 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
switch (restoreWallet.type) { switch (restoreWallet.type) {
case WalletType.monero: case WalletType.monero:
return monero!.createMoneroRestoreWalletFromSeedCredentials( return monero!.createMoneroRestoreWalletFromSeedCredentials(
name: name, name: name,
height: restoreWallet.height ?? 0, height: restoreWallet.height ?? 0,
mnemonic: restoreWallet.mnemonicSeed ?? '', mnemonic: restoreWallet.mnemonicSeed ?? '',
password: password); password: password,
);
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin: case WalletType.litecoin:
return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials( return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password); name: name,
mnemonic: restoreWallet.mnemonicSeed ?? '',
password: password,
passphrase: passphrase,
derivationType: derivationInfo!.derivationType!,
derivationPath: derivationInfo.derivationPath!,
);
case WalletType.bitcoinCash: case WalletType.bitcoinCash:
return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials( return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials(
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password); name: name,
mnemonic: restoreWallet.mnemonicSeed ?? '',
password: password,
);
case WalletType.ethereum: case WalletType.ethereum:
return ethereum!.createEthereumRestoreWalletFromSeedCredentials( return ethereum!.createEthereumRestoreWalletFromSeedCredentials(
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password); name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
case WalletType.nano: case WalletType.nano:
return nano!.createNanoRestoreWalletFromSeedCredentials( return nano!.createNanoRestoreWalletFromSeedCredentials(
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password); name: name,
mnemonic: restoreWallet.mnemonicSeed ?? '',
password: password,
derivationType: derivationInfo!.derivationType!,
);
case WalletType.polygon: case WalletType.polygon:
return polygon!.createPolygonRestoreWalletFromSeedCredentials( return polygon!.createPolygonRestoreWalletFromSeedCredentials(
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password); name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
@ -118,7 +139,8 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
} }
@override @override
Future<WalletBase> processFromRestoredWallet(WalletCredentials credentials, RestoredWallet restoreWallet) async { Future<WalletBase> processFromRestoredWallet(
WalletCredentials credentials, RestoredWallet restoreWallet) async {
try { try {
switch (restoreWallet.restoreMode) { switch (restoreWallet.restoreMode) {
case WalletRestoreMode.keys: case WalletRestoreMode.keys:

View file

@ -126,8 +126,8 @@ abstract class OutputBase with Store {
if (_wallet.type == WalletType.bitcoin) { if (_wallet.type == WalletType.bitcoin) {
if (_settingsStore.priority[_wallet.type] == bitcoin!.getBitcoinTransactionPriorityCustom()) { if (_settingsStore.priority[_wallet.type] == bitcoin!.getBitcoinTransactionPriorityCustom()) {
fee = bitcoin!.getFeeAmountWithFeeRate( fee = bitcoin!.getEstimatedFeeWithFeeRate(_wallet,
_settingsStore.customBitcoinFeeRate, formattedCryptoAmount, 1, 1); _settingsStore.customBitcoinFeeRate,formattedCryptoAmount);
} }
return bitcoin!.formatterBitcoinAmountToDouble(amount: fee); return bitcoin!.formatterBitcoinAmountToDouble(amount: fee);

View file

@ -166,6 +166,13 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
return null; return null;
} }
int? get maxCustomFeeRate {
if (wallet.type == WalletType.bitcoin) {
return bitcoin!.getMaxCustomFeeRate(wallet);
}
return null;
}
@computed @computed
int get customBitcoinFeeRate => _settingsStore.customBitcoinFeeRate; int get customBitcoinFeeRate => _settingsStore.customBitcoinFeeRate;
@ -324,14 +331,16 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
Future<PendingTransaction?> createTransaction({ExchangeProvider? provider}) async { Future<PendingTransaction?> createTransaction({ExchangeProvider? provider}) async {
try { try {
state = IsExecutingState(); state = IsExecutingState();
pendingTransaction = await wallet.createTransaction(_credentials()); pendingTransaction = await wallet.createTransaction(_credentials());
if (provider is ThorChainExchangeProvider) { if (provider is ThorChainExchangeProvider) {
final outputCount = pendingTransaction?.outputCount ?? 0; final outputCount = pendingTransaction?.outputCount ?? 0;
if (outputCount > 10) { if (outputCount > 10) {
throw Exception("ThorChain does not support more than 10 outputs"); throw Exception("THORChain does not support more than 10 outputs");
} }
if (_hasTaprootInput(pendingTransaction)) { if (_hasTaprootInput(pendingTransaction)) {
throw Exception("ThorChain does not support Taproot addresses"); throw Exception("THORChain does not support Taproot addresses");
} }
} }
state = ExecutedSuccessfullyState(); state = ExecutedSuccessfullyState();
@ -414,7 +423,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
Object _credentials() { Object _credentials() {
final priority = _settingsStore.priority[wallet.type]; final priority = _settingsStore.priority[wallet.type];
if (priority == null && wallet.type != WalletType.nano && wallet.type != WalletType.solana) { if (priority == null && wallet.type != WalletType.nano && wallet.type != WalletType.banano && wallet.type != WalletType.solana) {
throw Exception('Priority is null for wallet type: ${wallet.type}'); throw Exception('Priority is null for wallet type: ${wallet.type}');
} }
@ -553,7 +562,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
return S.current.tx_no_dust_exception; return S.current.tx_no_dust_exception;
} }
if (error is TransactionCommitFailed) { if (error is TransactionCommitFailed) {
return S.current.tx_commit_failed; return "${S.current.tx_commit_failed}${error.errorMessage != null ? "\n\n${error.errorMessage}" : ""}";
} }
if (error is TransactionCommitFailedDustChange) { if (error is TransactionCommitFailedDustChange) {
return S.current.tx_rejected_dust_change; return S.current.tx_rejected_dust_change;

View file

@ -140,6 +140,13 @@ abstract class OtherSettingsViewModelBase with Store {
return customItem != null ? priorities.indexOf(customItem) : null; return customItem != null ? priorities.indexOf(customItem) : null;
} }
int? get maxCustomFeeRate {
if (_wallet.type == WalletType.bitcoin) {
return bitcoin!.getMaxCustomFeeRate(_wallet);
}
return null;
}
@action @action
ProviderType onBuyProviderTypeSelected(ProviderType buyProviderType) => ProviderType onBuyProviderTypeSelected(ProviderType buyProviderType) =>
_settingsStore.defaultBuyProviders[walletType] = buyProviderType; _settingsStore.defaultBuyProviders[walletType] = buyProviderType;

View file

@ -348,12 +348,14 @@ abstract class TransactionDetailsViewModelBase with Store {
final customItem = priorities.firstWhereOrNull( final customItem = priorities.firstWhereOrNull(
(element) => element == sendViewModel.bitcoinTransactionPriorityCustom); (element) => element == sendViewModel.bitcoinTransactionPriorityCustom);
final customItemIndex = customItem != null ? priorities.indexOf(customItem) : null; final customItemIndex = customItem != null ? priorities.indexOf(customItem) : null;
final maxCustomFeeRate = sendViewModel.maxCustomFeeRate?.toDouble();
RBFListItems.add(StandardPickerListItem( RBFListItems.add(StandardPickerListItem(
title: S.current.estimated_new_fee, title: S.current.estimated_new_fee,
value: bitcoin!.formatterBitcoinAmountToString(amount: newFee) + ' ${walletTypeToCryptoCurrency(wallet.type)}', value: bitcoin!.formatterBitcoinAmountToString(amount: newFee) + ' ${walletTypeToCryptoCurrency(wallet.type)}',
items: priorityForWalletType(wallet.type), items: priorityForWalletType(wallet.type),
customValue: settingsStore.customBitcoinFeeRate.toDouble(), customValue: settingsStore.customBitcoinFeeRate.toDouble(),
maxValue: maxCustomFeeRate,
selectedIdx: selectedItem, selectedIdx: selectedItem,
customItemIndex: customItemIndex ?? 0, customItemIndex: customItemIndex ?? 0,
displayItem: (dynamic priority, double sliderValue) => displayItem: (dynamic priority, double sliderValue) =>
@ -388,16 +390,12 @@ abstract class TransactionDetailsViewModelBase with Store {
String setNewFee({double? value, required TransactionPriority priority}) { String setNewFee({double? value, required TransactionPriority priority}) {
newFee = priority == bitcoin!.getBitcoinTransactionPriorityCustom() && value != null newFee = priority == bitcoin!.getBitcoinTransactionPriorityCustom() && value != null
? bitcoin!.getFeeAmountWithFeeRate( ? bitcoin!.getEstimatedFeeWithFeeRate(wallet, value.round(), transactionInfo.amount)
wallet,
value.round(),
transactionInfo.inputAddresses?.length ?? 1,
transactionInfo.outputAddresses?.length ?? 1)
: bitcoin!.getFeeAmountForPriority( : bitcoin!.getFeeAmountForPriority(
wallet, wallet,
priority, priority,
transactionInfo.inputAddresses?.length ?? 1, transactionInfo.inputAddresses?.length ?? 1,
transactionInfo.outputAddresses?.length ?? 1); transactionInfo.outputAddresses?.length ?? 1);
return bitcoin!.formatterBitcoinAmountToString(amount: newFee); return bitcoin!.formatterBitcoinAmountToString(amount: newFee);
} }

View file

@ -89,9 +89,9 @@ abstract class WalletCreationVMBase with Store {
dirPath: dirPath, dirPath: dirPath,
address: '', address: '',
showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven, showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven,
derivationPath: credentials.derivationPath, derivationInfo: credentials.derivationInfo ?? getDefaultDerivation(),
derivationType: credentials.derivationType,
); );
credentials.walletInfo = walletInfo; credentials.walletInfo = walletInfo;
final wallet = restoreWallet != null final wallet = restoreWallet != null
? await processFromRestoredWallet(credentials, restoreWallet) ? await processFromRestoredWallet(credentials, restoreWallet)
@ -107,6 +107,48 @@ abstract class WalletCreationVMBase with Store {
} }
} }
DerivationInfo? getDefaultDerivation() {
switch (this.type) {
case WalletType.nano:
return DerivationInfo(
derivationType: DerivationType.nano,
);
case WalletType.bitcoin:
case WalletType.litecoin:
return DerivationInfo(
derivationType: DerivationType.electrum,
derivationPath: "m/0'/0",
);
default:
return null;
}
}
DerivationInfo? getCommonRestoreDerivation() {
switch (this.type) {
case WalletType.nano:
return DerivationInfo(
derivationType: DerivationType.nano,
);
case WalletType.bitcoin:
return DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/84'/0'/0'/0",
description: "Standard BIP84 native segwit",
scriptType: "p2wpkh",
);
case WalletType.litecoin:
return DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/84'/2'/0'/0",
description: "Standard BIP84 native segwit (litecoin)",
scriptType: "p2wpkh",
);
default:
return null;
}
}
WalletCredentials getCredentials(dynamic options) => throw UnimplementedError(); WalletCredentials getCredentials(dynamic options) => throw UnimplementedError();
Future<WalletBase> process(WalletCredentials credentials) => throw UnimplementedError(); Future<WalletBase> process(WalletCredentials credentials) => throw UnimplementedError();

View file

@ -2,6 +2,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cw_core/nano_account_info_response.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/solana/solana.dart';
@ -66,6 +67,8 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
final bool hasBlockchainHeightLanguageSelector; final bool hasBlockchainHeightLanguageSelector;
final bool hasRestoreFromPrivateKey; final bool hasRestoreFromPrivateKey;
bool get hasPassphrase => [WalletType.bitcoin, WalletType.litecoin].contains(type);
@observable @observable
WalletRestoreMode mode; WalletRestoreMode mode;
@ -75,10 +78,10 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
@override @override
WalletCredentials getCredentials(dynamic options) { WalletCredentials getCredentials(dynamic options) {
final password = walletPassword ?? generateWalletPassword(); final password = walletPassword ?? generateWalletPassword();
String? passphrase = options['passphrase'] as String?;
final height = options['height'] as int? ?? 0; final height = options['height'] as int? ?? 0;
name = options['name'] as String; name = options['name'] as String;
DerivationType? derivationType = options["derivationType"] as DerivationType?; DerivationInfo? derivationInfo = options["derivationInfo"] as DerivationInfo?;
String? derivationPath = options["derivationPath"] as String?;
if (mode == WalletRestoreMode.seed) { if (mode == WalletRestoreMode.seed) {
final seed = options['seed'] as String; final seed = options['seed'] as String;
@ -87,14 +90,15 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
return monero!.createMoneroRestoreWalletFromSeedCredentials( return monero!.createMoneroRestoreWalletFromSeedCredentials(
name: name, height: height, mnemonic: seed, password: password); name: name, height: height, mnemonic: seed, password: password);
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin:
return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials( return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
name: name, name: name,
mnemonic: seed, mnemonic: seed,
password: password, password: password,
passphrase: passphrase,
derivationType: derivationInfo!.derivationType!,
derivationPath: derivationInfo.derivationPath!,
); );
case WalletType.litecoin:
return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
name: name, mnemonic: seed, password: password);
case WalletType.haven: case WalletType.haven:
return haven!.createHavenRestoreWalletFromSeedCredentials( return haven!.createHavenRestoreWalletFromSeedCredentials(
name: name, height: height, mnemonic: seed, password: password); name: name, height: height, mnemonic: seed, password: password);
@ -106,7 +110,11 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
name: name, mnemonic: seed, password: password); name: name, mnemonic: seed, password: password);
case WalletType.nano: case WalletType.nano:
return nano!.createNanoRestoreWalletFromSeedCredentials( return nano!.createNanoRestoreWalletFromSeedCredentials(
name: name, mnemonic: seed, password: password, derivationType: derivationType); name: name,
mnemonic: seed,
password: password,
derivationType: derivationInfo!.derivationType!,
);
case WalletType.polygon: case WalletType.polygon:
return polygon!.createPolygonRestoreWalletFromSeedCredentials( return polygon!.createPolygonRestoreWalletFromSeedCredentials(
name: name, name: name,
@ -185,23 +193,34 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
throw Exception('Unexpected type: ${type.toString()}'); throw Exception('Unexpected type: ${type.toString()}');
} }
Future<List<DerivationType>> getDerivationTypes(dynamic options) async { Future<List<DerivationInfo>> getDerivationInfo(dynamic credentials) async {
final seedKey = options['private_key'] as String?; var list = <DerivationInfo>[];
final mnemonic = options['seed'] as String?; var walletType = credentials["walletType"] as WalletType;
WalletType walletType = options['walletType'] as WalletType;
var appStore = getIt.get<AppStore>(); var appStore = getIt.get<AppStore>();
var node = appStore.settingsStore.getCurrentNode(walletType); var node = appStore.settingsStore.getCurrentNode(walletType);
switch (type) { switch (walletType) {
case WalletType.bitcoin:
case WalletType.litecoin:
String? mnemonic = credentials['seed'] as String?;
String? passphrase = credentials['passphrase'] as String?;
return bitcoin!.getDerivationsFromMnemonic(
mnemonic: mnemonic!,
node: node,
passphrase: passphrase,
);
case WalletType.nano: case WalletType.nano:
return nanoUtil! String? mnemonic = credentials['seed'] as String?;
.compareDerivationMethods(mnemonic: mnemonic, privateKey: seedKey, node: node); String? seedKey = credentials['private_key'] as String?;
return nanoUtil!.getDerivationsFromMnemonic(
mnemonic: mnemonic,
seedKey: seedKey,
node: node,
);
default: default:
break; break;
} }
return list;
// throw Exception('Unexpected type: ${type.toString()}');
return [DerivationType.def];
} }
@override @override
@ -209,7 +228,6 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
if (mode == WalletRestoreMode.keys) { if (mode == WalletRestoreMode.keys) {
return walletCreationService.restoreFromKeys(credentials, isTestnet: useTestnet); return walletCreationService.restoreFromKeys(credentials, isTestnet: useTestnet);
} }
return walletCreationService.restoreFromSeed(credentials, isTestnet: useTestnet); return walletCreationService.restoreFromSeed(credentials, isTestnet: useTestnet);
} }
} }

View file

@ -413,6 +413,7 @@
"outputs": "المخرجات", "outputs": "المخرجات",
"overwrite_amount": "تغير المبلغ", "overwrite_amount": "تغير المبلغ",
"pairingInvalidEvent": "ﺢﻟﺎﺻ ﺮﻴﻏ ﺙﺪﺣ ﻥﺍﺮﻗﺇ", "pairingInvalidEvent": "ﺢﻟﺎﺻ ﺮﻴﻏ ﺙﺪﺣ ﻥﺍﺮﻗﺇ",
"passphrase": "عبارة الممر (اختياري)",
"password": "كلمة المرور", "password": "كلمة المرور",
"paste": "لصق", "paste": "لصق",
"pause_wallet_creation": ".ﺎﻴًﻟﺎﺣ ﺎﺘًﻗﺆﻣ ﺔﻔﻗﻮﺘﻣ Haven Wallet ءﺎﺸﻧﺇ ﻰﻠﻋ ﺓﺭﺪﻘﻟﺍ", "pause_wallet_creation": ".ﺎﻴًﻟﺎﺣ ﺎﺘًﻗﺆﻣ ﺔﻔﻗﻮﺘﻣ Haven Wallet ءﺎﺸﻧﺇ ﻰﻠﻋ ﺓﺭﺪﻘﻟﺍ",
@ -664,6 +665,7 @@
"template_name": "اسم القالب", "template_name": "اسم القالب",
"third_intro_content": "يعيش Yats خارج Cake Wallet أيضًا. يمكن استبدال أي عنوان محفظة على وجه الأرض بـ Yat!", "third_intro_content": "يعيش Yats خارج Cake Wallet أيضًا. يمكن استبدال أي عنوان محفظة على وجه الأرض بـ Yat!",
"third_intro_title": "يتماشي Yat بلطف مع الآخرين", "third_intro_title": "يتماشي Yat بلطف مع الآخرين",
"thorchain_contract_address_not_supported": "لا يدعم Thorchain الإرسال إلى عنوان العقد",
"thorchain_taproot_address_not_supported": "لا يدعم مزود Thorchain عناوين Taproot. يرجى تغيير العنوان أو تحديد مزود مختلف.", "thorchain_taproot_address_not_supported": "لا يدعم مزود Thorchain عناوين Taproot. يرجى تغيير العنوان أو تحديد مزود مختلف.",
"time": "${minutes}د ${seconds}س", "time": "${minutes}د ${seconds}س",
"tip": "بقشيش:", "tip": "بقشيش:",

View file

@ -413,6 +413,7 @@
"outputs": "Изходи", "outputs": "Изходи",
"overwrite_amount": "Промени сума", "overwrite_amount": "Промени сума",
"pairingInvalidEvent": "Невалидно събитие при сдвояване", "pairingInvalidEvent": "Невалидно събитие при сдвояване",
"passphrase": "Passphrase (по избор)",
"password": "Парола", "password": "Парола",
"paste": "Поставяне", "paste": "Поставяне",
"pause_wallet_creation": "Възможността за създаване на Haven Wallet в момента е на пауза.", "pause_wallet_creation": "Възможността за създаване на Haven Wallet в момента е на пауза.",
@ -664,6 +665,7 @@
"template_name": "Име на шаблон", "template_name": "Име на шаблон",
"third_intro_content": "Yats също живее извън Cake Wallet. Всеки адрес на портфейл може да бъде заменен с Yat!", "third_intro_content": "Yats също живее извън Cake Wallet. Всеки адрес на портфейл може да бъде заменен с Yat!",
"third_intro_title": "Yat добре се сработва с други", "third_intro_title": "Yat добре се сработва с други",
"thorchain_contract_address_not_supported": "Thorchain не подкрепя изпращането до адрес на договор",
"thorchain_taproot_address_not_supported": "Доставчикът на Thorchain не поддържа адреси на TapRoot. Моля, променете адреса или изберете друг доставчик.", "thorchain_taproot_address_not_supported": "Доставчикът на Thorchain не поддържа адреси на TapRoot. Моля, променете адреса или изберете друг доставчик.",
"time": "${minutes} мин ${seconds} сек", "time": "${minutes} мин ${seconds} сек",
"tip": "Tip:", "tip": "Tip:",

View file

@ -413,6 +413,7 @@
"outputs": "Výstupy", "outputs": "Výstupy",
"overwrite_amount": "Přepsat částku", "overwrite_amount": "Přepsat částku",
"pairingInvalidEvent": "Neplatná událost párování", "pairingInvalidEvent": "Neplatná událost párování",
"passphrase": "Passphrase (volitelné)",
"password": "Heslo", "password": "Heslo",
"paste": "Vložit", "paste": "Vložit",
"pause_wallet_creation": "Možnost vytvářet Haven Wallet je momentálně pozastavena.", "pause_wallet_creation": "Možnost vytvářet Haven Wallet je momentálně pozastavena.",
@ -664,6 +665,7 @@
"template_name": "Název šablony", "template_name": "Název šablony",
"third_intro_content": "Yat existuje i mimo Cake Wallet. Jakákoliv adresa peněženky na světě může být nahrazena Yatem!", "third_intro_content": "Yat existuje i mimo Cake Wallet. Jakákoliv adresa peněženky na světě může být nahrazena Yatem!",
"third_intro_title": "Yat dobře spolupracuje s ostatními", "third_intro_title": "Yat dobře spolupracuje s ostatními",
"thorchain_contract_address_not_supported": "Thorchain nepodporuje odeslání na adresu smlouvy",
"thorchain_taproot_address_not_supported": "Poskytovatel Thorchain nepodporuje adresy Taproot. Změňte adresu nebo vyberte jiného poskytovatele.", "thorchain_taproot_address_not_supported": "Poskytovatel Thorchain nepodporuje adresy Taproot. Změňte adresu nebo vyberte jiného poskytovatele.",
"time": "${minutes}m ${seconds}s", "time": "${minutes}m ${seconds}s",
"tip": "Spropitné:", "tip": "Spropitné:",

View file

@ -50,7 +50,7 @@
"anonpay_description": "Generieren Sie ${type}. Der Empfänger kann ${method} mit jeder unterstützten Kryptowährung verwenden, und Sie erhalten Geld in dieser Wallet.", "anonpay_description": "Generieren Sie ${type}. Der Empfänger kann ${method} mit jeder unterstützten Kryptowährung verwenden, und Sie erhalten Geld in dieser Wallet.",
"apk_update": "APK-Update", "apk_update": "APK-Update",
"approve": "Genehmigen", "approve": "Genehmigen",
"arrive_in_this_address": "${currency} ${tag}wird an dieser Adresse ankommen", "arrive_in_this_address": "${currency} ${tag} wird an dieser Adresse ankommen",
"ascending": "Aufsteigend", "ascending": "Aufsteigend",
"ask_each_time": "Jedes Mal fragen", "ask_each_time": "Jedes Mal fragen",
"auth_store_ban_timeout": "ban_timeout", "auth_store_ban_timeout": "ban_timeout",
@ -87,7 +87,7 @@
"buy_provider_unavailable": "Anbieter derzeit nicht verfügbar.", "buy_provider_unavailable": "Anbieter derzeit nicht verfügbar.",
"buy_with": "Kaufen mit", "buy_with": "Kaufen mit",
"by_cake_pay": "von Cake Pay", "by_cake_pay": "von Cake Pay",
"cake_2fa_preset": "Kuchen 2FA-Voreinstellung", "cake_2fa_preset": "Cake 2FA-Voreinstellung",
"cake_dark_theme": "Cake Dark Thema", "cake_dark_theme": "Cake Dark Thema",
"cake_pay_account_note": "Melden Sie sich nur mit einer E-Mail-Adresse an, um Karten anzuzeigen und zu kaufen. Einige sind sogar mit Rabatt erhältlich!", "cake_pay_account_note": "Melden Sie sich nur mit einer E-Mail-Adresse an, um Karten anzuzeigen und zu kaufen. Einige sind sogar mit Rabatt erhältlich!",
"cake_pay_learn_more": "Kaufen und lösen Sie Geschenkkarten sofort in der App ein!\nWischen Sie von links nach rechts, um mehr zu erfahren.", "cake_pay_learn_more": "Kaufen und lösen Sie Geschenkkarten sofort in der App ein!\nWischen Sie von links nach rechts, um mehr zu erfahren.",
@ -120,7 +120,7 @@
"change_wallet_alert_title": "Aktuelle Wallet ändern", "change_wallet_alert_title": "Aktuelle Wallet ändern",
"choose_account": "Konto auswählen", "choose_account": "Konto auswählen",
"choose_address": "\n\nBitte wählen Sie die Adresse:", "choose_address": "\n\nBitte wählen Sie die Adresse:",
"choose_derivation": "Wählen Sie Brieftaschenableitung", "choose_derivation": "Wählen Sie Wallet-Ableitung",
"choose_from_available_options": "Wähle aus verfügbaren Optionen:", "choose_from_available_options": "Wähle aus verfügbaren Optionen:",
"choose_one": "Wähle ein", "choose_one": "Wähle ein",
"choose_relay": "Bitte wählen Sie ein zu verwendendes Relais aus", "choose_relay": "Bitte wählen Sie ein zu verwendendes Relais aus",
@ -199,7 +199,7 @@
"disable_fiat": "Fiat deaktivieren", "disable_fiat": "Fiat deaktivieren",
"disable_sell": "Verkaufsaktion deaktivieren", "disable_sell": "Verkaufsaktion deaktivieren",
"disableBatteryOptimization": "Batterieoptimierung deaktivieren", "disableBatteryOptimization": "Batterieoptimierung deaktivieren",
"disableBatteryOptimizationDescription": "Möchten Sie die Batterieoptimierung deaktivieren, um die Hintergrundsynchronisierung freier und reibungsloser zu gestalten?", "disableBatteryOptimizationDescription": "Möchten Sie die Batterieoptimierung deaktivieren, um die Hintergrundsynchronisierung reibungsloser zu gestalten?",
"disabled": "Deaktiviert", "disabled": "Deaktiviert",
"discount": "${value} % sparen", "discount": "${value} % sparen",
"display_settings": "Anzeigeeinstellungen", "display_settings": "Anzeigeeinstellungen",
@ -413,6 +413,7 @@
"outputs": "Ausgänge", "outputs": "Ausgänge",
"overwrite_amount": "Overwrite amount", "overwrite_amount": "Overwrite amount",
"pairingInvalidEvent": "Paarung ungültiges Ereignis", "pairingInvalidEvent": "Paarung ungültiges Ereignis",
"passphrase": "Passphrase (optional)",
"password": "Passwort", "password": "Passwort",
"paste": "Einfügen", "paste": "Einfügen",
"pause_wallet_creation": "Die Möglichkeit, Haven Wallet zu erstellen, ist derzeit pausiert.", "pause_wallet_creation": "Die Möglichkeit, Haven Wallet zu erstellen, ist derzeit pausiert.",
@ -427,8 +428,8 @@
"placeholder_transactions": "Ihre Transaktionen werden hier angezeigt", "placeholder_transactions": "Ihre Transaktionen werden hier angezeigt",
"please_fill_totp": "Bitte geben Sie den 8-stelligen Code ein, der auf Ihrem anderen Gerät vorhanden ist", "please_fill_totp": "Bitte geben Sie den 8-stelligen Code ein, der auf Ihrem anderen Gerät vorhanden ist",
"please_make_selection": "Bitte treffen Sie unten eine Auswahl zum Erstellen oder Wiederherstellen Ihrer Wallet.", "please_make_selection": "Bitte treffen Sie unten eine Auswahl zum Erstellen oder Wiederherstellen Ihrer Wallet.",
"Please_reference_document": "Weitere Informationen finden Sie in den Dokumenten unten.",
"please_reference_document": "Bitte verweisen Sie auf die folgenden Dokumente, um weitere Informationen zu erhalten.", "please_reference_document": "Bitte verweisen Sie auf die folgenden Dokumente, um weitere Informationen zu erhalten.",
"Please_reference_document": "Weitere Informationen finden Sie in den Dokumenten unten.",
"please_select": "Bitte auswählen:", "please_select": "Bitte auswählen:",
"please_select_backup_file": "Bitte wählen Sie die Sicherungsdatei und geben Sie das Sicherungskennwort ein.", "please_select_backup_file": "Bitte wählen Sie die Sicherungsdatei und geben Sie das Sicherungskennwort ein.",
"please_try_to_connect_to_another_node": "Bitte versuchen Sie, sich mit einem anderen Knoten zu verbinden", "please_try_to_connect_to_another_node": "Bitte versuchen Sie, sich mit einem anderen Knoten zu verbinden",
@ -461,8 +462,8 @@
"reconnect_alert_text": "Sind Sie sicher, dass Sie sich neu verbinden möchten?", "reconnect_alert_text": "Sind Sie sicher, dass Sie sich neu verbinden möchten?",
"reconnection": "Neu verbinden", "reconnection": "Neu verbinden",
"red_dark_theme": "Red Dark Thema", "red_dark_theme": "Red Dark Thema",
"red_light_theme": "Rotlichtthema", "red_light_theme": "Red Light Thema",
"redeemed": "Versilbert", "redeemed": "Eingelöst",
"refund_address": "Rückerstattungsadresse", "refund_address": "Rückerstattungsadresse",
"reject": "Ablehnen", "reject": "Ablehnen",
"remaining": "Rest", "remaining": "Rest",
@ -536,7 +537,7 @@
"seed_alert_title": "Achtung", "seed_alert_title": "Achtung",
"seed_alert_yes": "Ja, habe ich", "seed_alert_yes": "Ja, habe ich",
"seed_choose": "Seed-Sprache auswählen", "seed_choose": "Seed-Sprache auswählen",
"seed_hex_form": "Brieftaschensamen (Sechskantform)", "seed_hex_form": "Seed (Hexformat)",
"seed_key": "Seed-Schlüssel", "seed_key": "Seed-Schlüssel",
"seed_language": "Seed-Sprache", "seed_language": "Seed-Sprache",
"seed_language_chinese": "Chinesisch", "seed_language_chinese": "Chinesisch",
@ -588,7 +589,7 @@
"send_your_wallet": "Ihre Wallet", "send_your_wallet": "Ihre Wallet",
"sending": "Senden", "sending": "Senden",
"sent": "Versendet", "sent": "Versendet",
"service_health_disabled": "Service Health Bulletin ist behindert", "service_health_disabled": "Service Health Bulletin ist deaktiviert",
"service_health_disabled_message": "Dies ist die Seite \"Service Health Bulletin\", können Sie diese Seite unter Einstellungen -> Privatsphäre aktivieren", "service_health_disabled_message": "Dies ist die Seite \"Service Health Bulletin\", können Sie diese Seite unter Einstellungen -> Privatsphäre aktivieren",
"settings": "Einstellungen", "settings": "Einstellungen",
"settings_all": "ALLE", "settings_all": "ALLE",
@ -665,6 +666,7 @@
"template_name": "Vorlagenname", "template_name": "Vorlagenname",
"third_intro_content": "Yats leben auch außerhalb von Cake Wallet. Jede Wallet-Adresse auf der Welt kann durch ein Yat ersetzt werden!", "third_intro_content": "Yats leben auch außerhalb von Cake Wallet. Jede Wallet-Adresse auf der Welt kann durch ein Yat ersetzt werden!",
"third_intro_title": "Yat spielt gut mit anderen", "third_intro_title": "Yat spielt gut mit anderen",
"thorchain_contract_address_not_supported": "Thorchain unterstützt das Senden an eine Vertragsadresse nicht",
"thorchain_taproot_address_not_supported": "Der Thorchain -Anbieter unterstützt keine Taproot -Adressen. Bitte ändern Sie die Adresse oder wählen Sie einen anderen Anbieter aus.", "thorchain_taproot_address_not_supported": "Der Thorchain -Anbieter unterstützt keine Taproot -Adressen. Bitte ändern Sie die Adresse oder wählen Sie einen anderen Anbieter aus.",
"time": "${minutes}m ${seconds}s", "time": "${minutes}m ${seconds}s",
"tip": "Hinweis:", "tip": "Hinweis:",

View file

@ -413,6 +413,7 @@
"outputs": "Outputs", "outputs": "Outputs",
"overwrite_amount": "Overwrite amount", "overwrite_amount": "Overwrite amount",
"pairingInvalidEvent": "Pairing Invalid Event", "pairingInvalidEvent": "Pairing Invalid Event",
"passphrase": "Passphrase (Optional)",
"password": "Password", "password": "Password",
"paste": "Paste", "paste": "Paste",
"pause_wallet_creation": "Ability to create Haven Wallet is currently paused.", "pause_wallet_creation": "Ability to create Haven Wallet is currently paused.",
@ -664,6 +665,7 @@
"template_name": "Template Name", "template_name": "Template Name",
"third_intro_content": "Yats live outside of Cake Wallet, too. Any wallet address on earth can be replaced with a Yat!", "third_intro_content": "Yats live outside of Cake Wallet, too. Any wallet address on earth can be replaced with a Yat!",
"third_intro_title": "Yat plays nicely with others", "third_intro_title": "Yat plays nicely with others",
"thorchain_contract_address_not_supported": "THORChain does not support sending to a contract address",
"thorchain_taproot_address_not_supported": "The ThorChain provider does not support Taproot addresses. Please change the address or select a different provider.", "thorchain_taproot_address_not_supported": "The ThorChain provider does not support Taproot addresses. Please change the address or select a different provider.",
"time": "${minutes}m ${seconds}s", "time": "${minutes}m ${seconds}s",
"tip": "Tip:", "tip": "Tip:",

View file

@ -413,6 +413,7 @@
"outputs": "Salidas", "outputs": "Salidas",
"overwrite_amount": "Overwrite amount", "overwrite_amount": "Overwrite amount",
"pairingInvalidEvent": "Evento de emparejamiento no válido", "pairingInvalidEvent": "Evento de emparejamiento no válido",
"passphrase": "Passfrase (opcional)",
"password": "Contraseña", "password": "Contraseña",
"paste": "Pegar", "paste": "Pegar",
"pause_wallet_creation": "La capacidad para crear Haven Wallet está actualmente pausada.", "pause_wallet_creation": "La capacidad para crear Haven Wallet está actualmente pausada.",
@ -665,6 +666,7 @@
"template_name": "Nombre de la plantilla", "template_name": "Nombre de la plantilla",
"third_intro_content": "Los Yats también viven fuera de Cake Wallet. Cualquier dirección de billetera en la tierra se puede reemplazar con un Yat!", "third_intro_content": "Los Yats también viven fuera de Cake Wallet. Cualquier dirección de billetera en la tierra se puede reemplazar con un Yat!",
"third_intro_title": "Yat juega muy bien con otras", "third_intro_title": "Yat juega muy bien con otras",
"thorchain_contract_address_not_supported": "Thorchain no admite enviar a una dirección de contrato",
"thorchain_taproot_address_not_supported": "El proveedor de Thorchain no admite las direcciones de Taproot. Cambie la dirección o seleccione un proveedor diferente.", "thorchain_taproot_address_not_supported": "El proveedor de Thorchain no admite las direcciones de Taproot. Cambie la dirección o seleccione un proveedor diferente.",
"time": "${minutes}m ${seconds}s", "time": "${minutes}m ${seconds}s",
"tip": "Consejo:", "tip": "Consejo:",

View file

@ -413,6 +413,7 @@
"outputs": "Les sorties", "outputs": "Les sorties",
"overwrite_amount": "Remplacer le montant", "overwrite_amount": "Remplacer le montant",
"pairingInvalidEvent": "Événement de couplage non valide", "pairingInvalidEvent": "Événement de couplage non valide",
"passphrase": "Phrase de passe (facultative)",
"password": "Mot de passe", "password": "Mot de passe",
"paste": "Coller", "paste": "Coller",
"pause_wallet_creation": "La possibilité de créer Haven Wallet est actuellement suspendue.", "pause_wallet_creation": "La possibilité de créer Haven Wallet est actuellement suspendue.",
@ -664,6 +665,7 @@
"template_name": "Nom du modèle", "template_name": "Nom du modèle",
"third_intro_content": "Les Yats existent aussi en dehors de Cake Wallet. Toute adresse sur terre peut être remplacée par un Yat !", "third_intro_content": "Les Yats existent aussi en dehors de Cake Wallet. Toute adresse sur terre peut être remplacée par un Yat !",
"third_intro_title": "Yat est universel", "third_intro_title": "Yat est universel",
"thorchain_contract_address_not_supported": "Thorchain ne prend pas en charge l'envoi à une adresse de contrat",
"thorchain_taproot_address_not_supported": "Le fournisseur de Thorchain ne prend pas en charge les adresses de tapoot. Veuillez modifier l'adresse ou sélectionner un autre fournisseur.", "thorchain_taproot_address_not_supported": "Le fournisseur de Thorchain ne prend pas en charge les adresses de tapoot. Veuillez modifier l'adresse ou sélectionner un autre fournisseur.",
"time": "${minutes}m ${seconds}s", "time": "${minutes}m ${seconds}s",
"tip": "Pourboire :", "tip": "Pourboire :",

View file

@ -415,6 +415,7 @@
"outputs": "Abubuwan fashewa", "outputs": "Abubuwan fashewa",
"overwrite_amount": "Rubuta adadin", "overwrite_amount": "Rubuta adadin",
"pairingInvalidEvent": "Haɗa Lamarin mara inganci", "pairingInvalidEvent": "Haɗa Lamarin mara inganci",
"passphrase": "Passphrase (Zabi)",
"password": "Kalmar wucewa", "password": "Kalmar wucewa",
"paste": "Manna", "paste": "Manna",
"pause_wallet_creation": "A halin yanzu an dakatar da ikon ƙirƙirar Haven Wallet.", "pause_wallet_creation": "A halin yanzu an dakatar da ikon ƙirƙirar Haven Wallet.",
@ -666,6 +667,7 @@
"template_name": "Sunan Samfura", "template_name": "Sunan Samfura",
"third_intro_content": "Yats suna zaune a wajen Kek Wallet, kuma. Ana iya maye gurbin kowane adireshin walat a duniya da Yat!", "third_intro_content": "Yats suna zaune a wajen Kek Wallet, kuma. Ana iya maye gurbin kowane adireshin walat a duniya da Yat!",
"third_intro_title": "Yat yana wasa da kyau tare da wasu", "third_intro_title": "Yat yana wasa da kyau tare da wasu",
"thorchain_contract_address_not_supported": "Thorchain baya goyon bayan aika zuwa adireshin kwangila",
"thorchain_taproot_address_not_supported": "Mai ba da tallafi na ThorChain baya goyan bayan adreshin taproot. Da fatan za a canza adireshin ko zaɓi mai bayarwa daban.", "thorchain_taproot_address_not_supported": "Mai ba da tallafi na ThorChain baya goyan bayan adreshin taproot. Da fatan za a canza adireshin ko zaɓi mai bayarwa daban.",
"time": "${minutes}m ${seconds}s", "time": "${minutes}m ${seconds}s",
"tip": "Tukwici:", "tip": "Tukwici:",

View file

@ -413,6 +413,7 @@
"outputs": "आउटपुट", "outputs": "आउटपुट",
"overwrite_amount": "Overwrite amount", "overwrite_amount": "Overwrite amount",
"pairingInvalidEvent": "अमान्य ईवेंट युग्मित करना", "pairingInvalidEvent": "अमान्य ईवेंट युग्मित करना",
"passphrase": "पासफ्रेज़ (वैकल्पिक)",
"password": "पारण शब्द", "password": "पारण शब्द",
"paste": "पेस्ट करें", "paste": "पेस्ट करें",
"pause_wallet_creation": "हेवन वॉलेट बनाने की क्षमता फिलहाल रुकी हुई है।", "pause_wallet_creation": "हेवन वॉलेट बनाने की क्षमता फिलहाल रुकी हुई है।",
@ -666,6 +667,7 @@
"template_name": "टेम्पलेट नाम", "template_name": "टेम्पलेट नाम",
"third_intro_content": "Yats Cake Wallet के बाहर भी रहता है। धरती पर किसी भी वॉलेट पते को Yat से बदला जा सकता है!", "third_intro_content": "Yats Cake Wallet के बाहर भी रहता है। धरती पर किसी भी वॉलेट पते को Yat से बदला जा सकता है!",
"third_intro_title": "Yat दूसरों के साथ अच्छा खेलता है", "third_intro_title": "Yat दूसरों के साथ अच्छा खेलता है",
"thorchain_contract_address_not_supported": "थोरचेन एक अनुबंध पते पर भेजने का समर्थन नहीं करता है",
"thorchain_taproot_address_not_supported": "थोरचेन प्रदाता टैपरोट पते का समर्थन नहीं करता है। कृपया पता बदलें या एक अलग प्रदाता का चयन करें।", "thorchain_taproot_address_not_supported": "थोरचेन प्रदाता टैपरोट पते का समर्थन नहीं करता है। कृपया पता बदलें या एक अलग प्रदाता का चयन करें।",
"time": "${minutes}m ${seconds}s", "time": "${minutes}m ${seconds}s",
"tip": "टिप:", "tip": "टिप:",

View file

@ -413,6 +413,7 @@
"outputs": "Izlazi", "outputs": "Izlazi",
"overwrite_amount": "Overwrite amount", "overwrite_amount": "Overwrite amount",
"pairingInvalidEvent": "Nevažeći događaj uparivanja", "pairingInvalidEvent": "Nevažeći događaj uparivanja",
"passphrase": "Prolaznica (neobavezno)",
"password": "Lozinka", "password": "Lozinka",
"paste": "Zalijepi", "paste": "Zalijepi",
"pause_wallet_creation": "Mogućnost stvaranja novčanika Haven trenutno je pauzirana.", "pause_wallet_creation": "Mogućnost stvaranja novčanika Haven trenutno je pauzirana.",
@ -664,6 +665,7 @@
"template_name": "Naziv predloška", "template_name": "Naziv predloška",
"third_intro_content": "Yats žive i izvan Cake Wallet -a. Bilo koja adresa novčanika na svijetu može se zamijeniti Yat!", "third_intro_content": "Yats žive i izvan Cake Wallet -a. Bilo koja adresa novčanika na svijetu može se zamijeniti Yat!",
"third_intro_title": "Yat se lijepo igra s drugima", "third_intro_title": "Yat se lijepo igra s drugima",
"thorchain_contract_address_not_supported": "Thorchain ne podržava slanje na adresu ugovora",
"thorchain_taproot_address_not_supported": "Thorchain pružatelj ne podržava Taproot adrese. Promijenite adresu ili odaberite drugog davatelja usluga.", "thorchain_taproot_address_not_supported": "Thorchain pružatelj ne podržava Taproot adrese. Promijenite adresu ili odaberite drugog davatelja usluga.",
"time": "${minutes}m ${seconds}s", "time": "${minutes}m ${seconds}s",
"tip": "Savjet:", "tip": "Savjet:",

View file

@ -415,6 +415,7 @@
"outputs": "Output", "outputs": "Output",
"overwrite_amount": "Timpa jumlah", "overwrite_amount": "Timpa jumlah",
"pairingInvalidEvent": "Menyandingkan Acara Tidak Valid", "pairingInvalidEvent": "Menyandingkan Acara Tidak Valid",
"passphrase": "Frasa sandi (opsional)",
"password": "Kata Sandi", "password": "Kata Sandi",
"paste": "Tempel", "paste": "Tempel",
"pause_wallet_creation": "Kemampuan untuk membuat Haven Wallet saat ini dijeda.", "pause_wallet_creation": "Kemampuan untuk membuat Haven Wallet saat ini dijeda.",
@ -667,6 +668,7 @@
"template_name": "Nama Templat", "template_name": "Nama Templat",
"third_intro_content": "Yats hidup di luar Cake Wallet juga. Setiap alamat dompet di dunia dapat diganti dengan Yat!", "third_intro_content": "Yats hidup di luar Cake Wallet juga. Setiap alamat dompet di dunia dapat diganti dengan Yat!",
"third_intro_title": "Yat bermain baik dengan yang lain", "third_intro_title": "Yat bermain baik dengan yang lain",
"thorchain_contract_address_not_supported": "Thorchain tidak mendukung pengiriman ke alamat kontrak",
"thorchain_taproot_address_not_supported": "Penyedia Thorchain tidak mendukung alamat Taproot. Harap ubah alamatnya atau pilih penyedia yang berbeda.", "thorchain_taproot_address_not_supported": "Penyedia Thorchain tidak mendukung alamat Taproot. Harap ubah alamatnya atau pilih penyedia yang berbeda.",
"time": "${minutes}m ${seconds}s", "time": "${minutes}m ${seconds}s",
"tip": "Tip:", "tip": "Tip:",

View file

@ -415,6 +415,7 @@
"outputs": "Output", "outputs": "Output",
"overwrite_amount": "Sovrascrivi quantità", "overwrite_amount": "Sovrascrivi quantità",
"pairingInvalidEvent": "Associazione evento non valido", "pairingInvalidEvent": "Associazione evento non valido",
"passphrase": "Passphrase (opzionale)",
"password": "Password", "password": "Password",
"paste": "Incolla", "paste": "Incolla",
"pause_wallet_creation": "La possibilità di creare Haven Wallet è attualmente sospesa.", "pause_wallet_creation": "La possibilità di creare Haven Wallet è attualmente sospesa.",
@ -666,6 +667,7 @@
"template_name": "Nome modello", "template_name": "Nome modello",
"third_intro_content": "Yat può funzionare anche fuori da Cake Wallet. Qualsiasi indirizzo di portafoglio sulla terra può essere sostituito con uno Yat!", "third_intro_content": "Yat può funzionare anche fuori da Cake Wallet. Qualsiasi indirizzo di portafoglio sulla terra può essere sostituito con uno Yat!",
"third_intro_title": "Yat gioca bene con gli altri", "third_intro_title": "Yat gioca bene con gli altri",
"thorchain_contract_address_not_supported": "Thorchain non supporta l'invio a un indirizzo contrattuale",
"thorchain_taproot_address_not_supported": "Il provider di Thorchain non supporta gli indirizzi di TapRoot. Si prega di modificare l'indirizzo o selezionare un fornitore diverso.", "thorchain_taproot_address_not_supported": "Il provider di Thorchain non supporta gli indirizzi di TapRoot. Si prega di modificare l'indirizzo o selezionare un fornitore diverso.",
"time": "${minutes}m ${seconds}s", "time": "${minutes}m ${seconds}s",
"tip": "Suggerimento:", "tip": "Suggerimento:",

View file

@ -414,6 +414,7 @@
"outputs": "出力", "outputs": "出力",
"overwrite_amount": "Overwrite amount", "overwrite_amount": "Overwrite amount",
"pairingInvalidEvent": "ペアリング無効イベント", "pairingInvalidEvent": "ペアリング無効イベント",
"passphrase": "パスフレーズ(オプション)",
"password": "パスワード", "password": "パスワード",
"paste": "ペースト", "paste": "ペースト",
"pause_wallet_creation": "Haven Wallet を作成する機能は現在一時停止されています。", "pause_wallet_creation": "Haven Wallet を作成する機能は現在一時停止されています。",
@ -665,6 +666,7 @@
"template_name": "テンプレート名", "template_name": "テンプレート名",
"third_intro_content": "YatsはCakeWalletの外にも住んでいます。 地球上のどのウォレットアドレスもYatに置き換えることができます", "third_intro_content": "YatsはCakeWalletの外にも住んでいます。 地球上のどのウォレットアドレスもYatに置き換えることができます",
"third_intro_title": "Yatは他の人とうまく遊ぶ", "third_intro_title": "Yatは他の人とうまく遊ぶ",
"thorchain_contract_address_not_supported": "Thorchainは、契約アドレスへの送信をサポートしていません",
"thorchain_taproot_address_not_supported": "Thorchainプロバイダーは、TapRootアドレスをサポートしていません。アドレスを変更するか、別のプロバイダーを選択してください。", "thorchain_taproot_address_not_supported": "Thorchainプロバイダーは、TapRootアドレスをサポートしていません。アドレスを変更するか、別のプロバイダーを選択してください。",
"time": "${minutes}m ${seconds}s", "time": "${minutes}m ${seconds}s",
"tip": "ヒント: ", "tip": "ヒント: ",

View file

@ -413,6 +413,7 @@
"outputs": "출력", "outputs": "출력",
"overwrite_amount": "Overwrite amount", "overwrite_amount": "Overwrite amount",
"pairingInvalidEvent": "잘못된 이벤트 페어링", "pairingInvalidEvent": "잘못된 이벤트 페어링",
"passphrase": "암호화 (선택 사항)",
"password": "암호", "password": "암호",
"paste": "풀", "paste": "풀",
"pause_wallet_creation": "Haven Wallet 생성 기능이 현재 일시 중지되었습니다.", "pause_wallet_creation": "Haven Wallet 생성 기능이 현재 일시 중지되었습니다.",
@ -427,8 +428,8 @@
"placeholder_transactions": "거래가 여기에 표시됩니다", "placeholder_transactions": "거래가 여기에 표시됩니다",
"please_fill_totp": "다른 기기에 있는 8자리 코드를 입력하세요.", "please_fill_totp": "다른 기기에 있는 8자리 코드를 입력하세요.",
"please_make_selection": "아래에서 선택하십시오 지갑 만들기 또는 복구.", "please_make_selection": "아래에서 선택하십시오 지갑 만들기 또는 복구.",
"please_reference_document": "자세한 내용은 아래 문서를 참조하십시오.",
"Please_reference_document": "자세한 내용은 아래 문서를 참조하십시오.", "Please_reference_document": "자세한 내용은 아래 문서를 참조하십시오.",
"please_reference_document": "자세한 내용은 아래 문서를 참조하십시오.",
"please_select": "선택 해주세요:", "please_select": "선택 해주세요:",
"please_select_backup_file": "백업 파일을 선택하고 백업 암호를 입력하십시오.", "please_select_backup_file": "백업 파일을 선택하고 백업 암호를 입력하십시오.",
"please_try_to_connect_to_another_node": "다른 노드에 연결을 시도하십시오", "please_try_to_connect_to_another_node": "다른 노드에 연결을 시도하십시오",
@ -665,6 +666,7 @@
"template_name": "템플릿 이름", "template_name": "템플릿 이름",
"third_intro_content": "Yats는 Cake Wallet 밖에서도 살고 있습니다. 지구상의 모든 지갑 주소는 Yat!", "third_intro_content": "Yats는 Cake Wallet 밖에서도 살고 있습니다. 지구상의 모든 지갑 주소는 Yat!",
"third_intro_title": "Yat는 다른 사람들과 잘 놉니다.", "third_intro_title": "Yat는 다른 사람들과 잘 놉니다.",
"thorchain_contract_address_not_supported": "Thorchain은 계약 주소로 보내는 것을 지원하지 않습니다",
"thorchain_taproot_address_not_supported": "Thorchain 제공 업체는 Taproot 주소를 지원하지 않습니다. 주소를 변경하거나 다른 공급자를 선택하십시오.", "thorchain_taproot_address_not_supported": "Thorchain 제공 업체는 Taproot 주소를 지원하지 않습니다. 주소를 변경하거나 다른 공급자를 선택하십시오.",
"time": "${minutes}m ${seconds}s", "time": "${minutes}m ${seconds}s",
"tip": "팁:", "tip": "팁:",

View file

@ -413,6 +413,7 @@
"outputs": "ထုတ်လုပ်မှု", "outputs": "ထုတ်လုပ်မှု",
"overwrite_amount": "ပမာဏကို ထပ်ရေးပါ။", "overwrite_amount": "ပမာဏကို ထပ်ရေးပါ။",
"pairingInvalidEvent": "မမှန်ကန်သောဖြစ်ရပ်ကို တွဲချိတ်ခြင်း။", "pairingInvalidEvent": "မမှန်ကန်သောဖြစ်ရပ်ကို တွဲချိတ်ခြင်း။",
"passphrase": "passphrase (optional)",
"password": "စကားဝှက်", "password": "စကားဝှက်",
"paste": "ငါးပိ", "paste": "ငါးပိ",
"pause_wallet_creation": "Haven Wallet ဖန်တီးနိုင်မှုကို လောလောဆယ် ခေတ္တရပ်ထားသည်။", "pause_wallet_creation": "Haven Wallet ဖန်တီးနိုင်မှုကို လောလောဆယ် ခေတ္တရပ်ထားသည်။",
@ -664,6 +665,7 @@
"template_name": "နမူနာပုံစံ", "template_name": "နမူနာပုံစံ",
"third_intro_content": "Yats သည် Cake Wallet အပြင်ဘက်တွင် နေထိုင်ပါသည်။ ကမ္ဘာပေါ်ရှိ မည်သည့်ပိုက်ဆံအိတ်လိပ်စာကို Yat ဖြင့် အစားထိုးနိုင်ပါသည်။", "third_intro_content": "Yats သည် Cake Wallet အပြင်ဘက်တွင် နေထိုင်ပါသည်။ ကမ္ဘာပေါ်ရှိ မည်သည့်ပိုက်ဆံအိတ်လိပ်စာကို Yat ဖြင့် အစားထိုးနိုင်ပါသည်။",
"third_intro_title": "Yat သည် အခြားသူများနှင့် ကောင်းစွာကစားသည်။", "third_intro_title": "Yat သည် အခြားသူများနှင့် ကောင်းစွာကစားသည်။",
"thorchain_contract_address_not_supported": "Thorchain သည်စာချုပ်လိပ်စာသို့ပို့ခြင်းမပြုပါ",
"thorchain_taproot_address_not_supported": "Thorchain Provider သည် Taproot လိပ်စာများကိုမထောက်ခံပါ။ ကျေးဇူးပြု. လိပ်စာကိုပြောင်းပါသို့မဟုတ်အခြားပံ့ပိုးပေးသူကိုရွေးချယ်ပါ။", "thorchain_taproot_address_not_supported": "Thorchain Provider သည် Taproot လိပ်စာများကိုမထောက်ခံပါ။ ကျေးဇူးပြု. လိပ်စာကိုပြောင်းပါသို့မဟုတ်အခြားပံ့ပိုးပေးသူကိုရွေးချယ်ပါ။",
"time": "${minutes}m ${seconds}s", "time": "${minutes}m ${seconds}s",
"tip": "အကြံပြုချက်-", "tip": "အကြံပြုချက်-",

View file

@ -413,6 +413,7 @@
"outputs": "Uitgangen", "outputs": "Uitgangen",
"overwrite_amount": "Overwrite amount", "overwrite_amount": "Overwrite amount",
"pairingInvalidEvent": "Koppelen Ongeldige gebeurtenis", "pairingInvalidEvent": "Koppelen Ongeldige gebeurtenis",
"passphrase": "PassaspHRASE (optioneel)",
"password": "Wachtwoord", "password": "Wachtwoord",
"paste": "Plakken", "paste": "Plakken",
"pause_wallet_creation": "De mogelijkheid om Haven Wallet te maken is momenteel onderbroken.", "pause_wallet_creation": "De mogelijkheid om Haven Wallet te maken is momenteel onderbroken.",
@ -664,6 +665,7 @@
"template_name": "Sjabloonnaam", "template_name": "Sjabloonnaam",
"third_intro_content": "Yats wonen ook buiten Cake Wallet. Elk portemonnee-adres op aarde kan worden vervangen door een Yat!", "third_intro_content": "Yats wonen ook buiten Cake Wallet. Elk portemonnee-adres op aarde kan worden vervangen door een Yat!",
"third_intro_title": "Yat speelt leuk met anderen", "third_intro_title": "Yat speelt leuk met anderen",
"thorchain_contract_address_not_supported": "Thorchain ondersteunt het verzenden niet naar een contractadres",
"thorchain_taproot_address_not_supported": "De Thorchain -provider ondersteunt geen Taprooot -adressen. Wijzig het adres of selecteer een andere provider.", "thorchain_taproot_address_not_supported": "De Thorchain -provider ondersteunt geen Taprooot -adressen. Wijzig het adres of selecteer een andere provider.",
"time": "${minutes}m ${seconds}s", "time": "${minutes}m ${seconds}s",
"tip": "Tip:", "tip": "Tip:",

View file

@ -413,6 +413,7 @@
"outputs": "Wyjścia", "outputs": "Wyjścia",
"overwrite_amount": "Nadpisz ilość", "overwrite_amount": "Nadpisz ilość",
"pairingInvalidEvent": "Nieprawidłowe zdarzenie parowania", "pairingInvalidEvent": "Nieprawidłowe zdarzenie parowania",
"passphrase": "PassPhraza (opcjonalnie)",
"password": "Hasło", "password": "Hasło",
"paste": "Wklej", "paste": "Wklej",
"pause_wallet_creation": "Możliwość utworzenia Portfela Haven jest obecnie wstrzymana.", "pause_wallet_creation": "Możliwość utworzenia Portfela Haven jest obecnie wstrzymana.",
@ -664,6 +665,7 @@
"template_name": "Nazwa szablonu", "template_name": "Nazwa szablonu",
"third_intro_content": "Yats mieszkają również poza Cake Wallet. Każdy adres portfela na ziemi można zastąpić Yat!", "third_intro_content": "Yats mieszkają również poza Cake Wallet. Każdy adres portfela na ziemi można zastąpić Yat!",
"third_intro_title": "Yat ładnie bawi się z innymi", "third_intro_title": "Yat ładnie bawi się z innymi",
"thorchain_contract_address_not_supported": "Thorchain nie wspiera wysyłania na adres umowy",
"thorchain_taproot_address_not_supported": "Dostawca Thorchain nie obsługuje adresów TAPROOT. Zmień adres lub wybierz innego dostawcę.", "thorchain_taproot_address_not_supported": "Dostawca Thorchain nie obsługuje adresów TAPROOT. Zmień adres lub wybierz innego dostawcę.",
"time": "${minutes}m ${seconds}s", "time": "${minutes}m ${seconds}s",
"tip": "wskazówka:", "tip": "wskazówka:",

View file

@ -415,6 +415,7 @@
"outputs": "Saídas", "outputs": "Saídas",
"overwrite_amount": "Overwrite amount", "overwrite_amount": "Overwrite amount",
"pairingInvalidEvent": "Emparelhamento de evento inválido", "pairingInvalidEvent": "Emparelhamento de evento inválido",
"passphrase": "Senha (opcional)",
"password": "Senha", "password": "Senha",
"paste": "Colar", "paste": "Colar",
"pause_wallet_creation": "A capacidade de criar a Haven Wallet está atualmente pausada.", "pause_wallet_creation": "A capacidade de criar a Haven Wallet está atualmente pausada.",
@ -666,6 +667,7 @@
"template_name": "Nome do modelo", "template_name": "Nome do modelo",
"third_intro_content": "Yats também mora fora da Cake Wallet. Qualquer endereço de carteira na Terra pode ser substituído por um Yat!", "third_intro_content": "Yats também mora fora da Cake Wallet. Qualquer endereço de carteira na Terra pode ser substituído por um Yat!",
"third_intro_title": "Yat joga bem com os outros", "third_intro_title": "Yat joga bem com os outros",
"thorchain_contract_address_not_supported": "Thorchain não suporta o envio para um endereço de contrato",
"thorchain_taproot_address_not_supported": "O provedor de Thorchain não suporta endereços de raiz de Tap. Altere o endereço ou selecione um provedor diferente.", "thorchain_taproot_address_not_supported": "O provedor de Thorchain não suporta endereços de raiz de Tap. Altere o endereço ou selecione um provedor diferente.",
"time": "${minutes}m ${seconds}s", "time": "${minutes}m ${seconds}s",
"tip": "Dica:", "tip": "Dica:",

View file

@ -414,6 +414,7 @@
"outputs": "Выходы", "outputs": "Выходы",
"overwrite_amount": "Overwrite amount", "overwrite_amount": "Overwrite amount",
"pairingInvalidEvent": "Недействительное событие сопряжения", "pairingInvalidEvent": "Недействительное событие сопряжения",
"passphrase": "Passfrase (необязательно)",
"password": "Пароль", "password": "Пароль",
"paste": "Вставить", "paste": "Вставить",
"pause_wallet_creation": "Возможность создания Haven Wallet в настоящее время приостановлена.", "pause_wallet_creation": "Возможность создания Haven Wallet в настоящее время приостановлена.",
@ -665,6 +666,7 @@
"template_name": "Имя Шаблона", "template_name": "Имя Шаблона",
"third_intro_content": "Yat находятся за пределами Cake Wallet. Любой адрес кошелька на земле можно заменить на Yat!", "third_intro_content": "Yat находятся за пределами Cake Wallet. Любой адрес кошелька на земле можно заменить на Yat!",
"third_intro_title": "Yat хорошо взаимодействует с другими", "third_intro_title": "Yat хорошо взаимодействует с другими",
"thorchain_contract_address_not_supported": "Thorchain не поддерживает отправку на адрес контракта",
"thorchain_taproot_address_not_supported": "Поставщик Thorchain не поддерживает адреса taproot. Пожалуйста, измените адрес или выберите другого поставщика.", "thorchain_taproot_address_not_supported": "Поставщик Thorchain не поддерживает адреса taproot. Пожалуйста, измените адрес или выберите другого поставщика.",
"time": "${minutes}мин ${seconds}сек", "time": "${minutes}мин ${seconds}сек",
"tip": "Совет:", "tip": "Совет:",

View file

@ -413,6 +413,7 @@
"outputs": "เอาต์พุต", "outputs": "เอาต์พุต",
"overwrite_amount": "เขียนทับจำนวน", "overwrite_amount": "เขียนทับจำนวน",
"pairingInvalidEvent": "การจับคู่เหตุการณ์ที่ไม่ถูกต้อง", "pairingInvalidEvent": "การจับคู่เหตุการณ์ที่ไม่ถูกต้อง",
"passphrase": "ข้อความรหัสผ่าน (ไม่บังคับ)",
"password": "รหัสผ่าน", "password": "รหัสผ่าน",
"paste": "วาง", "paste": "วาง",
"pause_wallet_creation": "ขณะนี้ความสามารถในการสร้าง Haven Wallet ถูกหยุดชั่วคราว", "pause_wallet_creation": "ขณะนี้ความสามารถในการสร้าง Haven Wallet ถูกหยุดชั่วคราว",
@ -664,6 +665,7 @@
"template_name": "ชื่อแม่แบบ", "template_name": "ชื่อแม่แบบ",
"third_intro_content": "Yat อาศัยอยู่นอก Cake Wallet ด้วย ที่อยู่กระเป๋าใดๆ ทั่วโลกสามารถแทนด้วย Yat ได้อีกด้วย!", "third_intro_content": "Yat อาศัยอยู่นอก Cake Wallet ด้วย ที่อยู่กระเป๋าใดๆ ทั่วโลกสามารถแทนด้วย Yat ได้อีกด้วย!",
"third_intro_title": "Yat ปฏิบัติตนอย่างดีกับผู้อื่น", "third_intro_title": "Yat ปฏิบัติตนอย่างดีกับผู้อื่น",
"thorchain_contract_address_not_supported": "Thorchain ไม่สนับสนุนการส่งไปยังที่อยู่สัญญา",
"thorchain_taproot_address_not_supported": "ผู้ให้บริการ Thorchain ไม่รองรับที่อยู่ taproot โปรดเปลี่ยนที่อยู่หรือเลือกผู้ให้บริการอื่น", "thorchain_taproot_address_not_supported": "ผู้ให้บริการ Thorchain ไม่รองรับที่อยู่ taproot โปรดเปลี่ยนที่อยู่หรือเลือกผู้ให้บริการอื่น",
"time": "${minutes}m ${seconds}s", "time": "${minutes}m ${seconds}s",
"tip": "เพิ่มค่าตอบแทน:", "tip": "เพิ่มค่าตอบแทน:",

View file

@ -413,6 +413,7 @@
"outputs": "Mga output", "outputs": "Mga output",
"overwrite_amount": "Overwrite na halaga", "overwrite_amount": "Overwrite na halaga",
"pairingInvalidEvent": "Pagpares ng Di-wastong Kaganapan", "pairingInvalidEvent": "Pagpares ng Di-wastong Kaganapan",
"passphrase": "Passphrase (opsyonal)",
"password": "Password", "password": "Password",
"paste": "I -paste", "paste": "I -paste",
"pause_wallet_creation": "Kasalukuyang naka-pause ang kakayahang gumawa ng Haven Wallet.", "pause_wallet_creation": "Kasalukuyang naka-pause ang kakayahang gumawa ng Haven Wallet.",
@ -664,6 +665,7 @@
"template_name": "Pangalan ng Template", "template_name": "Pangalan ng Template",
"third_intro_content": "Ang mga yats ay nakatira sa labas ng cake wallet, din. Ang anumang address ng pitaka sa mundo ay maaaring mapalitan ng isang yat!", "third_intro_content": "Ang mga yats ay nakatira sa labas ng cake wallet, din. Ang anumang address ng pitaka sa mundo ay maaaring mapalitan ng isang yat!",
"third_intro_title": "Si Yat ay mahusay na gumaganap sa iba", "third_intro_title": "Si Yat ay mahusay na gumaganap sa iba",
"thorchain_contract_address_not_supported": "Hindi sinusuportahan ng Thorchain ang pagpapadala sa isang address ng kontrata",
"thorchain_taproot_address_not_supported": "Ang Tagabigay ng Thorchain ay hindi sumusuporta sa mga address ng taproot. Mangyaring baguhin ang address o pumili ng ibang provider.", "thorchain_taproot_address_not_supported": "Ang Tagabigay ng Thorchain ay hindi sumusuporta sa mga address ng taproot. Mangyaring baguhin ang address o pumili ng ibang provider.",
"time": "${minutes} m ${seconds} s", "time": "${minutes} m ${seconds} s",
"tip": "Tip:", "tip": "Tip:",

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