Merge remote-tracking branch 'origin/main' into CW-453-silent-payments

This commit is contained in:
Rafael Saes 2024-05-06 10:19:03 -03:00
commit 0a57de0628
108 changed files with 1396 additions and 561 deletions

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

@ -12,6 +12,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';
@ -30,10 +31,12 @@ 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,
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses, List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
int initialSilentAddressIndex = 0, int initialSilentAddressIndex = 0,
}) : super( }) : super(
mnemonic: mnemonic, mnemonic: mnemonic,
passphrase: passphrase,
password: password, password: password,
walletInfo: walletInfo, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
@ -74,6 +77,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
required String password, required String password,
required WalletInfo walletInfo, required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo,
String? passphrase,
String? addressPageType, String? addressPageType,
BasedUtxoNetwork? network, BasedUtxoNetwork? network,
List<BitcoinAddressRecord>? initialAddresses, List<BitcoinAddressRecord>? initialAddresses,
@ -83,9 +87,23 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
Map<String, int>? initialChangeAddressIndex, Map<String, int>? initialChangeAddressIndex,
int initialSilentAddressIndex = 0, int initialSilentAddressIndex = 0,
}) async { }) async {
final seedBytes = await mnemonicToSeedBytes(mnemonic); 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,
@ -112,10 +130,33 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
: BitcoinNetwork.mainnet; : BitcoinNetwork.mainnet;
final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, network); final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, network);
final seedBytes = await mnemonicToSeedBytes(snp.mnemonic); 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,

View file

@ -2,14 +2,35 @@ 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}) BitcoinNewWalletCredentials(
: super(name: name, walletInfo: walletInfo); {required String name,
WalletInfo? walletInfo,
DerivationType? derivationType,
String? derivationPath})
: super(
name: name,
walletInfo: walletInfo,
);
} }
class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials { class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials {
BitcoinRestoreWalletFromSeedCredentials( BitcoinRestoreWalletFromSeedCredentials({
{required String name, required String password, required this.mnemonic, WalletInfo? walletInfo}) required String name,
: super(name: name, password: password, walletInfo: walletInfo); 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,
));
final String mnemonic; final String mnemonic;
} }

View file

@ -12,6 +12,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> {
@ -29,8 +30,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,
@ -105,7 +107,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();
} }
@ -114,6 +116,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

@ -60,13 +60,15 @@ abstract class ElectrumWalletBase
required this.networkType, required this.networkType,
required this.mnemonic, required this.mnemonic,
required Uint8List seedBytes, required Uint8List seedBytes,
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>[],
@ -111,6 +113,7 @@ abstract class ElectrumWalletBase
final bitcoin.HDWallet hd; final bitcoin.HDWallet hd;
final String mnemonic; final String mnemonic;
final String? passphrase;
@override @override
@observable @observable
@ -132,6 +135,15 @@ abstract class ElectrumWalletBase
Set<String> get addressesSet => walletAddresses.allAddresses.map((addr) => addr.address).toSet(); Set<String> get addressesSet => walletAddresses.allAddresses.map((addr) => addr.address).toSet();
List<String> get scriptHashes => walletAddresses.addressesByReceiveType
.map((addr) => scriptHash(addr.address, network: network))
.toList();
List<String> get publicScriptHashes => walletAddresses.allAddresses
.where((addr) => !addr.isHidden)
.map((addr) => scriptHash(addr.address, network: network))
.toList();
String get xpub => hd.base58!; String get xpub => hd.base58!;
@override @override
@ -423,13 +435,15 @@ abstract class ElectrumWalletBase
List<ECPrivateInfo> inputPrivKeyInfos = []; List<ECPrivateInfo> inputPrivKeyInfos = [];
int allInputsAmount = 0; int allInputsAmount = 0;
bool spendsSilentPayment = false; bool spendsSilentPayment = false;
bool spendsCPFP = false; bool spendsUnconfirmedTX = false;
int leftAmount = credentialsAmount; int leftAmount = credentialsAmount;
final availableInputs = unspentCoins.where((utx) => utx.isSending && !utx.isFrozen).toList(); final availableInputs = unspentCoins.where((utx) => utx.isSending && !utx.isFrozen).toList();
final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList();
for (int i = 0; i < availableInputs.length; i++) { for (int i = 0; i < availableInputs.length; i++) {
final utx = availableInputs[i]; final utx = availableInputs[i];
if (!spendsUnconfirmedTX) spendsUnconfirmedTX = utx.confirmations == 0;
if (paysToSilentPayment) { if (paysToSilentPayment) {
// Check inputs for shared secret derivation // Check inputs for shared secret derivation
@ -438,7 +452,7 @@ abstract class ElectrumWalletBase
} }
} }
spendsCPFP = (utx.confirmations ?? 0) <= 0; spendsUnconfirmedTX = (utx.confirmations ?? 0) <= 0;
allInputsAmount += utx.value; allInputsAmount += utx.value;
leftAmount = leftAmount - utx.value; leftAmount = leftAmount - utx.value;
@ -502,12 +516,13 @@ abstract class ElectrumWalletBase
return UtxoDetails( return UtxoDetails(
availableInputs: availableInputs, availableInputs: availableInputs,
unconfirmedCoins: unconfirmedCoins,
utxos: utxos, utxos: utxos,
vinOutpoints: vinOutpoints, vinOutpoints: vinOutpoints,
inputPrivKeyInfos: inputPrivKeyInfos, inputPrivKeyInfos: inputPrivKeyInfos,
allInputsAmount: allInputsAmount, allInputsAmount: allInputsAmount,
spendsSilentPayment: spendsSilentPayment, spendsSilentPayment: spendsSilentPayment,
spendsCPFP: spendsCPFP, spendsUnconfirmedTX: spendsUnconfirmedTX,
); );
} }
@ -556,6 +571,10 @@ abstract class ElectrumWalletBase
throw BitcoinTransactionWrongBalanceException(amount: utxoDetails.allInputsAmount + fee); throw BitcoinTransactionWrongBalanceException(amount: utxoDetails.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();
@ -582,7 +601,7 @@ abstract class ElectrumWalletBase
hasChange: false, hasChange: false,
memo: memo, memo: memo,
spendsSilentPayment: utxoDetails.spendsSilentPayment, spendsSilentPayment: utxoDetails.spendsSilentPayment,
spendsCPFP: utxoDetails.spendsCPFP, spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX,
); );
} }
@ -592,6 +611,7 @@ abstract class ElectrumWalletBase
int feeRate, { int feeRate, {
int? inputsCount, int? inputsCount,
String? memo, String? memo,
bool? useUnconfirmed,
bool hasSilentPayment = false, bool hasSilentPayment = false,
}) async { }) async {
final utxoDetails = _createUTXOS( final utxoDetails = _createUTXOS(
@ -602,6 +622,9 @@ abstract class ElectrumWalletBase
); );
final spendingAllCoins = utxoDetails.availableInputs.length == utxoDetails.utxos.length; final spendingAllCoins = utxoDetails.availableInputs.length == utxoDetails.utxos.length;
final spendingAllConfirmedCoins = !utxoDetails.spendsUnconfirmedTX &&
utxoDetails.utxos.length ==
utxoDetails.availableInputs.length - utxoDetails.unconfirmedCoins.length;
// How much is being spent - how much is being sent // How much is being spent - how much is being sent
int amountLeftForChangeAndFee = utxoDetails.allInputsAmount - credentialsAmount; int amountLeftForChangeAndFee = utxoDetails.allInputsAmount - credentialsAmount;
@ -674,6 +697,7 @@ abstract class ElectrumWalletBase
feeRate, feeRate,
inputsCount: utxoDetails.utxos.length + 1, inputsCount: utxoDetails.utxos.length + 1,
memo: memo, memo: memo,
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
); );
} }
@ -720,6 +744,7 @@ abstract class ElectrumWalletBase
feeRate, feeRate,
inputsCount: utxoDetails.utxos.length + 1, inputsCount: utxoDetails.utxos.length + 1,
memo: memo, memo: memo,
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
hasSilentPayment: hasSilentPayment, hasSilentPayment: hasSilentPayment,
); );
} }
@ -734,7 +759,7 @@ abstract class ElectrumWalletBase
isSendAll: false, isSendAll: false,
memo: memo, memo: memo,
spendsSilentPayment: utxoDetails.spendsSilentPayment, spendsSilentPayment: utxoDetails.spendsSilentPayment,
spendsCPFP: utxoDetails.spendsCPFP, spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX,
); );
} }
@ -812,7 +837,7 @@ abstract class ElectrumWalletBase
network: network, network: network,
memo: estimatedTx.memo, memo: estimatedTx.memo,
outputOrdering: BitcoinOrdering.none, outputOrdering: BitcoinOrdering.none,
enableRBF: !estimatedTx.spendsCPFP, enableRBF: !estimatedTx.spendsUnconfirmedTX,
); );
} else { } else {
txb = BitcoinTransactionBuilder( txb = BitcoinTransactionBuilder(
@ -822,7 +847,7 @@ abstract class ElectrumWalletBase
network: network, network: network,
memo: estimatedTx.memo, memo: estimatedTx.memo,
outputOrdering: BitcoinOrdering.none, outputOrdering: BitcoinOrdering.none,
enableRBF: !estimatedTx.spendsCPFP, enableRBF: !estimatedTx.spendsUnconfirmedTX,
); );
} }
@ -878,6 +903,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(),
@ -1929,7 +1955,7 @@ class EstimatedTxResult {
required this.isSendAll, required this.isSendAll,
this.memo, this.memo,
required this.spendsSilentPayment, required this.spendsSilentPayment,
required this.spendsCPFP, required this.spendsUnconfirmedTX,
}); });
final List<UtxoWithAddress> utxos; final List<UtxoWithAddress> utxos;
@ -1940,7 +1966,7 @@ class EstimatedTxResult {
final bool hasChange; final bool hasChange;
final bool isSendAll; final bool isSendAll;
final String? memo; final String? memo;
final bool spendsCPFP; final bool spendsUnconfirmedTX;
} }
BitcoinBaseAddress addressTypeFromStr(String address, BasedUtxoNetwork network) { BitcoinBaseAddress addressTypeFromStr(String address, BasedUtxoNetwork network) {
@ -1986,20 +2012,22 @@ BitcoinAddressType _getScriptType(BitcoinBaseAddress type) {
class UtxoDetails { class UtxoDetails {
final List<BitcoinUnspent> availableInputs; final List<BitcoinUnspent> availableInputs;
final List<BitcoinUnspent> unconfirmedCoins;
final List<UtxoWithAddress> utxos; final List<UtxoWithAddress> utxos;
final List<Outpoint> vinOutpoints; final List<Outpoint> vinOutpoints;
final List<ECPrivateInfo> inputPrivKeyInfos; final List<ECPrivateInfo> inputPrivKeyInfos;
final int allInputsAmount; final int allInputsAmount;
final bool spendsSilentPayment; final bool spendsSilentPayment;
final bool spendsCPFP; final bool spendsUnconfirmedTX;
UtxoDetails({ UtxoDetails({
required this.availableInputs, required this.availableInputs,
required this.unconfirmedCoins,
required this.utxos, required this.utxos,
required this.vinOutpoints, required this.vinOutpoints,
required this.inputPrivKeyInfos, required this.inputPrivKeyInfos,
required this.allInputsAmount, required this.allInputsAmount,
required this.spendsSilentPayment, required this.spendsSilentPayment,
required this.spendsCPFP, required this.spendsUnconfirmedTX,
}); });
} }

View file

@ -3,6 +3,7 @@ import 'package:bitcoin_base/bitcoin_base.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_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';
@ -13,12 +14,15 @@ class ElectrumWalletSnapshot {
required this.password, required this.password,
required this.mnemonic, required this.mnemonic,
required this.addresses, required this.addresses,
required this.silentAddresses,
required this.balance, required this.balance,
required this.regularAddressIndex, required this.regularAddressIndex,
required this.changeAddressIndex, required this.changeAddressIndex,
required this.addressPageType, required this.addressPageType,
required this.silentAddresses,
required this.silentAddressIndex, required this.silentAddressIndex,
this.passphrase,
this.derivationType,
this.derivationPath,
}); });
final String name; final String name;
@ -33,15 +37,18 @@ class ElectrumWalletSnapshot {
Map<String, int> regularAddressIndex; Map<String, int> regularAddressIndex;
Map<String, int> changeAddressIndex; Map<String, int> changeAddressIndex;
int silentAddressIndex; int silentAddressIndex;
String? passphrase;
DerivationType? derivationType;
String? derivationPath;
static Future<ElectrumWalletSnapshot> load( static Future<ElectrumWalletSnapshot> load(
String name, WalletType type, String password, BasedUtxoNetwork network) async { String name, WalletType type, String password, BasedUtxoNetwork network) async {
final path = await pathForWallet(name: name, type: type); final path = await pathForWallet(name: name, type: type);
final jsonSource = await read(path: path, password: password); final jsonSource = await read(path: path, password: password);
final data = json.decode(jsonSource) as Map; final data = json.decode(jsonSource) as Map;
final mnemonic = data['mnemonic'] as String;
final addressesTmp = data['addresses'] as List? ?? <Object>[]; final addressesTmp = data['addresses'] as List? ?? <Object>[];
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: network)) .map((addr) => BitcoinAddressRecord.fromJSON(addr, network: network))
@ -59,6 +66,10 @@ class ElectrumWalletSnapshot {
var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0}; var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
var silentAddressIndex = 0; var silentAddressIndex = 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')
@ -79,13 +90,16 @@ class ElectrumWalletSnapshot {
name: name, name: name,
type: type, type: type,
password: password, password: password,
passphrase: passphrase,
mnemonic: mnemonic, mnemonic: mnemonic,
addresses: addresses, addresses: addresses,
silentAddresses: silentAddresses,
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,
silentAddresses: silentAddresses,
silentAddressIndex: silentAddressIndex, silentAddressIndex: silentAddressIndex,
); );
} }

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

@ -15,6 +15,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';
@ -61,11 +62,26 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
required String password, required String password,
required WalletInfo walletInfo, required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo,
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,
@ -73,7 +89,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
initialBalance: initialBalance, initialBalance: initialBalance,
seedBytes: await mnemonicToSeedBytes(mnemonic), seedBytes: seedBytes,
initialRegularAddressIndex: initialRegularAddressIndex, initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex,
addressPageType: addressPageType, addressPageType: addressPageType,

View file

@ -11,6 +11,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,
@ -27,8 +28,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);
await wallet.save(); await wallet.save();
@ -100,12 +102,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

@ -77,7 +77,10 @@ class PendingBitcoinTransaction with PendingTransaction {
if (error.contains("non-BIP68-final")) { if (error.contains("non-BIP68-final")) {
throw BitcoinTransactionCommitFailedBIP68Final(); throw BitcoinTransactionCommitFailedBIP68Final();
} }
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-v3 ref: cake-update-v3
resolved-ref: "3ddad3d1a9b78f49c9ef542962758400315d64a7" resolved-ref: "2a18ab92a9f7136b76fcd1bf8480eaaa90e0a6b2"
url: "https://github.com/cake-tech/bitcoin_base" url: "https://github.com/cake-tech/bitcoin_base"
source: git source: git
version: "4.0.0" version: "4.0.0"
@ -154,10 +154,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:
@ -326,10 +326,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 +339,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 +483,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 +587,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 +603,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:
@ -697,7 +697,7 @@ packages:
description: description:
path: "." path: "."
ref: master ref: master
resolved-ref: de90b20f4250647d0f55f6bd5e7203710d0d5678 resolved-ref: "0ac9108db2f475c5b685af9eb9df393dcc978820"
url: "https://github.com/rafael-xmr/sp_scanner" url: "https://github.com/rafael-xmr/sp_scanner"
source: git source: git
version: "0.0.1" version: "0.0.1"

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

@ -20,7 +20,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

@ -15,3 +15,4 @@ 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

@ -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,8 +87,7 @@ 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,
@ -90,8 +102,8 @@ class WalletInfo extends HiveObject {
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

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

@ -468,9 +468,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:
@ -643,10 +643,10 @@ packages:
dependency: transitive dependency: transitive
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_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:

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:
@ -667,10 +667,10 @@ packages:
dependency: transitive dependency: transitive
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_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:

View file

@ -43,7 +43,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,
_client = NanoClient(), _client = NanoClient(),
walletAddresses = NanoWalletAddresses(walletInfo), walletAddresses = NanoWalletAddresses(walletInfo),
@ -389,7 +389,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

@ -28,11 +28,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!,
@ -88,9 +88,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
@ -128,9 +125,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:
@ -793,10 +793,10 @@ packages:
dependency: transitive dependency: transitive
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_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:

View file

@ -35,9 +35,6 @@ class SolanaTransactionInfo extends TransactionInfo {
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';
@ -108,7 +109,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}";
@ -135,8 +146,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);
@ -262,32 +273,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);
@ -301,13 +292,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,
@ -449,12 +447,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 {
@ -469,10 +478,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}) => {required String name, WalletInfo? walletInfo}) =>
BitcoinNewWalletCredentials(name: name, walletInfo: walletInfo); BitcoinNewWalletCredentials(name: name, walletInfo: walletInfo);
@override
TransactionPriority getMediumTransactionPriority() => BitcoinTransactionPriority.medium;
@override @override
List<String> getWordList() => wordlist; List<String> getWordList() => wordlist;
@ -253,6 +265,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;
@ -287,16 +430,23 @@ 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();
}
@override @override
List<BitcoinSilentPaymentAddressRecord> getSilentPaymentAddresses(Object wallet) { List<BitcoinSilentPaymentAddressRecord> getSilentPaymentAddresses(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet; final bitcoinWallet = wallet as ElectrumWallet;

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,27 +1,28 @@
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 {
WalletLoadingService( WalletLoadingService(this.sharedPreferences, this.keyService, this.walletServiceFactory);
this.sharedPreferences, this.keyService, this.walletServiceFactory);
final SharedPreferences sharedPreferences; final SharedPreferences sharedPreferences;
final KeyService keyService; final KeyService keyService;
final WalletService Function(WalletType type) walletServiceFactory; final WalletService Function(WalletType type) walletServiceFactory;
Future<void> renameWallet( Future<void> renameWallet(WalletType type, String name, String newName) async {
WalletType type, String name, String newName) async {
final walletService = walletServiceFactory.call(type); final walletService = walletServiceFactory.call(type);
final password = await keyService.getWalletPassword(walletName: name); final password = await keyService.getWalletPassword(walletName: name);
// Save the current wallet's password to the new wallet name's key // Save the current wallet's password to the new wallet name's key
await keyService.saveWalletPassword( await keyService.saveWalletPassword(walletName: newName, password: password);
walletName: newName, password: password);
// Delete previous wallet name from keyService to keep only new wallet's name // Delete previous wallet name from keyService to keep only new wallet's name
// otherwise keeps duplicate (old and new names) // otherwise keeps duplicate (old and new names)
await keyService.deleteWalletPassword(walletName: name); await keyService.deleteWalletPassword(walletName: name);
@ -38,6 +39,7 @@ class WalletLoadingService {
} }
Future<WalletBase> load(WalletType type, String name) async { Future<WalletBase> load(WalletType type, String name) async {
try {
final walletService = walletServiceFactory.call(type); final walletService = walletServiceFactory.call(type);
final password = await keyService.getWalletPassword(walletName: name); final password = await keyService.getWalletPassword(walletName: name);
final wallet = await walletService.openWallet(name, password); final wallet = await walletService.openWallet(name, password);
@ -47,6 +49,33 @@ class WalletLoadingService {
} }
return 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 password = await keyService.getWalletPassword(walletName: walletInfo.name);
final wallet = await walletService.openWallet(walletInfo.name, password);
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;
}
} }
Future<void> updateMoneroWalletPassword(WalletBase wallet) async { Future<void> updateMoneroWalletPassword(WalletBase wallet) async {
@ -61,11 +90,9 @@ class WalletLoadingService {
// Save new generated password with backup key for case where // Save new generated password with backup key for case where
// wallet will change password, but it will fail to update in secure storage // wallet will change password, but it will fail to update in secure storage
final bakWalletName = '#__${wallet.name}_bak__#'; final bakWalletName = '#__${wallet.name}_bak__#';
await keyService.saveWalletPassword( await keyService.saveWalletPassword(walletName: bakWalletName, password: password);
walletName: bakWalletName, password: password);
await wallet.changePassword(password); await wallet.changePassword(password);
await keyService.saveWalletPassword( await keyService.saveWalletPassword(walletName: wallet.name, password: password);
walletName: wallet.name, password: password);
isPasswordUpdated = true; isPasswordUpdated = true;
await sharedPreferences.setBool(key, isPasswordUpdated); await sharedPreferences.setBool(key, isPasswordUpdated);
} }

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;
} }
@ -756,6 +760,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

@ -142,8 +142,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

@ -102,6 +102,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());
} }
@ -163,7 +167,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

@ -140,8 +140,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

@ -110,8 +110,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

@ -95,21 +95,21 @@ class NodeForm extends StatelessWidget {
) )
], ],
), ),
// if () ...[
// SizedBox(height: 10.0),
// Row(
// children: <Widget>[
// Expanded(
// child: BaseTextFormField(
// controller: _pathController,
// hintText: "/path",
// validator: NodePathValidator(),
// ),
// )
// ],
// ),
// ],
SizedBox(height: 10.0), SizedBox(height: 10.0),
if (nodeViewModel.hasPathSupport) ...[
Row(
children: <Widget>[
Expanded(
child: BaseTextFormField(
controller: _pathController,
hintText: "/path",
validator: NodePathValidator(),
),
)
],
),
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.seedTypeViewModel, required this.seedTypeViewModel,
this.blockHeightFocusNode, this.blockHeightFocusNode,
@ -31,6 +32,7 @@ class WalletRestoreFromSeedForm extends StatefulWidget {
final WalletType type; final WalletType type;
final bool displayLanguageSelector; final bool displayLanguageSelector;
final bool displayBlockHeightSelector; final bool displayBlockHeightSelector;
final bool displayPassphrase;
final SeedTypeViewModel seedTypeViewModel; final SeedTypeViewModel seedTypeViewModel;
final FocusNode? blockHeightFocusNode; final FocusNode? blockHeightFocusNode;
final Function(bool)? onHeightOrDateEntered; final Function(bool)? onHeightOrDateEntered;
@ -48,6 +50,7 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
formKey = GlobalKey<FormState>(), formKey = GlobalKey<FormState>(),
languageController = TextEditingController(), languageController = TextEditingController(),
nameTextEditingController = TextEditingController(), nameTextEditingController = TextEditingController(),
passphraseController = TextEditingController(),
seedTypeController = TextEditingController(); seedTypeController = TextEditingController();
final GlobalKey<SeedWidgetState> seedWidgetStateKey; final GlobalKey<SeedWidgetState> seedWidgetStateKey;
@ -55,6 +58,7 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
final TextEditingController languageController; final TextEditingController languageController;
final TextEditingController nameTextEditingController; final TextEditingController nameTextEditingController;
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;
@ -194,6 +198,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,
@ -99,8 +96,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) {
@ -298,6 +297,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) {
@ -318,58 +322,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();
@ -398,51 +355,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) { if (derivationsWithHistory > 1) {
derivationInfo = await Navigator.of(context).pushNamed(Routes.restoreWalletChooseDerivation, dInfo = await Navigator.of(context).pushNamed(
arguments: derivations) as DerivationInfo?; Routes.restoreWalletChooseDerivation,
arguments: derivations,
) as DerivationInfo?;
} else if (derivationsWithHistory == 1) { } else if (derivationsWithHistory == 1) {
derivationInfo = derivations[derivationWithHistoryIndex]; dInfo = derivations[derivationWithHistoryIndex];
} else if (derivationsWithHistory == 0) {
// default derivation:
derivationInfo = DerivationInfo(
derivationType: derivationTypes[0],
derivationPath: "m/0'/1",
height: 0,
);
} }
if (derivationInfo == null) { // get the default derivation for this wallet type:
walletRestoreViewModel.state = InitialExecutionState(); if (dInfo == null) {
return; // we only return 1 derivation if we're pretty sure we know which one to use:
} if (derivations.length == 1) {
this.derivationType = derivationInfo.derivationType; dInfo = derivations.first;
this.derivationPath = derivationInfo.derivationPath;
} else { } else {
// electrum derivation: // if we have multiple possible derivations, and none have histories
this.derivationType = derivationTypes[0]; // we just default to the most common one:
this.derivationPath = "m/0'/1"; dInfo = walletRestoreViewModel.getCommonRestoreDerivation();
}
} }
walletRestoreViewModel.state = InitialExecutionState(); this.derivationInfo = dInfo;
if (this.derivationInfo == null) {
this.derivationInfo = walletRestoreViewModel.getDefaultDerivation();
}
walletRestoreViewModel.create(options: _credentials()); walletRestoreViewModel.create(options: _credentials());
} }

View file

@ -101,9 +101,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

@ -350,8 +350,10 @@ class WalletListBodyState extends State<WalletListBody> {
}); });
} }
} catch (e) { } catch (e) {
if (this.mounted) {
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString())); changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
} }
}
}, },
conditionToDetermineIfToUse2FA: conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForAccessingWallet, widget.walletListViewModel.shouldRequireTOTP2FAForAccessingWallet,

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,
@ -57,6 +58,7 @@ class BaseTextFormField extends StatelessWidget {
final String? initialValue; final String? initialValue;
final double borderWidth; final double borderWidth;
final void Function(String)? onSubmit; final void Function(String)? onSubmit;
final bool obscureText;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -70,6 +72,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

@ -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 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 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; return false;
} }
} }
return true;
}
} }

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:
@ -89,20 +96,34 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
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}');
} }
@ -557,7 +566,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,11 +390,7 @@ 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,

View file

@ -71,9 +71,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)
@ -89,6 +89,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 = generateWalletPassword(); final password = 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

@ -416,6 +416,7 @@
"outputs": "المخرجات", "outputs": "المخرجات",
"overwrite_amount": "تغير المبلغ", "overwrite_amount": "تغير المبلغ",
"pairingInvalidEvent": "ﺢﻟﺎﺻ ﺮﻴﻏ ﺙﺪﺣ ﻥﺍﺮﻗﺇ", "pairingInvalidEvent": "ﺢﻟﺎﺻ ﺮﻴﻏ ﺙﺪﺣ ﻥﺍﺮﻗﺇ",
"passphrase": "عبارة الممر (اختياري)",
"password": "كلمة المرور", "password": "كلمة المرور",
"paste": "لصق", "paste": "لصق",
"pause_wallet_creation": ".ﺎﻴًﻟﺎﺣ ﺎﺘًﻗﺆﻣ ﺔﻔﻗﻮﺘﻣ Haven Wallet ءﺎﺸﻧﺇ ﻰﻠﻋ ﺓﺭﺪﻘﻟﺍ", "pause_wallet_creation": ".ﺎﻴًﻟﺎﺣ ﺎﺘًﻗﺆﻣ ﺔﻔﻗﻮﺘﻣ Haven Wallet ءﺎﺸﻧﺇ ﻰﻠﻋ ﺓﺭﺪﻘﻟﺍ",
@ -676,6 +677,7 @@
"testnet_coins_no_value": "عملات TestNet ليس لها قيمة", "testnet_coins_no_value": "عملات TestNet ليس لها قيمة",
"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

@ -416,6 +416,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 в момента е на пауза.",
@ -676,6 +677,7 @@
"testnet_coins_no_value": "Тестовите монети нямат стойност", "testnet_coins_no_value": "Тестовите монети нямат стойност",
"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

@ -416,6 +416,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.",
@ -676,6 +677,7 @@
"testnet_coins_no_value": "Mince TestNet nemají žádnou hodnotu", "testnet_coins_no_value": "Mince TestNet nemají žádnou hodnotu",
"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",
@ -89,7 +89,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.",
@ -122,7 +122,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",
@ -202,7 +202,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",
@ -416,6 +416,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.",
@ -430,8 +431,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",
@ -464,8 +465,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",
@ -538,7 +539,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",
@ -590,7 +591,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",
@ -677,6 +678,7 @@
"testnet_coins_no_value": "Testnet -Münzen haben keinen Wert", "testnet_coins_no_value": "Testnet -Münzen haben keinen Wert",
"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

@ -416,6 +416,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.",
@ -676,6 +677,7 @@
"testnet_coins_no_value": "Testnet coins have no value", "testnet_coins_no_value": "Testnet coins have no value",
"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

@ -416,6 +416,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.",
@ -677,6 +678,7 @@
"testnet_coins_no_value": "Las monedas de prueba no tienen valor", "testnet_coins_no_value": "Las monedas de prueba no tienen valor",
"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

@ -416,6 +416,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.",
@ -676,6 +677,7 @@
"testnet_coins_no_value": "Les pièces TestNet n'ont aucune valeur", "testnet_coins_no_value": "Les pièces TestNet n'ont aucune valeur",
"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

@ -418,6 +418,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.",
@ -678,6 +679,7 @@
"testnet_coins_no_value": "TalkNet tsabar kudi ba su da darajar", "testnet_coins_no_value": "TalkNet tsabar kudi ba su da darajar",
"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

@ -416,6 +416,7 @@
"outputs": "आउटपुट", "outputs": "आउटपुट",
"overwrite_amount": "Overwrite amount", "overwrite_amount": "Overwrite amount",
"pairingInvalidEvent": "अमान्य ईवेंट युग्मित करना", "pairingInvalidEvent": "अमान्य ईवेंट युग्मित करना",
"passphrase": "पासफ्रेज़ (वैकल्पिक)",
"password": "पारण शब्द", "password": "पारण शब्द",
"paste": "पेस्ट करें", "paste": "पेस्ट करें",
"pause_wallet_creation": "हेवन वॉलेट बनाने की क्षमता फिलहाल रुकी हुई है।", "pause_wallet_creation": "हेवन वॉलेट बनाने की क्षमता फिलहाल रुकी हुई है।",
@ -678,6 +679,7 @@
"testnet_coins_no_value": "टेस्टनेट सिक्कों का कोई मूल्य नहीं है", "testnet_coins_no_value": "टेस्टनेट सिक्कों का कोई मूल्य नहीं है",
"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

@ -416,6 +416,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.",
@ -676,6 +677,7 @@
"testnet_coins_no_value": "TestNet kovanice nemaju vrijednost", "testnet_coins_no_value": "TestNet kovanice nemaju vrijednost",
"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

@ -418,6 +418,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.",
@ -679,6 +680,7 @@
"testnet_coins_no_value": "Koin TestNet tidak memiliki nilai", "testnet_coins_no_value": "Koin TestNet tidak memiliki nilai",
"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

@ -418,6 +418,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.",
@ -678,6 +679,7 @@
"testnet_coins_no_value": "Le monete TestNet non hanno valore", "testnet_coins_no_value": "Le monete TestNet non hanno valore",
"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

@ -417,6 +417,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 を作成する機能は現在一時停止されています。",
@ -677,6 +678,7 @@
"testnet_coins_no_value": "テストネットコインには価値がありません", "testnet_coins_no_value": "テストネットコインには価値がありません",
"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

@ -416,6 +416,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 생성 기능이 현재 일시 중지되었습니다.",
@ -430,8 +431,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": "다른 노드에 연결을 시도하십시오",
@ -677,6 +678,7 @@
"testnet_coins_no_value": "Testnet 코인은 가치가 없습니다", "testnet_coins_no_value": "Testnet 코인은 가치가 없습니다",
"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

@ -416,6 +416,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 ဖန်တီးနိုင်မှုကို လောလောဆယ် ခေတ္တရပ်ထားသည်။",
@ -676,6 +677,7 @@
"testnet_coins_no_value": "Testnet ဒင်္ဂါးပြားတန်ဖိုးမရှိပါ", "testnet_coins_no_value": "Testnet ဒင်္ဂါးပြားတန်ဖိုးမရှိပါ",
"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

@ -416,6 +416,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.",
@ -676,6 +677,7 @@
"testnet_coins_no_value": "Testnet -munten hebben geen waarde", "testnet_coins_no_value": "Testnet -munten hebben geen waarde",
"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

@ -416,6 +416,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.",
@ -676,6 +677,7 @@
"testnet_coins_no_value": "Monety testowe nie mają wartości", "testnet_coins_no_value": "Monety testowe nie mają wartości",
"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

@ -418,6 +418,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.",
@ -678,6 +679,7 @@
"testnet_coins_no_value": "As moedas de teste não têm valor", "testnet_coins_no_value": "As moedas de teste não têm valor",
"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

@ -417,6 +417,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 в настоящее время приостановлена.",
@ -677,6 +678,7 @@
"testnet_coins_no_value": "Монеты теста не имеют значения", "testnet_coins_no_value": "Монеты теста не имеют значения",
"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

@ -416,6 +416,7 @@
"outputs": "เอาต์พุต", "outputs": "เอาต์พุต",
"overwrite_amount": "เขียนทับจำนวน", "overwrite_amount": "เขียนทับจำนวน",
"pairingInvalidEvent": "การจับคู่เหตุการณ์ที่ไม่ถูกต้อง", "pairingInvalidEvent": "การจับคู่เหตุการณ์ที่ไม่ถูกต้อง",
"passphrase": "ข้อความรหัสผ่าน (ไม่บังคับ)",
"password": "รหัสผ่าน", "password": "รหัสผ่าน",
"paste": "วาง", "paste": "วาง",
"pause_wallet_creation": "ขณะนี้ความสามารถในการสร้าง Haven Wallet ถูกหยุดชั่วคราว", "pause_wallet_creation": "ขณะนี้ความสามารถในการสร้าง Haven Wallet ถูกหยุดชั่วคราว",
@ -676,6 +677,7 @@
"testnet_coins_no_value": "Testnet Coins ไม่มีค่า", "testnet_coins_no_value": "Testnet Coins ไม่มีค่า",
"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

@ -416,6 +416,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.",
@ -676,6 +677,7 @@
"testnet_coins_no_value": "Ang mga barya ng testnet ay walang halaga", "testnet_coins_no_value": "Ang mga barya ng testnet ay walang halaga",
"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:",

View file

@ -416,6 +416,7 @@
"outputs": "çıktılar", "outputs": "çıktılar",
"overwrite_amount": "Miktarın üzerine yaz", "overwrite_amount": "Miktarın üzerine yaz",
"pairingInvalidEvent": "Geçersiz Etkinliği Eşleştirme", "pairingInvalidEvent": "Geçersiz Etkinliği Eşleştirme",
"passphrase": "Passfrase (isteğe bağlı)",
"password": "Parola", "password": "Parola",
"paste": "Yapıştır", "paste": "Yapıştır",
"pause_wallet_creation": "Haven Cüzdanı oluşturma yeteneği şu anda duraklatıldı.", "pause_wallet_creation": "Haven Cüzdanı oluşturma yeteneği şu anda duraklatıldı.",
@ -676,6 +677,7 @@
"testnet_coins_no_value": "TestNet paralarının değeri yok", "testnet_coins_no_value": "TestNet paralarının değeri yok",
"third_intro_content": "Yat'lar Cake Wallet'ın dışında da çalışabilir. Dünya üzerindeki herhangi bir cüzdan adresi Yat ile değiştirilebilir!", "third_intro_content": "Yat'lar Cake Wallet'ın dışında da çalışabilir. Dünya üzerindeki herhangi bir cüzdan adresi Yat ile değiştirilebilir!",
"third_intro_title": "Yat diğerleriyle iyi çalışır", "third_intro_title": "Yat diğerleriyle iyi çalışır",
"thorchain_contract_address_not_supported": "Thorchain bir sözleşme adresine göndermeyi desteklemiyor",
"thorchain_taproot_address_not_supported": "Thorchain sağlayıcısı Taproot adreslerini desteklemiyor. Lütfen adresi değiştirin veya farklı bir sağlayıcı seçin.", "thorchain_taproot_address_not_supported": "Thorchain sağlayıcısı Taproot adreslerini desteklemiyor. Lütfen adresi değiştirin veya farklı bir sağlayıcı seçin.",
"time": "${minutes}d ${seconds}s", "time": "${minutes}d ${seconds}s",
"tip": "Bahşiş:", "tip": "Bahşiş:",

View file

@ -416,6 +416,7 @@
"outputs": "Виходи", "outputs": "Виходи",
"overwrite_amount": "Overwrite amount", "overwrite_amount": "Overwrite amount",
"pairingInvalidEvent": "Недійсна подія сполучення", "pairingInvalidEvent": "Недійсна подія сполучення",
"passphrase": "Пасофрази (необов’язково)",
"password": "Пароль", "password": "Пароль",
"paste": "Вставити", "paste": "Вставити",
"pause_wallet_creation": "Можливість створення гаманця Haven зараз призупинено.", "pause_wallet_creation": "Можливість створення гаманця Haven зараз призупинено.",
@ -677,6 +678,7 @@
"testnet_coins_no_value": "Монети TestNet не мають значення", "testnet_coins_no_value": "Монети TestNet не мають значення",
"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

@ -418,6 +418,7 @@
"outputs": "نتائج", "outputs": "نتائج",
"overwrite_amount": "رقم کو اوور رائٹ کریں۔", "overwrite_amount": "رقم کو اوور رائٹ کریں۔",
"pairingInvalidEvent": "ﭧﻧﻮﯾﺍ ﻂﻠﻏ ﺎﻧﺎﻨﺑ ﺍﮌﻮﺟ", "pairingInvalidEvent": "ﭧﻧﻮﯾﺍ ﻂﻠﻏ ﺎﻧﺎﻨﺑ ﺍﮌﻮﺟ",
"passphrase": "پاسفریز (اختیاری)",
"password": "پاس ورڈ", "password": "پاس ورڈ",
"paste": "چسپاں کریں۔", "paste": "چسپاں کریں۔",
"pause_wallet_creation": "Haven Wallet ۔ﮯﮨ ﻑﻮﻗﻮﻣ ﻝﺎﺤﻟﺍ ﯽﻓ ﺖﯿﻠﮨﺍ ﯽﮐ ﮯﻧﺎﻨﺑ", "pause_wallet_creation": "Haven Wallet ۔ﮯﮨ ﻑﻮﻗﻮﻣ ﻝﺎﺤﻟﺍ ﯽﻓ ﺖﯿﻠﮨﺍ ﯽﮐ ﮯﻧﺎﻨﺑ",
@ -678,6 +679,7 @@
"testnet_coins_no_value": "ٹیسٹ نیٹ سکے کی کوئی قیمت نہیں ہے", "testnet_coins_no_value": "ٹیسٹ نیٹ سکے کی کوئی قیمت نہیں ہے",
"third_intro_content": "Yats بھی Cake والیٹ سے باہر رہتے ہیں۔ زمین پر کسی بھی بٹوے کے پتے کو Yat سے تبدیل کیا جا سکتا ہے!", "third_intro_content": "Yats بھی Cake والیٹ سے باہر رہتے ہیں۔ زمین پر کسی بھی بٹوے کے پتے کو 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

@ -417,6 +417,7 @@
"outputs": "Awọn iṣan", "outputs": "Awọn iṣan",
"overwrite_amount": "Pààrọ̀ iye owó", "overwrite_amount": "Pààrọ̀ iye owó",
"pairingInvalidEvent": "Pipọpọ Iṣẹlẹ Ti ko tọ", "pairingInvalidEvent": "Pipọpọ Iṣẹlẹ Ti ko tọ",
"passphrase": "Ọrọ kukuru (iyan)",
"password": "Ọ̀rọ̀ aṣínà", "password": "Ọ̀rọ̀ aṣínà",
"paste": "Fikún ẹ̀dà yín", "paste": "Fikún ẹ̀dà yín",
"pause_wallet_creation": "Agbara lati ṣẹda Haven Wallet ti wa ni idaduro lọwọlọwọ.", "pause_wallet_creation": "Agbara lati ṣẹda Haven Wallet ti wa ni idaduro lọwọlọwọ.",
@ -677,6 +678,7 @@
"testnet_coins_no_value": "Awọn aṣọ irekọja ko ni iye", "testnet_coins_no_value": "Awọn aṣọ irekọja ko ni iye",
"third_intro_content": "A sì lè lo Yats níta Cake Wallet. A lè rọ́pò Àdírẹ́sì kankan àpamọ́wọ́ fún Yat!", "third_intro_content": "A sì lè lo Yats níta Cake Wallet. A lè rọ́pò Àdírẹ́sì kankan àpamọ́wọ́ fún Yat!",
"third_intro_title": "Àlàáfíà ni Yat àti àwọn ìmíìn jọ wà", "third_intro_title": "Àlàáfíà ni Yat àti àwọn ìmíìn jọ wà",
"thorchain_contract_address_not_supported": "Thorchain ko ṣe atilẹyin fifiranṣẹ si adirẹsi adehun kan",
"thorchain_taproot_address_not_supported": "Olupese Trockchain ko ṣe atilẹyin awọn adirẹsi Taproot. Jọwọ yi adirẹsi pada tabi yan olupese ti o yatọ.", "thorchain_taproot_address_not_supported": "Olupese Trockchain ko ṣe atilẹyin awọn adirẹsi Taproot. Jọwọ yi adirẹsi pada tabi yan olupese ti o yatọ.",
"time": "${minutes}ìṣj ${seconds}ìṣs", "time": "${minutes}ìṣj ${seconds}ìṣs",
"tip": "Owó àfikún:", "tip": "Owó àfikún:",

View file

@ -416,6 +416,7 @@
"outputs": "输出", "outputs": "输出",
"overwrite_amount": "Overwrite amount", "overwrite_amount": "Overwrite amount",
"pairingInvalidEvent": "配对无效事件", "pairingInvalidEvent": "配对无效事件",
"passphrase": "密码(可选)",
"password": "密码", "password": "密码",
"paste": "粘贴", "paste": "粘贴",
"pause_wallet_creation": "创建 Haven 钱包的功能当前已暂停。", "pause_wallet_creation": "创建 Haven 钱包的功能当前已暂停。",
@ -676,6 +677,7 @@
"testnet_coins_no_value": "TestNet硬币没有价值", "testnet_coins_no_value": "TestNet硬币没有价值",
"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": "提示:",

30
run-android.sh Executable file
View file

@ -0,0 +1,30 @@
#!/bin/bash
# Get the current git branch
get_current_branch() {
if git rev-parse --git-dir > /dev/null 2>&1; then
branch=$(git rev-parse --abbrev-ref HEAD)
echo "$branch"
else
echo "Error: Not a git repository."
return 1
fi
}
# Update the app.properties file
update_app_properties() {
local branch=$1
local file_path="./android/app.properties"
sed -i "s/^id=.*/id=com.cakewallet.$branch/" "$file_path"
sed -i "s/^name=.*/name=$branch-Cake Wallet/" "$file_path"
}
# only update app.properties if getting the current branch was successful
current_branch=$(get_current_branch)
if [[ $? -eq 0 ]]; then
update_app_properties "$current_branch"
fi
# run the app
flutter run

View file

@ -15,15 +15,15 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
APP_ANDROID_TYPE=$1 APP_ANDROID_TYPE=$1
MONERO_COM_NAME="Monero.com" MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="1.12.2" MONERO_COM_VERSION="1.12.3"
MONERO_COM_BUILD_NUMBER=82 MONERO_COM_BUILD_NUMBER=84
MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_BUNDLE_ID="com.monero.app"
MONERO_COM_PACKAGE="com.monero.app" MONERO_COM_PACKAGE="com.monero.app"
MONERO_COM_SCHEME="monero.com" MONERO_COM_SCHEME="monero.com"
CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.15.4" CAKEWALLET_VERSION="4.15.5"
CAKEWALLET_BUILD_NUMBER=204 CAKEWALLET_BUILD_NUMBER=206
CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"
CAKEWALLET_SCHEME="cakewallet" CAKEWALLET_SCHEME="cakewallet"

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