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

 Conflicts:
	cw_bitcoin/lib/bitcoin_wallet.dart
	cw_bitcoin/lib/bitcoin_wallet_service.dart
	cw_bitcoin/lib/electrum_transaction_history.dart
	cw_bitcoin/lib/electrum_wallet.dart
	cw_bitcoin/pubspec.lock
	cw_monero/ios/Classes/monero_api.cpp
	lib/bitcoin/cw_bitcoin.dart
	lib/core/wallet_creation_service.dart
	lib/di.dart
	lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart
	lib/src/screens/dashboard/pages/cake_features_page.dart
	lib/src/screens/settings/security_backup_page.dart
	model_generator.sh
	tool/configure.dart
This commit is contained in:
OmarHatem 2024-05-29 18:39:01 +03:00
commit 5571536c96
132 changed files with 4190 additions and 1455 deletions

View file

@ -42,7 +42,7 @@ jobs:
- name: Flutter action
uses: subosito/flutter-action@v1
with:
flutter-version: "3.19.5"
flutter-version: "3.19.6"
channel: stable
- name: Install package dependencies

65
assets/images/cards.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 158 KiB

BIN
assets/images/tbtc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -1,3 +1,2 @@
Add Tron wallet
Hardware wallets enhancements
Bug fixes
Bitcoin Silent Payments
Bug fixes and generic enhancements

View file

@ -3,8 +3,8 @@ import 'dart:convert';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/script_hash.dart' as sh;
class BitcoinAddressRecord {
BitcoinAddressRecord(
abstract class BaseBitcoinAddressRecord {
BaseBitcoinAddressRecord(
this.address, {
required this.index,
this.isHidden = false,
@ -13,15 +13,62 @@ class BitcoinAddressRecord {
String name = '',
bool isUsed = false,
required this.type,
String? scriptHash,
required this.network,
}) : _txCount = txCount,
_balance = balance,
_name = name,
_isUsed = isUsed,
scriptHash = scriptHash ?? sh.scriptHash(address, network: network);
_isUsed = isUsed;
factory BitcoinAddressRecord.fromJSON(String jsonSource, BasedUtxoNetwork network) {
@override
bool operator ==(Object o) => o is BaseBitcoinAddressRecord && address == o.address;
final String address;
bool isHidden;
final int index;
int _txCount;
int _balance;
String _name;
bool _isUsed;
BasedUtxoNetwork? network;
int get txCount => _txCount;
String get name => _name;
int get balance => _balance;
set txCount(int value) => _txCount = value;
set balance(int value) => _balance = value;
bool get isUsed => _isUsed;
void setAsUsed() => _isUsed = true;
void setNewName(String label) => _name = label;
int get hashCode => address.hashCode;
BitcoinAddressType type;
String toJSON();
}
class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
BitcoinAddressRecord(
super.address, {
required super.index,
super.isHidden = false,
super.txCount = 0,
super.balance = 0,
super.name = '',
super.isUsed = false,
required super.type,
String? scriptHash,
required super.network,
}) : scriptHash =
scriptHash ?? (network != null ? sh.scriptHash(address, network: network) : null);
factory BitcoinAddressRecord.fromJSON(String jsonSource, {BasedUtxoNetwork? network}) {
final decoded = json.decode(jsonSource) as Map;
return BitcoinAddressRecord(
@ -41,44 +88,15 @@ class BitcoinAddressRecord {
);
}
@override
bool operator ==(Object o) => o is BitcoinAddressRecord && address == o.address;
final String address;
bool isHidden;
final int index;
int _txCount;
int _balance;
String _name;
bool _isUsed;
String? scriptHash;
BasedUtxoNetwork network;
int get txCount => _txCount;
String get name => _name;
int get balance => _balance;
set txCount(int value) => _txCount = value;
set balance(int value) => _balance = value;
bool get isUsed => _isUsed;
void setAsUsed() => _isUsed = true;
void setNewName(String label) => _name = label;
@override
int get hashCode => address.hashCode;
BitcoinAddressType type;
String updateScriptHash(BasedUtxoNetwork network) {
String getScriptHash(BasedUtxoNetwork network) {
if (scriptHash != null) return scriptHash!;
scriptHash = sh.scriptHash(address, network: network);
return scriptHash!;
}
@override
String toJSON() => json.encode({
'address': address,
'index': index,
@ -91,3 +109,57 @@ class BitcoinAddressRecord {
'scriptHash': scriptHash,
});
}
class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
BitcoinSilentPaymentAddressRecord(
super.address, {
required super.index,
super.isHidden = false,
super.txCount = 0,
super.balance = 0,
super.name = '',
super.isUsed = false,
required this.silentPaymentTweak,
required super.network,
required super.type,
}) : super();
factory BitcoinSilentPaymentAddressRecord.fromJSON(String jsonSource,
{BasedUtxoNetwork? network}) {
final decoded = json.decode(jsonSource) as Map;
return BitcoinSilentPaymentAddressRecord(
decoded['address'] as String,
index: decoded['index'] as int,
isHidden: decoded['isHidden'] as bool? ?? false,
isUsed: decoded['isUsed'] as bool? ?? false,
txCount: decoded['txCount'] as int? ?? 0,
name: decoded['name'] as String? ?? '',
balance: decoded['balance'] as int? ?? 0,
network: (decoded['network'] as String?) == null
? network
: BasedUtxoNetwork.fromName(decoded['network'] as String),
silentPaymentTweak: decoded['silent_payment_tweak'] as String?,
type: decoded['type'] != null && decoded['type'] != ''
? BitcoinAddressType.values
.firstWhere((type) => type.toString() == decoded['type'] as String)
: SilentPaymentsAddresType.p2sp,
);
}
final String? silentPaymentTweak;
@override
String toJSON() => json.encode({
'address': address,
'index': index,
'isHidden': isHidden,
'isUsed': isUsed,
'txCount': txCount,
'name': name,
'balance': balance,
'type': type.toString(),
'network': network?.value,
'silent_payment_tweak': silentPaymentTweak,
});
}

View file

@ -8,6 +8,8 @@ class BitcoinReceivePageOption implements ReceivePageOption {
static const p2wsh = BitcoinReceivePageOption._('Segwit (P2WSH)');
static const p2pkh = BitcoinReceivePageOption._('Legacy (P2PKH)');
static const silent_payments = BitcoinReceivePageOption._('Silent Payments');
const BitcoinReceivePageOption._(this.value);
final String value;
@ -17,6 +19,7 @@ class BitcoinReceivePageOption implements ReceivePageOption {
}
static const all = [
BitcoinReceivePageOption.silent_payments,
BitcoinReceivePageOption.p2wpkh,
BitcoinReceivePageOption.p2tr,
BitcoinReceivePageOption.p2wsh,
@ -24,6 +27,24 @@ class BitcoinReceivePageOption implements ReceivePageOption {
BitcoinReceivePageOption.p2pkh
];
BitcoinAddressType toType() {
switch (this) {
case BitcoinReceivePageOption.p2tr:
return SegwitAddresType.p2tr;
case BitcoinReceivePageOption.p2wsh:
return SegwitAddresType.p2wsh;
case BitcoinReceivePageOption.p2pkh:
return P2pkhAddressType.p2pkh;
case BitcoinReceivePageOption.p2sh:
return P2shAddressType.p2wpkhInP2sh;
case BitcoinReceivePageOption.silent_payments:
return SilentPaymentsAddresType.p2sp;
case BitcoinReceivePageOption.p2wpkh:
default:
return SegwitAddresType.p2wpkh;
}
}
factory BitcoinReceivePageOption.fromType(BitcoinAddressType type) {
switch (type) {
case SegwitAddresType.p2tr:
@ -34,6 +55,8 @@ class BitcoinReceivePageOption implements ReceivePageOption {
return BitcoinReceivePageOption.p2pkh;
case P2shAddressType.p2wpkhInP2sh:
return BitcoinReceivePageOption.p2sh;
case SilentPaymentsAddresType.p2sp:
return BitcoinReceivePageOption.silent_payments;
case SegwitAddresType.p2wpkh:
default:
return BitcoinReceivePageOption.p2wpkh;

View file

@ -2,13 +2,66 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_core/unspent_transaction_output.dart';
class BitcoinUnspent extends Unspent {
BitcoinUnspent(BitcoinAddressRecord addressRecord, String hash, int value, int vout)
BitcoinUnspent(BaseBitcoinAddressRecord addressRecord, String hash, int value, int vout)
: bitcoinAddressRecord = addressRecord,
super(addressRecord.address, hash, value, vout, null);
factory BitcoinUnspent.fromJSON(BitcoinAddressRecord address, Map<String, dynamic> json) =>
factory BitcoinUnspent.fromJSON(BaseBitcoinAddressRecord? address, Map<String, dynamic> json) =>
BitcoinUnspent(
address, json['tx_hash'] as String, json['value'] as int, json['tx_pos'] as int);
address ?? BitcoinAddressRecord.fromJSON(json['address_record'].toString()),
json['tx_hash'] as String,
json['value'] as int,
json['tx_pos'] as int,
);
final BitcoinAddressRecord bitcoinAddressRecord;
Map<String, dynamic> toJson() {
final json = <String, dynamic>{
'address_record': bitcoinAddressRecord.toJSON(),
'tx_hash': hash,
'value': value,
'tx_pos': vout,
};
return json;
}
final BaseBitcoinAddressRecord bitcoinAddressRecord;
}
class BitcoinSilentPaymentsUnspent extends BitcoinUnspent {
BitcoinSilentPaymentsUnspent(
BitcoinSilentPaymentAddressRecord addressRecord,
String hash,
int value,
int vout, {
required this.silentPaymentTweak,
required this.silentPaymentLabel,
}) : super(addressRecord, hash, value, vout);
@override
factory BitcoinSilentPaymentsUnspent.fromJSON(
BitcoinSilentPaymentAddressRecord? address, Map<String, dynamic> json) =>
BitcoinSilentPaymentsUnspent(
address ?? BitcoinSilentPaymentAddressRecord.fromJSON(json['address_record'].toString()),
json['tx_hash'] as String,
json['value'] as int,
json['tx_pos'] as int,
silentPaymentTweak: json['silent_payment_tweak'] as String?,
silentPaymentLabel: json['silent_payment_label'] as String?,
);
@override
Map<String, dynamic> toJson() {
final json = <String, dynamic>{
'address_record': bitcoinAddressRecord.toJSON(),
'tx_hash': hash,
'value': value,
'tx_pos': vout,
'silent_payment_tweak': silentPaymentTweak,
'silent_payment_label': silentPaymentLabel,
};
return json;
}
String? silentPaymentTweak;
String? silentPaymentLabel;
}

View file

@ -41,6 +41,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex,
String? passphrase,
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
int initialSilentAddressIndex = 0,
bool? alwaysScan,
}) : super(
mnemonic: mnemonic,
passphrase: passphrase,
@ -56,8 +59,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: seedBytes,
currency: CryptoCurrency.btc,
encryptionFileUtils: encryptionFileUtils,
currency:
networkParam == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc,
alwaysScan: alwaysScan,
) {
// in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here)
// the sideHd derivation path = m/84'/0'/0'/1 (account 1, index unspecified here)
@ -66,14 +71,18 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
// final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType);
walletAddresses = BitcoinWalletAddresses(
walletInfo,
electrumClient: electrumClient,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
initialSilentAddresses: initialSilentAddresses,
initialSilentAddressIndex: initialSilentAddressIndex,
mainHd: hd,
sideHd: accountHD.derive(1),
network: networkParam ?? network,
masterHd:
seedBytes != null ? bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) : null,
);
autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
});
@ -89,9 +98,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
String? addressPageType,
BasedUtxoNetwork? network,
List<BitcoinAddressRecord>? initialAddresses,
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
ElectrumBalance? initialBalance,
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex,
int initialSilentAddressIndex = 0,
}) async {
late Uint8List seedBytes;
@ -114,6 +125,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses,
initialSilentAddresses: initialSilentAddresses,
initialSilentAddressIndex: initialSilentAddressIndex,
initialBalance: initialBalance,
encryptionFileUtils: encryptionFileUtils,
seedBytes: seedBytes,
@ -130,6 +143,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required String password,
required EncryptionFileUtils encryptionFileUtils,
required bool alwaysScan,
}) async {
final network = walletInfo.network != null
? BasedUtxoNetwork.fromName(walletInfo.network!)
@ -170,6 +184,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses,
initialSilentAddresses: snp.silentAddresses,
initialSilentAddressIndex: snp.silentAddressIndex,
initialBalance: snp.balance,
encryptionFileUtils: encryptionFileUtils,
seedBytes: seedBytes,
@ -177,6 +193,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialChangeAddressIndex: snp.changeAddressIndex,
addressPageType: snp.addressPageType,
networkParam: network,
alwaysScan: alwaysScan,
);
}
@ -187,7 +204,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) {
_ledger = setLedger;
_ledgerDevice = setLedgerDevice;
_bitcoinLedgerApp = BitcoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!);
_bitcoinLedgerApp =
BitcoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!);
}
@override
@ -219,7 +237,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
));
}
final psbt = PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
final psbt =
PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt);
return BtcTransaction.fromRaw(hex.encode(rawHex));

View file

@ -15,10 +15,12 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
required super.mainHd,
required super.sideHd,
required super.network,
required super.electrumClient,
super.initialAddresses,
super.initialRegularAddressIndex,
super.initialChangeAddressIndex,
super.initialSilentAddresses,
super.initialSilentAddressIndex = 0,
super.masterHd,
}) : super(walletInfo);
@override

View file

@ -20,10 +20,11 @@ class BitcoinWalletService extends WalletService<
BitcoinRestoreWalletFromSeedCredentials,
BitcoinRestoreWalletFromWIFCredentials,
BitcoinRestoreWalletFromHardware> {
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.isDirect);
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan, this.isDirect);
final Box<WalletInfo> walletInfoSource;
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
final bool alwaysScan;
final bool isDirect;
@override
@ -62,7 +63,9 @@ class BitcoinWalletService extends WalletService<
name: name,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource,
encryptionFileUtils: encryptionFileUtilsFor(isDirect));
alwaysScan: alwaysScan,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
);
await wallet.init();
saveBackup(name);
return wallet;
@ -73,7 +76,9 @@ class BitcoinWalletService extends WalletService<
name: name,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource,
encryptionFileUtils: encryptionFileUtilsFor(isDirect));
alwaysScan: alwaysScan,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
);
await wallet.init();
return wallet;
}
@ -96,7 +101,9 @@ class BitcoinWalletService extends WalletService<
name: currentName,
walletInfo: currentWalletInfo,
unspentCoinsInfo: unspentCoinsInfoSource,
encryptionFileUtils: encryptionFileUtilsFor(isDirect));
alwaysScan: alwaysScan,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
);
await currentWallet.renameWalletFiles(newName);
await saveBackup(newName);
@ -111,12 +118,13 @@ class BitcoinWalletService extends WalletService<
@override
Future<BitcoinWallet> restoreFromHardwareWallet(BitcoinRestoreWalletFromHardware credentials,
{bool? isTestnet}) async {
final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
credentials.walletInfo?.network = network.value;
credentials.walletInfo?.derivationInfo?.derivationPath = credentials.hwAccountData.derivationPath;
credentials.walletInfo?.derivationInfo?.derivationPath =
credentials.hwAccountData.derivationPath;
final wallet = await BitcoinWallet(password: credentials.password!,
final wallet = await BitcoinWallet(
password: credentials.password!,
xpub: credentials.hwAccountData.xpub,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,

View file

@ -41,23 +41,35 @@ class ElectrumClient {
bool get isConnected => _isConnected;
Socket? socket;
void Function(bool)? onConnectionStatusChange;
void Function(bool?)? onConnectionStatusChange;
int _id;
final Map<String, SocketTask> _tasks;
Map<String, SocketTask> get tasks => _tasks;
final Map<String, String> _errors;
bool _isConnected;
Timer? _aliveTimer;
String unterminatedString;
Future<void> connectToUri(Uri uri) async => await connect(host: uri.host, port: uri.port);
Uri? uri;
bool? useSSL;
Future<void> connect({required String host, required int port}) async {
Future<void> connectToUri(Uri uri, {bool? useSSL}) async {
this.uri = uri;
this.useSSL = useSSL;
await connect(host: uri.host, port: uri.port, useSSL: useSSL);
}
Future<void> connect({required String host, required int port, bool? useSSL}) async {
try {
await socket?.close();
} catch (_) {}
if (useSSL == false) {
socket = await Socket.connect(host, port, timeout: connectionTimeout);
} else {
socket = await SecureSocket.connect(host, port,
timeout: connectionTimeout, onBadCertificate: (_) => true);
}
_setIsConnected(true);
socket!.listen((Uint8List event) {
@ -79,7 +91,7 @@ class ElectrumClient {
_setIsConnected(false);
}, onDone: () {
unterminatedString = '';
_setIsConnected(false);
_setIsConnected(null);
});
keepAlive();
}
@ -134,11 +146,12 @@ class ElectrumClient {
await callWithTimeout(method: 'server.ping');
_setIsConnected(true);
} on RequestFailedTimeoutException catch (_) {
_setIsConnected(false);
_setIsConnected(null);
}
}
Future<List<String>> version() => call(method: 'server.version').then((dynamic result) {
Future<List<String>> version() =>
call(method: 'server.version', params: ["", "1.4"]).then((dynamic result) {
if (result is List) {
return result.map((dynamic val) => val.toString()).toList();
}
@ -266,6 +279,18 @@ class ElectrumClient {
Future<Map<String, dynamic>> getHeader({required int height}) async =>
await call(method: 'blockchain.block.get_header', params: [height]) as Map<String, dynamic>;
BehaviorSubject<Object>? tweaksSubscribe({required int height, required int count}) {
_id += 1;
return subscribe<Object>(
id: 'blockchain.tweaks.subscribe:${height + count}',
method: 'blockchain.tweaks.subscribe',
params: [height, count, false],
);
}
Future<dynamic> getTweaks({required int height}) async =>
await callWithTimeout(method: 'blockchain.tweaks.subscribe', params: [height, 1, false]);
Future<double> estimatefee({required int p}) =>
call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) {
if (result is double) {
@ -308,9 +333,6 @@ class ElectrumClient {
});
Future<List<int>> feeRates({BasedUtxoNetwork? network}) async {
if (network == BitcoinNetwork.testnet) {
return [1, 1, 1];
}
try {
final topDoubleString = await estimatefee(p: 1);
final middleDoubleString = await estimatefee(p: 5);
@ -332,7 +354,7 @@ class ElectrumClient {
// "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4"
// }
Future<int?> getCurrentBlockChainTip() =>
call(method: 'blockchain.headers.subscribe').then((result) {
callWithTimeout(method: 'blockchain.headers.subscribe').then((result) {
if (result is Map<String, dynamic>) {
return result["height"] as int;
}
@ -340,6 +362,12 @@ class ElectrumClient {
return null;
});
BehaviorSubject<Object>? chainTipSubscribe() {
_id += 1;
return subscribe<Object>(
id: 'blockchain.headers.subscribe', method: 'blockchain.headers.subscribe');
}
BehaviorSubject<Object>? scripthashUpdate(String scripthash) {
_id += 1;
return subscribe<Object>(
@ -396,7 +424,9 @@ class ElectrumClient {
Future<void> close() async {
_aliveTimer?.cancel();
try {
await socket?.close();
} catch (_) {}
onConnectionStatusChange = null;
}
@ -431,17 +461,25 @@ class ElectrumClient {
_tasks[id]?.subject?.add(params.last);
break;
case 'blockchain.headers.subscribe':
final params = request['params'] as List<dynamic>;
_tasks[method]?.subject?.add(params.last);
break;
case 'blockchain.tweaks.subscribe':
final params = request['params'] as List<dynamic>;
_tasks[_tasks.keys.first]?.subject?.add(params.last);
break;
default:
break;
}
}
void _setIsConnected(bool isConnected) {
void _setIsConnected(bool? isConnected) {
if (_isConnected != isConnected) {
onConnectionStatusChange?.call(isConnected);
}
_isConnected = isConnected;
_isConnected = isConnected ?? false;
}
void _handleResponse(Map<String, dynamic> response) {

View file

@ -3,8 +3,11 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_core/balance.dart';
class ElectrumBalance extends Balance {
const ElectrumBalance({required this.confirmed, required this.unconfirmed, required this.frozen})
: super(confirmed, unconfirmed);
ElectrumBalance({
required this.confirmed,
required this.unconfirmed,
required this.frozen,
}) : super(confirmed, unconfirmed);
static ElectrumBalance? fromJSON(String? jsonSource) {
if (jsonSource == null) {
@ -19,8 +22,8 @@ class ElectrumBalance extends Balance {
frozen: decoded['frozen'] as int? ?? 0);
}
final int confirmed;
final int unconfirmed;
int confirmed;
int unconfirmed;
final int frozen;
@override

View file

@ -14,8 +14,7 @@ part 'electrum_transaction_history.g.dart';
const transactionsHistoryFileName = 'transactions.json';
class ElectrumTransactionHistory = ElectrumTransactionHistoryBase
with _$ElectrumTransactionHistory;
class ElectrumTransactionHistory = ElectrumTransactionHistoryBase with _$ElectrumTransactionHistory;
abstract class ElectrumTransactionHistoryBase
extends TransactionHistoryBase<ElectrumTransactionInfo> with Store {
@ -34,8 +33,7 @@ abstract class ElectrumTransactionHistoryBase
Future<void> init() async => await _load();
@override
void addOne(ElectrumTransactionInfo transaction) =>
transactions[transaction.id] = transaction;
void addOne(ElectrumTransactionInfo transaction) => transactions[transaction.id] = transaction;
@override
void addMany(Map<String, ElectrumTransactionInfo> transactions) =>
@ -44,11 +42,13 @@ abstract class ElectrumTransactionHistoryBase
@override
Future<void> save() async {
try {
final dirPath =
await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
final path = '$dirPath/$transactionsHistoryFileName';
final data =
json.encode({'height': _height, 'transactions': transactions});
final txjson = {};
for (final tx in transactions.entries) {
txjson[tx.key] = tx.value.toJson();
}
final data = json.encode({'height': _height, 'transactions': txjson});
await encryptionFileUtils.write(path: path, password: _password, data: data);
} catch (e) {
print('Error while save bitcoin transaction history: ${e.toString()}');
@ -61,8 +61,7 @@ abstract class ElectrumTransactionHistoryBase
}
Future<Map<String, dynamic>> _read() async {
final dirPath =
await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
final path = '$dirPath/$transactionsHistoryFileName';
final content = await encryptionFileUtils.read(path: path, password: _password);
return json.decode(content) as Map<String, dynamic>;
@ -88,7 +87,5 @@ abstract class ElectrumTransactionHistoryBase
}
}
void _update(ElectrumTransactionInfo transaction) =>
transactions[transaction.id] = transaction;
void _update(ElectrumTransactionInfo transaction) => transactions[transaction.id] = transaction;
}

View file

@ -1,9 +1,8 @@
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
import 'package:cw_bitcoin/address_from_output.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/format_amount.dart';
@ -19,6 +18,8 @@ class ElectrumTransactionBundle {
}
class ElectrumTransactionInfo extends TransactionInfo {
List<BitcoinSilentPaymentsUnspent>? unspents;
ElectrumTransactionInfo(this.type,
{required String id,
required int height,
@ -29,7 +30,9 @@ class ElectrumTransactionInfo extends TransactionInfo {
required TransactionDirection direction,
required bool isPending,
required DateTime date,
required int confirmations}) {
required int confirmations,
String? to,
this.unspents}) {
this.id = id;
this.height = height;
this.amount = amount;
@ -40,6 +43,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
this.date = date;
this.isPending = isPending;
this.confirmations = confirmations;
this.to = to;
}
factory ElectrumTransactionInfo.fromElectrumVerbose(Map<String, Object> obj, WalletType type,
@ -153,42 +157,13 @@ class ElectrumTransactionInfo extends TransactionInfo {
confirmations: bundle.confirmations);
}
factory ElectrumTransactionInfo.fromHexAndHeader(WalletType type, String hex,
{List<String>? addresses, required int height, int? timestamp, required int confirmations}) {
final tx = bitcoin.Transaction.fromHex(hex);
var exist = false;
var amount = 0;
if (addresses != null) {
tx.outs.forEach((out) {
try {
final p2pkh =
bitcoin.P2PKH(data: PaymentData(output: out.script), network: bitcoin.bitcoin);
exist = addresses.contains(p2pkh.data.address);
if (exist) {
amount += out.value!;
}
} catch (_) {}
});
}
final date =
timestamp != null ? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000) : DateTime.now();
return ElectrumTransactionInfo(type,
id: tx.getId(),
height: height,
isPending: false,
fee: null,
direction: TransactionDirection.incoming,
amount: amount,
date: date,
confirmations: confirmations);
}
factory ElectrumTransactionInfo.fromJson(Map<String, dynamic> data, WalletType type) {
return ElectrumTransactionInfo(type,
final inputAddresses = data['inputAddresses'] as List<dynamic>? ?? [];
final outputAddresses = data['outputAddresses'] as List<dynamic>? ?? [];
final unspents = data['unspents'] as List<dynamic>? ?? [];
return ElectrumTransactionInfo(
type,
id: data['id'] as String,
height: data['height'] as int,
amount: data['amount'] as int,
@ -196,9 +171,17 @@ class ElectrumTransactionInfo extends TransactionInfo {
direction: parseTransactionDirectionFromInt(data['direction'] as int),
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
isPending: data['isPending'] as bool,
inputAddresses: data['inputAddresses'] as List<String>,
outputAddresses: data['outputAddresses'] as List<String>,
confirmations: data['confirmations'] as int);
confirmations: data['confirmations'] as int,
inputAddresses:
inputAddresses.isEmpty ? [] : inputAddresses.map((e) => e.toString()).toList(),
outputAddresses:
outputAddresses.isEmpty ? [] : outputAddresses.map((e) => e.toString()).toList(),
to: data['to'] as String?,
unspents: unspents
.map((unspent) =>
BitcoinSilentPaymentsUnspent.fromJSON(null, unspent as Map<String, dynamic>))
.toList(),
);
}
final WalletType type;
@ -244,8 +227,14 @@ class ElectrumTransactionInfo extends TransactionInfo {
m['isPending'] = isPending;
m['confirmations'] = confirmations;
m['fee'] = fee;
m['to'] = to;
m['unspents'] = unspents?.map((e) => e.toJson()).toList() ?? [];
m['inputAddresses'] = inputAddresses;
m['outputAddresses'] = outputAddresses;
return m;
}
String toString() {
return 'ElectrumTransactionInfo(id: $id, height: $height, amount: $amount, fee: $fee, direction: $direction, date: $date, isPending: $isPending, confirmations: $confirmations, to: $to, unspent: $unspents)';
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_core/wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
@ -24,15 +24,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
WalletInfo walletInfo, {
required this.mainHd,
required this.sideHd,
required this.electrumClient,
required this.network,
List<BitcoinAddressRecord>? initialAddresses,
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex,
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
int initialSilentAddressIndex = 0,
bitcoin.HDWallet? masterHd,
BitcoinAddressType? initialAddressPageType,
}) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
addressesByReceiveType =
ObservableList<BitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
ObservableList<BaseBitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed)
.toSet()),
@ -45,7 +47,38 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
(walletInfo.addressPageType != null
? BitcoinAddressType.fromValue(walletInfo.addressPageType!)
: SegwitAddresType.p2wpkh),
silentAddresses = ObservableList<BitcoinSilentPaymentAddressRecord>.of(
(initialSilentAddresses ?? []).toSet()),
currentSilentAddressIndex = initialSilentAddressIndex,
super(walletInfo) {
if (masterHd != null) {
silentAddress = SilentPaymentOwner.fromPrivateKeys(
b_scan: ECPrivate.fromHex(masterHd.derivePath(SCAN_PATH).privKey!),
b_spend: ECPrivate.fromHex(masterHd.derivePath(SPEND_PATH).privKey!),
hrp: network == BitcoinNetwork.testnet ? 'tsp' : 'sp');
if (silentAddresses.length == 0) {
silentAddresses.add(BitcoinSilentPaymentAddressRecord(
silentAddress.toString(),
index: 0,
isHidden: false,
name: "",
silentPaymentTweak: null,
network: network,
type: SilentPaymentsAddresType.p2sp,
));
silentAddresses.add(BitcoinSilentPaymentAddressRecord(
silentAddress!.toLabeledSilentPaymentAddress(0).toString(),
index: 0,
isHidden: true,
name: "",
silentPaymentTweak: BytesUtils.toHexString(silentAddress!.generateLabel(0)),
network: network,
type: SilentPaymentsAddresType.p2sp,
));
}
}
updateAddressesByMatch();
}
@ -54,27 +87,40 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
static const gap = 20;
final ObservableList<BitcoinAddressRecord> _addresses;
// Matched by addressPageType
late ObservableList<BitcoinAddressRecord> addressesByReceiveType;
late ObservableList<BaseBitcoinAddressRecord> addressesByReceiveType;
final ObservableList<BitcoinAddressRecord> receiveAddresses;
final ObservableList<BitcoinAddressRecord> changeAddresses;
final ElectrumClient electrumClient;
final ObservableList<BitcoinSilentPaymentAddressRecord> silentAddresses;
final BasedUtxoNetwork network;
final bitcoin.HDWallet mainHd;
final bitcoin.HDWallet sideHd;
@observable
SilentPaymentOwner? silentAddress;
@observable
late BitcoinAddressType _addressPageType;
@computed
BitcoinAddressType get addressPageType => _addressPageType;
@observable
String? activeSilentAddress;
@computed
List<BitcoinAddressRecord> get allAddresses => _addresses;
@override
@computed
String get address {
if (addressPageType == SilentPaymentsAddresType.p2sp) {
if (activeSilentAddress != null) {
return activeSilentAddress!;
}
return silentAddress.toString();
}
String receiveAddress;
final typeMatchingReceiveAddresses =
@ -103,6 +149,18 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@override
set address(String addr) {
if (addressPageType == SilentPaymentsAddresType.p2sp) {
final selected = silentAddresses.firstWhere((addressRecord) => addressRecord.address == addr);
if (selected.silentPaymentTweak != null && silentAddress != null) {
activeSilentAddress =
silentAddress!.toLabeledSilentPaymentAddress(selected.index).toString();
} else {
activeSilentAddress = silentAddress!.toString();
}
return;
}
final addressRecord = _addresses.firstWhere((addressRecord) => addressRecord.address == addr);
previousAddressRecord = addressRecord;
@ -129,6 +187,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
void set currentChangeAddressIndex(int index) =>
currentChangeAddressIndexByType[_addressPageType.toString()] = index;
int currentSilentAddressIndex;
@observable
BitcoinAddressRecord? previousAddressRecord;
@ -196,7 +256,50 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
return address;
}
BitcoinAddressRecord generateNewAddress({String label = ''}) {
Map<String, String> get labels {
final G = ECPublic.fromBytes(BigintUtils.toBytes(Curves.generatorSecp256k1.x, length: 32));
final labels = <String, String>{};
for (int i = 0; i < silentAddresses.length; i++) {
final silentAddressRecord = silentAddresses[i];
final silentPaymentTweak = silentAddressRecord.silentPaymentTweak;
if (silentPaymentTweak != null &&
SilentPaymentAddress.regex.hasMatch(silentAddressRecord.address)) {
labels[G
.tweakMul(BigintUtils.fromBytes(BytesUtils.fromHexString(silentPaymentTweak)))
.toHex()] = silentPaymentTweak;
}
}
return labels;
}
@action
BaseBitcoinAddressRecord generateNewAddress({String label = ''}) {
if (addressPageType == SilentPaymentsAddresType.p2sp && silentAddress != null) {
final currentSilentAddressIndex = silentAddresses
.where((addressRecord) => addressRecord.type != SegwitAddresType.p2tr)
.length -
1;
this.currentSilentAddressIndex = currentSilentAddressIndex;
final address = BitcoinSilentPaymentAddressRecord(
silentAddress!.toLabeledSilentPaymentAddress(currentSilentAddressIndex).toString(),
index: currentSilentAddressIndex,
isHidden: false,
name: label,
silentPaymentTweak:
BytesUtils.toHexString(silentAddress!.generateLabel(currentSilentAddressIndex)),
network: network,
type: SilentPaymentsAddresType.p2sp,
);
silentAddresses.add(address);
updateAddressesByMatch();
return address;
}
final newAddressIndex = addressesByReceiveType.fold(
0, (int acc, addressRecord) => addressRecord.isHidden == false ? acc + 1 : acc);
@ -221,12 +324,70 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
Future<void> updateAddressesInBox() async {
try {
addressesMap.clear();
addressesMap[address] = '';
addressesMap[address] = 'Active';
allAddressesMap.clear();
_addresses.forEach((addressRecord) {
allAddressesMap[addressRecord.address] = addressRecord.name;
});
final lastP2wpkh = _addresses
.where((addressRecord) =>
_isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wpkh))
.toList()
.last;
if (lastP2wpkh.address != address) {
addressesMap[lastP2wpkh.address] = 'P2WPKH';
} else {
addressesMap[address] = 'Active - P2WPKH';
}
final lastP2pkh = _addresses.firstWhere(
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, P2pkhAddressType.p2pkh));
if (lastP2pkh.address != address) {
addressesMap[lastP2pkh.address] = 'P2PKH';
} else {
addressesMap[address] = 'Active - P2PKH';
}
final lastP2sh = _addresses.firstWhere((addressRecord) =>
_isUnusedReceiveAddressByType(addressRecord, P2shAddressType.p2wpkhInP2sh));
if (lastP2sh.address != address) {
addressesMap[lastP2sh.address] = 'P2SH';
} else {
addressesMap[address] = 'Active - P2SH';
}
final lastP2tr = _addresses.firstWhere(
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2tr));
if (lastP2tr.address != address) {
addressesMap[lastP2tr.address] = 'P2TR';
} else {
addressesMap[address] = 'Active - P2TR';
}
final lastP2wsh = _addresses.firstWhere(
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wsh));
if (lastP2wsh.address != address) {
addressesMap[lastP2wsh.address] = 'P2WSH';
} else {
addressesMap[address] = 'Active - P2WSH';
}
silentAddresses.forEach((addressRecord) {
if (addressRecord.type != SilentPaymentsAddresType.p2sp || addressRecord.isHidden) {
return;
}
if (addressRecord.address != address) {
addressesMap[addressRecord.address] = addressRecord.name.isEmpty
? "Silent Payments"
: "Silent Payments - " + addressRecord.name;
} else {
addressesMap[address] = 'Active - Silent Payments';
}
});
await saveAddressesInBox();
} catch (e) {
print(e.toString());
@ -235,18 +396,41 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@action
void updateAddress(String address, String label) {
final addressRecord =
_addresses.firstWhere((addressRecord) => addressRecord.address == address);
addressRecord.setNewName(label);
final index = _addresses.indexOf(addressRecord);
_addresses.remove(addressRecord);
_addresses.insert(index, addressRecord);
BaseBitcoinAddressRecord? foundAddress;
_addresses.forEach((addressRecord) {
if (addressRecord.address == address) {
foundAddress = addressRecord;
}
});
silentAddresses.forEach((addressRecord) {
if (addressRecord.address == address) {
foundAddress = addressRecord;
}
});
updateAddressesByMatch();
if (foundAddress != null) {
foundAddress!.setNewName(label);
if (foundAddress is BitcoinAddressRecord) {
final index = _addresses.indexOf(foundAddress);
_addresses.remove(foundAddress);
_addresses.insert(index, foundAddress as BitcoinAddressRecord);
} else {
final index = silentAddresses.indexOf(foundAddress as BitcoinSilentPaymentAddressRecord);
silentAddresses.remove(foundAddress);
silentAddresses.insert(index, foundAddress as BitcoinSilentPaymentAddressRecord);
}
}
}
@action
void updateAddressesByMatch() {
if (addressPageType == SilentPaymentsAddresType.p2sp) {
addressesByReceiveType.clear();
addressesByReceiveType.addAll(silentAddresses);
return;
}
addressesByReceiveType.clear();
addressesByReceiveType.addAll(_addresses.where(_isAddressPageTypeMatch).toList());
}
@ -272,7 +456,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@action
Future<void> discoverAddresses(List<BitcoinAddressRecord> addressList, bool isHidden,
Future<String?> Function(BitcoinAddressRecord, Set<String>) getAddressHistory,
Future<String?> Function(BitcoinAddressRecord) getAddressHistory,
{BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
if (!isHidden) {
_validateSideHdAddresses(addressList.toList());
@ -282,8 +466,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
startIndex: addressList.length, isHidden: isHidden, type: type);
addAddresses(newAddresses);
final addressesWithHistory = await Future.wait(newAddresses
.map((addr) => getAddressHistory(addr, _addresses.map((e) => e.address).toSet())));
final addressesWithHistory = await Future.wait(newAddresses.map(getAddressHistory));
final isLastAddressUsed = addressesWithHistory.last == addressList.last.address;
if (isLastAddressUsed) {
@ -349,6 +532,15 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
updateAddressesByMatch();
}
@action
void addSilentAddresses(Iterable<BitcoinSilentPaymentAddressRecord> addresses) {
final addressesSet = this.silentAddresses.toSet();
addressesSet.addAll(addresses);
this.silentAddresses.clear();
this.silentAddresses.addAll(addressesSet);
updateAddressesByMatch();
}
void _validateSideHdAddresses(List<BitcoinAddressRecord> addrWithTransactions) {
addrWithTransactions.forEach((element) {
if (element.address !=
@ -371,4 +563,15 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
bitcoin.HDWallet _getHd(bool isHidden) => isHidden ? sideHd : mainHd;
bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type;
bool _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) =>
!addr.isHidden && !addr.isUsed && addr.type == type;
@action
void deleteSilentPaymentAddress(String address) {
final addressRecord = silentAddresses.firstWhere((addressRecord) =>
addressRecord.type == SilentPaymentsAddresType.p2sp && addressRecord.address == address);
silentAddresses.remove(addressRecord);
updateAddressesByMatch();
}
}

View file

@ -20,6 +20,8 @@ class ElectrumWalletSnapshot {
required this.regularAddressIndex,
required this.changeAddressIndex,
required this.addressPageType,
required this.silentAddresses,
required this.silentAddressIndex,
this.passphrase,
this.derivationType,
this.derivationPath,
@ -33,9 +35,11 @@ class ElectrumWalletSnapshot {
String? mnemonic;
String? xpub;
List<BitcoinAddressRecord> addresses;
List<BitcoinSilentPaymentAddressRecord> silentAddresses;
ElectrumBalance balance;
Map<String, int> regularAddressIndex;
Map<String, int> changeAddressIndex;
int silentAddressIndex;
String? passphrase;
DerivationType? derivationType;
String? derivationPath;
@ -51,15 +55,23 @@ class ElectrumWalletSnapshot {
final passphrase = data['passphrase'] as String? ?? '';
final addresses = addressesTmp
.whereType<String>()
.map((addr) => BitcoinAddressRecord.fromJSON(addr, network))
.map((addr) => BitcoinAddressRecord.fromJSON(addr, network: network))
.toList();
final balance = ElectrumBalance.fromJSON(data['balance'] as String) ??
final silentAddressesTmp = data['silent_addresses'] as List? ?? <Object>[];
final silentAddresses = silentAddressesTmp
.whereType<String>()
.map((addr) => BitcoinSilentPaymentAddressRecord.fromJSON(addr, network: network))
.toList();
final balance = ElectrumBalance.fromJSON(data['balance'] as String?) ??
ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
var silentAddressIndex = 0;
final derivationType =
DerivationType.values[(data['derivationTypeIndex'] as int?) ?? DerivationType.electrum.index];
final derivationType = DerivationType
.values[(data['derivationTypeIndex'] as int?) ?? DerivationType.electrum.index];
final derivationPath = data['derivationPath'] as String? ?? "m/0'/0";
try {
@ -70,6 +82,7 @@ class ElectrumWalletSnapshot {
SegwitAddresType.p2wpkh.toString():
int.parse(data['change_address_index'] as String? ?? '0')
};
silentAddressIndex = int.parse(data['silent_address_index'] as String? ?? '0');
} catch (_) {
try {
regularAddressIndexByType = data["account_index"] as Map<String, int>? ?? {};
@ -91,6 +104,8 @@ class ElectrumWalletSnapshot {
addressPageType: data['address_page_type'] as String?,
derivationType: derivationType,
derivationPath: derivationPath,
silentAddresses: silentAddresses,
silentAddressIndex: silentAddressIndex,
);
}
}

View file

@ -2,7 +2,7 @@ import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/exceptions.dart';
class BitcoinTransactionWrongBalanceException extends TransactionWrongBalanceException {
BitcoinTransactionWrongBalanceException() : super(CryptoCurrency.btc);
BitcoinTransactionWrongBalanceException({super.amount}) : super(CryptoCurrency.btc);
}
class BitcoinTransactionNoInputsException extends TransactionNoInputsException {}
@ -27,3 +27,7 @@ class BitcoinTransactionCommitFailedDustOutputSendAll
extends TransactionCommitFailedDustOutputSendAll {}
class BitcoinTransactionCommitFailedVoutNegative extends TransactionCommitFailedVoutNegative {}
class BitcoinTransactionCommitFailedBIP68Final extends TransactionCommitFailedBIP68Final {}
class BitcoinTransactionSilentPaymentsNotSupported extends TransactionInputNotSupported {}

View file

@ -47,7 +47,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
currency: CryptoCurrency.ltc) {
walletAddresses = LitecoinWalletAddresses(
walletInfo,
electrumClient: electrumClient,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,

View file

@ -15,7 +15,6 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
required super.mainHd,
required super.sideHd,
required super.network,
required super.electrumClient,
super.initialAddresses,
super.initialRegularAddressIndex,
super.initialChangeAddressIndex,

View file

@ -73,6 +73,11 @@ class PendingBitcoinTransaction with PendingTransaction {
if (error.contains("bad-txns-vout-negative")) {
throw BitcoinTransactionCommitFailedVoutNegative();
}
if (error.contains("non-BIP68-final")) {
throw BitcoinTransactionCommitFailedBIP68Final();
}
throw BitcoinTransactionCommitFailed(errorMessage: error);
}

View file

@ -29,10 +29,10 @@ packages:
dependency: transitive
description:
name: asn1lib
sha256: c9c85fedbe2188b95133cbe960e16f5f448860f7133330e272edbbca5893ddc6
sha256: "58082b3f0dca697204dbab0ef9ff208bfaea7767ea771076af9a343488428dda"
url: "https://pub.dev"
source: hosted
version: "1.5.2"
version: "1.5.3"
async:
dependency: transitive
description:
@ -79,11 +79,11 @@ packages:
dependency: "direct main"
description:
path: "."
ref: cake-update-v2
resolved-ref: "01d844a5f5a520a31df5254e34169af4664aa769"
url: "https://github.com/cake-tech/bitcoin_base.git"
ref: cake-update-v3
resolved-ref: cc99eedb1d28ee9376dda0465ef72aa627ac6149
url: "https://github.com/cake-tech/bitcoin_base"
source: git
version: "4.2.0"
version: "4.2.1"
bitcoin_flutter:
dependency: "direct main"
description:
@ -96,11 +96,12 @@ packages:
blockchain_utils:
dependency: "direct main"
description:
name: blockchain_utils
sha256: "38ef5f4a22441ac4370aed9071dc71c460acffc37c79b344533f67d15f24c13c"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
path: "."
ref: cake-update-v1
resolved-ref: cabd7e0e16c4da9920338c76eff3aeb8af0211f3
url: "https://github.com/cake-tech/blockchain_utils"
source: git
version: "2.1.2"
boolean_selector:
dependency: transitive
description:
@ -181,15 +182,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.9.2"
cake_backup:
dependency: transitive
description:
path: "."
ref: main
resolved-ref: "3aba867dcab6737f6707782f5db15d71f303db38"
url: "https://github.com/cake-tech/cake_backup.git"
source: git
version: "1.0.0+1"
characters:
dependency: transitive
description:
@ -206,6 +198,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.3"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19
url: "https://pub.dev"
source: hosted
version: "0.4.1"
clock:
dependency: transitive
description:
@ -250,18 +250,10 @@ packages:
dependency: "direct main"
description:
name: cryptography
sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35
sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05
url: "https://pub.dev"
source: hosted
version: "2.5.0"
cupertino_icons:
dependency: transitive
description:
name: cupertino_icons
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
url: "https://pub.dev"
source: hosted
version: "1.0.6"
version: "2.7.0"
cw_core:
dependency: "direct main"
description:
@ -305,10 +297,18 @@ packages:
dependency: transitive
description:
name: ffi
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.1.2"
ffigen:
dependency: transitive
description:
name: ffigen
sha256: d3e76c2ad48a4e7f93a29a162006f00eba46ce7c08194a77bb5c5e97d1b5ff0a
url: "https://pub.dev"
source: hosted
version: "8.0.2"
file:
dependency: transitive
description:
@ -363,10 +363,10 @@ packages:
dependency: transitive
description:
name: functional_data
sha256: aefdec4365452283b2a7cf420a3169654d51d3e9553069a22d76680d7a9d7c3d
sha256: "76d17dc707c40e552014f5a49c0afcc3f1e3f05e800cd6b7872940bfe41a5039"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
version: "1.2.0"
glob:
dependency: transitive
description:
@ -411,10 +411,10 @@ packages:
dependency: "direct main"
description:
name: http
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.2.1"
http_multi_server:
dependency: transitive
description:
@ -459,10 +459,10 @@ packages:
dependency: transitive
description:
name: json_annotation
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.8.1"
version: "4.9.0"
leak_tracker:
dependency: transitive
description:
@ -548,10 +548,10 @@ packages:
dependency: transitive
description:
name: mime
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
version: "1.0.5"
mobx:
dependency: "direct main"
description:
@ -596,26 +596,26 @@ packages:
dependency: "direct main"
description:
name: path_provider
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.3"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
url: "https://pub.dev"
source: hosted
version: "2.2.2"
version: "2.2.4"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.4.0"
path_provider_linux:
dependency: transitive
description:
@ -660,10 +660,10 @@ packages:
dependency: transitive
description:
name: pointycastle
sha256: "70fe966348fe08c34bf929582f1d8247d9d9408130723206472b4687227e4333"
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
url: "https://pub.dev"
source: hosted
version: "3.8.0"
version: "3.9.1"
pool:
dependency: transitive
description:
@ -704,6 +704,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.3"
quiver:
dependency: transitive
description:
name: quiver
sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47
url: "https://pub.dev"
source: hosted
version: "3.2.1"
reactive_ble_mobile:
dependency: transitive
description:
@ -753,10 +761,10 @@ packages:
dependency: transitive
description:
name: socks5_proxy
sha256: "1d21b5606169654bbf4cfb904e8e6ed897e9f763358709f87310c757096d909a"
sha256: "045cbba84f6e2b01c1c77634a63e926352bf110ef5f07fc462c6d43bbd4b6a83"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
version: "1.0.5+dev.2"
source_gen:
dependency: transitive
description:
@ -781,6 +789,15 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.10.0"
sp_scanner:
dependency: "direct main"
description:
path: "."
ref: "sp_v1.0.0"
resolved-ref: a9a4c6d051f37a15a3a52cc2a0094f24c68b62c5
url: "https://github.com/cake-tech/sp_scanner"
source: git
version: "0.0.1"
stack_trace:
dependency: transitive
description:
@ -837,14 +854,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.1"
tuple:
dependency: transitive
description:
name: tuple
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
url: "https://pub.dev"
source: hosted
version: "2.0.2"
typed_data:
dependency: transitive
description:
@ -885,22 +894,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
web:
dependency: transitive
description:
name: web
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.4.5"
win32:
dependency: transitive
description:
name: win32
sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
url: "https://pub.dev"
source: hosted
version: "5.0.9"
version: "5.5.0"
xdg_directories:
dependency: transitive
description:
@ -917,6 +934,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.2"
yaml_edit:
dependency: transitive
description:
name: yaml_edit
sha256: e9c1a3543d2da0db3e90270dbb1e4eebc985ee5e3ffe468d83224472b2194a5f
url: "https://pub.dev"
source: hosted
version: "2.2.1"
sdks:
dart: ">=3.2.0-0 <4.0.0"
flutter: ">=3.10.0"
dart: ">=3.3.0 <4.0.0"
flutter: ">=3.16.6"

View file

@ -32,13 +32,21 @@ dependencies:
cryptography: ^2.0.5
bitcoin_base:
git:
url: https://github.com/cake-tech/bitcoin_base.git
ref: cake-update-v2
blockchain_utils: ^2.1.1
url: https://github.com/cake-tech/bitcoin_base
ref: cake-update-v3
blockchain_utils:
git:
url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v1
ledger_flutter: ^1.0.1
ledger_bitcoin:
git:
url: https://github.com/cake-tech/ledger-bitcoin
sp_scanner:
git:
url: https://github.com/cake-tech/sp_scanner
ref: sp_v2.0.0
dev_dependencies:
flutter_test:

View file

@ -49,7 +49,6 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
encryptionFileUtils: encryptionFileUtils) {
walletAddresses = BitcoinCashWalletAddresses(
walletInfo,
electrumClient: electrumClient,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,

View file

@ -15,7 +15,6 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
required super.mainHd,
required super.sideHd,
required super.network,
required super.electrumClient,
super.initialAddresses,
super.initialRegularAddressIndex,
super.initialChangeAddressIndex,

View file

@ -31,10 +31,12 @@ dependencies:
ref: Add-Support-For-OP-Return-data
bitcoin_base:
git:
url: https://github.com/cake-tech/bitcoin_base.git
ref: cake-update-v2
url: https://github.com/cake-tech/bitcoin_base
ref: cake-update-v3
blockchain_utils:
git:
url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v1
dev_dependencies:
flutter_test:

View file

@ -104,6 +104,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
CryptoCurrency.digibyte,
CryptoCurrency.usdtSol,
CryptoCurrency.usdcTrc20,
CryptoCurrency.tbtc,
];
static const havenCurrencies = [
@ -219,6 +220,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
static const digibyte = CryptoCurrency(title: 'DGB', fullName: 'DigiByte', raw: 90, name: 'dgb', iconPath: 'assets/images/digibyte.png', decimals: 8);
static const usdtSol = CryptoCurrency(title: 'USDT', tag: 'SOL', fullName: 'USDT Tether', raw: 91, name: 'usdtsol', iconPath: 'assets/images/usdt_icon.png', decimals: 6);
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 final Map<int, CryptoCurrency> _rawCurrencyMap =
@ -253,7 +255,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
static CryptoCurrency fromString(String name, {CryptoCurrency? walletCurrency}) {
try {
return CryptoCurrency.all.firstWhere((element) =>
element.title.toLowerCase() == name &&
element.title.toLowerCase() == name.toLowerCase() &&
(element.tag == null ||
element.tag == walletCurrency?.title ||
element.tag == walletCurrency?.tag));

View file

@ -1,9 +1,12 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_type.dart';
CryptoCurrency currencyForWalletType(WalletType type) {
CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) {
switch (type) {
case WalletType.bitcoin:
if (isTestnet == true) {
return CryptoCurrency.tbtc;
}
return CryptoCurrency.btc;
case WalletType.monero:
return CryptoCurrency.xmr;

View file

@ -1,9 +1,10 @@
import 'package:cw_core/crypto_currency.dart';
class TransactionWrongBalanceException implements Exception {
TransactionWrongBalanceException(this.currency);
TransactionWrongBalanceException(this.currency, {this.amount});
final CryptoCurrency currency;
final int? amount;
}
class TransactionNoInputsException implements Exception {}
@ -32,3 +33,7 @@ class TransactionCommitFailedDustOutput implements Exception {}
class TransactionCommitFailedDustOutputSendAll implements Exception {}
class TransactionCommitFailedVoutNegative implements Exception {}
class TransactionCommitFailedBIP68Final implements Exception {}
class TransactionInputNotSupported implements Exception {}

View file

@ -242,3 +242,57 @@ Future<int> getHavenCurrentHeight() async {
throw Exception('Failed to load current blockchain height');
}
}
// Data taken from https://timechaincalendar.com/
const bitcoinDates = {
"2024-05": 841590,
"2024-04": 837182,
"2024-03": 832623,
"2024-02": 828319,
"2024-01": 823807,
"2023-12": 819206,
"2023-11": 814765,
"2023-10": 810098,
"2023-09": 805675,
"2023-08": 801140,
"2023-07": 796640,
"2023-06": 792330,
"2023-05": 787733,
"2023-04": 783403,
"2023-03": 778740,
"2023-02": 774525,
"2023-01": 769810,
};
int getBitcoinHeightByDate({required DateTime date}) {
String dateKey = '${date.year}-${date.month.toString().padLeft(2, '0')}';
final closestKey = bitcoinDates.keys
.firstWhere((key) => formatMapKey(key).isBefore(date), orElse: () => bitcoinDates.keys.last);
final beginningBlock = bitcoinDates[dateKey] ?? bitcoinDates[closestKey]!;
final startOfMonth = DateTime(date.year, date.month);
final daysDifference = date.difference(startOfMonth).inDays;
// approximately 6 blocks per hour, 24 hours per day
int estimatedBlocksSinceStartOfMonth = (daysDifference * 24 * 6);
return beginningBlock + estimatedBlocksSinceStartOfMonth;
}
DateTime getDateByBitcoinHeight(int height) {
final closestEntry = bitcoinDates.entries
.lastWhere((entry) => entry.value >= height, orElse: () => bitcoinDates.entries.first);
final beginningBlock = closestEntry.value;
final startOfMonth = formatMapKey(closestEntry.key);
final blocksDifference = height - beginningBlock;
final hoursDifference = blocksDifference / 5.5;
final estimatedDate = startOfMonth.add(Duration(hours: hoursDifference.ceil()));
if (estimatedDate.isAfter(DateTime.now())) {
return DateTime.now();
}
return estimatedDate;
}

View file

@ -244,8 +244,12 @@ class Node extends HiveObject with Keyable {
Future<bool> requestElectrumServer() async {
try {
if (useSSL == true) {
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));
}
return true;
} catch (_) {
return false;

View file

@ -14,6 +14,16 @@ class SyncingSyncStatus extends SyncStatus {
@override
String toString() => '$blocksLeft';
factory SyncingSyncStatus.fromHeightValues(int chainTip, int initialSyncHeight, int syncHeight) {
final track = chainTip - initialSyncHeight;
final diff = track - (chainTip - syncHeight);
final ptc = diff <= 0 ? 0.0 : diff / track;
final left = chainTip - syncHeight;
// sum 1 because if at the chain tip, will say "0 blocks left"
return SyncingSyncStatus(left + 1, ptc);
}
}
class SyncedSyncStatus extends SyncStatus {
@ -21,6 +31,17 @@ class SyncedSyncStatus extends SyncStatus {
double progress() => 1.0;
}
class SyncedTipSyncStatus extends SyncedSyncStatus {
SyncedTipSyncStatus(this.tip);
final int tip;
}
class SyncronizingSyncStatus extends SyncStatus {
@override
double progress() => 0.0;
}
class NotConnectedSyncStatus extends SyncStatus {
const NotConnectedSyncStatus();
@ -33,10 +54,7 @@ class AttemptingSyncStatus extends SyncStatus {
double progress() => 0.0;
}
class FailedSyncStatus extends SyncStatus {
@override
double progress() => 1.0;
}
class FailedSyncStatus extends NotConnectedSyncStatus {}
class ConnectingSyncStatus extends SyncStatus {
@override
@ -48,7 +66,14 @@ class ConnectedSyncStatus extends SyncStatus {
double progress() => 0.0;
}
class LostConnectionSyncStatus extends SyncStatus {
class UnsupportedSyncStatus extends NotConnectedSyncStatus {}
class TimedOutSyncStatus extends NotConnectedSyncStatus {
@override
double progress() => 1.0;
String toString() => 'Timed out';
}
class LostConnectionSyncStatus extends NotConnectedSyncStatus {
@override
String toString() => 'Reconnecting';
}

View file

@ -16,7 +16,8 @@ class UnspentCoinsInfo extends HiveObject {
required this.value,
this.keyImage = null,
this.isChange = false,
this.accountIndex = 0
this.accountIndex = 0,
this.isSilentPayment = false,
});
static const typeId = UNSPENT_COINS_INFO_TYPE_ID;
@ -56,6 +57,9 @@ class UnspentCoinsInfo extends HiveObject {
@HiveField(10, defaultValue: 0)
int accountIndex;
@HiveField(11, defaultValue: false)
bool? isSilentPayment;
String get note => noteRaw ?? '';
set note(String value) => noteRaw = value;

View file

@ -17,5 +17,6 @@ class Unspent {
int? confirmations;
String note;
bool get isP2wpkh => address.startsWith('bc') || address.startsWith('ltc');
bool get isP2wpkh =>
address.startsWith('bc') || address.startsWith('tb') || address.startsWith('ltc');
}

View file

@ -24,7 +24,7 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
WalletType get type => walletInfo.type;
CryptoCurrency get currency => currencyForWalletType(type);
CryptoCurrency get currency => currencyForWalletType(type, isTestnet: isTestnet);
String get id => walletInfo.id;

View file

@ -207,4 +207,9 @@ class WalletInfo extends HiveObject {
Stream<String> get yatLastUsedAddressStream => _yatLastUsedAddressController.stream;
StreamController<String> _yatLastUsedAddressController;
Future<void> updateRestoreHeight(int height) async {
restoreHeight = height;
await save();
}
}

View file

@ -173,11 +173,14 @@ String walletTypeToDisplayName(WalletType type) {
}
}
CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = false}) {
switch (type) {
case WalletType.monero:
return CryptoCurrency.xmr;
case WalletType.bitcoin:
if (isTestnet) {
return CryptoCurrency.tbtc;
}
return CryptoCurrency.btc;
case WalletType.litecoin:
return CryptoCurrency.ltc;

View file

@ -410,6 +410,7 @@ extern "C"
return false;
}
wallet->store(std::string(path));
change_current_wallet(wallet);
return true;
}
@ -484,6 +485,19 @@ extern "C"
return strdup(get_current_wallet()->address(account_index, address_index).c_str());
}
FUNCTION_VISABILITY_ATTRIBUTE
char *get_cache_attribute(char *name)
{
return strdup(get_current_wallet()->getCacheAttribute(std::string(name)).c_str());
}
FUNCTION_VISABILITY_ATTRIBUTE
bool set_cache_attribute(char *name, char *value)
{
get_current_wallet()->setCacheAttribute(std::string(name), std::string(value));
return true;
}
FUNCTION_VISABILITY_ATTRIBUTE
const char *seed()
{

View file

@ -154,3 +154,7 @@ typedef freeze_coin = Void Function(Int32 index);
typedef thaw_coin = Void Function(Int32 index);
typedef sign_message = Pointer<Utf8> Function(Pointer<Utf8> message, Pointer<Utf8> address);
typedef get_cache_attribute = Pointer<Utf8> Function(Pointer<Utf8> name);
typedef set_cache_attribute = Int8 Function(Pointer<Utf8> name, Pointer<Utf8> value);

View file

@ -154,3 +154,7 @@ typedef FreezeCoin = void Function(int);
typedef ThawCoin = void Function(int);
typedef SignMessage = Pointer<Utf8> Function(Pointer<Utf8>, Pointer<Utf8>);
typedef GetCacheAttribute = Pointer<Utf8> Function(Pointer<Utf8>);
typedef SetCacheAttribute = int Function(Pointer<Utf8>, Pointer<Utf8>);

View file

@ -1,26 +1,24 @@
import 'dart:async';
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:cw_monero/api/structs/ut8_box.dart';
import 'package:cw_monero/api/convert_utf8_to_string.dart';
import 'package:cw_monero/api/signatures.dart';
import 'package:cw_monero/api/types.dart';
import 'package:cw_monero/api/monero_api.dart';
import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart';
import 'package:cw_monero/api/monero_api.dart';
import 'package:cw_monero/api/signatures.dart';
import 'package:cw_monero/api/structs/ut8_box.dart';
import 'package:cw_monero/api/types.dart';
import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart';
int _boolToInt(bool value) => value ? 1 : 0;
final getFileNameNative = moneroApi
.lookup<NativeFunction<get_filename>>('get_filename')
.asFunction<GetFilename>();
final getFileNameNative =
moneroApi.lookup<NativeFunction<get_filename>>('get_filename').asFunction<GetFilename>();
final getSeedNative =
moneroApi.lookup<NativeFunction<get_seed>>('seed').asFunction<GetSeed>();
final getSeedNative = moneroApi.lookup<NativeFunction<get_seed>>('seed').asFunction<GetSeed>();
final getAddressNative = moneroApi
.lookup<NativeFunction<get_address>>('get_address')
.asFunction<GetAddress>();
final getAddressNative =
moneroApi.lookup<NativeFunction<get_address>>('get_address').asFunction<GetAddress>();
final getFullBalanceNative = moneroApi
.lookup<NativeFunction<get_full_balanace>>('get_full_balance')
@ -38,41 +36,34 @@ final getNodeHeightNative = moneroApi
.lookup<NativeFunction<get_node_height>>('get_node_height')
.asFunction<GetNodeHeight>();
final isConnectedNative = moneroApi
.lookup<NativeFunction<is_connected>>('is_connected')
.asFunction<IsConnected>();
final isConnectedNative =
moneroApi.lookup<NativeFunction<is_connected>>('is_connected').asFunction<IsConnected>();
final setupNodeNative = moneroApi
.lookup<NativeFunction<setup_node>>('setup_node')
.asFunction<SetupNode>();
final setupNodeNative =
moneroApi.lookup<NativeFunction<setup_node>>('setup_node').asFunction<SetupNode>();
final startRefreshNative = moneroApi
.lookup<NativeFunction<start_refresh>>('start_refresh')
.asFunction<StartRefresh>();
final startRefreshNative =
moneroApi.lookup<NativeFunction<start_refresh>>('start_refresh').asFunction<StartRefresh>();
final connecToNodeNative = moneroApi
.lookup<NativeFunction<connect_to_node>>('connect_to_node')
.asFunction<ConnectToNode>();
final setRefreshFromBlockHeightNative = moneroApi
.lookup<NativeFunction<set_refresh_from_block_height>>(
'set_refresh_from_block_height')
.lookup<NativeFunction<set_refresh_from_block_height>>('set_refresh_from_block_height')
.asFunction<SetRefreshFromBlockHeight>();
final setRecoveringFromSeedNative = moneroApi
.lookup<NativeFunction<set_recovering_from_seed>>(
'set_recovering_from_seed')
.lookup<NativeFunction<set_recovering_from_seed>>('set_recovering_from_seed')
.asFunction<SetRecoveringFromSeed>();
final storeNative =
moneroApi.lookup<NativeFunction<store_c>>('store').asFunction<Store>();
final storeNative = moneroApi.lookup<NativeFunction<store_c>>('store').asFunction<Store>();
final setPasswordNative =
moneroApi.lookup<NativeFunction<set_password>>('set_password').asFunction<SetPassword>();
final setListenerNative = moneroApi
.lookup<NativeFunction<set_listener>>('set_listener')
.asFunction<SetListener>();
final setListenerNative =
moneroApi.lookup<NativeFunction<set_listener>>('set_listener').asFunction<SetListener>();
final getSyncingHeightNative = moneroApi
.lookup<NativeFunction<get_syncing_height>>('get_syncing_height')
@ -83,8 +74,7 @@ final isNeededToRefreshNative = moneroApi
.asFunction<IsNeededToRefresh>();
final isNewTransactionExistNative = moneroApi
.lookup<NativeFunction<is_new_transaction_exist>>(
'is_new_transaction_exist')
.lookup<NativeFunction<is_new_transaction_exist>>('is_new_transaction_exist')
.asFunction<IsNewTransactionExist>();
final getSecretViewKeyNative = moneroApi
@ -107,9 +97,8 @@ final closeCurrentWalletNative = moneroApi
.lookup<NativeFunction<close_current_wallet>>('close_current_wallet')
.asFunction<CloseCurrentWallet>();
final onStartupNative = moneroApi
.lookup<NativeFunction<on_startup>>('on_startup')
.asFunction<OnStartup>();
final onStartupNative =
moneroApi.lookup<NativeFunction<on_startup>>('on_startup').asFunction<OnStartup>();
final rescanBlockchainAsyncNative = moneroApi
.lookup<NativeFunction<rescan_blockchain>>('rescan_blockchain')
@ -123,13 +112,19 @@ final setTrustedDaemonNative = moneroApi
.lookup<NativeFunction<set_trusted_daemon>>('set_trusted_daemon')
.asFunction<SetTrustedDaemon>();
final trustedDaemonNative = moneroApi
.lookup<NativeFunction<trusted_daemon>>('trusted_daemon')
.asFunction<TrustedDaemon>();
final trustedDaemonNative =
moneroApi.lookup<NativeFunction<trusted_daemon>>('trusted_daemon').asFunction<TrustedDaemon>();
final signMessageNative = moneroApi
.lookup<NativeFunction<sign_message>>('sign_message')
.asFunction<SignMessage>();
final signMessageNative =
moneroApi.lookup<NativeFunction<sign_message>>('sign_message').asFunction<SignMessage>();
final getCacheAttributeNative = moneroApi
.lookup<NativeFunction<get_cache_attribute>>('get_cache_attribute')
.asFunction<GetCacheAttribute>();
final setCacheAttributeNative = moneroApi
.lookup<NativeFunction<set_cache_attribute>>('set_cache_attribute')
.asFunction<SetCacheAttribute>();
int getSyncingHeight() => getSyncingHeightNative();
@ -144,11 +139,9 @@ String getSeed() => convertUTF8ToString(pointer: getSeedNative());
String getAddress({int accountIndex = 0, int addressIndex = 0}) =>
convertUTF8ToString(pointer: getAddressNative(accountIndex, addressIndex));
int getFullBalance({int accountIndex = 0}) =>
getFullBalanceNative(accountIndex);
int getFullBalance({int accountIndex = 0}) => getFullBalanceNative(accountIndex);
int getUnlockedBalance({int accountIndex = 0}) =>
getUnlockedBalanceNative(accountIndex);
int getUnlockedBalance({int accountIndex = 0}) => getUnlockedBalanceNative(accountIndex);
int getCurrentHeight() => getCurrentHeightNative();
@ -202,8 +195,7 @@ bool setupNodeSync(
}
if (!isSetupNode) {
throw SetupWalletException(
message: convertUTF8ToString(pointer: errorMessagePointer));
throw SetupWalletException(message: convertUTF8ToString(pointer: errorMessagePointer));
}
return isSetupNode;
@ -213,8 +205,7 @@ void startRefreshSync() => startRefreshNative();
Future<bool> connectToNode() async => connecToNodeNative() != 0;
void setRefreshFromBlockHeight({required int height}) =>
setRefreshFromBlockHeightNative(height);
void setRefreshFromBlockHeight({required int height}) => setRefreshFromBlockHeightNative(height);
void setRecoveringFromSeed({required bool isRecovery}) =>
setRecoveringFromSeedNative(_boolToInt(isRecovery));
@ -242,17 +233,13 @@ void setPasswordSync(String password) {
void closeCurrentWallet() => closeCurrentWalletNative();
String getSecretViewKey() =>
convertUTF8ToString(pointer: getSecretViewKeyNative());
String getSecretViewKey() => convertUTF8ToString(pointer: getSecretViewKeyNative());
String getPublicViewKey() =>
convertUTF8ToString(pointer: getPublicViewKeyNative());
String getPublicViewKey() => convertUTF8ToString(pointer: getPublicViewKeyNative());
String getSecretSpendKey() =>
convertUTF8ToString(pointer: getSecretSpendKeyNative());
String getSecretSpendKey() => convertUTF8ToString(pointer: getSecretSpendKeyNative());
String getPublicSpendKey() =>
convertUTF8ToString(pointer: getPublicSpendKeyNative());
String getPublicSpendKey() => convertUTF8ToString(pointer: getPublicSpendKeyNative());
class SyncListener {
SyncListener(this.onNewBlock, this.onNewTransaction)
@ -260,7 +247,6 @@ class SyncListener {
_lastKnownBlockHeight = 0,
_initialSyncHeight = 0;
void Function(int, int, double) onNewBlock;
void Function() onNewTransaction;
@ -281,8 +267,7 @@ class SyncListener {
_cachedBlockchainHeight = 0;
_lastKnownBlockHeight = 0;
_initialSyncHeight = 0;
_updateSyncInfoTimer ??=
Timer.periodic(Duration(milliseconds: 1200), (_) async {
_updateSyncInfoTimer ??= Timer.periodic(Duration(milliseconds: 1200), (_) async {
if (isNewTransactionExist()) {
onNewTransaction();
}
@ -321,8 +306,8 @@ class SyncListener {
void stop() => _updateSyncInfoTimer?.cancel();
}
SyncListener setListeners(void Function(int, int, double) onNewBlock,
void Function() onNewTransaction) {
SyncListener setListeners(
void Function(int, int, double) onNewBlock, void Function() onNewTransaction) {
final listener = SyncListener(onNewBlock, onNewTransaction);
setListenerNative();
return listener;
@ -397,3 +382,23 @@ String signMessage(String message, {String address = ""}) {
return signature;
}
bool setCacheAttribute(String name, String value) {
final namePointer = name.toNativeUtf8();
final valuePointer = value.toNativeUtf8();
final isSet = setCacheAttributeNative(namePointer, valuePointer);
calloc.free(namePointer);
calloc.free(valuePointer);
return isSet == 1;
}
String getCacheAttribute(String name) {
final namePointer = name.toNativeUtf8();
final value = convertUTF8ToString(pointer: getCacheAttributeNative(namePointer));
calloc.free(namePointer);
return value;
}

View file

@ -1,147 +0,0 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.5.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.10"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.19"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
sdks:
dart: ">=2.12.0-0.0 <3.0.0"
flutter: ">=1.20.0"

View file

@ -19,30 +19,6 @@ class DefaultSPLTokens {
mint: 'usdcsol',
enabled: true,
),
SPLToken(
name: 'Wrapped Ethereum (Sollet)',
symbol: 'soETH',
mintAddress: '2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk',
decimal: 6,
mint: 'soEth',
iconPath: 'assets/images/eth_icon.png',
),
SPLToken(
name: 'Wrapped SOL',
symbol: 'WSOL',
mintAddress: 'So11111111111111111111111111111111111111112',
decimal: 9,
mint: 'WSOL',
iconPath: 'assets/images/sol_icon.png',
),
SPLToken(
name: 'Wrapped Bitcoin (Sollet)',
symbol: 'BTC',
mintAddress: '9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E',
decimal: 6,
mint: 'btcsol',
iconPath: 'assets/images/btc.png',
),
SPLToken(
name: 'Bonk',
symbol: 'Bonk',
@ -50,21 +26,7 @@ class DefaultSPLTokens {
decimal: 5,
mint: 'Bonk',
iconPath: 'assets/images/bonk_icon.png',
),
SPLToken(
name: 'Helium Network Token',
symbol: 'HNT',
mintAddress: 'hntyVP6YFm1Hg25TN9WGLqM12b8TQmcknKrdu1oxWux',
decimal: 8,
mint: 'hnt',
iconPath: 'assets/images/hnt_icon.png',
),
SPLToken(
name: 'Pyth Network',
symbol: 'PYTH',
mintAddress: 'HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3',
decimal: 6,
mint: 'pyth',
enabled: true,
),
SPLToken(
name: 'Raydium',
@ -73,6 +35,51 @@ class DefaultSPLTokens {
decimal: 6,
mint: 'ray',
iconPath: 'assets/images/ray_icon.png',
enabled: true,
),
SPLToken(
name: 'Wrapped Ethereum (Sollet)',
symbol: 'soETH',
mintAddress: '2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk',
decimal: 6,
mint: 'soEth',
iconPath: 'assets/images/eth_icon.png',
enabled: false,
),
SPLToken(
name: 'Wrapped SOL',
symbol: 'WSOL',
mintAddress: 'So11111111111111111111111111111111111111112',
decimal: 9,
mint: 'WSOL',
iconPath: 'assets/images/sol_icon.png',
enabled: false,
),
SPLToken(
name: 'Wrapped Bitcoin (Sollet)',
symbol: 'BTC',
mintAddress: '9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E',
decimal: 6,
mint: 'btcsol',
iconPath: 'assets/images/btc.png',
enabled: false,
),
SPLToken(
name: 'Helium Network Token',
symbol: 'HNT',
mintAddress: 'hntyVP6YFm1Hg25TN9WGLqM12b8TQmcknKrdu1oxWux',
decimal: 8,
mint: 'hnt',
iconPath: 'assets/images/hnt_icon.png',
enabled: false,
),
SPLToken(
name: 'Pyth Network',
symbol: 'PYTH',
mintAddress: 'HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3',
decimal: 6,
mint: 'pyth',
enabled: false,
),
SPLToken(
name: 'GMT',
@ -81,6 +88,7 @@ class DefaultSPLTokens {
decimal: 6,
mint: 'ray',
iconPath: 'assets/images/gmt_icon.png',
enabled: false,
),
SPLToken(
name: 'AvocadoCoin',
@ -89,6 +97,7 @@ class DefaultSPLTokens {
decimal: 8,
mint: 'avdo',
iconPath: 'assets/images/avdo_icon.png',
enabled: false,
),
];

View file

@ -15,8 +15,8 @@ class SolanaBalance extends Balance {
String _balanceFormatted() {
String stringBalance = balance.toString();
if (stringBalance.toString().length >= 6) {
stringBalance = stringBalance.substring(0, 6);
if (stringBalance.toString().length >= 12) {
stringBalance = stringBalance.substring(0, 12);
}
return stringBalance;
}

View file

@ -15,8 +15,14 @@ dependencies:
path: ../cw_core
cw_evm:
path: ../cw_evm
on_chain: ^3.0.1
blockchain_utils: ^2.1.1
on_chain:
git:
url: https://github.com/cake-tech/On_chain
ref: cake-update-v1
blockchain_utils:
git:
url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v1
mobx: ^2.3.0+1
bip39: ^1.0.6
hive: ^2.2.3

View file

@ -142,27 +142,9 @@ Then we need to generate localization files.
`$ flutter packages pub run tool/generate_localization.dart`
Lastly, we will generate mobx models for the project.
Generate mobx models for `cw_core`:
`cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..`
Generate mobx models for `cw_monero`:
`cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..`
Generate mobx models for `cw_bitcoin`:
`cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..`
Generate mobx models for `cw_haven`:
`cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..`
Finally build mobx models for the app:
`$ flutter packages pub run build_runner build --delete-conflicting-outputs`
`$ ./model_generator.sh`
### 9. Build!

View file

@ -147,6 +147,8 @@ PODS:
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sp_scanner (0.0.1):
- Flutter
- SwiftProtobuf (1.25.2)
- SwiftyGif (5.4.4)
- Toast (4.1.0)
@ -188,6 +190,7 @@ DEPENDENCIES:
- sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sp_scanner (from `.symlinks/plugins/sp_scanner/ios`)
- uni_links (from `.symlinks/plugins/uni_links/ios`)
- UnstoppableDomainsResolution (~> 4.0.0)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
@ -259,6 +262,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sp_scanner:
:path: ".symlinks/plugins/sp_scanner/ios"
uni_links:
:path: ".symlinks/plugins/uni_links/ios"
url_launcher_ios:
@ -302,6 +307,7 @@ SPEC CHECKSUMS:
sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sp_scanner: eaa617fa827396b967116b7f1f43549ca62e9a12
SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
Toast: ec33c32b8688982cecc6348adeae667c1b9938da

View file

@ -121,20 +121,12 @@ class CWBitcoin extends Bitcoin {
priority: priority != null ? priority as BitcoinTransactionPriority : null,
feeRate: feeRate);
@override
List<String> getAddresses(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.walletAddresses.addressesByReceiveType
.map((BitcoinAddressRecord addr) => addr.address)
.toList();
}
@override
@computed
List<ElectrumSubAddress> getSubAddresses(Object wallet) {
final electrumWallet = wallet as ElectrumWallet;
return electrumWallet.walletAddresses.addressesByReceiveType
.map((BitcoinAddressRecord addr) => ElectrumSubAddress(
.map((BaseBitcoinAddressRecord addr) => ElectrumSubAddress(
id: addr.index,
name: addr.name,
address: addr.address,
@ -207,12 +199,12 @@ class CWBitcoin extends Bitcoin {
Future<void> updateUnspents(Object wallet) async {
final bitcoinWallet = wallet as ElectrumWallet;
await bitcoinWallet.updateUnspent();
await bitcoinWallet.updateAllUnspents();
}
WalletService createBitcoinWalletService(
Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource, bool isDirect) {
return BitcoinWalletService(walletInfoSource, unspentCoinSource, isDirect);
Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource, bool alwaysScan, bool isDirect) {
return BitcoinWalletService(walletInfoSource, unspentCoinSource, alwaysScan, isDirect);
}
WalletService createLitecoinWalletService(
@ -247,6 +239,12 @@ class CWBitcoin extends Bitcoin {
return BitcoinReceivePageOption.fromType(bitcoinWallet.walletAddresses.addressPageType);
}
@override
bool hasSelectedSilentPayments(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.walletAddresses.addressPageType == SilentPaymentsAddresType.p2sp;
}
@override
List<ReceivePageOption> getBitcoinReceivePageOptions() => BitcoinReceivePageOption.all;
@ -465,4 +463,137 @@ class CWBitcoin extends Bitcoin {
throw err;
}
}
@override
List<ElectrumSubAddress> getSilentPaymentAddresses(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.walletAddresses.silentAddresses
.where((addr) => addr.type != SegwitAddresType.p2tr)
.map((addr) => ElectrumSubAddress(
id: addr.index,
name: addr.name,
address: addr.address,
txCount: addr.txCount,
balance: addr.balance,
isChange: addr.isHidden))
.toList();
}
@override
List<ElectrumSubAddress> getSilentPaymentReceivedAddresses(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.walletAddresses.silentAddresses
.where((addr) => addr.type == SegwitAddresType.p2tr)
.map((addr) => ElectrumSubAddress(
id: addr.index,
name: addr.name,
address: addr.address,
txCount: addr.txCount,
balance: addr.balance,
isChange: addr.isHidden))
.toList();
}
@override
bool isBitcoinReceivePageOption(ReceivePageOption option) {
return option is BitcoinReceivePageOption;
}
@override
BitcoinAddressType getOptionToType(ReceivePageOption option) {
return (option as BitcoinReceivePageOption).toType();
}
@override
@computed
bool getScanningActive(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.silentPaymentsScanningActive;
}
@override
Future<void> setScanningActive(Object wallet, bool active) async {
final bitcoinWallet = wallet as ElectrumWallet;
if (active && !(await getNodeIsElectrsSPEnabled(wallet))) {
final node = Node(
useSSL: false,
uri: 'electrs.cakewallet.com:${(wallet.network == BitcoinNetwork.testnet ? 50002 : 50001)}',
);
node.type = WalletType.bitcoin;
await bitcoinWallet.connectToNode(node: node);
}
bitcoinWallet.setSilentPaymentsScanning(active);
}
@override
bool isTestnet(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.isTestnet ?? false;
}
@override
int getHeightByDate({required DateTime date}) => getBitcoinHeightByDate(date: date);
@override
Future<void> rescan(Object wallet, {required int height, bool? doSingleScan}) async {
final bitcoinWallet = wallet as ElectrumWallet;
if (!(await getNodeIsElectrsSPEnabled(wallet))) {
final node = Node(
useSSL: false,
uri: 'electrs.cakewallet.com:${(wallet.network == BitcoinNetwork.testnet ? 50002 : 50001)}',
);
node.type = WalletType.bitcoin;
await bitcoinWallet.connectToNode(node: node);
}
bitcoinWallet.rescan(height: height, doSingleScan: doSingleScan);
}
Future<bool> getNodeIsElectrs(Object wallet) async {
final bitcoinWallet = wallet as ElectrumWallet;
final version = await bitcoinWallet.electrumClient.version();
if (version.isEmpty) {
return false;
}
final server = version[0];
if (server.toLowerCase().contains('electrs')) {
return true;
}
return false;
}
@override
Future<bool> getNodeIsElectrsSPEnabled(Object wallet) async {
if (!(await getNodeIsElectrs(wallet))) {
return false;
}
final bitcoinWallet = wallet as ElectrumWallet;
final tweaksResponse = await bitcoinWallet.electrumClient.getTweaks(height: 0);
if (tweaksResponse != null) {
return true;
}
return false;
}
@override
void deleteSilentPaymentAddress(Object wallet, String address) {
final bitcoinWallet = wallet as ElectrumWallet;
bitcoinWallet.walletAddresses.deleteSilentPaymentAddress(address);
}
@override
Future<void> updateFeeRates(Object wallet) async {
final bitcoinWallet = wallet as ElectrumWallet;
await bitcoinWallet.updateFeeRates();
}
}

View file

@ -26,7 +26,7 @@ class AddressValidator extends TextValidator {
return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$'
'|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$';
case CryptoCurrency.btc:
return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$';
return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$|^${SilentPaymentAddress.regex.pattern}\$';
case CryptoCurrency.nano:
return '[0-9a-zA-Z_]';
case CryptoCurrency.banano:
@ -274,7 +274,9 @@ class AddressValidator extends TextValidator {
'|([^0-9a-zA-Z]|^)([23][a-km-zA-HJ-NP-Z1-9]{25,34})([^0-9a-zA-Z]|\$)' //P2shAddress type
'|([^0-9a-zA-Z]|^)((bc|tb)1q[ac-hj-np-z02-9]{25,39})([^0-9a-zA-Z]|\$)' //P2wpkhAddress type
'|([^0-9a-zA-Z]|^)((bc|tb)1q[ac-hj-np-z02-9]{40,80})([^0-9a-zA-Z]|\$)' //P2wshAddress type
'|([^0-9a-zA-Z]|^)((bc|tb)1p([ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89}))([^0-9a-zA-Z]|\$)'; //P2trAddress type
'|([^0-9a-zA-Z]|^)((bc|tb)1p([ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89}))([^0-9a-zA-Z]|\$)' //P2trAddress type
'|${SilentPaymentAddress.regex.pattern}\$';
case CryptoCurrency.ltc:
return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)'
'|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)'

View file

@ -3,7 +3,13 @@ import 'package:cw_core/sync_status.dart';
String syncStatusTitle(SyncStatus syncStatus) {
if (syncStatus is SyncingSyncStatus) {
return S.current.Blocks_remaining('${syncStatus.blocksLeft}');
return syncStatus.blocksLeft == 1
? S.current.block_remaining
: S.current.Blocks_remaining('${syncStatus.blocksLeft}');
}
if (syncStatus is SyncedTipSyncStatus) {
return S.current.silent_payments_scanned_tip(syncStatus.tip.toString());
}
if (syncStatus is SyncedSyncStatus) {
@ -34,5 +40,17 @@ String syncStatusTitle(SyncStatus syncStatus) {
return S.current.sync_status_failed_connect;
}
if (syncStatus is UnsupportedSyncStatus) {
return S.current.sync_status_unsupported;
}
if (syncStatus is TimedOutSyncStatus) {
return S.current.sync_status_timed_out;
}
if (syncStatus is SyncronizingSyncStatus) {
return S.current.sync_status_syncronizing;
}
return '';
}

View file

@ -61,7 +61,7 @@ class WalletCreationService {
password: credentials.password!, walletName: credentials.name);
}
if (type == WalletType.bitcoinCash || type == WalletType.ethereum) {
if (_hasSeedPhraseLengthOption) {
credentials.seedPhraseLength = settingsStore.seedPhraseLength.value;
}
final wallet = await _service!.create(credentials, isTestnet: isTestnet);
@ -74,6 +74,25 @@ class WalletCreationService {
return wallet;
}
bool get _hasSeedPhraseLengthOption {
switch (type) {
case WalletType.ethereum:
case WalletType.bitcoinCash:
case WalletType.polygon:
case WalletType.solana:
case WalletType.tron:
return true;
case WalletType.monero:
case WalletType.none:
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.haven:
case WalletType.nano:
case WalletType.banano:
return false;
}
}
Future<WalletBase> restoreFromKeys(WalletCredentials credentials, {bool? isTestnet}) async {
checkIfExists(credentials.name);

View file

@ -10,6 +10,7 @@ import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
import 'package:cake_wallet/anypay/anypay_api.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart';
import 'package:cake_wallet/buy/order.dart';
import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart';
@ -17,6 +18,8 @@ import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/backup_service.dart';
import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:cake_wallet/core/totp_request_details.dart';
import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart';
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
@ -27,6 +30,7 @@ import 'package:cake_wallet/entities/biometric_auth.dart';
import 'package:cake_wallet/entities/contact.dart';
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
@ -106,6 +110,7 @@ import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart';
import 'package:cake_wallet/src/screens/send/send_page.dart';
import 'package:cake_wallet/src/screens/send/send_template_page.dart';
import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart';
import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/domain_lookups_page.dart';
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
@ -117,6 +122,7 @@ import 'package:cake_wallet/themes/theme_list.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
import 'package:cake_wallet/src/screens/settings/silent_payments_settings.dart';
import 'package:cake_wallet/src/screens/settings/tor_page.dart';
import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
@ -131,6 +137,7 @@ import 'package:cake_wallet/src/screens/wallet/wallet_edit_page.dart';
import 'package:cake_wallet/src/screens/support_chat/support_chat_page.dart';
import 'package:cake_wallet/src/screens/support_other_links/support_other_links_page.dart';
import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart';
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart';
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
@ -152,6 +159,8 @@ import 'package:cake_wallet/store/templates/exchange_template_store.dart';
import 'package:cake_wallet/store/templates/send_template_store.dart';
import 'package:cake_wallet/store/wallet_list_store.dart';
import 'package:cake_wallet/store/yat/yat_store.dart';
import 'package:cake_wallet/themes/theme_list.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
@ -163,9 +172,9 @@ import 'package:cake_wallet/view_model/buy/buy_view_model.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart';
@ -181,6 +190,7 @@ import 'package:cake_wallet/view_model/ionia/ionia_gift_card_details_view_model.
import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_payment_status_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
@ -201,6 +211,7 @@ import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart';
import 'package:cake_wallet/view_model/settings/security_settings_view_model.dart';
import 'package:cake_wallet/view_model/settings/silent_payments_settings_view_model.dart';
import 'package:cake_wallet/view_model/settings/trocador_providers_view_model.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart';
@ -232,6 +243,7 @@ import 'package:cake_wallet/view_model/wallet_seed_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/nano_account.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/receive_page_option.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_info.dart';
@ -755,6 +767,9 @@ Future<void> setup({
return DisplaySettingsViewModel(getIt.get<SettingsStore>());
});
getIt.registerFactory(() =>
SilentPaymentsSettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!));
getIt.registerFactory(() {
return PrivacySettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!);
});
@ -805,8 +820,8 @@ Future<void> setup({
getIt.registerFactory(() => ConnectionSyncPage(getIt.get<DashboardViewModel>()));
getIt.registerFactory(
() => SecurityBackupPage(getIt.get<SecuritySettingsViewModel>(), getIt.get<AuthService>()));
getIt.registerFactory(() => SecurityBackupPage(getIt.get<SecuritySettingsViewModel>(),
getIt.get<AuthService>(), getIt.get<AppStore>().wallet!.isHardwareWallet));
getIt.registerFactory(() => PrivacyPage(getIt.get<PrivacySettingsViewModel>()));
@ -816,6 +831,9 @@ Future<void> setup({
getIt.registerFactory(() => DisplaySettingsPage(getIt.get<DisplaySettingsViewModel>()));
getIt.registerFactory(
() => SilentPaymentsSettingsPage(getIt.get<SilentPaymentsSettingsViewModel>()));
getIt.registerFactory(() => OtherSettingsPage(getIt.get<OtherSettingsViewModel>()));
getIt.registerFactory(() => NanoChangeRepPage(
@ -903,8 +921,12 @@ Future<void> setup({
case WalletType.monero:
return monero!.createMoneroWalletService(_walletInfoSource, _unspentCoinsInfoSource);
case WalletType.bitcoin:
return bitcoin!.createBitcoinWalletService(_walletInfoSource, _unspentCoinsInfoSource,
SettingsStoreBase.walletPasswordDirectInput);
return bitcoin!.createBitcoinWalletService(
_walletInfoSource,
_unspentCoinsInfoSource,
getIt.get<SettingsStore>().silentPaymentsAlwaysScan,
SettingsStoreBase.walletPasswordDirectInput,
);
case WalletType.litecoin:
return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource,
SettingsStoreBase.walletPasswordDirectInput);
@ -1106,7 +1128,7 @@ Future<void> setup({
getIt.registerFactory(() => IoniaGiftCardsListViewModel(ioniaService: getIt.get<IoniaService>()));
getIt.registerFactory(() => MarketPlaceViewModel(getIt.get<IoniaService>()));
getIt.registerFactory(() => CakeFeaturesViewModel(getIt.get<IoniaService>()));
getIt.registerFactory(() => IoniaAuthViewModel(ioniaService: getIt.get<IoniaService>()));

View file

@ -24,8 +24,9 @@ import 'package:collection/collection.dart';
const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081';
const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
const publicBitcoinTestnetElectrumAddress = 'electrum.blockstream.info';
const publicBitcoinTestnetElectrumPort = '60002';
const cakeWalletSilentPaymentsElectrsUri = 'electrs.cakewallet.com:50001';
const publicBitcoinTestnetElectrumAddress = 'electrs.cakewallet.com';
const publicBitcoinTestnetElectrumPort = '50002';
const publicBitcoinTestnetElectrumUri =
'$publicBitcoinTestnetElectrumAddress:$publicBitcoinTestnetElectrumPort';
const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
@ -224,6 +225,9 @@ Future<void> defaultSettingsMigration(
await addTronNodeList(nodes: nodes);
await changeTronCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
break;
case 34:
await _addElectRsNode(nodes, sharedPreferences);
break;
default:
break;
}
@ -790,7 +794,8 @@ Future<void> changeDefaultBitcoinNode(
final needToReplaceCurrentBitcoinNode =
currentBitcoinNode.uri.toString().contains(cakeWalletBitcoinNodeUriPattern);
final newCakeWalletBitcoinNode = Node(uri: newCakeWalletBitcoinUri, type: WalletType.bitcoin);
final newCakeWalletBitcoinNode =
Node(uri: newCakeWalletBitcoinUri, type: WalletType.bitcoin, useSSL: false);
await nodeSource.add(newCakeWalletBitcoinNode);
@ -800,6 +805,26 @@ Future<void> changeDefaultBitcoinNode(
}
}
Future<void> _addElectRsNode(Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
const cakeWalletBitcoinNodeUriPattern = '.cakewallet.com';
final currentBitcoinNodeId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
final currentBitcoinNode =
nodeSource.values.firstWhere((node) => node.key == currentBitcoinNodeId);
final needToReplaceCurrentBitcoinNode =
currentBitcoinNode.uri.toString().contains(cakeWalletBitcoinNodeUriPattern);
final newElectRsBitcoinNode =
Node(uri: cakeWalletSilentPaymentsElectrsUri, type: WalletType.bitcoin, useSSL: false);
await nodeSource.add(newElectRsBitcoinNode);
if (needToReplaceCurrentBitcoinNode) {
await sharedPreferences.setInt(
PreferencesKey.currentBitcoinElectrumSererIdKey, newElectRsBitcoinNode.key as int);
}
}
Future<void> checkCurrentNodes(
Box<Node> nodeSource, Box<Node> powNodeSource, SharedPreferences sharedPreferences) async {
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
@ -845,14 +870,19 @@ Future<void> checkCurrentNodes(
}
if (currentBitcoinElectrumServer == null) {
final cakeWalletElectrum = Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
final cakeWalletElectrum =
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: false);
await nodeSource.add(cakeWalletElectrum);
final cakeWalletElectrumTestnet =
Node(uri: publicBitcoinTestnetElectrumUri, type: WalletType.bitcoin, useSSL: false);
await nodeSource.add(cakeWalletElectrumTestnet);
await sharedPreferences.setInt(
PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletElectrum.key as int);
}
if (currentLitecoinElectrumServer == null) {
final cakeWalletElectrum = Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin);
final cakeWalletElectrum =
Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin, useSSL: false);
await nodeSource.add(cakeWalletElectrum);
await sharedPreferences.setInt(
PreferencesKey.currentLitecoinElectrumSererIdKey, cakeWalletElectrum.key as int);
@ -887,7 +917,8 @@ Future<void> checkCurrentNodes(
}
if (currentBitcoinCashNodeServer == null) {
final node = Node(uri: cakeWalletBitcoinCashDefaultNodeUri, type: WalletType.bitcoinCash);
final node =
Node(uri: cakeWalletBitcoinCashDefaultNodeUri, type: WalletType.bitcoinCash, useSSL: false);
await nodeSource.add(node);
await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int);
}
@ -921,7 +952,11 @@ Future<void> resetBitcoinElectrumServer(
.firstWhereOrNull((node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri);
if (cakeWalletNode == null) {
cakeWalletNode = Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
cakeWalletNode =
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: false);
// final cakeWalletElectrumTestnet =
// Node(uri: publicBitcoinTestnetElectrumUri, type: WalletType.bitcoin, useSSL: false);
// await nodeSource.add(cakeWalletElectrumTestnet);
await nodeSource.add(cakeWalletNode);
}

View file

@ -44,6 +44,8 @@ class PreferencesKey {
static const polygonTransactionPriority = 'current_fee_priority_polygon';
static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash';
static const customBitcoinFeeRate = 'custom_electrum_fee_rate';
static const silentPaymentsCardDisplay = 'silentPaymentsCardDisplay';
static const silentPaymentsAlwaysScan = 'silentPaymentsAlwaysScan';
static const shouldShowReceiveWarning = 'should_show_receive_warning';
static const shouldShowYatPopup = 'should_show_yat_popup';
static const shouldShowRepWarning = 'should_show_rep_warning';

View file

@ -203,7 +203,7 @@ Future<void> initializeAppConfigs() async {
transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 33,
initialMigrationVersion: 34,
);
}

View file

@ -3,15 +3,19 @@ import 'dart:async';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/store/settings_store.dart';
Timer? _checkConnectionTimer;
void startCheckConnectionReaction(
WalletBase wallet, SettingsStore settingsStore,
void startCheckConnectionReaction(WalletBase wallet, SettingsStore settingsStore,
{int timeInterval = 5}) {
_checkConnectionTimer?.cancel();
_checkConnectionTimer =
Timer.periodic(Duration(seconds: timeInterval), (_) async {
_checkConnectionTimer = Timer.periodic(Duration(seconds: timeInterval), (_) async {
if (wallet.type == WalletType.bitcoin && wallet.syncStatus is SyncingSyncStatus) {
return;
}
try {
final connectivityResult = await (Connectivity().checkConnectivity());
@ -20,14 +24,11 @@ void startCheckConnectionReaction(
return;
}
if (wallet.syncStatus is LostConnectionSyncStatus ||
wallet.syncStatus is FailedSyncStatus) {
final alive =
await settingsStore.getCurrentNode(wallet.type).requestNode();
if (wallet.syncStatus is LostConnectionSyncStatus || wallet.syncStatus is FailedSyncStatus) {
final alive = await settingsStore.getCurrentNode(wallet.type).requestNode();
if (alive) {
await wallet.connectToNode(
node: settingsStore.getCurrentNode(wallet.type));
await wallet.connectToNode(node: settingsStore.getCurrentNode(wallet.type));
}
}
} catch (e) {

View file

@ -12,12 +12,10 @@ import 'package:wakelock_plus/wakelock_plus.dart';
ReactionDisposer? _onWalletSyncStatusChangeReaction;
void startWalletSyncStatusChangeReaction(
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
TransactionInfo> wallet,
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet,
FiatConversionStore fiatConversionStore) {
_onWalletSyncStatusChangeReaction?.reaction.dispose();
_onWalletSyncStatusChangeReaction =
reaction((_) => wallet.syncStatus, (SyncStatus status) async {
_onWalletSyncStatusChangeReaction = reaction((_) => wallet.syncStatus, (SyncStatus status) async {
try {
if (status is ConnectedSyncStatus) {
await wallet.startSync();

View file

@ -76,6 +76,7 @@ import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
import 'package:cake_wallet/src/screens/settings/silent_payments_settings.dart';
import 'package:cake_wallet/src/screens/settings/tor_page.dart';
import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
@ -407,6 +408,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
param2: false),
onWillPop: () async => false));
case Routes.silentPaymentsSettings:
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<SilentPaymentsSettingsPage>());
case Routes.connectionSync:
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<ConnectionSyncPage>());

View file

@ -81,6 +81,7 @@ class Routes {
static const ioniaMoreOptionsPage = '/ionia_more_options_page';
static const ioniaCustomRedeemPage = '/ionia_custom_redeem_page';
static const webViewPage = '/web_view_page';
static const silentPaymentsSettings = '/silent_payments_settings';
static const connectionSync = '/connection_sync_page';
static const securityBackupPage = '/security_and_backup_page';
static const privacyPage = '/privacy_page';

View file

@ -46,6 +46,7 @@ class ContactPage extends BasePage {
final TextEditingController _nameController;
final TextEditingController _currencyTypeController;
final TextEditingController _addressController;
bool _isEffectsApplied = false;
@override
Widget body(BuildContext context) {
@ -53,15 +54,7 @@ class ContactPage extends BasePage {
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
height: 8);
reaction((_) => contactViewModel.state, (ExecutionState state) {
if (state is FailureState) {
_onContactSavingFailure(context, state.error);
}
if (state is ExecutedSuccessfullyState) {
_onContactSavedSuccessfully(context);
}
});
_setEffects(context);
return Observer(
builder: (_) => ScrollableWithBottomSection(
@ -177,4 +170,22 @@ class ContactPage extends BasePage {
void _onContactSavedSuccessfully(BuildContext context) =>
Navigator.of(context).pop();
void _setEffects(BuildContext context) {
if (_isEffectsApplied) {
return;
}
_isEffectsApplied = true;
reaction((_) => contactViewModel.state, (ExecutionState state) {
if (state is FailureState) {
_onContactSavingFailure(context, state.error);
}
if (state is ExecutedSuccessfullyState) {
_onContactSavedSuccessfully(context);
}
});
}
}

View file

@ -4,7 +4,7 @@ import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/main_actions.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/market_place_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/cake_features_page.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart';
import 'package:cake_wallet/src/widgets/gradient_background.dart';
import 'package:cake_wallet/src/widgets/services_updates_widget.dart';
@ -12,7 +12,7 @@ import 'package:cake_wallet/src/widgets/vulnerable_seeds_popup.dart';
import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/version_comparator.dart';
import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/yat_emoji_id.dart';
@ -36,7 +36,7 @@ import 'package:cake_wallet/src/screens/release_notes/release_notes_screen.dart'
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
class DashboardPage extends StatelessWidget {
class DashboardPage extends StatefulWidget {
DashboardPage({
required this.bottomSheetService,
required this.balancePage,
@ -49,24 +49,53 @@ class DashboardPage extends StatelessWidget {
final DashboardViewModel dashboardViewModel;
final WalletAddressListViewModel addressListViewModel;
@override
State<DashboardPage> createState() => _DashboardPageState();
}
class _DashboardPageState extends State<DashboardPage> {
@override
void initState() {
super.initState();
bool isMobileLayout =
responsiveLayoutUtil.screenWidth < ResponsiveLayoutUtilBase.kMobileThreshold;
reaction((_) => responsiveLayoutUtil.screenWidth, (screenWidth) {
// Check if it was previously in mobile layout, and now changing to desktop
if (isMobileLayout &&
screenWidth > ResponsiveLayoutUtilBase.kDesktopMaxDashBoardWidthConstraint) {
setState(() {
isMobileLayout = false;
});
}
// Check if it was previously in desktop layout, and now changing to mobile
if (!isMobileLayout &&
screenWidth <= ResponsiveLayoutUtilBase.kDesktopMaxDashBoardWidthConstraint) {
setState(() {
isMobileLayout = true;
});
}
});
}
@override
Widget build(BuildContext context) {
final screenHeight = MediaQuery.of(context).size.height;
return Scaffold(
body: Builder(
builder: (_) {
Widget dashboardChild;
final dashboardPageView = RefreshIndicator(
displacement: screenHeight * 0.1,
onRefresh: () async => await dashboardViewModel.refreshDashboard(),
displacement: responsiveLayoutUtil.screenHeight * 0.1,
onRefresh: () async => await widget.dashboardViewModel.refreshDashboard(),
child: SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: Container(
height: screenHeight,
height: responsiveLayoutUtil.screenHeight,
child: _DashboardPageView(
balancePage: balancePage,
bottomSheetService: bottomSheetService,
dashboardViewModel: dashboardViewModel,
addressListViewModel: addressListViewModel,
balancePage: widget.balancePage,
bottomSheetService: widget.bottomSheetService,
dashboardViewModel: widget.dashboardViewModel,
addressListViewModel: widget.addressListViewModel,
),
),
),
@ -75,18 +104,17 @@ class DashboardPage extends StatelessWidget {
if (DeviceInfo.instance.isDesktop) {
if (responsiveLayoutUtil.screenWidth >
ResponsiveLayoutUtilBase.kDesktopMaxDashBoardWidthConstraint) {
return getIt.get<DesktopSidebarWrapper>();
dashboardChild = getIt.get<DesktopSidebarWrapper>();
} else {
return dashboardPageView;
dashboardChild = dashboardPageView;
}
} else if (responsiveLayoutUtil.shouldRenderMobileUI) {
return dashboardPageView;
dashboardChild = dashboardPageView;
} else {
return getIt.get<DesktopSidebarWrapper>();
dashboardChild = getIt.get<DesktopSidebarWrapper>();
}
},
),
);
return Scaffold(body: dashboardChild);
}
}
@ -302,10 +330,10 @@ class _DashboardPageView extends BasePage {
if (dashboardViewModel.shouldShowMarketPlaceInDashboard) {
pages.add(
Semantics(
label: S.of(context).market_place,
child: MarketPlacePage(
label: 'Cake ${S.of(context).features}',
child: CakeFeaturesPage(
dashboardViewModel: dashboardViewModel,
marketPlaceViewModel: getIt.get<MarketPlaceViewModel>(),
cakeFeaturesViewModel: getIt.get<CakeFeaturesViewModel>(),
),
),
);

View file

@ -1,9 +1,9 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/main_actions.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_action_button.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/market_place_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/cake_features_page.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@ -74,9 +74,9 @@ class DesktopDashboardActions extends StatelessWidget {
],
),
Expanded(
child: MarketPlacePage(
child: CakeFeaturesPage(
dashboardViewModel: dashboardViewModel,
marketPlaceViewModel: getIt.get<MarketPlaceViewModel>(),
cakeFeaturesViewModel: getIt.get<CakeFeaturesViewModel>(),
),
),
],

View file

@ -33,6 +33,7 @@ class DesktopWalletSelectionDropDown extends StatefulWidget {
class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionDropDown> {
final moneroIcon = Image.asset('assets/images/monero_logo.png', height: 24, width: 24);
final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
final tBitcoinIcon = Image.asset('assets/images/tbtc.png', height: 24, width: 24);
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
@ -72,7 +73,10 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
constraints: BoxConstraints(maxWidth: 500),
child: DropDownItemWidget(
title: wallet.name,
image: wallet.isEnabled ? _imageFor(type: wallet.type) : nonWalletTypeIcon),
image: wallet.isEnabled
? _imageFor(type: wallet.type, isTestnet: wallet.isTestnet)
: nonWalletTypeIcon,
),
),
onSelected: () => _onSelectedWallet(wallet),
))
@ -141,9 +145,12 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
});
}
Image _imageFor({required WalletType type}) {
Image _imageFor({required WalletType type, bool? isTestnet}) {
switch (type) {
case WalletType.bitcoin:
if (isTestnet == true) {
return tBitcoinIcon;
}
return bitcoinIcon;
case WalletType.monero:
return moneroIcon;
@ -215,17 +222,16 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
context,
route: Routes.newWallet,
arguments: widget.walletListViewModel.currentWalletType,
conditionToDetermineIfToUse2FA: widget
.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
);
} else {
widget._authService.authenticateAction(
context,
route: Routes.newWalletType,
conditionToDetermineIfToUse2FA: widget
.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
);
}
}

View file

@ -187,6 +187,11 @@ class AddressPage extends BasePage {
}
reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) {
if (bitcoin!.isBitcoinReceivePageOption(option)) {
addressListViewModel.setAddressType(bitcoin!.getOptionToType(option));
return;
}
switch (option) {
case ReceivePageOption.anonPayInvoice:
Navigator.pushNamed(

View file

@ -1,15 +1,18 @@
import 'dart:math';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/nft_listing_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
import 'package:cake_wallet/src/widgets/introducing_card.dart';
import 'package:cake_wallet/src/widgets/standard_switch.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
@ -21,6 +24,7 @@ import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:url_launcher/url_launcher.dart';
class BalancePage extends StatelessWidget {
BalancePage({
@ -221,6 +225,7 @@ class CryptoBalanceWidget extends StatelessWidget {
itemBuilder: (__, index) {
final balance =
dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index);
return Observer(builder: (_) {
return BalanceRowWidget(
availableBalanceLabel:
'${dashboardViewModel.balanceViewModel.availableBalanceLabel}',
@ -235,15 +240,120 @@ class CryptoBalanceWidget extends StatelessWidget {
currency: balance.asset,
hasAdditionalBalance:
dashboardViewModel.balanceViewModel.hasAdditionalBalance,
isTestnet: dashboardViewModel.isTestnet,
);
});
},
);
},
),
Observer(builder: (context) {
return Column(
children: [
if (dashboardViewModel.showSilentPaymentsCard) ...[
SizedBox(height: 10),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: DashBoardRoundedCardWidget(
customBorder: 30,
title: S.of(context).silent_payments,
subTitle: S.of(context).enable_silent_payments_scanning,
hint: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => launchUrl(
Uri.parse(
"https://guides.cakewallet.com/docs/cryptos/bitcoin/#silent-payments"),
mode: LaunchMode.externalApplication,
),
child: Row(
children: [
Text(
S.of(context).what_is_silent_payments,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
height: 1,
),
softWrap: true,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Icon(Icons.help_outline,
size: 16,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor),
)
],
),
),
Observer(
builder: (_) => StandardSwitch(
value: dashboardViewModel.silentPaymentsScanningActive,
onTaped: () => _toggleSilentPaymentsScanning(context),
),
)
],
),
],
),
onTap: () => _toggleSilentPaymentsScanning(context),
icon: Icon(
Icons.lock,
color:
Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
size: 50,
),
),
),
]
],
);
}),
],
),
),
);
}
Future<void> _toggleSilentPaymentsScanning(BuildContext context) async {
final isSilentPaymentsScanningActive = dashboardViewModel.silentPaymentsScanningActive;
final newValue = !isSilentPaymentsScanningActive;
dashboardViewModel.silentPaymentsScanningActive = newValue;
final needsToSwitch = !isSilentPaymentsScanningActive &&
await bitcoin!.getNodeIsElectrsSPEnabled(dashboardViewModel.wallet) == false;
if (needsToSwitch) {
return showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithTwoActions(
alertTitle: S.of(context).change_current_node_title,
alertContent: S.of(context).confirm_silent_payments_switch_node,
rightButtonText: S.of(context).ok,
leftButtonText: S.of(context).cancel,
actionRightButton: () {
dashboardViewModel.setSilentPaymentsScanning(newValue);
Navigator.of(context).pop();
},
actionLeftButton: () {
dashboardViewModel.silentPaymentsScanningActive = isSilentPaymentsScanningActive;
Navigator.of(context).pop();
},
));
}
return dashboardViewModel.setSilentPaymentsScanning(newValue);
}
}
@ -259,6 +369,7 @@ class BalanceRowWidget extends StatelessWidget {
required this.frozenFiatBalance,
required this.currency,
required this.hasAdditionalBalance,
required this.isTestnet,
super.key,
});
@ -272,6 +383,7 @@ class BalanceRowWidget extends StatelessWidget {
final String frozenFiatBalance;
final CryptoCurrency currency;
final bool hasAdditionalBalance;
final bool isTestnet;
// void _showBalanceDescription(BuildContext context) {
// showPopUp<void>(
@ -346,6 +458,16 @@ class BalanceRowWidget extends StatelessWidget {
maxLines: 1,
textAlign: TextAlign.start),
SizedBox(height: 6),
if (isTestnet)
Text(S.current.testnet_coins_no_value,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
height: 1)),
if (!isTestnet)
Text('${availableFiatBalance}',
textAlign: TextAlign.center,
style: TextStyle(
@ -362,10 +484,7 @@ class BalanceRowWidget extends StatelessWidget {
child: Center(
child: Column(
children: [
Container(
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(shape: BoxShape.circle),
child: CakeImageWidget(
CakeImageWidget(
imageUrl: currency.iconPath,
height: 40,
width: 40,
@ -384,7 +503,6 @@ class BalanceRowWidget extends StatelessWidget {
),
),
),
),
const SizedBox(height: 10),
Text(
currency.title,
@ -449,6 +567,7 @@ class BalanceRowWidget extends StatelessWidget {
textAlign: TextAlign.center,
),
SizedBox(height: 4),
if (!isTestnet)
Text(
frozenFiatBalance,
textAlign: TextAlign.center,
@ -493,6 +612,7 @@ class BalanceRowWidget extends StatelessWidget {
textAlign: TextAlign.center,
),
SizedBox(height: 4),
if (!isTestnet)
Text(
'${additionalFiatBalance}',
textAlign: TextAlign.center,

View file

@ -1,23 +1,20 @@
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
import 'package:flutter_svg/flutter_svg.dart';
class MarketPlacePage extends StatelessWidget {
MarketPlacePage({
class CakeFeaturesPage extends StatelessWidget {
CakeFeaturesPage({
required this.dashboardViewModel,
required this.marketPlaceViewModel,
required this.cakeFeaturesViewModel,
});
final DashboardViewModel dashboardViewModel;
final MarketPlaceViewModel marketPlaceViewModel;
final CakeFeaturesViewModel cakeFeaturesViewModel;
final _scrollController = ScrollController();
@override
@ -37,7 +34,7 @@ class MarketPlacePage extends StatelessWidget {
children: [
SizedBox(height: 50),
Text(
S.of(context).market_place,
'Cake ${S.of(context).features}',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w500,
@ -59,15 +56,21 @@ class MarketPlacePage extends StatelessWidget {
// ),
SizedBox(height: 20),
DashBoardRoundedCardWidget(
onTap: () => _launchUrl("buy.cakepay.com"),
title: S.of(context).cake_pay_web_cards_title,
subTitle: S.of(context).cake_pay_web_cards_subtitle,
onTap: () => _launchMarketPlaceUrl("buy.cakepay.com"),
svgPicture: SvgPicture.asset(
'assets/images/cards.svg',
height: 125,
width: 125,
fit: BoxFit.cover,
),
const SizedBox(height: 20),
),
SizedBox(height: 10),
DashBoardRoundedCardWidget(
title: "NanoGPT",
subTitle: S.of(context).nanogpt_subtitle,
onTap: () => _launchMarketPlaceUrl("cake.nano-gpt.com"),
onTap: () => _launchUrl("cake.nano-gpt.com"),
),
],
),
@ -79,41 +82,12 @@ class MarketPlacePage extends StatelessWidget {
);
}
void _launchMarketPlaceUrl(String url) async {
void _launchUrl(String url) {
try {
launchUrl(
Uri.https(url),
mode: LaunchMode.externalApplication,
);
} catch (e) {
print(e);
}
}
// TODO: Remove ionia flow/files if we will discard it
void _navigatorToGiftCardsPage(BuildContext context) {
final walletType = dashboardViewModel.type;
switch (walletType) {
case WalletType.haven:
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: S.of(context).gift_cards_unavailable,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
break;
default:
marketPlaceViewModel.isIoniaUserAuthenticated().then((value) {
if (value) {
Navigator.pushNamed(context, Routes.ioniaManageCardsPage);
return;
}
Navigator.of(context).pushNamed(Routes.ioniaWelcomePage);
});
}
} catch (_) {}
}
}

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/setting_action_button.dart';
import 'package:cake_wallet/src/widgets/setting_actions.dart';
import 'package:cake_wallet/themes/extensions/menu_theme.dart';
@ -180,6 +181,11 @@ class MenuWidgetState extends State<MenuWidget> {
final item = SettingActions.all[index];
if (!widget.dashboardViewModel.hasSilentPayments &&
item.name(context) == S.of(context).silent_payments_settings) {
return Container();
}
final isLastTile = index == itemCount - 1;
return SettingActionButton(

View file

@ -10,8 +10,7 @@ import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
class PresentReceiveOptionPicker extends StatelessWidget {
PresentReceiveOptionPicker(
{required this.receiveOptionViewModel, required this.color});
PresentReceiveOptionPicker({required this.receiveOptionViewModel, required this.color});
final ReceiveOptionViewModel receiveOptionViewModel;
final Color color;
@ -43,17 +42,17 @@ class PresentReceiveOptionPicker extends StatelessWidget {
Text(
S.current.receive,
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
fontFamily: 'Lato',
color: color),
fontSize: 18.0, fontWeight: FontWeight.bold, fontFamily: 'Lato', color: color),
),
Observer(
builder: (_) => Text(receiveOptionViewModel.selectedReceiveOption.toString(),
style: TextStyle(
fontSize: 10.0,
fontWeight: FontWeight.w500,
color: color)))
builder: (_) => Text(
receiveOptionViewModel.selectedReceiveOption
.toString()
.replaceAll(RegExp(r'silent payments', caseSensitive: false),
S.current.silent_payments)
.replaceAll(
RegExp(r'default', caseSensitive: false), S.current.string_default),
style: TextStyle(fontSize: 10.0, fontWeight: FontWeight.w500, color: color)))
],
),
SizedBox(width: 5),
@ -73,7 +72,8 @@ class PresentReceiveOptionPicker extends StatelessWidget {
backgroundColor: Colors.transparent,
body: Stack(
alignment: AlignmentDirectional.center,
children:[ AlertBackground(
children: [
AlertBackground(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
@ -107,10 +107,19 @@ class PresentReceiveOptionPicker extends StatelessWidget {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(option.toString(),
Text(
option
.toString()
.replaceAll(
RegExp(r'silent payments', caseSensitive: false),
S.current.silent_payments)
.replaceAll(RegExp(r'default', caseSensitive: false),
S.current.string_default),
textAlign: TextAlign.left,
style: textSmall(
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor,
).copyWith(
fontWeight:
value == option ? FontWeight.w800 : FontWeight.w500,

View file

@ -67,8 +67,7 @@ class ReceivePage extends BasePage {
@override
Widget Function(BuildContext, Widget) get rootWrapper =>
(BuildContext context, Widget scaffold) =>
GradientBackground(scaffold: scaffold);
(BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold);
@override
Widget trailing(BuildContext context) {
@ -159,41 +158,60 @@ class ReceivePage extends BasePage {
}
if (item is WalletAddressListHeader) {
final hasTitle = item.title != null;
cell = HeaderTile(
title: S.of(context).addresses,
title: hasTitle ? item.title! : S.of(context).addresses,
walletAddressListViewModel: addressListViewModel,
showTrailingButton: !addressListViewModel.isAutoGenerateSubaddressEnabled,
showTrailingButton:
!addressListViewModel.isAutoGenerateSubaddressEnabled && !hasTitle,
showSearchButton: true,
trailingButtonTap: () =>
Navigator.of(context).pushNamed(Routes.newSubaddress),
trailingIcon: Icon(
trailingIcon: hasTitle
? null
: Icon(
Icons.add,
size: 20,
color: Theme.of(context)
.extension<ReceivePageTheme>()!
.iconsColor,
));
color:
Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
),
);
}
if (item is WalletAddressListItem) {
cell = Observer(builder: (_) {
final isCurrent =
item.address == addressListViewModel.address.address;
final isCurrent = item.address == addressListViewModel.address.address;
final backgroundColor = isCurrent
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileBackgroundColor
: Theme.of(context).extension<ReceivePageTheme>()!.tilesBackgroundColor;
? Theme.of(context)
.extension<ReceivePageTheme>()!
.currentTileBackgroundColor
: Theme.of(context)
.extension<ReceivePageTheme>()!
.tilesBackgroundColor;
final textColor = isCurrent
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileTextColor
? Theme.of(context)
.extension<ReceivePageTheme>()!
.currentTileTextColor
: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor;
return AddressCell.fromItem(item,
return AddressCell.fromItem(
item,
isCurrent: isCurrent,
hasBalance: addressListViewModel.isElectrumWallet,
backgroundColor: backgroundColor,
textColor: textColor,
onTap: (_) => addressListViewModel.setAddress(item),
onEdit: () => Navigator.of(context)
.pushNamed(Routes.newSubaddress, arguments: item));
onTap: item.isOneTimeReceiveAddress == true
? null
: (_) => addressListViewModel.setAddress(item),
onEdit: item.isOneTimeReceiveAddress == true || item.isPrimary
? null
: () => Navigator.of(context)
.pushNamed(Routes.newSubaddress, arguments: item),
onDelete: !addressListViewModel.isSilentPayments || item.isPrimary
? null
: () => addressListViewModel.deleteAddress(item),
);
});
}
@ -201,11 +219,21 @@ class ReceivePage extends BasePage {
? cell
: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30)),
topLeft: Radius.circular(30), topRight: Radius.circular(30)),
child: cell,
);
})),
Padding(
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
child: Text(
addressListViewModel.isSilentPayments
? S.of(context).silent_payments_disclaimer
: S.of(context).electrum_address_disclaimer,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
color: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor)),
),
],
),
));

View file

@ -15,18 +15,22 @@ class AddressCell extends StatelessWidget {
required this.textColor,
this.onTap,
this.onEdit,
this.onDelete,
this.txCount,
this.balance,
this.isChange = false,
this.hasBalance = false});
factory AddressCell.fromItem(WalletAddressListItem item,
{required bool isCurrent,
factory AddressCell.fromItem(
WalletAddressListItem item, {
required bool isCurrent,
required Color backgroundColor,
required Color textColor,
Function(String)? onTap,
bool hasBalance = false,
Function()? onEdit}) =>
Function()? onEdit,
Function()? onDelete,
}) =>
AddressCell(
address: item.address,
name: item.name ?? '',
@ -36,6 +40,7 @@ class AddressCell extends StatelessWidget {
textColor: textColor,
onTap: onTap,
onEdit: onEdit,
onDelete: onDelete,
txCount: item.txCount,
balance: item.balance,
isChange: item.isChange,
@ -49,6 +54,7 @@ class AddressCell extends StatelessWidget {
final Color textColor;
final Function(String)? onTap;
final Function()? onEdit;
final Function()? onDelete;
final int? txCount;
final String? balance;
final bool isChange;
@ -64,7 +70,8 @@ class AddressCell extends StatelessWidget {
} else {
return formatIfCashAddr.substring(0, addressPreviewLength) +
'...' +
formatIfCashAddr.substring(formatIfCashAddr.length - addressPreviewLength, formatIfCashAddr.length);
formatIfCashAddr.substring(
formatIfCashAddr.length - addressPreviewLength, formatIfCashAddr.length);
}
}
@ -139,7 +146,7 @@ class AddressCell extends StatelessWidget {
mainAxisSize: MainAxisSize.max,
children: [
Text(
'Balance: $balance',
'${S.of(context).balance}: $balance',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
@ -180,7 +187,7 @@ class AddressCell extends StatelessWidget {
ActionPane _actionPane(BuildContext context) => ActionPane(
motion: const ScrollMotion(),
extentRatio: 0.3,
extentRatio: onDelete != null ? 0.4 : 0.3,
children: [
SlidableAction(
onPressed: (_) => onEdit?.call(),
@ -189,6 +196,14 @@ class AddressCell extends StatelessWidget {
icon: Icons.edit,
label: S.of(context).edit,
),
if (onDelete != null)
SlidableAction(
onPressed: (_) => onDelete!.call(),
backgroundColor: Colors.red,
foregroundColor: Colors.white,
icon: Icons.delete,
label: S.of(context).delete,
),
],
);
}

View file

@ -1,3 +1,7 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/view_model/rescan_view_model.dart';
@ -11,7 +15,8 @@ class RescanPage extends BasePage {
: _blockchainHeightWidgetKey = GlobalKey<BlockchainHeightState>();
@override
String get title => S.current.rescan;
String get title =>
_rescanViewModel.isSilentPaymentsScan ? S.current.silent_payments_scanning : S.current.rescan;
final GlobalKey<BlockchainHeightState> _blockchainHeightWidgetKey;
final RescanViewModel _rescanViewModel;
@ -19,20 +24,28 @@ class RescanPage extends BasePage {
Widget body(BuildContext context) {
return Padding(
padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
child:
Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
BlockchainHeightWidget(key: _blockchainHeightWidgetKey,
onHeightOrDateEntered: (value) =>
_rescanViewModel.isButtonEnabled = value),
child: Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Observer(
builder: (_) => BlockchainHeightWidget(
key: _blockchainHeightWidgetKey,
onHeightOrDateEntered: (value) => _rescanViewModel.isButtonEnabled = value,
isSilentPaymentsScan: _rescanViewModel.isSilentPaymentsScan,
doSingleScan: _rescanViewModel.doSingleScan,
toggleSingleScan: () =>
_rescanViewModel.doSingleScan = !_rescanViewModel.doSingleScan,
)),
Observer(
builder: (_) => LoadingPrimaryButton(
isLoading:
_rescanViewModel.state == RescanWalletState.rescaning,
isLoading: _rescanViewModel.state == RescanWalletState.rescaning,
text: S.of(context).rescan,
onPressed: () async {
await _rescanViewModel.rescanCurrentWallet(
restoreHeight:
_blockchainHeightWidgetKey.currentState!.height);
if (_rescanViewModel.isSilentPaymentsScan) {
return _toggleSilentPaymentsScanning(context);
}
_rescanViewModel.rescanCurrentWallet(
restoreHeight: _blockchainHeightWidgetKey.currentState!.height);
Navigator.of(context).pop();
},
color: Theme.of(context).primaryColor,
@ -42,4 +55,32 @@ class RescanPage extends BasePage {
]),
);
}
Future<void> _toggleSilentPaymentsScanning(BuildContext context) async {
final height = _blockchainHeightWidgetKey.currentState!.height;
Navigator.of(context).pop();
final needsToSwitch =
await bitcoin!.getNodeIsElectrsSPEnabled(_rescanViewModel.wallet) == false;
if (needsToSwitch) {
return showPopUp<void>(
context: navigatorKey.currentState!.context,
builder: (BuildContext _dialogContext) => AlertWithTwoActions(
alertTitle: S.of(_dialogContext).change_current_node_title,
alertContent: S.of(_dialogContext).confirm_silent_payments_switch_node,
rightButtonText: S.of(_dialogContext).ok,
leftButtonText: S.of(_dialogContext).cancel,
actionRightButton: () async {
Navigator.of(_dialogContext).pop();
_rescanViewModel.rescanCurrentWallet(restoreHeight: height);
},
actionLeftButton: () => Navigator.of(_dialogContext).pop(),
));
}
_rescanViewModel.rescanCurrentWallet(restoreHeight: height);
}
}

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/core/execution_state.dart';
@ -420,6 +421,10 @@ class SendPage extends BasePage {
return;
}
if (sendViewModel.isElectrumWallet) {
bitcoin!.updateFeeRates(sendViewModel.wallet);
}
reaction((_) => sendViewModel.state, (ExecutionState state) {
if (dialogContext != null && dialogContext?.mounted == true) {
Navigator.of(dialogContext!).pop();

View file

@ -39,7 +39,9 @@ class ConnectionSyncPage extends BasePage {
),
if (dashboardViewModel.hasRescan) ...[
SettingsCellWithArrow(
title: S.current.rescan,
title: dashboardViewModel.hasSilentPayments
? S.current.silent_payments_scanning
: S.current.rescan,
handler: (context) => Navigator.of(context).pushNamed(Routes.rescan),
),
if (DeviceInfo.instance.isMobile && FeatureFlag.isBackgroundSyncEnabled) ...[

View file

@ -9,7 +9,6 @@ import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/view_model/settings/security_settings_view_model.dart';
@ -17,7 +16,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class SecurityBackupPage extends BasePage {
SecurityBackupPage(this._securitySettingsViewModel, this._authService);
SecurityBackupPage(this._securitySettingsViewModel, this._authService, [this._isHardwareWallet = false]);
final AuthService _authService;
@ -26,18 +25,21 @@ class SecurityBackupPage extends BasePage {
final SecuritySettingsViewModel _securitySettingsViewModel;
final bool _isHardwareWallet;
@override
Widget body(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: 10),
child: Column(mainAxisSize: MainAxisSize.min, children: [
if (!_isHardwareWallet)
SettingsCellWithArrow(
title: S.current.show_keys,
handler: (_) => _authService.authenticateAction(
context,
route: Routes.showKeys,
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
conditionToDetermineIfToUse2FA:
_securitySettingsViewModel.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
),
),
if (!SettingsStoreBase.walletPasswordDirectInput)
@ -46,8 +48,8 @@ class SecurityBackupPage extends BasePage {
handler: (_) => _authService.authenticateAction(
context,
route: Routes.backup,
conditionToDetermineIfToUse2FA:
_securitySettingsViewModel.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
),
),
SettingsCellWithArrow(

View file

@ -0,0 +1,50 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
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/silent_payments_settings_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class SilentPaymentsSettingsPage extends BasePage {
SilentPaymentsSettingsPage(this._silentPaymentsSettingsViewModel);
@override
String get title => S.current.silent_payments_settings;
final SilentPaymentsSettingsViewModel _silentPaymentsSettingsViewModel;
@override
Widget body(BuildContext context) {
return SingleChildScrollView(
child: Observer(builder: (_) {
return Container(
padding: EdgeInsets.only(top: 10),
child: Column(
children: [
SettingsSwitcherCell(
title: S.current.silent_payments_display_card,
value: _silentPaymentsSettingsViewModel.silentPaymentsCardDisplay,
onValueChange: (_, bool value) {
_silentPaymentsSettingsViewModel.setSilentPaymentsCardDisplay(value);
},
),
SettingsSwitcherCell(
title: S.current.silent_payments_always_scan,
value: _silentPaymentsSettingsViewModel.silentPaymentsAlwaysScan,
onValueChange: (_, bool value) {
_silentPaymentsSettingsViewModel.setSilentPaymentsAlwaysScan(value);
},
),
SettingsCellWithArrow(
title: S.current.silent_payments_scanning,
handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.rescan),
),
],
),
);
}),
);
}
}

View file

@ -57,6 +57,7 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
isSending: item.isSending,
isFrozen: item.isFrozen,
isChange: item.isChange,
isSilentPayment: item.isSilentPayment,
onCheckBoxTap: item.isFrozen
? null
: () async {

View file

@ -12,6 +12,7 @@ class UnspentCoinsListItem extends StatelessWidget {
required this.isSending,
required this.isFrozen,
required this.isChange,
required this.isSilentPayment,
this.onCheckBoxTap,
});
@ -21,18 +22,16 @@ class UnspentCoinsListItem extends StatelessWidget {
final bool isSending;
final bool isFrozen;
final bool isChange;
final bool isSilentPayment;
final Function()? onCheckBoxTap;
@override
Widget build(BuildContext context) {
final unselectedItemColor = Theme.of(context).cardColor;
final selectedItemColor = Theme.of(context).primaryColor;
final itemColor = isSending
? selectedItemColor
: unselectedItemColor;
final amountColor = isSending
? Colors.white
: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor;
final itemColor = isSending ? selectedItemColor : unselectedItemColor;
final amountColor =
isSending ? Colors.white : Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor;
final addressColor = isSending
? Colors.white.withOpacity(0.5)
: Theme.of(context).extension<CakeTextTheme>()!.buttonSecondaryTextColor;
@ -121,6 +120,23 @@ class UnspentCoinsListItem extends StatelessWidget {
),
),
),
if (isSilentPayment)
Container(
height: 17,
padding: EdgeInsets.only(left: 6, right: 6),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8.5)),
color: Colors.white),
alignment: Alignment.center,
child: Text(
S.of(context).silent_payments,
style: TextStyle(
color: itemColor,
fontSize: 7,
fontWeight: FontWeight.w600,
),
),
),
],
),
),

View file

@ -99,6 +99,7 @@ class WalletListBody extends StatefulWidget {
class WalletListBodyState extends State<WalletListBody> {
final moneroIcon = Image.asset('assets/images/monero_logo.png', height: 24, width: 24);
final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
final tBitcoinIcon = Image.asset('assets/images/tbtc.png', height: 24, width: 24);
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
@ -165,7 +166,10 @@ class WalletListBodyState extends State<WalletListBody> {
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
wallet.isEnabled
? _imageFor(type: wallet.type)
? _imageFor(
type: wallet.type,
isTestnet: wallet.isTestnet,
)
: nonWalletTypeIcon,
SizedBox(width: 10),
Flexible(
@ -300,9 +304,12 @@ class WalletListBodyState extends State<WalletListBody> {
);
}
Image _imageFor({required WalletType type}) {
Image _imageFor({required WalletType type, bool? isTestnet}) {
switch (type) {
case WalletType.bitcoin:
if (isTestnet == true) {
return tBitcoinIcon;
}
return bitcoinIcon;
case WalletType.monero:
return moneroIcon;

View file

@ -1,3 +1,5 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/src/widgets/standard_switch.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/utils/date_picker.dart';
import 'package:flutter/material.dart';
@ -12,13 +14,19 @@ class BlockchainHeightWidget extends StatefulWidget {
this.onHeightChange,
this.focusNode,
this.onHeightOrDateEntered,
this.hasDatePicker = true})
: super(key: key);
this.hasDatePicker = true,
this.isSilentPaymentsScan = false,
this.toggleSingleScan,
this.doSingleScan = false,
}) : super(key: key);
final Function(int)? onHeightChange;
final Function(bool)? onHeightOrDateEntered;
final FocusNode? focusNode;
final bool hasDatePicker;
final bool isSilentPaymentsScan;
final bool doSingleScan;
final Function()? toggleSingleScan;
@override
State<StatefulWidget> createState() => BlockchainHeightState();
@ -64,9 +72,10 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
child: BaseTextFormField(
focusNode: widget.focusNode,
controller: restoreHeightController,
keyboardType: TextInputType.numberWithOptions(
signed: false, decimal: false),
hintText: S.of(context).widgets_restore_from_blockheight,
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: false),
hintText: widget.isSilentPaymentsScan
? S.of(context).silent_payments_scan_from_height
: S.of(context).widgets_restore_from_blockheight,
)))
],
),
@ -78,8 +87,7 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.w500,
color:
Theme.of(context).extension<CakeTextTheme>()!.titleColor),
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
),
),
Row(
@ -91,22 +99,47 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
child: IgnorePointer(
child: BaseTextFormField(
controller: dateController,
hintText: S.of(context).widgets_restore_from_date,
hintText: widget.isSilentPaymentsScan
? S.of(context).silent_payments_scan_from_date
: S.of(context).widgets_restore_from_date,
)),
),
))
],
),
if (widget.isSilentPaymentsScan)
Padding(
padding: EdgeInsets.only(top: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
S.of(context).scan_one_block,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
),
),
Padding(
padding: const EdgeInsets.only(right: 8),
child: StandardSwitch(
value: widget.doSingleScan,
onTaped: () => widget.toggleSingleScan?.call(),
),
)
],
),
),
Padding(
padding: EdgeInsets.only(left: 40, right: 40, top: 24),
child: Text(
S.of(context).restore_from_date_or_blockheight,
widget.isSilentPaymentsScan
? S.of(context).silent_payments_scan_from_date_or_blockheight
: S.of(context).restore_from_date_or_blockheight,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.normal,
color: Theme.of(context).hintColor
),
fontSize: 12, fontWeight: FontWeight.normal, color: Theme.of(context).hintColor),
),
)
]
@ -123,7 +156,12 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
lastDate: now);
if (date != null) {
final height = monero!.getHeightByDate(date: date);
int height;
if (widget.isSilentPaymentsScan) {
height = bitcoin!.getHeightByDate(date: date);
} else {
height = monero!.getHeightByDate(date: date);
}
setState(() {
dateController.text = DateFormat('yyyy-MM-dd').format(date);
restoreHeightController.text = '$height';

View file

@ -2,19 +2,28 @@ import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
import 'package:flutter_svg/flutter_svg.dart';
class DashBoardRoundedCardWidget extends StatelessWidget {
DashBoardRoundedCardWidget({
required this.onTap,
required this.title,
required this.subTitle,
this.hint,
this.svgPicture,
this.icon,
this.onClose,
this.customBorder,
});
final VoidCallback onTap;
final VoidCallback? onClose;
final String title;
final String subTitle;
final Widget? hint;
final SvgPicture? svgPicture;
final Icon? icon;
final double? customBorder;
@override
Widget build(BuildContext context) {
@ -26,34 +35,56 @@ class DashBoardRoundedCardWidget extends StatelessWidget {
child: Stack(
children: [
Container(
padding: EdgeInsets.fromLTRB(20, 20, 40, 20),
padding: EdgeInsets.all(20),
width: double.infinity,
decoration: BoxDecoration(
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(customBorder ?? 20),
border: Border.all(
color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
),
),
child: Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
color:
Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
fontSize: 24,
fontWeight: FontWeight.w900,
),
softWrap: true,
),
SizedBox(height: 5),
Text(
subTitle,
style: TextStyle(
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
color: Theme.of(context)
.extension<DashboardPageTheme>()!
.cardTextColor,
fontWeight: FontWeight.w500,
fontFamily: 'Lato'),
)
softWrap: true,
),
],
),
),
if (svgPicture != null) svgPicture!,
if (icon != null) icon!
],
),
if (hint != null) ...[
SizedBox(height: 10),
hint!,
]
],
),
),

View file

@ -17,6 +17,7 @@ class SettingActions {
connectionSettingAction,
walletSettingAction,
addressBookSettingAction,
silentPaymentsSettingAction,
securityBackupSettingAction,
privacySettingAction,
displaySettingAction,
@ -35,6 +36,15 @@ class SettingActions {
supportSettingAction,
];
static SettingActions silentPaymentsSettingAction = SettingActions._(
name: (context) => S.of(context).silent_payments_settings,
image: 'assets/images/bitcoin_menu.png',
onTap: (BuildContext context) {
Navigator.pop(context);
Navigator.of(context).pushNamed(Routes.silentPaymentsSettings);
},
);
static SettingActions connectionSettingAction = SettingActions._(
name: (context) => S.of(context).connection_sync,
image: 'assets/images/nodes_menu.png',

View file

@ -1,5 +1,4 @@
import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/core/secure_storage.dart';
@ -108,6 +107,8 @@ abstract class SettingsStoreBase with Store {
required this.lookupsOpenAlias,
required this.lookupsENS,
required this.customBitcoinFeeRate,
required this.silentPaymentsCardDisplay,
required this.silentPaymentsAlwaysScan,
TransactionPriority? initialBitcoinTransactionPriority,
TransactionPriority? initialMoneroTransactionPriority,
TransactionPriority? initialHavenTransactionPriority,
@ -518,6 +519,16 @@ abstract class SettingsStoreBase with Store {
(int customBitcoinFeeRate) =>
_sharedPreferences.setInt(PreferencesKey.customBitcoinFeeRate, customBitcoinFeeRate));
reaction((_) => silentPaymentsCardDisplay, (bool silentPaymentsCardDisplay) {
_sharedPreferences.setBool(
PreferencesKey.silentPaymentsCardDisplay, silentPaymentsCardDisplay);
});
reaction(
(_) => silentPaymentsAlwaysScan,
(bool silentPaymentsAlwaysScan) => _sharedPreferences.setBool(
PreferencesKey.silentPaymentsAlwaysScan, silentPaymentsAlwaysScan));
this.nodes.observe((change) {
if (change.newValue != null && change.key != null) {
_saveCurrentNode(change.newValue!, change.key!);
@ -714,6 +725,12 @@ abstract class SettingsStoreBase with Store {
@observable
int customBitcoinFeeRate;
@observable
bool silentPaymentsCardDisplay;
@observable
bool silentPaymentsAlwaysScan;
final SecureStorage _secureStorage;
final SharedPreferences _sharedPreferences;
final BackgroundTasks _backgroundTasks;
@ -860,6 +877,10 @@ abstract class SettingsStoreBase with Store {
final lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
final lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
final customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1;
final silentPaymentsCardDisplay =
sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true;
final silentPaymentsAlwaysScan =
sharedPreferences.getBool(PreferencesKey.silentPaymentsAlwaysScan) ?? false;
// If no value
if (pinLength == null || pinLength == 0) {
@ -1104,6 +1125,8 @@ abstract class SettingsStoreBase with Store {
lookupsOpenAlias: lookupsOpenAlias,
lookupsENS: lookupsENS,
customBitcoinFeeRate: customBitcoinFeeRate,
silentPaymentsCardDisplay: silentPaymentsCardDisplay,
silentPaymentsAlwaysScan: silentPaymentsAlwaysScan,
initialMoneroTransactionPriority: moneroTransactionPriority,
initialBitcoinTransactionPriority: bitcoinTransactionPriority,
initialHavenTransactionPriority: havenTransactionPriority,
@ -1243,6 +1266,10 @@ abstract class SettingsStoreBase with Store {
lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1;
silentPaymentsCardDisplay =
sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true;
silentPaymentsAlwaysScan =
sharedPreferences.getBool(PreferencesKey.silentPaymentsAlwaysScan) ?? false;
final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final bitcoinElectrumServerId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);

View file

@ -6,7 +6,7 @@ part 'responsive_layout_util.g.dart';
class _ResponsiveLayoutUtil = ResponsiveLayoutUtilBase with _$_ResponsiveLayoutUtil;
abstract class ResponsiveLayoutUtilBase with Store, WidgetsBindingObserver {
static const double _kMobileThreshold = 550;
static const double kMobileThreshold = 550;
static const double kDesktopMaxWidthConstraint = 400;
static const double kDesktopMaxDashBoardWidthConstraint = 900;
static const double kPopupWidth = 400;
@ -42,13 +42,13 @@ abstract class ResponsiveLayoutUtilBase with Store, WidgetsBindingObserver {
@computed
bool get shouldRenderMobileUI {
return (screenWidth <= _kMobileThreshold) ||
return (screenWidth <= kMobileThreshold) ||
(orientation == Orientation.portrait && screenWidth < screenHeight) ||
(orientation == Orientation.landscape && screenWidth < screenHeight);
}
bool get shouldRenderTabletUI {
return screenWidth > _kMobileThreshold && screenWidth < kDesktopMaxDashBoardWidthConstraint;
return screenWidth > kMobileThreshold && screenWidth < kDesktopMaxDashBoardWidthConstraint;
}
}

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/contact_base.dart';
import 'package:cake_wallet/entities/wallet_contact.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
@ -40,16 +41,19 @@ abstract class ContactListViewModelBase with Store {
});
} else if (info.addresses?.isNotEmpty == true) {
info.addresses!.forEach((address, label) {
if (label.isEmpty) {
return;
}
final name = _createName(info.name, label);
walletContacts.add(WalletContact(
address,
name,
walletTypeToCryptoCurrency(info.type),
walletTypeToCryptoCurrency(info.type,
isTestnet:
info.network == null ? false : info.network!.toLowerCase().contains("testnet")),
));
// Only one contact address per wallet
return;
});
} else if (info.address != null) {
} else {
walletContacts.add(WalletContact(
info.address,
info.name,
@ -64,7 +68,9 @@ abstract class ContactListViewModelBase with Store {
}
String _createName(String walletName, String label) {
return label.isNotEmpty ? '$walletName ($label)' : walletName;
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;
}
final bool isAutoGenerateEnabled;

View file

@ -60,6 +60,9 @@ abstract class BalanceViewModelBase with Store {
@observable
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
@computed
bool get hasSilentPayments => wallet.type == WalletType.bitcoin;
@computed
double get price {
final price = fiatConvertationStore.prices[appStore.wallet!.currency];

View file

@ -0,0 +1,16 @@
import 'package:cake_wallet/ionia/ionia_service.dart';
import 'package:mobx/mobx.dart';
part 'cake_features_view_model.g.dart';
class CakeFeaturesViewModel = CakeFeaturesViewModelBase with _$CakeFeaturesViewModel;
abstract class CakeFeaturesViewModelBase with Store {
final IoniaService _ioniaService;
CakeFeaturesViewModelBase(this._ioniaService);
Future<bool> isIoniaUserAuthenticated() async {
return await _ioniaService.isLogined();
}
}

View file

@ -45,6 +45,7 @@ import 'package:cw_core/wallet_type.dart';
import 'package:eth_sig_util/util/utils.dart';
import 'package:flutter/services.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
@ -201,6 +202,14 @@ abstract class DashboardViewModelBase with Store {
return true;
});
if (hasSilentPayments) {
silentPaymentsScanningActive = bitcoin!.getScanningActive(wallet);
reaction((_) => wallet.syncStatus, (SyncStatus syncStatus) {
silentPaymentsScanningActive = bitcoin!.getScanningActive(wallet);
});
}
}
@observable
@ -287,11 +296,36 @@ abstract class DashboardViewModelBase with Store {
@observable
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
bool get hasRescan => wallet.type == WalletType.monero || wallet.type == WalletType.haven;
@computed
bool get isTestnet => wallet.type == WalletType.bitcoin && bitcoin!.isTestnet(wallet);
@computed
bool get hasRescan =>
wallet.type == WalletType.bitcoin ||
wallet.type == WalletType.monero ||
wallet.type == WalletType.haven;
@computed
bool get hasSilentPayments => wallet.type == WalletType.bitcoin;
@computed
bool get showSilentPaymentsCard => hasSilentPayments && settingsStore.silentPaymentsCardDisplay;
final KeyService keyService;
final SharedPreferences sharedPreferences;
@observable
bool silentPaymentsScanningActive = false;
@action
void setSilentPaymentsScanning(bool active) {
silentPaymentsScanningActive = active;
if (hasSilentPayments) {
bitcoin!.setScanningActive(wallet, active);
}
}
BalanceViewModel balanceViewModel;
AppStore appStore;
@ -535,6 +569,7 @@ abstract class DashboardViewModelBase with Store {
Future<ServicesResponse> getServicesStatus() async {
try {
if (isEnabledBulletinAction) {
final res = await http.get(Uri.parse("https://service-api.cakewallet.com/v1/active-notices"));
if (res.statusCode < 200 || res.statusCode >= 300) {
@ -553,6 +588,10 @@ abstract class DashboardViewModelBase with Store {
hasUpdates,
currentSha,
);
}
else {
return ServicesResponse([], false, '');
}
} catch (_) {
return ServicesResponse([], false, '');
}

View file

@ -1,17 +0,0 @@
import 'package:cake_wallet/ionia/ionia_service.dart';
import 'package:mobx/mobx.dart';
part 'market_place_view_model.g.dart';
class MarketPlaceViewModel = MarketPlaceViewModelBase with _$MarketPlaceViewModel;
abstract class MarketPlaceViewModelBase with Store {
final IoniaService _ioniaService;
MarketPlaceViewModelBase(this._ioniaService);
Future<bool> isIoniaUserAuthenticated() async {
return await _ioniaService.isLogined();
}
}

View file

@ -154,11 +154,11 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
ChangeNowExchangeProvider(settingsStore: _settingsStore),
SideShiftExchangeProvider(),
SimpleSwapExchangeProvider(),
TrocadorExchangeProvider(
useTorOnly: _useTorOnly, providerStates: _settingsStore.trocadorProviderStates),
ThorChainExchangeProvider(tradesStore: trades),
if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(),
QuantexExchangeProvider(),
TrocadorExchangeProvider(
useTorOnly: _useTorOnly, providerStates: _settingsStore.trocadorProviderStates),
];
@observable

View file

@ -1,4 +1,6 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
part 'rescan_view_model.g.dart';
@ -8,11 +10,12 @@ class RescanViewModel = RescanViewModelBase with _$RescanViewModel;
enum RescanWalletState { rescaning, none }
abstract class RescanViewModelBase with Store {
RescanViewModelBase(this._wallet)
RescanViewModelBase(this.wallet)
: state = RescanWalletState.none,
isButtonEnabled = false;
isButtonEnabled = false,
doSingleScan = false;
final WalletBase _wallet;
final WalletBase wallet;
@observable
RescanWalletState state;
@ -20,11 +23,21 @@ abstract class RescanViewModelBase with Store {
@observable
bool isButtonEnabled;
@observable
bool doSingleScan;
@computed
bool get isSilentPaymentsScan => wallet.type == WalletType.bitcoin;
@action
Future<void> rescanCurrentWallet({required int restoreHeight}) async {
state = RescanWalletState.rescaning;
await _wallet.rescan(height: restoreHeight);
_wallet.transactionHistory.clear();
if (wallet.type != WalletType.bitcoin) {
wallet.rescan(height: restoreHeight);
wallet.transactionHistory.clear();
} else {
bitcoin!.rescan(wallet, height: restoreHeight, doSingleScan: doSingleScan);
}
state = RescanWalletState.none;
}
}

View file

@ -139,8 +139,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
String get pendingTransactionFeeFiatAmount {
try {
if (pendingTransaction != null) {
final currency =
isEVMCompatibleChain(walletType) ? wallet.currency : selectedCryptoCurrency;
final currency = pendingTransactionFeeCurrency(walletType);
final fiat = calculateFiatAmount(
price: _fiatConversationStore.prices[currency]!,
cryptoAmount: pendingTransaction!.feeFormatted);
@ -153,6 +152,18 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
}
}
CryptoCurrency pendingTransactionFeeCurrency(WalletType type) {
switch (type) {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.tron:
case WalletType.solana:
return wallet.currency;
default:
return selectedCryptoCurrency;
}
}
FiatCurrency get fiat => _settingsStore.fiatCurrency;
TransactionPriority get transactionPriority {
@ -218,7 +229,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
isFiatDisabled ? '' : pendingTransactionFeeFiatAmount + ' ' + fiat.title;
@computed
bool get isReadyForSend => wallet.syncStatus is SyncedSyncStatus;
bool get isReadyForSend =>
wallet.syncStatus is SyncedSyncStatus ||
// If silent payments scanning, can still send payments
(wallet.type == WalletType.bitcoin && wallet.syncStatus is SyncingSyncStatus);
@computed
List<Template> get templates => sendTemplateViewModel.templates
@ -588,6 +602,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
walletType == WalletType.litecoin ||
walletType == WalletType.bitcoinCash) {
if (error is TransactionWrongBalanceException) {
if (error.amount != null)
return S.current
.tx_wrong_balance_with_amount_exception(currency.toString(), error.amount.toString());
return S.current.tx_wrong_balance_exception(currency.toString());
}
if (error is TransactionNoInputsException) {
@ -614,9 +632,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
if (error is TransactionCommitFailedVoutNegative) {
return S.current.tx_rejected_vout_negative;
}
if (error is TransactionCommitFailedBIP68Final) {
return S.current.tx_rejected_bip68_final;
}
if (error is TransactionNoDustOnChangeException) {
return S.current.tx_commit_exception_no_dust_on_change(error.min, error.max);
}
if (error is TransactionInputNotSupported) {
return S.current.tx_invalid_input;
}
}
return errorMessage;

View file

@ -0,0 +1,33 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:mobx/mobx.dart';
part 'silent_payments_settings_view_model.g.dart';
class SilentPaymentsSettingsViewModel = SilentPaymentsSettingsViewModelBase
with _$SilentPaymentsSettingsViewModel;
abstract class SilentPaymentsSettingsViewModelBase with Store {
SilentPaymentsSettingsViewModelBase(this._settingsStore, this._wallet);
final SettingsStore _settingsStore;
final WalletBase _wallet;
@computed
bool get silentPaymentsCardDisplay => _settingsStore.silentPaymentsCardDisplay;
@computed
bool get silentPaymentsAlwaysScan => _settingsStore.silentPaymentsAlwaysScan;
@action
void setSilentPaymentsCardDisplay(bool value) {
_settingsStore.silentPaymentsCardDisplay = value;
}
@action
void setSilentPaymentsAlwaysScan(bool value) {
_settingsStore.silentPaymentsAlwaysScan = value;
if (value) bitcoin!.setScanningActive(_wallet, true);
}
}

View file

@ -15,7 +15,8 @@ abstract class UnspentCoinsItemBase with Store {
required this.isChange,
required this.amountRaw,
required this.vout,
required this.keyImage
required this.keyImage,
required this.isSilentPayment,
});
@observable
@ -47,4 +48,7 @@ abstract class UnspentCoinsItemBase with Store {
@observable
String? keyImage;
@observable
bool isSilentPayment;
}

View file

@ -1,10 +1,12 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/unspent_transaction_output.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/cupertino.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
@ -86,11 +88,14 @@ abstract class UnspentCoinsListViewModelBase with Store {
@action
void _updateUnspentCoinsInfo() {
_items.clear();
_items.addAll(_getUnspents().map((elem) {
List<UnspentCoinsItem> unspents = [];
_getUnspents().forEach((elem) {
try {
final info =
getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
return UnspentCoinsItem(
unspents.add(UnspentCoinsItem(
address: elem.address,
amount: '${formatAmountToString(elem.value)} ${wallet.currency.title}',
hash: elem.hash,
@ -101,7 +106,15 @@ abstract class UnspentCoinsListViewModelBase with Store {
vout: elem.vout,
keyImage: elem.keyImage,
isChange: elem.isChange,
);
}));
isSilentPayment: info.isSilentPayment ?? false,
));
} catch (e, s) {
print(s);
print(e.toString());
ExceptionHandler.onError(FlutterErrorDetails(exception: e, stack: s));
}
});
_items.addAll(unspents);
}
}

View file

@ -1,3 +1,6 @@
import 'package:cake_wallet/utils/list_item.dart';
class WalletAddressListHeader extends ListItem {}
class WalletAddressListHeader extends ListItem {
final String? title;
WalletAddressListHeader({this.title});
}

View file

@ -8,8 +8,10 @@ class WalletAddressListItem extends ListItem {
this.name,
this.txCount,
this.balance,
this.isChange = false})
: super();
this.isChange = false,
// Address that is only ever used once, shouldn't be used to receive funds, copy and paste, share etc
this.isOneTimeReceiveAddress = false,
}) : super();
final int? id;
final bool isPrimary;
@ -18,6 +20,7 @@ class WalletAddressListItem extends ListItem {
final int? txCount;
final String? balance;
final bool isChange;
final bool? isOneTimeReceiveAddress;
@override
String toString() => name ?? address;

View file

@ -334,6 +334,40 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
}
if (isElectrumWallet) {
if (bitcoin!.hasSelectedSilentPayments(wallet)) {
final addressItems = bitcoin!.getSilentPaymentAddresses(wallet).map((address) {
final isPrimary = address.id == 0;
return WalletAddressListItem(
id: address.id,
isPrimary: isPrimary,
name: address.name,
address: address.address,
txCount: address.txCount,
balance: AmountConverter.amountIntToString(
walletTypeToCryptoCurrency(type), address.balance),
isChange: address.isChange,
);
});
addressList.addAll(addressItems);
addressList.add(WalletAddressListHeader(title: S.current.received));
final receivedAddressItems =
bitcoin!.getSilentPaymentReceivedAddresses(wallet).map((address) {
return WalletAddressListItem(
id: address.id,
isPrimary: false,
name: address.name,
address: address.address,
txCount: address.txCount,
balance: AmountConverter.amountIntToString(
walletTypeToCryptoCurrency(type), address.balance),
isChange: address.isChange,
isOneTimeReceiveAddress: true,
);
});
addressList.addAll(receivedAddressItems);
} else {
final addressItems = bitcoin!.getSubAddresses(wallet).map((subaddress) {
final isPrimary = subaddress.id == 0;
@ -349,6 +383,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
});
addressList.addAll(addressItems);
}
}
if (wallet.type == WalletType.ethereum) {
final primaryAddress = ethereum!.getAddress(wallet);
@ -416,9 +451,14 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
wallet.type == WalletType.litecoin ||
wallet.type == WalletType.bitcoinCash;
@computed
bool get isSilentPayments =>
wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet);
@computed
bool get isAutoGenerateSubaddressEnabled =>
_settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled;
_settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled &&
!isSilentPayments;
List<ListItem> _baseItems;
@ -478,4 +518,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
amount = '';
}
}
@action
void deleteAddress(ListItem item) {
if (wallet.type == WalletType.bitcoin && item is WalletAddressListItem) {
bitcoin!.deleteSilentPaymentAddress(wallet, item.address);
}
}
}

View file

@ -7,6 +7,7 @@ class WalletListItem {
required this.key,
this.isCurrent = false,
this.isEnabled = true,
this.isTestnet = false,
});
final String name;
@ -14,4 +15,5 @@ class WalletListItem {
final bool isCurrent;
final dynamic key;
final bool isEnabled;
final bool isTestnet;
}

View file

@ -61,6 +61,7 @@ abstract class WalletListViewModelBase with Store {
key: info.key,
isCurrent: info.name == _appStore.wallet?.name && info.type == _appStore.wallet?.type,
isEnabled: availableWalletTypes.contains(info.type),
isTestnet: info.network?.toLowerCase().contains('testnet') ?? false,
),
),
);

View file

@ -47,6 +47,8 @@ PODS:
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sp_scanner (0.0.1):
- FlutterMacOS
- url_launcher_macos (0.0.1):
- FlutterMacOS
- wakelock_plus (0.0.1):
@ -67,6 +69,7 @@ DEPENDENCIES:
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- share_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- sp_scanner (from `Flutter/ephemeral/.symlinks/plugins/sp_scanner/macos`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
@ -104,6 +107,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
sp_scanner:
:path: Flutter/ephemeral/.symlinks/plugins/sp_scanner/macos
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
wakelock_plus:
@ -126,7 +131,8 @@ SPEC CHECKSUMS:
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
share_plus_macos: 853ee48e7dce06b633998ca0735d482dd671ade4
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
sp_scanner: 269d96e0ec3173e69156be7239b95182be3b8303
url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399
wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269
PODFILE CHECKSUM: 65ec1541137fb5b35d00490dec1bb48d4d9586bb

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