mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 19:49:22 +00:00
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into cw_linux_direct_input_password
Conflicts: cw_bitcoin/lib/bitcoin_wallet.dart cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart cw_bitcoin/lib/electrum_wallet.dart cw_bitcoin/lib/litecoin_wallet.dart cw_bitcoin/pubspec.lock cw_monero/example/pubspec.lock lib/core/wallet_loading_service.dart lib/src/screens/restore/wallet_restore_from_seed_form.dart lib/view_model/wallet_restore_view_model.dart
This commit is contained in:
commit
6b0170e69a
110 changed files with 1506 additions and 652 deletions
|
@ -6,4 +6,4 @@
|
|||
uri: workers.perish.co
|
||||
-
|
||||
uri: worker.nanoriver.cc
|
||||
useSSL: true
|
||||
useSSL: true
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
UI enhancements
|
||||
Bug fixes
|
||||
Generic bug fixes and enhancements
|
|
@ -1,7 +1,3 @@
|
|||
Add Replace-By-Fee to boost pending Bitcoin transactions
|
||||
Enable WalletConnect for Solana
|
||||
WalletConnect Enhancements
|
||||
Enhancements for ERC-20 tokens and Solana tokens
|
||||
Enhancements for Nano wallet
|
||||
UI enhancements
|
||||
Bug fixes
|
||||
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)
|
||||
Bitcoin enhancements and bug fixes
|
||||
Generic bug fixes and enhancements
|
|
@ -23,14 +23,4 @@ source ./app_env.sh cakewallet
|
|||
./app_config.sh
|
||||
cd ../.. && flutter pub get
|
||||
flutter packages pub run tool/generate_localization.dart
|
||||
cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
cd cw_evm && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
cd cw_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
|
||||
./model_generator.sh
|
||||
|
|
|
@ -90,8 +90,7 @@ List<bool> prefixMatches(String source, List<String> prefixes) {
|
|||
return prefixes.map((prefix) => hx.startsWith(prefix.toLowerCase())).toList();
|
||||
}
|
||||
|
||||
Future<String> generateMnemonic(
|
||||
{int strength = 264, String prefix = segwit}) async {
|
||||
Future<String> generateElectrumMnemonic({int strength = 264, String prefix = segwit}) async {
|
||||
final wordBitlen = logBase(wordlist.length, 2).ceil();
|
||||
final wordCount = strength / wordBitlen;
|
||||
final byteCount = ((wordCount * wordBitlen).ceil() / 8).ceil();
|
||||
|
@ -106,22 +105,29 @@ Future<String> generateMnemonic(
|
|||
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 {
|
||||
final pbkdf2 = cryptography.Pbkdf2(
|
||||
macAlgorithm: cryptography.Hmac.sha512(),
|
||||
iterations: 2048,
|
||||
bits: 512);
|
||||
final pbkdf2 =
|
||||
cryptography.Pbkdf2(macAlgorithm: cryptography.Hmac.sha512(), iterations: 2048, bits: 512);
|
||||
final text = normalizeText(mnemonic);
|
||||
// pbkdf2.deriveKey(secretKey: secretKey, nonce: nonce)
|
||||
final key = await pbkdf2.deriveKey(
|
||||
secretKey: cryptography.SecretKey(text.codeUnits),
|
||||
nonce: 'electrum'.codeUnits);
|
||||
secretKey: cryptography.SecretKey(text.codeUnits), nonce: 'electrum'.codeUnits);
|
||||
final bytes = await key.extractBytes();
|
||||
return Uint8List.fromList(bytes);
|
||||
}
|
||||
|
||||
bool matchesAnyPrefix(String mnemonic) =>
|
||||
prefixMatches(mnemonic, [segwit]).any((el) => el);
|
||||
bool matchesAnyPrefix(String mnemonic) => prefixMatches(mnemonic, [segwit]).any((el) => el);
|
||||
|
||||
bool validateMnemonic(String mnemonic, {String prefix = segwit}) {
|
||||
try {
|
||||
|
@ -208,10 +214,8 @@ String removeCJKSpaces(String source) {
|
|||
}
|
||||
|
||||
String normalizeText(String source) {
|
||||
final res = removeCombiningCharacters(unorm.nfkd(source).toLowerCase())
|
||||
.trim()
|
||||
.split('/\s+/')
|
||||
.join(' ');
|
||||
final res =
|
||||
removeCombiningCharacters(unorm.nfkd(source).toLowerCase()).trim().split('/\s+/').join(' ');
|
||||
|
||||
return removeCJKSpaces(res);
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ class BitcoinTransactionPriority extends TransactionPriority {
|
|||
|
||||
switch (this) {
|
||||
case BitcoinTransactionPriority.slow:
|
||||
label = 'Slow ~24hrs'; // '${S.current.transaction_priority_slow} ~24hrs';
|
||||
label = 'Slow ~24hrs+'; // '${S.current.transaction_priority_slow} ~24hrs';
|
||||
break;
|
||||
case BitcoinTransactionPriority.medium:
|
||||
label = 'Medium'; // S.current.transaction_priority_medium;
|
||||
|
|
|
@ -13,6 +13,7 @@ import 'package:cw_core/wallet_info.dart';
|
|||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
|
||||
part 'bitcoin_wallet.g.dart';
|
||||
|
||||
|
@ -32,8 +33,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
ElectrumBalance? initialBalance,
|
||||
Map<String, int>? initialRegularAddressIndex,
|
||||
Map<String, int>? initialChangeAddressIndex,
|
||||
String? passphrase,
|
||||
}) : super(
|
||||
mnemonic: mnemonic,
|
||||
passphrase: passphrase,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
|
@ -47,14 +50,19 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
seedBytes: seedBytes,
|
||||
currency: CryptoCurrency.btc,
|
||||
encryptionFileUtils: encryptionFileUtils) {
|
||||
// in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here)
|
||||
// the sideHd derivation path = m/84'/0'/0'/1 (account 1, index unspecified here)
|
||||
String derivationPath = walletInfo.derivationInfo!.derivationPath!;
|
||||
String sideDerivationPath = derivationPath.substring(0, derivationPath.length - 1) + "1";
|
||||
final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType);
|
||||
walletAddresses = BitcoinWalletAddresses(
|
||||
walletInfo,
|
||||
electrumClient: electrumClient,
|
||||
initialAddresses: initialAddresses,
|
||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
mainHd: hd,
|
||||
sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"),
|
||||
mainHd: hd.derivePath(derivationPath),
|
||||
sideHd: hd.derivePath(sideDerivationPath),
|
||||
network: networkParam ?? network,
|
||||
);
|
||||
autorun((_) {
|
||||
|
@ -68,6 +76,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
required WalletInfo walletInfo,
|
||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
required EncryptionFileUtils encryptionFileUtils,
|
||||
String? passphrase,
|
||||
String? addressPageType,
|
||||
BasedUtxoNetwork? network,
|
||||
List<BitcoinAddressRecord>? initialAddresses,
|
||||
|
@ -75,15 +84,30 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
Map<String, int>? initialRegularAddressIndex,
|
||||
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 BitcoinWallet(
|
||||
mnemonic: mnemonic,
|
||||
passphrase: passphrase ?? "",
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
initialAddresses: initialAddresses,
|
||||
initialBalance: initialBalance,
|
||||
encryptionFileUtils: encryptionFileUtils,
|
||||
seedBytes: await mnemonicToSeedBytes(mnemonic),
|
||||
seedBytes: seedBytes,
|
||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
addressPageType: addressPageType,
|
||||
|
@ -103,15 +127,39 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
: BitcoinNetwork.mainnet;
|
||||
final snp = await ElectrumWalletSnapshot.load(encryptionFileUtils, name, walletInfo.type, password, network);
|
||||
|
||||
walletInfo.derivationInfo ??= DerivationInfo(
|
||||
derivationType: snp.derivationType ?? DerivationType.electrum,
|
||||
derivationPath: snp.derivationPath,
|
||||
);
|
||||
|
||||
// set the default if not present:
|
||||
walletInfo.derivationInfo!.derivationPath = snp.derivationPath ?? "m/0'/1";
|
||||
|
||||
late Uint8List seedBytes;
|
||||
|
||||
switch (walletInfo.derivationInfo!.derivationType) {
|
||||
case DerivationType.electrum:
|
||||
seedBytes = await mnemonicToSeedBytes(snp.mnemonic);
|
||||
break;
|
||||
case DerivationType.bip39:
|
||||
default:
|
||||
seedBytes = await bip39.mnemonicToSeed(
|
||||
snp.mnemonic,
|
||||
passphrase: snp.passphrase ?? '',
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return BitcoinWallet(
|
||||
mnemonic: snp.mnemonic,
|
||||
password: password,
|
||||
passphrase: snp.passphrase,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
initialAddresses: snp.addresses,
|
||||
initialBalance: snp.balance,
|
||||
seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
|
||||
encryptionFileUtils: encryptionFileUtils,
|
||||
seedBytes: seedBytes,
|
||||
initialRegularAddressIndex: snp.regularAddressIndex,
|
||||
initialChangeAddressIndex: snp.changeAddressIndex,
|
||||
addressPageType: snp.addressPageType,
|
||||
|
|
|
@ -2,13 +2,25 @@ import 'package:cw_core/wallet_credentials.dart';
|
|||
import 'package:cw_core/wallet_info.dart';
|
||||
|
||||
class BitcoinNewWalletCredentials extends WalletCredentials {
|
||||
BitcoinNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password})
|
||||
: super(name: name, walletInfo: walletInfo, password: password);
|
||||
BitcoinNewWalletCredentials(
|
||||
{required String name,
|
||||
WalletInfo? walletInfo,
|
||||
String? password,
|
||||
DerivationType? derivationType,
|
||||
String? derivationPath})
|
||||
: super(
|
||||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
password: password,
|
||||
);
|
||||
}
|
||||
|
||||
class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||
BitcoinRestoreWalletFromSeedCredentials(
|
||||
{required String name, required String password, required this.mnemonic, WalletInfo? walletInfo})
|
||||
{required String name,
|
||||
required String password,
|
||||
required this.mnemonic,
|
||||
WalletInfo? walletInfo})
|
||||
: super(name: name, password: password, walletInfo: walletInfo);
|
||||
|
||||
final String mnemonic;
|
||||
|
@ -20,4 +32,22 @@ class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials {
|
|||
: super(name: name, password: password, walletInfo: walletInfo);
|
||||
|
||||
final String wif;
|
||||
}
|
||||
}
|
||||
|
||||
BitcoinRestoreWalletFromSeedCredentials({
|
||||
required String name,
|
||||
required String password,
|
||||
required this.mnemonic,
|
||||
WalletInfo? walletInfo,
|
||||
required DerivationType derivationType,
|
||||
required String derivationPath,
|
||||
String? passphrase,
|
||||
}) : super(
|
||||
name: name,
|
||||
password: password,
|
||||
passphrase: passphrase,
|
||||
walletInfo: walletInfo,
|
||||
derivationInfo: DerivationInfo(
|
||||
derivationType: derivationType,
|
||||
derivationPath: derivationPath,
|
||||
));
|
||||
|
|
|
@ -13,6 +13,7 @@ import 'package:cw_core/wallet_info.dart';
|
|||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
|
||||
class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
|
||||
BitcoinRestoreWalletFromSeedCredentials, BitcoinRestoreWalletFromWIFCredentials> {
|
||||
|
@ -31,8 +32,9 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
|
|||
credentials.walletInfo?.network = network.value;
|
||||
|
||||
final wallet = await BitcoinWalletBase.create(
|
||||
mnemonic: await generateMnemonic(),
|
||||
mnemonic: await generateElectrumMnemonic(),
|
||||
password: credentials.password!,
|
||||
passphrase: credentials.passphrase,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
network: network,
|
||||
|
@ -111,7 +113,7 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
|
|||
@override
|
||||
Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials,
|
||||
{bool? isTestnet}) async {
|
||||
if (!validateMnemonic(credentials.mnemonic)) {
|
||||
if (!validateMnemonic(credentials.mnemonic) && !bip39.validateMnemonic(credentials.mnemonic)) {
|
||||
throw BitcoinMnemonicIsIncorrectException();
|
||||
}
|
||||
|
||||
|
@ -120,6 +122,7 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
|
|||
|
||||
final wallet = await BitcoinWalletBase.create(
|
||||
password: credentials.password!,
|
||||
passphrase: credentials.passphrase,
|
||||
mnemonic: credentials.mnemonic,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
|
|
104
cw_bitcoin/lib/electrum_derivations.dart
Normal file
104
cw_bitcoin/lib/electrum_derivations.dart
Normal 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",
|
||||
),
|
||||
],
|
||||
};
|
|
@ -57,13 +57,15 @@ abstract class ElectrumWalletBase
|
|||
required this.mnemonic,
|
||||
required Uint8List seedBytes,
|
||||
required this.encryptionFileUtils,
|
||||
this.passphrase,
|
||||
List<BitcoinAddressRecord>? initialAddresses,
|
||||
ElectrumClient? electrumClient,
|
||||
ElectrumBalance? initialBalance,
|
||||
CryptoCurrency? currency})
|
||||
: hd = currency == CryptoCurrency.bch
|
||||
? 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(),
|
||||
_password = password,
|
||||
_feeRates = <int>[],
|
||||
|
@ -99,6 +101,7 @@ abstract class ElectrumWalletBase
|
|||
final bitcoin.HDWallet hd;
|
||||
final String mnemonic;
|
||||
final EncryptionFileUtils encryptionFileUtils;
|
||||
final String? passphrase;
|
||||
|
||||
@override
|
||||
@observable
|
||||
|
@ -213,10 +216,14 @@ abstract class ElectrumWalletBase
|
|||
List<ECPrivate> privateKeys = [];
|
||||
int allInputsAmount = 0;
|
||||
|
||||
bool spendsUnconfirmedTX = false;
|
||||
|
||||
for (int i = 0; i < unspentCoins.length; i++) {
|
||||
final utx = unspentCoins[i];
|
||||
|
||||
if (utx.isSending) {
|
||||
if (utx.isSending && !utx.isFrozen) {
|
||||
if (!spendsUnconfirmedTX) spendsUnconfirmedTX = utx.confirmations == 0;
|
||||
|
||||
allInputsAmount += utx.value;
|
||||
|
||||
final address = addressTypeFromStr(utx.address, network);
|
||||
|
@ -274,6 +281,10 @@ abstract class ElectrumWalletBase
|
|||
// Here, when sending all, the output amount equals to the input value - fee to fully spend every input on the transaction and have no amount left for change
|
||||
int amount = allInputsAmount - fee;
|
||||
|
||||
if (amount <= 0) {
|
||||
throw BitcoinTransactionWrongBalanceException();
|
||||
}
|
||||
|
||||
// Attempting to send less than the dust limit
|
||||
if (_isBelowDust(amount)) {
|
||||
throw BitcoinTransactionNoDustException();
|
||||
|
@ -298,6 +309,7 @@ abstract class ElectrumWalletBase
|
|||
isSendAll: true,
|
||||
hasChange: false,
|
||||
memo: memo,
|
||||
spendsUnconfirmedTX: spendsUnconfirmedTX,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -307,17 +319,25 @@ abstract class ElectrumWalletBase
|
|||
int feeRate, {
|
||||
int? inputsCount,
|
||||
String? memo,
|
||||
bool? useUnconfirmed,
|
||||
}) async {
|
||||
final utxos = <UtxoWithAddress>[];
|
||||
List<ECPrivate> privateKeys = [];
|
||||
int allInputsAmount = 0;
|
||||
bool spendsUnconfirmedTX = false;
|
||||
|
||||
int leftAmount = credentialsAmount;
|
||||
final sendingCoins = unspentCoins.where((utx) => utx.isSending).toList();
|
||||
final sendingCoins = unspentCoins.where((utx) => utx.isSending && !utx.isFrozen).toList();
|
||||
final unconfirmedCoins = sendingCoins.where((utx) => utx.confirmations == 0).toList();
|
||||
|
||||
for (int i = 0; i < sendingCoins.length; i++) {
|
||||
final utx = sendingCoins[i];
|
||||
|
||||
final isUncormirmed = utx.confirmations == 0;
|
||||
if (useUnconfirmed != true && isUncormirmed) continue;
|
||||
|
||||
if (!spendsUnconfirmedTX) spendsUnconfirmedTX = isUncormirmed;
|
||||
|
||||
allInputsAmount += utx.value;
|
||||
leftAmount = leftAmount - utx.value;
|
||||
|
||||
|
@ -355,11 +375,23 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
final spendingAllCoins = sendingCoins.length == utxos.length;
|
||||
final spendingAllConfirmedCoins =
|
||||
!spendsUnconfirmedTX && utxos.length == sendingCoins.length - unconfirmedCoins.length;
|
||||
|
||||
// How much is being spent - how much is being sent
|
||||
int amountLeftForChangeAndFee = allInputsAmount - credentialsAmount;
|
||||
|
||||
if (amountLeftForChangeAndFee <= 0) {
|
||||
if (!spendingAllCoins) {
|
||||
return estimateTxForAmount(
|
||||
credentialsAmount,
|
||||
outputs,
|
||||
feeRate,
|
||||
inputsCount: utxos.length + 1,
|
||||
memo: memo,
|
||||
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
|
||||
);
|
||||
}
|
||||
throw BitcoinTransactionWrongBalanceException();
|
||||
}
|
||||
|
||||
|
@ -413,6 +445,7 @@ abstract class ElectrumWalletBase
|
|||
feeRate,
|
||||
inputsCount: utxos.length + 1,
|
||||
memo: memo,
|
||||
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -459,6 +492,7 @@ abstract class ElectrumWalletBase
|
|||
feeRate,
|
||||
inputsCount: utxos.length + 1,
|
||||
memo: memo,
|
||||
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -471,6 +505,7 @@ abstract class ElectrumWalletBase
|
|||
hasChange: true,
|
||||
isSendAll: false,
|
||||
memo: memo,
|
||||
spendsUnconfirmedTX: spendsUnconfirmedTX,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -541,7 +576,7 @@ abstract class ElectrumWalletBase
|
|||
network: network,
|
||||
memo: estimatedTx.memo,
|
||||
outputOrdering: BitcoinOrdering.none,
|
||||
enableRBF: true,
|
||||
enableRBF: !estimatedTx.spendsUnconfirmedTX,
|
||||
);
|
||||
} else {
|
||||
txb = BitcoinTransactionBuilder(
|
||||
|
@ -551,7 +586,7 @@ abstract class ElectrumWalletBase
|
|||
network: network,
|
||||
memo: estimatedTx.memo,
|
||||
outputOrdering: BitcoinOrdering.none,
|
||||
enableRBF: true,
|
||||
enableRBF: !estimatedTx.spendsUnconfirmedTX,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -595,6 +630,7 @@ abstract class ElectrumWalletBase
|
|||
|
||||
String toJSON() => json.encode({
|
||||
'mnemonic': mnemonic,
|
||||
'passphrase': passphrase ?? '',
|
||||
'account_index': walletAddresses.currentReceiveAddressIndexByType,
|
||||
'change_address_index': walletAddresses.currentChangeAddressIndexByType,
|
||||
'addresses': walletAddresses.allAddresses.map((addr) => addr.toJSON()).toList(),
|
||||
|
@ -602,6 +638,8 @@ abstract class ElectrumWalletBase
|
|||
? SegwitAddresType.p2wpkh.toString()
|
||||
: walletInfo.addressPageType.toString(),
|
||||
'balance': balance[currency]?.toJSON(),
|
||||
'derivationTypeIndex': walletInfo.derivationInfo?.derivationType?.index,
|
||||
'derivationPath': walletInfo.derivationInfo?.derivationPath,
|
||||
});
|
||||
|
||||
int feeRate(TransactionPriority priority) {
|
||||
|
@ -731,6 +769,7 @@ abstract class ElectrumWalletBase
|
|||
final tx = await fetchTransactionInfo(
|
||||
hash: coin.hash, height: 0, myAddresses: addressesSet);
|
||||
coin.isChange = tx?.direction == TransactionDirection.outgoing;
|
||||
coin.confirmations = tx?.confirmations;
|
||||
updatedUnspentCoins.add(coin);
|
||||
} catch (_) {}
|
||||
}))));
|
||||
|
@ -755,6 +794,7 @@ abstract class ElectrumWalletBase
|
|||
coin.isFrozen = coinInfo.isFrozen;
|
||||
coin.isSending = coinInfo.isSending;
|
||||
coin.note = coinInfo.note;
|
||||
coin.bitcoinAddressRecord.balance += coinInfo.value;
|
||||
} else {
|
||||
_addCoinInfo(coin);
|
||||
}
|
||||
|
@ -1047,9 +1087,11 @@ abstract class ElectrumWalletBase
|
|||
|
||||
return Future.wait(addressesByType.map((addressRecord) async {
|
||||
final history = await _fetchAddressHistory(addressRecord, addressesSet, currentHeight);
|
||||
final balance = await electrumClient.getBalance(addressRecord.scriptHash!);
|
||||
|
||||
if (history.isNotEmpty) {
|
||||
addressRecord.txCount = history.length;
|
||||
addressRecord.balance = balance['confirmed'] as int? ?? 0;
|
||||
historiesWithDetails.addAll(history);
|
||||
|
||||
final matchedAddresses =
|
||||
|
@ -1280,6 +1322,7 @@ class EstimatedTxResult {
|
|||
required this.hasChange,
|
||||
required this.isSendAll,
|
||||
this.memo,
|
||||
required this.spendsUnconfirmedTX,
|
||||
});
|
||||
|
||||
final List<UtxoWithAddress> utxos;
|
||||
|
@ -1289,6 +1332,7 @@ class EstimatedTxResult {
|
|||
final bool hasChange;
|
||||
final bool isSendAll;
|
||||
final String? memo;
|
||||
final bool spendsUnconfirmedTX;
|
||||
}
|
||||
|
||||
BitcoinBaseAddress addressTypeFromStr(String address, BasedUtxoNetwork network) {
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
|||
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||
import 'package:cw_core/encryption_file_utils.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/wallet_type.dart';
|
||||
|
||||
|
@ -18,6 +19,9 @@ class ElectrumWalletSnapshot {
|
|||
required this.regularAddressIndex,
|
||||
required this.changeAddressIndex,
|
||||
required this.addressPageType,
|
||||
this.passphrase,
|
||||
this.derivationType,
|
||||
this.derivationPath,
|
||||
});
|
||||
|
||||
final String name;
|
||||
|
@ -30,6 +34,9 @@ class ElectrumWalletSnapshot {
|
|||
ElectrumBalance balance;
|
||||
Map<String, int> regularAddressIndex;
|
||||
Map<String, int> changeAddressIndex;
|
||||
String? passphrase;
|
||||
DerivationType? derivationType;
|
||||
String? derivationPath;
|
||||
|
||||
static Future<ElectrumWalletSnapshot> load(
|
||||
EncryptionFileUtils encryptionFileUtils, String name, WalletType type, String password, BasedUtxoNetwork network) async {
|
||||
|
@ -38,6 +45,7 @@ class ElectrumWalletSnapshot {
|
|||
final data = json.decode(jsonSource) as Map;
|
||||
final addressesTmp = data['addresses'] as List? ?? <Object>[];
|
||||
final mnemonic = data['mnemonic'] as String;
|
||||
final passphrase = data['passphrase'] as String? ?? '';
|
||||
final addresses = addressesTmp
|
||||
.whereType<String>()
|
||||
.map((addr) => BitcoinAddressRecord.fromJSON(addr, network))
|
||||
|
@ -47,6 +55,10 @@ class ElectrumWalletSnapshot {
|
|||
var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
|
||||
var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
|
||||
|
||||
final derivationType =
|
||||
DerivationType.values[(data['derivationTypeIndex'] as int?) ?? DerivationType.electrum.index];
|
||||
final derivationPath = data['derivationPath'] as String? ?? "m/0'/0";
|
||||
|
||||
try {
|
||||
regularAddressIndexByType = {
|
||||
SegwitAddresType.p2wpkh.toString(): int.parse(data['account_index'] as String? ?? '0')
|
||||
|
@ -66,12 +78,15 @@ class ElectrumWalletSnapshot {
|
|||
name: name,
|
||||
type: type,
|
||||
password: password,
|
||||
passphrase: passphrase,
|
||||
mnemonic: mnemonic,
|
||||
addresses: addresses,
|
||||
balance: balance,
|
||||
regularAddressIndex: regularAddressIndexByType,
|
||||
changeAddressIndex: changeAddressIndexByType,
|
||||
addressPageType: data['address_page_type'] as String?,
|
||||
derivationType: derivationType,
|
||||
derivationPath: derivationPath,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,9 @@ class BitcoinTransactionNoDustOnChangeException extends TransactionNoDustOnChang
|
|||
BitcoinTransactionNoDustOnChangeException(super.max, super.min);
|
||||
}
|
||||
|
||||
class BitcoinTransactionCommitFailed extends TransactionCommitFailed {}
|
||||
class BitcoinTransactionCommitFailed extends TransactionCommitFailed {
|
||||
BitcoinTransactionCommitFailed({super.errorMessage});
|
||||
}
|
||||
|
||||
class BitcoinTransactionCommitFailedDustChange extends TransactionCommitFailedDustChange {}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
|||
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||
import 'package:cw_bitcoin/litecoin_network.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
|
||||
part 'litecoin_wallet.g.dart';
|
||||
|
||||
|
@ -66,11 +67,26 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
required WalletInfo walletInfo,
|
||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
required EncryptionFileUtils encryptionFileUtils,
|
||||
String? passphrase,
|
||||
String? addressPageType,
|
||||
List<BitcoinAddressRecord>? initialAddresses,
|
||||
ElectrumBalance? initialBalance,
|
||||
Map<String, int>? initialRegularAddressIndex,
|
||||
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(
|
||||
mnemonic: mnemonic,
|
||||
password: password,
|
||||
|
@ -78,8 +94,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
initialAddresses: initialAddresses,
|
||||
initialBalance: initialBalance,
|
||||
seedBytes: await mnemonicToSeedBytes(mnemonic),
|
||||
encryptionFileUtils: encryptionFileUtils,
|
||||
seedBytes: seedBytes,
|
||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
addressPageType: addressPageType,
|
||||
|
|
|
@ -12,6 +12,7 @@ import 'package:cw_core/wallet_type.dart';
|
|||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
|
||||
class LitecoinWalletService extends WalletService<
|
||||
BitcoinNewWalletCredentials,
|
||||
|
@ -29,8 +30,9 @@ class LitecoinWalletService extends WalletService<
|
|||
@override
|
||||
Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||
final wallet = await LitecoinWalletBase.create(
|
||||
mnemonic: await generateMnemonic(),
|
||||
mnemonic: await generateElectrumMnemonic(),
|
||||
password: credentials.password!,
|
||||
passphrase: credentials.passphrase,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect));
|
||||
|
@ -106,12 +108,13 @@ class LitecoinWalletService extends WalletService<
|
|||
@override
|
||||
Future<LitecoinWallet> restoreFromSeed(
|
||||
BitcoinRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
|
||||
if (!validateMnemonic(credentials.mnemonic)) {
|
||||
if (!validateMnemonic(credentials.mnemonic) && !bip39.validateMnemonic(credentials.mnemonic)) {
|
||||
throw LitecoinMnemonicIsIncorrectException();
|
||||
}
|
||||
|
||||
final wallet = await LitecoinWalletBase.create(
|
||||
password: credentials.password!,
|
||||
passphrase: credentials.passphrase,
|
||||
mnemonic: credentials.mnemonic,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
|
|
|
@ -73,7 +73,9 @@ class PendingBitcoinTransaction with PendingTransaction {
|
|||
if (error.contains("bad-txns-vout-negative")) {
|
||||
throw BitcoinTransactionCommitFailedVoutNegative();
|
||||
}
|
||||
throw BitcoinTransactionCommitFailed(errorMessage: error);
|
||||
}
|
||||
|
||||
throw BitcoinTransactionCommitFailed();
|
||||
}
|
||||
|
||||
|
|
|
@ -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:hex/hex.dart';
|
||||
|
||||
bitcoin.PaymentData generatePaymentData({required bitcoin.HDWallet hd, required int index}) =>
|
||||
PaymentData(pubkey: Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!)));
|
||||
bitcoin.PaymentData generatePaymentData({required bitcoin.HDWallet hd, int? index}) {
|
||||
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
|
||||
return PaymentData(pubkey: Uint8List.fromList(HEX.decode(pubKey)));
|
||||
}
|
||||
|
||||
ECPrivate generateECPrivate(
|
||||
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
|
||||
ECPrivate.fromWif(hd.derive(index).wif!, netVersion: network.wifNetVer);
|
||||
{required bitcoin.HDWallet hd, required BasedUtxoNetwork network, int? index}) {
|
||||
final wif = index != null ? hd.derive(index).wif! : hd.wif!;
|
||||
return ECPrivate.fromWif(wif, netVersion: network.wifNetVer);
|
||||
}
|
||||
|
||||
String generateP2WPKHAddress(
|
||||
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
|
||||
ECPublic.fromHex(hd.derive(index).pubKey!).toP2wpkhAddress().toAddress(network);
|
||||
String generateP2WPKHAddress({
|
||||
required bitcoin.HDWallet hd,
|
||||
required BasedUtxoNetwork network,
|
||||
int? index,
|
||||
}) {
|
||||
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
|
||||
return ECPublic.fromHex(pubKey).toP2wpkhAddress().toAddress(network);
|
||||
}
|
||||
|
||||
String generateP2SHAddress(
|
||||
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
|
||||
ECPublic.fromHex(hd.derive(index).pubKey!).toP2wpkhInP2sh().toAddress(network);
|
||||
String generateP2SHAddress({
|
||||
required bitcoin.HDWallet hd,
|
||||
required BasedUtxoNetwork network,
|
||||
int? index,
|
||||
}) {
|
||||
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
|
||||
return ECPublic.fromHex(pubKey).toP2wpkhInP2sh().toAddress(network);
|
||||
}
|
||||
|
||||
String generateP2WSHAddress(
|
||||
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
|
||||
ECPublic.fromHex(hd.derive(index).pubKey!).toP2wshAddress().toAddress(network);
|
||||
String generateP2WSHAddress({
|
||||
required bitcoin.HDWallet hd,
|
||||
required BasedUtxoNetwork network,
|
||||
int? index,
|
||||
}) {
|
||||
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
|
||||
return ECPublic.fromHex(pubKey).toP2wshAddress().toAddress(network);
|
||||
}
|
||||
|
||||
String generateP2PKHAddress(
|
||||
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
|
||||
ECPublic.fromHex(hd.derive(index).pubKey!).toP2pkhAddress().toAddress(network);
|
||||
String generateP2PKHAddress({
|
||||
required bitcoin.HDWallet hd,
|
||||
required BasedUtxoNetwork network,
|
||||
int? index,
|
||||
}) {
|
||||
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
|
||||
return ECPublic.fromHex(pubKey).toP2pkhAddress().toAddress(network);
|
||||
}
|
||||
|
||||
String generateP2TRAddress(
|
||||
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
|
||||
ECPublic.fromHex(hd.derive(index).pubKey!).toTaprootAddress().toAddress(network);
|
||||
String generateP2TRAddress({
|
||||
required bitcoin.HDWallet hd,
|
||||
required BasedUtxoNetwork network,
|
||||
int? index,
|
||||
}) {
|
||||
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
|
||||
return ECPublic.fromHex(pubKey).toTaprootAddress().toAddress(network);
|
||||
}
|
||||
|
|
|
@ -21,10 +21,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
|
||||
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
version: "2.5.0"
|
||||
asn1lib:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -80,7 +80,7 @@ packages:
|
|||
description:
|
||||
path: "."
|
||||
ref: cake-update-v2
|
||||
resolved-ref: "3fd81d238b990bb767fc7a4fdd5053a22a142e2e"
|
||||
resolved-ref: "01d844a5f5a520a31df5254e34169af4664aa769"
|
||||
url: "https://github.com/cake-tech/bitcoin_base.git"
|
||||
source: git
|
||||
version: "4.2.0"
|
||||
|
@ -153,10 +153,10 @@ packages:
|
|||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21"
|
||||
sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.8"
|
||||
version: "2.4.9"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -177,19 +177,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6
|
||||
sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.9.0"
|
||||
cake_backup:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: main
|
||||
resolved-ref: "3aba867dcab6737f6707782f5db15d71f303db38"
|
||||
url: "https://github.com/cake-tech/cake_backup.git"
|
||||
source: git
|
||||
version: "1.0.0+1"
|
||||
version: "8.9.2"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -254,14 +245,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
cupertino_icons:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
cw_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -326,10 +309,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_mobx
|
||||
sha256: "4a5d062ff85ed3759f4aac6410ff0ffae32e324b2e71ca722ae1b37b32e865f4"
|
||||
sha256: "859fbf452fa9c2519d2700b125dd7fb14c508bbdd7fb65e26ca8ff6c92280e2e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0+2"
|
||||
version: "2.2.1+1"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
|
@ -339,10 +322,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: frontend_server_client
|
||||
sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
|
||||
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
version: "4.0.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -483,10 +466,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: mobx
|
||||
sha256: "74ee54012dc7c1b3276eaa960a600a7418ef5f9997565deb8fca1fd88fb36b78"
|
||||
sha256: "63920b27b32ad1910adfe767ab1750e4c212e8923232a1f891597b362074ea5e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0+1"
|
||||
version: "2.3.3+2"
|
||||
mobx_codegen:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
@ -587,10 +570,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: pointycastle
|
||||
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
|
||||
sha256: "70fe966348fe08c34bf929582f1d8247d9d9408130723206472b4687227e4333"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.7.4"
|
||||
version: "3.8.0"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -603,10 +586,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: provider
|
||||
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
|
||||
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.1"
|
||||
version: "6.1.2"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -740,14 +723,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
tuple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: tuple
|
||||
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -62,7 +62,9 @@ class PendingBitcoinCashTransaction with PendingTransaction {
|
|||
if (error.contains("bad-txns-vout-negative")) {
|
||||
throw BitcoinTransactionCommitFailedVoutNegative();
|
||||
}
|
||||
throw BitcoinTransactionCommitFailed(errorMessage: error);
|
||||
}
|
||||
|
||||
throw BitcoinTransactionCommitFailed();
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,11 @@ class TransactionNoDustOnChangeException implements Exception {
|
|||
final String min;
|
||||
}
|
||||
|
||||
class TransactionCommitFailed implements Exception {}
|
||||
class TransactionCommitFailed implements Exception {
|
||||
final String? errorMessage;
|
||||
|
||||
TransactionCommitFailed({this.errorMessage});
|
||||
}
|
||||
|
||||
class TransactionCommitFailedDustChange implements Exception {}
|
||||
|
||||
|
|
|
@ -14,4 +14,5 @@ const ERC20_TOKEN_TYPE_ID = 12;
|
|||
const NANO_ACCOUNT_TYPE_ID = 13;
|
||||
const POW_NODE_TYPE_ID = 14;
|
||||
const DERIVATION_TYPE_TYPE_ID = 15;
|
||||
const SPL_TOKEN_TYPE_ID = 16;
|
||||
const SPL_TOKEN_TYPE_ID = 16;
|
||||
const DERIVATION_INFO_TYPE_ID = 17;
|
||||
|
|
|
@ -10,7 +10,7 @@ import 'package:http/io_client.dart' as ioc;
|
|||
|
||||
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)
|
||||
class Node extends HiveObject with Keyable {
|
||||
|
@ -83,7 +83,7 @@ class Node extends HiveObject with Keyable {
|
|||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
case WalletType.bitcoinCash:
|
||||
return createUriFromElectrumAddress(uriRaw);
|
||||
return createUriFromElectrumAddress(uriRaw, path ?? '');
|
||||
case WalletType.nano:
|
||||
case WalletType.banano:
|
||||
if (isSSL) {
|
||||
|
@ -94,7 +94,7 @@ class Node extends HiveObject with Keyable {
|
|||
case WalletType.ethereum:
|
||||
case WalletType.polygon:
|
||||
case WalletType.solana:
|
||||
return Uri.https(uriRaw, '');
|
||||
return Uri.https(uriRaw, path ?? '');
|
||||
default:
|
||||
throw Exception('Unexpected type ${type.toString()} for Node uri');
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ class Unspent {
|
|||
bool isChange;
|
||||
bool isSending;
|
||||
bool isFrozen;
|
||||
int? confirmations;
|
||||
String note;
|
||||
|
||||
bool get isP2wpkh => address.startsWith('bc') || address.startsWith('ltc');
|
||||
|
|
|
@ -7,15 +7,19 @@ abstract class WalletCredentials {
|
|||
this.seedPhraseLength,
|
||||
this.walletInfo,
|
||||
this.password,
|
||||
this.derivationType,
|
||||
this.derivationPath,
|
||||
});
|
||||
this.passphrase,
|
||||
this.derivationInfo,
|
||||
}) {
|
||||
if (this.walletInfo != null && derivationInfo != null) {
|
||||
this.walletInfo!.derivationInfo = derivationInfo;
|
||||
}
|
||||
}
|
||||
|
||||
final String name;
|
||||
final int? height;
|
||||
int? seedPhraseLength;
|
||||
String? password;
|
||||
DerivationType? derivationType;
|
||||
String? derivationPath;
|
||||
String? passphrase;
|
||||
WalletInfo? walletInfo;
|
||||
DerivationInfo? derivationInfo;
|
||||
}
|
||||
|
|
|
@ -17,28 +17,42 @@ enum DerivationType {
|
|||
@HiveField(3)
|
||||
bip39,
|
||||
@HiveField(4)
|
||||
electrum1,
|
||||
@HiveField(5)
|
||||
electrum2,
|
||||
electrum,
|
||||
}
|
||||
|
||||
class DerivationInfo {
|
||||
@HiveType(typeId: DerivationInfo.typeId)
|
||||
class DerivationInfo extends HiveObject {
|
||||
DerivationInfo({
|
||||
required this.derivationType,
|
||||
this.derivationType,
|
||||
this.derivationPath,
|
||||
this.balance = "",
|
||||
this.address = "",
|
||||
this.height = 0,
|
||||
this.script_type,
|
||||
this.transactionsCount = 0,
|
||||
this.scriptType,
|
||||
this.description,
|
||||
});
|
||||
|
||||
String balance;
|
||||
static const typeId = DERIVATION_INFO_TYPE_ID;
|
||||
|
||||
@HiveField(0, defaultValue: '')
|
||||
String address;
|
||||
int height;
|
||||
final DerivationType derivationType;
|
||||
final String? derivationPath;
|
||||
final String? script_type;
|
||||
|
||||
@HiveField(1, defaultValue: '')
|
||||
String balance;
|
||||
|
||||
@HiveField(2)
|
||||
int transactionsCount;
|
||||
|
||||
@HiveField(3)
|
||||
DerivationType? derivationType;
|
||||
|
||||
@HiveField(4)
|
||||
String? derivationPath;
|
||||
|
||||
@HiveField(5)
|
||||
final String? scriptType;
|
||||
|
||||
@HiveField(6)
|
||||
final String? description;
|
||||
}
|
||||
|
||||
|
@ -57,8 +71,7 @@ class WalletInfo extends HiveObject {
|
|||
this.yatEid,
|
||||
this.yatLastUsedAddressRaw,
|
||||
this.showIntroCakePayCard,
|
||||
this.derivationType,
|
||||
this.derivationPath)
|
||||
this.derivationInfo)
|
||||
: _yatLastUsedAddressController = StreamController<String>.broadcast();
|
||||
|
||||
factory WalletInfo.external({
|
||||
|
@ -74,24 +87,23 @@ class WalletInfo extends HiveObject {
|
|||
bool? showIntroCakePayCard,
|
||||
String yatEid = '',
|
||||
String yatLastUsedAddressRaw = '',
|
||||
DerivationType? derivationType,
|
||||
String? derivationPath,
|
||||
DerivationInfo? derivationInfo,
|
||||
}) {
|
||||
return WalletInfo(
|
||||
id,
|
||||
name,
|
||||
type,
|
||||
isRecovery,
|
||||
restoreHeight,
|
||||
date.millisecondsSinceEpoch,
|
||||
dirPath,
|
||||
path,
|
||||
address,
|
||||
yatEid,
|
||||
yatLastUsedAddressRaw,
|
||||
showIntroCakePayCard,
|
||||
derivationType,
|
||||
derivationPath);
|
||||
id,
|
||||
name,
|
||||
type,
|
||||
isRecovery,
|
||||
restoreHeight,
|
||||
date.millisecondsSinceEpoch,
|
||||
dirPath,
|
||||
path,
|
||||
address,
|
||||
yatEid,
|
||||
yatLastUsedAddressRaw,
|
||||
showIntroCakePayCard,
|
||||
derivationInfo,
|
||||
);
|
||||
}
|
||||
|
||||
static const typeId = WALLET_INFO_TYPE_ID;
|
||||
|
@ -143,10 +155,10 @@ class WalletInfo extends HiveObject {
|
|||
List<String>? usedAddresses;
|
||||
|
||||
@HiveField(16)
|
||||
DerivationType? derivationType;
|
||||
DerivationType? derivationType; // no longer used
|
||||
|
||||
@HiveField(17)
|
||||
String? derivationPath;
|
||||
String? derivationPath; // no longer used
|
||||
|
||||
@HiveField(18)
|
||||
String? addressPageType;
|
||||
|
@ -154,6 +166,9 @@ class WalletInfo extends HiveObject {
|
|||
@HiveField(19)
|
||||
String? network;
|
||||
|
||||
@HiveField(20)
|
||||
DerivationInfo? derivationInfo;
|
||||
|
||||
String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
|
||||
|
||||
set yatLastUsedAddress(String address) {
|
||||
|
|
|
@ -38,6 +38,7 @@ dev_dependencies:
|
|||
mobx_codegen: ^2.0.7
|
||||
hive_generator: ^2.0.1
|
||||
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
|
|
|
@ -234,14 +234,17 @@ abstract class EVMChainClient {
|
|||
|
||||
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 symbol = decodedResponse['symbol'] ?? '';
|
||||
final decimal = decodedResponse['decimals'] ?? '0';
|
||||
final iconPath = decodedResponse['logo'] ?? '';
|
||||
|
||||
return Erc20Token(
|
||||
name: name,
|
||||
symbol: symbol,
|
||||
symbol: filteredSymbol,
|
||||
contractAddress: contractAddress,
|
||||
decimal: int.tryParse(decimal) ?? 0,
|
||||
iconPath: iconPath,
|
||||
|
|
|
@ -474,9 +474,15 @@ abstract class EVMChainWalletBase
|
|||
await token.delete();
|
||||
|
||||
balance.remove(token);
|
||||
await _removeTokenTransactionsInHistory(token);
|
||||
_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 =>
|
||||
await _client.getErc20Token(contractAddress, chainName);
|
||||
|
||||
|
|
|
@ -69,10 +69,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf"
|
||||
sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
version: "4.0.0"
|
||||
build_resolvers:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
@ -85,10 +85,10 @@ packages:
|
|||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727
|
||||
sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.3"
|
||||
version: "2.4.9"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -158,10 +158,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
|
||||
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.1"
|
||||
version: "1.17.2"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -375,18 +375,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
|
||||
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.15"
|
||||
version: "0.12.16"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
|
||||
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
version: "0.5.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -588,10 +588,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
version: "1.10.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -636,10 +636,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
|
||||
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
version: "0.6.0"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -673,13 +673,21 @@ packages:
|
|||
source: hosted
|
||||
version: "2.1.4"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0"
|
||||
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
version: "1.1.0"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.4-beta"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -713,5 +721,5 @@ packages:
|
|||
source: hosted
|
||||
version: "3.1.1"
|
||||
sdks:
|
||||
dart: ">=3.0.0 <4.0.0"
|
||||
dart: ">=3.1.0-185.0.dev <4.0.0"
|
||||
flutter: ">=3.7.0"
|
||||
|
|
|
@ -33,15 +33,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
cake_backup:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: main
|
||||
resolved-ref: "3aba867dcab6737f6707782f5db15d71f303db38"
|
||||
url: "https://github.com/cake-tech/cake_backup.git"
|
||||
source: git
|
||||
version: "1.0.0+1"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -62,10 +53,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
|
||||
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.1"
|
||||
version: "1.17.2"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -82,14 +73,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
cryptography:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cryptography
|
||||
sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -230,18 +213,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
|
||||
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.15"
|
||||
version: "0.12.16"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
|
||||
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
version: "0.5.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -371,10 +354,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
version: "1.10.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -411,18 +394,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
|
||||
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
tuple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: tuple
|
||||
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
version: "0.6.0"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -439,6 +414,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.4-beta"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -456,5 +439,5 @@ packages:
|
|||
source: hosted
|
||||
version: "0.2.0+3"
|
||||
sdks:
|
||||
dart: ">=3.0.6 <4.0.0"
|
||||
dart: ">=3.1.0-185.0.dev <4.0.0"
|
||||
flutter: ">=3.7.0"
|
||||
|
|
|
@ -69,10 +69,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf"
|
||||
sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
version: "4.0.0"
|
||||
build_resolvers:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
@ -85,10 +85,10 @@ packages:
|
|||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727
|
||||
sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.3"
|
||||
version: "2.4.9"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -158,10 +158,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
|
||||
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.1"
|
||||
version: "1.17.2"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -391,18 +391,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
|
||||
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.15"
|
||||
version: "0.12.16"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
|
||||
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
version: "0.5.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -612,10 +612,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
version: "1.10.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -660,10 +660,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
|
||||
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
version: "0.6.0"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -697,13 +697,21 @@ packages:
|
|||
source: hosted
|
||||
version: "2.1.4"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0"
|
||||
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
version: "1.1.0"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.4-beta"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -737,5 +745,5 @@ packages:
|
|||
source: hosted
|
||||
version: "3.1.1"
|
||||
sdks:
|
||||
dart: ">=3.0.6 <4.0.0"
|
||||
dart: ">=3.1.0-185.0.dev <4.0.0"
|
||||
flutter: ">=3.7.0"
|
||||
|
|
|
@ -44,7 +44,7 @@ abstract class NanoWalletBase
|
|||
}) : syncStatus = NotConnectedSyncStatus(),
|
||||
_password = password,
|
||||
_mnemonic = mnemonic,
|
||||
_derivationType = walletInfo.derivationType!,
|
||||
_derivationType = walletInfo.derivationInfo!.derivationType!,
|
||||
_isTransactionUpdating = false,
|
||||
_encryptionFileUtils = encryptionFileUtils,
|
||||
_client = NanoClient(),
|
||||
|
@ -401,7 +401,10 @@ abstract class NanoWalletBase
|
|||
derivationType = DerivationType.bip39;
|
||||
}
|
||||
|
||||
walletInfo.derivationType = derivationType;
|
||||
walletInfo.derivationInfo ??= DerivationInfo(derivationType: derivationType);
|
||||
if (walletInfo.derivationInfo!.derivationType == null) {
|
||||
walletInfo.derivationInfo!.derivationType = derivationType;
|
||||
}
|
||||
|
||||
return NanoWallet(
|
||||
walletInfo: walletInfo,
|
||||
|
|
|
@ -2,8 +2,15 @@ import 'package:cw_core/wallet_credentials.dart';
|
|||
import 'package:cw_core/wallet_info.dart';
|
||||
|
||||
class NanoNewWalletCredentials extends WalletCredentials {
|
||||
NanoNewWalletCredentials({required String name, String? password})
|
||||
: super(name: name, password: password);
|
||||
NanoNewWalletCredentials({
|
||||
required String name,
|
||||
String? password,
|
||||
DerivationType? derivationType,
|
||||
}) : super(
|
||||
name: name,
|
||||
password: password,
|
||||
derivationInfo: DerivationInfo(derivationType: derivationType),
|
||||
);
|
||||
}
|
||||
|
||||
class NanoRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||
|
@ -11,11 +18,11 @@ class NanoRestoreWalletFromSeedCredentials extends WalletCredentials {
|
|||
required String name,
|
||||
required this.mnemonic,
|
||||
String? password,
|
||||
DerivationType? derivationType,
|
||||
required DerivationType derivationType,
|
||||
}) : super(
|
||||
name: name,
|
||||
password: password,
|
||||
derivationType: derivationType,
|
||||
derivationInfo: DerivationInfo(derivationType: derivationType),
|
||||
);
|
||||
|
||||
final String mnemonic;
|
||||
|
@ -30,12 +37,12 @@ class NanoRestoreWalletFromKeysCredentials extends WalletCredentials {
|
|||
NanoRestoreWalletFromKeysCredentials({
|
||||
required String name,
|
||||
required String password,
|
||||
required DerivationType derivationType,
|
||||
required this.seedKey,
|
||||
DerivationType? derivationType,
|
||||
}) : super(
|
||||
name: name,
|
||||
password: password,
|
||||
derivationType: derivationType,
|
||||
derivationInfo: DerivationInfo(derivationType: derivationType),
|
||||
);
|
||||
|
||||
final String seedKey;
|
||||
|
|
|
@ -30,11 +30,11 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
|
|||
@override
|
||||
Future<WalletBase> create(NanoNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||
// nano standard:
|
||||
DerivationType derivationType = DerivationType.nano;
|
||||
String seedKey = NanoSeeds.generateSeed();
|
||||
String mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey);
|
||||
|
||||
credentials.walletInfo!.derivationType = derivationType;
|
||||
// ensure default if not present:
|
||||
credentials.walletInfo!.derivationInfo ??= DerivationInfo(derivationType: DerivationType.nano);
|
||||
|
||||
final wallet = NanoWallet(
|
||||
walletInfo: credentials.walletInfo!,
|
||||
|
@ -95,9 +95,6 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
|
|||
}
|
||||
}
|
||||
|
||||
DerivationType derivationType = credentials.derivationType ?? DerivationType.nano;
|
||||
credentials.walletInfo!.derivationType = derivationType;
|
||||
|
||||
String? mnemonic;
|
||||
|
||||
// we can't derive the mnemonic from the key in all cases, only if it's a "nano" seed
|
||||
|
@ -136,9 +133,10 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
|
|||
}
|
||||
}
|
||||
|
||||
DerivationType derivationType = credentials.derivationType ?? DerivationType.nano;
|
||||
DerivationType derivationType =
|
||||
credentials.walletInfo?.derivationInfo?.derivationType ?? DerivationType.nano;
|
||||
|
||||
credentials.walletInfo!.derivationType = derivationType;
|
||||
credentials.walletInfo!.derivationInfo ??= DerivationInfo(derivationType: derivationType);
|
||||
|
||||
final wallet = await NanoWallet(
|
||||
password: credentials.password!,
|
||||
|
|
|
@ -93,10 +93,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169"
|
||||
sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
version: "4.0.1"
|
||||
build_resolvers:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -109,10 +109,10 @@ packages:
|
|||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727
|
||||
sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.3"
|
||||
version: "2.4.9"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -182,10 +182,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
|
||||
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.1"
|
||||
version: "1.17.2"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -444,18 +444,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
|
||||
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.15"
|
||||
version: "0.12.16"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
|
||||
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
version: "0.5.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -738,10 +738,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
version: "1.10.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -786,10 +786,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
|
||||
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
version: "0.6.0"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -823,13 +823,21 @@ packages:
|
|||
source: hosted
|
||||
version: "2.1.4"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0"
|
||||
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
version: "1.1.0"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.4-beta"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -863,5 +871,5 @@ packages:
|
|||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.0.0 <4.0.0"
|
||||
dart: ">=3.1.0-185.0.dev <4.0.0"
|
||||
flutter: ">=3.7.0"
|
||||
|
|
|
@ -34,10 +34,7 @@ class SolanaTransactionInfo extends TransactionInfo {
|
|||
@override
|
||||
String amountFormatted() {
|
||||
String stringBalance = solAmount.toString();
|
||||
|
||||
if (stringBalance.toString().length >= 6) {
|
||||
stringBalance = stringBalance.substring(0, 6);
|
||||
}
|
||||
|
||||
return '$stringBalance $tokenSymbol';
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import 'package:hex/hex.dart';
|
|||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:solana/base58.dart';
|
||||
import 'package:solana/metaplex.dart' as metaplex;
|
||||
import 'package:solana/solana.dart';
|
||||
|
||||
|
@ -114,7 +115,17 @@ abstract class SolanaWalletBase
|
|||
String? get seed => _mnemonic;
|
||||
|
||||
@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 {
|
||||
final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${SPLToken.boxName}";
|
||||
|
@ -141,8 +152,8 @@ abstract class SolanaWalletBase
|
|||
assert(mnemonic != null || privateKey != null);
|
||||
|
||||
if (privateKey != null) {
|
||||
final privateKeyBytes = HEX.decode(privateKey);
|
||||
return await Wallet.fromPrivateKeyBytes(privateKey: privateKeyBytes);
|
||||
final privateKeyBytes = base58decode(privateKey);
|
||||
return await Wallet.fromPrivateKeyBytes(privateKey: privateKeyBytes.take(32).toList());
|
||||
}
|
||||
|
||||
return Wallet.fromMnemonic(mnemonic!, account: 0, change: 0);
|
||||
|
@ -268,32 +279,12 @@ abstract class SolanaWalletBase
|
|||
|
||||
final transactions = await _client.fetchTransactions(address);
|
||||
|
||||
final Map<String, SolanaTransactionInfo> result = {};
|
||||
|
||||
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();
|
||||
await _addTransactionsToTransactionHistory(transactions);
|
||||
}
|
||||
|
||||
/// Fetches the SPL Tokens transactions linked to the token account Public Key
|
||||
Future<void> _updateSPLTokenTransactions() async {
|
||||
List<SolanaTransactionModel> splTokenTransactions = [];
|
||||
// List<SolanaTransactionModel> splTokenTransactions = [];
|
||||
|
||||
// Make a copy of keys to avoid concurrent modification
|
||||
var tokenKeys = List<CryptoCurrency>.from(balance.keys);
|
||||
|
@ -307,13 +298,20 @@ abstract class SolanaWalletBase
|
|||
_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 = {};
|
||||
|
||||
for (var transactionModel in splTokenTransactions) {
|
||||
for (var transactionModel in transactions) {
|
||||
result[transactionModel.id] = SolanaTransactionInfo(
|
||||
id: transactionModel.id,
|
||||
to: transactionModel.to,
|
||||
|
@ -457,12 +455,23 @@ abstract class SolanaWalletBase
|
|||
await token.delete();
|
||||
|
||||
balance.remove(token);
|
||||
await _removeTokenTransactionsInHistory(token);
|
||||
_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 {
|
||||
// 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
|
||||
try {
|
||||
|
@ -477,10 +486,12 @@ abstract class SolanaWalletBase
|
|||
iconPath = await _client.getIconImageFromTokenUri(token.uri);
|
||||
} catch (_) {}
|
||||
|
||||
String filteredTokenSymbol = token.symbol.replaceFirst(RegExp('^\\\$'), '');
|
||||
|
||||
return SPLToken.fromMetadata(
|
||||
name: token.name,
|
||||
mint: token.mint,
|
||||
symbol: token.symbol,
|
||||
symbol: filteredTokenSymbol,
|
||||
mintAddress: mintAddress,
|
||||
iconPath: iconPath,
|
||||
);
|
||||
|
|
|
@ -19,7 +19,7 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin {
|
|||
@HiveField(3)
|
||||
final int decimal;
|
||||
|
||||
@HiveField(4, defaultValue: false)
|
||||
@HiveField(4, defaultValue: true)
|
||||
bool _enabled;
|
||||
|
||||
@HiveField(5)
|
||||
|
@ -39,7 +39,7 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin {
|
|||
required this.mint,
|
||||
this.iconPath,
|
||||
this.tag = 'SOL',
|
||||
bool enabled = false,
|
||||
bool enabled = true,
|
||||
}) : _enabled = enabled,
|
||||
super(
|
||||
name: mint.toLowerCase(),
|
||||
|
|
|
@ -1,13 +1,22 @@
|
|||
part of 'bitcoin.dart';
|
||||
|
||||
class CWBitcoin extends Bitcoin {
|
||||
@override
|
||||
TransactionPriority getMediumTransactionPriority() => BitcoinTransactionPriority.medium;
|
||||
|
||||
@override
|
||||
WalletCredentials createBitcoinRestoreWalletFromSeedCredentials(
|
||||
{required String name, required String mnemonic, required String password}) =>
|
||||
BitcoinRestoreWalletFromSeedCredentials(name: name, mnemonic: mnemonic, password: password);
|
||||
WalletCredentials createBitcoinRestoreWalletFromSeedCredentials({
|
||||
required String name,
|
||||
required String mnemonic,
|
||||
required String password,
|
||||
required DerivationType derivationType,
|
||||
required String derivationPath,
|
||||
String? passphrase,
|
||||
}) =>
|
||||
BitcoinRestoreWalletFromSeedCredentials(
|
||||
name: name,
|
||||
mnemonic: mnemonic,
|
||||
password: password,
|
||||
derivationType: derivationType,
|
||||
derivationPath: derivationPath,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
@override
|
||||
WalletCredentials createBitcoinRestoreWalletFromWIFCredentials(
|
||||
|
@ -23,6 +32,9 @@ class CWBitcoin extends Bitcoin {
|
|||
{required String name, WalletInfo? walletInfo, String? password}) =>
|
||||
BitcoinNewWalletCredentials(name: name, walletInfo: walletInfo, password: password);
|
||||
|
||||
@override
|
||||
TransactionPriority getMediumTransactionPriority() => BitcoinTransactionPriority.medium;
|
||||
|
||||
@override
|
||||
List<String> getWordList() => wordlist;
|
||||
|
||||
|
@ -78,21 +90,20 @@ class CWBitcoin extends Bitcoin {
|
|||
final bitcoinFeeRate =
|
||||
priority == BitcoinTransactionPriority.custom && feeRate != null ? feeRate : null;
|
||||
return BitcoinTransactionCredentials(
|
||||
outputs
|
||||
.map((out) => OutputInfo(
|
||||
fiatAmount: out.fiatAmount,
|
||||
cryptoAmount: out.cryptoAmount,
|
||||
address: out.address,
|
||||
note: out.note,
|
||||
sendAll: out.sendAll,
|
||||
extractedAddress: out.extractedAddress,
|
||||
isParsedAddress: out.isParsedAddress,
|
||||
formattedCryptoAmount: out.formattedCryptoAmount,
|
||||
memo: out.memo))
|
||||
.toList(),
|
||||
priority: priority as BitcoinTransactionPriority,
|
||||
feeRate: bitcoinFeeRate
|
||||
);
|
||||
outputs
|
||||
.map((out) => OutputInfo(
|
||||
fiatAmount: out.fiatAmount,
|
||||
cryptoAmount: out.cryptoAmount,
|
||||
address: out.address,
|
||||
note: out.note,
|
||||
sendAll: out.sendAll,
|
||||
extractedAddress: out.extractedAddress,
|
||||
isParsedAddress: out.isParsedAddress,
|
||||
formattedCryptoAmount: out.formattedCryptoAmount,
|
||||
memo: out.memo))
|
||||
.toList(),
|
||||
priority: priority as BitcoinTransactionPriority,
|
||||
feeRate: bitcoinFeeRate);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -248,6 +259,137 @@ class CWBitcoin extends Bitcoin {
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<DerivationType>> compareDerivationMethods(
|
||||
{required String mnemonic, required Node node}) async {
|
||||
if (await checkIfMnemonicIsElectrum2(mnemonic)) {
|
||||
return [DerivationType.electrum];
|
||||
}
|
||||
|
||||
return [DerivationType.bip39, DerivationType.electrum];
|
||||
}
|
||||
|
||||
int _countOccurrences(String str, String charToCount) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < str.length; i++) {
|
||||
if (str[i] == charToCount) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<DerivationInfo>> getDerivationsFromMnemonic({
|
||||
required String mnemonic,
|
||||
required Node node,
|
||||
String? passphrase,
|
||||
}) async {
|
||||
List<DerivationInfo> list = [];
|
||||
|
||||
List<DerivationType> types = await compareDerivationMethods(mnemonic: mnemonic, node: node);
|
||||
if (types.length == 1 && types.first == DerivationType.electrum) {
|
||||
return [
|
||||
DerivationInfo(
|
||||
derivationType: DerivationType.electrum,
|
||||
derivationPath: "m/0'/0",
|
||||
description: "Electrum",
|
||||
scriptType: "p2wpkh",
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
final electrumClient = ElectrumClient();
|
||||
await electrumClient.connectToUri(node.uri);
|
||||
|
||||
late BasedUtxoNetwork network;
|
||||
btc.NetworkType networkType;
|
||||
switch (node.type) {
|
||||
case WalletType.litecoin:
|
||||
network = LitecoinNetwork.mainnet;
|
||||
networkType = litecoinNetwork;
|
||||
break;
|
||||
case WalletType.bitcoin:
|
||||
default:
|
||||
network = BitcoinNetwork.mainnet;
|
||||
networkType = btc.bitcoin;
|
||||
break;
|
||||
}
|
||||
|
||||
for (DerivationType dType in electrum_derivations.keys) {
|
||||
late Uint8List seedBytes;
|
||||
if (dType == DerivationType.electrum) {
|
||||
seedBytes = await mnemonicToSeedBytes(mnemonic);
|
||||
} else if (dType == DerivationType.bip39) {
|
||||
seedBytes = bip39.mnemonicToSeed(mnemonic, passphrase: passphrase ?? '');
|
||||
}
|
||||
|
||||
for (DerivationInfo dInfo in electrum_derivations[dType]!) {
|
||||
try {
|
||||
DerivationInfo dInfoCopy = DerivationInfo(
|
||||
derivationType: dInfo.derivationType,
|
||||
derivationPath: dInfo.derivationPath,
|
||||
description: dInfo.description,
|
||||
scriptType: dInfo.scriptType,
|
||||
);
|
||||
|
||||
String derivationPath = dInfoCopy.derivationPath!;
|
||||
int derivationDepth = _countOccurrences(derivationPath, "/");
|
||||
|
||||
// the correct derivation depth is dependant on the derivation type:
|
||||
// the derivation paths defined in electrum_derivations are at the ROOT level, i.e.:
|
||||
// electrum's format doesn't specify subaddresses, just subaccounts:
|
||||
|
||||
// for BIP44
|
||||
if (derivationDepth == 3) {
|
||||
// we add "/0/0" so that we generate account 0, index 0 and correctly get balance
|
||||
derivationPath += "/0/0";
|
||||
// we don't support sub-ACCOUNTS in bitcoin like we do monero, and so the path dInfoCopy
|
||||
// expects should be ACCOUNT 0, index unspecified:
|
||||
dInfoCopy.derivationPath = dInfoCopy.derivationPath! + "/0";
|
||||
}
|
||||
|
||||
// var hd = bip32.BIP32.fromSeed(seedBytes).derivePath(derivationPath);
|
||||
final hd = btc.HDWallet.fromSeed(
|
||||
seedBytes,
|
||||
network: networkType,
|
||||
).derivePath(derivationPath);
|
||||
|
||||
String? address;
|
||||
switch (dInfoCopy.scriptType) {
|
||||
case "p2wpkh":
|
||||
address = generateP2WPKHAddress(hd: hd, network: network);
|
||||
break;
|
||||
case "p2pkh":
|
||||
address = generateP2PKHAddress(hd: hd, network: network);
|
||||
break;
|
||||
case "p2wpkh-p2sh":
|
||||
address = generateP2SHAddress(hd: hd, network: network);
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
final sh = scriptHash(address, network: network);
|
||||
final history = await electrumClient.getHistory(sh);
|
||||
|
||||
final balance = await electrumClient.getBalance(sh);
|
||||
dInfoCopy.balance = balance.entries.first.value.toString();
|
||||
dInfoCopy.address = address;
|
||||
dInfoCopy.transactionsCount = history.length;
|
||||
|
||||
list.add(dInfoCopy);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sort the list such that derivations with the most transactions are first:
|
||||
list.sort((a, b) => b.transactionsCount.compareTo(a.transactionsCount));
|
||||
return list;
|
||||
}
|
||||
|
||||
@override
|
||||
bool hasTaprootInput(PendingTransaction pendingTransaction) {
|
||||
return (pendingTransaction as PendingBitcoinTransaction).hasTaprootInputs;
|
||||
|
@ -282,13 +424,20 @@ class CWBitcoin extends Bitcoin {
|
|||
}
|
||||
|
||||
@override
|
||||
int getFeeAmountWithFeeRate(Object wallet, int feeRate, int inputsCount, int outputsCount,
|
||||
{int? size}) {
|
||||
int getEstimatedFeeWithFeeRate(Object wallet, int feeRate, int? amount,
|
||||
{int? outputsCount, int? size}) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return bitcoinWallet.feeAmountWithFeeRate(
|
||||
return bitcoinWallet.calculateEstimatedFeeWithFeeRate(
|
||||
feeRate,
|
||||
inputsCount,
|
||||
outputsCount,
|
||||
amount,
|
||||
outputsCount: outputsCount,
|
||||
size: size,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
int getMaxCustomFeeRate(Object wallet) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return (bitcoinWallet.feeRate(BitcoinTransactionPriority.fast) * 1.1).round();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,13 +17,12 @@ class DFXBuyProvider extends BuyProvider {
|
|||
: super(wallet: wallet, isTestEnvironment: isTestEnvironment);
|
||||
|
||||
static const _baseUrl = 'api.dfx.swiss';
|
||||
static const _authPath = '/v1/auth/signMessage';
|
||||
static const _signUpPath = '/v1/auth/signUp';
|
||||
static const _signInPath = '/v1/auth/signIn';
|
||||
// static const _signMessagePath = '/v1/auth/signMessage';
|
||||
static const _authPath = '/v1/auth';
|
||||
static const walletName = 'CakeWallet';
|
||||
|
||||
@override
|
||||
String get title => 'DFX Connect';
|
||||
String get title => 'DFX.swiss';
|
||||
|
||||
@override
|
||||
String get providerDescription => S.current.dfx_option_description;
|
||||
|
@ -73,21 +72,25 @@ class DFXBuyProvider extends BuyProvider {
|
|||
String get walletAddress =>
|
||||
wallet.walletAddresses.primaryAddress ?? wallet.walletAddresses.address;
|
||||
|
||||
Future<String> getSignMessage() async {
|
||||
final uri = Uri.https(_baseUrl, _authPath, {'address': walletAddress});
|
||||
Future<String> getSignMessage() async =>
|
||||
"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) {
|
||||
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 {
|
||||
Future<String> auth() async {
|
||||
final signMessage = getSignature(await getSignMessage());
|
||||
|
||||
final requestBody = jsonEncode({
|
||||
|
@ -96,7 +99,7 @@ class DFXBuyProvider extends BuyProvider {
|
|||
'signature': signMessage,
|
||||
});
|
||||
|
||||
final uri = Uri.https(_baseUrl, _signUpPath);
|
||||
final uri = Uri.https(_baseUrl, _authPath);
|
||||
var response = await http.post(
|
||||
uri,
|
||||
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) {
|
||||
switch (wallet.type) {
|
||||
case WalletType.ethereum:
|
||||
|
@ -164,17 +140,7 @@ class DFXBuyProvider extends BuyProvider {
|
|||
final blockchain = this.blockchain;
|
||||
final actionType = isBuyAction == true ? '/buy' : '/sell';
|
||||
|
||||
String accessToken;
|
||||
|
||||
try {
|
||||
accessToken = await signUp();
|
||||
} on Exception catch (e) {
|
||||
if (e.toString().contains('409')) {
|
||||
accessToken = await signIn();
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
final accessToken = await auth();
|
||||
|
||||
final uri = Uri.https('services.dfx.swiss', actionType, {
|
||||
'session': accessToken,
|
||||
|
@ -198,7 +164,7 @@ class DFXBuyProvider extends BuyProvider {
|
|||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: "DFX Connect",
|
||||
alertTitle: "DFX.swiss",
|
||||
alertContent: S.of(context).buy_provider_unavailable + ': $e',
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
|
|
9
lib/core/create_trade_result.dart
Normal file
9
lib/core/create_trade_result.dart
Normal file
|
@ -0,0 +1,9 @@
|
|||
class CreateTradeResult {
|
||||
bool result;
|
||||
String? errorMessage;
|
||||
|
||||
CreateTradeResult({
|
||||
required this.result,
|
||||
this.errorMessage,
|
||||
});
|
||||
}
|
|
@ -11,5 +11,9 @@ class NodeAddressValidator extends TextValidator {
|
|||
|
||||
class NodePathValidator extends TextValidator {
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import 'package:cake_wallet/core/generate_wallet_password.dart';
|
||||
import 'package:cake_wallet/core/key_service.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_info.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class WalletLoadingService {
|
||||
|
@ -36,15 +40,43 @@ class WalletLoadingService {
|
|||
}
|
||||
|
||||
Future<WalletBase> load(WalletType type, String name, {String? password}) async {
|
||||
final walletService = walletServiceFactory.call(type);
|
||||
final walletPassword = password ?? (await keyService.getWalletPassword(walletName: name));
|
||||
final wallet = await walletService.openWallet(name, walletPassword);
|
||||
try {
|
||||
final walletService = walletServiceFactory.call(type);
|
||||
final walletPassword = password ?? (await keyService.getWalletPassword(walletName: name));
|
||||
final wallet = await walletService.openWallet(name, walletPassword);
|
||||
|
||||
if (type == WalletType.monero) {
|
||||
await updateMoneroWalletPassword(wallet);
|
||||
if (type == WalletType.monero) {
|
||||
await updateMoneroWalletPassword(wallet);
|
||||
}
|
||||
|
||||
return wallet;
|
||||
} catch (error, stack) {
|
||||
ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack));
|
||||
|
||||
// try opening another wallet that is not corrupted to give user access to the app
|
||||
final walletInfoSource = await CakeHive.openBox<WalletInfo>(WalletInfo.boxName);
|
||||
|
||||
for (var walletInfo in walletInfoSource.values) {
|
||||
try {
|
||||
final walletService = walletServiceFactory.call(walletInfo.type);
|
||||
final walletPassword = password ?? (await keyService.getWalletPassword(walletName: name));
|
||||
final wallet = await walletService.openWallet(walletInfo.name, walletPassword);
|
||||
|
||||
if (walletInfo.type == WalletType.monero) {
|
||||
await updateMoneroWalletPassword(wallet);
|
||||
}
|
||||
|
||||
await sharedPreferences.setString(PreferencesKey.currentWalletName, wallet.name);
|
||||
await sharedPreferences.setInt(
|
||||
PreferencesKey.currentWalletType, serializeToInt(wallet.type));
|
||||
|
||||
return wallet;
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
// if all user's wallets are corrupted throw exception
|
||||
throw error;
|
||||
}
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
Future<void> updateMoneroWalletPassword(WalletBase wallet) async {
|
||||
|
|
|
@ -29,4 +29,4 @@ class BiometricAuth {
|
|||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,8 @@ String calculateFiatAmount({double? price, String? cryptoAmount}) {
|
|||
return '0.00';
|
||||
}
|
||||
|
||||
cryptoAmount = cryptoAmount.replaceAll(',', '.');
|
||||
|
||||
final _amount = double.parse(cryptoAmount);
|
||||
final _result = price * _amount;
|
||||
final result = _result < 0 ? _result * -1 : _result;
|
||||
|
|
|
@ -220,6 +220,10 @@ Future<void> defaultSettingsMigration(
|
|||
await updateNanoNodeList(nodes: nodes);
|
||||
break;
|
||||
|
||||
case 32:
|
||||
await updateBtcNanoWalletInfos(walletInfoSource);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -757,6 +761,20 @@ Future<void> changeDefaultMoneroNode(
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> updateBtcNanoWalletInfos(Box<WalletInfo> walletsInfoSource) async {
|
||||
for (WalletInfo walletInfo in walletsInfoSource.values) {
|
||||
if (walletInfo.type == WalletType.nano || walletInfo.type == WalletType.bitcoin) {
|
||||
walletInfo.derivationInfo = DerivationInfo(
|
||||
derivationPath: walletInfo.derivationPath,
|
||||
derivationType: walletInfo.derivationType,
|
||||
address: walletInfo.address,
|
||||
transactionsCount: walletInfo.restoreHeight,
|
||||
);
|
||||
await walletInfo.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> changeDefaultBitcoinNode(
|
||||
Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
|
||||
const cakeWalletBitcoinNodeUriPattern = '.cakewallet.com';
|
||||
|
|
|
@ -51,10 +51,12 @@ class AddressResolver {
|
|||
}
|
||||
|
||||
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) {
|
||||
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 '';
|
||||
|
|
|
@ -22,7 +22,7 @@ extension ProviderTypeName on ProviderType {
|
|||
case ProviderType.robinhood:
|
||||
return 'Robinhood Connect';
|
||||
case ProviderType.dfx:
|
||||
return 'DFX Connect';
|
||||
return 'DFX.swiss';
|
||||
case ProviderType.onramper:
|
||||
return 'Onramper';
|
||||
case ProviderType.moonpay:
|
||||
|
|
|
@ -143,8 +143,10 @@ class CWEthereum extends Ethereum {
|
|||
}
|
||||
|
||||
wallet as EthereumWallet;
|
||||
return wallet.erc20Currencies
|
||||
.firstWhere((element) => transaction.tokenSymbol == element.symbol);
|
||||
|
||||
return wallet.erc20Currencies.firstWhere(
|
||||
(element) => transaction.tokenSymbol == element.symbol,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -955,4 +955,4 @@ String get todayLabel => 'Oyọ';
|
|||
|
||||
@override
|
||||
String get noSpellCheckReplacementsLabel => "";
|
||||
}
|
||||
}
|
|
@ -106,6 +106,10 @@ Future<void> initializeAppConfigs() async {
|
|||
CakeHive.registerAdapter(DerivationTypeAdapter());
|
||||
}
|
||||
|
||||
if (!CakeHive.isAdapterRegistered(DERIVATION_INFO_TYPE_ID)) {
|
||||
CakeHive.registerAdapter(DerivationInfoAdapter());
|
||||
}
|
||||
|
||||
if (!CakeHive.isAdapterRegistered(WALLET_TYPE_TYPE_ID)) {
|
||||
CakeHive.registerAdapter(WalletTypeAdapter());
|
||||
}
|
||||
|
@ -165,7 +169,7 @@ Future<void> initializeAppConfigs() async {
|
|||
transactionDescriptions: transactionDescriptions,
|
||||
secureStorage: secureStorage,
|
||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||
initialMigrationVersion: 31,
|
||||
initialMigrationVersion: 32,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -96,6 +96,7 @@ class CWNano extends Nano {
|
|||
NanoNewWalletCredentials(
|
||||
name: name,
|
||||
password: password,
|
||||
derivationType: DerivationType.nano,
|
||||
);
|
||||
|
||||
@override
|
||||
|
@ -103,15 +104,10 @@ class CWNano extends Nano {
|
|||
required String name,
|
||||
required String password,
|
||||
required String mnemonic,
|
||||
DerivationType? derivationType,
|
||||
required DerivationType derivationType,
|
||||
}) {
|
||||
if (derivationType == null) {
|
||||
// figure out the derivation type as best we can, otherwise set it to "unknown"
|
||||
if (mnemonic.split(" ").length == 12) {
|
||||
derivationType = DerivationType.bip39;
|
||||
} else {
|
||||
derivationType = DerivationType.unknown;
|
||||
}
|
||||
if (mnemonic.split(" ").length == 12 && derivationType != DerivationType.bip39) {
|
||||
throw Exception("Invalid mnemonic for derivation type!");
|
||||
}
|
||||
|
||||
return NanoRestoreWalletFromSeedCredentials(
|
||||
|
@ -127,15 +123,10 @@ class CWNano extends Nano {
|
|||
required String name,
|
||||
required String password,
|
||||
required String seedKey,
|
||||
DerivationType? derivationType,
|
||||
required DerivationType derivationType,
|
||||
}) {
|
||||
if (derivationType == null) {
|
||||
// figure out the derivation type as best we can, otherwise set it to "unknown"
|
||||
if (seedKey.length == 64) {
|
||||
derivationType = DerivationType.nano;
|
||||
} else {
|
||||
derivationType = DerivationType.unknown;
|
||||
}
|
||||
if (seedKey.length == 128 && derivationType != DerivationType.bip39) {
|
||||
throw Exception("Invalid seed key length for derivation type!");
|
||||
}
|
||||
|
||||
return NanoRestoreWalletFromKeysCredentials(
|
||||
|
@ -199,7 +190,6 @@ class CWNano extends Nano {
|
|||
}
|
||||
|
||||
class CWNanoUtil extends NanoUtil {
|
||||
|
||||
@override
|
||||
bool isValidBip39Seed(String seed) {
|
||||
return NanoDerivations.isValidBip39Seed(seed);
|
||||
|
@ -353,4 +343,54 @@ class CWNanoUtil extends NanoUtil {
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,8 +141,10 @@ class CWPolygon extends Polygon {
|
|||
}
|
||||
|
||||
wallet as PolygonWallet;
|
||||
|
||||
return wallet.erc20Currencies.firstWhere(
|
||||
(element) => transaction.tokenSymbol.toLowerCase() == element.symbol.toLowerCase());
|
||||
(element) => transaction.tokenSymbol.toLowerCase() == element.symbol.toLowerCase(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -111,8 +111,10 @@ class CWSolana extends Solana {
|
|||
}
|
||||
|
||||
wallet as SolanaWallet;
|
||||
return wallet.splTokenCurrencies
|
||||
.firstWhere((element) => transaction.tokenSymbol == element.symbol);
|
||||
|
||||
return wallet.splTokenCurrencies.firstWhere(
|
||||
(element) => transaction.tokenSymbol == element.symbol,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -96,18 +96,20 @@ class NodeForm extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
SizedBox(height: 10.0),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: BaseTextFormField(
|
||||
controller: _pathController,
|
||||
hintText: "/path",
|
||||
validator: NodePathValidator(),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(height: 10.0),
|
||||
if (nodeViewModel.hasPathSupport) ...[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: BaseTextFormField(
|
||||
controller: _pathController,
|
||||
hintText: "/path",
|
||||
validator: NodePathValidator(),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(height: 10.0),
|
||||
],
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import 'package:cake_wallet/di.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/theme_base.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -13,15 +10,14 @@ class WalletRestoreChooseDerivationPage extends BasePage {
|
|||
WalletRestoreChooseDerivationPage(this.walletRestoreChooseDerivationViewModel) {}
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) => Observer(
|
||||
builder: (_) => Text(
|
||||
S.current.choose_derivation,
|
||||
style: TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'Lato',
|
||||
color: titleColor(context)),
|
||||
));
|
||||
Widget middle(BuildContext context) => Text(
|
||||
S.current.choose_derivation,
|
||||
style: TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'Lato',
|
||||
color: titleColor(context)),
|
||||
);
|
||||
|
||||
final WalletRestoreChooseDerivationViewModel walletRestoreChooseDerivationViewModel;
|
||||
DerivationType derivationType = DerivationType.unknown;
|
||||
|
@ -105,7 +101,7 @@ class WalletRestoreChooseDerivationPage extends BasePage {
|
|||
),
|
||||
),
|
||||
Text(
|
||||
"${S.current.transactions}: ${derivation.height}",
|
||||
"${S.current.transactions}: ${derivation.transactionsCount}",
|
||||
style: Theme.of(context).primaryTextTheme.labelMedium!.copyWith(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
|
|
|
@ -20,6 +20,7 @@ class WalletRestoreFromSeedForm extends StatefulWidget {
|
|||
{Key? key,
|
||||
required this.displayLanguageSelector,
|
||||
required this.displayBlockHeightSelector,
|
||||
required this.displayPassphrase,
|
||||
required this.type,
|
||||
required this.displayWalletPassword,
|
||||
required this.seedTypeViewModel,
|
||||
|
@ -35,6 +36,7 @@ class WalletRestoreFromSeedForm extends StatefulWidget {
|
|||
final bool displayLanguageSelector;
|
||||
final bool displayBlockHeightSelector;
|
||||
final bool displayWalletPassword;
|
||||
final bool displayPassphrase;
|
||||
final SeedTypeViewModel seedTypeViewModel;
|
||||
final FocusNode? blockHeightFocusNode;
|
||||
final Function(bool)? onHeightOrDateEntered;
|
||||
|
@ -57,6 +59,7 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
|
|||
nameTextEditingController = TextEditingController(),
|
||||
passwordTextEditingController = displayWalletPassword ? TextEditingController() : null,
|
||||
repeatedPasswordTextEditingController = displayWalletPassword ? TextEditingController() : null,
|
||||
passphraseController = TextEditingController(),
|
||||
seedTypeController = TextEditingController();
|
||||
|
||||
final GlobalKey<SeedWidgetState> seedWidgetStateKey;
|
||||
|
@ -66,6 +69,7 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
|
|||
final TextEditingController? passwordTextEditingController;
|
||||
final TextEditingController? repeatedPasswordTextEditingController;
|
||||
final TextEditingController seedTypeController;
|
||||
final TextEditingController passphraseController;
|
||||
final GlobalKey<FormState> formKey;
|
||||
late ReactionDisposer moneroSeedTypeReaction;
|
||||
String language;
|
||||
|
@ -206,15 +210,15 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
|
|||
obscureText: true)],
|
||||
if (widget.displayLanguageSelector)
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) => SeedLanguagePicker(
|
||||
selected: language,
|
||||
onItemSelected: _changeLanguage,
|
||||
seedType: isPolyseed ? SeedType.polyseed : SeedType.legacy,
|
||||
));
|
||||
},
|
||||
onTap: () async {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) => SeedLanguagePicker(
|
||||
selected: language,
|
||||
onItemSelected: _changeLanguage,
|
||||
seedType: isPolyseed ? SeedType.polyseed : SeedType.legacy,
|
||||
));
|
||||
},
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
padding: EdgeInsets.only(top: 20.0),
|
||||
|
@ -234,6 +238,14 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
|
|||
key: blockchainHeightKey,
|
||||
onHeightOrDateEntered: widget.onHeightOrDateEntered,
|
||||
hasDatePicker: widget.type == WalletType.monero),
|
||||
if (widget.displayPassphrase) ...[
|
||||
const SizedBox(height: 10),
|
||||
BaseTextFormField(
|
||||
hintText: S.current.passphrase,
|
||||
controller: passphraseController,
|
||||
obscureText: true,
|
||||
),
|
||||
]
|
||||
]));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
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/nano/nano.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.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/keyboard_done_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/wallet_list_theme.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/seed_type_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_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -42,6 +38,7 @@ class WalletRestorePage extends BasePage {
|
|||
displayBlockHeightSelector:
|
||||
walletRestoreViewModel.hasBlockchainHeightLanguageSelector,
|
||||
displayLanguageSelector: walletRestoreViewModel.hasSeedLanguageSelector,
|
||||
displayPassphrase: walletRestoreViewModel.hasPassphrase,
|
||||
type: walletRestoreViewModel.type,
|
||||
key: walletRestoreFromSeedFormKey,
|
||||
blockHeightFocusNode: _blockHeightFocusNode,
|
||||
|
@ -105,8 +102,10 @@ class WalletRestorePage extends BasePage {
|
|||
final GlobalKey<WalletRestoreFromSeedFormState> walletRestoreFromSeedFormKey;
|
||||
final GlobalKey<WalletRestoreFromKeysFromState> walletRestoreFromKeysFormKey;
|
||||
final FocusNode _blockHeightFocusNode;
|
||||
DerivationType derivationType = DerivationType.unknown;
|
||||
String? derivationPath = null;
|
||||
|
||||
// DerivationType derivationType = DerivationType.unknown;
|
||||
// String? derivationPath = null;
|
||||
DerivationInfo? derivationInfo;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
|
@ -306,6 +305,11 @@ class WalletRestorePage extends BasePage {
|
|||
-1;
|
||||
}
|
||||
|
||||
if (walletRestoreViewModel.hasPassphrase) {
|
||||
credentials['passphrase'] =
|
||||
walletRestoreFromSeedFormKey.currentState!.passphraseController.text;
|
||||
}
|
||||
|
||||
credentials['name'] =
|
||||
walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.text;
|
||||
} else if (walletRestoreViewModel.mode == WalletRestoreMode.keys) {
|
||||
|
@ -326,58 +330,11 @@ class WalletRestorePage extends BasePage {
|
|||
}
|
||||
}
|
||||
|
||||
credentials['derivationType'] = this.derivationType;
|
||||
credentials['derivationPath'] = this.derivationPath;
|
||||
credentials['derivationInfo'] = this.derivationInfo;
|
||||
credentials['walletType'] = walletRestoreViewModel.type;
|
||||
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 {
|
||||
// Dismissing all visible keyboard to provide context for navigation
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
|
@ -406,51 +363,46 @@ class WalletRestorePage extends BasePage {
|
|||
|
||||
walletRestoreViewModel.state = IsExecutingState();
|
||||
|
||||
List<DerivationType> derivationTypes =
|
||||
await walletRestoreViewModel.getDerivationTypes(_credentials());
|
||||
DerivationInfo? dInfo;
|
||||
|
||||
if (derivationTypes[0] == DerivationType.unknown || derivationTypes.length > 1) {
|
||||
// push screen to choose the derivation type:
|
||||
List<DerivationInfo> derivations = await getDerivationInfo(_credentials());
|
||||
// get info about the different derivations:
|
||||
List<DerivationInfo> derivations =
|
||||
await walletRestoreViewModel.getDerivationInfo(_credentials());
|
||||
|
||||
int derivationsWithHistory = 0;
|
||||
int derivationWithHistoryIndex = 0;
|
||||
for (int i = 0; i < derivations.length; i++) {
|
||||
if (derivations[i].height > 0) {
|
||||
derivationsWithHistory++;
|
||||
derivationWithHistoryIndex = i;
|
||||
}
|
||||
int derivationsWithHistory = 0;
|
||||
int derivationWithHistoryIndex = 0;
|
||||
for (int i = 0; i < derivations.length; i++) {
|
||||
if (derivations[i].transactionsCount > 0) {
|
||||
derivationsWithHistory++;
|
||||
derivationWithHistoryIndex = i;
|
||||
}
|
||||
|
||||
DerivationInfo? derivationInfo;
|
||||
|
||||
if (derivationsWithHistory > 1) {
|
||||
derivationInfo = await Navigator.of(context).pushNamed(Routes.restoreWalletChooseDerivation,
|
||||
arguments: derivations) as DerivationInfo?;
|
||||
} else if (derivationsWithHistory == 1) {
|
||||
derivationInfo = derivations[derivationWithHistoryIndex];
|
||||
} else if (derivationsWithHistory == 0) {
|
||||
// default derivation:
|
||||
derivationInfo = DerivationInfo(
|
||||
derivationType: derivationTypes[0],
|
||||
derivationPath: "m/0'/1",
|
||||
height: 0,
|
||||
);
|
||||
}
|
||||
|
||||
if (derivationInfo == null) {
|
||||
walletRestoreViewModel.state = InitialExecutionState();
|
||||
return;
|
||||
}
|
||||
this.derivationType = derivationInfo.derivationType;
|
||||
this.derivationPath = derivationInfo.derivationPath;
|
||||
} else {
|
||||
// electrum derivation:
|
||||
this.derivationType = derivationTypes[0];
|
||||
this.derivationPath = "m/0'/1";
|
||||
}
|
||||
|
||||
walletRestoreViewModel.state = InitialExecutionState();
|
||||
if (derivationsWithHistory > 1) {
|
||||
dInfo = await Navigator.of(context).pushNamed(
|
||||
Routes.restoreWalletChooseDerivation,
|
||||
arguments: derivations,
|
||||
) as DerivationInfo?;
|
||||
} else if (derivationsWithHistory == 1) {
|
||||
dInfo = derivations[derivationWithHistoryIndex];
|
||||
}
|
||||
|
||||
// get the default derivation for this wallet type:
|
||||
if (dInfo == null) {
|
||||
// we only return 1 derivation if we're pretty sure we know which one to use:
|
||||
if (derivations.length == 1) {
|
||||
dInfo = derivations.first;
|
||||
} else {
|
||||
// if we have multiple possible derivations, and none have histories
|
||||
// we just default to the most common one:
|
||||
dInfo = walletRestoreViewModel.getCommonRestoreDerivation();
|
||||
}
|
||||
}
|
||||
|
||||
this.derivationInfo = dInfo;
|
||||
if (this.derivationInfo == null) {
|
||||
this.derivationInfo = walletRestoreViewModel.getDefaultDerivation();
|
||||
}
|
||||
|
||||
walletRestoreViewModel.create(options: _credentials());
|
||||
}
|
||||
|
|
|
@ -100,9 +100,9 @@ class SendPage extends BasePage {
|
|||
AppBarStyle get appBarStyle => AppBarStyle.transparent;
|
||||
|
||||
double _sendCardHeight(BuildContext context) {
|
||||
double initialHeight = 450;
|
||||
double initialHeight = 480;
|
||||
if (sendViewModel.hasCoinControl) {
|
||||
initialHeight += 35;
|
||||
initialHeight += 55;
|
||||
}
|
||||
|
||||
if (!responsiveLayoutUtil.shouldRenderMobileUI) {
|
||||
|
|
|
@ -675,6 +675,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
|||
final selectedItem = items.indexOf(sendViewModel.transactionPriority);
|
||||
final customItemIndex = sendViewModel.getCustomPriorityIndex(items);
|
||||
final isBitcoinWallet = sendViewModel.walletType == WalletType.bitcoin;
|
||||
final maxCustomFeeRate = sendViewModel.maxCustomFeeRate?.toDouble();
|
||||
double? customFeeRate = isBitcoinWallet ? sendViewModel.customBitcoinFeeRate.toDouble() : null;
|
||||
|
||||
await showPopUp<void>(
|
||||
|
@ -689,6 +690,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
|||
sendViewModel.displayFeeRate(priority, customFeeRate?.round()),
|
||||
selectedAtIndex: selectedIdx,
|
||||
customItemIndex: customItemIndex,
|
||||
maxValue: maxCustomFeeRate,
|
||||
title: S.of(context).please_select,
|
||||
headerEnabled: !isBitcoinWallet,
|
||||
closeOnItemSelected: !isBitcoinWallet,
|
||||
|
|
|
@ -37,6 +37,7 @@ class OtherSettingsPage extends BasePage {
|
|||
customItemIndex: _otherSettingsViewModel.customPriorityItemIndex,
|
||||
onItemSelected: _otherSettingsViewModel.onDisplayBitcoinPrioritySelected,
|
||||
customValue: _otherSettingsViewModel.customBitcoinFeeRate,
|
||||
maxValue: _otherSettingsViewModel.maxCustomFeeRate?.toDouble(),
|
||||
) :
|
||||
SettingsPickerCell(
|
||||
title: S.current.settings_fee_priority,
|
||||
|
|
|
@ -15,6 +15,7 @@ class SettingsPriorityPickerCell<ItemType> extends StandardListRow {
|
|||
this.isGridView = false,
|
||||
this.matchingCriteria,
|
||||
this.customValue,
|
||||
this.maxValue,
|
||||
this.customItemIndex,
|
||||
this.onItemSelected})
|
||||
: super(
|
||||
|
@ -34,6 +35,7 @@ class SettingsPriorityPickerCell<ItemType> extends StandardListRow {
|
|||
displayItem: (ItemType item) => displayItem!(item, sliderValue.round()),
|
||||
selectedAtIndex: selectedAtIndex,
|
||||
customItemIndex: customItemIndex,
|
||||
maxValue: maxValue,
|
||||
headerEnabled: false,
|
||||
closeOnItemSelected: false,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
@ -61,6 +63,7 @@ class SettingsPriorityPickerCell<ItemType> extends StandardListRow {
|
|||
final bool isGridView;
|
||||
final bool Function(ItemType, String)? matchingCriteria;
|
||||
double? customValue;
|
||||
double? maxValue;
|
||||
int? customItemIndex;
|
||||
|
||||
@override
|
||||
|
|
|
@ -10,6 +10,7 @@ class StandardPickerListItem<T> extends TransactionDetailsListItem {
|
|||
required this.onItemSelected,
|
||||
required this.selectedIdx,
|
||||
required this.customItemIndex,
|
||||
this.maxValue,
|
||||
required this.customValue})
|
||||
: super(title: title, value: value);
|
||||
|
||||
|
@ -18,6 +19,7 @@ class StandardPickerListItem<T> extends TransactionDetailsListItem {
|
|||
final Function(double) onSliderChanged;
|
||||
final Function(T) onItemSelected;
|
||||
final int selectedIdx;
|
||||
final double? maxValue;
|
||||
final int customItemIndex;
|
||||
double customValue;
|
||||
}
|
||||
|
|
|
@ -74,6 +74,7 @@ class RBFDetailsPage extends BasePage {
|
|||
selectedIdx: item.selectedIdx,
|
||||
customItemIndex: item.customItemIndex,
|
||||
customValue: item.customValue,
|
||||
maxValue: item.maxValue,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -360,7 +360,9 @@ class WalletListBodyState extends State<WalletListBody> {
|
|||
});
|
||||
}
|
||||
} catch (e) {
|
||||
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
|
||||
if (this.mounted) {
|
||||
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
|
||||
}
|
||||
}
|
||||
},
|
||||
conditionToDetermineIfToUse2FA:
|
||||
|
|
|
@ -22,6 +22,7 @@ class BaseTextFormField extends StatelessWidget {
|
|||
this.enabled = true,
|
||||
this.readOnly = false,
|
||||
this.enableInteractiveSelection = true,
|
||||
this.obscureText = false,
|
||||
this.validator,
|
||||
this.textStyle,
|
||||
this.placeholderTextStyle,
|
||||
|
@ -72,6 +73,7 @@ class BaseTextFormField extends StatelessWidget {
|
|||
textInputAction: textInputAction,
|
||||
textAlign: textAlign,
|
||||
autovalidateMode: autovalidateMode,
|
||||
obscureText: obscureText,
|
||||
maxLines: maxLines,
|
||||
inputFormatters: inputFormatters,
|
||||
enabled: enabled,
|
||||
|
|
|
@ -27,14 +27,21 @@ class Picker<Item> extends StatefulWidget {
|
|||
this.headerEnabled = true,
|
||||
this.closeOnItemSelected = true,
|
||||
this.sliderValue,
|
||||
this.minValue,
|
||||
this.maxValue,
|
||||
this.customItemIndex,
|
||||
this.isWrapped = true,
|
||||
this.borderColor,
|
||||
this.onSliderChanged,
|
||||
this.matchingCriteria,
|
||||
}) : assert(hintText == null ||
|
||||
matchingCriteria !=
|
||||
null); // make sure that if the search field is enabled then there is a searching criteria provided
|
||||
}) : assert(hintText == null || matchingCriteria != 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 List<Item> items;
|
||||
|
@ -49,12 +56,14 @@ class Picker<Item> extends StatefulWidget {
|
|||
final String? hintText;
|
||||
final bool headerEnabled;
|
||||
final bool closeOnItemSelected;
|
||||
final double? sliderValue;
|
||||
double? sliderValue;
|
||||
final double? minValue;
|
||||
final int? customItemIndex;
|
||||
final bool isWrapped;
|
||||
final Color? borderColor;
|
||||
final Function(double)? onSliderChanged;
|
||||
final bool Function(Item, String)? matchingCriteria;
|
||||
final double? maxValue;
|
||||
|
||||
@override
|
||||
_PickerState<Item> createState() => _PickerState<Item>(items, images, onItemSelected);
|
||||
|
@ -138,7 +147,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
|||
containerHeight = height * 0.75;
|
||||
}
|
||||
|
||||
final content = Column (
|
||||
final content = Column(
|
||||
children: [
|
||||
if (widget.title?.isNotEmpty ?? false)
|
||||
Container(
|
||||
|
@ -211,8 +220,9 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
|||
fontWeight: FontWeight.w500,
|
||||
fontFamily: 'Lato',
|
||||
decoration: TextDecoration.none,
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
color: Theme.of(context)
|
||||
.extension<CakeTextTheme>()!
|
||||
.titleColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -491,8 +501,8 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
|||
child: Slider(
|
||||
value: widget.sliderValue ?? 1,
|
||||
onChanged: isActivated ? widget.onSliderChanged : null,
|
||||
min: 1,
|
||||
max: 100,
|
||||
min: widget.minValue ?? 1,
|
||||
max: widget.maxValue ?? 100,
|
||||
divisions: 100,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -15,6 +15,7 @@ class StandardPickerList<T> extends StatefulWidget {
|
|||
required this.selectedIdx,
|
||||
required this.customItemIndex,
|
||||
required this.customValue,
|
||||
this.maxValue,
|
||||
}) : super(key: key);
|
||||
|
||||
final String title;
|
||||
|
@ -26,6 +27,7 @@ class StandardPickerList<T> extends StatefulWidget {
|
|||
final String value;
|
||||
final int selectedIdx;
|
||||
final double customValue;
|
||||
final double? maxValue;
|
||||
|
||||
@override
|
||||
_StandardPickerListState<T> createState() => _StandardPickerListState<T>();
|
||||
|
@ -59,6 +61,7 @@ class _StandardPickerListState<T> extends State<StandardPickerList<T>> {
|
|||
displayItem: adaptedDisplayItem,
|
||||
selectedAtIndex: selectedIdx,
|
||||
customItemIndex: widget.customItemIndex,
|
||||
maxValue: widget.maxValue,
|
||||
headerEnabled: false,
|
||||
closeOnItemSelected: false,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
|
|
@ -167,4 +167,4 @@ class ValidatableAnnotatedEditableTextState extends EditableTextState {
|
|||
|
||||
return TextSpan(style: widget.style, text: text);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -125,4 +125,4 @@ abstract class AuthViewModelBase with Store {
|
|||
_authService.saveLastAuthTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,8 +3,20 @@ import 'dart:collection';
|
|||
import 'dart:convert';
|
||||
|
||||
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_cash/bitcoin_cash.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/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/utils/feature_flag.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';
|
||||
|
||||
|
@ -516,10 +520,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
|||
trade.walletId = wallet.id;
|
||||
trade.fromWalletAddress = wallet.walletAddresses.address;
|
||||
|
||||
if (!isCanCreateTrade(trade)) {
|
||||
final canCreateTrade = await isCanCreateTrade(trade);
|
||||
if (!canCreateTrade.result) {
|
||||
tradeState = TradeIsCreatedFailure(
|
||||
title: S.current.trade_not_created,
|
||||
error: S.current.thorchain_taproot_address_not_supported);
|
||||
title: S.current.trade_not_created,
|
||||
error: canCreateTrade.errorMessage ?? '',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -776,16 +782,100 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
|||
|
||||
int get receiveMaxDigits => receiveCurrency.decimals;
|
||||
|
||||
bool isCanCreateTrade(Trade trade) {
|
||||
Future<CreateTradeResult> isCanCreateTrade(Trade trade) async {
|
||||
if (trade.provider == ExchangeProviderDescription.thorChain) {
|
||||
final payoutAddress = trade.payoutAddress ?? '';
|
||||
final fromWalletAddress = trade.fromWalletAddress ?? '';
|
||||
final tapRootPattern = RegExp(P2trAddress.regex.pattern);
|
||||
|
||||
if (tapRootPattern.hasMatch(payoutAddress) || tapRootPattern.hasMatch(fromWalletAddress)) {
|
||||
return false;
|
||||
return CreateTradeResult(
|
||||
result: false,
|
||||
errorMessage: S.current.thorchain_taproot_address_not_supported,
|
||||
);
|
||||
}
|
||||
|
||||
final currenciesToCheckPattern = RegExp('0x[0-9a-zA-Z]');
|
||||
|
||||
// Perform checks for payOutAddress
|
||||
final isPayOutAddressAccordingToPattern = currenciesToCheckPattern.hasMatch(payoutAddress);
|
||||
|
||||
if (isPayOutAddressAccordingToPattern) {
|
||||
final isPayOutAddressEOA = await _isExternallyOwnedAccountAddress(payoutAddress);
|
||||
|
||||
return CreateTradeResult(
|
||||
result: isPayOutAddressEOA,
|
||||
errorMessage:
|
||||
!isPayOutAddressEOA ? S.current.thorchain_contract_address_not_supported : null,
|
||||
);
|
||||
}
|
||||
|
||||
// Perform checks for fromWalletAddress
|
||||
final isFromWalletAddressAddressAccordingToPattern =
|
||||
currenciesToCheckPattern.hasMatch(fromWalletAddress);
|
||||
|
||||
if (isFromWalletAddressAddressAccordingToPattern) {
|
||||
final isFromWalletAddressEOA = await _isExternallyOwnedAccountAddress(fromWalletAddress);
|
||||
|
||||
return CreateTradeResult(
|
||||
result: isFromWalletAddressEOA,
|
||||
errorMessage:
|
||||
!isFromWalletAddressEOA ? S.current.thorchain_contract_address_not_supported : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return CreateTradeResult(result: true);
|
||||
}
|
||||
|
||||
String _normalizeReceiveCurrency(CryptoCurrency receiveCurrency) {
|
||||
switch (receiveCurrency) {
|
||||
case CryptoCurrency.eth:
|
||||
return 'eth';
|
||||
case CryptoCurrency.maticpoly:
|
||||
return 'polygon';
|
||||
default:
|
||||
return receiveCurrency.tag ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _isExternallyOwnedAccountAddress(String receivingAddress) async {
|
||||
final normalizedReceiveCurrency = _normalizeReceiveCurrency(receiveCurrency);
|
||||
|
||||
final isEOAAddress = !(await _isContractAddress(normalizedReceiveCurrency, receivingAddress));
|
||||
return isEOAAddress;
|
||||
}
|
||||
|
||||
Future<bool> _isContractAddress(String chainName, String contractAddress) async {
|
||||
final httpClient = http.Client();
|
||||
|
||||
final uri = Uri.https(
|
||||
'deep-index.moralis.io',
|
||||
'/api/v2.2/erc20/metadata',
|
||||
{
|
||||
"chain": chainName,
|
||||
"addresses": contractAddress,
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
final response = await httpClient.get(
|
||||
uri,
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"X-API-Key": secrets.moralisApiKey,
|
||||
},
|
||||
);
|
||||
|
||||
final decodedResponse = jsonDecode(response.body)[0] as Map<String, dynamic>;
|
||||
|
||||
final name = decodedResponse['name'] as String?;
|
||||
|
||||
bool isContractAddress = name!.isNotEmpty;
|
||||
|
||||
return isContractAddress;
|
||||
} catch (e) {
|
||||
print(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,6 +69,24 @@ abstract class NodeCreateOrEditViewModelBase with Store {
|
|||
|
||||
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 {
|
||||
var uri = address;
|
||||
|
||||
|
@ -217,7 +235,6 @@ abstract class NodeCreateOrEditViewModelBase with Store {
|
|||
final port = uri.port.toString();
|
||||
final path = uri.path;
|
||||
|
||||
|
||||
setAddress(ipAddress);
|
||||
setPath(path);
|
||||
setPassword(rpcPassword);
|
||||
|
|
|
@ -30,8 +30,7 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
|
|||
spendKey = '',
|
||||
wif = '',
|
||||
address = '',
|
||||
super(appStore, walletInfoSource, walletCreationService,
|
||||
type: type, isRecovery: true);
|
||||
super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true);
|
||||
|
||||
@observable
|
||||
int height;
|
||||
|
@ -51,8 +50,16 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
|
|||
bool get hasRestorationHeight => type == WalletType.monero;
|
||||
|
||||
@override
|
||||
WalletCredentials getCredentialsFromRestoredWallet(dynamic options, RestoredWallet restoreWallet) {
|
||||
WalletCredentials getCredentialsFromRestoredWallet(
|
||||
dynamic options, RestoredWallet restoreWallet) {
|
||||
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) {
|
||||
case WalletRestoreMode.keys:
|
||||
|
@ -86,23 +93,37 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
|
|||
switch (restoreWallet.type) {
|
||||
case WalletType.monero:
|
||||
return monero!.createMoneroRestoreWalletFromSeedCredentials(
|
||||
name: name,
|
||||
height: restoreWallet.height ?? 0,
|
||||
mnemonic: restoreWallet.mnemonicSeed ?? '',
|
||||
password: password);
|
||||
name: name,
|
||||
height: restoreWallet.height ?? 0,
|
||||
mnemonic: restoreWallet.mnemonicSeed ?? '',
|
||||
password: password,
|
||||
);
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
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:
|
||||
return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials(
|
||||
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
|
||||
name: name,
|
||||
mnemonic: restoreWallet.mnemonicSeed ?? '',
|
||||
password: password,
|
||||
);
|
||||
case WalletType.ethereum:
|
||||
return ethereum!.createEthereumRestoreWalletFromSeedCredentials(
|
||||
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
|
||||
case WalletType.nano:
|
||||
return nano!.createNanoRestoreWalletFromSeedCredentials(
|
||||
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
|
||||
name: name,
|
||||
mnemonic: restoreWallet.mnemonicSeed ?? '',
|
||||
password: password,
|
||||
derivationType: derivationInfo!.derivationType!,
|
||||
);
|
||||
case WalletType.polygon:
|
||||
return polygon!.createPolygonRestoreWalletFromSeedCredentials(
|
||||
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
|
||||
|
@ -118,7 +139,8 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
|
|||
}
|
||||
|
||||
@override
|
||||
Future<WalletBase> processFromRestoredWallet(WalletCredentials credentials, RestoredWallet restoreWallet) async {
|
||||
Future<WalletBase> processFromRestoredWallet(
|
||||
WalletCredentials credentials, RestoredWallet restoreWallet) async {
|
||||
try {
|
||||
switch (restoreWallet.restoreMode) {
|
||||
case WalletRestoreMode.keys:
|
||||
|
|
|
@ -126,8 +126,8 @@ abstract class OutputBase with Store {
|
|||
|
||||
if (_wallet.type == WalletType.bitcoin) {
|
||||
if (_settingsStore.priority[_wallet.type] == bitcoin!.getBitcoinTransactionPriorityCustom()) {
|
||||
fee = bitcoin!.getFeeAmountWithFeeRate(
|
||||
_settingsStore.customBitcoinFeeRate, formattedCryptoAmount, 1, 1);
|
||||
fee = bitcoin!.getEstimatedFeeWithFeeRate(_wallet,
|
||||
_settingsStore.customBitcoinFeeRate,formattedCryptoAmount);
|
||||
}
|
||||
|
||||
return bitcoin!.formatterBitcoinAmountToDouble(amount: fee);
|
||||
|
|
|
@ -166,6 +166,13 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
return null;
|
||||
}
|
||||
|
||||
int? get maxCustomFeeRate {
|
||||
if (wallet.type == WalletType.bitcoin) {
|
||||
return bitcoin!.getMaxCustomFeeRate(wallet);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@computed
|
||||
int get customBitcoinFeeRate => _settingsStore.customBitcoinFeeRate;
|
||||
|
||||
|
@ -324,14 +331,16 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
Future<PendingTransaction?> createTransaction({ExchangeProvider? provider}) async {
|
||||
try {
|
||||
state = IsExecutingState();
|
||||
|
||||
pendingTransaction = await wallet.createTransaction(_credentials());
|
||||
if (provider is ThorChainExchangeProvider) {
|
||||
final outputCount = pendingTransaction?.outputCount ?? 0;
|
||||
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)) {
|
||||
throw Exception("ThorChain does not support Taproot addresses");
|
||||
throw Exception("THORChain does not support Taproot addresses");
|
||||
}
|
||||
}
|
||||
state = ExecutedSuccessfullyState();
|
||||
|
@ -414,7 +423,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
Object _credentials() {
|
||||
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}');
|
||||
}
|
||||
|
||||
|
@ -553,7 +562,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
return S.current.tx_no_dust_exception;
|
||||
}
|
||||
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) {
|
||||
return S.current.tx_rejected_dust_change;
|
||||
|
|
|
@ -140,6 +140,13 @@ abstract class OtherSettingsViewModelBase with Store {
|
|||
return customItem != null ? priorities.indexOf(customItem) : null;
|
||||
}
|
||||
|
||||
int? get maxCustomFeeRate {
|
||||
if (_wallet.type == WalletType.bitcoin) {
|
||||
return bitcoin!.getMaxCustomFeeRate(_wallet);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@action
|
||||
ProviderType onBuyProviderTypeSelected(ProviderType buyProviderType) =>
|
||||
_settingsStore.defaultBuyProviders[walletType] = buyProviderType;
|
||||
|
|
|
@ -348,12 +348,14 @@ abstract class TransactionDetailsViewModelBase with Store {
|
|||
final customItem = priorities.firstWhereOrNull(
|
||||
(element) => element == sendViewModel.bitcoinTransactionPriorityCustom);
|
||||
final customItemIndex = customItem != null ? priorities.indexOf(customItem) : null;
|
||||
final maxCustomFeeRate = sendViewModel.maxCustomFeeRate?.toDouble();
|
||||
|
||||
RBFListItems.add(StandardPickerListItem(
|
||||
title: S.current.estimated_new_fee,
|
||||
value: bitcoin!.formatterBitcoinAmountToString(amount: newFee) + ' ${walletTypeToCryptoCurrency(wallet.type)}',
|
||||
items: priorityForWalletType(wallet.type),
|
||||
customValue: settingsStore.customBitcoinFeeRate.toDouble(),
|
||||
maxValue: maxCustomFeeRate,
|
||||
selectedIdx: selectedItem,
|
||||
customItemIndex: customItemIndex ?? 0,
|
||||
displayItem: (dynamic priority, double sliderValue) =>
|
||||
|
@ -388,16 +390,12 @@ abstract class TransactionDetailsViewModelBase with Store {
|
|||
|
||||
String setNewFee({double? value, required TransactionPriority priority}) {
|
||||
newFee = priority == bitcoin!.getBitcoinTransactionPriorityCustom() && value != null
|
||||
? bitcoin!.getFeeAmountWithFeeRate(
|
||||
wallet,
|
||||
value.round(),
|
||||
transactionInfo.inputAddresses?.length ?? 1,
|
||||
transactionInfo.outputAddresses?.length ?? 1)
|
||||
? bitcoin!.getEstimatedFeeWithFeeRate(wallet, value.round(), transactionInfo.amount)
|
||||
: bitcoin!.getFeeAmountForPriority(
|
||||
wallet,
|
||||
priority,
|
||||
transactionInfo.inputAddresses?.length ?? 1,
|
||||
transactionInfo.outputAddresses?.length ?? 1);
|
||||
wallet,
|
||||
priority,
|
||||
transactionInfo.inputAddresses?.length ?? 1,
|
||||
transactionInfo.outputAddresses?.length ?? 1);
|
||||
|
||||
return bitcoin!.formatterBitcoinAmountToString(amount: newFee);
|
||||
}
|
||||
|
|
|
@ -89,9 +89,9 @@ abstract class WalletCreationVMBase with Store {
|
|||
dirPath: dirPath,
|
||||
address: '',
|
||||
showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven,
|
||||
derivationPath: credentials.derivationPath,
|
||||
derivationType: credentials.derivationType,
|
||||
derivationInfo: credentials.derivationInfo ?? getDefaultDerivation(),
|
||||
);
|
||||
|
||||
credentials.walletInfo = walletInfo;
|
||||
final wallet = restoreWallet != null
|
||||
? await processFromRestoredWallet(credentials, restoreWallet)
|
||||
|
@ -107,6 +107,48 @@ abstract class WalletCreationVMBase with Store {
|
|||
}
|
||||
}
|
||||
|
||||
DerivationInfo? getDefaultDerivation() {
|
||||
switch (this.type) {
|
||||
case WalletType.nano:
|
||||
return DerivationInfo(
|
||||
derivationType: DerivationType.nano,
|
||||
);
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
return DerivationInfo(
|
||||
derivationType: DerivationType.electrum,
|
||||
derivationPath: "m/0'/0",
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
DerivationInfo? getCommonRestoreDerivation() {
|
||||
switch (this.type) {
|
||||
case WalletType.nano:
|
||||
return DerivationInfo(
|
||||
derivationType: DerivationType.nano,
|
||||
);
|
||||
case WalletType.bitcoin:
|
||||
return DerivationInfo(
|
||||
derivationType: DerivationType.bip39,
|
||||
derivationPath: "m/84'/0'/0'/0",
|
||||
description: "Standard BIP84 native segwit",
|
||||
scriptType: "p2wpkh",
|
||||
);
|
||||
case WalletType.litecoin:
|
||||
return DerivationInfo(
|
||||
derivationType: DerivationType.bip39,
|
||||
derivationPath: "m/84'/2'/0'/0",
|
||||
description: "Standard BIP84 native segwit (litecoin)",
|
||||
scriptType: "p2wpkh",
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
WalletCredentials getCredentials(dynamic options) => throw UnimplementedError();
|
||||
|
||||
Future<WalletBase> process(WalletCredentials credentials) => throw UnimplementedError();
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
|||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/nano/nano.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/polygon/polygon.dart';
|
||||
import 'package:cake_wallet/solana/solana.dart';
|
||||
|
@ -66,6 +67,8 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
|||
final bool hasBlockchainHeightLanguageSelector;
|
||||
final bool hasRestoreFromPrivateKey;
|
||||
|
||||
bool get hasPassphrase => [WalletType.bitcoin, WalletType.litecoin].contains(type);
|
||||
|
||||
@observable
|
||||
WalletRestoreMode mode;
|
||||
|
||||
|
@ -75,10 +78,10 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
|||
@override
|
||||
WalletCredentials getCredentials(dynamic options) {
|
||||
final password = walletPassword ?? generateWalletPassword();
|
||||
String? passphrase = options['passphrase'] as String?;
|
||||
final height = options['height'] as int? ?? 0;
|
||||
name = options['name'] as String;
|
||||
DerivationType? derivationType = options["derivationType"] as DerivationType?;
|
||||
String? derivationPath = options["derivationPath"] as String?;
|
||||
DerivationInfo? derivationInfo = options["derivationInfo"] as DerivationInfo?;
|
||||
|
||||
if (mode == WalletRestoreMode.seed) {
|
||||
final seed = options['seed'] as String;
|
||||
|
@ -87,14 +90,15 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
|||
return monero!.createMoneroRestoreWalletFromSeedCredentials(
|
||||
name: name, height: height, mnemonic: seed, password: password);
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
|
||||
name: name,
|
||||
mnemonic: seed,
|
||||
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:
|
||||
return haven!.createHavenRestoreWalletFromSeedCredentials(
|
||||
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);
|
||||
case WalletType.nano:
|
||||
return nano!.createNanoRestoreWalletFromSeedCredentials(
|
||||
name: name, mnemonic: seed, password: password, derivationType: derivationType);
|
||||
name: name,
|
||||
mnemonic: seed,
|
||||
password: password,
|
||||
derivationType: derivationInfo!.derivationType!,
|
||||
);
|
||||
case WalletType.polygon:
|
||||
return polygon!.createPolygonRestoreWalletFromSeedCredentials(
|
||||
name: name,
|
||||
|
@ -185,23 +193,34 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
|||
throw Exception('Unexpected type: ${type.toString()}');
|
||||
}
|
||||
|
||||
Future<List<DerivationType>> getDerivationTypes(dynamic options) async {
|
||||
final seedKey = options['private_key'] as String?;
|
||||
final mnemonic = options['seed'] as String?;
|
||||
WalletType walletType = options['walletType'] as WalletType;
|
||||
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 (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:
|
||||
return nanoUtil!
|
||||
.compareDerivationMethods(mnemonic: mnemonic, privateKey: seedKey, node: node);
|
||||
String? mnemonic = credentials['seed'] as String?;
|
||||
String? seedKey = credentials['private_key'] as String?;
|
||||
return nanoUtil!.getDerivationsFromMnemonic(
|
||||
mnemonic: mnemonic,
|
||||
seedKey: seedKey,
|
||||
node: node,
|
||||
);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// throw Exception('Unexpected type: ${type.toString()}');
|
||||
return [DerivationType.def];
|
||||
return list;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -209,7 +228,6 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
|||
if (mode == WalletRestoreMode.keys) {
|
||||
return walletCreationService.restoreFromKeys(credentials, isTestnet: useTestnet);
|
||||
}
|
||||
|
||||
return walletCreationService.restoreFromSeed(credentials, isTestnet: useTestnet);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -413,6 +413,7 @@
|
|||
"outputs": "المخرجات",
|
||||
"overwrite_amount": "تغير المبلغ",
|
||||
"pairingInvalidEvent": "ﺢﻟﺎﺻ ﺮﻴﻏ ﺙﺪﺣ ﻥﺍﺮﻗﺇ",
|
||||
"passphrase": "عبارة الممر (اختياري)",
|
||||
"password": "كلمة المرور",
|
||||
"paste": "لصق",
|
||||
"pause_wallet_creation": ".ﺎﻴًﻟﺎﺣ ﺎﺘًﻗﺆﻣ ﺔﻔﻗﻮﺘﻣ Haven Wallet ءﺎﺸﻧﺇ ﻰﻠﻋ ﺓﺭﺪﻘﻟﺍ",
|
||||
|
@ -664,6 +665,7 @@
|
|||
"template_name": "اسم القالب",
|
||||
"third_intro_content": "يعيش Yats خارج Cake Wallet أيضًا. يمكن استبدال أي عنوان محفظة على وجه الأرض بـ Yat!",
|
||||
"third_intro_title": "يتماشي Yat بلطف مع الآخرين",
|
||||
"thorchain_contract_address_not_supported": "لا يدعم Thorchain الإرسال إلى عنوان العقد",
|
||||
"thorchain_taproot_address_not_supported": "لا يدعم مزود Thorchain عناوين Taproot. يرجى تغيير العنوان أو تحديد مزود مختلف.",
|
||||
"time": "${minutes}د ${seconds}س",
|
||||
"tip": "بقشيش:",
|
||||
|
|
|
@ -413,6 +413,7 @@
|
|||
"outputs": "Изходи",
|
||||
"overwrite_amount": "Промени сума",
|
||||
"pairingInvalidEvent": "Невалидно събитие при сдвояване",
|
||||
"passphrase": "Passphrase (по избор)",
|
||||
"password": "Парола",
|
||||
"paste": "Поставяне",
|
||||
"pause_wallet_creation": "Възможността за създаване на Haven Wallet в момента е на пауза.",
|
||||
|
@ -664,6 +665,7 @@
|
|||
"template_name": "Име на шаблон",
|
||||
"third_intro_content": "Yats също живее извън Cake Wallet. Всеки адрес на портфейл може да бъде заменен с Yat!",
|
||||
"third_intro_title": "Yat добре се сработва с други",
|
||||
"thorchain_contract_address_not_supported": "Thorchain не подкрепя изпращането до адрес на договор",
|
||||
"thorchain_taproot_address_not_supported": "Доставчикът на Thorchain не поддържа адреси на TapRoot. Моля, променете адреса или изберете друг доставчик.",
|
||||
"time": "${minutes} мин ${seconds} сек",
|
||||
"tip": "Tip:",
|
||||
|
|
|
@ -413,6 +413,7 @@
|
|||
"outputs": "Výstupy",
|
||||
"overwrite_amount": "Přepsat částku",
|
||||
"pairingInvalidEvent": "Neplatná událost párování",
|
||||
"passphrase": "Passphrase (volitelné)",
|
||||
"password": "Heslo",
|
||||
"paste": "Vložit",
|
||||
"pause_wallet_creation": "Možnost vytvářet Haven Wallet je momentálně pozastavena.",
|
||||
|
@ -664,6 +665,7 @@
|
|||
"template_name": "Název šablony",
|
||||
"third_intro_content": "Yat existuje i mimo Cake Wallet. Jakákoliv adresa peněženky na světě může být nahrazena Yatem!",
|
||||
"third_intro_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.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "Spropitné:",
|
||||
|
|
|
@ -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.",
|
||||
"apk_update": "APK-Update",
|
||||
"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",
|
||||
"ask_each_time": "Jedes Mal fragen",
|
||||
"auth_store_ban_timeout": "ban_timeout",
|
||||
|
@ -87,7 +87,7 @@
|
|||
"buy_provider_unavailable": "Anbieter derzeit nicht verfügbar.",
|
||||
"buy_with": "Kaufen mit",
|
||||
"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_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.",
|
||||
|
@ -120,7 +120,7 @@
|
|||
"change_wallet_alert_title": "Aktuelle Wallet ändern",
|
||||
"choose_account": "Konto auswählen",
|
||||
"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_one": "Wähle ein",
|
||||
"choose_relay": "Bitte wählen Sie ein zu verwendendes Relais aus",
|
||||
|
@ -199,7 +199,7 @@
|
|||
"disable_fiat": "Fiat deaktivieren",
|
||||
"disable_sell": "Verkaufsaktion 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",
|
||||
"discount": "${value} % sparen",
|
||||
"display_settings": "Anzeigeeinstellungen",
|
||||
|
@ -413,6 +413,7 @@
|
|||
"outputs": "Ausgänge",
|
||||
"overwrite_amount": "Overwrite amount",
|
||||
"pairingInvalidEvent": "Paarung ungültiges Ereignis",
|
||||
"passphrase": "Passphrase (optional)",
|
||||
"password": "Passwort",
|
||||
"paste": "Einfügen",
|
||||
"pause_wallet_creation": "Die Möglichkeit, Haven Wallet zu erstellen, ist derzeit pausiert.",
|
||||
|
@ -427,8 +428,8 @@
|
|||
"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_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": "Weitere Informationen finden Sie in den Dokumenten unten.",
|
||||
"please_select": "Bitte auswählen:",
|
||||
"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",
|
||||
|
@ -461,8 +462,8 @@
|
|||
"reconnect_alert_text": "Sind Sie sicher, dass Sie sich neu verbinden möchten?",
|
||||
"reconnection": "Neu verbinden",
|
||||
"red_dark_theme": "Red Dark Thema",
|
||||
"red_light_theme": "Rotlichtthema",
|
||||
"redeemed": "Versilbert",
|
||||
"red_light_theme": "Red Light Thema",
|
||||
"redeemed": "Eingelöst",
|
||||
"refund_address": "Rückerstattungsadresse",
|
||||
"reject": "Ablehnen",
|
||||
"remaining": "Rest",
|
||||
|
@ -536,7 +537,7 @@
|
|||
"seed_alert_title": "Achtung",
|
||||
"seed_alert_yes": "Ja, habe ich",
|
||||
"seed_choose": "Seed-Sprache auswählen",
|
||||
"seed_hex_form": "Brieftaschensamen (Sechskantform)",
|
||||
"seed_hex_form": "Seed (Hexformat)",
|
||||
"seed_key": "Seed-Schlüssel",
|
||||
"seed_language": "Seed-Sprache",
|
||||
"seed_language_chinese": "Chinesisch",
|
||||
|
@ -588,7 +589,7 @@
|
|||
"send_your_wallet": "Ihre Wallet",
|
||||
"sending": "Senden",
|
||||
"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",
|
||||
"settings": "Einstellungen",
|
||||
"settings_all": "ALLE",
|
||||
|
@ -665,6 +666,7 @@
|
|||
"template_name": "Vorlagenname",
|
||||
"third_intro_content": "Yats leben auch außerhalb von Cake Wallet. Jede Wallet-Adresse auf der Welt kann durch ein Yat ersetzt werden!",
|
||||
"third_intro_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.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "Hinweis:",
|
||||
|
|
|
@ -413,6 +413,7 @@
|
|||
"outputs": "Outputs",
|
||||
"overwrite_amount": "Overwrite amount",
|
||||
"pairingInvalidEvent": "Pairing Invalid Event",
|
||||
"passphrase": "Passphrase (Optional)",
|
||||
"password": "Password",
|
||||
"paste": "Paste",
|
||||
"pause_wallet_creation": "Ability to create Haven Wallet is currently paused.",
|
||||
|
@ -664,6 +665,7 @@
|
|||
"template_name": "Template Name",
|
||||
"third_intro_content": "Yats live outside of Cake Wallet, too. Any wallet address on earth can be replaced with a Yat!",
|
||||
"third_intro_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.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "Tip:",
|
||||
|
|
|
@ -413,6 +413,7 @@
|
|||
"outputs": "Salidas",
|
||||
"overwrite_amount": "Overwrite amount",
|
||||
"pairingInvalidEvent": "Evento de emparejamiento no válido",
|
||||
"passphrase": "Passfrase (opcional)",
|
||||
"password": "Contraseña",
|
||||
"paste": "Pegar",
|
||||
"pause_wallet_creation": "La capacidad para crear Haven Wallet está actualmente pausada.",
|
||||
|
@ -665,6 +666,7 @@
|
|||
"template_name": "Nombre de la plantilla",
|
||||
"third_intro_content": "Los Yats también viven fuera de Cake Wallet. Cualquier dirección de billetera en la tierra se puede reemplazar con un Yat!",
|
||||
"third_intro_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.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "Consejo:",
|
||||
|
|
|
@ -413,6 +413,7 @@
|
|||
"outputs": "Les sorties",
|
||||
"overwrite_amount": "Remplacer le montant",
|
||||
"pairingInvalidEvent": "Événement de couplage non valide",
|
||||
"passphrase": "Phrase de passe (facultative)",
|
||||
"password": "Mot de passe",
|
||||
"paste": "Coller",
|
||||
"pause_wallet_creation": "La possibilité de créer Haven Wallet est actuellement suspendue.",
|
||||
|
@ -664,6 +665,7 @@
|
|||
"template_name": "Nom du modèle",
|
||||
"third_intro_content": "Les Yats existent aussi en dehors de Cake Wallet. Toute adresse sur terre peut être remplacée par un Yat !",
|
||||
"third_intro_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.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "Pourboire :",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"outputs": "Abubuwan fashewa",
|
||||
"overwrite_amount": "Rubuta adadin",
|
||||
"pairingInvalidEvent": "Haɗa Lamarin mara inganci",
|
||||
"passphrase": "Passphrase (Zabi)",
|
||||
"password": "Kalmar wucewa",
|
||||
"paste": "Manna",
|
||||
"pause_wallet_creation": "A halin yanzu an dakatar da ikon ƙirƙirar Haven Wallet.",
|
||||
|
@ -666,6 +667,7 @@
|
|||
"template_name": "Sunan Samfura",
|
||||
"third_intro_content": "Yats suna zaune a wajen Kek Wallet, kuma. Ana iya maye gurbin kowane adireshin walat a duniya da Yat!",
|
||||
"third_intro_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.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "Tukwici:",
|
||||
|
|
|
@ -413,6 +413,7 @@
|
|||
"outputs": "आउटपुट",
|
||||
"overwrite_amount": "Overwrite amount",
|
||||
"pairingInvalidEvent": "अमान्य ईवेंट युग्मित करना",
|
||||
"passphrase": "पासफ्रेज़ (वैकल्पिक)",
|
||||
"password": "पारण शब्द",
|
||||
"paste": "पेस्ट करें",
|
||||
"pause_wallet_creation": "हेवन वॉलेट बनाने की क्षमता फिलहाल रुकी हुई है।",
|
||||
|
@ -666,6 +667,7 @@
|
|||
"template_name": "टेम्पलेट नाम",
|
||||
"third_intro_content": "Yats Cake Wallet के बाहर भी रहता है। धरती पर किसी भी वॉलेट पते को Yat से बदला जा सकता है!",
|
||||
"third_intro_title": "Yat दूसरों के साथ अच्छा खेलता है",
|
||||
"thorchain_contract_address_not_supported": "थोरचेन एक अनुबंध पते पर भेजने का समर्थन नहीं करता है",
|
||||
"thorchain_taproot_address_not_supported": "थोरचेन प्रदाता टैपरोट पते का समर्थन नहीं करता है। कृपया पता बदलें या एक अलग प्रदाता का चयन करें।",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "टिप:",
|
||||
|
|
|
@ -413,6 +413,7 @@
|
|||
"outputs": "Izlazi",
|
||||
"overwrite_amount": "Overwrite amount",
|
||||
"pairingInvalidEvent": "Nevažeći događaj uparivanja",
|
||||
"passphrase": "Prolaznica (neobavezno)",
|
||||
"password": "Lozinka",
|
||||
"paste": "Zalijepi",
|
||||
"pause_wallet_creation": "Mogućnost stvaranja novčanika Haven trenutno je pauzirana.",
|
||||
|
@ -664,6 +665,7 @@
|
|||
"template_name": "Naziv predloška",
|
||||
"third_intro_content": "Yats žive i izvan Cake Wallet -a. Bilo koja adresa novčanika na svijetu može se zamijeniti Yat!",
|
||||
"third_intro_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.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "Savjet:",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"outputs": "Output",
|
||||
"overwrite_amount": "Timpa jumlah",
|
||||
"pairingInvalidEvent": "Menyandingkan Acara Tidak Valid",
|
||||
"passphrase": "Frasa sandi (opsional)",
|
||||
"password": "Kata Sandi",
|
||||
"paste": "Tempel",
|
||||
"pause_wallet_creation": "Kemampuan untuk membuat Haven Wallet saat ini dijeda.",
|
||||
|
@ -667,6 +668,7 @@
|
|||
"template_name": "Nama Templat",
|
||||
"third_intro_content": "Yats hidup di luar Cake Wallet juga. Setiap alamat dompet di dunia dapat diganti dengan Yat!",
|
||||
"third_intro_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.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "Tip:",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"outputs": "Output",
|
||||
"overwrite_amount": "Sovrascrivi quantità",
|
||||
"pairingInvalidEvent": "Associazione evento non valido",
|
||||
"passphrase": "Passphrase (opzionale)",
|
||||
"password": "Password",
|
||||
"paste": "Incolla",
|
||||
"pause_wallet_creation": "La possibilità di creare Haven Wallet è attualmente sospesa.",
|
||||
|
@ -666,6 +667,7 @@
|
|||
"template_name": "Nome modello",
|
||||
"third_intro_content": "Yat può funzionare anche fuori da Cake Wallet. Qualsiasi indirizzo di portafoglio sulla terra può essere sostituito con uno Yat!",
|
||||
"third_intro_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.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "Suggerimento:",
|
||||
|
|
|
@ -414,6 +414,7 @@
|
|||
"outputs": "出力",
|
||||
"overwrite_amount": "Overwrite amount",
|
||||
"pairingInvalidEvent": "ペアリング無効イベント",
|
||||
"passphrase": "パスフレーズ(オプション)",
|
||||
"password": "パスワード",
|
||||
"paste": "ペースト",
|
||||
"pause_wallet_creation": "Haven Wallet を作成する機能は現在一時停止されています。",
|
||||
|
@ -665,6 +666,7 @@
|
|||
"template_name": "テンプレート名",
|
||||
"third_intro_content": "YatsはCakeWalletの外にも住んでいます。 地球上のどのウォレットアドレスもYatに置き換えることができます!",
|
||||
"third_intro_title": "Yatは他の人とうまく遊ぶ",
|
||||
"thorchain_contract_address_not_supported": "Thorchainは、契約アドレスへの送信をサポートしていません",
|
||||
"thorchain_taproot_address_not_supported": "Thorchainプロバイダーは、TapRootアドレスをサポートしていません。アドレスを変更するか、別のプロバイダーを選択してください。",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "ヒント: ",
|
||||
|
|
|
@ -413,6 +413,7 @@
|
|||
"outputs": "출력",
|
||||
"overwrite_amount": "Overwrite amount",
|
||||
"pairingInvalidEvent": "잘못된 이벤트 페어링",
|
||||
"passphrase": "암호화 (선택 사항)",
|
||||
"password": "암호",
|
||||
"paste": "풀",
|
||||
"pause_wallet_creation": "Haven Wallet 생성 기능이 현재 일시 중지되었습니다.",
|
||||
|
@ -427,8 +428,8 @@
|
|||
"placeholder_transactions": "거래가 여기에 표시됩니다",
|
||||
"please_fill_totp": "다른 기기에 있는 8자리 코드를 입력하세요.",
|
||||
"please_make_selection": "아래에서 선택하십시오 지갑 만들기 또는 복구.",
|
||||
"please_reference_document": "자세한 내용은 아래 문서를 참조하십시오.",
|
||||
"Please_reference_document": "자세한 내용은 아래 문서를 참조하십시오.",
|
||||
"please_reference_document": "자세한 내용은 아래 문서를 참조하십시오.",
|
||||
"please_select": "선택 해주세요:",
|
||||
"please_select_backup_file": "백업 파일을 선택하고 백업 암호를 입력하십시오.",
|
||||
"please_try_to_connect_to_another_node": "다른 노드에 연결을 시도하십시오",
|
||||
|
@ -665,6 +666,7 @@
|
|||
"template_name": "템플릿 이름",
|
||||
"third_intro_content": "Yats는 Cake Wallet 밖에서도 살고 있습니다. 지구상의 모든 지갑 주소는 Yat!",
|
||||
"third_intro_title": "Yat는 다른 사람들과 잘 놉니다.",
|
||||
"thorchain_contract_address_not_supported": "Thorchain은 계약 주소로 보내는 것을 지원하지 않습니다",
|
||||
"thorchain_taproot_address_not_supported": "Thorchain 제공 업체는 Taproot 주소를 지원하지 않습니다. 주소를 변경하거나 다른 공급자를 선택하십시오.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "팁:",
|
||||
|
|
|
@ -413,6 +413,7 @@
|
|||
"outputs": "ထုတ်လုပ်မှု",
|
||||
"overwrite_amount": "ပမာဏကို ထပ်ရေးပါ။",
|
||||
"pairingInvalidEvent": "မမှန်ကန်သောဖြစ်ရပ်ကို တွဲချိတ်ခြင်း။",
|
||||
"passphrase": "passphrase (optional)",
|
||||
"password": "စကားဝှက်",
|
||||
"paste": "ငါးပိ",
|
||||
"pause_wallet_creation": "Haven Wallet ဖန်တီးနိုင်မှုကို လောလောဆယ် ခေတ္တရပ်ထားသည်။",
|
||||
|
@ -664,6 +665,7 @@
|
|||
"template_name": "နမူနာပုံစံ",
|
||||
"third_intro_content": "Yats သည် Cake Wallet အပြင်ဘက်တွင် နေထိုင်ပါသည်။ ကမ္ဘာပေါ်ရှိ မည်သည့်ပိုက်ဆံအိတ်လိပ်စာကို Yat ဖြင့် အစားထိုးနိုင်ပါသည်။",
|
||||
"third_intro_title": "Yat သည် အခြားသူများနှင့် ကောင်းစွာကစားသည်။",
|
||||
"thorchain_contract_address_not_supported": "Thorchain သည်စာချုပ်လိပ်စာသို့ပို့ခြင်းမပြုပါ",
|
||||
"thorchain_taproot_address_not_supported": "Thorchain Provider သည် Taproot လိပ်စာများကိုမထောက်ခံပါ။ ကျေးဇူးပြု. လိပ်စာကိုပြောင်းပါသို့မဟုတ်အခြားပံ့ပိုးပေးသူကိုရွေးချယ်ပါ။",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "အကြံပြုချက်-",
|
||||
|
|
|
@ -413,6 +413,7 @@
|
|||
"outputs": "Uitgangen",
|
||||
"overwrite_amount": "Overwrite amount",
|
||||
"pairingInvalidEvent": "Koppelen Ongeldige gebeurtenis",
|
||||
"passphrase": "PassaspHRASE (optioneel)",
|
||||
"password": "Wachtwoord",
|
||||
"paste": "Plakken",
|
||||
"pause_wallet_creation": "De mogelijkheid om Haven Wallet te maken is momenteel onderbroken.",
|
||||
|
@ -664,6 +665,7 @@
|
|||
"template_name": "Sjabloonnaam",
|
||||
"third_intro_content": "Yats wonen ook buiten Cake Wallet. Elk portemonnee-adres op aarde kan worden vervangen door een Yat!",
|
||||
"third_intro_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.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "Tip:",
|
||||
|
|
|
@ -413,6 +413,7 @@
|
|||
"outputs": "Wyjścia",
|
||||
"overwrite_amount": "Nadpisz ilość",
|
||||
"pairingInvalidEvent": "Nieprawidłowe zdarzenie parowania",
|
||||
"passphrase": "PassPhraza (opcjonalnie)",
|
||||
"password": "Hasło",
|
||||
"paste": "Wklej",
|
||||
"pause_wallet_creation": "Możliwość utworzenia Portfela Haven jest obecnie wstrzymana.",
|
||||
|
@ -664,6 +665,7 @@
|
|||
"template_name": "Nazwa szablonu",
|
||||
"third_intro_content": "Yats mieszkają również poza Cake Wallet. Każdy adres portfela na ziemi można zastąpić Yat!",
|
||||
"third_intro_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ę.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "wskazówka:",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"outputs": "Saídas",
|
||||
"overwrite_amount": "Overwrite amount",
|
||||
"pairingInvalidEvent": "Emparelhamento de evento inválido",
|
||||
"passphrase": "Senha (opcional)",
|
||||
"password": "Senha",
|
||||
"paste": "Colar",
|
||||
"pause_wallet_creation": "A capacidade de criar a Haven Wallet está atualmente pausada.",
|
||||
|
@ -666,6 +667,7 @@
|
|||
"template_name": "Nome do modelo",
|
||||
"third_intro_content": "Yats também mora fora da Cake Wallet. Qualquer endereço de carteira na Terra pode ser substituído por um Yat!",
|
||||
"third_intro_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.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "Dica:",
|
||||
|
|
|
@ -414,6 +414,7 @@
|
|||
"outputs": "Выходы",
|
||||
"overwrite_amount": "Overwrite amount",
|
||||
"pairingInvalidEvent": "Недействительное событие сопряжения",
|
||||
"passphrase": "Passfrase (необязательно)",
|
||||
"password": "Пароль",
|
||||
"paste": "Вставить",
|
||||
"pause_wallet_creation": "Возможность создания Haven Wallet в настоящее время приостановлена.",
|
||||
|
@ -665,6 +666,7 @@
|
|||
"template_name": "Имя Шаблона",
|
||||
"third_intro_content": "Yat находятся за пределами Cake Wallet. Любой адрес кошелька на земле можно заменить на Yat!",
|
||||
"third_intro_title": "Yat хорошо взаимодействует с другими",
|
||||
"thorchain_contract_address_not_supported": "Thorchain не поддерживает отправку на адрес контракта",
|
||||
"thorchain_taproot_address_not_supported": "Поставщик Thorchain не поддерживает адреса taproot. Пожалуйста, измените адрес или выберите другого поставщика.",
|
||||
"time": "${minutes}мин ${seconds}сек",
|
||||
"tip": "Совет:",
|
||||
|
|
|
@ -413,6 +413,7 @@
|
|||
"outputs": "เอาต์พุต",
|
||||
"overwrite_amount": "เขียนทับจำนวน",
|
||||
"pairingInvalidEvent": "การจับคู่เหตุการณ์ที่ไม่ถูกต้อง",
|
||||
"passphrase": "ข้อความรหัสผ่าน (ไม่บังคับ)",
|
||||
"password": "รหัสผ่าน",
|
||||
"paste": "วาง",
|
||||
"pause_wallet_creation": "ขณะนี้ความสามารถในการสร้าง Haven Wallet ถูกหยุดชั่วคราว",
|
||||
|
@ -664,6 +665,7 @@
|
|||
"template_name": "ชื่อแม่แบบ",
|
||||
"third_intro_content": "Yat อาศัยอยู่นอก Cake Wallet ด้วย ที่อยู่กระเป๋าใดๆ ทั่วโลกสามารถแทนด้วย Yat ได้อีกด้วย!",
|
||||
"third_intro_title": "Yat ปฏิบัติตนอย่างดีกับผู้อื่น",
|
||||
"thorchain_contract_address_not_supported": "Thorchain ไม่สนับสนุนการส่งไปยังที่อยู่สัญญา",
|
||||
"thorchain_taproot_address_not_supported": "ผู้ให้บริการ Thorchain ไม่รองรับที่อยู่ taproot โปรดเปลี่ยนที่อยู่หรือเลือกผู้ให้บริการอื่น",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "เพิ่มค่าตอบแทน:",
|
||||
|
|
|
@ -413,6 +413,7 @@
|
|||
"outputs": "Mga output",
|
||||
"overwrite_amount": "Overwrite na halaga",
|
||||
"pairingInvalidEvent": "Pagpares ng Di-wastong Kaganapan",
|
||||
"passphrase": "Passphrase (opsyonal)",
|
||||
"password": "Password",
|
||||
"paste": "I -paste",
|
||||
"pause_wallet_creation": "Kasalukuyang naka-pause ang kakayahang gumawa ng Haven Wallet.",
|
||||
|
@ -664,6 +665,7 @@
|
|||
"template_name": "Pangalan ng Template",
|
||||
"third_intro_content": "Ang mga yats ay nakatira sa labas ng cake wallet, din. Ang anumang address ng pitaka sa mundo ay maaaring mapalitan ng isang yat!",
|
||||
"third_intro_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.",
|
||||
"time": "${minutes} m ${seconds} s",
|
||||
"tip": "Tip:",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue