mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-03-31 19:49:04 +00:00
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into mweb-bg-sync-2 [skip-ci]
This commit is contained in:
commit
239adc2dcc
147 changed files with 2748 additions and 1362 deletions
android/app/src/main
assets
cw_bitcoin
lib
bitcoin_receive_page_option.dartbitcoin_transaction_credentials.dartelectrum.dartelectrum_wallet.dartelectrum_wallet_addresses.dartelectrum_wallet_snapshot.dartexceptions.dartlitecoin_wallet.dartlitecoin_wallet_addresses.dartpending_bitcoin_transaction.dart
pubspec.lockcw_bitcoin_cash/lib/src
cw_core/lib
crypto_currency.dartnode.dartsync_status.dartunspent_coin_type.dartwallet_addresses.dartwallet_base.dart
cw_ethereum/lib
cw_evm/lib
cw_haven/lib
cw_monero/lib
cw_mweb
android/src/main/kotlin/com/cakewallet/mweb
ios/Classes
lib
cw_nano
cw_polygon/lib
cw_solana/lib
cw_tron/lib
tron_wallet.darttron_wallet_addresses.darttron_wallet_creation_credentials.darttron_wallet_service.dart
cw_wownero/lib
ios
lib
bitcoin
core
di.dartentities
ethereum
main.dartnano
polygon
reactions
router.dartsolana
src
screens
dashboard
new_wallet
receive/widgets
restore
root
send
settings
transaction_details/widgets
unspent_coins
wallet
widgets
store
tron
utils
view_model
|
@ -1,35 +1,30 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="__APP_PACKAGE__">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
|
||||
<!--bibo01 : hardware option-->
|
||||
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/>
|
||||
<!-- bibo01 : hardware option-->
|
||||
<uses-feature android:name="android.hardware.bluetooth" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false" />
|
||||
|
||||
<!-- required for API 18 - 30 -->
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH_ADMIN"
|
||||
android:maxSdkVersion="30" />
|
||||
<!-- required for API 18 - 30 -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
|
||||
<!-- API 31+ -->
|
||||
<!-- required for API <= 29 -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
|
||||
|
||||
<!-- API 31+ -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH_SCAN"
|
||||
android:usesPermissionFlags="neverForLocation" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
|
||||
|
||||
|
||||
<application
|
||||
android:name=".Application"
|
||||
android:label="${APP_NAME}"
|
||||
|
|
|
@ -6,3 +6,6 @@
|
|||
isDefault: true
|
||||
-
|
||||
uri: electrs.cakewallet.com:50001
|
||||
-
|
||||
uri: fulcrum.sethforprivacy.com:50002
|
||||
useSSL: true
|
||||
|
|
BIN
assets/images/ton_icon.png
Normal file
BIN
assets/images/ton_icon.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 14 KiB |
|
@ -1,2 +1,3 @@
|
|||
Enhance auto-address generation for Monero
|
||||
Bug fixes and enhancements
|
||||
Monero enhancements
|
||||
Introducing StealthEx and LetxExchange
|
||||
Bug fixes
|
|
@ -1,4 +1,6 @@
|
|||
Enable BIP39 by default for wallet creation also on Bitcoin/Litecoin (Electrum seed type is still accessible through advanced settings page)
|
||||
Improve fee calculation for Bitcoin to protect against overpaying or underpaying
|
||||
Enhance auto-address generation for Monero
|
||||
Bug fixes and enhancements
|
||||
Added Litecoin MWEB
|
||||
Added wallet groups
|
||||
Silent Payment enhancements for speed & reliability
|
||||
Monero enhancements
|
||||
Introducing StealthEx and LetxExchange
|
||||
Additional ERC20 tokens scam detection
|
|
@ -30,7 +30,7 @@ class BitcoinReceivePageOption implements ReceivePageOption {
|
|||
|
||||
static const allLitecoin = [
|
||||
BitcoinReceivePageOption.p2wpkh,
|
||||
BitcoinReceivePageOption.mweb
|
||||
BitcoinReceivePageOption.mweb,
|
||||
];
|
||||
|
||||
BitcoinAddressType toType() {
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
||||
import 'package:cw_core/output_info.dart';
|
||||
import 'package:cw_core/unspent_coin_type.dart';
|
||||
|
||||
class BitcoinTransactionCredentials {
|
||||
BitcoinTransactionCredentials(this.outputs,
|
||||
{required this.priority, this.feeRate});
|
||||
{required this.priority, this.feeRate, this.coinTypeToSpendFrom = UnspentCoinType.any});
|
||||
|
||||
final List<OutputInfo> outputs;
|
||||
final BitcoinTransactionPriority? priority;
|
||||
final int? feeRate;
|
||||
final UnspentCoinType coinTypeToSpendFrom;
|
||||
}
|
||||
|
|
|
@ -68,8 +68,8 @@ class ElectrumClient {
|
|||
|
||||
try {
|
||||
await socket?.close();
|
||||
socket = null;
|
||||
} catch (_) {}
|
||||
socket = null;
|
||||
|
||||
try {
|
||||
if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) {
|
||||
|
@ -102,7 +102,8 @@ class ElectrumClient {
|
|||
return;
|
||||
}
|
||||
|
||||
_setConnectionStatus(ConnectionStatus.connected);
|
||||
// use ping to determine actual connection status since we could've just not timed out yet:
|
||||
// _setConnectionStatus(ConnectionStatus.connected);
|
||||
|
||||
socket!.listen(
|
||||
(Uint8List event) {
|
||||
|
@ -116,7 +117,7 @@ class ElectrumClient {
|
|||
_parseResponse(message);
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
print("socket.listen: $e");
|
||||
}
|
||||
},
|
||||
onError: (Object error) {
|
||||
|
@ -125,14 +126,15 @@ class ElectrumClient {
|
|||
unterminatedString = '';
|
||||
},
|
||||
onDone: () {
|
||||
print("SOCKET CLOSED!!!!!");
|
||||
unterminatedString = '';
|
||||
try {
|
||||
if (host == socket?.address.host) {
|
||||
socket?.destroy();
|
||||
if (host == socket?.address.host || socket == null) {
|
||||
_setConnectionStatus(ConnectionStatus.disconnected);
|
||||
socket?.destroy();
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
print("onDone: $e");
|
||||
}
|
||||
},
|
||||
cancelOnError: true,
|
||||
|
@ -177,7 +179,7 @@ class ElectrumClient {
|
|||
unterminatedString = '';
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
print("parse $e");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -190,7 +192,7 @@ class ElectrumClient {
|
|||
try {
|
||||
await callWithTimeout(method: 'server.ping');
|
||||
_setConnectionStatus(ConnectionStatus.connected);
|
||||
} on RequestFailedTimeoutException catch (_) {
|
||||
} catch (_) {
|
||||
_setConnectionStatus(ConnectionStatus.disconnected);
|
||||
}
|
||||
}
|
||||
|
@ -430,7 +432,7 @@ class ElectrumClient {
|
|||
|
||||
return subscription;
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
print("subscribe $e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -469,7 +471,8 @@ class ElectrumClient {
|
|||
|
||||
return completer.future;
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
print("callWithTimeout $e");
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -536,6 +539,12 @@ class ElectrumClient {
|
|||
onConnectionStatusChange?.call(status);
|
||||
_connectionStatus = status;
|
||||
_isConnected = status == ConnectionStatus.connected;
|
||||
if (!_isConnected) {
|
||||
try {
|
||||
socket?.destroy();
|
||||
} catch (_) {}
|
||||
socket = null;
|
||||
}
|
||||
}
|
||||
|
||||
void _handleResponse(Map<String, dynamic> response) {
|
||||
|
|
|
@ -4,7 +4,7 @@ import 'dart:io';
|
|||
import 'dart:isolate';
|
||||
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:cw_bitcoin/litecoin_wallet_addresses.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_wallet.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:cw_core/encryption_file_utils.dart';
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
|
@ -23,7 +23,6 @@ import 'package:cw_bitcoin/electrum_transaction_history.dart';
|
|||
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||
import 'package:cw_bitcoin/exceptions.dart';
|
||||
import 'package:cw_bitcoin/litecoin_wallet.dart';
|
||||
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
|
||||
import 'package:cw_bitcoin/utils.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
|
@ -39,6 +38,7 @@ import 'package:cw_core/wallet_info.dart';
|
|||
import 'package:cw_core/wallet_keys_file.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_core/get_height_by_date.dart';
|
||||
import 'package:cw_core/unspent_coin_type.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
@ -168,7 +168,10 @@ abstract class ElectrumWalletBase
|
|||
@observable
|
||||
SyncStatus syncStatus;
|
||||
|
||||
Set<String> get addressesSet => walletAddresses.allAddresses.map((addr) => addr.address).toSet();
|
||||
Set<String> get addressesSet => walletAddresses.allAddresses
|
||||
.where((element) => element.type != SegwitAddresType.mweb)
|
||||
.map((addr) => addr.address)
|
||||
.toSet();
|
||||
|
||||
List<String> get scriptHashes => walletAddresses.addressesByReceiveType
|
||||
.where((addr) => RegexUtils.addressTypeFromStr(addr.address, network) is! MwebAddress)
|
||||
|
@ -247,7 +250,7 @@ abstract class ElectrumWalletBase
|
|||
int? _currentChainTip;
|
||||
|
||||
Future<int> getCurrentChainTip() async {
|
||||
if (_currentChainTip != null) {
|
||||
if ((_currentChainTip ?? 0) > 0) {
|
||||
return _currentChainTip!;
|
||||
}
|
||||
_currentChainTip = await electrumClient.getCurrentBlockChainTip() ?? 0;
|
||||
|
@ -299,6 +302,7 @@ abstract class ElectrumWalletBase
|
|||
|
||||
@action
|
||||
Future<void> _setListeners(int height, {int? chainTipParam, bool? doSingleScan}) async {
|
||||
if (this is! BitcoinWallet) return;
|
||||
final chainTip = chainTipParam ?? await getUpdatedChainTip();
|
||||
|
||||
if (chainTip == height) {
|
||||
|
@ -466,7 +470,7 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
} catch (e, stacktrace) {
|
||||
print(stacktrace);
|
||||
print(e.toString());
|
||||
print("startSync $e");
|
||||
syncStatus = FailedSyncStatus();
|
||||
}
|
||||
}
|
||||
|
@ -491,13 +495,21 @@ abstract class ElectrumWalletBase
|
|||
final response =
|
||||
await http.get(Uri.parse("http://mempool.cakewallet.com:8999/api/v1/fees/recommended"));
|
||||
|
||||
final result = json.decode(response.body) as Map<String, num>;
|
||||
final slowFee = result['economyFee']?.toInt() ?? 0;
|
||||
final mediumFee = result['hourFee']?.toInt() ?? 0;
|
||||
final fastFee = result['fastestFee']?.toInt() ?? 0;
|
||||
final result = json.decode(response.body) as Map<String, dynamic>;
|
||||
final slowFee = (result['economyFee'] as num?)?.toInt() ?? 0;
|
||||
int mediumFee = (result['hourFee'] as num?)?.toInt() ?? 0;
|
||||
int fastFee = (result['fastestFee'] as num?)?.toInt() ?? 0;
|
||||
if (slowFee == mediumFee) {
|
||||
mediumFee++;
|
||||
}
|
||||
while (fastFee <= mediumFee) {
|
||||
fastFee++;
|
||||
}
|
||||
_feeRates = [slowFee, mediumFee, fastFee];
|
||||
return;
|
||||
} catch (_) {}
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
|
||||
final feeRates = await electrumClient.feeRates(network: network);
|
||||
|
@ -586,7 +598,7 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
} catch (e, stacktrace) {
|
||||
print(stacktrace);
|
||||
print(e.toString());
|
||||
print("connectToNode $e");
|
||||
syncStatus = FailedSyncStatus();
|
||||
}
|
||||
}
|
||||
|
@ -600,6 +612,7 @@ abstract class ElectrumWalletBase
|
|||
required int credentialsAmount,
|
||||
required bool paysToSilentPayment,
|
||||
int? inputsCount,
|
||||
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
|
||||
}) {
|
||||
List<UtxoWithAddress> utxos = [];
|
||||
List<Outpoint> vinOutpoints = [];
|
||||
|
@ -610,7 +623,20 @@ abstract class ElectrumWalletBase
|
|||
bool spendsUnconfirmedTX = false;
|
||||
|
||||
int leftAmount = credentialsAmount;
|
||||
final availableInputs = unspentCoins.where((utx) => utx.isSending && !utx.isFrozen).toList();
|
||||
final availableInputs = unspentCoins.where((utx) {
|
||||
if (!utx.isSending || utx.isFrozen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (coinTypeToSpendFrom) {
|
||||
case UnspentCoinType.mweb:
|
||||
return utx.bitcoinAddressRecord.type == SegwitAddresType.mweb;
|
||||
case UnspentCoinType.nonMweb:
|
||||
return utx.bitcoinAddressRecord.type != SegwitAddresType.mweb;
|
||||
case UnspentCoinType.any:
|
||||
return true;
|
||||
}
|
||||
}).toList();
|
||||
final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList();
|
||||
|
||||
for (int i = 0; i < availableInputs.length; i++) {
|
||||
|
@ -717,11 +743,13 @@ abstract class ElectrumWalletBase
|
|||
String? memo,
|
||||
int credentialsAmount = 0,
|
||||
bool hasSilentPayment = false,
|
||||
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
|
||||
}) async {
|
||||
final utxoDetails = _createUTXOS(
|
||||
sendAll: true,
|
||||
credentialsAmount: credentialsAmount,
|
||||
paysToSilentPayment: hasSilentPayment,
|
||||
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
||||
);
|
||||
|
||||
int fee = await calcFee(
|
||||
|
@ -783,17 +811,20 @@ abstract class ElectrumWalletBase
|
|||
Future<EstimatedTxResult> estimateTxForAmount(
|
||||
int credentialsAmount,
|
||||
List<BitcoinOutput> outputs,
|
||||
List<BitcoinOutput> updatedOutputs,
|
||||
int feeRate, {
|
||||
int? inputsCount,
|
||||
String? memo,
|
||||
bool? useUnconfirmed,
|
||||
bool hasSilentPayment = false,
|
||||
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
|
||||
}) async {
|
||||
final utxoDetails = _createUTXOS(
|
||||
sendAll: false,
|
||||
credentialsAmount: credentialsAmount,
|
||||
inputsCount: inputsCount,
|
||||
paysToSilentPayment: hasSilentPayment,
|
||||
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
||||
);
|
||||
|
||||
final spendingAllCoins = utxoDetails.availableInputs.length == utxoDetails.utxos.length;
|
||||
|
@ -809,10 +840,12 @@ abstract class ElectrumWalletBase
|
|||
return estimateTxForAmount(
|
||||
credentialsAmount,
|
||||
outputs,
|
||||
updatedOutputs,
|
||||
feeRate,
|
||||
inputsCount: utxoDetails.utxos.length + 1,
|
||||
memo: memo,
|
||||
hasSilentPayment: hasSilentPayment,
|
||||
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -820,36 +853,56 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
final changeAddress = await walletAddresses.getChangeAddress(
|
||||
outputs: outputs,
|
||||
utxoDetails: utxoDetails,
|
||||
inputs: utxoDetails.availableInputs,
|
||||
outputs: updatedOutputs,
|
||||
);
|
||||
final address = RegexUtils.addressTypeFromStr(changeAddress, network);
|
||||
updatedOutputs.add(BitcoinOutput(
|
||||
address: address,
|
||||
value: BigInt.from(amountLeftForChangeAndFee),
|
||||
isChange: true,
|
||||
));
|
||||
outputs.add(BitcoinOutput(
|
||||
address: address,
|
||||
value: BigInt.from(amountLeftForChangeAndFee),
|
||||
isChange: true,
|
||||
));
|
||||
|
||||
// calcFee updates the silent payment outputs to calculate the tx size accounting
|
||||
// for taproot addresses, but if more inputs are needed to make up for fees,
|
||||
// the silent payment outputs need to be recalculated for the new inputs
|
||||
var temp = outputs.map((output) => output).toList();
|
||||
int fee = await calcFee(
|
||||
utxos: utxoDetails.utxos,
|
||||
outputs: outputs,
|
||||
// Always take only not updated bitcoin outputs here so for every estimation
|
||||
// the SP outputs are re-generated to the proper taproot addresses
|
||||
outputs: temp,
|
||||
network: network,
|
||||
memo: memo,
|
||||
feeRate: feeRate,
|
||||
inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos,
|
||||
vinOutpoints: utxoDetails.vinOutpoints,
|
||||
);
|
||||
|
||||
updatedOutputs.clear();
|
||||
updatedOutputs.addAll(temp);
|
||||
|
||||
if (fee == 0) {
|
||||
throw BitcoinTransactionNoFeeException();
|
||||
}
|
||||
|
||||
int amount = credentialsAmount;
|
||||
final lastOutput = outputs.last;
|
||||
final lastOutput = updatedOutputs.last;
|
||||
final amountLeftForChange = amountLeftForChangeAndFee - fee;
|
||||
|
||||
print(amountLeftForChangeAndFee);
|
||||
|
||||
if (!_isBelowDust(amountLeftForChange)) {
|
||||
// Here, lastOutput already is change, return the amount left without the fee to the user's address.
|
||||
updatedOutputs[updatedOutputs.length - 1] = BitcoinOutput(
|
||||
address: lastOutput.address,
|
||||
value: BigInt.from(amountLeftForChange),
|
||||
isSilentPayment: lastOutput.isSilentPayment,
|
||||
isChange: true,
|
||||
);
|
||||
outputs[outputs.length - 1] = BitcoinOutput(
|
||||
address: lastOutput.address,
|
||||
value: BigInt.from(amountLeftForChange),
|
||||
|
@ -858,6 +911,7 @@ abstract class ElectrumWalletBase
|
|||
);
|
||||
} else {
|
||||
// If has change that is lower than dust, will end up with tx rejected by network rules, so estimate again without the added change
|
||||
updatedOutputs.removeLast();
|
||||
outputs.removeLast();
|
||||
|
||||
// Still has inputs to spend before failing
|
||||
|
@ -865,17 +919,21 @@ abstract class ElectrumWalletBase
|
|||
return estimateTxForAmount(
|
||||
credentialsAmount,
|
||||
outputs,
|
||||
updatedOutputs,
|
||||
feeRate,
|
||||
inputsCount: utxoDetails.utxos.length + 1,
|
||||
memo: memo,
|
||||
hasSilentPayment: hasSilentPayment,
|
||||
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
|
||||
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
||||
);
|
||||
}
|
||||
|
||||
final estimatedSendAll = await estimateSendAllTx(
|
||||
outputs,
|
||||
updatedOutputs,
|
||||
feeRate,
|
||||
memo: memo,
|
||||
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
||||
);
|
||||
|
||||
if (estimatedSendAll.amount == credentialsAmount) {
|
||||
|
@ -905,15 +963,18 @@ abstract class ElectrumWalletBase
|
|||
if (spendingAllCoins) {
|
||||
throw BitcoinTransactionWrongBalanceException();
|
||||
} else {
|
||||
updatedOutputs.removeLast();
|
||||
outputs.removeLast();
|
||||
return estimateTxForAmount(
|
||||
credentialsAmount,
|
||||
outputs,
|
||||
updatedOutputs,
|
||||
feeRate,
|
||||
inputsCount: utxoDetails.utxos.length + 1,
|
||||
memo: memo,
|
||||
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
|
||||
hasSilentPayment: hasSilentPayment,
|
||||
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -971,6 +1032,7 @@ abstract class ElectrumWalletBase
|
|||
final hasMultiDestination = transactionCredentials.outputs.length > 1;
|
||||
final sendAll = !hasMultiDestination && transactionCredentials.outputs.first.sendAll;
|
||||
final memo = transactionCredentials.outputs.first.memo;
|
||||
final coinTypeToSpendFrom = transactionCredentials.coinTypeToSpendFrom;
|
||||
|
||||
int credentialsAmount = 0;
|
||||
bool hasSilentPayment = false;
|
||||
|
@ -1019,6 +1081,9 @@ abstract class ElectrumWalletBase
|
|||
: feeRate(transactionCredentials.priority!);
|
||||
|
||||
EstimatedTxResult estimatedTx;
|
||||
final updatedOutputs =
|
||||
outputs.map((e) => BitcoinOutput(address: e.address, value: e.value)).toList();
|
||||
|
||||
if (sendAll) {
|
||||
estimatedTx = await estimateSendAllTx(
|
||||
outputs,
|
||||
|
@ -1026,21 +1091,24 @@ abstract class ElectrumWalletBase
|
|||
memo: memo,
|
||||
credentialsAmount: credentialsAmount,
|
||||
hasSilentPayment: hasSilentPayment,
|
||||
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
||||
);
|
||||
} else {
|
||||
estimatedTx = await estimateTxForAmount(
|
||||
credentialsAmount,
|
||||
outputs,
|
||||
updatedOutputs,
|
||||
feeRateInt,
|
||||
memo: memo,
|
||||
hasSilentPayment: hasSilentPayment,
|
||||
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
||||
);
|
||||
}
|
||||
|
||||
if (walletInfo.isHardwareWallet) {
|
||||
final transaction = await buildHardwareWalletTransaction(
|
||||
utxos: estimatedTx.utxos,
|
||||
outputs: outputs,
|
||||
outputs: updatedOutputs,
|
||||
publicKeys: estimatedTx.publicKeys,
|
||||
fee: BigInt.from(estimatedTx.fee),
|
||||
network: network,
|
||||
|
@ -1080,7 +1148,7 @@ abstract class ElectrumWalletBase
|
|||
} else {
|
||||
txb = BitcoinTransactionBuilder(
|
||||
utxos: estimatedTx.utxos,
|
||||
outputs: outputs,
|
||||
outputs: updatedOutputs,
|
||||
fee: BigInt.from(estimatedTx.fee),
|
||||
network: network,
|
||||
memo: estimatedTx.memo,
|
||||
|
@ -1140,6 +1208,7 @@ abstract class ElectrumWalletBase
|
|||
hasChange: estimatedTx.hasChange,
|
||||
isSendAll: estimatedTx.isSendAll,
|
||||
hasTaprootInputs: hasTaprootInputs,
|
||||
utxos: estimatedTx.utxos,
|
||||
)..addListener((transaction) async {
|
||||
transactionHistory.addOne(transaction);
|
||||
if (estimatedTx.spendsSilentPayment) {
|
||||
|
@ -1189,6 +1258,7 @@ abstract class ElectrumWalletBase
|
|||
'silent_addresses': walletAddresses.silentAddresses.map((addr) => addr.toJSON()).toList(),
|
||||
'silent_address_index': walletAddresses.currentSilentAddressIndex.toString(),
|
||||
'mweb_addresses': walletAddresses.mwebAddresses.map((addr) => addr.toJSON()).toList(),
|
||||
'alwaysScan': alwaysScan,
|
||||
});
|
||||
|
||||
int feeRate(TransactionPriority priority) {
|
||||
|
@ -1306,7 +1376,7 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
Future<void> close({required bool shouldCleanup}) async {
|
||||
try {
|
||||
await _receiveStream?.cancel();
|
||||
await electrumClient.close();
|
||||
|
@ -1328,12 +1398,16 @@ abstract class ElectrumWalletBase
|
|||
});
|
||||
}
|
||||
|
||||
// Set the balance of all non-silent payment addresses to 0 before updating
|
||||
walletAddresses.allAddresses.forEach((addr) {
|
||||
if(addr is! BitcoinSilentPaymentAddressRecord) addr.balance = 0;
|
||||
// Set the balance of all non-silent payment and non-mweb addresses to 0 before updating
|
||||
walletAddresses.allAddresses
|
||||
.where((element) => element.type != SegwitAddresType.mweb)
|
||||
.forEach((addr) {
|
||||
if (addr is! BitcoinSilentPaymentAddressRecord) addr.balance = 0;
|
||||
});
|
||||
|
||||
await Future.wait(walletAddresses.allAddresses.map((address) async {
|
||||
await Future.wait(walletAddresses.allAddresses
|
||||
.where((element) => element.type != SegwitAddresType.mweb)
|
||||
.map((address) async {
|
||||
updatedUnspentCoins.addAll(await fetchUnspent(address));
|
||||
}));
|
||||
|
||||
|
@ -1441,7 +1515,7 @@ abstract class ElectrumWalletBase
|
|||
await unspentCoinsInfo.deleteAll(keys);
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
print("refreshUnspentCoinsInfo $e");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1662,27 +1736,29 @@ abstract class ElectrumWalletBase
|
|||
if (verboseTransaction.isEmpty) {
|
||||
transactionHex = await electrumClient.getTransactionHex(hash: hash);
|
||||
|
||||
if (height != null && await checkIfMempoolAPIIsEnabled()) {
|
||||
final blockHash = await http.get(
|
||||
Uri.parse(
|
||||
"http://mempool.cakewallet.com:8999/api/v1/block-height/$height",
|
||||
),
|
||||
);
|
||||
|
||||
if (blockHash.statusCode == 200 &&
|
||||
blockHash.body.isNotEmpty &&
|
||||
jsonDecode(blockHash.body) != null) {
|
||||
final blockResponse = await http.get(
|
||||
if (height != null && height > 0 && await checkIfMempoolAPIIsEnabled()) {
|
||||
try {
|
||||
final blockHash = await http.get(
|
||||
Uri.parse(
|
||||
"http://mempool.cakewallet.com:8999/api/v1/block/${blockHash.body}",
|
||||
"http://mempool.cakewallet.com:8999/api/v1/block-height/$height",
|
||||
),
|
||||
);
|
||||
if (blockResponse.statusCode == 200 &&
|
||||
blockResponse.body.isNotEmpty &&
|
||||
jsonDecode(blockResponse.body)['timestamp'] != null) {
|
||||
time = int.parse(jsonDecode(blockResponse.body)['timestamp'].toString());
|
||||
|
||||
if (blockHash.statusCode == 200 &&
|
||||
blockHash.body.isNotEmpty &&
|
||||
jsonDecode(blockHash.body) != null) {
|
||||
final blockResponse = await http.get(
|
||||
Uri.parse(
|
||||
"http://mempool.cakewallet.com:8999/api/v1/block/${blockHash.body}",
|
||||
),
|
||||
);
|
||||
if (blockResponse.statusCode == 200 &&
|
||||
blockResponse.body.isNotEmpty &&
|
||||
jsonDecode(blockResponse.body)['timestamp'] != null) {
|
||||
time = int.parse(jsonDecode(blockResponse.body)['timestamp'].toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
} else {
|
||||
transactionHex = verboseTransaction['hex'] as String;
|
||||
|
@ -1783,7 +1859,7 @@ abstract class ElectrumWalletBase
|
|||
|
||||
return historiesWithDetails;
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
print("fetchTransactions $e");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
@ -1838,6 +1914,8 @@ abstract class ElectrumWalletBase
|
|||
|
||||
Future<Map<String, ElectrumTransactionInfo>> _fetchAddressHistory(
|
||||
BitcoinAddressRecord addressRecord, int? currentHeight) async {
|
||||
String txid = "";
|
||||
|
||||
try {
|
||||
final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
|
||||
|
||||
|
@ -1847,7 +1925,7 @@ abstract class ElectrumWalletBase
|
|||
addressRecord.setAsUsed();
|
||||
|
||||
await Future.wait(history.map((transaction) async {
|
||||
final txid = transaction['tx_hash'] as String;
|
||||
txid = transaction['tx_hash'] as String;
|
||||
final height = transaction['height'] as int;
|
||||
final storedTx = transactionHistory.transactions[txid];
|
||||
|
||||
|
@ -1855,7 +1933,9 @@ abstract class ElectrumWalletBase
|
|||
if (height > 0) {
|
||||
storedTx.height = height;
|
||||
// the tx's block itself is the first confirmation so add 1
|
||||
if (currentHeight != null) storedTx.confirmations = currentHeight - height + 1;
|
||||
if ((currentHeight ?? 0) > 0) {
|
||||
storedTx.confirmations = currentHeight! - height + 1;
|
||||
}
|
||||
storedTx.isPending = storedTx.confirmations == 0;
|
||||
}
|
||||
|
||||
|
@ -1878,22 +1958,31 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
return historiesWithDetails;
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
} catch (e, stacktrace) {
|
||||
_onError?.call(FlutterErrorDetails(
|
||||
exception: "$txid - $e",
|
||||
stack: stacktrace,
|
||||
library: this.runtimeType.toString(),
|
||||
));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateTransactions() async {
|
||||
print("updateTransactions() called!");
|
||||
try {
|
||||
if (_isTransactionUpdating) {
|
||||
return;
|
||||
}
|
||||
await getCurrentChainTip();
|
||||
|
||||
transactionHistory.transactions.values.forEach((tx) async {
|
||||
if (tx.unspents != null && tx.unspents!.isNotEmpty && tx.height != null && tx.height! > 0) {
|
||||
tx.confirmations = await getCurrentChainTip() - tx.height! + 1;
|
||||
transactionHistory.transactions.values.forEach((tx) {
|
||||
if (tx.unspents != null &&
|
||||
tx.unspents!.isNotEmpty &&
|
||||
tx.height != null &&
|
||||
tx.height! > 0 &&
|
||||
(_currentChainTip ?? 0) > 0) {
|
||||
tx.confirmations = _currentChainTip! - tx.height! + 1;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1910,13 +1999,25 @@ abstract class ElectrumWalletBase
|
|||
|
||||
Future<void> subscribeForUpdates() async {
|
||||
final unsubscribedScriptHashes = walletAddresses.allAddresses.where(
|
||||
(address) => !_scripthashesUpdateSubject.containsKey(address.getScriptHash(network)),
|
||||
(address) =>
|
||||
!_scripthashesUpdateSubject.containsKey(address.getScriptHash(network)) &&
|
||||
address.type != SegwitAddresType.mweb,
|
||||
);
|
||||
|
||||
await Future.wait(unsubscribedScriptHashes.map((address) async {
|
||||
final sh = address.getScriptHash(network);
|
||||
await _scripthashesUpdateSubject[sh]?.close();
|
||||
_scripthashesUpdateSubject[sh] = await electrumClient.scripthashUpdate(sh);
|
||||
if (!(_scripthashesUpdateSubject[sh]?.isClosed ?? true)) {
|
||||
try {
|
||||
await _scripthashesUpdateSubject[sh]?.close();
|
||||
} catch (e) {
|
||||
print("failed to close: $e");
|
||||
}
|
||||
}
|
||||
try {
|
||||
_scripthashesUpdateSubject[sh] = await electrumClient.scripthashUpdate(sh);
|
||||
} catch (e) {
|
||||
print("failed scripthashUpdate: $e");
|
||||
}
|
||||
_scripthashesUpdateSubject[sh]?.listen((event) async {
|
||||
try {
|
||||
await updateUnspentsForAddress(address);
|
||||
|
@ -1925,7 +2026,7 @@ abstract class ElectrumWalletBase
|
|||
|
||||
await _fetchAddressHistory(address, await getCurrentChainTip());
|
||||
} catch (e, s) {
|
||||
print(e.toString());
|
||||
print("sub error: $e");
|
||||
_onError?.call(FlutterErrorDetails(
|
||||
exception: e,
|
||||
stack: s,
|
||||
|
@ -2003,6 +2104,7 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
Future<void> updateBalance() async {
|
||||
print("updateBalance() called!");
|
||||
balance[currency] = await fetchBalances();
|
||||
await save();
|
||||
}
|
||||
|
@ -2111,6 +2213,7 @@ abstract class ElectrumWalletBase
|
|||
|
||||
@action
|
||||
void _onConnectionStatusChange(ConnectionStatus status) {
|
||||
|
||||
switch (status) {
|
||||
case ConnectionStatus.connected:
|
||||
if (syncStatus is NotConnectedSyncStatus ||
|
||||
|
@ -2122,19 +2225,26 @@ abstract class ElectrumWalletBase
|
|||
|
||||
break;
|
||||
case ConnectionStatus.disconnected:
|
||||
syncStatus = NotConnectedSyncStatus();
|
||||
if (syncStatus is! NotConnectedSyncStatus) {
|
||||
syncStatus = NotConnectedSyncStatus();
|
||||
}
|
||||
break;
|
||||
case ConnectionStatus.failed:
|
||||
syncStatus = LostConnectionSyncStatus();
|
||||
if (syncStatus is! LostConnectionSyncStatus) {
|
||||
syncStatus = LostConnectionSyncStatus();
|
||||
}
|
||||
break;
|
||||
case ConnectionStatus.connecting:
|
||||
syncStatus = ConnectingSyncStatus();
|
||||
if (syncStatus is! ConnectingSyncStatus) {
|
||||
syncStatus = ConnectingSyncStatus();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
void _syncStatusReaction(SyncStatus syncStatus) async {
|
||||
print("SYNC_STATUS_CHANGE: ${syncStatus}");
|
||||
if (syncStatus is SyncingSyncStatus) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import 'dart:io' show Platform;
|
||||
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
@ -100,10 +102,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
static const gap = 20;
|
||||
|
||||
final ObservableList<BitcoinAddressRecord> _addresses;
|
||||
late ObservableList<BaseBitcoinAddressRecord> addressesByReceiveType;
|
||||
final ObservableList<BaseBitcoinAddressRecord> addressesByReceiveType;
|
||||
final ObservableList<BitcoinAddressRecord> receiveAddresses;
|
||||
final ObservableList<BitcoinAddressRecord> changeAddresses;
|
||||
// TODO: add this variable in `bitcoin_wallet_addresses` and just add a cast in cw_bitcoin to use it
|
||||
final ObservableList<BitcoinSilentPaymentAddressRecord> silentAddresses;
|
||||
// TODO: add this variable in `litecoin_wallet_addresses` and just add a cast in cw_bitcoin to use it
|
||||
final ObservableList<BitcoinAddressRecord> mwebAddresses;
|
||||
final BasedUtxoNetwork network;
|
||||
final Bip32Slip10Secp256k1 mainHd;
|
||||
|
@ -236,7 +240,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
|
||||
} else if (walletInfo.type == WalletType.litecoin) {
|
||||
await _generateInitialAddresses(type: SegwitAddresType.p2wpkh);
|
||||
await _generateInitialAddresses(type: SegwitAddresType.mweb);
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
await _generateInitialAddresses(type: SegwitAddresType.mweb);
|
||||
}
|
||||
} else if (walletInfo.type == WalletType.bitcoin) {
|
||||
await _generateInitialAddresses();
|
||||
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
|
||||
|
@ -261,7 +267,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
}
|
||||
|
||||
@action
|
||||
Future<String> getChangeAddress({List<BitcoinOutput>? outputs, UtxoDetails? utxoDetails}) async {
|
||||
Future<String> getChangeAddress({List<BitcoinUnspent>? inputs, List<BitcoinOutput>? outputs, bool isPegIn = false}) async {
|
||||
updateChangeAddresses();
|
||||
|
||||
if (changeAddresses.isEmpty) {
|
||||
|
@ -472,7 +478,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
|
||||
await saveAddressesInBox();
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
print("updateAddresses $e");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ class ElectrumWalletSnapshot {
|
|||
required this.silentAddresses,
|
||||
required this.silentAddressIndex,
|
||||
required this.mwebAddresses,
|
||||
required this.alwaysScan,
|
||||
this.passphrase,
|
||||
this.derivationType,
|
||||
this.derivationPath,
|
||||
|
@ -46,6 +47,7 @@ class ElectrumWalletSnapshot {
|
|||
List<BitcoinAddressRecord> addresses;
|
||||
List<BitcoinSilentPaymentAddressRecord> silentAddresses;
|
||||
List<BitcoinAddressRecord> mwebAddresses;
|
||||
bool alwaysScan;
|
||||
|
||||
ElectrumBalance balance;
|
||||
Map<String, int> regularAddressIndex;
|
||||
|
@ -54,15 +56,15 @@ class ElectrumWalletSnapshot {
|
|||
DerivationType? derivationType;
|
||||
String? derivationPath;
|
||||
|
||||
static Future<ElectrumWalletSnapshot> load(
|
||||
EncryptionFileUtils encryptionFileUtils, String name, WalletType type, String password, BasedUtxoNetwork network) async {
|
||||
static Future<ElectrumWalletSnapshot> load(EncryptionFileUtils encryptionFileUtils, String name,
|
||||
WalletType type, String password, BasedUtxoNetwork network) async {
|
||||
final path = await pathForWallet(name: name, type: type);
|
||||
final jsonSource = await encryptionFileUtils.read(path: path, password: password);
|
||||
final data = json.decode(jsonSource) as Map;
|
||||
final mnemonic = data['mnemonic'] as String?;
|
||||
final xpub = data['xpub'] as String?;
|
||||
final passphrase = data['passphrase'] as String? ?? '';
|
||||
|
||||
|
||||
final addressesTmp = data['addresses'] as List? ?? <Object>[];
|
||||
final addresses = addressesTmp
|
||||
.whereType<String>()
|
||||
|
@ -81,6 +83,8 @@ class ElectrumWalletSnapshot {
|
|||
.map((addr) => BitcoinAddressRecord.fromJSON(addr, network: network))
|
||||
.toList();
|
||||
|
||||
final alwaysScan = data['alwaysScan'] as bool? ?? false;
|
||||
|
||||
final balance = ElectrumBalance.fromJSON(data['balance'] as String?) ??
|
||||
ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
|
||||
var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
|
||||
|
@ -124,6 +128,7 @@ class ElectrumWalletSnapshot {
|
|||
silentAddresses: silentAddresses,
|
||||
silentAddressIndex: silentAddressIndex,
|
||||
mwebAddresses: mwebAddresses,
|
||||
alwaysScan: alwaysScan,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,6 @@ class BitcoinTransactionCommitFailedVoutNegative extends TransactionCommitFailed
|
|||
|
||||
class BitcoinTransactionCommitFailedBIP68Final extends TransactionCommitFailedBIP68Final {}
|
||||
|
||||
class BitcoinTransactionCommitFailedLessThanMin extends TransactionCommitFailedBIP68Final {}
|
||||
class BitcoinTransactionCommitFailedLessThanMin extends TransactionCommitFailedLessThanMin {}
|
||||
|
||||
class BitcoinTransactionSilentPaymentsNotSupported extends TransactionInputNotSupported {}
|
||||
|
|
|
@ -76,6 +76,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
seedBytes: seedBytes,
|
||||
encryptionFileUtils: encryptionFileUtils,
|
||||
currency: CryptoCurrency.ltc,
|
||||
alwaysScan: alwaysScan,
|
||||
) {
|
||||
mwebHd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/1000'") as Bip32Slip10Secp256k1;
|
||||
mwebEnabled = alwaysScan ?? false;
|
||||
|
@ -94,6 +95,36 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
autorun((_) {
|
||||
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
||||
});
|
||||
reaction((_) => mwebSyncStatus, (status) async {
|
||||
if (mwebSyncStatus is FailedSyncStatus) {
|
||||
// we failed to connect to mweb, check if we are connected to the litecoin node:
|
||||
late int nodeHeight;
|
||||
try {
|
||||
nodeHeight = await electrumClient.getCurrentBlockChainTip() ?? 0;
|
||||
} catch (_) {
|
||||
nodeHeight = 0;
|
||||
}
|
||||
|
||||
if (nodeHeight == 0) {
|
||||
// we aren't connected to the litecoin node, so the current electrum_wallet reactions will take care of this case for us
|
||||
} else {
|
||||
// we're connected to the litecoin node, but we failed to connect to mweb, try again after a few seconds:
|
||||
await CwMweb.stop();
|
||||
await Future.delayed(const Duration(seconds: 5));
|
||||
startSync();
|
||||
}
|
||||
} else if (mwebSyncStatus is SyncingSyncStatus) {
|
||||
syncStatus = mwebSyncStatus;
|
||||
} else if (mwebSyncStatus is SyncronizingSyncStatus) {
|
||||
if (syncStatus is! SyncronizingSyncStatus) {
|
||||
syncStatus = mwebSyncStatus;
|
||||
}
|
||||
} else if (mwebSyncStatus is SyncedSyncStatus) {
|
||||
if (syncStatus is! SyncedSyncStatus) {
|
||||
syncStatus = mwebSyncStatus;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
late final Bip32Slip10Secp256k1 mwebHd;
|
||||
late final Box<MwebUtxo> mwebUtxosBox;
|
||||
|
@ -101,10 +132,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
Timer? _feeRatesTimer;
|
||||
Timer? _processingTimer;
|
||||
StreamSubscription<Utxo>? _utxoStream;
|
||||
late RpcClient _stub;
|
||||
late bool mwebEnabled;
|
||||
bool processingUtxos = false;
|
||||
|
||||
@observable
|
||||
SyncStatus mwebSyncStatus = NotConnectedSyncStatus();
|
||||
|
||||
List<int> get scanSecret => mwebHd.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
|
||||
List<int> get spendSecret => mwebHd.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw;
|
||||
|
||||
|
@ -229,109 +262,129 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
initialRegularAddressIndex: snp?.regularAddressIndex,
|
||||
initialChangeAddressIndex: snp?.changeAddressIndex,
|
||||
addressPageType: snp?.addressPageType,
|
||||
alwaysScan: alwaysScan,
|
||||
alwaysScan: snp?.alwaysScan,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> waitForMwebAddresses() async {
|
||||
print("waitForMwebAddresses() called!");
|
||||
// ensure that we have the full 1000 mweb addresses generated before continuing:
|
||||
// should no longer be needed, but leaving here just in case
|
||||
final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs;
|
||||
while (mwebAddrs.length < 1000) {
|
||||
print("waiting for mweb addresses to finish generating...");
|
||||
await Future.delayed(const Duration(milliseconds: 1000));
|
||||
}
|
||||
await (walletAddresses as LitecoinWalletAddresses).ensureMwebAddressUpToIndexExists(1020);
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> startSync() async {
|
||||
if (syncStatus is SyncronizingSyncStatus) {
|
||||
print("startSync() called!");
|
||||
print("STARTING SYNC - MWEB ENABLED: $mwebEnabled");
|
||||
if (!mwebEnabled) {
|
||||
try {
|
||||
// in case we're switching from a litecoin wallet that had mweb enabled
|
||||
CwMweb.stop();
|
||||
} catch (_) {}
|
||||
super.startSync();
|
||||
return;
|
||||
}
|
||||
print("STARTING SYNC - MWEB ENABLED: $mwebEnabled");
|
||||
try {
|
||||
syncStatus = SyncronizingSyncStatus();
|
||||
await subscribeForUpdates();
|
||||
updateFeeRates();
|
||||
|
||||
if (mwebSyncStatus is SyncronizingSyncStatus) {
|
||||
return;
|
||||
}
|
||||
|
||||
print("STARTING SYNC - MWEB ENABLED: $mwebEnabled");
|
||||
_syncTimer?.cancel();
|
||||
try {
|
||||
mwebSyncStatus = SyncronizingSyncStatus();
|
||||
try {
|
||||
await subscribeForUpdates();
|
||||
} catch (e) {
|
||||
print("failed to subcribe for updates: $e");
|
||||
}
|
||||
updateFeeRates();
|
||||
_feeRatesTimer?.cancel();
|
||||
_feeRatesTimer =
|
||||
Timer.periodic(const Duration(minutes: 1), (timer) async => await updateFeeRates());
|
||||
|
||||
if (!mwebEnabled) {
|
||||
try {
|
||||
await updateAllUnspents();
|
||||
await updateTransactions();
|
||||
await updateBalance();
|
||||
syncStatus = SyncedSyncStatus();
|
||||
} catch (e, s) {
|
||||
print(e);
|
||||
print(s);
|
||||
syncStatus = FailedSyncStatus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
print("START SYNC FUNCS");
|
||||
await waitForMwebAddresses();
|
||||
await getStub();
|
||||
await processMwebUtxos();
|
||||
await updateTransactions();
|
||||
await updateUnspent();
|
||||
await updateBalance();
|
||||
} catch (e) {
|
||||
print("failed to start mweb sync: $e");
|
||||
syncStatus = FailedSyncStatus();
|
||||
print("DONE SYNC FUNCS");
|
||||
} catch (e, s) {
|
||||
print("mweb sync failed: $e $s");
|
||||
mwebSyncStatus = FailedSyncStatus(error: "mweb sync failed: $e");
|
||||
return;
|
||||
}
|
||||
|
||||
_syncTimer?.cancel();
|
||||
_syncTimer = Timer.periodic(const Duration(milliseconds: 1500), (timer) async {
|
||||
if (syncStatus is FailedSyncStatus) return;
|
||||
_syncTimer = Timer.periodic(const Duration(milliseconds: 3000), (timer) async {
|
||||
if (mwebSyncStatus is FailedSyncStatus) {
|
||||
_syncTimer?.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
final nodeHeight =
|
||||
await electrumClient.getCurrentBlockChainTip() ?? 0; // current block height of our node
|
||||
final resp = await CwMweb.status(StatusRequest());
|
||||
|
||||
if (resp.blockHeaderHeight < nodeHeight) {
|
||||
int h = resp.blockHeaderHeight;
|
||||
syncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
|
||||
} else if (resp.mwebHeaderHeight < nodeHeight) {
|
||||
int h = resp.mwebHeaderHeight;
|
||||
syncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
|
||||
} else if (resp.mwebUtxosHeight < nodeHeight) {
|
||||
syncStatus = SyncingSyncStatus(1, 0.999);
|
||||
} else {
|
||||
if (resp.mwebUtxosHeight > walletInfo.restoreHeight) {
|
||||
await walletInfo.updateRestoreHeight(resp.mwebUtxosHeight);
|
||||
await checkMwebUtxosSpent();
|
||||
// update the confirmations for each transaction:
|
||||
for (final transaction in transactionHistory.transactions.values) {
|
||||
if (transaction.isPending) continue;
|
||||
int txHeight = transaction.height ?? resp.mwebUtxosHeight;
|
||||
final confirmations = (resp.mwebUtxosHeight - txHeight) + 1;
|
||||
if (transaction.confirmations == confirmations) continue;
|
||||
transaction.confirmations = confirmations;
|
||||
transactionHistory.addOne(transaction);
|
||||
}
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
// prevent unnecessary reaction triggers:
|
||||
if (syncStatus is! SyncedSyncStatus) {
|
||||
// mwebd is synced, but we could still be processing incoming utxos:
|
||||
if (!processingUtxos) {
|
||||
syncStatus = SyncedSyncStatus();
|
||||
}
|
||||
if (nodeHeight == 0) {
|
||||
// we aren't connected to the ltc node yet
|
||||
if (mwebSyncStatus is! NotConnectedSyncStatus) {
|
||||
mwebSyncStatus = FailedSyncStatus(error: "litecoin node isn't connected");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final resp = await CwMweb.status(StatusRequest());
|
||||
|
||||
try {
|
||||
if (resp.blockHeaderHeight < nodeHeight) {
|
||||
int h = resp.blockHeaderHeight;
|
||||
mwebSyncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
|
||||
} else if (resp.mwebHeaderHeight < nodeHeight) {
|
||||
int h = resp.mwebHeaderHeight;
|
||||
mwebSyncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
|
||||
} else if (resp.mwebUtxosHeight < nodeHeight) {
|
||||
mwebSyncStatus = SyncingSyncStatus(1, 0.999);
|
||||
} else {
|
||||
if (resp.mwebUtxosHeight > walletInfo.restoreHeight) {
|
||||
await walletInfo.updateRestoreHeight(resp.mwebUtxosHeight);
|
||||
await checkMwebUtxosSpent();
|
||||
// update the confirmations for each transaction:
|
||||
for (final transaction in transactionHistory.transactions.values) {
|
||||
if (transaction.isPending) continue;
|
||||
int txHeight = transaction.height ?? resp.mwebUtxosHeight;
|
||||
final confirmations = (resp.mwebUtxosHeight - txHeight) + 1;
|
||||
if (transaction.confirmations == confirmations) continue;
|
||||
if (transaction.confirmations == 0) {
|
||||
updateBalance();
|
||||
}
|
||||
transaction.confirmations = confirmations;
|
||||
transactionHistory.addOne(transaction);
|
||||
}
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
// prevent unnecessary reaction triggers:
|
||||
if (mwebSyncStatus is! SyncedSyncStatus) {
|
||||
// mwebd is synced, but we could still be processing incoming utxos:
|
||||
if (!processingUtxos) {
|
||||
mwebSyncStatus = SyncedSyncStatus();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
print("error syncing: $e");
|
||||
mwebSyncStatus = FailedSyncStatus(error: e.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> stopSync() async {
|
||||
print("stopSync() called!");
|
||||
_syncTimer?.cancel();
|
||||
_utxoStream?.cancel();
|
||||
_feeRatesTimer?.cancel();
|
||||
|
@ -371,7 +424,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
bool? usingElectrs,
|
||||
}) async {
|
||||
_syncTimer?.cancel();
|
||||
int oldHeight = walletInfo.restoreHeight;
|
||||
await walletInfo.updateRestoreHeight(height);
|
||||
|
||||
// go through mwebUtxos and clear any that are above the new restore height:
|
||||
|
@ -408,8 +460,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
await initMwebUtxosBox();
|
||||
}
|
||||
|
||||
Future<void> handleIncoming(MwebUtxo utxo, RpcClient stub) async {
|
||||
final status = await stub.status(StatusRequest());
|
||||
Future<void> handleIncoming(MwebUtxo utxo) async {
|
||||
print("handleIncoming() called!");
|
||||
final status = await CwMweb.status(StatusRequest());
|
||||
var date = DateTime.now();
|
||||
var confirmations = 0;
|
||||
if (utxo.height > 0) {
|
||||
|
@ -475,6 +528,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
}
|
||||
|
||||
Future<void> processMwebUtxos() async {
|
||||
print("processMwebUtxos() called!");
|
||||
if (!mwebEnabled) {
|
||||
return;
|
||||
}
|
||||
|
@ -484,15 +538,15 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
final req = UtxosRequest(scanSecret: scanSecret, fromHeight: restoreHeight);
|
||||
|
||||
// process new utxos as they come in:
|
||||
_utxoStream?.cancel();
|
||||
await _utxoStream?.cancel();
|
||||
ResponseStream<Utxo>? responseStream = await CwMweb.utxos(req);
|
||||
if (responseStream == null) {
|
||||
throw Exception("failed to get utxos stream!");
|
||||
}
|
||||
_utxoStream = responseStream.listen((Utxo sUtxo) async {
|
||||
// we're processing utxos, so our balance could still be innacurate:
|
||||
if (syncStatus is! SyncronizingSyncStatus && syncStatus is! SyncingSyncStatus) {
|
||||
syncStatus = SyncronizingSyncStatus();
|
||||
if (mwebSyncStatus is! SyncronizingSyncStatus && mwebSyncStatus is! SyncingSyncStatus) {
|
||||
mwebSyncStatus = SyncronizingSyncStatus();
|
||||
processingUtxos = true;
|
||||
_processingTimer?.cancel();
|
||||
_processingTimer = Timer.periodic(const Duration(seconds: 2), (timer) async {
|
||||
|
@ -509,10 +563,18 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
value: sUtxo.value.toInt(),
|
||||
);
|
||||
|
||||
// if (mwebUtxosBox.containsKey(utxo.outputId)) {
|
||||
// // we've already stored this utxo, skip it:
|
||||
// return;
|
||||
// }
|
||||
if (mwebUtxosBox.containsKey(utxo.outputId)) {
|
||||
// we've already stored this utxo, skip it:
|
||||
// but do update the utxo height if it's somehow different:
|
||||
final existingUtxo = mwebUtxosBox.get(utxo.outputId);
|
||||
if (existingUtxo!.height != utxo.height) {
|
||||
print(
|
||||
"updating utxo height for $utxo.outputId: ${existingUtxo.height} -> ${utxo.height}");
|
||||
existingUtxo.height = utxo.height;
|
||||
await mwebUtxosBox.put(utxo.outputId, existingUtxo);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await updateUnspent();
|
||||
await updateBalance();
|
||||
|
@ -526,7 +588,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
|
||||
await mwebUtxosBox.put(utxo.outputId, utxo);
|
||||
|
||||
await handleIncoming(utxo, _stub);
|
||||
await handleIncoming(utxo);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -558,7 +620,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
final height = await electrumClient.getCurrentBlockChainTip();
|
||||
if (height == null || status.blockHeaderHeight != height) return;
|
||||
if (status.mwebUtxosHeight != height) return; // we aren't synced
|
||||
|
||||
int amount = 0;
|
||||
Set<String> inputAddresses = {};
|
||||
var output = convert.AccumulatorSink<Digest>();
|
||||
|
@ -644,6 +705,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
}
|
||||
|
||||
Future<void> updateUnspent() async {
|
||||
print("updateUnspent() called!");
|
||||
await checkMwebUtxosSpent();
|
||||
await updateAllUnspents();
|
||||
}
|
||||
|
@ -651,13 +713,10 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
@override
|
||||
@action
|
||||
Future<void> updateAllUnspents() async {
|
||||
// get ltc unspents:
|
||||
await super.updateAllUnspents();
|
||||
|
||||
if (!mwebEnabled) {
|
||||
await super.updateAllUnspents();
|
||||
return;
|
||||
}
|
||||
await getStub();
|
||||
|
||||
// add the mweb unspents to the list:
|
||||
List<BitcoinUnspent> mwebUnspentCoins = [];
|
||||
|
@ -691,6 +750,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
}
|
||||
mwebUnspentCoins.add(unspent);
|
||||
});
|
||||
|
||||
// copy coin control attributes to mwebCoins:
|
||||
await updateCoins(mwebUnspentCoins);
|
||||
// get regular ltc unspents (this resets unspentCoins):
|
||||
await super.updateAllUnspents();
|
||||
// add the mwebCoins:
|
||||
unspentCoins.addAll(mwebUnspentCoins);
|
||||
}
|
||||
|
||||
|
@ -700,7 +765,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
if (!mwebEnabled) {
|
||||
return balance;
|
||||
}
|
||||
await getStub();
|
||||
|
||||
// update unspent balances:
|
||||
await updateUnspent();
|
||||
|
@ -870,10 +934,11 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
tx.isMweb = mwebEnabled;
|
||||
|
||||
if (!mwebEnabled) {
|
||||
tx.changeAddressOverride =
|
||||
await (walletAddresses as LitecoinWalletAddresses).getChangeAddress(isPegIn: false);
|
||||
return tx;
|
||||
}
|
||||
await waitForMwebAddresses();
|
||||
await getStub();
|
||||
|
||||
final resp = await CwMweb.create(CreateRequest(
|
||||
rawTx: hex.decode(tx.hex),
|
||||
|
@ -894,13 +959,25 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
hasMwebOutput = true;
|
||||
break;
|
||||
}
|
||||
if (output.address.toLowerCase().contains("mweb")) {
|
||||
hasMwebOutput = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tx2.mwebBytes != null && tx2.mwebBytes!.isNotEmpty) {
|
||||
hasMwebInput = true;
|
||||
// check if mweb inputs are used:
|
||||
for (final utxo in tx.utxos) {
|
||||
if (utxo.utxo.scriptType == SegwitAddresType.mweb) {
|
||||
hasMwebInput = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool isPegIn = !hasMwebInput && hasMwebOutput;
|
||||
bool isRegular = !hasMwebInput && !hasMwebOutput;
|
||||
tx.changeAddressOverride = await (walletAddresses as LitecoinWalletAddresses)
|
||||
.getChangeAddress(isPegIn: isPegIn || isRegular);
|
||||
if (!hasMwebInput && !hasMwebOutput) {
|
||||
tx.isMweb = false;
|
||||
return tx;
|
||||
}
|
||||
|
||||
|
@ -951,7 +1028,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
final addresses = <String>{};
|
||||
transaction.inputAddresses?.forEach((id) async {
|
||||
final utxo = mwebUtxosBox.get(id);
|
||||
// await mwebUtxosBox.delete(id);// gets deleted in checkMwebUtxosSpent
|
||||
await mwebUtxosBox.delete(id); // gets deleted in checkMwebUtxosSpent
|
||||
if (utxo == null) return;
|
||||
final addressRecord = walletAddresses.allAddresses
|
||||
.firstWhere((addressRecord) => addressRecord.address == utxo.address);
|
||||
|
@ -970,6 +1047,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
print(e);
|
||||
print(s);
|
||||
if (e.toString().contains("commit failed")) {
|
||||
print(e);
|
||||
throw Exception("Transaction commit failed (no peers responded), please try again.");
|
||||
}
|
||||
rethrow;
|
||||
|
@ -982,13 +1060,17 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
Future<void> close({required bool shouldCleanup}) async {
|
||||
_utxoStream?.cancel();
|
||||
_feeRatesTimer?.cancel();
|
||||
_syncTimer?.cancel();
|
||||
_processingTimer?.cancel();
|
||||
await stopSync();
|
||||
await super.close();
|
||||
if (shouldCleanup) {
|
||||
try {
|
||||
await stopSync();
|
||||
} catch (_) {}
|
||||
}
|
||||
await super.close(shouldCleanup: shouldCleanup);
|
||||
}
|
||||
|
||||
Future<void> setMwebEnabled(bool enabled) async {
|
||||
|
@ -996,17 +1078,16 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
return;
|
||||
}
|
||||
|
||||
alwaysScan = enabled;
|
||||
mwebEnabled = enabled;
|
||||
(walletAddresses as LitecoinWalletAddresses).mwebEnabled = enabled;
|
||||
await stopSync();
|
||||
await save();
|
||||
try {
|
||||
await stopSync();
|
||||
} catch (_) {}
|
||||
await startSync();
|
||||
}
|
||||
|
||||
Future<RpcClient> getStub() async {
|
||||
_stub = await CwMweb.stub();
|
||||
return _stub;
|
||||
}
|
||||
|
||||
Future<StatusResponse> getStatusRequest() async {
|
||||
final resp = await CwMweb.status(StatusRequest());
|
||||
return resp;
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io' show Platform;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||
import 'package:cw_bitcoin/utils.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||
|
@ -39,8 +41,10 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
|
|||
bool mwebEnabled;
|
||||
int mwebTopUpIndex = 1000;
|
||||
List<String> mwebAddrs = [];
|
||||
bool generating = false;
|
||||
|
||||
List<int> get scanSecret => mwebHd.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
|
||||
|
||||
List<int> get spendPubkey =>
|
||||
mwebHd.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed;
|
||||
|
||||
|
@ -57,26 +61,40 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
|
|||
}
|
||||
|
||||
Future<void> ensureMwebAddressUpToIndexExists(int index) async {
|
||||
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Uint8List scan = Uint8List.fromList(scanSecret);
|
||||
Uint8List spend = Uint8List.fromList(spendPubkey);
|
||||
int count = 0;
|
||||
while (mwebAddrs.length <= (index + 1)) {
|
||||
final address = await CwMweb.address(scan, spend, mwebAddrs.length);
|
||||
mwebAddrs.add(address!);
|
||||
count++;
|
||||
// sleep for a bit to avoid making the main thread unresponsive:
|
||||
if (count > 50) {
|
||||
count = 0;
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initMwebAddresses() async {
|
||||
if (mwebAddrs.length < 1000) {
|
||||
print("Generating MWEB addresses...");
|
||||
await ensureMwebAddressUpToIndexExists(1020);
|
||||
print("done generating MWEB addresses");
|
||||
if (index < mwebAddresses.length && index < mwebAddrs.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (generating) {
|
||||
print("generating.....");
|
||||
// this function was called multiple times in multiple places:
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
}
|
||||
|
||||
print("Generating MWEB addresses up to index $index");
|
||||
generating = true;
|
||||
try {
|
||||
while (mwebAddrs.length <= (index + 1)) {
|
||||
final addresses =
|
||||
await CwMweb.addresses(scan, spend, mwebAddrs.length, mwebAddrs.length + 50);
|
||||
print("generated up to index ${mwebAddrs.length}");
|
||||
// sleep for a bit to avoid making the main thread unresponsive:
|
||||
await Future.delayed(Duration(milliseconds: 200));
|
||||
mwebAddrs.addAll(addresses!);
|
||||
}
|
||||
} catch (_) {}
|
||||
generating = false;
|
||||
print("Done generating MWEB addresses len: ${mwebAddrs.length}");
|
||||
|
||||
// ensure mweb addresses are up to date:
|
||||
if (mwebAddresses.length < mwebAddrs.length) {
|
||||
List<BitcoinAddressRecord> addressRecords = mwebAddrs
|
||||
.asMap()
|
||||
.entries
|
||||
|
@ -88,7 +106,13 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
|
|||
))
|
||||
.toList();
|
||||
addMwebAddresses(addressRecords);
|
||||
print("added ${addressRecords.length} mweb addresses");
|
||||
print("set ${addressRecords.length} mweb addresses");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initMwebAddresses() async {
|
||||
if (mwebAddrs.length < 1000) {
|
||||
await ensureMwebAddressUpToIndexExists(20);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -119,14 +143,15 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
|
|||
|
||||
@action
|
||||
@override
|
||||
Future<String> getChangeAddress({List<BitcoinOutput>? outputs, UtxoDetails? utxoDetails}) async {
|
||||
Future<String> getChangeAddress(
|
||||
{List<BitcoinUnspent>? inputs, List<BitcoinOutput>? outputs, bool isPegIn = false}) async {
|
||||
// use regular change address on peg in, otherwise use mweb for change address:
|
||||
|
||||
if (!mwebEnabled) {
|
||||
if (!mwebEnabled || isPegIn) {
|
||||
return super.getChangeAddress();
|
||||
}
|
||||
|
||||
if (outputs != null && utxoDetails != null) {
|
||||
if (inputs != null && outputs != null) {
|
||||
// check if this is a PEGIN:
|
||||
bool outputsToMweb = false;
|
||||
bool comesFromMweb = false;
|
||||
|
@ -138,14 +163,18 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
|
|||
outputsToMweb = true;
|
||||
}
|
||||
}
|
||||
// TODO: this doesn't respect coin control because it doesn't know which available inputs are selected
|
||||
utxoDetails.availableInputs.forEach((element) {
|
||||
|
||||
inputs.forEach((element) {
|
||||
if (!element.isSending || element.isFrozen) {
|
||||
return;
|
||||
}
|
||||
if (element.address.contains("mweb")) {
|
||||
comesFromMweb = true;
|
||||
}
|
||||
});
|
||||
|
||||
bool isPegIn = !comesFromMweb && outputsToMweb;
|
||||
|
||||
if (isPegIn && mwebEnabled) {
|
||||
return super.getChangeAddress();
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ class PendingBitcoinTransaction with PendingTransaction {
|
|||
this.isSendAll = false,
|
||||
this.hasTaprootInputs = false,
|
||||
this.isMweb = false,
|
||||
this.utxos = const [],
|
||||
}) : _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
|
||||
|
||||
final WalletType type;
|
||||
|
@ -36,7 +37,9 @@ class PendingBitcoinTransaction with PendingTransaction {
|
|||
final bool isSendAll;
|
||||
final bool hasChange;
|
||||
final bool hasTaprootInputs;
|
||||
List<UtxoWithAddress> utxos;
|
||||
bool isMweb;
|
||||
String? changeAddressOverride;
|
||||
String? idOverride;
|
||||
String? hexOverride;
|
||||
List<String>? outputAddresses;
|
||||
|
@ -63,6 +66,9 @@ class PendingBitcoinTransaction with PendingTransaction {
|
|||
PendingChange? get change {
|
||||
try {
|
||||
final change = _tx.outputs.firstWhere((out) => out.isChange);
|
||||
if (changeAddressOverride != null) {
|
||||
return PendingChange(changeAddressOverride!, BtcUtils.fromSatoshi(change.amount));
|
||||
}
|
||||
return PendingChange(change.scriptPubKey.toAddress(), BtcUtils.fromSatoshi(change.amount));
|
||||
} catch (_) {
|
||||
return null;
|
||||
|
@ -117,6 +123,8 @@ class PendingBitcoinTransaction with PendingTransaction {
|
|||
idOverride = resp.txid;
|
||||
} on GrpcError catch (e) {
|
||||
throw BitcoinTransactionCommitFailed(errorMessage: e.message);
|
||||
} catch (e) {
|
||||
throw BitcoinTransactionCommitFailed(errorMessage: "Unknown error: ${e.toString()}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,10 +21,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d"
|
||||
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.10"
|
||||
version: "3.6.1"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -49,6 +49,15 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
bech32:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: HEAD
|
||||
resolved-ref: "05755063b593aa6cca0a4820a318e0ce17de6192"
|
||||
url: "https://github.com/cake-tech/bech32.git"
|
||||
source: git
|
||||
version: "0.2.2"
|
||||
bip32:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -78,8 +87,8 @@ packages:
|
|||
dependency: "direct overridden"
|
||||
description:
|
||||
path: "."
|
||||
ref: cake-update-v7
|
||||
resolved-ref: f577e83fe78766b2655ea0602baa9299b953a31b
|
||||
ref: cake-update-v8
|
||||
resolved-ref: fc045a11db3d85d806ca67f75e8b916c706745a2
|
||||
url: "https://github.com/cake-tech/bitcoin_base"
|
||||
source: git
|
||||
version: "4.7.0"
|
||||
|
@ -308,13 +317,13 @@ packages:
|
|||
source: hosted
|
||||
version: "1.3.1"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
version: "2.1.0"
|
||||
ffigen:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -327,10 +336,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
version: "7.0.1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -394,14 +403,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
google_identity_services_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: google_identity_services_web
|
||||
sha256: "5be191523702ba8d7a01ca97c17fca096822ccf246b0a9f11923a6ded06199b6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.1+4"
|
||||
googleapis_auth:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: googleapis_auth
|
||||
sha256: af7c3a3edf9d0de2e1e0a77e994fae0a581c525fa7012af4fa0d4a52ed9484da
|
||||
sha256: befd71383a955535060acde8792e7efc11d2fccd03dd1d3ec434e85b68775938
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
version: "1.6.0"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -450,6 +467,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
http2:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http2
|
||||
sha256: "9ced024a160b77aba8fb8674e38f70875e321d319e6f303ec18e87bd5a4b0c1d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -693,13 +718,13 @@ packages:
|
|||
source: hosted
|
||||
version: "2.1.8"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: pointycastle
|
||||
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
|
||||
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.9.1"
|
||||
version: "3.7.4"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -709,13 +734,13 @@ packages:
|
|||
source: hosted
|
||||
version: "1.5.1"
|
||||
protobuf:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: protobuf
|
||||
sha256: "01dd9bd0fa02548bf2ceee13545d4a0ec6046459d847b6b061d8a27237108a08"
|
||||
sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
version: "3.1.0"
|
||||
provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -792,10 +817,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f
|
||||
sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.2"
|
||||
version: "2.5.3"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1014,10 +1039,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
version: "1.1.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -83,7 +83,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
|||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
initialAddresses: initialAddresses,
|
||||
initialBalance: initialBalance,
|
||||
seedBytes: await MnemonicBip39.toSeed(mnemonic, passphrase: passphrase),
|
||||
seedBytes: MnemonicBip39.toSeed(mnemonic, passphrase: passphrase),
|
||||
encryptionFileUtils: encryptionFileUtils,
|
||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
|
|
|
@ -36,7 +36,7 @@ class BitcoinCashWalletService extends WalletService<
|
|||
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
||||
|
||||
final wallet = await BitcoinCashWalletBase.create(
|
||||
mnemonic: credentials.mnemonic ?? await MnemonicBip39.generate(strength: strength),
|
||||
mnemonic: credentials.mnemonic ?? MnemonicBip39.generate(strength: strength),
|
||||
password: credentials.password!,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
|
|
|
@ -106,6 +106,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
|||
CryptoCurrency.usdcTrc20,
|
||||
CryptoCurrency.tbtc,
|
||||
CryptoCurrency.wow,
|
||||
CryptoCurrency.ton,
|
||||
];
|
||||
|
||||
static const havenCurrencies = [
|
||||
|
@ -223,6 +224,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
|||
static const usdcTrc20 = CryptoCurrency(title: 'USDC', tag: 'TRX', fullName: 'USDC Coin', raw: 92, name: 'usdctrc20', iconPath: 'assets/images/usdc_icon.png', decimals: 6);
|
||||
static const tbtc = CryptoCurrency(title: 'tBTC', fullName: 'Testnet Bitcoin', raw: 93, name: 'tbtc', iconPath: 'assets/images/tbtc.png', decimals: 8);
|
||||
static const wow = CryptoCurrency(title: 'WOW', fullName: 'Wownero', raw: 94, name: 'wow', iconPath: 'assets/images/wownero_icon.png', decimals: 11);
|
||||
static const ton = CryptoCurrency(title: 'TON', fullName: 'Toncoin', raw: 95, name: 'ton', iconPath: 'assets/images/ton_icon.png', decimals: 8);
|
||||
|
||||
|
||||
static final Map<int, CryptoCurrency> _rawCurrencyMap =
|
||||
|
|
|
@ -239,12 +239,15 @@ class Node extends HiveObject with Keyable {
|
|||
// you try to communicate with it
|
||||
Future<bool> requestElectrumServer() async {
|
||||
try {
|
||||
final Socket socket;
|
||||
if (useSSL == true) {
|
||||
await SecureSocket.connect(uri.host, uri.port,
|
||||
socket = await SecureSocket.connect(uri.host, uri.port,
|
||||
timeout: Duration(seconds: 5), onBadCertificate: (_) => true);
|
||||
} else {
|
||||
await Socket.connect(uri.host, uri.port, timeout: Duration(seconds: 5));
|
||||
socket = await Socket.connect(uri.host, uri.port, timeout: Duration(seconds: 5));
|
||||
}
|
||||
|
||||
socket.destroy();
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
|
|
|
@ -67,7 +67,13 @@ class AttemptingScanSyncStatus extends SyncStatus {
|
|||
double progress() => 0.0;
|
||||
}
|
||||
|
||||
class FailedSyncStatus extends NotConnectedSyncStatus {}
|
||||
class FailedSyncStatus extends NotConnectedSyncStatus {
|
||||
String? error;
|
||||
FailedSyncStatus({this.error});
|
||||
|
||||
@override
|
||||
String toString() => error ?? super.toString();
|
||||
}
|
||||
|
||||
class ConnectingSyncStatus extends SyncStatus {
|
||||
@override
|
||||
|
|
1
cw_core/lib/unspent_coin_type.dart
Normal file
1
cw_core/lib/unspent_coin_type.dart
Normal file
|
@ -0,0 +1 @@
|
|||
enum UnspentCoinType { mweb, nonMweb, any }
|
|
@ -23,7 +23,7 @@ abstract class WalletAddresses {
|
|||
return _localAddress ?? address;
|
||||
}
|
||||
|
||||
String? get primaryAddress => null;
|
||||
String get primaryAddress => address;
|
||||
|
||||
String? _localAddress;
|
||||
|
||||
|
|
|
@ -83,7 +83,7 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
|
|||
|
||||
Future<void> rescan({required int height});
|
||||
|
||||
void close();
|
||||
Future<void> close({required bool shouldCleanup});
|
||||
|
||||
Future<void> changePassword(String password);
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ class EthereumWallet extends EVMChainWallet {
|
|||
super.initialBalance,
|
||||
super.privateKey,
|
||||
required super.encryptionFileUtils,
|
||||
super.passphrase,
|
||||
}) : super(nativeCurrency: CryptoCurrency.eth);
|
||||
|
||||
@override
|
||||
|
@ -150,8 +151,9 @@ class EthereumWallet extends EVMChainWallet {
|
|||
if (!hasKeysFile) {
|
||||
final mnemonic = data!['mnemonic'] as String?;
|
||||
final privateKey = data['private_key'] as String?;
|
||||
final passphrase = data['passphrase'] as String?;
|
||||
|
||||
keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey);
|
||||
keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey, passphrase: passphrase);
|
||||
} else {
|
||||
keysData = await WalletKeysFile.readKeysFile(
|
||||
name,
|
||||
|
@ -166,6 +168,7 @@ class EthereumWallet extends EVMChainWallet {
|
|||
password: password,
|
||||
mnemonic: keysData.mnemonic,
|
||||
privateKey: keysData.privateKey,
|
||||
passphrase: keysData.passphrase,
|
||||
initialBalance: balance,
|
||||
client: EthereumClient(),
|
||||
encryptionFileUtils: encryptionFileUtils,
|
||||
|
|
|
@ -27,6 +27,7 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
|
|||
walletInfo: credentials.walletInfo!,
|
||||
mnemonic: mnemonic,
|
||||
password: credentials.password!,
|
||||
passphrase: credentials.passphrase,
|
||||
client: client,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
@ -144,6 +145,7 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
|
|||
password: credentials.password!,
|
||||
mnemonic: credentials.mnemonic,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
passphrase: credentials.passphrase,
|
||||
client: client,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
|
|
@ -70,6 +70,7 @@ abstract class EVMChainWalletBase
|
|||
required String password,
|
||||
EVMChainERC20Balance? initialBalance,
|
||||
required this.encryptionFileUtils,
|
||||
this.passphrase,
|
||||
}) : syncStatus = const NotConnectedSyncStatus(),
|
||||
_password = password,
|
||||
_mnemonic = mnemonic,
|
||||
|
@ -178,6 +179,7 @@ abstract class EVMChainWalletBase
|
|||
mnemonic: _mnemonic,
|
||||
privateKey: _hexPrivateKey,
|
||||
password: _password,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
walletAddresses.address = _evmChainPrivateKey.address.hexEip55;
|
||||
}
|
||||
|
@ -262,7 +264,7 @@ abstract class EVMChainWalletBase
|
|||
}
|
||||
|
||||
@override
|
||||
void close() {
|
||||
Future<void> close({required bool shouldCleanup}) async {
|
||||
_client.stop();
|
||||
_transactionsUpdateTimer?.cancel();
|
||||
_updateFeesTimer?.cancel();
|
||||
|
@ -545,6 +547,7 @@ abstract class EVMChainWalletBase
|
|||
'mnemonic': _mnemonic,
|
||||
'private_key': privateKey,
|
||||
'balance': balance[currency]!.toJSON(),
|
||||
'passphrase': passphrase,
|
||||
});
|
||||
|
||||
Future<void> _updateBalance() async {
|
||||
|
@ -574,15 +577,19 @@ abstract class EVMChainWalletBase
|
|||
}
|
||||
}
|
||||
|
||||
Future<EthPrivateKey> getPrivateKey(
|
||||
{String? mnemonic, String? privateKey, required String password}) async {
|
||||
Future<EthPrivateKey> getPrivateKey({
|
||||
String? mnemonic,
|
||||
String? privateKey,
|
||||
required String password,
|
||||
String? passphrase,
|
||||
}) async {
|
||||
assert(mnemonic != null || privateKey != null);
|
||||
|
||||
if (privateKey != null) {
|
||||
return EthPrivateKey.fromHex(privateKey);
|
||||
}
|
||||
|
||||
final seed = bip39.mnemonicToSeed(mnemonic!);
|
||||
final seed = bip39.mnemonicToSeed(mnemonic!, passphrase: passphrase ?? '');
|
||||
|
||||
final root = bip32.BIP32.fromSeed(seed);
|
||||
|
||||
|
@ -716,4 +723,7 @@ abstract class EVMChainWalletBase
|
|||
|
||||
@override
|
||||
String get password => _password;
|
||||
|
||||
@override
|
||||
final String? passphrase;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,9 @@ abstract class EVMChainWalletAddressesBase extends WalletAddresses with Store {
|
|||
@observable
|
||||
String address;
|
||||
|
||||
@override
|
||||
String get primaryAddress => address;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
address = walletInfo.address;
|
||||
|
|
|
@ -4,28 +4,25 @@ import 'package:cw_core/wallet_info.dart';
|
|||
|
||||
class EVMChainNewWalletCredentials extends WalletCredentials {
|
||||
EVMChainNewWalletCredentials({
|
||||
required String name,
|
||||
WalletInfo? walletInfo,
|
||||
String? password,
|
||||
String? parentAddress,
|
||||
required super.name,
|
||||
super.walletInfo,
|
||||
super.password,
|
||||
super.parentAddress,
|
||||
this.mnemonic,
|
||||
}) : super(
|
||||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
password: password,
|
||||
parentAddress: parentAddress,
|
||||
);
|
||||
super.passphrase,
|
||||
});
|
||||
|
||||
final String? mnemonic;
|
||||
}
|
||||
|
||||
class EVMChainRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||
EVMChainRestoreWalletFromSeedCredentials({
|
||||
required String name,
|
||||
required String password,
|
||||
required super.name,
|
||||
required super.password,
|
||||
required this.mnemonic,
|
||||
WalletInfo? walletInfo,
|
||||
}) : super(name: name, password: password, walletInfo: walletInfo);
|
||||
super.walletInfo,
|
||||
super.passphrase,
|
||||
});
|
||||
|
||||
final String mnemonic;
|
||||
}
|
||||
|
|
|
@ -106,7 +106,7 @@ abstract class HavenWalletBase
|
|||
Future<void>? updateBalance() => null;
|
||||
|
||||
@override
|
||||
void close() {
|
||||
Future<void> close({required bool shouldCleanup}) async {
|
||||
_listener?.stop();
|
||||
_onAccountChangeReaction?.reaction.dispose();
|
||||
_autoSaveTimer?.cancel();
|
||||
|
|
|
@ -116,7 +116,7 @@ class HavenWalletService extends WalletService<
|
|||
|
||||
if (!isValid) {
|
||||
await restoreOrResetWalletFiles(name);
|
||||
wallet.close();
|
||||
wallet.close(shouldCleanup: false);
|
||||
return openWallet(name, password);
|
||||
}
|
||||
|
||||
|
|
|
@ -163,7 +163,7 @@ abstract class MoneroWalletBase
|
|||
Future<void>? updateBalance() => null;
|
||||
|
||||
@override
|
||||
void close() async {
|
||||
Future<void> close({required bool shouldCleanup}) async {
|
||||
_listener?.stop();
|
||||
_onAccountChangeReaction?.reaction.dispose();
|
||||
_onTxHistoryChangeReaction?.reaction.dispose();
|
||||
|
|
|
@ -29,6 +29,9 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
|
|||
@observable
|
||||
String address;
|
||||
|
||||
@override
|
||||
String get primaryAddress => getAddress(accountIndex: account?.id ?? 0, addressIndex: 0);
|
||||
|
||||
@override
|
||||
String get latestAddress {
|
||||
var addressIndex = subaddress_list.numSubaddresses(account?.id??0) - 1;
|
||||
|
|
|
@ -137,7 +137,7 @@ class MoneroWalletService extends WalletService<
|
|||
|
||||
if (!isValid) {
|
||||
await restoreOrResetWalletFiles(name);
|
||||
wallet.close();
|
||||
wallet.close(shouldCleanup: false);
|
||||
return openWallet(name, password);
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,8 @@ class CwMwebPlugin: FlutterPlugin, MethodCallHandler {
|
|||
if (call.method == "start") {
|
||||
server?.stop()
|
||||
val dataDir = call.argument("dataDir") ?: ""
|
||||
server = server ?: Mwebd.newServer("", dataDir, "")
|
||||
val nodeUri = call.argument("nodeUri") ?: ""
|
||||
server = server ?: Mwebd.newServer("", dataDir, nodeUri)
|
||||
port = server?.start(0)
|
||||
result.success(port)
|
||||
} else if (call.method == "stop") {
|
||||
|
@ -39,10 +40,17 @@ class CwMwebPlugin: FlutterPlugin, MethodCallHandler {
|
|||
port = null
|
||||
result.success(null)
|
||||
} else if (call.method == "address") {
|
||||
// val scanSecret: ByteArray = call.argument<ByteArray>("scanSecret") ?: ByteArray(0)
|
||||
// val spendPub: ByteArray = call.argument<ByteArray>("spendPub") ?: ByteArray(0)
|
||||
// val index: Int = call.argument<Int>("index") ?: 0
|
||||
// val res = Mwebd.address(scanSecret, spendPub, index)
|
||||
// result.success(res)
|
||||
} else if (call.method == "addresses") {
|
||||
val scanSecret: ByteArray = call.argument<ByteArray>("scanSecret") ?: ByteArray(0)
|
||||
val spendPub: ByteArray = call.argument<ByteArray>("spendPub") ?: ByteArray(0)
|
||||
val index: Int = call.argument<Int>("index") ?: 0
|
||||
val res = Mwebd.address(scanSecret, spendPub, index)
|
||||
val fromIndex: Int = call.argument<Int>("fromIndex") ?: 0
|
||||
val toIndex: Int = call.argument<Int>("toIndex") ?: 0
|
||||
val res = Mwebd.addresses(scanSecret, spendPub, fromIndex, toIndex)
|
||||
result.success(res)
|
||||
} else {
|
||||
result.notImplemented()
|
||||
|
|
|
@ -12,6 +12,7 @@ public static func register(with registrar: FlutterPluginRegistrar) {
|
|||
private static var server: MwebdServer?
|
||||
private static var port: Int = 0
|
||||
private static var dataDir: String?
|
||||
private static var nodeUri: String?
|
||||
|
||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
switch call.method {
|
||||
|
@ -22,22 +23,35 @@ public static func register(with registrar: FlutterPluginRegistrar) {
|
|||
stopServer()
|
||||
let args = call.arguments as? [String: String]
|
||||
let dataDir = args?["dataDir"]
|
||||
let nodeUri = args?["nodeUri"]
|
||||
CwMwebPlugin.dataDir = dataDir
|
||||
CwMwebPlugin.nodeUri = nodeUri
|
||||
startServer(result: result)
|
||||
break
|
||||
case "stop":
|
||||
stopServer()
|
||||
result(nil)
|
||||
break
|
||||
case "address":
|
||||
// case "address":
|
||||
// let args = call.arguments as! [String: Any]
|
||||
// let scanSecret = args["scanSecret"] as! FlutterStandardTypedData
|
||||
// let spendPub = args["spendPub"] as! FlutterStandardTypedData
|
||||
// let index = args["index"] as! Int32
|
||||
|
||||
// let scanSecretData = scanSecret.data
|
||||
// let spendPubData = spendPub.data
|
||||
// result(MwebdAddress(scanSecretData, spendPubData, index))
|
||||
// break
|
||||
case "addresses":
|
||||
let args = call.arguments as! [String: Any]
|
||||
let scanSecret = args["scanSecret"] as! FlutterStandardTypedData
|
||||
let spendPub = args["spendPub"] as! FlutterStandardTypedData
|
||||
let index = args["index"] as! Int32
|
||||
let fromIndex = args["fromIndex"] as! Int32
|
||||
let toIndex = args["toIndex"] as! Int32
|
||||
|
||||
let scanSecretData = scanSecret.data
|
||||
let spendPubData = spendPub.data
|
||||
result(MwebdAddress(scanSecretData, spendPubData, index))
|
||||
result(MwebdAddresses(scanSecretData, spendPubData, fromIndex, toIndex))
|
||||
break
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
|
@ -48,7 +62,7 @@ public static func register(with registrar: FlutterPluginRegistrar) {
|
|||
private func startServer(result: @escaping FlutterResult) {
|
||||
if CwMwebPlugin.server == nil {
|
||||
var error: NSError?
|
||||
CwMwebPlugin.server = MwebdNewServer("", CwMwebPlugin.dataDir, "", &error)
|
||||
CwMwebPlugin.server = MwebdNewServer("", CwMwebPlugin.dataDir, CwMwebPlugin.nodeUri, &error)
|
||||
|
||||
if let server = CwMwebPlugin.server {
|
||||
do {
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:grpc/grpc.dart';
|
||||
|
@ -10,24 +14,51 @@ class CwMweb {
|
|||
static ClientChannel? _clientChannel;
|
||||
static int? _port;
|
||||
static const TIMEOUT_DURATION = Duration(seconds: 5);
|
||||
static Timer? logTimer;
|
||||
|
||||
static void readFileWithTimer(String filePath) {
|
||||
final file = File(filePath);
|
||||
int lastLength = 0;
|
||||
|
||||
logTimer?.cancel();
|
||||
logTimer = Timer.periodic(const Duration(seconds: 1), (timer) async {
|
||||
try {
|
||||
final currentLength = await file.length();
|
||||
|
||||
if (currentLength != lastLength) {
|
||||
final fileStream = file.openRead(lastLength, currentLength);
|
||||
final newLines = await fileStream.transform(utf8.decoder).join();
|
||||
lastLength = currentLength;
|
||||
log(newLines);
|
||||
}
|
||||
} on GrpcError catch (e) {
|
||||
log('Caught grpc error: ${e.message}');
|
||||
} catch (e) {
|
||||
log('The mwebd debug log probably is not initialized yet.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static Future<void> _initializeClient() async {
|
||||
await stop();
|
||||
// wait a few seconds to make sure the server is stopped
|
||||
await Future.delayed(const Duration(seconds: 5));
|
||||
|
||||
print("_initializeClient() called!");
|
||||
final appDir = await getApplicationSupportDirectory();
|
||||
_port = await CwMwebPlatform.instance.start(appDir.path);
|
||||
const ltcNodeUri = "ltc-electrum.cakewallet.com:9333";
|
||||
|
||||
String debugLogPath = "${appDir.path}/logs/debug.log";
|
||||
readFileWithTimer(debugLogPath);
|
||||
|
||||
_port = await CwMwebPlatform.instance.start(appDir.path, ltcNodeUri);
|
||||
if (_port == null || _port == 0) {
|
||||
throw Exception("Failed to start server");
|
||||
}
|
||||
print("Attempting to connect to server on port: $_port");
|
||||
log("Attempting to connect to server on port: $_port");
|
||||
|
||||
// wait for the server to finish starting up before we try to connect to it:
|
||||
await Future.delayed(const Duration(seconds: 5));
|
||||
await Future.delayed(const Duration(seconds: 8));
|
||||
|
||||
_clientChannel = ClientChannel('127.0.0.1', port: _port!, channelShutdownHandler: () {
|
||||
print("Channel is shutting down!");
|
||||
_rpcClient = null;
|
||||
log("Channel is shutting down!");
|
||||
},
|
||||
options: const ChannelOptions(
|
||||
credentials: ChannelCredentials.insecure(),
|
||||
|
@ -48,9 +79,18 @@ class CwMweb {
|
|||
throw Exception("blockTime shouldn't be 0! (this connection is likely broken)");
|
||||
}
|
||||
return _rpcClient!;
|
||||
} catch (e) {
|
||||
print("Attempt $i failed: $e");
|
||||
} on GrpcError catch (e) {
|
||||
log("Attempt $i failed: $e");
|
||||
log('Caught grpc error: ${e.message}');
|
||||
_rpcClient = null;
|
||||
// necessary if the database isn't open:
|
||||
await stop();
|
||||
await Future.delayed(const Duration(seconds: 3));
|
||||
} catch (e) {
|
||||
log("Attempt $i failed: $e");
|
||||
_rpcClient = null;
|
||||
await stop();
|
||||
await Future.delayed(const Duration(seconds: 3));
|
||||
}
|
||||
}
|
||||
throw Exception("Failed to connect after $maxRetries attempts");
|
||||
|
@ -60,22 +100,43 @@ class CwMweb {
|
|||
try {
|
||||
await CwMwebPlatform.instance.stop();
|
||||
await cleanup();
|
||||
} on GrpcError catch (e) {
|
||||
log('Caught grpc error: ${e.message}');
|
||||
} catch (e) {
|
||||
print("Error stopping server: $e");
|
||||
log("Error stopping server: $e");
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String?> address(Uint8List scanSecret, Uint8List spendPub, int index) async {
|
||||
try {
|
||||
return CwMwebPlatform.instance.address(scanSecret, spendPub, index);
|
||||
return (await CwMwebPlatform.instance.addresses(scanSecret, spendPub, index, index + 1))
|
||||
?.split(',')
|
||||
.first;
|
||||
} on GrpcError catch (e) {
|
||||
log('Caught grpc error: ${e.message}');
|
||||
} catch (e) {
|
||||
print("Error getting address: $e");
|
||||
return null;
|
||||
log("Error getting address: $e");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static Future<List<String>?> addresses(
|
||||
Uint8List scanSecret, Uint8List spendPub, int fromIndex, int toIndex) async {
|
||||
try {
|
||||
return (await CwMwebPlatform.instance.addresses(scanSecret, spendPub, fromIndex, toIndex))
|
||||
?.split(',');
|
||||
} on GrpcError catch (e) {
|
||||
log('Caught grpc error: ${e.message}');
|
||||
} catch (e) {
|
||||
log("Error getting addresses: $e");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static Future<void> cleanup() async {
|
||||
await _clientChannel?.terminate();
|
||||
try {
|
||||
await _clientChannel?.terminate();
|
||||
} catch (_) {}
|
||||
_rpcClient = null;
|
||||
_clientChannel = null;
|
||||
_port = null;
|
||||
|
@ -83,51 +144,57 @@ class CwMweb {
|
|||
|
||||
// wrappers that handle the connection issues:
|
||||
static Future<SpentResponse> spent(SpentRequest request) async {
|
||||
log("mweb.spent() called");
|
||||
try {
|
||||
if (_rpcClient == null) {
|
||||
await _initializeClient();
|
||||
}
|
||||
_rpcClient = await stub();
|
||||
return await _rpcClient!.spent(request, options: CallOptions(timeout: TIMEOUT_DURATION));
|
||||
} on GrpcError catch (e) {
|
||||
log('Caught grpc error: ${e.message}');
|
||||
} catch (e) {
|
||||
print("Error getting spent: $e");
|
||||
return SpentResponse();
|
||||
log("Error getting spent: $e");
|
||||
}
|
||||
return SpentResponse();
|
||||
}
|
||||
|
||||
static Future<StatusResponse> status(StatusRequest request) async {
|
||||
log("mweb.status() called");
|
||||
try {
|
||||
if (_rpcClient == null) {
|
||||
await _initializeClient();
|
||||
}
|
||||
_rpcClient = await stub();
|
||||
return await _rpcClient!.status(request, options: CallOptions(timeout: TIMEOUT_DURATION));
|
||||
} on GrpcError catch (e) {
|
||||
log('Caught grpc error: ${e.message}');
|
||||
} catch (e) {
|
||||
print("Error getting status: $e");
|
||||
return StatusResponse();
|
||||
log("Error getting status: $e");
|
||||
}
|
||||
return StatusResponse();
|
||||
}
|
||||
|
||||
static Future<CreateResponse> create(CreateRequest request) async {
|
||||
log("mweb.create() called");
|
||||
try {
|
||||
if (_rpcClient == null) {
|
||||
await _initializeClient();
|
||||
}
|
||||
_rpcClient = await stub();
|
||||
return await _rpcClient!.create(request, options: CallOptions(timeout: TIMEOUT_DURATION));
|
||||
} on GrpcError catch (e) {
|
||||
log('Caught grpc error: ${e.message}');
|
||||
} catch (e) {
|
||||
print("Error getting create: $e");
|
||||
return CreateResponse();
|
||||
log("Error getting create: $e");
|
||||
}
|
||||
return CreateResponse();
|
||||
}
|
||||
|
||||
static Future<ResponseStream<Utxo>?> utxos(UtxosRequest request) async {
|
||||
log("mweb.utxos() called");
|
||||
try {
|
||||
if (_rpcClient == null) {
|
||||
await _initializeClient();
|
||||
}
|
||||
// this is a stream, so we should have an effectively infinite timeout:
|
||||
return _rpcClient!.utxos(request, options: CallOptions(timeout: const Duration(days: 1000 * 365)));
|
||||
_rpcClient = await stub();
|
||||
final resp = _rpcClient!
|
||||
.utxos(request, options: CallOptions(timeout: const Duration(days: 1000 * 365)));
|
||||
log("got utxo stream");
|
||||
return resp;
|
||||
} on GrpcError catch (e) {
|
||||
log('Caught grpc error: ${e.message}');
|
||||
} catch (e) {
|
||||
print("Error getting utxos: $e");
|
||||
return null;
|
||||
log("Error getting utxos: $e");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:io' show Platform;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
|
@ -10,18 +12,28 @@ class MethodChannelCwMweb extends CwMwebPlatform {
|
|||
final methodChannel = const MethodChannel('cw_mweb');
|
||||
|
||||
@override
|
||||
Future<int?> start(String dataDir) async {
|
||||
final result = await methodChannel.invokeMethod<int>('start', {'dataDir': dataDir});
|
||||
Future<int?> start(String dataDir, String nodeUri) async {
|
||||
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
|
||||
return null;
|
||||
}
|
||||
final result =
|
||||
await methodChannel.invokeMethod<int>('start', {'dataDir': dataDir, 'nodeUri': nodeUri});
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> stop() async {
|
||||
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
|
||||
return;
|
||||
}
|
||||
await methodChannel.invokeMethod<void>('stop');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> address(Uint8List scanSecret, Uint8List spendPub, int index) async {
|
||||
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
|
||||
return null;
|
||||
}
|
||||
final result = await methodChannel.invokeMethod<String>('address', {
|
||||
'scanSecret': scanSecret,
|
||||
'spendPub': spendPub,
|
||||
|
@ -29,4 +41,18 @@ class MethodChannelCwMweb extends CwMwebPlatform {
|
|||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> addresses(Uint8List scanSecret, Uint8List spendPub, int fromIndex, int toIndex) async {
|
||||
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
|
||||
return null;
|
||||
}
|
||||
final result = await methodChannel.invokeMethod<String>('addresses', {
|
||||
'scanSecret': scanSecret,
|
||||
'spendPub': spendPub,
|
||||
'fromIndex': fromIndex,
|
||||
'toIndex': toIndex,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ abstract class CwMwebPlatform extends PlatformInterface {
|
|||
_instance = instance;
|
||||
}
|
||||
|
||||
Future<int?> start(String dataDir) {
|
||||
Future<int?> start(String dataDir, String nodeUri) {
|
||||
throw UnimplementedError('start() has not been implemented.');
|
||||
}
|
||||
|
||||
|
@ -36,4 +36,8 @@ abstract class CwMwebPlatform extends PlatformInterface {
|
|||
Future<String?> address(Uint8List scanSecret, Uint8List spendPub, int index) {
|
||||
throw UnimplementedError('address(int) has not been implemented.');
|
||||
}
|
||||
|
||||
Future<String?> addresses(Uint8List scanSecret, Uint8List spendPub, int fromIndex, int toIndex) {
|
||||
throw UnimplementedError('addresses has not been implemented.');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ abstract class NanoWalletBase
|
|||
required String password,
|
||||
NanoBalance? initialBalance,
|
||||
required EncryptionFileUtils encryptionFileUtils,
|
||||
this.passphrase,
|
||||
}) : syncStatus = NotConnectedSyncStatus(),
|
||||
_password = password,
|
||||
_mnemonic = mnemonic,
|
||||
|
@ -148,7 +149,7 @@ abstract class NanoWalletBase
|
|||
Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
|
||||
|
||||
@override
|
||||
void close() {
|
||||
Future<void> close({required bool shouldCleanup}) async {
|
||||
_client.stop();
|
||||
_receiveTimer?.cancel();
|
||||
}
|
||||
|
@ -548,4 +549,7 @@ abstract class NanoWalletBase
|
|||
}
|
||||
return await NanoSignatures.verifyMessage(message, signature, address);
|
||||
}
|
||||
|
||||
@override
|
||||
final String? passphrase;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,9 @@ abstract class NanoWalletAddressesBase extends WalletAddresses with Store {
|
|||
@observable
|
||||
String address;
|
||||
|
||||
@override
|
||||
String get primaryAddress => address;
|
||||
|
||||
@observable
|
||||
NanoAccount? account;
|
||||
|
||||
|
|
|
@ -9,13 +9,15 @@ class NanoNewWalletCredentials extends WalletCredentials {
|
|||
DerivationType? derivationType,
|
||||
this.mnemonic,
|
||||
String? parentAddress,
|
||||
String? passphrase,
|
||||
}) : super(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
parentAddress: parentAddress,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
|
||||
final String? mnemonic;
|
||||
}
|
||||
|
||||
|
@ -25,10 +27,12 @@ class NanoRestoreWalletFromSeedCredentials extends WalletCredentials {
|
|||
required this.mnemonic,
|
||||
String? password,
|
||||
required DerivationType derivationType,
|
||||
String? passphrase,
|
||||
}) : super(
|
||||
name: name,
|
||||
password: password,
|
||||
derivationInfo: DerivationInfo(derivationType: derivationType),
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
final String mnemonic;
|
||||
|
|
|
@ -117,10 +117,10 @@ packages:
|
|||
dependency: "direct overridden"
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292"
|
||||
sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.2.7"
|
||||
version: "7.2.7+1"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -27,6 +27,7 @@ class PolygonWallet extends EVMChainWallet {
|
|||
super.privateKey,
|
||||
required super.client,
|
||||
required super.encryptionFileUtils,
|
||||
super.passphrase,
|
||||
}) : super(nativeCurrency: CryptoCurrency.maticpoly);
|
||||
|
||||
@override
|
||||
|
@ -128,8 +129,9 @@ class PolygonWallet extends EVMChainWallet {
|
|||
if (!hasKeysFile) {
|
||||
final mnemonic = data!['mnemonic'] as String?;
|
||||
final privateKey = data['private_key'] as String?;
|
||||
final passphrase = data['passphrase'] as String?;
|
||||
|
||||
keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey);
|
||||
keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey, passphrase: passphrase);
|
||||
} else {
|
||||
keysData = await WalletKeysFile.readKeysFile(
|
||||
name,
|
||||
|
@ -144,6 +146,7 @@ class PolygonWallet extends EVMChainWallet {
|
|||
password: password,
|
||||
mnemonic: keysData.mnemonic,
|
||||
privateKey: keysData.privateKey,
|
||||
passphrase: keysData.passphrase,
|
||||
initialBalance: balance,
|
||||
client: PolygonClient(),
|
||||
encryptionFileUtils: encryptionFileUtils,
|
||||
|
|
|
@ -30,6 +30,7 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
|
|||
walletInfo: credentials.walletInfo!,
|
||||
mnemonic: mnemonic,
|
||||
password: credentials.password!,
|
||||
passphrase: credentials.passphrase,
|
||||
client: client,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
@ -125,6 +126,7 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
|
|||
password: credentials.password!,
|
||||
mnemonic: credentials.mnemonic,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
passphrase: credentials.passphrase,
|
||||
client: client,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
|
|
@ -33,7 +33,6 @@ import 'package:solana/base58.dart';
|
|||
import 'package:solana/metaplex.dart' as metaplex;
|
||||
import 'package:solana/solana.dart';
|
||||
import 'package:solana/src/crypto/ed25519_hd_keypair.dart';
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
|
||||
part 'solana_wallet.g.dart';
|
||||
|
||||
|
@ -49,6 +48,7 @@ abstract class SolanaWalletBase
|
|||
required String password,
|
||||
SolanaBalance? initialBalance,
|
||||
required this.encryptionFileUtils,
|
||||
this.passphrase,
|
||||
}) : syncStatus = const NotConnectedSyncStatus(),
|
||||
_password = password,
|
||||
_mnemonic = mnemonic,
|
||||
|
@ -179,7 +179,7 @@ abstract class SolanaWalletBase
|
|||
Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
|
||||
|
||||
@override
|
||||
void close() {
|
||||
Future<void> close({required bool shouldCleanup}) async {
|
||||
_client.stop();
|
||||
_transactionsUpdateTimer?.cancel();
|
||||
}
|
||||
|
@ -632,4 +632,7 @@ abstract class SolanaWalletBase
|
|||
|
||||
@override
|
||||
String get password => _password;
|
||||
|
||||
@override
|
||||
final String? passphrase;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,9 @@ abstract class SolanaWalletAddressesBase extends WalletAddresses with Store {
|
|||
@override
|
||||
String address;
|
||||
|
||||
@override
|
||||
String get primaryAddress => address;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
address = walletInfo.address;
|
||||
|
|
|
@ -8,22 +8,30 @@ class SolanaNewWalletCredentials extends WalletCredentials {
|
|||
String? password,
|
||||
String? parentAddress,
|
||||
this.mnemonic,
|
||||
String? passphrase,
|
||||
}) : super(
|
||||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
password: password,
|
||||
parentAddress: parentAddress,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
final String? mnemonic;
|
||||
}
|
||||
|
||||
class SolanaRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||
SolanaRestoreWalletFromSeedCredentials(
|
||||
{required String name,
|
||||
required String password,
|
||||
required this.mnemonic,
|
||||
WalletInfo? walletInfo})
|
||||
: super(name: name, password: password, walletInfo: walletInfo);
|
||||
SolanaRestoreWalletFromSeedCredentials({
|
||||
required String name,
|
||||
required String password,
|
||||
required this.mnemonic,
|
||||
WalletInfo? walletInfo,
|
||||
String? passphrase,
|
||||
}) : super(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
final String mnemonic;
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ abstract class TronWalletBase
|
|||
required String password,
|
||||
TronBalance? initialBalance,
|
||||
required this.encryptionFileUtils,
|
||||
this.passphrase,
|
||||
}) : syncStatus = const NotConnectedSyncStatus(),
|
||||
_password = password,
|
||||
_mnemonic = mnemonic,
|
||||
|
@ -113,6 +114,7 @@ abstract class TronWalletBase
|
|||
mnemonic: _mnemonic,
|
||||
privateKey: _hexPrivateKey,
|
||||
password: _password,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
_tronPublicKey = _tronPrivateKey.publicKey();
|
||||
|
@ -149,8 +151,9 @@ abstract class TronWalletBase
|
|||
if (!hasKeysFile) {
|
||||
final mnemonic = data!['mnemonic'] as String?;
|
||||
final privateKey = data['private_key'] as String?;
|
||||
final passphrase = data['passphrase'] as String?;
|
||||
|
||||
keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey);
|
||||
keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey, passphrase: passphrase);
|
||||
} else {
|
||||
keysData = await WalletKeysFile.readKeysFile(
|
||||
name,
|
||||
|
@ -165,6 +168,7 @@ abstract class TronWalletBase
|
|||
password: password,
|
||||
mnemonic: keysData.mnemonic,
|
||||
privateKey: keysData.privateKey,
|
||||
passphrase: keysData.passphrase,
|
||||
initialBalance: balance,
|
||||
encryptionFileUtils: encryptionFileUtils,
|
||||
);
|
||||
|
@ -190,12 +194,13 @@ abstract class TronWalletBase
|
|||
String? mnemonic,
|
||||
String? privateKey,
|
||||
required String password,
|
||||
String? passphrase,
|
||||
}) async {
|
||||
assert(mnemonic != null || privateKey != null);
|
||||
|
||||
if (privateKey != null) return TronPrivateKey(privateKey);
|
||||
|
||||
final seed = bip39.mnemonicToSeed(mnemonic!);
|
||||
final seed = bip39.mnemonicToSeed(mnemonic!, passphrase: passphrase ?? '');
|
||||
|
||||
// Derive a TRON private key from the seed
|
||||
final bip44 = Bip44.fromSeed(seed, Bip44Coins.tron);
|
||||
|
@ -212,7 +217,7 @@ abstract class TronWalletBase
|
|||
Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
|
||||
|
||||
@override
|
||||
void close() => _transactionsUpdateTimer?.cancel();
|
||||
Future<void> close({required bool shouldCleanup}) async => _transactionsUpdateTimer?.cancel();
|
||||
|
||||
@action
|
||||
@override
|
||||
|
@ -463,6 +468,7 @@ abstract class TronWalletBase
|
|||
'mnemonic': _mnemonic,
|
||||
'private_key': privateKey,
|
||||
'balance': balance[currency]!.toJSON(),
|
||||
'passphrase': passphrase,
|
||||
});
|
||||
|
||||
Future<void> _updateBalance() async {
|
||||
|
@ -607,4 +613,7 @@ abstract class TronWalletBase
|
|||
|
||||
@override
|
||||
String get password => _password;
|
||||
|
||||
@override
|
||||
final String? passphrase;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,9 @@ abstract class TronWalletAddressesBase extends WalletAddresses with Store {
|
|||
@observable
|
||||
String address;
|
||||
|
||||
@override
|
||||
String get primaryAddress => address;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
address = walletInfo.address;
|
||||
|
|
|
@ -8,23 +8,31 @@ class TronNewWalletCredentials extends WalletCredentials {
|
|||
String? password,
|
||||
this.mnemonic,
|
||||
String? parentAddress,
|
||||
String? passphrase,
|
||||
}) : super(
|
||||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
password: password,
|
||||
parentAddress: parentAddress,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
final String? mnemonic;
|
||||
}
|
||||
|
||||
class TronRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||
TronRestoreWalletFromSeedCredentials(
|
||||
{required String name,
|
||||
required String password,
|
||||
required this.mnemonic,
|
||||
WalletInfo? walletInfo})
|
||||
: super(name: name, password: password, walletInfo: walletInfo);
|
||||
TronRestoreWalletFromSeedCredentials({
|
||||
required String name,
|
||||
required String password,
|
||||
required this.mnemonic,
|
||||
WalletInfo? walletInfo,
|
||||
String? passphrase,
|
||||
}) : super(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
final String mnemonic;
|
||||
}
|
||||
|
|
|
@ -33,10 +33,7 @@ class TronWalletService extends WalletService<
|
|||
WalletType getType() => WalletType.tron;
|
||||
|
||||
@override
|
||||
Future<TronWallet> create(
|
||||
TronNewWalletCredentials credentials, {
|
||||
bool? isTestnet,
|
||||
}) async {
|
||||
Future<TronWallet> create(TronNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
||||
|
||||
final mnemonic = credentials.mnemonic ?? bip39.generateMnemonic(strength: strength);
|
||||
|
@ -45,6 +42,7 @@ class TronWalletService extends WalletService<
|
|||
walletInfo: credentials.walletInfo!,
|
||||
mnemonic: mnemonic,
|
||||
password: credentials.password!,
|
||||
passphrase: credentials.passphrase,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
|
@ -120,6 +118,7 @@ class TronWalletService extends WalletService<
|
|||
password: credentials.password!,
|
||||
mnemonic: credentials.mnemonic,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
passphrase: credentials.passphrase,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
|
|
|
@ -160,7 +160,7 @@ abstract class WowneroWalletBase
|
|||
Future<void>? updateBalance() => null;
|
||||
|
||||
@override
|
||||
void close() async {
|
||||
Future<void> close({required bool shouldCleanup}) async {
|
||||
_listener?.stop();
|
||||
_onAccountChangeReaction?.reaction.dispose();
|
||||
_onTxHistoryChangeReaction?.reaction.dispose();
|
||||
|
|
|
@ -29,6 +29,9 @@ abstract class WowneroWalletAddressesBase extends WalletAddresses with Store {
|
|||
@observable
|
||||
String address;
|
||||
|
||||
@override
|
||||
String get primaryAddress => getAddress(accountIndex: account?.id ?? 0, addressIndex: 0);
|
||||
|
||||
@override
|
||||
String get latestAddress {
|
||||
var addressIndex = subaddress_list.numSubaddresses(account?.id??0) - 1;
|
||||
|
|
|
@ -134,7 +134,7 @@ class WowneroWalletService extends WalletService<
|
|||
|
||||
if (!isValid) {
|
||||
await restoreOrResetWalletFiles(name);
|
||||
wallet.close();
|
||||
wallet.close(shouldCleanup: false);
|
||||
return openWallet(name, password);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,38 @@ PODS:
|
|||
- Flutter
|
||||
- ReachabilitySwift
|
||||
- CryptoSwift (1.8.2)
|
||||
- cw_haven (0.0.1):
|
||||
- cw_haven/Boost (= 0.0.1)
|
||||
- cw_haven/Haven (= 0.0.1)
|
||||
- cw_haven/OpenSSL (= 0.0.1)
|
||||
- cw_haven/Sodium (= 0.0.1)
|
||||
- cw_shared_external
|
||||
- Flutter
|
||||
- cw_haven/Boost (0.0.1):
|
||||
- cw_shared_external
|
||||
- Flutter
|
||||
- cw_haven/Haven (0.0.1):
|
||||
- cw_shared_external
|
||||
- Flutter
|
||||
- cw_haven/OpenSSL (0.0.1):
|
||||
- cw_shared_external
|
||||
- Flutter
|
||||
- cw_haven/Sodium (0.0.1):
|
||||
- cw_shared_external
|
||||
- Flutter
|
||||
- cw_mweb (0.0.1):
|
||||
- Flutter
|
||||
- cw_shared_external (0.0.1):
|
||||
- cw_shared_external/Boost (= 0.0.1)
|
||||
- cw_shared_external/OpenSSL (= 0.0.1)
|
||||
- cw_shared_external/Sodium (= 0.0.1)
|
||||
- Flutter
|
||||
- cw_shared_external/Boost (0.0.1):
|
||||
- Flutter
|
||||
- cw_shared_external/OpenSSL (0.0.1):
|
||||
- Flutter
|
||||
- cw_shared_external/Sodium (0.0.1):
|
||||
- Flutter
|
||||
- device_display_brightness (0.0.1):
|
||||
- Flutter
|
||||
- device_info_plus (0.0.1):
|
||||
|
@ -96,7 +127,7 @@ PODS:
|
|||
- FlutterMacOS
|
||||
- sp_scanner (0.0.1):
|
||||
- Flutter
|
||||
- SwiftProtobuf (1.26.0)
|
||||
- SwiftProtobuf (1.27.1)
|
||||
- SwiftyGif (5.4.5)
|
||||
- Toast (4.1.1)
|
||||
- uni_links (0.0.1):
|
||||
|
@ -112,7 +143,9 @@ DEPENDENCIES:
|
|||
- barcode_scan2 (from `.symlinks/plugins/barcode_scan2/ios`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- CryptoSwift
|
||||
- cw_haven (from `.symlinks/plugins/cw_haven/ios`)
|
||||
- cw_mweb (from `.symlinks/plugins/cw_mweb/ios`)
|
||||
- cw_shared_external (from `.symlinks/plugins/cw_shared_external/ios`)
|
||||
- device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- devicelocale (from `.symlinks/plugins/devicelocale/ios`)
|
||||
|
@ -125,7 +158,6 @@ DEPENDENCIES:
|
|||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||
- in_app_review (from `.symlinks/plugins/in_app_review/ios`)
|
||||
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||
- package_info (from `.symlinks/plugins/package_info/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
|
@ -158,8 +190,12 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/barcode_scan2/ios"
|
||||
connectivity_plus:
|
||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
cw_haven:
|
||||
:path: ".symlinks/plugins/cw_haven/ios"
|
||||
cw_mweb:
|
||||
:path: ".symlinks/plugins/cw_mweb/ios"
|
||||
cw_shared_external:
|
||||
:path: ".symlinks/plugins/cw_shared_external/ios"
|
||||
device_display_brightness:
|
||||
:path: ".symlinks/plugins/device_display_brightness/ios"
|
||||
device_info_plus:
|
||||
|
@ -184,8 +220,6 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/in_app_review/ios"
|
||||
integration_test:
|
||||
:path: ".symlinks/plugins/integration_test/ios"
|
||||
package_info:
|
||||
:path: ".symlinks/plugins/package_info/ios"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
|
@ -215,7 +249,9 @@ SPEC CHECKSUMS:
|
|||
barcode_scan2: 0af2bb63c81b4565aab6cd78278e4c0fa136dbb0
|
||||
connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
|
||||
CryptoSwift: c63a805d8bb5e5538e88af4e44bb537776af11ea
|
||||
cw_haven: b3e54e1fbe7b8e6fda57a93206bc38f8e89b898a
|
||||
cw_mweb: 87af74f9659fed0c1a2cbfb44413f1070e79e3ae
|
||||
cw_shared_external: 2972d872b8917603478117c9957dfca611845a92
|
||||
device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7
|
||||
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
|
||||
devicelocale: b22617f40038496deffba44747101255cee005b0
|
||||
|
@ -243,7 +279,7 @@ SPEC CHECKSUMS:
|
|||
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
sp_scanner: eaa617fa827396b967116b7f1f43549ca62e9a12
|
||||
SwiftProtobuf: 5e8349171e7c2f88f5b9e683cb3cb79d1dc780b3
|
||||
SwiftProtobuf: b109bd17979d7993a84da14b1e1fdd8b0ded934a
|
||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||
Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e
|
||||
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
|
||||
|
|
|
@ -14,12 +14,12 @@
|
|||
2193F104374FA2746CE8945B /* ResourceHelper.swift in Resources */ = {isa = PBXBuildFile; fileRef = 78D25C60B94E9D9E48D52E5E /* ResourceHelper.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
495FEFF9B395392FED3425DE /* TaskProtocol.swift in Resources */ = {isa = PBXBuildFile; fileRef = 0F42D8065219E0653321EE2B /* TaskProtocol.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
|
||||
4DFD1BB54A3A50573E19A583 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C663361C56EBB242598F609 /* Pods_Runner.framework */; };
|
||||
525A2200C6C2A43EDC5C8FC5 /* BreezSDKConnector.swift in Resources */ = {isa = PBXBuildFile; fileRef = 1FB06A93B13D606F06B3924D /* BreezSDKConnector.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
|
||||
6909E1D79C9986ADF2DE41E9 /* LnurlPayInvoice.swift in Resources */ = {isa = PBXBuildFile; fileRef = DCEA540E3586164FB47AD13E /* LnurlPayInvoice.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
|
||||
724FDA327BF191BC29DCAA2E /* Constants.swift in Resources */ = {isa = PBXBuildFile; fileRef = 0CCA7ADAD6FF9185EBBB2BCA /* Constants.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
|
||||
73138617307FA4F838D21D62 /* ServiceLogger.swift in Resources */ = {isa = PBXBuildFile; fileRef = F42258C3697CFE3C8C8D1933 /* ServiceLogger.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
8B1F4FCAA5EB9F3A83D32D5F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D7CD6B6020744E8FA471915D /* Pods_Runner.framework */; };
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
|
@ -50,6 +50,7 @@
|
|||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
014D7E4DBCFD76DDE652A4D9 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
0C400E0F25B21ABB0025E469 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
||||
0C44A7192518EF8000B570ED /* decrypt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = decrypt.swift; sourceTree = "<group>"; };
|
||||
0C50DFB82BF3CB56002B0EB3 /* MoneroWallet.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = MoneroWallet.framework; sourceTree = "<group>"; };
|
||||
|
@ -57,13 +58,11 @@
|
|||
0C9D68C8264854B60011B691 /* secRandom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = secRandom.swift; sourceTree = "<group>"; };
|
||||
0CCA7ADAD6FF9185EBBB2BCA /* Constants.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Constants.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/Constants.swift"; sourceTree = "<group>"; };
|
||||
0F42D8065219E0653321EE2B /* TaskProtocol.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TaskProtocol.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/TaskProtocol.swift"; sourceTree = "<group>"; };
|
||||
11F9FC13F9EE2A705B213FA9 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
1F083F2041D1F553F2AF8B62 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
1FB06A93B13D606F06B3924D /* BreezSDKConnector.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BreezSDKConnector.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/BreezSDKConnector.swift"; sourceTree = "<group>"; };
|
||||
28F61114229803070973270D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
3C663361C56EBB242598F609 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
41102141140E57B1DC27FBA1 /* SDKNotificationService.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SDKNotificationService.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/SDKNotificationService.swift"; sourceTree = "<group>"; };
|
||||
58C22CBD8C22B9D6023D59F8 /* LnurlPayInfo.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LnurlPayInfo.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/Task/LnurlPayInfo.swift"; sourceTree = "<group>"; };
|
||||
5AFFEBFC279AD49C00F906A4 /* wakeLock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = wakeLock.swift; sourceTree = "<group>"; };
|
||||
|
@ -83,11 +82,12 @@
|
|||
9D2F2C9F2555316C95EE7EA3 /* RedeemSwap.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RedeemSwap.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/Task/RedeemSwap.swift"; sourceTree = "<group>"; };
|
||||
9F46EE5D2BC11178009318F5 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
ABD6FCBB0F4244B090459128 /* BreezSDK.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BreezSDK.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/BreezSDK.swift"; sourceTree = "<group>"; };
|
||||
AD0937B0140D5A4C24E73BEA /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
B3D5E78267F5F18D882FDC3B /* ServiceConfig.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ServiceConfig.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/ServiceConfig.swift"; sourceTree = "<group>"; };
|
||||
C58D93382C00FAC6004BCF69 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; };
|
||||
CE291CFD2C15DB9A00B9F709 /* WowneroWallet.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = WowneroWallet.framework; sourceTree = "<group>"; };
|
||||
CEAFE49D2C539250009FF3AD /* Mwebd.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Mwebd.xcframework; sourceTree = "<group>"; };
|
||||
D139E30AEB36740C21C00A9E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
D7CD6B6020744E8FA471915D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DCEA540E3586164FB47AD13E /* LnurlPayInvoice.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LnurlPayInvoice.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/Task/LnurlPayInvoice.swift"; sourceTree = "<group>"; };
|
||||
F42258C3697CFE3C8C8D1933 /* ServiceLogger.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ServiceLogger.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/ServiceLogger.swift"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
@ -97,8 +97,8 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4DFD1BB54A3A50573E19A583 /* Pods_Runner.framework in Frameworks */,
|
||||
CEAFE4A02C53926F009FF3AD /* libresolv.tbd in Frameworks */,
|
||||
8B1F4FCAA5EB9F3A83D32D5F /* Pods_Runner.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -111,7 +111,7 @@
|
|||
CEAFE49D2C539250009FF3AD /* Mwebd.xcframework */,
|
||||
C58D93382C00FAC6004BCF69 /* libresolv.tbd */,
|
||||
0C9986A3251A932F00D566FD /* CryptoSwift.framework */,
|
||||
3C663361C56EBB242598F609 /* Pods_Runner.framework */,
|
||||
D7CD6B6020744E8FA471915D /* Pods_Runner.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
|
@ -137,10 +137,10 @@
|
|||
84389F1A05D5860790D82820 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
11F9FC13F9EE2A705B213FA9 /* Pods-Runner.debug.xcconfig */,
|
||||
1F083F2041D1F553F2AF8B62 /* Pods-Runner.release.xcconfig */,
|
||||
AD0937B0140D5A4C24E73BEA /* Pods-Runner.profile.xcconfig */,
|
||||
0B80439B9064C9708DDB0ADA /* breez_sdk-OnDemandResources */,
|
||||
014D7E4DBCFD76DDE652A4D9 /* Pods-Runner.debug.xcconfig */,
|
||||
28F61114229803070973270D /* Pods-Runner.release.xcconfig */,
|
||||
D139E30AEB36740C21C00A9E /* Pods-Runner.profile.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
|
@ -222,14 +222,14 @@
|
|||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
B91154210ADCED81FBF06A85 /* [CP] Check Pods Manifest.lock */,
|
||||
11278EDF4D5DB437B3FDB787 /* [CP] Check Pods Manifest.lock */,
|
||||
CE5E8A222BEE19C700608EA1 /* CopyFiles */,
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
32D0076A9969C0C38D68AF62 /* [CP] Embed Pods Frameworks */,
|
||||
F6F67323547956BC4F7B67F1 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
|
@ -305,21 +305,26 @@
|
|||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
32D0076A9969C0C38D68AF62 /* [CP] Embed Pods Frameworks */ = {
|
||||
11278EDF4D5DB437B3FDB787 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
|
@ -353,26 +358,21 @@
|
|||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n";
|
||||
};
|
||||
B91154210ADCED81FBF06A85 /* [CP] Check Pods Manifest.lock */ = {
|
||||
F6F67323547956BC4F7B67F1 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
|
|
@ -106,34 +106,33 @@ class CWBitcoin extends Bitcoin {
|
|||
}
|
||||
|
||||
@override
|
||||
Object createBitcoinTransactionCredentials(List<Output> outputs,
|
||||
{required TransactionPriority priority, int? feeRate}) {
|
||||
Object createBitcoinTransactionCredentials(
|
||||
List<Output> outputs, {
|
||||
required TransactionPriority priority,
|
||||
int? feeRate,
|
||||
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
|
||||
}) {
|
||||
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,
|
||||
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Object createBitcoinTransactionCredentialsRaw(List<OutputInfo> outputs,
|
||||
{TransactionPriority? priority, required int feeRate}) =>
|
||||
BitcoinTransactionCredentials(outputs,
|
||||
priority: priority != null ? priority as BitcoinTransactionPriority : null,
|
||||
feeRate: feeRate);
|
||||
|
||||
@override
|
||||
@computed
|
||||
List<ElectrumSubAddress> getSubAddresses(Object wallet) {
|
||||
|
@ -205,9 +204,19 @@ class CWBitcoin extends Bitcoin {
|
|||
(priority as BitcoinTransactionPriority).labelWithRate(rate, customRate);
|
||||
|
||||
@override
|
||||
List<BitcoinUnspent> getUnspents(Object wallet) {
|
||||
List<BitcoinUnspent> getUnspents(Object wallet,
|
||||
{UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any}) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return bitcoinWallet.unspentCoins;
|
||||
return bitcoinWallet.unspentCoins.where((element) {
|
||||
switch (coinTypeToSpendFrom) {
|
||||
case UnspentCoinType.mweb:
|
||||
return element.bitcoinAddressRecord.type == SegwitAddresType.mweb;
|
||||
case UnspentCoinType.nonMweb:
|
||||
return element.bitcoinAddressRecord.type != SegwitAddresType.mweb;
|
||||
case UnspentCoinType.any:
|
||||
return true;
|
||||
}
|
||||
}).toList();
|
||||
}
|
||||
|
||||
Future<void> updateUnspents(Object wallet) async {
|
||||
|
@ -262,7 +271,14 @@ class CWBitcoin extends Bitcoin {
|
|||
List<ReceivePageOption> getBitcoinReceivePageOptions() => BitcoinReceivePageOption.all;
|
||||
|
||||
@override
|
||||
List<ReceivePageOption> getLitecoinReceivePageOptions() => BitcoinReceivePageOption.allLitecoin;
|
||||
List<ReceivePageOption> getLitecoinReceivePageOptions() {
|
||||
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
|
||||
return BitcoinReceivePageOption.allLitecoin
|
||||
.where((element) => element != BitcoinReceivePageOption.mweb)
|
||||
.toList();
|
||||
}
|
||||
return BitcoinReceivePageOption.allLitecoin;
|
||||
}
|
||||
|
||||
@override
|
||||
BitcoinAddressType getBitcoinAddressType(ReceivePageOption option) {
|
||||
|
@ -382,19 +398,21 @@ class CWBitcoin extends Bitcoin {
|
|||
final history = await electrumClient.getHistory(sh);
|
||||
|
||||
final balance = await electrumClient.getBalance(sh);
|
||||
dInfoCopy.balance = balance.entries.first.value.toString();
|
||||
dInfoCopy.balance = balance.entries.firstOrNull?.value.toString() ?? "0";
|
||||
dInfoCopy.address = address;
|
||||
dInfoCopy.transactionsCount = history.length;
|
||||
|
||||
list.add(dInfoCopy);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
} catch (e, s) {
|
||||
print("derivationInfoError: $e");
|
||||
print("derivationInfoStack: $s");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sort the list such that derivations with the most transactions are first:
|
||||
list.sort((a, b) => b.transactionsCount.compareTo(a.transactionsCount));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
@ -620,7 +638,7 @@ class CWBitcoin extends Bitcoin {
|
|||
|
||||
final updatedOutputs = outputs.map((output) {
|
||||
try {
|
||||
final pendingOut = pendingTx!.outputs[outputs.indexOf(output)];
|
||||
final pendingOut = pendingTx.outputs[outputs.indexOf(output)];
|
||||
final updatedOutput = output;
|
||||
|
||||
updatedOutput.stealthAddress = P2trAddress.fromScriptPubkey(script: pendingOut.scriptPubKey)
|
||||
|
@ -666,4 +684,26 @@ class CWBitcoin extends Bitcoin {
|
|||
// TODO: this could be improved:
|
||||
return inputAddressesContainMweb || outputAddressesContainMweb;
|
||||
}
|
||||
|
||||
String? getUnusedMwebAddress(Object wallet) {
|
||||
try {
|
||||
final electrumWallet = wallet as ElectrumWallet;
|
||||
final mwebAddress =
|
||||
electrumWallet.walletAddresses.mwebAddresses.firstWhere((element) => !element.isUsed);
|
||||
return mwebAddress.address;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String? getUnusedSegwitAddress(Object wallet) {
|
||||
try {
|
||||
final electrumWallet = wallet as ElectrumWallet;
|
||||
final segwitAddress = electrumWallet.walletAddresses.allAddresses
|
||||
.firstWhere((element) => !element.isUsed && element.type == SegwitAddresType.p2wpkh);
|
||||
return segwitAddress.address;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,9 +36,9 @@ class AddressValidator extends TextValidator {
|
|||
'|[0-9a-zA-Z]{105}|addr1[0-9a-zA-Z]{98}';
|
||||
case CryptoCurrency.btc:
|
||||
pattern =
|
||||
'${P2pkhAddress.regex.pattern}|${P2shAddress.regex.pattern}|${P2wpkhAddress.regex.pattern}|${P2trAddress.regex.pattern}|${P2wshAddress.regex.pattern}|${SilentPaymentAddress.regex.pattern}';
|
||||
'${P2pkhAddress.regex.pattern}|${P2shAddress.regex.pattern}|${RegExp(r'(bc|tb)1q[ac-hj-np-z02-9]{25,39}}').pattern}|${P2trAddress.regex.pattern}|${P2wshAddress.regex.pattern}|${SilentPaymentAddress.regex.pattern}';
|
||||
case CryptoCurrency.ltc:
|
||||
pattern = '^${P2wpkhAddress.regex.pattern}\$|^${MwebAddress.regex.pattern}\$';
|
||||
pattern = '^${RegExp(r'ltc1q[ac-hj-np-z02-9]{25,39}').pattern}\$|^${MwebAddress.regex.pattern}\$';
|
||||
case CryptoCurrency.nano:
|
||||
pattern = '[0-9a-zA-Z_]+';
|
||||
case CryptoCurrency.banano:
|
||||
|
|
|
@ -2,14 +2,14 @@ import 'dart:convert';
|
|||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:cake_wallet/core/secure_storage.dart';
|
||||
import 'package:cake_wallet/entities/get_encryption_key.dart';
|
||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||
import 'package:cake_wallet/themes/theme_list.dart';
|
||||
import 'package:cw_core/root_dir.dart';
|
||||
import 'package:cake_wallet/utils/device_info.dart';
|
||||
import 'package:cw_core/root_dir.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:archive/archive_io.dart';
|
||||
|
@ -24,8 +24,8 @@ import 'package:cake_wallet/wallet_types.g.dart';
|
|||
import 'package:cake_backup/backup.dart' as cake_backup;
|
||||
|
||||
class BackupService {
|
||||
BackupService(
|
||||
this._secureStorage, this._walletInfoSource, this._keyService, this._sharedPreferences)
|
||||
BackupService(this._secureStorage, this._walletInfoSource, this._transactionDescriptionBox,
|
||||
this._keyService, this._sharedPreferences)
|
||||
: _cipher = Cryptography.instance.chacha20Poly1305Aead(),
|
||||
_correctWallets = <WalletInfo>[];
|
||||
|
||||
|
@ -38,6 +38,7 @@ class BackupService {
|
|||
final SecureStorage _secureStorage;
|
||||
final SharedPreferences _sharedPreferences;
|
||||
final Box<WalletInfo> _walletInfoSource;
|
||||
final Box<TransactionDescription> _transactionDescriptionBox;
|
||||
final KeyService _keyService;
|
||||
List<WalletInfo> _correctWallets;
|
||||
|
||||
|
@ -86,6 +87,13 @@ class BackupService {
|
|||
final preferencesDump = await _exportPreferencesJSON();
|
||||
final preferencesDumpFile = File('${tmpDir.path}/~_preferences_dump_TMP');
|
||||
final keychainDumpFile = File('${tmpDir.path}/~_keychain_dump_TMP');
|
||||
final transactionDescriptionDumpFile =
|
||||
File('${tmpDir.path}/~_transaction_descriptions_dump_TMP');
|
||||
|
||||
final transactionDescriptionData = _transactionDescriptionBox
|
||||
.toMap()
|
||||
.map((key, value) => MapEntry(key.toString(), value.toJson()));
|
||||
final transactionDescriptionDump = jsonEncode(transactionDescriptionData);
|
||||
|
||||
if (tmpDir.existsSync()) {
|
||||
tmpDir.deleteSync(recursive: true);
|
||||
|
@ -98,7 +106,15 @@ class BackupService {
|
|||
if (entity.path == archivePath || entity.path == tmpDir.path) {
|
||||
return;
|
||||
}
|
||||
|
||||
final filename = entity.absolute;
|
||||
for (var ignore in ignoreFiles) {
|
||||
final filename = entity.absolute.path;
|
||||
if (filename.endsWith(ignore) && !filename.contains("wallets/")) {
|
||||
print("ignoring backup file: $filename");
|
||||
return;
|
||||
}
|
||||
}
|
||||
print("restoring: $filename");
|
||||
if (entity.statSync().type == FileSystemEntityType.directory) {
|
||||
zipEncoder.addDirectory(Directory(entity.path));
|
||||
} else {
|
||||
|
@ -107,8 +123,10 @@ class BackupService {
|
|||
});
|
||||
await keychainDumpFile.writeAsBytes(keychainDump.toList());
|
||||
await preferencesDumpFile.writeAsString(preferencesDump);
|
||||
await transactionDescriptionDumpFile.writeAsString(transactionDescriptionDump);
|
||||
await zipEncoder.addFile(preferencesDumpFile, '~_preferences_dump');
|
||||
await zipEncoder.addFile(keychainDumpFile, '~_keychain_dump');
|
||||
await zipEncoder.addFile(transactionDescriptionDumpFile, '~_transaction_descriptions_dump');
|
||||
zipEncoder.close();
|
||||
|
||||
final content = File(archivePath).readAsBytesSync();
|
||||
|
@ -121,45 +139,61 @@ class BackupService {
|
|||
final decryptedData = await _decryptV1(data, password, nonce);
|
||||
final zip = ZipDecoder().decodeBytes(decryptedData);
|
||||
|
||||
zip.files.forEach((file) {
|
||||
for (var file in zip.files) {
|
||||
final filename = file.name;
|
||||
|
||||
if (file.isFile) {
|
||||
final content = file.content as List<int>;
|
||||
File('${appDir.path}/' + filename)
|
||||
..createSync(recursive: true)
|
||||
..writeAsBytesSync(content);
|
||||
..writeAsBytesSync(content, flush: true);
|
||||
} else {
|
||||
Directory('${appDir.path}/' + filename)..create(recursive: true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
await _verifyWallets();
|
||||
await _importKeychainDumpV1(password, nonce: nonce);
|
||||
await _importPreferencesDump();
|
||||
}
|
||||
|
||||
// checked with .endsWith - so this should be the last part of the filename
|
||||
static const ignoreFiles = [
|
||||
"flutter_assets/kernel_blob.bin",
|
||||
"flutter_assets/vm_snapshot_data",
|
||||
"flutter_assets/isolate_snapshot_data",
|
||||
".lock",
|
||||
];
|
||||
|
||||
Future<void> _importBackupV2(Uint8List data, String password) async {
|
||||
final appDir = await getAppDir();
|
||||
final decryptedData = await _decryptV2(data, password);
|
||||
final zip = ZipDecoder().decodeBytes(decryptedData);
|
||||
|
||||
zip.files.forEach((file) {
|
||||
outer:
|
||||
for (var file in zip.files) {
|
||||
final filename = file.name;
|
||||
|
||||
for (var ignore in ignoreFiles) {
|
||||
if (filename.endsWith(ignore) && !filename.contains("wallets/")) {
|
||||
print("ignoring backup file: $filename");
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
print("restoring: $filename");
|
||||
if (file.isFile) {
|
||||
final content = file.content as List<int>;
|
||||
File('${appDir.path}/' + filename)
|
||||
..createSync(recursive: true)
|
||||
..writeAsBytesSync(content);
|
||||
..writeAsBytesSync(content, flush: true);
|
||||
} else {
|
||||
Directory('${appDir.path}/' + filename)..create(recursive: true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
await _verifyWallets();
|
||||
await _importKeychainDumpV2(password);
|
||||
await _importPreferencesDump();
|
||||
await _importTransactionDescriptionDump();
|
||||
}
|
||||
|
||||
Future<void> _verifyWallets() async {
|
||||
|
@ -184,6 +218,31 @@ class BackupService {
|
|||
return await CakeHive.openBox<WalletInfo>(WalletInfo.boxName);
|
||||
}
|
||||
|
||||
Future<void> _importTransactionDescriptionDump() async {
|
||||
final appDir = await getAppDir();
|
||||
final transactionDescriptionFile = File('${appDir.path}/~_transaction_descriptions_dump');
|
||||
|
||||
if (!transactionDescriptionFile.existsSync()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final jsonData =
|
||||
json.decode(transactionDescriptionFile.readAsStringSync()) as Map<String, dynamic>;
|
||||
final descriptionsMap = jsonData.map((key, value) =>
|
||||
MapEntry(key, TransactionDescription.fromJson(value as Map<String, dynamic>)));
|
||||
|
||||
if (!_transactionDescriptionBox.isOpen) {
|
||||
final transactionDescriptionsBoxKey = await getEncryptionKey(secureStorage: secureStorageShared, forKey: TransactionDescription.boxKey);
|
||||
final transactionDescriptionBox = await CakeHive.openBox<TransactionDescription>(
|
||||
TransactionDescription.boxName,
|
||||
encryptionKey: transactionDescriptionsBoxKey,
|
||||
);
|
||||
await transactionDescriptionBox.putAll(descriptionsMap);
|
||||
return;
|
||||
}
|
||||
await _transactionDescriptionBox.putAll(descriptionsMap);
|
||||
}
|
||||
|
||||
Future<void> _importPreferencesDump() async {
|
||||
final appDir = await getAppDir();
|
||||
final preferencesFile = File('${appDir.path}/~_preferences_dump');
|
||||
|
|
|
@ -16,6 +16,13 @@ String syncStatusTitle(SyncStatus syncStatus) {
|
|||
return S.current.sync_status_syncronized;
|
||||
}
|
||||
|
||||
if (syncStatus is FailedSyncStatus) {
|
||||
if (syncStatus.error != null) {
|
||||
return syncStatus.error!;
|
||||
}
|
||||
return S.current.sync_status_failed_connect;
|
||||
}
|
||||
|
||||
if (syncStatus is NotConnectedSyncStatus) {
|
||||
return S.current.sync_status_not_connected;
|
||||
}
|
||||
|
@ -24,10 +31,6 @@ String syncStatusTitle(SyncStatus syncStatus) {
|
|||
return S.current.sync_status_attempting_sync;
|
||||
}
|
||||
|
||||
if (syncStatus is FailedSyncStatus) {
|
||||
return S.current.sync_status_failed_connect;
|
||||
}
|
||||
|
||||
if (syncStatus is ConnectingSyncStatus) {
|
||||
return S.current.sync_status_connecting;
|
||||
}
|
||||
|
|
34
lib/di.dart
34
lib/di.dart
|
@ -167,6 +167,7 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_i
|
|||
import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart';
|
||||
import 'package:cw_core/nano_account.dart';
|
||||
import 'package:cw_core/unspent_coin_type.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
|
@ -382,7 +383,11 @@ Future<void> setup({
|
|||
getIt.registerFactory<NewWalletTypeViewModel>(() => NewWalletTypeViewModel(_walletInfoSource));
|
||||
|
||||
getIt.registerFactory<WalletManager>(
|
||||
() => WalletManager(_walletInfoSource, getIt.get<SharedPreferences>()),
|
||||
() {
|
||||
final instance = WalletManager(_walletInfoSource, getIt.get<SharedPreferences>());
|
||||
instance.updateWalletGroups();
|
||||
return instance;
|
||||
},
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<WalletGroupsDisplayViewModel, WalletType, void>(
|
||||
|
@ -715,8 +720,8 @@ Future<void> setup({
|
|||
getIt.get<SendTemplateStore>(),
|
||||
getIt.get<FiatConversionStore>()));
|
||||
|
||||
getIt.registerFactory<SendViewModel>(
|
||||
() => SendViewModel(
|
||||
getIt.registerFactoryParam<SendViewModel, UnspentCoinType?, void>(
|
||||
(coinTypeToSpendFrom, _) => SendViewModel(
|
||||
getIt.get<AppStore>(),
|
||||
getIt.get<SendTemplateViewModel>(),
|
||||
getIt.get<FiatConversionStore>(),
|
||||
|
@ -724,12 +729,13 @@ Future<void> setup({
|
|||
getIt.get<ContactListViewModel>(),
|
||||
_transactionDescriptionBox,
|
||||
getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null,
|
||||
coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.any,
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<SendPage, PaymentRequest?, void>(
|
||||
(PaymentRequest? initialPaymentRequest, _) => SendPage(
|
||||
sendViewModel: getIt.get<SendViewModel>(),
|
||||
getIt.registerFactoryParam<SendPage, PaymentRequest?, UnspentCoinType?>(
|
||||
(PaymentRequest? initialPaymentRequest, coinTypeToSpendFrom) => SendPage(
|
||||
sendViewModel: getIt.get<SendViewModel>(param1: coinTypeToSpendFrom),
|
||||
authService: getIt.get<AuthService>(),
|
||||
initialPaymentRequest: initialPaymentRequest,
|
||||
));
|
||||
|
@ -1147,6 +1153,7 @@ Future<void> setup({
|
|||
getIt.registerFactory(() => CakeFeaturesViewModel(getIt.get<CakePayService>()));
|
||||
|
||||
getIt.registerFactory(() => BackupService(getIt.get<SecureStorage>(), _walletInfoSource,
|
||||
_transactionDescriptionBox,
|
||||
getIt.get<KeyService>(), getIt.get<SharedPreferences>()));
|
||||
|
||||
getIt.registerFactory(() => BackupViewModel(
|
||||
|
@ -1208,14 +1215,21 @@ Future<void> setup({
|
|||
|
||||
getIt.registerFactory(() => SupportOtherLinksPage(getIt.get<SupportViewModel>()));
|
||||
|
||||
getIt.registerFactory(() {
|
||||
getIt.registerFactoryParam<UnspentCoinsListViewModel, UnspentCoinType?, void>(
|
||||
(coinTypeToSpendFrom, _) {
|
||||
final wallet = getIt.get<AppStore>().wallet;
|
||||
|
||||
return UnspentCoinsListViewModel(wallet: wallet!, unspentCoinsInfo: _unspentCoinsInfoSource);
|
||||
return UnspentCoinsListViewModel(
|
||||
wallet: wallet!,
|
||||
unspentCoinsInfo: _unspentCoinsInfoSource,
|
||||
coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.any,
|
||||
);
|
||||
});
|
||||
|
||||
getIt.registerFactory(() =>
|
||||
UnspentCoinsListPage(unspentCoinsListViewModel: getIt.get<UnspentCoinsListViewModel>()));
|
||||
getIt.registerFactoryParam<UnspentCoinsListPage, UnspentCoinType?, void>(
|
||||
(coinTypeToSpendFrom, _) => UnspentCoinsListPage(
|
||||
unspentCoinsListViewModel:
|
||||
getIt.get<UnspentCoinsListViewModel>(param1: coinTypeToSpendFrom)));
|
||||
|
||||
getIt.registerFactoryParam<UnspentCoinsDetailsViewModel, UnspentCoinsItem,
|
||||
UnspentCoinsListViewModel>(
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io' show Directory, File, Platform;
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/core/secure_storage.dart';
|
||||
|
@ -234,7 +235,8 @@ Future<void> defaultSettingsMigration(
|
|||
break;
|
||||
case 36:
|
||||
await addWowneroNodeList(nodes: nodes);
|
||||
await changeWowneroCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
|
||||
await changeWowneroCurrentNodeToDefault(
|
||||
sharedPreferences: sharedPreferences, nodes: nodes);
|
||||
break;
|
||||
case 37:
|
||||
await replaceTronDefaultNode(sharedPreferences: sharedPreferences, nodes: nodes);
|
||||
|
@ -249,6 +251,11 @@ Future<void> defaultSettingsMigration(
|
|||
case 40:
|
||||
await removeMoneroWorld(sharedPreferences: sharedPreferences, nodes: nodes);
|
||||
break;
|
||||
case 41:
|
||||
_deselectQuantex(sharedPreferences);
|
||||
await _addSethNode(nodes, sharedPreferences);
|
||||
await updateTronNodesWithNowNodes(sharedPreferences: sharedPreferences, nodes: nodes);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -263,6 +270,19 @@ Future<void> defaultSettingsMigration(
|
|||
await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, version);
|
||||
}
|
||||
|
||||
void _deselectQuantex(SharedPreferences sharedPreferences) {
|
||||
final Map<String, dynamic> exchangeProvidersSelection =
|
||||
json.decode(sharedPreferences.getString(PreferencesKey.exchangeProvidersSelection) ?? "{}")
|
||||
as Map<String, dynamic>;
|
||||
|
||||
exchangeProvidersSelection['Quantex'] = false;
|
||||
|
||||
sharedPreferences.setString(
|
||||
PreferencesKey.exchangeProvidersSelection,
|
||||
json.encode(exchangeProvidersSelection),
|
||||
);
|
||||
}
|
||||
|
||||
void _fixNodesUseSSLFlag(Box<Node> nodes) {
|
||||
for (Node node in nodes.values) {
|
||||
switch (node.uriRaw) {
|
||||
|
@ -879,7 +899,9 @@ Future<void> changeDefaultBitcoinNode(
|
|||
final newCakeWalletBitcoinNode =
|
||||
Node(uri: newCakeWalletBitcoinUri, type: WalletType.bitcoin, useSSL: false);
|
||||
|
||||
await nodeSource.add(newCakeWalletBitcoinNode);
|
||||
if (!nodeSource.values.any((element) => element.uriRaw == newCakeWalletBitcoinUri)) {
|
||||
await nodeSource.add(newCakeWalletBitcoinNode);
|
||||
}
|
||||
|
||||
if (needToReplaceCurrentBitcoinNode) {
|
||||
await sharedPreferences.setInt(
|
||||
|
@ -887,7 +909,34 @@ Future<void> changeDefaultBitcoinNode(
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _addSethNode(Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
|
||||
_addBitcoinNode(
|
||||
nodeSource: nodeSource,
|
||||
sharedPreferences: sharedPreferences,
|
||||
nodeUri: "fulcrum.sethforprivacy.com:50002",
|
||||
useSSL: false,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _addElectRsNode(Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
|
||||
_addBitcoinNode(
|
||||
nodeSource: nodeSource,
|
||||
sharedPreferences: sharedPreferences,
|
||||
nodeUri: cakeWalletSilentPaymentsElectrsUri,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _addBitcoinNode({
|
||||
required Box<Node> nodeSource,
|
||||
required SharedPreferences sharedPreferences,
|
||||
required String nodeUri,
|
||||
bool replaceExisting = false,
|
||||
bool useSSL = false,
|
||||
}) async {
|
||||
bool isNodeExists = nodeSource.values.any((element) => element.uriRaw == nodeUri);
|
||||
if (isNodeExists) {
|
||||
return;
|
||||
}
|
||||
const cakeWalletBitcoinNodeUriPattern = '.cakewallet.com';
|
||||
final currentBitcoinNodeId =
|
||||
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
|
||||
|
@ -896,12 +945,11 @@ Future<void> _addElectRsNode(Box<Node> nodeSource, SharedPreferences sharedPrefe
|
|||
final needToReplaceCurrentBitcoinNode =
|
||||
currentBitcoinNode.uri.toString().contains(cakeWalletBitcoinNodeUriPattern);
|
||||
|
||||
final newElectRsBitcoinNode =
|
||||
Node(uri: cakeWalletSilentPaymentsElectrsUri, type: WalletType.bitcoin, useSSL: false);
|
||||
final newElectRsBitcoinNode = Node(uri: nodeUri, type: WalletType.bitcoin, useSSL: useSSL);
|
||||
|
||||
await nodeSource.add(newElectRsBitcoinNode);
|
||||
|
||||
if (needToReplaceCurrentBitcoinNode) {
|
||||
if (needToReplaceCurrentBitcoinNode && replaceExisting) {
|
||||
await sharedPreferences.setInt(
|
||||
PreferencesKey.currentBitcoinElectrumSererIdKey, newElectRsBitcoinNode.key as int);
|
||||
}
|
||||
|
@ -1262,7 +1310,8 @@ Future<void> removeMoneroWorld(
|
|||
const cakeWalletMoneroNodeUriPattern = '.moneroworld.com';
|
||||
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
||||
final currentMoneroNode = nodes.values.firstWhere((node) => node.key == currentMoneroNodeId);
|
||||
final needToReplaceCurrentMoneroNode = currentMoneroNode.uri.toString().contains(cakeWalletMoneroNodeUriPattern);
|
||||
final needToReplaceCurrentMoneroNode =
|
||||
currentMoneroNode.uri.toString().contains(cakeWalletMoneroNodeUriPattern);
|
||||
|
||||
nodes.values.forEach((node) async {
|
||||
if (node.type == WalletType.monero &&
|
||||
|
@ -1275,3 +1324,16 @@ Future<void> removeMoneroWorld(
|
|||
await changeMoneroCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateTronNodesWithNowNodes({
|
||||
required SharedPreferences sharedPreferences,
|
||||
required Box<Node> nodes,
|
||||
}) async {
|
||||
final tronNowNodesUri = 'trx.nownodes.io';
|
||||
|
||||
if (nodes.values.any((node) => node.uriRaw == tronNowNodesUri)) return;
|
||||
|
||||
await nodes.add(Node(uri: tronNowNodesUri, type: WalletType.tron));
|
||||
|
||||
await replaceTronDefaultNode(sharedPreferences: sharedPreferences, nodes: nodes);
|
||||
}
|
||||
|
|
|
@ -21,4 +21,18 @@ class TransactionDescription extends HiveObject {
|
|||
String? transactionNote;
|
||||
|
||||
String get note => transactionNote ?? '';
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'recipientAddress': recipientAddress,
|
||||
'transactionNote': transactionNote,
|
||||
};
|
||||
|
||||
factory TransactionDescription.fromJson(Map<String, dynamic> json) {
|
||||
return TransactionDescription(
|
||||
id: json['id'] as String,
|
||||
recipientAddress: json['recipientAddress'] as String?,
|
||||
transactionNote: json['transactionNote'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
const channel = MethodChannel('com.cake_wallet/native_utils');
|
||||
|
||||
Future<String> fetchUnstoppableDomainAddress(String domain, String ticker) async {
|
||||
var address = '';
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ class CWEthereum extends Ethereum {
|
|||
String? parentAddress,
|
||||
WalletInfo? walletInfo,
|
||||
String? password,
|
||||
String? passphrase,
|
||||
}) =>
|
||||
EVMChainNewWalletCredentials(
|
||||
name: name,
|
||||
|
@ -21,6 +22,7 @@ class CWEthereum extends Ethereum {
|
|||
password: password,
|
||||
parentAddress: parentAddress,
|
||||
mnemonic: mnemonic,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
@override
|
||||
|
@ -28,8 +30,14 @@ class CWEthereum extends Ethereum {
|
|||
required String name,
|
||||
required String mnemonic,
|
||||
required String password,
|
||||
String? passphrase,
|
||||
}) =>
|
||||
EVMChainRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic);
|
||||
EVMChainRestoreWalletFromSeedCredentials(
|
||||
name: name,
|
||||
password: password,
|
||||
mnemonic: mnemonic,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
@override
|
||||
WalletCredentials createEthereumRestoreWalletFromPrivateKey({
|
||||
|
|
|
@ -193,7 +193,7 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
|
|||
transactionDescriptions: transactionDescriptions,
|
||||
secureStorage: secureStorage,
|
||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||
initialMigrationVersion: 40,
|
||||
initialMigrationVersion: 41,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -95,6 +95,7 @@ class CWNano extends Nano {
|
|||
String? password,
|
||||
String? mnemonic,
|
||||
String? parentAddress,
|
||||
String? passphrase,
|
||||
}) =>
|
||||
NanoNewWalletCredentials(
|
||||
name: name,
|
||||
|
@ -102,6 +103,7 @@ class CWNano extends Nano {
|
|||
mnemonic: mnemonic,
|
||||
parentAddress: parentAddress,
|
||||
walletInfo: walletInfo,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
@override
|
||||
|
@ -110,6 +112,7 @@ class CWNano extends Nano {
|
|||
required String password,
|
||||
required String mnemonic,
|
||||
required DerivationType derivationType,
|
||||
String? passphrase,
|
||||
}) {
|
||||
if (mnemonic.split(" ").length == 12 && derivationType != DerivationType.bip39) {
|
||||
throw Exception("Invalid mnemonic for derivation type!");
|
||||
|
@ -120,6 +123,7 @@ class CWNano extends Nano {
|
|||
password: password,
|
||||
mnemonic: mnemonic,
|
||||
derivationType: derivationType,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,18 +8,21 @@ class CWPolygon extends Polygon {
|
|||
PolygonWalletService(walletInfoSource, isDirect, client: PolygonClient());
|
||||
|
||||
@override
|
||||
WalletCredentials createPolygonNewWalletCredentials(
|
||||
{required String name,
|
||||
String? mnemonic,
|
||||
String? parentAddress,
|
||||
WalletInfo? walletInfo,
|
||||
String? password}) =>
|
||||
WalletCredentials createPolygonNewWalletCredentials({
|
||||
required String name,
|
||||
String? mnemonic,
|
||||
String? parentAddress,
|
||||
WalletInfo? walletInfo,
|
||||
String? password,
|
||||
String? passphrase,
|
||||
}) =>
|
||||
EVMChainNewWalletCredentials(
|
||||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
password: password,
|
||||
mnemonic: mnemonic,
|
||||
parentAddress: parentAddress,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
@override
|
||||
|
@ -27,8 +30,14 @@ class CWPolygon extends Polygon {
|
|||
required String name,
|
||||
required String mnemonic,
|
||||
required String password,
|
||||
String? passphrase,
|
||||
}) =>
|
||||
EVMChainRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic);
|
||||
EVMChainRestoreWalletFromSeedCredentials(
|
||||
name: name,
|
||||
password: password,
|
||||
mnemonic: mnemonic,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
@override
|
||||
WalletCredentials createPolygonRestoreWalletFromPrivateKey({
|
||||
|
|
|
@ -11,6 +11,8 @@ Timer? _checkConnectionTimer;
|
|||
void startCheckConnectionReaction(WalletBase wallet, SettingsStore settingsStore,
|
||||
{int timeInterval = 5}) {
|
||||
_checkConnectionTimer?.cancel();
|
||||
// TODO: check the validity of this code, and if it's working fine, then no need for
|
||||
// having the connect function in electrum.dart when the syncstatus is lost or failed and add the not connected state
|
||||
_checkConnectionTimer = Timer.periodic(Duration(seconds: timeInterval), (_) async {
|
||||
if (wallet.type == WalletType.bitcoin && wallet.syncStatus is SyncingSyncStatus) {
|
||||
return;
|
||||
|
|
|
@ -152,11 +152,6 @@ void _setAutoGenerateSubaddressStatus(
|
|||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet,
|
||||
SettingsStore settingsStore,
|
||||
) async {
|
||||
final walletHasAddresses = await wallet.walletAddresses.addressesMap.length > 1;
|
||||
if (settingsStore.autoGenerateSubaddressStatus == AutoGenerateSubaddressStatus.initialized &&
|
||||
walletHasAddresses) {
|
||||
settingsStore.autoGenerateSubaddressStatus = AutoGenerateSubaddressStatus.disabled;
|
||||
}
|
||||
wallet.isEnabledAutoGenerateSubaddress =
|
||||
settingsStore.autoGenerateSubaddressStatus == AutoGenerateSubaddressStatus.enabled ||
|
||||
settingsStore.autoGenerateSubaddressStatus == AutoGenerateSubaddressStatus.initialized;
|
||||
|
|
|
@ -120,6 +120,7 @@ import 'package:cw_core/crypto_currency.dart';
|
|||
import 'package:cw_core/nano_account.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/unspent_coin_type.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
@ -184,7 +185,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
final type = settings.arguments as WalletType;
|
||||
final walletGroupsDisplayVM = getIt.get<WalletGroupsDisplayViewModel>(param1: type);
|
||||
|
||||
return CupertinoPageRoute<void>(builder: (_) => WalletGroupsDisplayPage(walletGroupsDisplayVM));
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => WalletGroupsDisplayPage(walletGroupsDisplayVM));
|
||||
|
||||
case Routes.newWallet:
|
||||
final args = settings.arguments as NewWalletArguments;
|
||||
|
@ -348,13 +350,17 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
settings: settings, builder: (_) => getIt.get<DashboardPage>());
|
||||
|
||||
case Routes.send:
|
||||
final initialPaymentRequest = settings.arguments as PaymentRequest?;
|
||||
final args = settings.arguments as Map<String, dynamic>?;
|
||||
final initialPaymentRequest = args?['paymentRequest'] as PaymentRequest?;
|
||||
final coinTypeToSpendFrom = args?['coinTypeToSpendFrom'] as UnspentCoinType?;
|
||||
|
||||
return CupertinoPageRoute<void>(
|
||||
fullscreenDialog: true,
|
||||
builder: (_) => getIt.get<SendPage>(
|
||||
param1: initialPaymentRequest,
|
||||
));
|
||||
fullscreenDialog: true,
|
||||
builder: (_) => getIt.get<SendPage>(
|
||||
param1: initialPaymentRequest,
|
||||
param2: coinTypeToSpendFrom,
|
||||
),
|
||||
);
|
||||
|
||||
case Routes.sendTemplate:
|
||||
return CupertinoPageRoute<void>(
|
||||
|
@ -604,7 +610,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
fullscreenDialog: true, builder: (_) => getIt.get<SupportOtherLinksPage>());
|
||||
|
||||
case Routes.unspentCoinsList:
|
||||
return MaterialPageRoute<void>(builder: (_) => getIt.get<UnspentCoinsListPage>());
|
||||
final coinTypeToSpendFrom = settings.arguments as UnspentCoinType?;
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => getIt.get<UnspentCoinsListPage>(param1: coinTypeToSpendFrom));
|
||||
|
||||
case Routes.unspentCoinsDetails:
|
||||
final args = settings.arguments as List;
|
||||
|
@ -778,7 +786,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
|
||||
case Routes.walletGroupDescription:
|
||||
final walletType = settings.arguments as WalletType;
|
||||
|
||||
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => WalletGroupDescriptionPage(
|
||||
selectedWalletType: walletType,
|
||||
|
|
|
@ -14,6 +14,7 @@ class CWSolana extends Solana {
|
|||
String? parentAddress,
|
||||
WalletInfo? walletInfo,
|
||||
String? password,
|
||||
String? passphrase,
|
||||
}) =>
|
||||
SolanaNewWalletCredentials(
|
||||
name: name,
|
||||
|
@ -21,6 +22,7 @@ class CWSolana extends Solana {
|
|||
password: password,
|
||||
mnemonic: mnemonic,
|
||||
parentAddress: parentAddress,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
@override
|
||||
|
@ -28,8 +30,14 @@ class CWSolana extends Solana {
|
|||
required String name,
|
||||
required String mnemonic,
|
||||
required String password,
|
||||
String? passphrase,
|
||||
}) =>
|
||||
SolanaRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic);
|
||||
SolanaRestoreWalletFromSeedCredentials(
|
||||
name: name,
|
||||
password: password,
|
||||
mnemonic: mnemonic,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
@override
|
||||
WalletCredentials createSolanaRestoreWalletFromPrivateKey({
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -97,14 +97,13 @@ class MenuWidgetState extends State<MenuWidget> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<SettingActions> items = SettingActions.all;
|
||||
List<SettingActions> items = List.of(SettingActions.all);
|
||||
if (!widget.dashboardViewModel.hasSilentPayments) {
|
||||
items.removeWhere((element) => element.name(context) == S.of(context).silent_payments_settings);
|
||||
}
|
||||
// if (!widget.dashboardViewModel.hasMweb) {
|
||||
// itemCount--;
|
||||
// items.removeWhere((element) => element.name(context) == S.of(context).litecoin_mweb_settings);
|
||||
// }
|
||||
if (!widget.dashboardViewModel.hasMweb) {
|
||||
items.removeWhere((element) => element.name(context) == S.of(context).litecoin_mweb_settings);
|
||||
}
|
||||
int itemCount = items.length;
|
||||
|
||||
moneroIcon = Image.asset('assets/images/monero_menu.png',
|
||||
|
@ -191,11 +190,6 @@ class MenuWidgetState extends State<MenuWidget> {
|
|||
|
||||
final item = items[index];
|
||||
|
||||
if (!widget.dashboardViewModel.hasMweb &&
|
||||
item.name(context) == S.current.litecoin_mweb_settings) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
final isLastTile = index == itemCount - 1;
|
||||
|
||||
return SettingActionButton(
|
||||
|
|
|
@ -189,7 +189,7 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
|
|||
),
|
||||
);
|
||||
}),
|
||||
if (!widget.isFromRestore) ...[
|
||||
if (!widget.isFromRestore)
|
||||
Observer(builder: (_) {
|
||||
if (widget.privacySettingsViewModel.hasSeedPhraseLengthOption)
|
||||
return SettingsPickerCell<SeedPhraseLength>(
|
||||
|
@ -202,54 +202,53 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
|
|||
);
|
||||
return Container();
|
||||
}),
|
||||
if (widget.privacySettingsViewModel.hasPassphraseOption)
|
||||
Padding(
|
||||
padding: EdgeInsets.all(24),
|
||||
child: Form(
|
||||
key: _passphraseFormKey,
|
||||
child: Column(
|
||||
children: [
|
||||
BaseTextFormField(
|
||||
hintText: S.of(context).passphrase,
|
||||
controller: passphraseController,
|
||||
obscureText: obscurePassphrase,
|
||||
suffixIcon: GestureDetector(
|
||||
onTap: () => setState(() {
|
||||
obscurePassphrase = !obscurePassphrase;
|
||||
}),
|
||||
child: Icon(
|
||||
Icons.remove_red_eye,
|
||||
color: obscurePassphrase ? Colors.black54 : Colors.black26,
|
||||
),
|
||||
if (widget.privacySettingsViewModel.hasPassphraseOption)
|
||||
Padding(
|
||||
padding: EdgeInsets.all(24),
|
||||
child: Form(
|
||||
key: _passphraseFormKey,
|
||||
child: Column(
|
||||
children: [
|
||||
BaseTextFormField(
|
||||
hintText: S.of(context).passphrase,
|
||||
controller: passphraseController,
|
||||
obscureText: obscurePassphrase,
|
||||
suffixIcon: GestureDetector(
|
||||
onTap: () => setState(() {
|
||||
obscurePassphrase = !obscurePassphrase;
|
||||
}),
|
||||
child: Icon(
|
||||
Icons.remove_red_eye,
|
||||
color: obscurePassphrase ? Colors.black54 : Colors.black26,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
BaseTextFormField(
|
||||
hintText: S.of(context).confirm_passphrase,
|
||||
controller: confirmPassphraseController,
|
||||
obscureText: obscurePassphrase,
|
||||
validator: (text) {
|
||||
if (text == passphraseController.text) {
|
||||
return null;
|
||||
}
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
BaseTextFormField(
|
||||
hintText: S.of(context).confirm_passphrase,
|
||||
controller: confirmPassphraseController,
|
||||
obscureText: obscurePassphrase,
|
||||
validator: (text) {
|
||||
if (text == passphraseController.text) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return S.of(context).passphrases_doesnt_match;
|
||||
},
|
||||
suffixIcon: GestureDetector(
|
||||
onTap: () => setState(() {
|
||||
obscurePassphrase = !obscurePassphrase;
|
||||
}),
|
||||
child: Icon(
|
||||
Icons.remove_red_eye,
|
||||
color: obscurePassphrase ? Colors.black54 : Colors.black26,
|
||||
),
|
||||
return S.of(context).passphrases_doesnt_match;
|
||||
},
|
||||
suffixIcon: GestureDetector(
|
||||
onTap: () => setState(() {
|
||||
obscurePassphrase = !obscurePassphrase;
|
||||
}),
|
||||
child: Icon(
|
||||
Icons.remove_red_eye,
|
||||
color: obscurePassphrase ? Colors.black54 : Colors.black26,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Observer(builder: (_) {
|
||||
return Column(
|
||||
children: [
|
||||
|
@ -311,13 +310,14 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
|
|||
widget.nodeViewModel.save();
|
||||
}
|
||||
if (passphraseController.text.isNotEmpty) {
|
||||
if (_passphraseFormKey.currentState != null && !_passphraseFormKey.currentState!.validate()) {
|
||||
if (_passphraseFormKey.currentState != null &&
|
||||
!_passphraseFormKey.currentState!.validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
widget.seedTypeViewModel.setPassphrase(passphraseController.text);
|
||||
}
|
||||
|
||||
widget.seedTypeViewModel.setPassphrase(passphraseController.text);
|
||||
|
||||
Navigator.pop(context);
|
||||
},
|
||||
text: S.of(context).continue_text,
|
||||
|
|
|
@ -7,7 +7,6 @@ import 'package:cake_wallet/themes/theme_base.dart';
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
class WalletGroupDescriptionPage extends BasePage {
|
||||
WalletGroupDescriptionPage({required this.selectedWalletType});
|
||||
|
@ -21,12 +20,6 @@ class WalletGroupDescriptionPage extends BasePage {
|
|||
@override
|
||||
Widget body(BuildContext context) {
|
||||
|
||||
final lightImage = 'assets/images/wallet_group_light.png';
|
||||
final darkImage = 'assets/images/wallet_group_dark.png';
|
||||
final brightImage = 'assets/images/wallet_group_bright.png';
|
||||
|
||||
final image = currentTheme.type == ThemeType.light ? lightImage : darkImage;
|
||||
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
padding: EdgeInsets.all(24),
|
||||
|
|
|
@ -4,11 +4,11 @@ import 'package:cake_wallet/routes.dart';
|
|||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/new_wallet/widgets/grouped_wallet_expansion_tile.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_groups_display_view_model.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
import '../../../themes/extensions/cake_text_theme.dart';
|
||||
|
||||
class WalletGroupsDisplayPage extends BasePage {
|
||||
|
@ -16,22 +16,24 @@ class WalletGroupsDisplayPage extends BasePage {
|
|||
|
||||
final WalletGroupsDisplayViewModel walletGroupsDisplayViewModel;
|
||||
|
||||
final walletTypeImage = Image.asset('assets/images/wallet_type.png');
|
||||
final walletTypeLightImage = Image.asset('assets/images/wallet_type_light.png');
|
||||
|
||||
@override
|
||||
String get title => S.current.wallet_group;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => WalletGroupsDisplayBody(
|
||||
walletGroupsDisplayViewModel: walletGroupsDisplayViewModel,
|
||||
currentTheme: currentTheme,
|
||||
);
|
||||
}
|
||||
|
||||
class WalletGroupsDisplayBody extends StatelessWidget {
|
||||
WalletGroupsDisplayBody({required this.walletGroupsDisplayViewModel});
|
||||
WalletGroupsDisplayBody({
|
||||
required this.walletGroupsDisplayViewModel,
|
||||
required this.currentTheme,
|
||||
});
|
||||
|
||||
final WalletGroupsDisplayViewModel walletGroupsDisplayViewModel;
|
||||
final ThemeBase currentTheme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -47,7 +49,9 @@ class WalletGroupsDisplayBody extends StatelessWidget {
|
|||
return Column(
|
||||
children: [
|
||||
if (walletGroupsDisplayViewModel.hasNoFilteredWallet) ...{
|
||||
WalletGroupEmptyStateWidget(),
|
||||
WalletGroupEmptyStateWidget(
|
||||
currentTheme: currentTheme,
|
||||
),
|
||||
},
|
||||
...walletGroupsDisplayViewModel.multiWalletGroups.map(
|
||||
(walletGroup) {
|
||||
|
@ -153,17 +157,17 @@ class WalletGroupsDisplayBody extends StatelessWidget {
|
|||
}
|
||||
|
||||
class WalletGroupEmptyStateWidget extends StatelessWidget {
|
||||
const WalletGroupEmptyStateWidget({
|
||||
super.key,
|
||||
});
|
||||
const WalletGroupEmptyStateWidget({required this.currentTheme, super.key});
|
||||
|
||||
final ThemeBase currentTheme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/wallet_group.png',
|
||||
scale: 0.8,
|
||||
_getThemedWalletGroupImage(currentTheme.type),
|
||||
scale: 1.8,
|
||||
),
|
||||
SizedBox(height: 32),
|
||||
Text.rich(
|
||||
|
@ -190,4 +194,19 @@ class WalletGroupEmptyStateWidget extends StatelessWidget {
|
|||
],
|
||||
);
|
||||
}
|
||||
|
||||
String _getThemedWalletGroupImage(ThemeType theme) {
|
||||
final lightImage = 'assets/images/wallet_group_light.png';
|
||||
final darkImage = 'assets/images/wallet_group_dark.png';
|
||||
final brightImage = 'assets/images/wallet_group_bright.png';
|
||||
|
||||
switch (theme) {
|
||||
case ThemeType.bright:
|
||||
return brightImage;
|
||||
case ThemeType.light:
|
||||
return lightImage;
|
||||
default:
|
||||
return darkImage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ class QrImage extends StatelessWidget {
|
|||
return qr.QrImageView(
|
||||
data: data,
|
||||
errorCorrectionLevel: errorCorrectionLevel,
|
||||
version: version ?? 9, // Previous value: 7 something happened after flutter upgrade monero wallets addresses are longer than ver. 7 ???
|
||||
version: version ?? qr.QrVersions.auto,
|
||||
size: size,
|
||||
foregroundColor: foregroundColor,
|
||||
backgroundColor: backgroundColor,
|
||||
|
|
|
@ -19,7 +19,6 @@ class WalletRestoreFromSeedForm extends StatefulWidget {
|
|||
WalletRestoreFromSeedForm({Key? key,
|
||||
required this.displayLanguageSelector,
|
||||
required this.displayBlockHeightSelector,
|
||||
required this.displayPassphrase,
|
||||
required this.type,
|
||||
required this.displayWalletPassword,
|
||||
required this.seedSettingsViewModel,
|
||||
|
@ -35,7 +34,6 @@ class WalletRestoreFromSeedForm extends StatefulWidget {
|
|||
final bool displayLanguageSelector;
|
||||
final bool displayBlockHeightSelector;
|
||||
final bool displayWalletPassword;
|
||||
final bool displayPassphrase;
|
||||
final SeedSettingsViewModel seedSettingsViewModel;
|
||||
final FocusNode? blockHeightFocusNode;
|
||||
final Function(bool)? onHeightOrDateEntered;
|
||||
|
@ -60,7 +58,6 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
|
|||
repeatedPasswordTextEditingController = displayWalletPassword
|
||||
? TextEditingController()
|
||||
: null,
|
||||
passphraseController = TextEditingController(),
|
||||
seedTypeController = TextEditingController();
|
||||
|
||||
final GlobalKey<SeedWidgetState> seedWidgetStateKey;
|
||||
|
@ -70,15 +67,11 @@ 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;
|
||||
void Function()? passwordListener;
|
||||
void Function()? repeatedPasswordListener;
|
||||
void Function()? passphraseListener;
|
||||
|
||||
bool obscurePassphrase = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -96,9 +89,6 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
|
|||
repeatedPasswordTextEditingController?.addListener(repeatedPasswordListener!);
|
||||
}
|
||||
|
||||
passphraseListener = () => widget.seedSettingsViewModel.setPassphrase(passphraseController.text);
|
||||
passphraseController.addListener(passphraseListener!);
|
||||
|
||||
moneroSeedTypeReaction =
|
||||
reaction((_) => widget.seedSettingsViewModel.moneroSeedType, (MoneroSeedType item) {
|
||||
_setSeedType(item);
|
||||
|
@ -120,8 +110,6 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
|
|||
repeatedPasswordTextEditingController?.removeListener(repeatedPasswordListener!);
|
||||
}
|
||||
|
||||
passphraseController.removeListener(passphraseListener!);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -280,23 +268,6 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
|
|||
hasDatePicker: widget.type == WalletType.monero || widget.type == WalletType.wownero,
|
||||
walletType: widget.type,
|
||||
),
|
||||
if (widget.displayPassphrase) ...[
|
||||
const SizedBox(height: 10),
|
||||
BaseTextFormField(
|
||||
hintText: S.current.passphrase,
|
||||
controller: passphraseController,
|
||||
obscureText: obscurePassphrase,
|
||||
suffixIcon: GestureDetector(
|
||||
onTap: () => setState(() {
|
||||
obscurePassphrase = !obscurePassphrase;
|
||||
}),
|
||||
child: Icon(
|
||||
Icons.remove_red_eye,
|
||||
color: obscurePassphrase ? Colors.black54 : Colors.black26,
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
]));
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,6 @@ class WalletRestorePage extends BasePage {
|
|||
displayBlockHeightSelector:
|
||||
walletRestoreViewModel.hasBlockchainHeightLanguageSelector,
|
||||
displayLanguageSelector: walletRestoreViewModel.hasSeedLanguageSelector,
|
||||
displayPassphrase: walletRestoreViewModel.hasPassphrase,
|
||||
type: walletRestoreViewModel.type,
|
||||
key: walletRestoreFromSeedFormKey,
|
||||
blockHeightFocusNode: _blockHeightFocusNode,
|
||||
|
@ -320,9 +319,7 @@ class WalletRestorePage extends BasePage {
|
|||
-1;
|
||||
}
|
||||
|
||||
if (walletRestoreViewModel.hasPassphrase) {
|
||||
credentials['passphrase'] = seedSettingsViewModel.passphrase;
|
||||
}
|
||||
credentials['passphrase'] = seedSettingsViewModel.passphrase;
|
||||
|
||||
credentials['name'] =
|
||||
walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.text;
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:cake_wallet/di.dart';
|
|||
import 'package:cake_wallet/entities/background_tasks.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/utils/device_info.dart';
|
||||
import 'package:cake_wallet/utils/feature_flag.dart';
|
||||
import 'package:cake_wallet/view_model/link_view_model.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
@ -143,7 +144,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
|||
setState(() => _setInactive(true));
|
||||
}
|
||||
|
||||
if (syncingWalletTypes.contains(widget.appStore.wallet?.type)) {
|
||||
if (FeatureFlag.isBackgroundSyncEnabled && syncingWalletTypes.contains(widget.appStore.wallet?.type)) {
|
||||
widget.appStore.wallet?.stopSync();
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ import 'package:cake_wallet/utils/request_review_handler.dart';
|
|||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/send/output.dart';
|
||||
import 'package:cw_core/unspent_coin_type.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
|
||||
|
@ -508,6 +509,10 @@ class SendPage extends BasePage {
|
|||
if (state is TransactionCommitted) {
|
||||
newContactAddress =
|
||||
newContactAddress ?? sendViewModel.newContactAddress();
|
||||
|
||||
if (sendViewModel.coinTypeToSpendFrom != UnspentCoinType.any) {
|
||||
newContactAddress = null;
|
||||
}
|
||||
|
||||
final successMessage = S.of(_dialogContext).send_success(
|
||||
sendViewModel.selectedCryptoCurrency.toString());
|
||||
|
|
|
@ -14,7 +14,6 @@ import 'package:cake_wallet/view_model/send/output.dart';
|
|||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:keyboard_actions/keyboard_actions.dart';
|
||||
|
@ -373,7 +372,10 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
|||
padding: EdgeInsets.only(top: 6),
|
||||
child: GestureDetector(
|
||||
key: ValueKey('send_page_unspent_coin_button_key'),
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.unspentCoinsList),
|
||||
onTap: () => Navigator.of(context).pushNamed(
|
||||
Routes.unspentCoinsList,
|
||||
arguments: widget.sendViewModel.coinTypeToSpendFrom,
|
||||
),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
|
|
|
@ -4,7 +4,6 @@ import 'package:cake_wallet/src/screens/base_page.dart';
|
|||
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
|
||||
import 'package:cake_wallet/view_model/settings/mweb_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/silent_payments_settings_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
|
@ -33,9 +32,9 @@ class MwebSettingsPage extends BasePage {
|
|||
),
|
||||
SettingsSwitcherCell(
|
||||
title: S.current.litecoin_mweb_always_scan,
|
||||
value: _mwebSettingsViewModel.mwebAlwaysScan,
|
||||
value: _mwebSettingsViewModel.mwebEnabled,
|
||||
onValueChange: (_, bool value) {
|
||||
_mwebSettingsViewModel.setMwebAlwaysScan(value);
|
||||
_mwebSettingsViewModel.setMwebEnabled(value);
|
||||
},
|
||||
),
|
||||
SettingsCellWithArrow(
|
||||
|
|
|
@ -1,27 +1,49 @@
|
|||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TextFieldListRow extends StatelessWidget {
|
||||
class TextFieldListRow extends StatefulWidget {
|
||||
TextFieldListRow(
|
||||
{required this.title,
|
||||
required this.value,
|
||||
this.titleFontSize = 14,
|
||||
this.valueFontSize = 16,
|
||||
this.onSubmitted,
|
||||
this.onTapOutside})
|
||||
: _textController = TextEditingController() {
|
||||
_textController.text = value;
|
||||
}
|
||||
this.onSubmitted});
|
||||
|
||||
final String title;
|
||||
final String value;
|
||||
final double titleFontSize;
|
||||
final double valueFontSize;
|
||||
final Function(String value)? onSubmitted;
|
||||
final Function(String value)? onTapOutside;
|
||||
final TextEditingController _textController;
|
||||
|
||||
@override
|
||||
_TextFieldListRowState createState() => _TextFieldListRowState();
|
||||
}
|
||||
|
||||
class _TextFieldListRowState extends State<TextFieldListRow> {
|
||||
late TextEditingController _textController;
|
||||
late FocusNode _focusNode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_textController = TextEditingController(text: widget.value);
|
||||
_focusNode = FocusNode();
|
||||
|
||||
_focusNode.addListener(() {
|
||||
if (!_focusNode.hasFocus) {
|
||||
widget.onSubmitted?.call(_textController.text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_textController.dispose();
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -29,41 +51,48 @@ class TextFieldListRow extends StatelessWidget {
|
|||
width: double.infinity,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24),
|
||||
padding: const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(title,
|
||||
style: TextStyle(
|
||||
fontSize: titleFontSize,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor),
|
||||
textAlign: TextAlign.left),
|
||||
TextField(
|
||||
controller: _textController,
|
||||
keyboardType: TextInputType.multiline,
|
||||
textInputAction: TextInputAction.done,
|
||||
maxLines: null,
|
||||
textAlign: TextAlign.start,
|
||||
style: TextStyle(
|
||||
fontSize: valueFontSize,
|
||||
fontWeight: FontWeight.w500,
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
contentPadding: EdgeInsets.only(top: 12, bottom: 0),
|
||||
hintText: S.of(context).enter_your_note,
|
||||
hintStyle: TextStyle(
|
||||
fontSize: valueFontSize,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor),
|
||||
border: InputBorder.none),
|
||||
onTapOutside: (_) => onTapOutside?.call(_textController.text),
|
||||
onSubmitted: (value) => onSubmitted?.call(value),
|
||||
)
|
||||
]),
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
widget.title,
|
||||
style: TextStyle(
|
||||
fontSize: widget.titleFontSize,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
TextField(
|
||||
controller: _textController,
|
||||
focusNode: _focusNode,
|
||||
keyboardType: TextInputType.multiline,
|
||||
textInputAction: TextInputAction.done,
|
||||
maxLines: null,
|
||||
textAlign: TextAlign.start,
|
||||
style: TextStyle(
|
||||
fontSize: widget.valueFontSize,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
contentPadding: EdgeInsets.only(top: 12, bottom: 0),
|
||||
hintText: S.of(context).enter_your_note,
|
||||
hintStyle: TextStyle(
|
||||
fontSize: widget.valueFontSize,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||
),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onSubmitted: (value) {
|
||||
widget.onSubmitted?.call(value);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -44,7 +44,6 @@ class UnspentCoinsDetailsPage extends BasePage {
|
|||
return TextFieldListRow(
|
||||
title: item.title,
|
||||
value: item.value,
|
||||
onTapOutside: item.onSubmitted,
|
||||
onSubmitted: item.onSubmitted,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
|||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
|
||||
class WalletEditPage extends BasePage {
|
||||
WalletEditPage({
|
||||
required this.pageArguments,
|
||||
|
@ -86,8 +85,9 @@ class WalletEditPage extends BasePage {
|
|||
child: LoadingPrimaryButton(
|
||||
onPressed: () async {
|
||||
if (_formKey.currentState?.validate() ?? false) {
|
||||
if (pageArguments.walletNewVM!
|
||||
.nameExists(walletEditViewModel.newName)) {
|
||||
if (!pageArguments.isWalletGroup &&
|
||||
pageArguments.walletNewVM!
|
||||
.nameExists(walletEditViewModel.newName)) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
|
|
|
@ -31,7 +31,6 @@ class SettingActions {
|
|||
walletSettingAction,
|
||||
addressBookSettingAction,
|
||||
silentPaymentsSettingAction,
|
||||
litecoinMwebSettingAction,
|
||||
securityBackupSettingAction,
|
||||
privacySettingAction,
|
||||
displaySettingAction,
|
||||
|
@ -49,8 +48,8 @@ class SettingActions {
|
|||
);
|
||||
|
||||
static SettingActions litecoinMwebSettingAction = SettingActions._(
|
||||
name: (context) => S.current.litecoin_mweb_settings,
|
||||
image: 'assets/images/bitcoin_menu.png',
|
||||
name: (context) => S.of(context).litecoin_mweb_settings,
|
||||
image: 'assets/images/litecoin_menu.png',
|
||||
onTap: (BuildContext context) {
|
||||
Navigator.pop(context);
|
||||
Navigator.of(context).pushNamed(Routes.mwebSettings);
|
||||
|
|
|
@ -37,7 +37,8 @@ abstract class AppStoreBase with Store {
|
|||
@action
|
||||
Future<void> changeCurrentWallet(
|
||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet) async {
|
||||
this.wallet?.close();
|
||||
bool changingToSameWalletType = this.wallet?.type == wallet.type;
|
||||
this.wallet?.close(shouldCleanup: !changingToSameWalletType);
|
||||
this.wallet = wallet;
|
||||
this.wallet!.setExceptionHandler(ExceptionHandler.onError);
|
||||
|
||||
|
|
|
@ -15,12 +15,14 @@ class CWTron extends Tron {
|
|||
String? password,
|
||||
String? mnemonic,
|
||||
String? parentAddress,
|
||||
String? passphrase,
|
||||
}) =>
|
||||
TronNewWalletCredentials(
|
||||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
password: password,
|
||||
mnemonic: mnemonic,
|
||||
passphrase: passphrase,
|
||||
parentAddress: parentAddress);
|
||||
|
||||
@override
|
||||
|
@ -28,8 +30,14 @@ class CWTron extends Tron {
|
|||
required String name,
|
||||
required String mnemonic,
|
||||
required String password,
|
||||
String? passphrase,
|
||||
}) =>
|
||||
TronRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic);
|
||||
TronRestoreWalletFromSeedCredentials(
|
||||
name: name,
|
||||
password: password,
|
||||
mnemonic: mnemonic,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
@override
|
||||
WalletCredentials createTronRestoreWalletFromPrivateKey({
|
||||
|
|
|
@ -9,15 +9,12 @@ class BrightnessUtil {
|
|||
return;
|
||||
}
|
||||
|
||||
// Get the current brightness:
|
||||
final brightness = await DeviceDisplayBrightness.getBrightness();
|
||||
|
||||
// ignore: unawaited_futures
|
||||
DeviceDisplayBrightness.setBrightness(1.0);
|
||||
|
||||
await func();
|
||||
|
||||
// ignore: unawaited_futures
|
||||
DeviceDisplayBrightness.setBrightness(brightness);
|
||||
DeviceDisplayBrightness.resetBrightness();
|
||||
}
|
||||
}
|
|
@ -75,6 +75,9 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
|
|||
WalletType.bitcoin,
|
||||
WalletType.litecoin,
|
||||
WalletType.bitcoinCash,
|
||||
WalletType.ethereum,
|
||||
WalletType.polygon,
|
||||
WalletType.tron,
|
||||
].contains(type);
|
||||
|
||||
@computed
|
||||
|
|
|
@ -26,22 +26,23 @@ abstract class ContactListViewModelBase with Store {
|
|||
isAutoGenerateEnabled =
|
||||
settingsStore.autoGenerateSubaddressStatus == AutoGenerateSubaddressStatus.enabled {
|
||||
walletInfoSource.values.forEach((info) {
|
||||
if (isAutoGenerateEnabled && info.type == WalletType.monero && info.addressInfos != null) {
|
||||
final key = info.addressInfos!.keys.first;
|
||||
final value = info.addressInfos![key];
|
||||
final address = value?.first;
|
||||
if (address != null) {
|
||||
final name = _createName(info.name, address.label);
|
||||
walletContacts.add(WalletContact(
|
||||
address.address,
|
||||
name,
|
||||
walletTypeToCryptoCurrency(info.type),
|
||||
));
|
||||
if ([WalletType.monero, WalletType.wownero, WalletType.haven].contains(info.type) && info.addressInfos != null) {
|
||||
for (var key in info.addressInfos!.keys) {
|
||||
final value = info.addressInfos![key];
|
||||
final address = value?.first;
|
||||
if (address != null) {
|
||||
final name = _createName(info.name, address.label, key: key);
|
||||
walletContacts.add(WalletContact(
|
||||
address.address,
|
||||
name,
|
||||
walletTypeToCryptoCurrency(info.type),
|
||||
));
|
||||
}
|
||||
}
|
||||
} else if (info.addresses?.isNotEmpty == true && info.addresses!.length > 1) {
|
||||
if ([WalletType.monero, WalletType.wownero, WalletType.haven].contains(info.type)) {
|
||||
final address = info.address;
|
||||
final name = _createName(info.name, "");
|
||||
final name = _createName(info.name, "", key: 0);
|
||||
walletContacts.add(WalletContact(
|
||||
address,
|
||||
name,
|
||||
|
@ -52,7 +53,7 @@ abstract class ContactListViewModelBase with Store {
|
|||
if (label.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final name = _createName(info.name, label);
|
||||
final name = _createName(info.name, label, key: null);
|
||||
walletContacts.add(WalletContact(
|
||||
address,
|
||||
name,
|
||||
|
@ -65,7 +66,7 @@ abstract class ContactListViewModelBase with Store {
|
|||
} else {
|
||||
walletContacts.add(WalletContact(
|
||||
info.address,
|
||||
info.name,
|
||||
_createName(info.name, "", key: [WalletType.monero, WalletType.wownero, WalletType.haven].contains(info.type) ? 0 : null),
|
||||
walletTypeToCryptoCurrency(info.type),
|
||||
));
|
||||
}
|
||||
|
@ -76,10 +77,9 @@ abstract class ContactListViewModelBase with Store {
|
|||
initialFire: true);
|
||||
}
|
||||
|
||||
String _createName(String walletName, String label) {
|
||||
return label.isNotEmpty
|
||||
? '$walletName (${label.replaceAll(RegExp(r'active', caseSensitive: false), S.current.active).replaceAll(RegExp(r'silent payments', caseSensitive: false), S.current.silent_payments)})'
|
||||
: walletName;
|
||||
String _createName(String walletName, String label, {int? key = null}) {
|
||||
final actualLabel = label.replaceAll(RegExp(r'active', caseSensitive: false), S.current.active).replaceAll(RegExp(r'silent payments', caseSensitive: false), S.current.silent_payments);
|
||||
return '$walletName${key == null ? "" : " [#${key}]"} ${actualLabel.isNotEmpty ? "($actualLabel)" : ""}'.trim();
|
||||
}
|
||||
|
||||
final bool isAutoGenerateEnabled;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||
import 'package:cake_wallet/entities/sort_balance_types.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
|
@ -31,6 +32,7 @@ class BalanceRecord {
|
|||
required this.fiatSecondAdditionalBalance,
|
||||
required this.asset,
|
||||
required this.formattedAssetTitle});
|
||||
|
||||
final String fiatAdditionalBalance;
|
||||
final String fiatAvailableBalance;
|
||||
final String fiatFrozenBalance;
|
||||
|
@ -53,7 +55,22 @@ abstract class BalanceViewModelBase with Store {
|
|||
: isReversing = false,
|
||||
isShowCard = appStore.wallet!.walletInfo.isShowIntroCakePayCard,
|
||||
wallet = appStore.wallet! {
|
||||
reaction((_) => appStore.wallet, _onWalletChange);
|
||||
reaction((_) => appStore.wallet, (wallet) {
|
||||
_onWalletChange(wallet);
|
||||
_checkMweb();
|
||||
});
|
||||
|
||||
_checkMweb();
|
||||
|
||||
reaction((_) => settingsStore.mwebAlwaysScan, (bool value) {
|
||||
_checkMweb();
|
||||
});
|
||||
}
|
||||
|
||||
void _checkMweb() {
|
||||
if (wallet.type == WalletType.litecoin) {
|
||||
mwebEnabled = bitcoin!.getMwebEnabled(wallet);
|
||||
}
|
||||
}
|
||||
|
||||
final AppStore appStore;
|
||||
|
@ -336,14 +353,19 @@ abstract class BalanceViewModelBase with Store {
|
|||
});
|
||||
}
|
||||
|
||||
@observable
|
||||
bool mwebEnabled = false;
|
||||
|
||||
@computed
|
||||
bool get hasAdditionalBalance => _hasAdditionalBalanceForWalletType(wallet.type);
|
||||
|
||||
@computed
|
||||
bool get hasSecondAdditionalBalance => _hasSecondAdditionalBalanceForWalletType(wallet.type);
|
||||
bool get hasSecondAdditionalBalance =>
|
||||
mwebEnabled && _hasSecondAdditionalBalanceForWalletType(wallet.type);
|
||||
|
||||
@computed
|
||||
bool get hasSecondAvailableBalance => _hasSecondAvailableBalanceForWalletType(wallet.type);
|
||||
bool get hasSecondAvailableBalance =>
|
||||
mwebEnabled && _hasSecondAvailableBalanceForWalletType(wallet.type);
|
||||
|
||||
bool _hasAdditionalBalanceForWalletType(WalletType type) {
|
||||
switch (type) {
|
||||
|
@ -358,14 +380,16 @@ abstract class BalanceViewModelBase with Store {
|
|||
}
|
||||
|
||||
bool _hasSecondAdditionalBalanceForWalletType(WalletType type) {
|
||||
if (wallet.type == WalletType.litecoin && settingsStore.mwebEnabled) {
|
||||
return true;
|
||||
if (wallet.type == WalletType.litecoin) {
|
||||
if ((wallet.balance[CryptoCurrency.ltc]?.secondAdditional ?? 0) > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _hasSecondAvailableBalanceForWalletType(WalletType type) {
|
||||
if (wallet.type == WalletType.litecoin && settingsStore.mwebEnabled) {
|
||||
if (wallet.type == WalletType.litecoin) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
|
@ -222,7 +224,10 @@ abstract class DashboardViewModelBase with Store {
|
|||
// subname = nano!.getCurrentAccount(_wallet).label;
|
||||
// }
|
||||
|
||||
reaction((_) => appStore.wallet, _onWalletChange);
|
||||
reaction((_) => appStore.wallet, (wallet) {
|
||||
_onWalletChange(wallet);
|
||||
_checkMweb();
|
||||
});
|
||||
|
||||
connectMapToListWithTransform(
|
||||
appStore.wallet!.transactionHistory.transactions,
|
||||
|
@ -256,14 +261,16 @@ abstract class DashboardViewModelBase with Store {
|
|||
});
|
||||
}
|
||||
|
||||
_checkMweb();
|
||||
reaction((_) => settingsStore.mwebAlwaysScan, (bool value) {
|
||||
_checkMweb();
|
||||
});
|
||||
}
|
||||
|
||||
void _checkMweb() {
|
||||
if (hasMweb) {
|
||||
mwebScanningActive = bitcoin!.getMwebEnabled(wallet);
|
||||
settingsStore.mwebEnabled = mwebScanningActive;
|
||||
reaction((_) => settingsStore.mwebAlwaysScan, (bool alwaysScan) {
|
||||
if (alwaysScan) {
|
||||
mwebScanningActive = true;
|
||||
}
|
||||
});
|
||||
mwebEnabled = bitcoin!.getMwebEnabled(wallet);
|
||||
balanceViewModel.mwebEnabled = mwebEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -428,30 +435,37 @@ abstract class DashboardViewModelBase with Store {
|
|||
}
|
||||
|
||||
@computed
|
||||
bool get hasMweb => wallet.type == WalletType.litecoin;
|
||||
bool get hasMweb => wallet.type == WalletType.litecoin && (Platform.isIOS || Platform.isAndroid);
|
||||
|
||||
@computed
|
||||
bool get showMwebCard => hasMweb && settingsStore.mwebCardDisplay;
|
||||
bool get showMwebCard => hasMweb && settingsStore.mwebCardDisplay && !mwebEnabled;
|
||||
|
||||
@observable
|
||||
bool mwebScanningActive = false;
|
||||
bool mwebEnabled = false;
|
||||
|
||||
@computed
|
||||
bool get hasEnabledMwebBefore => settingsStore.hasEnabledMwebBefore;
|
||||
|
||||
@action
|
||||
void setMwebScanningActive(bool active) {
|
||||
void setMwebEnabled() {
|
||||
if (!hasMweb) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (active) {
|
||||
settingsStore.hasEnabledMwebBefore = true;
|
||||
}
|
||||
settingsStore.hasEnabledMwebBefore = true;
|
||||
mwebEnabled = true;
|
||||
bitcoin!.setMwebEnabled(wallet, true);
|
||||
balanceViewModel.mwebEnabled = true;
|
||||
settingsStore.mwebAlwaysScan = true;
|
||||
}
|
||||
|
||||
settingsStore.mwebEnabled = active;
|
||||
mwebScanningActive = active;
|
||||
bitcoin!.setMwebEnabled(wallet, active);
|
||||
@action
|
||||
void dismissMweb() {
|
||||
settingsStore.mwebCardDisplay = false;
|
||||
balanceViewModel.mwebEnabled = false;
|
||||
settingsStore.mwebAlwaysScan = false;
|
||||
mwebEnabled = false;
|
||||
bitcoin!.setMwebEnabled(wallet, false);
|
||||
}
|
||||
|
||||
BalanceViewModel balanceViewModel;
|
||||
|
|
|
@ -65,15 +65,16 @@ class LinkViewModel {
|
|||
if (isNanoGptLink) {
|
||||
switch (currentLink?.authority ?? '') {
|
||||
case "exchange":
|
||||
case "send":
|
||||
return PaymentRequest.fromUri(currentLink);
|
||||
case "send":
|
||||
return {"paymentRequest": PaymentRequest.fromUri(currentLink)};
|
||||
case "buy":
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (_isValidPaymentUri) {
|
||||
return PaymentRequest.fromUri(currentLink);
|
||||
return {"paymentRequest": PaymentRequest.fromUri(currentLink)};
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
@ -13,7 +13,7 @@ import 'package:hive/hive.dart';
|
|||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cake_wallet/core/generate_wallet_password.dart';
|
||||
import 'package:cake_wallet/core/wallet_creation_service.dart';
|
||||
import 'package:cw_core/wallet_credentials.dart';
|
||||
|
@ -26,14 +26,19 @@ part 'restore_from_qr_vm.g.dart';
|
|||
class WalletRestorationFromQRVM = WalletRestorationFromQRVMBase with _$WalletRestorationFromQRVM;
|
||||
|
||||
abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store {
|
||||
WalletRestorationFromQRVMBase(AppStore appStore, WalletCreationService walletCreationService,
|
||||
Box<WalletInfo> walletInfoSource, WalletType type, SeedSettingsViewModel seedSettingsViewModel)
|
||||
WalletRestorationFromQRVMBase(
|
||||
AppStore appStore,
|
||||
WalletCreationService walletCreationService,
|
||||
Box<WalletInfo> walletInfoSource,
|
||||
WalletType type,
|
||||
SeedSettingsViewModel seedSettingsViewModel)
|
||||
: height = 0,
|
||||
viewKey = '',
|
||||
spendKey = '',
|
||||
wif = '',
|
||||
address = '',
|
||||
super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel, type: type, isRecovery: true);
|
||||
super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel,
|
||||
type: type, isRecovery: true);
|
||||
|
||||
@observable
|
||||
int height;
|
||||
|
@ -53,13 +58,10 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
|
|||
bool get hasRestorationHeight => type == WalletType.monero || type == WalletType.wownero;
|
||||
|
||||
@override
|
||||
WalletCredentials getCredentialsFromRestoredWallet(
|
||||
dynamic options, RestoredWallet restoreWallet) {
|
||||
Future<WalletCredentials> getWalletCredentialsFromQRCredentials(
|
||||
RestoredWallet restoreWallet) async {
|
||||
final password = generateWalletPassword();
|
||||
|
||||
DerivationInfo? derivationInfo;
|
||||
derivationInfo ??= getDefaultCreateDerivation();
|
||||
|
||||
switch (restoreWallet.restoreMode) {
|
||||
case WalletRestoreMode.keys:
|
||||
switch (restoreWallet.type) {
|
||||
|
@ -111,12 +113,20 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
|
|||
);
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
|
||||
final derivationInfoList = await getDerivationInfoFromQRCredentials(restoreWallet);
|
||||
DerivationInfo derivationInfo;
|
||||
if (derivationInfoList.isEmpty) {
|
||||
derivationInfo = getDefaultCreateDerivation()!;
|
||||
} else {
|
||||
derivationInfo = derivationInfoList.first;
|
||||
}
|
||||
return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
|
||||
name: name,
|
||||
mnemonic: restoreWallet.mnemonicSeed ?? '',
|
||||
password: password,
|
||||
passphrase: restoreWallet.passphrase,
|
||||
derivationType: derivationInfo!.derivationType!,
|
||||
derivationType: derivationInfo.derivationType!,
|
||||
derivationPath: derivationInfo.derivationPath!,
|
||||
);
|
||||
case WalletType.bitcoinCash:
|
||||
|
@ -124,26 +134,46 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
|
|||
name: name,
|
||||
mnemonic: restoreWallet.mnemonicSeed ?? '',
|
||||
password: password,
|
||||
passphrase: restoreWallet.passphrase,
|
||||
);
|
||||
case WalletType.ethereum:
|
||||
return ethereum!.createEthereumRestoreWalletFromSeedCredentials(
|
||||
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
|
||||
name: name,
|
||||
mnemonic: restoreWallet.mnemonicSeed ?? '',
|
||||
password: password,
|
||||
passphrase: restoreWallet.passphrase,
|
||||
);
|
||||
case WalletType.nano:
|
||||
final derivationInfo =
|
||||
(await getDerivationInfoFromQRCredentials(restoreWallet)).first;
|
||||
return nano!.createNanoRestoreWalletFromSeedCredentials(
|
||||
name: name,
|
||||
mnemonic: restoreWallet.mnemonicSeed ?? '',
|
||||
password: password,
|
||||
derivationType: derivationInfo!.derivationType!,
|
||||
derivationType: derivationInfo.derivationType!,
|
||||
passphrase: restoreWallet.passphrase,
|
||||
);
|
||||
case WalletType.polygon:
|
||||
return polygon!.createPolygonRestoreWalletFromSeedCredentials(
|
||||
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
|
||||
name: name,
|
||||
mnemonic: restoreWallet.mnemonicSeed ?? '',
|
||||
password: password,
|
||||
passphrase: restoreWallet.passphrase,
|
||||
);
|
||||
case WalletType.solana:
|
||||
return solana!.createSolanaRestoreWalletFromSeedCredentials(
|
||||
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
|
||||
name: name,
|
||||
mnemonic: restoreWallet.mnemonicSeed ?? '',
|
||||
password: password,
|
||||
passphrase: restoreWallet.passphrase,
|
||||
);
|
||||
case WalletType.tron:
|
||||
return tron!.createTronRestoreWalletFromSeedCredentials(
|
||||
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
|
||||
name: name,
|
||||
mnemonic: restoreWallet.mnemonicSeed ?? '',
|
||||
password: password,
|
||||
passphrase: restoreWallet.passphrase,
|
||||
);
|
||||
case WalletType.wownero:
|
||||
return wownero!.createWowneroRestoreWalletFromSeedCredentials(
|
||||
name: name,
|
||||
|
@ -160,8 +190,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:
|
||||
|
|
|
@ -142,6 +142,10 @@ class WalletRestoreFromQRCode {
|
|||
return WalletRestoreMode.seed;
|
||||
}
|
||||
|
||||
if ((type == WalletType.monero || type == WalletType.wownero)) {
|
||||
return WalletRestoreMode.seed;
|
||||
}
|
||||
|
||||
seedValue.split(' ').forEach((element) {
|
||||
if (!words.contains(element)) {
|
||||
throw Exception(
|
||||
|
|
|
@ -20,6 +20,7 @@ import 'package:cake_wallet/wownero/wownero.dart';
|
|||
import 'package:cw_core/exceptions.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/unspent_coin_type.dart';
|
||||
import 'package:cake_wallet/view_model/send/output.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
@ -67,8 +68,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
this.balanceViewModel,
|
||||
this.contactListViewModel,
|
||||
this.transactionDescriptionBox,
|
||||
this.ledgerViewModel,
|
||||
) : state = InitialExecutionState(),
|
||||
this.ledgerViewModel, {
|
||||
this.coinTypeToSpendFrom = UnspentCoinType.any,
|
||||
}) : state = InitialExecutionState(),
|
||||
currencies = appStore.wallet!.balance.keys.toList(),
|
||||
selectedCryptoCurrency = appStore.wallet!.currency,
|
||||
hasMultipleTokens = isEVMCompatibleChain(appStore.wallet!.type) ||
|
||||
|
@ -97,6 +99,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
|
||||
ObservableList<Output> outputs;
|
||||
|
||||
final UnspentCoinType coinTypeToSpendFrom;
|
||||
|
||||
@action
|
||||
void addOutput() {
|
||||
outputs
|
||||
|
@ -119,7 +123,17 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
@computed
|
||||
bool get isBatchSending => outputs.length > 1;
|
||||
|
||||
bool get shouldDisplaySendALL => walletType != WalletType.solana;
|
||||
bool get shouldDisplaySendALL {
|
||||
if (walletType == WalletType.solana) return false;
|
||||
|
||||
if (walletType == WalletType.ethereum && selectedCryptoCurrency == CryptoCurrency.eth)
|
||||
return false;
|
||||
|
||||
if (walletType == WalletType.polygon && selectedCryptoCurrency == CryptoCurrency.matic)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@computed
|
||||
String get pendingTransactionFiatAmount {
|
||||
|
@ -217,7 +231,14 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
PendingTransaction? pendingTransaction;
|
||||
|
||||
@computed
|
||||
String get balance => wallet.balance[selectedCryptoCurrency]!.formattedFullAvailableBalance;
|
||||
String get balance {
|
||||
if (coinTypeToSpendFrom == UnspentCoinType.mweb) {
|
||||
return balanceViewModel.balances.values.first.secondAvailableBalance;
|
||||
} else if (coinTypeToSpendFrom == UnspentCoinType.nonMweb) {
|
||||
return balanceViewModel.balances.values.first.availableBalance;
|
||||
}
|
||||
return wallet.balance[selectedCryptoCurrency]!.formattedFullAvailableBalance;
|
||||
}
|
||||
|
||||
@computed
|
||||
bool get isFiatDisabled => balanceViewModel.isFiatDisabled;
|
||||
|
@ -461,12 +482,18 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
nano!.updateTransactions(wallet);
|
||||
}
|
||||
|
||||
|
||||
if (pendingTransaction!.id.isNotEmpty) {
|
||||
|
||||
final descriptionKey = '${pendingTransaction!.id}_${wallet.walletAddresses.primaryAddress}';
|
||||
_settingsStore.shouldSaveRecipientAddress
|
||||
? await transactionDescriptionBox.add(TransactionDescription(
|
||||
id: pendingTransaction!.id, recipientAddress: address, transactionNote: note))
|
||||
: await transactionDescriptionBox
|
||||
.add(TransactionDescription(id: pendingTransaction!.id, transactionNote: note));
|
||||
id: descriptionKey,
|
||||
recipientAddress: address,
|
||||
transactionNote: note))
|
||||
: await transactionDescriptionBox.add(TransactionDescription(
|
||||
id: descriptionKey,
|
||||
transactionNote: note));
|
||||
}
|
||||
|
||||
state = TransactionCommitted();
|
||||
|
@ -494,8 +521,12 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
case WalletType.bitcoinCash:
|
||||
return bitcoin!.createBitcoinTransactionCredentials(outputs,
|
||||
priority: priority!, feeRate: customBitcoinFeeRate);
|
||||
return bitcoin!.createBitcoinTransactionCredentials(
|
||||
outputs,
|
||||
priority: priority!,
|
||||
feeRate: customBitcoinFeeRate,
|
||||
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
||||
);
|
||||
|
||||
case WalletType.monero:
|
||||
return monero!
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue