mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-01-03 17:40:43 +00:00
Merge branch 'main' into CW-621-Open-external-sites-in-default-browser
This commit is contained in:
commit
6b1dd1f780
133 changed files with 4195 additions and 1449 deletions
2
.github/workflows/pr_test_build.yml
vendored
2
.github/workflows/pr_test_build.yml
vendored
|
@ -42,7 +42,7 @@ jobs:
|
||||||
- name: Flutter action
|
- name: Flutter action
|
||||||
uses: subosito/flutter-action@v1
|
uses: subosito/flutter-action@v1
|
||||||
with:
|
with:
|
||||||
flutter-version: "3.19.5"
|
flutter-version: "3.19.6"
|
||||||
channel: stable
|
channel: stable
|
||||||
|
|
||||||
- name: Install package dependencies
|
- name: Install package dependencies
|
||||||
|
|
65
assets/images/cards.svg
Normal file
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
BIN
assets/images/tbtc.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
|
@ -1,3 +1,2 @@
|
||||||
Add Tron wallet
|
Bitcoin Silent Payments
|
||||||
Hardware wallets enhancements
|
Bug fixes and generic enhancements
|
||||||
Bug fixes
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ import 'dart:convert';
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:cw_bitcoin/script_hash.dart' as sh;
|
import 'package:cw_bitcoin/script_hash.dart' as sh;
|
||||||
|
|
||||||
class BitcoinAddressRecord {
|
abstract class BaseBitcoinAddressRecord {
|
||||||
BitcoinAddressRecord(
|
BaseBitcoinAddressRecord(
|
||||||
this.address, {
|
this.address, {
|
||||||
required this.index,
|
required this.index,
|
||||||
this.isHidden = false,
|
this.isHidden = false,
|
||||||
|
@ -13,15 +13,62 @@ class BitcoinAddressRecord {
|
||||||
String name = '',
|
String name = '',
|
||||||
bool isUsed = false,
|
bool isUsed = false,
|
||||||
required this.type,
|
required this.type,
|
||||||
String? scriptHash,
|
|
||||||
required this.network,
|
required this.network,
|
||||||
}) : _txCount = txCount,
|
}) : _txCount = txCount,
|
||||||
_balance = balance,
|
_balance = balance,
|
||||||
_name = name,
|
_name = name,
|
||||||
_isUsed = isUsed,
|
_isUsed = isUsed;
|
||||||
scriptHash = scriptHash ?? sh.scriptHash(address, network: network);
|
|
||||||
|
|
||||||
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;
|
final decoded = json.decode(jsonSource) as Map;
|
||||||
|
|
||||||
return BitcoinAddressRecord(
|
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;
|
String? scriptHash;
|
||||||
BasedUtxoNetwork network;
|
|
||||||
|
|
||||||
int get txCount => _txCount;
|
String getScriptHash(BasedUtxoNetwork network) {
|
||||||
|
if (scriptHash != null) return scriptHash!;
|
||||||
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) {
|
|
||||||
scriptHash = sh.scriptHash(address, network: network);
|
scriptHash = sh.scriptHash(address, network: network);
|
||||||
return scriptHash!;
|
return scriptHash!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
String toJSON() => json.encode({
|
String toJSON() => json.encode({
|
||||||
'address': address,
|
'address': address,
|
||||||
'index': index,
|
'index': index,
|
||||||
|
@ -91,3 +109,57 @@ class BitcoinAddressRecord {
|
||||||
'scriptHash': scriptHash,
|
'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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@ class BitcoinReceivePageOption implements ReceivePageOption {
|
||||||
static const p2wsh = BitcoinReceivePageOption._('Segwit (P2WSH)');
|
static const p2wsh = BitcoinReceivePageOption._('Segwit (P2WSH)');
|
||||||
static const p2pkh = BitcoinReceivePageOption._('Legacy (P2PKH)');
|
static const p2pkh = BitcoinReceivePageOption._('Legacy (P2PKH)');
|
||||||
|
|
||||||
|
static const silent_payments = BitcoinReceivePageOption._('Silent Payments');
|
||||||
|
|
||||||
const BitcoinReceivePageOption._(this.value);
|
const BitcoinReceivePageOption._(this.value);
|
||||||
|
|
||||||
final String value;
|
final String value;
|
||||||
|
@ -17,6 +19,7 @@ class BitcoinReceivePageOption implements ReceivePageOption {
|
||||||
}
|
}
|
||||||
|
|
||||||
static const all = [
|
static const all = [
|
||||||
|
BitcoinReceivePageOption.silent_payments,
|
||||||
BitcoinReceivePageOption.p2wpkh,
|
BitcoinReceivePageOption.p2wpkh,
|
||||||
BitcoinReceivePageOption.p2tr,
|
BitcoinReceivePageOption.p2tr,
|
||||||
BitcoinReceivePageOption.p2wsh,
|
BitcoinReceivePageOption.p2wsh,
|
||||||
|
@ -24,6 +27,24 @@ class BitcoinReceivePageOption implements ReceivePageOption {
|
||||||
BitcoinReceivePageOption.p2pkh
|
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) {
|
factory BitcoinReceivePageOption.fromType(BitcoinAddressType type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case SegwitAddresType.p2tr:
|
case SegwitAddresType.p2tr:
|
||||||
|
@ -34,6 +55,8 @@ class BitcoinReceivePageOption implements ReceivePageOption {
|
||||||
return BitcoinReceivePageOption.p2pkh;
|
return BitcoinReceivePageOption.p2pkh;
|
||||||
case P2shAddressType.p2wpkhInP2sh:
|
case P2shAddressType.p2wpkhInP2sh:
|
||||||
return BitcoinReceivePageOption.p2sh;
|
return BitcoinReceivePageOption.p2sh;
|
||||||
|
case SilentPaymentsAddresType.p2sp:
|
||||||
|
return BitcoinReceivePageOption.silent_payments;
|
||||||
case SegwitAddresType.p2wpkh:
|
case SegwitAddresType.p2wpkh:
|
||||||
default:
|
default:
|
||||||
return BitcoinReceivePageOption.p2wpkh;
|
return BitcoinReceivePageOption.p2wpkh;
|
||||||
|
|
|
@ -2,13 +2,66 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_core/unspent_transaction_output.dart';
|
import 'package:cw_core/unspent_transaction_output.dart';
|
||||||
|
|
||||||
class BitcoinUnspent extends Unspent {
|
class BitcoinUnspent extends Unspent {
|
||||||
BitcoinUnspent(BitcoinAddressRecord addressRecord, String hash, int value, int vout)
|
BitcoinUnspent(BaseBitcoinAddressRecord addressRecord, String hash, int value, int vout)
|
||||||
: bitcoinAddressRecord = addressRecord,
|
: bitcoinAddressRecord = addressRecord,
|
||||||
super(addressRecord.address, hash, value, vout, null);
|
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(
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,22 +39,28 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
Map<String, int>? initialRegularAddressIndex,
|
Map<String, int>? initialRegularAddressIndex,
|
||||||
Map<String, int>? initialChangeAddressIndex,
|
Map<String, int>? initialChangeAddressIndex,
|
||||||
String? passphrase,
|
String? passphrase,
|
||||||
|
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
||||||
|
int initialSilentAddressIndex = 0,
|
||||||
|
bool? alwaysScan,
|
||||||
}) : super(
|
}) : super(
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
passphrase: passphrase,
|
passphrase: passphrase,
|
||||||
xpub: xpub,
|
xpub: xpub,
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
networkType: networkParam == null
|
networkType: networkParam == null
|
||||||
? bitcoin.bitcoin
|
|
||||||
: networkParam == BitcoinNetwork.mainnet
|
|
||||||
? bitcoin.bitcoin
|
? bitcoin.bitcoin
|
||||||
: bitcoin.testnet,
|
: networkParam == BitcoinNetwork.mainnet
|
||||||
initialAddresses: initialAddresses,
|
? bitcoin.bitcoin
|
||||||
initialBalance: initialBalance,
|
: bitcoin.testnet,
|
||||||
seedBytes: seedBytes,
|
initialAddresses: initialAddresses,
|
||||||
currency: CryptoCurrency.btc) {
|
initialBalance: initialBalance,
|
||||||
|
seedBytes: seedBytes,
|
||||||
|
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)
|
// 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)
|
// the sideHd derivation path = m/84'/0'/0'/1 (account 1, index unspecified here)
|
||||||
// String derivationPath = walletInfo.derivationInfo!.derivationPath!;
|
// String derivationPath = walletInfo.derivationInfo!.derivationPath!;
|
||||||
|
@ -62,14 +68,18 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
// final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType);
|
// final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType);
|
||||||
walletAddresses = BitcoinWalletAddresses(
|
walletAddresses = BitcoinWalletAddresses(
|
||||||
walletInfo,
|
walletInfo,
|
||||||
electrumClient: electrumClient,
|
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
|
initialSilentAddresses: initialSilentAddresses,
|
||||||
|
initialSilentAddressIndex: initialSilentAddressIndex,
|
||||||
mainHd: hd,
|
mainHd: hd,
|
||||||
sideHd: accountHD.derive(1),
|
sideHd: accountHD.derive(1),
|
||||||
network: networkParam ?? network,
|
network: networkParam ?? network,
|
||||||
|
masterHd:
|
||||||
|
seedBytes != null ? bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) : null,
|
||||||
);
|
);
|
||||||
|
|
||||||
autorun((_) {
|
autorun((_) {
|
||||||
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
||||||
});
|
});
|
||||||
|
@ -84,9 +94,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
String? addressPageType,
|
String? addressPageType,
|
||||||
BasedUtxoNetwork? network,
|
BasedUtxoNetwork? network,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
List<BitcoinAddressRecord>? initialAddresses,
|
||||||
|
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
||||||
ElectrumBalance? initialBalance,
|
ElectrumBalance? initialBalance,
|
||||||
Map<String, int>? initialRegularAddressIndex,
|
Map<String, int>? initialRegularAddressIndex,
|
||||||
Map<String, int>? initialChangeAddressIndex,
|
Map<String, int>? initialChangeAddressIndex,
|
||||||
|
int initialSilentAddressIndex = 0,
|
||||||
}) async {
|
}) async {
|
||||||
late Uint8List seedBytes;
|
late Uint8List seedBytes;
|
||||||
|
|
||||||
|
@ -109,6 +121,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
|
initialSilentAddresses: initialSilentAddresses,
|
||||||
|
initialSilentAddressIndex: initialSilentAddressIndex,
|
||||||
initialBalance: initialBalance,
|
initialBalance: initialBalance,
|
||||||
seedBytes: seedBytes,
|
seedBytes: seedBytes,
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||||
|
@ -123,6 +137,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
required String password,
|
required String password,
|
||||||
|
required bool alwaysScan,
|
||||||
}) async {
|
}) async {
|
||||||
final network = walletInfo.network != null
|
final network = walletInfo.network != null
|
||||||
? BasedUtxoNetwork.fromName(walletInfo.network!)
|
? BasedUtxoNetwork.fromName(walletInfo.network!)
|
||||||
|
@ -163,12 +178,15 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
initialAddresses: snp.addresses,
|
initialAddresses: snp.addresses,
|
||||||
|
initialSilentAddresses: snp.silentAddresses,
|
||||||
|
initialSilentAddressIndex: snp.silentAddressIndex,
|
||||||
initialBalance: snp.balance,
|
initialBalance: snp.balance,
|
||||||
seedBytes: seedBytes,
|
seedBytes: seedBytes,
|
||||||
initialRegularAddressIndex: snp.regularAddressIndex,
|
initialRegularAddressIndex: snp.regularAddressIndex,
|
||||||
initialChangeAddressIndex: snp.changeAddressIndex,
|
initialChangeAddressIndex: snp.changeAddressIndex,
|
||||||
addressPageType: snp.addressPageType,
|
addressPageType: snp.addressPageType,
|
||||||
networkParam: network,
|
networkParam: network,
|
||||||
|
alwaysScan: alwaysScan,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,7 +197,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) {
|
void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) {
|
||||||
_ledger = setLedger;
|
_ledger = setLedger;
|
||||||
_ledgerDevice = setLedgerDevice;
|
_ledgerDevice = setLedgerDevice;
|
||||||
_bitcoinLedgerApp = BitcoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!);
|
_bitcoinLedgerApp =
|
||||||
|
BitcoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -202,16 +221,17 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
|
final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
|
||||||
|
|
||||||
psbtReadyInputs.add(PSBTReadyUtxoWithAddress(
|
psbtReadyInputs.add(PSBTReadyUtxoWithAddress(
|
||||||
utxo: utxo.utxo,
|
utxo: utxo.utxo,
|
||||||
rawTx: rawTx,
|
rawTx: rawTx,
|
||||||
ownerDetails: utxo.ownerDetails,
|
ownerDetails: utxo.ownerDetails,
|
||||||
ownerDerivationPath: publicKeyAndDerivationPath.derivationPath,
|
ownerDerivationPath: publicKeyAndDerivationPath.derivationPath,
|
||||||
ownerMasterFingerprint: masterFingerprint,
|
ownerMasterFingerprint: masterFingerprint,
|
||||||
ownerPublicKey: publicKeyAndDerivationPath.publicKey,
|
ownerPublicKey: publicKeyAndDerivationPath.publicKey,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt);
|
||||||
return BtcTransaction.fromRaw(hex.encode(rawHex));
|
return BtcTransaction.fromRaw(hex.encode(rawHex));
|
||||||
|
|
|
@ -15,10 +15,12 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
required super.mainHd,
|
required super.mainHd,
|
||||||
required super.sideHd,
|
required super.sideHd,
|
||||||
required super.network,
|
required super.network,
|
||||||
required super.electrumClient,
|
|
||||||
super.initialAddresses,
|
super.initialAddresses,
|
||||||
super.initialRegularAddressIndex,
|
super.initialRegularAddressIndex,
|
||||||
super.initialChangeAddressIndex,
|
super.initialChangeAddressIndex,
|
||||||
|
super.initialSilentAddresses,
|
||||||
|
super.initialSilentAddressIndex = 0,
|
||||||
|
super.masterHd,
|
||||||
}) : super(walletInfo);
|
}) : super(walletInfo);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -19,10 +19,11 @@ class BitcoinWalletService extends WalletService<
|
||||||
BitcoinRestoreWalletFromSeedCredentials,
|
BitcoinRestoreWalletFromSeedCredentials,
|
||||||
BitcoinRestoreWalletFromWIFCredentials,
|
BitcoinRestoreWalletFromWIFCredentials,
|
||||||
BitcoinRestoreWalletFromHardware> {
|
BitcoinRestoreWalletFromHardware> {
|
||||||
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
|
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan);
|
||||||
|
|
||||||
final Box<WalletInfo> walletInfoSource;
|
final Box<WalletInfo> walletInfoSource;
|
||||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
||||||
|
final bool alwaysScan;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
WalletType getType() => WalletType.bitcoin;
|
WalletType getType() => WalletType.bitcoin;
|
||||||
|
@ -55,20 +56,24 @@ class BitcoinWalletService extends WalletService<
|
||||||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
|
.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
|
||||||
try {
|
try {
|
||||||
final wallet = await BitcoinWalletBase.open(
|
final wallet = await BitcoinWalletBase.open(
|
||||||
password: password,
|
password: password,
|
||||||
name: name,
|
name: name,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
alwaysScan: alwaysScan,
|
||||||
|
);
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
saveBackup(name);
|
saveBackup(name);
|
||||||
return wallet;
|
return wallet;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
await restoreWalletFilesFromBackup(name);
|
await restoreWalletFilesFromBackup(name);
|
||||||
final wallet = await BitcoinWalletBase.open(
|
final wallet = await BitcoinWalletBase.open(
|
||||||
password: password,
|
password: password,
|
||||||
name: name,
|
name: name,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
alwaysScan: alwaysScan,
|
||||||
|
);
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
@ -87,10 +92,12 @@ class BitcoinWalletService extends WalletService<
|
||||||
final currentWalletInfo = walletInfoSource.values
|
final currentWalletInfo = walletInfoSource.values
|
||||||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!;
|
.firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!;
|
||||||
final currentWallet = await BitcoinWalletBase.open(
|
final currentWallet = await BitcoinWalletBase.open(
|
||||||
password: password,
|
password: password,
|
||||||
name: currentName,
|
name: currentName,
|
||||||
walletInfo: currentWalletInfo,
|
walletInfo: currentWalletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
alwaysScan: alwaysScan,
|
||||||
|
);
|
||||||
|
|
||||||
await currentWallet.renameWalletFiles(newName);
|
await currentWallet.renameWalletFiles(newName);
|
||||||
await saveBackup(newName);
|
await saveBackup(newName);
|
||||||
|
@ -105,12 +112,13 @@ class BitcoinWalletService extends WalletService<
|
||||||
@override
|
@override
|
||||||
Future<BitcoinWallet> restoreFromHardwareWallet(BitcoinRestoreWalletFromHardware credentials,
|
Future<BitcoinWallet> restoreFromHardwareWallet(BitcoinRestoreWalletFromHardware credentials,
|
||||||
{bool? isTestnet}) async {
|
{bool? isTestnet}) async {
|
||||||
|
|
||||||
final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
|
final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
|
||||||
credentials.walletInfo?.network = network.value;
|
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,
|
xpub: credentials.hwAccountData.xpub,
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
@ -123,7 +131,7 @@ class BitcoinWalletService extends WalletService<
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials,
|
Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials,
|
||||||
{bool? isTestnet}) async =>
|
{bool? isTestnet}) async =>
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -41,23 +41,35 @@ class ElectrumClient {
|
||||||
|
|
||||||
bool get isConnected => _isConnected;
|
bool get isConnected => _isConnected;
|
||||||
Socket? socket;
|
Socket? socket;
|
||||||
void Function(bool)? onConnectionStatusChange;
|
void Function(bool?)? onConnectionStatusChange;
|
||||||
int _id;
|
int _id;
|
||||||
final Map<String, SocketTask> _tasks;
|
final Map<String, SocketTask> _tasks;
|
||||||
|
Map<String, SocketTask> get tasks => _tasks;
|
||||||
final Map<String, String> _errors;
|
final Map<String, String> _errors;
|
||||||
bool _isConnected;
|
bool _isConnected;
|
||||||
Timer? _aliveTimer;
|
Timer? _aliveTimer;
|
||||||
String unterminatedString;
|
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 {
|
try {
|
||||||
await socket?.close();
|
await socket?.close();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
socket = await SecureSocket.connect(host, port,
|
if (useSSL == false) {
|
||||||
timeout: connectionTimeout, onBadCertificate: (_) => true);
|
socket = await Socket.connect(host, port, timeout: connectionTimeout);
|
||||||
|
} else {
|
||||||
|
socket = await SecureSocket.connect(host, port,
|
||||||
|
timeout: connectionTimeout, onBadCertificate: (_) => true);
|
||||||
|
}
|
||||||
_setIsConnected(true);
|
_setIsConnected(true);
|
||||||
|
|
||||||
socket!.listen((Uint8List event) {
|
socket!.listen((Uint8List event) {
|
||||||
|
@ -79,7 +91,7 @@ class ElectrumClient {
|
||||||
_setIsConnected(false);
|
_setIsConnected(false);
|
||||||
}, onDone: () {
|
}, onDone: () {
|
||||||
unterminatedString = '';
|
unterminatedString = '';
|
||||||
_setIsConnected(false);
|
_setIsConnected(null);
|
||||||
});
|
});
|
||||||
keepAlive();
|
keepAlive();
|
||||||
}
|
}
|
||||||
|
@ -134,11 +146,12 @@ class ElectrumClient {
|
||||||
await callWithTimeout(method: 'server.ping');
|
await callWithTimeout(method: 'server.ping');
|
||||||
_setIsConnected(true);
|
_setIsConnected(true);
|
||||||
} on RequestFailedTimeoutException catch (_) {
|
} 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) {
|
if (result is List) {
|
||||||
return result.map((dynamic val) => val.toString()).toList();
|
return result.map((dynamic val) => val.toString()).toList();
|
||||||
}
|
}
|
||||||
|
@ -266,6 +279,18 @@ class ElectrumClient {
|
||||||
Future<Map<String, dynamic>> getHeader({required int height}) async =>
|
Future<Map<String, dynamic>> getHeader({required int height}) async =>
|
||||||
await call(method: 'blockchain.block.get_header', params: [height]) as Map<String, dynamic>;
|
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}) =>
|
Future<double> estimatefee({required int p}) =>
|
||||||
call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) {
|
call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) {
|
||||||
if (result is double) {
|
if (result is double) {
|
||||||
|
@ -308,9 +333,6 @@ class ElectrumClient {
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<List<int>> feeRates({BasedUtxoNetwork? network}) async {
|
Future<List<int>> feeRates({BasedUtxoNetwork? network}) async {
|
||||||
if (network == BitcoinNetwork.testnet) {
|
|
||||||
return [1, 1, 1];
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
final topDoubleString = await estimatefee(p: 1);
|
final topDoubleString = await estimatefee(p: 1);
|
||||||
final middleDoubleString = await estimatefee(p: 5);
|
final middleDoubleString = await estimatefee(p: 5);
|
||||||
|
@ -332,7 +354,7 @@ class ElectrumClient {
|
||||||
// "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4"
|
// "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4"
|
||||||
// }
|
// }
|
||||||
Future<int?> getCurrentBlockChainTip() =>
|
Future<int?> getCurrentBlockChainTip() =>
|
||||||
call(method: 'blockchain.headers.subscribe').then((result) {
|
callWithTimeout(method: 'blockchain.headers.subscribe').then((result) {
|
||||||
if (result is Map<String, dynamic>) {
|
if (result is Map<String, dynamic>) {
|
||||||
return result["height"] as int;
|
return result["height"] as int;
|
||||||
}
|
}
|
||||||
|
@ -340,6 +362,12 @@ class ElectrumClient {
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
BehaviorSubject<Object>? chainTipSubscribe() {
|
||||||
|
_id += 1;
|
||||||
|
return subscribe<Object>(
|
||||||
|
id: 'blockchain.headers.subscribe', method: 'blockchain.headers.subscribe');
|
||||||
|
}
|
||||||
|
|
||||||
BehaviorSubject<Object>? scripthashUpdate(String scripthash) {
|
BehaviorSubject<Object>? scripthashUpdate(String scripthash) {
|
||||||
_id += 1;
|
_id += 1;
|
||||||
return subscribe<Object>(
|
return subscribe<Object>(
|
||||||
|
@ -396,7 +424,9 @@ class ElectrumClient {
|
||||||
|
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
_aliveTimer?.cancel();
|
_aliveTimer?.cancel();
|
||||||
await socket?.close();
|
try {
|
||||||
|
await socket?.close();
|
||||||
|
} catch (_) {}
|
||||||
onConnectionStatusChange = null;
|
onConnectionStatusChange = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,17 +461,25 @@ class ElectrumClient {
|
||||||
|
|
||||||
_tasks[id]?.subject?.add(params.last);
|
_tasks[id]?.subject?.add(params.last);
|
||||||
break;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setIsConnected(bool isConnected) {
|
void _setIsConnected(bool? isConnected) {
|
||||||
if (_isConnected != isConnected) {
|
if (_isConnected != isConnected) {
|
||||||
onConnectionStatusChange?.call(isConnected);
|
onConnectionStatusChange?.call(isConnected);
|
||||||
}
|
}
|
||||||
|
|
||||||
_isConnected = isConnected;
|
_isConnected = isConnected ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleResponse(Map<String, dynamic> response) {
|
void _handleResponse(Map<String, dynamic> response) {
|
||||||
|
|
|
@ -3,8 +3,11 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
||||||
import 'package:cw_core/balance.dart';
|
import 'package:cw_core/balance.dart';
|
||||||
|
|
||||||
class ElectrumBalance extends Balance {
|
class ElectrumBalance extends Balance {
|
||||||
const ElectrumBalance({required this.confirmed, required this.unconfirmed, required this.frozen})
|
ElectrumBalance({
|
||||||
: super(confirmed, unconfirmed);
|
required this.confirmed,
|
||||||
|
required this.unconfirmed,
|
||||||
|
required this.frozen,
|
||||||
|
}) : super(confirmed, unconfirmed);
|
||||||
|
|
||||||
static ElectrumBalance? fromJSON(String? jsonSource) {
|
static ElectrumBalance? fromJSON(String? jsonSource) {
|
||||||
if (jsonSource == null) {
|
if (jsonSource == null) {
|
||||||
|
@ -19,8 +22,8 @@ class ElectrumBalance extends Balance {
|
||||||
frozen: decoded['frozen'] as int? ?? 0);
|
frozen: decoded['frozen'] as int? ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
final int confirmed;
|
int confirmed;
|
||||||
final int unconfirmed;
|
int unconfirmed;
|
||||||
final int frozen;
|
final int frozen;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -11,13 +11,11 @@ part 'electrum_transaction_history.g.dart';
|
||||||
|
|
||||||
const transactionsHistoryFileName = 'transactions.json';
|
const transactionsHistoryFileName = 'transactions.json';
|
||||||
|
|
||||||
class ElectrumTransactionHistory = ElectrumTransactionHistoryBase
|
class ElectrumTransactionHistory = ElectrumTransactionHistoryBase with _$ElectrumTransactionHistory;
|
||||||
with _$ElectrumTransactionHistory;
|
|
||||||
|
|
||||||
abstract class ElectrumTransactionHistoryBase
|
abstract class ElectrumTransactionHistoryBase
|
||||||
extends TransactionHistoryBase<ElectrumTransactionInfo> with Store {
|
extends TransactionHistoryBase<ElectrumTransactionInfo> with Store {
|
||||||
ElectrumTransactionHistoryBase(
|
ElectrumTransactionHistoryBase({required this.walletInfo, required String password})
|
||||||
{required this.walletInfo, required String password})
|
|
||||||
: _password = password,
|
: _password = password,
|
||||||
_height = 0 {
|
_height = 0 {
|
||||||
transactions = ObservableMap<String, ElectrumTransactionInfo>();
|
transactions = ObservableMap<String, ElectrumTransactionInfo>();
|
||||||
|
@ -30,8 +28,7 @@ abstract class ElectrumTransactionHistoryBase
|
||||||
Future<void> init() async => await _load();
|
Future<void> init() async => await _load();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void addOne(ElectrumTransactionInfo transaction) =>
|
void addOne(ElectrumTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||||
transactions[transaction.id] = transaction;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void addMany(Map<String, ElectrumTransactionInfo> transactions) =>
|
void addMany(Map<String, ElectrumTransactionInfo> transactions) =>
|
||||||
|
@ -40,11 +37,13 @@ abstract class ElectrumTransactionHistoryBase
|
||||||
@override
|
@override
|
||||||
Future<void> save() async {
|
Future<void> save() async {
|
||||||
try {
|
try {
|
||||||
final dirPath =
|
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||||
await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
|
||||||
final path = '$dirPath/$transactionsHistoryFileName';
|
final path = '$dirPath/$transactionsHistoryFileName';
|
||||||
final data =
|
final txjson = {};
|
||||||
json.encode({'height': _height, 'transactions': transactions});
|
for (final tx in transactions.entries) {
|
||||||
|
txjson[tx.key] = tx.value.toJson();
|
||||||
|
}
|
||||||
|
final data = json.encode({'height': _height, 'transactions': txjson});
|
||||||
await writeData(path: path, password: _password, data: data);
|
await writeData(path: path, password: _password, data: data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error while save bitcoin transaction history: ${e.toString()}');
|
print('Error while save bitcoin transaction history: ${e.toString()}');
|
||||||
|
@ -57,8 +56,7 @@ abstract class ElectrumTransactionHistoryBase
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> _read() async {
|
Future<Map<String, dynamic>> _read() async {
|
||||||
final dirPath =
|
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||||
await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
|
||||||
final path = '$dirPath/$transactionsHistoryFileName';
|
final path = '$dirPath/$transactionsHistoryFileName';
|
||||||
final content = await read(path: path, password: _password);
|
final content = await read(path: path, password: _password);
|
||||||
return json.decode(content) as Map<String, dynamic>;
|
return json.decode(content) as Map<String, dynamic>;
|
||||||
|
@ -84,7 +82,5 @@ abstract class ElectrumTransactionHistoryBase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _update(ElectrumTransactionInfo transaction) =>
|
void _update(ElectrumTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||||
transactions[transaction.id] = transaction;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
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/address_from_output.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_amount_format.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_direction.dart';
|
||||||
import 'package:cw_core/transaction_info.dart';
|
import 'package:cw_core/transaction_info.dart';
|
||||||
import 'package:cw_core/format_amount.dart';
|
import 'package:cw_core/format_amount.dart';
|
||||||
|
@ -19,6 +18,8 @@ class ElectrumTransactionBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ElectrumTransactionInfo extends TransactionInfo {
|
class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
|
List<BitcoinSilentPaymentsUnspent>? unspents;
|
||||||
|
|
||||||
ElectrumTransactionInfo(this.type,
|
ElectrumTransactionInfo(this.type,
|
||||||
{required String id,
|
{required String id,
|
||||||
required int height,
|
required int height,
|
||||||
|
@ -29,7 +30,9 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
required TransactionDirection direction,
|
required TransactionDirection direction,
|
||||||
required bool isPending,
|
required bool isPending,
|
||||||
required DateTime date,
|
required DateTime date,
|
||||||
required int confirmations}) {
|
required int confirmations,
|
||||||
|
String? to,
|
||||||
|
this.unspents}) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
|
@ -40,6 +43,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
this.date = date;
|
this.date = date;
|
||||||
this.isPending = isPending;
|
this.isPending = isPending;
|
||||||
this.confirmations = confirmations;
|
this.confirmations = confirmations;
|
||||||
|
this.to = to;
|
||||||
}
|
}
|
||||||
|
|
||||||
factory ElectrumTransactionInfo.fromElectrumVerbose(Map<String, Object> obj, WalletType type,
|
factory ElectrumTransactionInfo.fromElectrumVerbose(Map<String, Object> obj, WalletType type,
|
||||||
|
@ -153,52 +157,31 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
confirmations: bundle.confirmations);
|
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) {
|
factory ElectrumTransactionInfo.fromJson(Map<String, dynamic> data, WalletType type) {
|
||||||
return ElectrumTransactionInfo(type,
|
final inputAddresses = data['inputAddresses'] as List<dynamic>? ?? [];
|
||||||
id: data['id'] as String,
|
final outputAddresses = data['outputAddresses'] as List<dynamic>? ?? [];
|
||||||
height: data['height'] as int,
|
final unspents = data['unspents'] as List<dynamic>? ?? [];
|
||||||
amount: data['amount'] as int,
|
|
||||||
fee: data['fee'] as int,
|
return ElectrumTransactionInfo(
|
||||||
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
type,
|
||||||
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
|
id: data['id'] as String,
|
||||||
isPending: data['isPending'] as bool,
|
height: data['height'] as int,
|
||||||
inputAddresses: data['inputAddresses'] as List<String>,
|
amount: data['amount'] as int,
|
||||||
outputAddresses: data['outputAddresses'] as List<String>,
|
fee: data['fee'] as int,
|
||||||
confirmations: data['confirmations'] as int);
|
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
||||||
|
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
|
||||||
|
isPending: data['isPending'] as bool,
|
||||||
|
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;
|
final WalletType type;
|
||||||
|
@ -244,8 +227,14 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
m['isPending'] = isPending;
|
m['isPending'] = isPending;
|
||||||
m['confirmations'] = confirmations;
|
m['confirmations'] = confirmations;
|
||||||
m['fee'] = fee;
|
m['fee'] = fee;
|
||||||
|
m['to'] = to;
|
||||||
|
m['unspents'] = unspents?.map((e) => e.toJson()).toList() ?? [];
|
||||||
m['inputAddresses'] = inputAddresses;
|
m['inputAddresses'] = inputAddresses;
|
||||||
m['outputAddresses'] = outputAddresses;
|
m['outputAddresses'] = outputAddresses;
|
||||||
return m;
|
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
|
@ -1,7 +1,7 @@
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
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/bitcoin_address_record.dart';
|
||||||
import 'package:cw_bitcoin/electrum.dart';
|
|
||||||
import 'package:cw_core/wallet_addresses.dart';
|
import 'package:cw_core/wallet_addresses.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
@ -24,15 +24,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
WalletInfo walletInfo, {
|
WalletInfo walletInfo, {
|
||||||
required this.mainHd,
|
required this.mainHd,
|
||||||
required this.sideHd,
|
required this.sideHd,
|
||||||
required this.electrumClient,
|
|
||||||
required this.network,
|
required this.network,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
List<BitcoinAddressRecord>? initialAddresses,
|
||||||
Map<String, int>? initialRegularAddressIndex,
|
Map<String, int>? initialRegularAddressIndex,
|
||||||
Map<String, int>? initialChangeAddressIndex,
|
Map<String, int>? initialChangeAddressIndex,
|
||||||
|
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
||||||
|
int initialSilentAddressIndex = 0,
|
||||||
|
bitcoin.HDWallet? masterHd,
|
||||||
BitcoinAddressType? initialAddressPageType,
|
BitcoinAddressType? initialAddressPageType,
|
||||||
}) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
|
}) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
|
||||||
addressesByReceiveType =
|
addressesByReceiveType =
|
||||||
ObservableList<BitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
|
ObservableList<BaseBitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
|
||||||
receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
|
receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
|
||||||
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed)
|
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed)
|
||||||
.toSet()),
|
.toSet()),
|
||||||
|
@ -45,7 +47,38 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
(walletInfo.addressPageType != null
|
(walletInfo.addressPageType != null
|
||||||
? BitcoinAddressType.fromValue(walletInfo.addressPageType!)
|
? BitcoinAddressType.fromValue(walletInfo.addressPageType!)
|
||||||
: SegwitAddresType.p2wpkh),
|
: SegwitAddresType.p2wpkh),
|
||||||
|
silentAddresses = ObservableList<BitcoinSilentPaymentAddressRecord>.of(
|
||||||
|
(initialSilentAddresses ?? []).toSet()),
|
||||||
|
currentSilentAddressIndex = initialSilentAddressIndex,
|
||||||
super(walletInfo) {
|
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();
|
updateAddressesByMatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,27 +87,40 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
static const gap = 20;
|
static const gap = 20;
|
||||||
|
|
||||||
final ObservableList<BitcoinAddressRecord> _addresses;
|
final ObservableList<BitcoinAddressRecord> _addresses;
|
||||||
// Matched by addressPageType
|
late ObservableList<BaseBitcoinAddressRecord> addressesByReceiveType;
|
||||||
late ObservableList<BitcoinAddressRecord> addressesByReceiveType;
|
|
||||||
final ObservableList<BitcoinAddressRecord> receiveAddresses;
|
final ObservableList<BitcoinAddressRecord> receiveAddresses;
|
||||||
final ObservableList<BitcoinAddressRecord> changeAddresses;
|
final ObservableList<BitcoinAddressRecord> changeAddresses;
|
||||||
final ElectrumClient electrumClient;
|
final ObservableList<BitcoinSilentPaymentAddressRecord> silentAddresses;
|
||||||
final BasedUtxoNetwork network;
|
final BasedUtxoNetwork network;
|
||||||
final bitcoin.HDWallet mainHd;
|
final bitcoin.HDWallet mainHd;
|
||||||
final bitcoin.HDWallet sideHd;
|
final bitcoin.HDWallet sideHd;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
SilentPaymentOwner? silentAddress;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
late BitcoinAddressType _addressPageType;
|
late BitcoinAddressType _addressPageType;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
BitcoinAddressType get addressPageType => _addressPageType;
|
BitcoinAddressType get addressPageType => _addressPageType;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
String? activeSilentAddress;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
List<BitcoinAddressRecord> get allAddresses => _addresses;
|
List<BitcoinAddressRecord> get allAddresses => _addresses;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@computed
|
@computed
|
||||||
String get address {
|
String get address {
|
||||||
|
if (addressPageType == SilentPaymentsAddresType.p2sp) {
|
||||||
|
if (activeSilentAddress != null) {
|
||||||
|
return activeSilentAddress!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return silentAddress.toString();
|
||||||
|
}
|
||||||
|
|
||||||
String receiveAddress;
|
String receiveAddress;
|
||||||
|
|
||||||
final typeMatchingReceiveAddresses =
|
final typeMatchingReceiveAddresses =
|
||||||
|
@ -103,6 +149,18 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
set address(String addr) {
|
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);
|
final addressRecord = _addresses.firstWhere((addressRecord) => addressRecord.address == addr);
|
||||||
|
|
||||||
previousAddressRecord = addressRecord;
|
previousAddressRecord = addressRecord;
|
||||||
|
@ -129,6 +187,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
void set currentChangeAddressIndex(int index) =>
|
void set currentChangeAddressIndex(int index) =>
|
||||||
currentChangeAddressIndexByType[_addressPageType.toString()] = index;
|
currentChangeAddressIndexByType[_addressPageType.toString()] = index;
|
||||||
|
|
||||||
|
int currentSilentAddressIndex;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
BitcoinAddressRecord? previousAddressRecord;
|
BitcoinAddressRecord? previousAddressRecord;
|
||||||
|
|
||||||
|
@ -196,7 +256,50 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
return address;
|
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(
|
final newAddressIndex = addressesByReceiveType.fold(
|
||||||
0, (int acc, addressRecord) => addressRecord.isHidden == false ? acc + 1 : acc);
|
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 {
|
Future<void> updateAddressesInBox() async {
|
||||||
try {
|
try {
|
||||||
addressesMap.clear();
|
addressesMap.clear();
|
||||||
addressesMap[address] = '';
|
addressesMap[address] = 'Active';
|
||||||
|
|
||||||
allAddressesMap.clear();
|
allAddressesMap.clear();
|
||||||
_addresses.forEach((addressRecord) {
|
_addresses.forEach((addressRecord) {
|
||||||
allAddressesMap[addressRecord.address] = addressRecord.name;
|
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();
|
await saveAddressesInBox();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e.toString());
|
print(e.toString());
|
||||||
|
@ -235,18 +396,41 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void updateAddress(String address, String label) {
|
void updateAddress(String address, String label) {
|
||||||
final addressRecord =
|
BaseBitcoinAddressRecord? foundAddress;
|
||||||
_addresses.firstWhere((addressRecord) => addressRecord.address == address);
|
_addresses.forEach((addressRecord) {
|
||||||
addressRecord.setNewName(label);
|
if (addressRecord.address == address) {
|
||||||
final index = _addresses.indexOf(addressRecord);
|
foundAddress = addressRecord;
|
||||||
_addresses.remove(addressRecord);
|
}
|
||||||
_addresses.insert(index, 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
|
@action
|
||||||
void updateAddressesByMatch() {
|
void updateAddressesByMatch() {
|
||||||
|
if (addressPageType == SilentPaymentsAddresType.p2sp) {
|
||||||
|
addressesByReceiveType.clear();
|
||||||
|
addressesByReceiveType.addAll(silentAddresses);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
addressesByReceiveType.clear();
|
addressesByReceiveType.clear();
|
||||||
addressesByReceiveType.addAll(_addresses.where(_isAddressPageTypeMatch).toList());
|
addressesByReceiveType.addAll(_addresses.where(_isAddressPageTypeMatch).toList());
|
||||||
}
|
}
|
||||||
|
@ -272,7 +456,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> discoverAddresses(List<BitcoinAddressRecord> addressList, bool isHidden,
|
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 {
|
{BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
|
||||||
if (!isHidden) {
|
if (!isHidden) {
|
||||||
_validateSideHdAddresses(addressList.toList());
|
_validateSideHdAddresses(addressList.toList());
|
||||||
|
@ -282,8 +466,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
startIndex: addressList.length, isHidden: isHidden, type: type);
|
startIndex: addressList.length, isHidden: isHidden, type: type);
|
||||||
addAddresses(newAddresses);
|
addAddresses(newAddresses);
|
||||||
|
|
||||||
final addressesWithHistory = await Future.wait(newAddresses
|
final addressesWithHistory = await Future.wait(newAddresses.map(getAddressHistory));
|
||||||
.map((addr) => getAddressHistory(addr, _addresses.map((e) => e.address).toSet())));
|
|
||||||
final isLastAddressUsed = addressesWithHistory.last == addressList.last.address;
|
final isLastAddressUsed = addressesWithHistory.last == addressList.last.address;
|
||||||
|
|
||||||
if (isLastAddressUsed) {
|
if (isLastAddressUsed) {
|
||||||
|
@ -349,6 +532,15 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
updateAddressesByMatch();
|
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) {
|
void _validateSideHdAddresses(List<BitcoinAddressRecord> addrWithTransactions) {
|
||||||
addrWithTransactions.forEach((element) {
|
addrWithTransactions.forEach((element) {
|
||||||
if (element.address !=
|
if (element.address !=
|
||||||
|
@ -371,4 +563,15 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
bitcoin.HDWallet _getHd(bool isHidden) => isHidden ? sideHd : mainHd;
|
bitcoin.HDWallet _getHd(bool isHidden) => isHidden ? sideHd : mainHd;
|
||||||
bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type;
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ class ElectrumWalletSnapshot {
|
||||||
required this.regularAddressIndex,
|
required this.regularAddressIndex,
|
||||||
required this.changeAddressIndex,
|
required this.changeAddressIndex,
|
||||||
required this.addressPageType,
|
required this.addressPageType,
|
||||||
|
required this.silentAddresses,
|
||||||
|
required this.silentAddressIndex,
|
||||||
this.passphrase,
|
this.passphrase,
|
||||||
this.derivationType,
|
this.derivationType,
|
||||||
this.derivationPath,
|
this.derivationPath,
|
||||||
|
@ -32,9 +34,11 @@ class ElectrumWalletSnapshot {
|
||||||
String? mnemonic;
|
String? mnemonic;
|
||||||
String? xpub;
|
String? xpub;
|
||||||
List<BitcoinAddressRecord> addresses;
|
List<BitcoinAddressRecord> addresses;
|
||||||
|
List<BitcoinSilentPaymentAddressRecord> silentAddresses;
|
||||||
ElectrumBalance balance;
|
ElectrumBalance balance;
|
||||||
Map<String, int> regularAddressIndex;
|
Map<String, int> regularAddressIndex;
|
||||||
Map<String, int> changeAddressIndex;
|
Map<String, int> changeAddressIndex;
|
||||||
|
int silentAddressIndex;
|
||||||
String? passphrase;
|
String? passphrase;
|
||||||
DerivationType? derivationType;
|
DerivationType? derivationType;
|
||||||
String? derivationPath;
|
String? derivationPath;
|
||||||
|
@ -50,15 +54,23 @@ class ElectrumWalletSnapshot {
|
||||||
final passphrase = data['passphrase'] as String? ?? '';
|
final passphrase = data['passphrase'] as String? ?? '';
|
||||||
final addresses = addressesTmp
|
final addresses = addressesTmp
|
||||||
.whereType<String>()
|
.whereType<String>()
|
||||||
.map((addr) => BitcoinAddressRecord.fromJSON(addr, network))
|
.map((addr) => BitcoinAddressRecord.fromJSON(addr, network: network))
|
||||||
.toList();
|
.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);
|
ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
|
||||||
var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
|
var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
|
||||||
var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
|
var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
|
||||||
|
var silentAddressIndex = 0;
|
||||||
|
|
||||||
final derivationType =
|
final derivationType = DerivationType
|
||||||
DerivationType.values[(data['derivationTypeIndex'] as int?) ?? DerivationType.electrum.index];
|
.values[(data['derivationTypeIndex'] as int?) ?? DerivationType.electrum.index];
|
||||||
final derivationPath = data['derivationPath'] as String? ?? "m/0'/0";
|
final derivationPath = data['derivationPath'] as String? ?? "m/0'/0";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -69,6 +81,7 @@ class ElectrumWalletSnapshot {
|
||||||
SegwitAddresType.p2wpkh.toString():
|
SegwitAddresType.p2wpkh.toString():
|
||||||
int.parse(data['change_address_index'] as String? ?? '0')
|
int.parse(data['change_address_index'] as String? ?? '0')
|
||||||
};
|
};
|
||||||
|
silentAddressIndex = int.parse(data['silent_address_index'] as String? ?? '0');
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
try {
|
try {
|
||||||
regularAddressIndexByType = data["account_index"] as Map<String, int>? ?? {};
|
regularAddressIndexByType = data["account_index"] as Map<String, int>? ?? {};
|
||||||
|
@ -90,6 +103,8 @@ class ElectrumWalletSnapshot {
|
||||||
addressPageType: data['address_page_type'] as String?,
|
addressPageType: data['address_page_type'] as String?,
|
||||||
derivationType: derivationType,
|
derivationType: derivationType,
|
||||||
derivationPath: derivationPath,
|
derivationPath: derivationPath,
|
||||||
|
silentAddresses: silentAddresses,
|
||||||
|
silentAddressIndex: silentAddressIndex,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_core/exceptions.dart';
|
import 'package:cw_core/exceptions.dart';
|
||||||
|
|
||||||
class BitcoinTransactionWrongBalanceException extends TransactionWrongBalanceException {
|
class BitcoinTransactionWrongBalanceException extends TransactionWrongBalanceException {
|
||||||
BitcoinTransactionWrongBalanceException() : super(CryptoCurrency.btc);
|
BitcoinTransactionWrongBalanceException({super.amount}) : super(CryptoCurrency.btc);
|
||||||
}
|
}
|
||||||
|
|
||||||
class BitcoinTransactionNoInputsException extends TransactionNoInputsException {}
|
class BitcoinTransactionNoInputsException extends TransactionNoInputsException {}
|
||||||
|
@ -27,3 +27,7 @@ class BitcoinTransactionCommitFailedDustOutputSendAll
|
||||||
extends TransactionCommitFailedDustOutputSendAll {}
|
extends TransactionCommitFailedDustOutputSendAll {}
|
||||||
|
|
||||||
class BitcoinTransactionCommitFailedVoutNegative extends TransactionCommitFailedVoutNegative {}
|
class BitcoinTransactionCommitFailedVoutNegative extends TransactionCommitFailedVoutNegative {}
|
||||||
|
|
||||||
|
class BitcoinTransactionCommitFailedBIP68Final extends TransactionCommitFailedBIP68Final {}
|
||||||
|
|
||||||
|
class BitcoinTransactionSilentPaymentsNotSupported extends TransactionInputNotSupported {}
|
||||||
|
|
|
@ -44,7 +44,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
currency: CryptoCurrency.ltc) {
|
currency: CryptoCurrency.ltc) {
|
||||||
walletAddresses = LitecoinWalletAddresses(
|
walletAddresses = LitecoinWalletAddresses(
|
||||||
walletInfo,
|
walletInfo,
|
||||||
electrumClient: electrumClient,
|
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
|
|
|
@ -15,7 +15,6 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
|
||||||
required super.mainHd,
|
required super.mainHd,
|
||||||
required super.sideHd,
|
required super.sideHd,
|
||||||
required super.network,
|
required super.network,
|
||||||
required super.electrumClient,
|
|
||||||
super.initialAddresses,
|
super.initialAddresses,
|
||||||
super.initialRegularAddressIndex,
|
super.initialRegularAddressIndex,
|
||||||
super.initialChangeAddressIndex,
|
super.initialChangeAddressIndex,
|
||||||
|
|
|
@ -73,6 +73,11 @@ class PendingBitcoinTransaction with PendingTransaction {
|
||||||
if (error.contains("bad-txns-vout-negative")) {
|
if (error.contains("bad-txns-vout-negative")) {
|
||||||
throw BitcoinTransactionCommitFailedVoutNegative();
|
throw BitcoinTransactionCommitFailedVoutNegative();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (error.contains("non-BIP68-final")) {
|
||||||
|
throw BitcoinTransactionCommitFailedBIP68Final();
|
||||||
|
}
|
||||||
|
|
||||||
throw BitcoinTransactionCommitFailed(errorMessage: error);
|
throw BitcoinTransactionCommitFailed(errorMessage: error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,10 +29,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: asn1lib
|
name: asn1lib
|
||||||
sha256: c9c85fedbe2188b95133cbe960e16f5f448860f7133330e272edbbca5893ddc6
|
sha256: "58082b3f0dca697204dbab0ef9ff208bfaea7767ea771076af9a343488428dda"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.2"
|
version: "1.5.3"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -79,11 +79,11 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: cake-update-v2
|
ref: cake-update-v3
|
||||||
resolved-ref: "01d844a5f5a520a31df5254e34169af4664aa769"
|
resolved-ref: cc99eedb1d28ee9376dda0465ef72aa627ac6149
|
||||||
url: "https://github.com/cake-tech/bitcoin_base.git"
|
url: "https://github.com/cake-tech/bitcoin_base"
|
||||||
source: git
|
source: git
|
||||||
version: "4.2.0"
|
version: "4.2.1"
|
||||||
bitcoin_flutter:
|
bitcoin_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -96,11 +96,12 @@ packages:
|
||||||
blockchain_utils:
|
blockchain_utils:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: blockchain_utils
|
path: "."
|
||||||
sha256: "38ef5f4a22441ac4370aed9071dc71c460acffc37c79b344533f67d15f24c13c"
|
ref: cake-update-v1
|
||||||
url: "https://pub.dev"
|
resolved-ref: cabd7e0e16c4da9920338c76eff3aeb8af0211f3
|
||||||
source: hosted
|
url: "https://github.com/cake-tech/blockchain_utils"
|
||||||
version: "2.1.1"
|
source: git
|
||||||
|
version: "2.1.2"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -197,6 +198,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.3"
|
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:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -241,10 +250,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: cryptography
|
name: cryptography
|
||||||
sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35
|
sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.0"
|
version: "2.7.0"
|
||||||
cw_core:
|
cw_core:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -288,10 +297,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: ffi
|
name: ffi
|
||||||
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
|
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
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:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -346,10 +363,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: functional_data
|
name: functional_data
|
||||||
sha256: aefdec4365452283b2a7cf420a3169654d51d3e9553069a22d76680d7a9d7c3d
|
sha256: "76d17dc707c40e552014f5a49c0afcc3f1e3f05e800cd6b7872940bfe41a5039"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.2.0"
|
||||||
glob:
|
glob:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -394,10 +411,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
|
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.2.1"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -442,10 +459,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: json_annotation
|
name: json_annotation
|
||||||
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
|
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.8.1"
|
version: "4.9.0"
|
||||||
leak_tracker:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -531,10 +548,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: mime
|
name: mime
|
||||||
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
|
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.5"
|
||||||
mobx:
|
mobx:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -579,26 +596,26 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
|
sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.3"
|
||||||
path_provider_android:
|
path_provider_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
|
sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.2"
|
version: "2.2.4"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_foundation
|
name: path_provider_foundation
|
||||||
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
|
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.4.0"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -643,10 +660,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pointycastle
|
name: pointycastle
|
||||||
sha256: "70fe966348fe08c34bf929582f1d8247d9d9408130723206472b4687227e4333"
|
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.8.0"
|
version: "3.9.1"
|
||||||
pool:
|
pool:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -687,6 +704,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.3"
|
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:
|
reactive_ble_mobile:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -736,10 +761,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: socks5_proxy
|
name: socks5_proxy
|
||||||
sha256: "1d21b5606169654bbf4cfb904e8e6ed897e9f763358709f87310c757096d909a"
|
sha256: "045cbba84f6e2b01c1c77634a63e926352bf110ef5f07fc462c6d43bbd4b6a83"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.5+dev.2"
|
||||||
source_gen:
|
source_gen:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -764,6 +789,15 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.0"
|
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:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -860,22 +894,30 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
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:
|
web_socket_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web_socket_channel
|
name: web_socket_channel
|
||||||
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
|
sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.0"
|
version: "2.4.5"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
|
sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.9"
|
version: "5.5.0"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -892,6 +934,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
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:
|
sdks:
|
||||||
dart: ">=3.2.0-0 <4.0.0"
|
dart: ">=3.3.0 <4.0.0"
|
||||||
flutter: ">=3.10.0"
|
flutter: ">=3.16.6"
|
||||||
|
|
|
@ -32,13 +32,21 @@ dependencies:
|
||||||
cryptography: ^2.0.5
|
cryptography: ^2.0.5
|
||||||
bitcoin_base:
|
bitcoin_base:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/bitcoin_base.git
|
url: https://github.com/cake-tech/bitcoin_base
|
||||||
ref: cake-update-v2
|
ref: cake-update-v3
|
||||||
blockchain_utils: ^2.1.1
|
blockchain_utils:
|
||||||
|
git:
|
||||||
|
url: https://github.com/cake-tech/blockchain_utils
|
||||||
|
ref: cake-update-v1
|
||||||
ledger_flutter: ^1.0.1
|
ledger_flutter: ^1.0.1
|
||||||
ledger_bitcoin:
|
ledger_bitcoin:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/ledger-bitcoin
|
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:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
@ -46,7 +46,6 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
currency: CryptoCurrency.bch) {
|
currency: CryptoCurrency.bch) {
|
||||||
walletAddresses = BitcoinCashWalletAddresses(
|
walletAddresses = BitcoinCashWalletAddresses(
|
||||||
walletInfo,
|
walletInfo,
|
||||||
electrumClient: electrumClient,
|
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
|
|
|
@ -15,7 +15,6 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
|
||||||
required super.mainHd,
|
required super.mainHd,
|
||||||
required super.sideHd,
|
required super.sideHd,
|
||||||
required super.network,
|
required super.network,
|
||||||
required super.electrumClient,
|
|
||||||
super.initialAddresses,
|
super.initialAddresses,
|
||||||
super.initialRegularAddressIndex,
|
super.initialRegularAddressIndex,
|
||||||
super.initialChangeAddressIndex,
|
super.initialChangeAddressIndex,
|
||||||
|
|
|
@ -31,10 +31,12 @@ dependencies:
|
||||||
ref: Add-Support-For-OP-Return-data
|
ref: Add-Support-For-OP-Return-data
|
||||||
bitcoin_base:
|
bitcoin_base:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/bitcoin_base.git
|
url: https://github.com/cake-tech/bitcoin_base
|
||||||
ref: cake-update-v2
|
ref: cake-update-v3
|
||||||
|
blockchain_utils:
|
||||||
|
git:
|
||||||
|
url: https://github.com/cake-tech/blockchain_utils
|
||||||
|
ref: cake-update-v1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
@ -104,6 +104,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
||||||
CryptoCurrency.digibyte,
|
CryptoCurrency.digibyte,
|
||||||
CryptoCurrency.usdtSol,
|
CryptoCurrency.usdtSol,
|
||||||
CryptoCurrency.usdcTrc20,
|
CryptoCurrency.usdcTrc20,
|
||||||
|
CryptoCurrency.tbtc,
|
||||||
];
|
];
|
||||||
|
|
||||||
static const havenCurrencies = [
|
static const havenCurrencies = [
|
||||||
|
@ -218,7 +219,8 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
||||||
static const kaspa = CryptoCurrency(title: 'KAS', fullName: 'Kaspa', raw: 89, name: 'kas', iconPath: 'assets/images/kaspa_icon.png', decimals: 8);
|
static const kaspa = CryptoCurrency(title: 'KAS', fullName: 'Kaspa', raw: 89, name: 'kas', iconPath: 'assets/images/kaspa_icon.png', decimals: 8);
|
||||||
static const digibyte = CryptoCurrency(title: 'DGB', fullName: 'DigiByte', raw: 90, name: 'dgb', iconPath: 'assets/images/digibyte.png', decimals: 8);
|
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 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 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 =
|
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}) {
|
static CryptoCurrency fromString(String name, {CryptoCurrency? walletCurrency}) {
|
||||||
try {
|
try {
|
||||||
return CryptoCurrency.all.firstWhere((element) =>
|
return CryptoCurrency.all.firstWhere((element) =>
|
||||||
element.title.toLowerCase() == name &&
|
element.title.toLowerCase() == name.toLowerCase() &&
|
||||||
(element.tag == null ||
|
(element.tag == null ||
|
||||||
element.tag == walletCurrency?.title ||
|
element.tag == walletCurrency?.title ||
|
||||||
element.tag == walletCurrency?.tag));
|
element.tag == walletCurrency?.tag));
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
|
||||||
CryptoCurrency currencyForWalletType(WalletType type) {
|
CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
|
if (isTestnet == true) {
|
||||||
|
return CryptoCurrency.tbtc;
|
||||||
|
}
|
||||||
return CryptoCurrency.btc;
|
return CryptoCurrency.btc;
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
return CryptoCurrency.xmr;
|
return CryptoCurrency.xmr;
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
|
||||||
class TransactionWrongBalanceException implements Exception {
|
class TransactionWrongBalanceException implements Exception {
|
||||||
TransactionWrongBalanceException(this.currency);
|
TransactionWrongBalanceException(this.currency, {this.amount});
|
||||||
|
|
||||||
final CryptoCurrency currency;
|
final CryptoCurrency currency;
|
||||||
|
final int? amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TransactionNoInputsException implements Exception {}
|
class TransactionNoInputsException implements Exception {}
|
||||||
|
@ -32,3 +33,7 @@ class TransactionCommitFailedDustOutput implements Exception {}
|
||||||
class TransactionCommitFailedDustOutputSendAll implements Exception {}
|
class TransactionCommitFailedDustOutputSendAll implements Exception {}
|
||||||
|
|
||||||
class TransactionCommitFailedVoutNegative implements Exception {}
|
class TransactionCommitFailedVoutNegative implements Exception {}
|
||||||
|
|
||||||
|
class TransactionCommitFailedBIP68Final implements Exception {}
|
||||||
|
|
||||||
|
class TransactionInputNotSupported implements Exception {}
|
||||||
|
|
|
@ -242,3 +242,57 @@ Future<int> getHavenCurrentHeight() async {
|
||||||
throw Exception('Failed to load current blockchain height');
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -244,8 +244,12 @@ class Node extends HiveObject with Keyable {
|
||||||
|
|
||||||
Future<bool> requestElectrumServer() async {
|
Future<bool> requestElectrumServer() async {
|
||||||
try {
|
try {
|
||||||
await SecureSocket.connect(uri.host, uri.port,
|
if (useSSL == true) {
|
||||||
timeout: Duration(seconds: 5), onBadCertificate: (_) => 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;
|
return true;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -14,6 +14,16 @@ class SyncingSyncStatus extends SyncStatus {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => '$blocksLeft';
|
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 {
|
class SyncedSyncStatus extends SyncStatus {
|
||||||
|
@ -21,6 +31,17 @@ class SyncedSyncStatus extends SyncStatus {
|
||||||
double progress() => 1.0;
|
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 {
|
class NotConnectedSyncStatus extends SyncStatus {
|
||||||
const NotConnectedSyncStatus();
|
const NotConnectedSyncStatus();
|
||||||
|
|
||||||
|
@ -33,10 +54,7 @@ class AttemptingSyncStatus extends SyncStatus {
|
||||||
double progress() => 0.0;
|
double progress() => 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
class FailedSyncStatus extends SyncStatus {
|
class FailedSyncStatus extends NotConnectedSyncStatus {}
|
||||||
@override
|
|
||||||
double progress() => 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConnectingSyncStatus extends SyncStatus {
|
class ConnectingSyncStatus extends SyncStatus {
|
||||||
@override
|
@override
|
||||||
|
@ -48,7 +66,14 @@ class ConnectedSyncStatus extends SyncStatus {
|
||||||
double progress() => 0.0;
|
double progress() => 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
class LostConnectionSyncStatus extends SyncStatus {
|
class UnsupportedSyncStatus extends NotConnectedSyncStatus {}
|
||||||
|
|
||||||
|
class TimedOutSyncStatus extends NotConnectedSyncStatus {
|
||||||
@override
|
@override
|
||||||
double progress() => 1.0;
|
String toString() => 'Timed out';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class LostConnectionSyncStatus extends NotConnectedSyncStatus {
|
||||||
|
@override
|
||||||
|
String toString() => 'Reconnecting';
|
||||||
|
}
|
||||||
|
|
|
@ -16,7 +16,8 @@ class UnspentCoinsInfo extends HiveObject {
|
||||||
required this.value,
|
required this.value,
|
||||||
this.keyImage = null,
|
this.keyImage = null,
|
||||||
this.isChange = false,
|
this.isChange = false,
|
||||||
this.accountIndex = 0
|
this.accountIndex = 0,
|
||||||
|
this.isSilentPayment = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
static const typeId = UNSPENT_COINS_INFO_TYPE_ID;
|
static const typeId = UNSPENT_COINS_INFO_TYPE_ID;
|
||||||
|
@ -49,13 +50,16 @@ class UnspentCoinsInfo extends HiveObject {
|
||||||
|
|
||||||
@HiveField(8, defaultValue: null)
|
@HiveField(8, defaultValue: null)
|
||||||
String? keyImage;
|
String? keyImage;
|
||||||
|
|
||||||
@HiveField(9, defaultValue: false)
|
@HiveField(9, defaultValue: false)
|
||||||
bool isChange;
|
bool isChange;
|
||||||
|
|
||||||
@HiveField(10, defaultValue: 0)
|
@HiveField(10, defaultValue: 0)
|
||||||
int accountIndex;
|
int accountIndex;
|
||||||
|
|
||||||
|
@HiveField(11, defaultValue: false)
|
||||||
|
bool? isSilentPayment;
|
||||||
|
|
||||||
String get note => noteRaw ?? '';
|
String get note => noteRaw ?? '';
|
||||||
|
|
||||||
set note(String value) => noteRaw = value;
|
set note(String value) => noteRaw = value;
|
||||||
|
|
|
@ -17,5 +17,6 @@ class Unspent {
|
||||||
int? confirmations;
|
int? confirmations;
|
||||||
String note;
|
String note;
|
||||||
|
|
||||||
bool get isP2wpkh => address.startsWith('bc') || address.startsWith('ltc');
|
bool get isP2wpkh =>
|
||||||
|
address.startsWith('bc') || address.startsWith('tb') || address.startsWith('ltc');
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
|
||||||
|
|
||||||
WalletType get type => walletInfo.type;
|
WalletType get type => walletInfo.type;
|
||||||
|
|
||||||
CryptoCurrency get currency => currencyForWalletType(type);
|
CryptoCurrency get currency => currencyForWalletType(type, isTestnet: isTestnet);
|
||||||
|
|
||||||
String get id => walletInfo.id;
|
String get id => walletInfo.id;
|
||||||
|
|
||||||
|
|
|
@ -66,21 +66,21 @@ class DerivationInfo extends HiveObject {
|
||||||
@HiveType(typeId: WalletInfo.typeId)
|
@HiveType(typeId: WalletInfo.typeId)
|
||||||
class WalletInfo extends HiveObject {
|
class WalletInfo extends HiveObject {
|
||||||
WalletInfo(
|
WalletInfo(
|
||||||
this.id,
|
this.id,
|
||||||
this.name,
|
this.name,
|
||||||
this.type,
|
this.type,
|
||||||
this.isRecovery,
|
this.isRecovery,
|
||||||
this.restoreHeight,
|
this.restoreHeight,
|
||||||
this.timestamp,
|
this.timestamp,
|
||||||
this.dirPath,
|
this.dirPath,
|
||||||
this.path,
|
this.path,
|
||||||
this.address,
|
this.address,
|
||||||
this.yatEid,
|
this.yatEid,
|
||||||
this.yatLastUsedAddressRaw,
|
this.yatLastUsedAddressRaw,
|
||||||
this.showIntroCakePayCard,
|
this.showIntroCakePayCard,
|
||||||
this.derivationInfo,
|
this.derivationInfo,
|
||||||
this.hardwareWalletType,
|
this.hardwareWalletType,
|
||||||
): _yatLastUsedAddressController = StreamController<String>.broadcast();
|
) : _yatLastUsedAddressController = StreamController<String>.broadcast();
|
||||||
|
|
||||||
factory WalletInfo.external({
|
factory WalletInfo.external({
|
||||||
required String id,
|
required String id,
|
||||||
|
@ -207,4 +207,9 @@ class WalletInfo extends HiveObject {
|
||||||
Stream<String> get yatLastUsedAddressStream => _yatLastUsedAddressController.stream;
|
Stream<String> get yatLastUsedAddressStream => _yatLastUsedAddressController.stream;
|
||||||
|
|
||||||
StreamController<String> _yatLastUsedAddressController;
|
StreamController<String> _yatLastUsedAddressController;
|
||||||
|
|
||||||
|
Future<void> updateRestoreHeight(int height) async {
|
||||||
|
restoreHeight = height;
|
||||||
|
await save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,11 +173,14 @@ String walletTypeToDisplayName(WalletType type) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
|
CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = false}) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
return CryptoCurrency.xmr;
|
return CryptoCurrency.xmr;
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
|
if (isTestnet) {
|
||||||
|
return CryptoCurrency.tbtc;
|
||||||
|
}
|
||||||
return CryptoCurrency.btc;
|
return CryptoCurrency.btc;
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
return CryptoCurrency.ltc;
|
return CryptoCurrency.ltc;
|
||||||
|
|
|
@ -399,6 +399,7 @@ extern "C"
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wallet->store(std::string(path));
|
||||||
change_current_wallet(wallet);
|
change_current_wallet(wallet);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -464,6 +465,16 @@ extern "C"
|
||||||
return strdup(get_current_wallet()->address(account_index, address_index).c_str());
|
return strdup(get_current_wallet()->address(account_index, address_index).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *get_cache_attribute(char *name)
|
||||||
|
{
|
||||||
|
return strdup(get_current_wallet()->getCacheAttribute(std::string(name)).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool set_cache_attribute(char *name, char *value)
|
||||||
|
{
|
||||||
|
get_current_wallet()->setCacheAttribute(std::string(name), std::string(value));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
const char *seed()
|
const char *seed()
|
||||||
{
|
{
|
||||||
|
|
|
@ -154,3 +154,7 @@ typedef freeze_coin = Void Function(Int32 index);
|
||||||
typedef thaw_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 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);
|
||||||
|
|
|
@ -154,3 +154,7 @@ typedef FreezeCoin = void Function(int);
|
||||||
typedef ThawCoin = void Function(int);
|
typedef ThawCoin = void Function(int);
|
||||||
|
|
||||||
typedef SignMessage = Pointer<Utf8> Function(Pointer<Utf8>, Pointer<Utf8>);
|
typedef SignMessage = Pointer<Utf8> Function(Pointer<Utf8>, Pointer<Utf8>);
|
||||||
|
|
||||||
|
typedef GetCacheAttribute = Pointer<Utf8> Function(Pointer<Utf8>);
|
||||||
|
|
||||||
|
typedef SetCacheAttribute = int Function(Pointer<Utf8>, Pointer<Utf8>);
|
||||||
|
|
|
@ -1,26 +1,24 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:ffi';
|
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/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/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';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
int _boolToInt(bool value) => value ? 1 : 0;
|
int _boolToInt(bool value) => value ? 1 : 0;
|
||||||
|
|
||||||
final getFileNameNative = moneroApi
|
final getFileNameNative =
|
||||||
.lookup<NativeFunction<get_filename>>('get_filename')
|
moneroApi.lookup<NativeFunction<get_filename>>('get_filename').asFunction<GetFilename>();
|
||||||
.asFunction<GetFilename>();
|
|
||||||
|
|
||||||
final getSeedNative =
|
final getSeedNative = moneroApi.lookup<NativeFunction<get_seed>>('seed').asFunction<GetSeed>();
|
||||||
moneroApi.lookup<NativeFunction<get_seed>>('seed').asFunction<GetSeed>();
|
|
||||||
|
|
||||||
final getAddressNative = moneroApi
|
final getAddressNative =
|
||||||
.lookup<NativeFunction<get_address>>('get_address')
|
moneroApi.lookup<NativeFunction<get_address>>('get_address').asFunction<GetAddress>();
|
||||||
.asFunction<GetAddress>();
|
|
||||||
|
|
||||||
final getFullBalanceNative = moneroApi
|
final getFullBalanceNative = moneroApi
|
||||||
.lookup<NativeFunction<get_full_balanace>>('get_full_balance')
|
.lookup<NativeFunction<get_full_balanace>>('get_full_balance')
|
||||||
|
@ -38,41 +36,34 @@ final getNodeHeightNative = moneroApi
|
||||||
.lookup<NativeFunction<get_node_height>>('get_node_height')
|
.lookup<NativeFunction<get_node_height>>('get_node_height')
|
||||||
.asFunction<GetNodeHeight>();
|
.asFunction<GetNodeHeight>();
|
||||||
|
|
||||||
final isConnectedNative = moneroApi
|
final isConnectedNative =
|
||||||
.lookup<NativeFunction<is_connected>>('is_connected')
|
moneroApi.lookup<NativeFunction<is_connected>>('is_connected').asFunction<IsConnected>();
|
||||||
.asFunction<IsConnected>();
|
|
||||||
|
|
||||||
final setupNodeNative = moneroApi
|
final setupNodeNative =
|
||||||
.lookup<NativeFunction<setup_node>>('setup_node')
|
moneroApi.lookup<NativeFunction<setup_node>>('setup_node').asFunction<SetupNode>();
|
||||||
.asFunction<SetupNode>();
|
|
||||||
|
|
||||||
final startRefreshNative = moneroApi
|
final startRefreshNative =
|
||||||
.lookup<NativeFunction<start_refresh>>('start_refresh')
|
moneroApi.lookup<NativeFunction<start_refresh>>('start_refresh').asFunction<StartRefresh>();
|
||||||
.asFunction<StartRefresh>();
|
|
||||||
|
|
||||||
final connecToNodeNative = moneroApi
|
final connecToNodeNative = moneroApi
|
||||||
.lookup<NativeFunction<connect_to_node>>('connect_to_node')
|
.lookup<NativeFunction<connect_to_node>>('connect_to_node')
|
||||||
.asFunction<ConnectToNode>();
|
.asFunction<ConnectToNode>();
|
||||||
|
|
||||||
final setRefreshFromBlockHeightNative = moneroApi
|
final setRefreshFromBlockHeightNative = moneroApi
|
||||||
.lookup<NativeFunction<set_refresh_from_block_height>>(
|
.lookup<NativeFunction<set_refresh_from_block_height>>('set_refresh_from_block_height')
|
||||||
'set_refresh_from_block_height')
|
|
||||||
.asFunction<SetRefreshFromBlockHeight>();
|
.asFunction<SetRefreshFromBlockHeight>();
|
||||||
|
|
||||||
final setRecoveringFromSeedNative = moneroApi
|
final setRecoveringFromSeedNative = moneroApi
|
||||||
.lookup<NativeFunction<set_recovering_from_seed>>(
|
.lookup<NativeFunction<set_recovering_from_seed>>('set_recovering_from_seed')
|
||||||
'set_recovering_from_seed')
|
|
||||||
.asFunction<SetRecoveringFromSeed>();
|
.asFunction<SetRecoveringFromSeed>();
|
||||||
|
|
||||||
final storeNative =
|
final storeNative = moneroApi.lookup<NativeFunction<store_c>>('store').asFunction<Store>();
|
||||||
moneroApi.lookup<NativeFunction<store_c>>('store').asFunction<Store>();
|
|
||||||
|
|
||||||
final setPasswordNative =
|
final setPasswordNative =
|
||||||
moneroApi.lookup<NativeFunction<set_password>>('set_password').asFunction<SetPassword>();
|
moneroApi.lookup<NativeFunction<set_password>>('set_password').asFunction<SetPassword>();
|
||||||
|
|
||||||
final setListenerNative = moneroApi
|
final setListenerNative =
|
||||||
.lookup<NativeFunction<set_listener>>('set_listener')
|
moneroApi.lookup<NativeFunction<set_listener>>('set_listener').asFunction<SetListener>();
|
||||||
.asFunction<SetListener>();
|
|
||||||
|
|
||||||
final getSyncingHeightNative = moneroApi
|
final getSyncingHeightNative = moneroApi
|
||||||
.lookup<NativeFunction<get_syncing_height>>('get_syncing_height')
|
.lookup<NativeFunction<get_syncing_height>>('get_syncing_height')
|
||||||
|
@ -83,8 +74,7 @@ final isNeededToRefreshNative = moneroApi
|
||||||
.asFunction<IsNeededToRefresh>();
|
.asFunction<IsNeededToRefresh>();
|
||||||
|
|
||||||
final isNewTransactionExistNative = moneroApi
|
final isNewTransactionExistNative = moneroApi
|
||||||
.lookup<NativeFunction<is_new_transaction_exist>>(
|
.lookup<NativeFunction<is_new_transaction_exist>>('is_new_transaction_exist')
|
||||||
'is_new_transaction_exist')
|
|
||||||
.asFunction<IsNewTransactionExist>();
|
.asFunction<IsNewTransactionExist>();
|
||||||
|
|
||||||
final getSecretViewKeyNative = moneroApi
|
final getSecretViewKeyNative = moneroApi
|
||||||
|
@ -107,9 +97,8 @@ final closeCurrentWalletNative = moneroApi
|
||||||
.lookup<NativeFunction<close_current_wallet>>('close_current_wallet')
|
.lookup<NativeFunction<close_current_wallet>>('close_current_wallet')
|
||||||
.asFunction<CloseCurrentWallet>();
|
.asFunction<CloseCurrentWallet>();
|
||||||
|
|
||||||
final onStartupNative = moneroApi
|
final onStartupNative =
|
||||||
.lookup<NativeFunction<on_startup>>('on_startup')
|
moneroApi.lookup<NativeFunction<on_startup>>('on_startup').asFunction<OnStartup>();
|
||||||
.asFunction<OnStartup>();
|
|
||||||
|
|
||||||
final rescanBlockchainAsyncNative = moneroApi
|
final rescanBlockchainAsyncNative = moneroApi
|
||||||
.lookup<NativeFunction<rescan_blockchain>>('rescan_blockchain')
|
.lookup<NativeFunction<rescan_blockchain>>('rescan_blockchain')
|
||||||
|
@ -123,13 +112,19 @@ final setTrustedDaemonNative = moneroApi
|
||||||
.lookup<NativeFunction<set_trusted_daemon>>('set_trusted_daemon')
|
.lookup<NativeFunction<set_trusted_daemon>>('set_trusted_daemon')
|
||||||
.asFunction<SetTrustedDaemon>();
|
.asFunction<SetTrustedDaemon>();
|
||||||
|
|
||||||
final trustedDaemonNative = moneroApi
|
final trustedDaemonNative =
|
||||||
.lookup<NativeFunction<trusted_daemon>>('trusted_daemon')
|
moneroApi.lookup<NativeFunction<trusted_daemon>>('trusted_daemon').asFunction<TrustedDaemon>();
|
||||||
.asFunction<TrustedDaemon>();
|
|
||||||
|
|
||||||
final signMessageNative = moneroApi
|
final signMessageNative =
|
||||||
.lookup<NativeFunction<sign_message>>('sign_message')
|
moneroApi.lookup<NativeFunction<sign_message>>('sign_message').asFunction<SignMessage>();
|
||||||
.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();
|
int getSyncingHeight() => getSyncingHeightNative();
|
||||||
|
|
||||||
|
@ -144,11 +139,9 @@ String getSeed() => convertUTF8ToString(pointer: getSeedNative());
|
||||||
String getAddress({int accountIndex = 0, int addressIndex = 0}) =>
|
String getAddress({int accountIndex = 0, int addressIndex = 0}) =>
|
||||||
convertUTF8ToString(pointer: getAddressNative(accountIndex, addressIndex));
|
convertUTF8ToString(pointer: getAddressNative(accountIndex, addressIndex));
|
||||||
|
|
||||||
int getFullBalance({int accountIndex = 0}) =>
|
int getFullBalance({int accountIndex = 0}) => getFullBalanceNative(accountIndex);
|
||||||
getFullBalanceNative(accountIndex);
|
|
||||||
|
|
||||||
int getUnlockedBalance({int accountIndex = 0}) =>
|
int getUnlockedBalance({int accountIndex = 0}) => getUnlockedBalanceNative(accountIndex);
|
||||||
getUnlockedBalanceNative(accountIndex);
|
|
||||||
|
|
||||||
int getCurrentHeight() => getCurrentHeightNative();
|
int getCurrentHeight() => getCurrentHeightNative();
|
||||||
|
|
||||||
|
@ -187,7 +180,7 @@ bool setupNodeSync(
|
||||||
passwordPointer,
|
passwordPointer,
|
||||||
_boolToInt(useSSL),
|
_boolToInt(useSSL),
|
||||||
_boolToInt(isLightWallet),
|
_boolToInt(isLightWallet),
|
||||||
socksProxyAddressPointer,
|
socksProxyAddressPointer,
|
||||||
errorMessagePointer) !=
|
errorMessagePointer) !=
|
||||||
0;
|
0;
|
||||||
|
|
||||||
|
@ -202,8 +195,7 @@ bool setupNodeSync(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isSetupNode) {
|
if (!isSetupNode) {
|
||||||
throw SetupWalletException(
|
throw SetupWalletException(message: convertUTF8ToString(pointer: errorMessagePointer));
|
||||||
message: convertUTF8ToString(pointer: errorMessagePointer));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return isSetupNode;
|
return isSetupNode;
|
||||||
|
@ -213,8 +205,7 @@ void startRefreshSync() => startRefreshNative();
|
||||||
|
|
||||||
Future<bool> connectToNode() async => connecToNodeNative() != 0;
|
Future<bool> connectToNode() async => connecToNodeNative() != 0;
|
||||||
|
|
||||||
void setRefreshFromBlockHeight({required int height}) =>
|
void setRefreshFromBlockHeight({required int height}) => setRefreshFromBlockHeightNative(height);
|
||||||
setRefreshFromBlockHeightNative(height);
|
|
||||||
|
|
||||||
void setRecoveringFromSeed({required bool isRecovery}) =>
|
void setRecoveringFromSeed({required bool isRecovery}) =>
|
||||||
setRecoveringFromSeedNative(_boolToInt(isRecovery));
|
setRecoveringFromSeedNative(_boolToInt(isRecovery));
|
||||||
|
@ -230,7 +221,7 @@ void setPasswordSync(String password) {
|
||||||
final errorMessagePointer = calloc<Utf8Box>();
|
final errorMessagePointer = calloc<Utf8Box>();
|
||||||
final changed = setPasswordNative(passwordPointer, errorMessagePointer) != 0;
|
final changed = setPasswordNative(passwordPointer, errorMessagePointer) != 0;
|
||||||
calloc.free(passwordPointer);
|
calloc.free(passwordPointer);
|
||||||
|
|
||||||
if (!changed) {
|
if (!changed) {
|
||||||
final message = errorMessagePointer.ref.getValue();
|
final message = errorMessagePointer.ref.getValue();
|
||||||
calloc.free(errorMessagePointer);
|
calloc.free(errorMessagePointer);
|
||||||
|
@ -242,24 +233,19 @@ void setPasswordSync(String password) {
|
||||||
|
|
||||||
void closeCurrentWallet() => closeCurrentWalletNative();
|
void closeCurrentWallet() => closeCurrentWalletNative();
|
||||||
|
|
||||||
String getSecretViewKey() =>
|
String getSecretViewKey() => convertUTF8ToString(pointer: getSecretViewKeyNative());
|
||||||
convertUTF8ToString(pointer: getSecretViewKeyNative());
|
|
||||||
|
|
||||||
String getPublicViewKey() =>
|
String getPublicViewKey() => convertUTF8ToString(pointer: getPublicViewKeyNative());
|
||||||
convertUTF8ToString(pointer: getPublicViewKeyNative());
|
|
||||||
|
|
||||||
String getSecretSpendKey() =>
|
String getSecretSpendKey() => convertUTF8ToString(pointer: getSecretSpendKeyNative());
|
||||||
convertUTF8ToString(pointer: getSecretSpendKeyNative());
|
|
||||||
|
|
||||||
String getPublicSpendKey() =>
|
String getPublicSpendKey() => convertUTF8ToString(pointer: getPublicSpendKeyNative());
|
||||||
convertUTF8ToString(pointer: getPublicSpendKeyNative());
|
|
||||||
|
|
||||||
class SyncListener {
|
class SyncListener {
|
||||||
SyncListener(this.onNewBlock, this.onNewTransaction)
|
SyncListener(this.onNewBlock, this.onNewTransaction)
|
||||||
: _cachedBlockchainHeight = 0,
|
: _cachedBlockchainHeight = 0,
|
||||||
_lastKnownBlockHeight = 0,
|
_lastKnownBlockHeight = 0,
|
||||||
_initialSyncHeight = 0;
|
_initialSyncHeight = 0;
|
||||||
|
|
||||||
|
|
||||||
void Function(int, int, double) onNewBlock;
|
void Function(int, int, double) onNewBlock;
|
||||||
void Function() onNewTransaction;
|
void Function() onNewTransaction;
|
||||||
|
@ -281,8 +267,7 @@ class SyncListener {
|
||||||
_cachedBlockchainHeight = 0;
|
_cachedBlockchainHeight = 0;
|
||||||
_lastKnownBlockHeight = 0;
|
_lastKnownBlockHeight = 0;
|
||||||
_initialSyncHeight = 0;
|
_initialSyncHeight = 0;
|
||||||
_updateSyncInfoTimer ??=
|
_updateSyncInfoTimer ??= Timer.periodic(Duration(milliseconds: 1200), (_) async {
|
||||||
Timer.periodic(Duration(milliseconds: 1200), (_) async {
|
|
||||||
if (isNewTransactionExist()) {
|
if (isNewTransactionExist()) {
|
||||||
onNewTransaction();
|
onNewTransaction();
|
||||||
}
|
}
|
||||||
|
@ -321,8 +306,8 @@ class SyncListener {
|
||||||
void stop() => _updateSyncInfoTimer?.cancel();
|
void stop() => _updateSyncInfoTimer?.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
SyncListener setListeners(void Function(int, int, double) onNewBlock,
|
SyncListener setListeners(
|
||||||
void Function() onNewTransaction) {
|
void Function(int, int, double) onNewBlock, void Function() onNewTransaction) {
|
||||||
final listener = SyncListener(onNewBlock, onNewTransaction);
|
final listener = SyncListener(onNewBlock, onNewTransaction);
|
||||||
setListenerNative();
|
setListenerNative();
|
||||||
return listener;
|
return listener;
|
||||||
|
@ -364,7 +349,7 @@ Future<void> setupNode(
|
||||||
bool isLightWallet = false}) =>
|
bool isLightWallet = false}) =>
|
||||||
compute<Map<String, Object?>, void>(_setupNodeSync, {
|
compute<Map<String, Object?>, void>(_setupNodeSync, {
|
||||||
'address': address,
|
'address': address,
|
||||||
'login': login ,
|
'login': login,
|
||||||
'password': password,
|
'password': password,
|
||||||
'useSSL': useSSL,
|
'useSSL': useSSL,
|
||||||
'isLightWallet': isLightWallet,
|
'isLightWallet': isLightWallet,
|
||||||
|
@ -397,3 +382,23 @@ String signMessage(String message, {String address = ""}) {
|
||||||
|
|
||||||
return signature;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -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"
|
|
|
@ -19,30 +19,6 @@ class DefaultSPLTokens {
|
||||||
mint: 'usdcsol',
|
mint: 'usdcsol',
|
||||||
enabled: true,
|
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(
|
SPLToken(
|
||||||
name: 'Bonk',
|
name: 'Bonk',
|
||||||
symbol: 'Bonk',
|
symbol: 'Bonk',
|
||||||
|
@ -50,21 +26,7 @@ class DefaultSPLTokens {
|
||||||
decimal: 5,
|
decimal: 5,
|
||||||
mint: 'Bonk',
|
mint: 'Bonk',
|
||||||
iconPath: 'assets/images/bonk_icon.png',
|
iconPath: 'assets/images/bonk_icon.png',
|
||||||
),
|
enabled: true,
|
||||||
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',
|
|
||||||
),
|
),
|
||||||
SPLToken(
|
SPLToken(
|
||||||
name: 'Raydium',
|
name: 'Raydium',
|
||||||
|
@ -73,6 +35,51 @@ class DefaultSPLTokens {
|
||||||
decimal: 6,
|
decimal: 6,
|
||||||
mint: 'ray',
|
mint: 'ray',
|
||||||
iconPath: 'assets/images/ray_icon.png',
|
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(
|
SPLToken(
|
||||||
name: 'GMT',
|
name: 'GMT',
|
||||||
|
@ -81,6 +88,7 @@ class DefaultSPLTokens {
|
||||||
decimal: 6,
|
decimal: 6,
|
||||||
mint: 'ray',
|
mint: 'ray',
|
||||||
iconPath: 'assets/images/gmt_icon.png',
|
iconPath: 'assets/images/gmt_icon.png',
|
||||||
|
enabled: false,
|
||||||
),
|
),
|
||||||
SPLToken(
|
SPLToken(
|
||||||
name: 'AvocadoCoin',
|
name: 'AvocadoCoin',
|
||||||
|
@ -89,6 +97,7 @@ class DefaultSPLTokens {
|
||||||
decimal: 8,
|
decimal: 8,
|
||||||
mint: 'avdo',
|
mint: 'avdo',
|
||||||
iconPath: 'assets/images/avdo_icon.png',
|
iconPath: 'assets/images/avdo_icon.png',
|
||||||
|
enabled: false,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,8 @@ class SolanaBalance extends Balance {
|
||||||
|
|
||||||
String _balanceFormatted() {
|
String _balanceFormatted() {
|
||||||
String stringBalance = balance.toString();
|
String stringBalance = balance.toString();
|
||||||
if (stringBalance.toString().length >= 6) {
|
if (stringBalance.toString().length >= 12) {
|
||||||
stringBalance = stringBalance.substring(0, 6);
|
stringBalance = stringBalance.substring(0, 12);
|
||||||
}
|
}
|
||||||
return stringBalance;
|
return stringBalance;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,14 @@ dependencies:
|
||||||
path: ../cw_core
|
path: ../cw_core
|
||||||
cw_evm:
|
cw_evm:
|
||||||
path: ../cw_evm
|
path: ../cw_evm
|
||||||
on_chain: ^3.0.1
|
on_chain:
|
||||||
blockchain_utils: ^2.1.1
|
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
|
mobx: ^2.3.0+1
|
||||||
bip39: ^1.0.6
|
bip39: ^1.0.6
|
||||||
hive: ^2.2.3
|
hive: ^2.2.3
|
||||||
|
|
|
@ -142,27 +142,9 @@ Then we need to generate localization files.
|
||||||
|
|
||||||
`$ flutter packages pub run tool/generate_localization.dart`
|
`$ 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:
|
Finally build mobx models for the app:
|
||||||
|
|
||||||
`$ flutter packages pub run build_runner build --delete-conflicting-outputs`
|
`$ ./model_generator.sh`
|
||||||
|
|
||||||
### 9. Build!
|
### 9. Build!
|
||||||
|
|
||||||
|
|
|
@ -147,6 +147,8 @@ PODS:
|
||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- sp_scanner (0.0.1):
|
||||||
|
- Flutter
|
||||||
- SwiftProtobuf (1.25.2)
|
- SwiftProtobuf (1.25.2)
|
||||||
- SwiftyGif (5.4.4)
|
- SwiftyGif (5.4.4)
|
||||||
- Toast (4.1.0)
|
- Toast (4.1.0)
|
||||||
|
@ -188,6 +190,7 @@ DEPENDENCIES:
|
||||||
- sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`)
|
- sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- 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`)
|
- uni_links (from `.symlinks/plugins/uni_links/ios`)
|
||||||
- UnstoppableDomainsResolution (~> 4.0.0)
|
- UnstoppableDomainsResolution (~> 4.0.0)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
|
@ -259,6 +262,8 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/share_plus/ios"
|
:path: ".symlinks/plugins/share_plus/ios"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||||
|
sp_scanner:
|
||||||
|
:path: ".symlinks/plugins/sp_scanner/ios"
|
||||||
uni_links:
|
uni_links:
|
||||||
:path: ".symlinks/plugins/uni_links/ios"
|
:path: ".symlinks/plugins/uni_links/ios"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
|
@ -302,6 +307,7 @@ SPEC CHECKSUMS:
|
||||||
sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986
|
sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986
|
||||||
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
|
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
|
||||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||||
|
sp_scanner: eaa617fa827396b967116b7f1f43549ca62e9a12
|
||||||
SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1
|
SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1
|
||||||
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
|
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
|
||||||
Toast: ec33c32b8688982cecc6348adeae667c1b9938da
|
Toast: ec33c32b8688982cecc6348adeae667c1b9938da
|
||||||
|
|
|
@ -121,20 +121,12 @@ class CWBitcoin extends Bitcoin {
|
||||||
priority: priority != null ? priority as BitcoinTransactionPriority : null,
|
priority: priority != null ? priority as BitcoinTransactionPriority : null,
|
||||||
feeRate: feeRate);
|
feeRate: feeRate);
|
||||||
|
|
||||||
@override
|
|
||||||
List<String> getAddresses(Object wallet) {
|
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
|
||||||
return bitcoinWallet.walletAddresses.addressesByReceiveType
|
|
||||||
.map((BitcoinAddressRecord addr) => addr.address)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@computed
|
@computed
|
||||||
List<ElectrumSubAddress> getSubAddresses(Object wallet) {
|
List<ElectrumSubAddress> getSubAddresses(Object wallet) {
|
||||||
final electrumWallet = wallet as ElectrumWallet;
|
final electrumWallet = wallet as ElectrumWallet;
|
||||||
return electrumWallet.walletAddresses.addressesByReceiveType
|
return electrumWallet.walletAddresses.addressesByReceiveType
|
||||||
.map((BitcoinAddressRecord addr) => ElectrumSubAddress(
|
.map((BaseBitcoinAddressRecord addr) => ElectrumSubAddress(
|
||||||
id: addr.index,
|
id: addr.index,
|
||||||
name: addr.name,
|
name: addr.name,
|
||||||
address: addr.address,
|
address: addr.address,
|
||||||
|
@ -207,12 +199,12 @@ class CWBitcoin extends Bitcoin {
|
||||||
|
|
||||||
Future<void> updateUnspents(Object wallet) async {
|
Future<void> updateUnspents(Object wallet) async {
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
final bitcoinWallet = wallet as ElectrumWallet;
|
||||||
await bitcoinWallet.updateUnspent();
|
await bitcoinWallet.updateAllUnspents();
|
||||||
}
|
}
|
||||||
|
|
||||||
WalletService createBitcoinWalletService(
|
WalletService createBitcoinWalletService(
|
||||||
Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource) {
|
Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource, bool alwaysScan) {
|
||||||
return BitcoinWalletService(walletInfoSource, unspentCoinSource);
|
return BitcoinWalletService(walletInfoSource, unspentCoinSource, alwaysScan);
|
||||||
}
|
}
|
||||||
|
|
||||||
WalletService createLitecoinWalletService(
|
WalletService createLitecoinWalletService(
|
||||||
|
@ -247,6 +239,12 @@ class CWBitcoin extends Bitcoin {
|
||||||
return BitcoinReceivePageOption.fromType(bitcoinWallet.walletAddresses.addressPageType);
|
return BitcoinReceivePageOption.fromType(bitcoinWallet.walletAddresses.addressPageType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool hasSelectedSilentPayments(Object wallet) {
|
||||||
|
final bitcoinWallet = wallet as ElectrumWallet;
|
||||||
|
return bitcoinWallet.walletAddresses.addressPageType == SilentPaymentsAddresType.p2sp;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<ReceivePageOption> getBitcoinReceivePageOptions() => BitcoinReceivePageOption.all;
|
List<ReceivePageOption> getBitcoinReceivePageOptions() => BitcoinReceivePageOption.all;
|
||||||
|
|
||||||
|
@ -465,4 +463,137 @@ class CWBitcoin extends Bitcoin {
|
||||||
throw err;
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ class AddressValidator extends TextValidator {
|
||||||
return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$'
|
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}\$';
|
'|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$';
|
||||||
case CryptoCurrency.btc:
|
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:
|
case CryptoCurrency.nano:
|
||||||
return '[0-9a-zA-Z_]';
|
return '[0-9a-zA-Z_]';
|
||||||
case CryptoCurrency.banano:
|
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]|^)([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]{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)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:
|
case CryptoCurrency.ltc:
|
||||||
return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)'
|
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]|\$)'
|
'|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)'
|
||||||
|
|
|
@ -3,7 +3,13 @@ import 'package:cw_core/sync_status.dart';
|
||||||
|
|
||||||
String syncStatusTitle(SyncStatus syncStatus) {
|
String syncStatusTitle(SyncStatus syncStatus) {
|
||||||
if (syncStatus is SyncingSyncStatus) {
|
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) {
|
if (syncStatus is SyncedSyncStatus) {
|
||||||
|
@ -34,5 +40,17 @@ String syncStatusTitle(SyncStatus syncStatus) {
|
||||||
return S.current.sync_status_failed_connect;
|
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 '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ class WalletCreationService {
|
||||||
checkIfExists(credentials.name);
|
checkIfExists(credentials.name);
|
||||||
final password = generateWalletPassword();
|
final password = generateWalletPassword();
|
||||||
credentials.password = password;
|
credentials.password = password;
|
||||||
if (type == WalletType.bitcoinCash || type == WalletType.ethereum) {
|
if (_hasSeedPhraseLengthOption) {
|
||||||
credentials.seedPhraseLength = settingsStore.seedPhraseLength.value;
|
credentials.seedPhraseLength = settingsStore.seedPhraseLength.value;
|
||||||
}
|
}
|
||||||
await keyService.saveWalletPassword(password: password, walletName: credentials.name);
|
await keyService.saveWalletPassword(password: password, walletName: credentials.name);
|
||||||
|
@ -72,6 +72,25 @@ class WalletCreationService {
|
||||||
return wallet;
|
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 {
|
Future<WalletBase> restoreFromKeys(WalletCredentials credentials, {bool? isTestnet}) async {
|
||||||
checkIfExists(credentials.name);
|
checkIfExists(credentials.name);
|
||||||
final password = generateWalletPassword();
|
final password = generateWalletPassword();
|
||||||
|
|
39
lib/di.dart
39
lib/di.dart
|
@ -6,6 +6,7 @@ import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
|
||||||
import 'package:cake_wallet/anypay/anypay_api.dart';
|
import 'package:cake_wallet/anypay/anypay_api.dart';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||||
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
import 'package:cake_wallet/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/moonpay/moonpay_provider.dart';
|
||||||
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
|
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
|
||||||
import 'package:cake_wallet/buy/order.dart';
|
import 'package:cake_wallet/buy/order.dart';
|
||||||
|
@ -14,6 +15,8 @@ import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
|
||||||
import 'package:cake_wallet/core/auth_service.dart';
|
import 'package:cake_wallet/core/auth_service.dart';
|
||||||
import 'package:cake_wallet/core/backup_service.dart';
|
import 'package:cake_wallet/core/backup_service.dart';
|
||||||
import 'package:cake_wallet/core/key_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/wallet_connect_key_service.dart';
|
||||||
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
||||||
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
|
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
|
||||||
|
@ -26,10 +29,6 @@ import 'package:cake_wallet/entities/contact.dart';
|
||||||
import 'package:cake_wallet/entities/contact_record.dart';
|
import 'package:cake_wallet/entities/contact_record.dart';
|
||||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||||
import 'package:cake_wallet/entities/parse_address_from_domain.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';
|
|
||||||
import 'package:cw_core/receive_page_option.dart';
|
|
||||||
import 'package:cake_wallet/entities/qr_view_data.dart';
|
import 'package:cake_wallet/entities/qr_view_data.dart';
|
||||||
import 'package:cake_wallet/entities/template.dart';
|
import 'package:cake_wallet/entities/template.dart';
|
||||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||||
|
@ -105,12 +104,14 @@ 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_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/send/send_template_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/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/display_settings_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/settings/domain_lookups_page.dart';
|
import 'package:cake_wallet/src/screens/settings/domain_lookups_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
|
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/other_settings_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/settings/privacy_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/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/tor_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/settings/trocador_providers_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';
|
import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
|
||||||
|
@ -124,6 +125,7 @@ import 'package:cake_wallet/src/screens/support/support_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/support_chat/support_chat_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/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/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/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_details_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
|
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
|
||||||
|
@ -147,6 +149,7 @@ import 'package:cake_wallet/store/templates/send_template_store.dart';
|
||||||
import 'package:cake_wallet/store/wallet_list_store.dart';
|
import 'package:cake_wallet/store/wallet_list_store.dart';
|
||||||
import 'package:cake_wallet/store/yat/yat_store.dart';
|
import 'package:cake_wallet/store/yat/yat_store.dart';
|
||||||
import 'package:cake_wallet/themes/theme_list.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/utils/device_info.dart';
|
||||||
import 'package:cake_wallet/utils/payment_request.dart';
|
import 'package:cake_wallet/utils/payment_request.dart';
|
||||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||||
|
@ -160,10 +163,10 @@ 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_list_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/contact_list/contact_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/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/dashboard_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
|
import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/home_settings_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/nft_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/receive_option_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';
|
import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart';
|
||||||
|
@ -179,6 +182,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_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_payment_status_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_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/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_edit_or_create_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
|
import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
|
||||||
|
@ -199,6 +203,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/other_settings_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/settings/privacy_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/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/settings/trocador_providers_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart';
|
import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/support_view_model.dart';
|
import 'package:cake_wallet/view_model/support_view_model.dart';
|
||||||
|
@ -222,6 +227,7 @@ import 'package:cake_wallet/view_model/wallet_seed_view_model.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_core/nano_account.dart';
|
import 'package:cw_core/nano_account.dart';
|
||||||
import 'package:cw_core/node.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/transaction_info.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
@ -233,11 +239,6 @@ import 'package:get_it/get_it.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:cake_wallet/core/secure_storage.dart';
|
|
||||||
|
|
||||||
import 'buy/dfx/dfx_buy_provider.dart';
|
|
||||||
import 'core/totp_request_details.dart';
|
|
||||||
import 'src/screens/settings/desktop_settings/desktop_settings_page.dart';
|
|
||||||
|
|
||||||
final getIt = GetIt.instance;
|
final getIt = GetIt.instance;
|
||||||
|
|
||||||
|
@ -745,6 +746,9 @@ Future<void> setup({
|
||||||
return DisplaySettingsViewModel(getIt.get<SettingsStore>());
|
return DisplaySettingsViewModel(getIt.get<SettingsStore>());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
getIt.registerFactory(() =>
|
||||||
|
SilentPaymentsSettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!));
|
||||||
|
|
||||||
getIt.registerFactory(() {
|
getIt.registerFactory(() {
|
||||||
return PrivacySettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!);
|
return PrivacySettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!);
|
||||||
});
|
});
|
||||||
|
@ -795,8 +799,8 @@ Future<void> setup({
|
||||||
|
|
||||||
getIt.registerFactory(() => ConnectionSyncPage(getIt.get<DashboardViewModel>()));
|
getIt.registerFactory(() => ConnectionSyncPage(getIt.get<DashboardViewModel>()));
|
||||||
|
|
||||||
getIt.registerFactory(
|
getIt.registerFactory(() => SecurityBackupPage(getIt.get<SecuritySettingsViewModel>(),
|
||||||
() => SecurityBackupPage(getIt.get<SecuritySettingsViewModel>(), getIt.get<AuthService>()));
|
getIt.get<AuthService>(), getIt.get<AppStore>().wallet!.isHardwareWallet));
|
||||||
|
|
||||||
getIt.registerFactory(() => PrivacyPage(getIt.get<PrivacySettingsViewModel>()));
|
getIt.registerFactory(() => PrivacyPage(getIt.get<PrivacySettingsViewModel>()));
|
||||||
|
|
||||||
|
@ -806,6 +810,9 @@ Future<void> setup({
|
||||||
|
|
||||||
getIt.registerFactory(() => DisplaySettingsPage(getIt.get<DisplaySettingsViewModel>()));
|
getIt.registerFactory(() => DisplaySettingsPage(getIt.get<DisplaySettingsViewModel>()));
|
||||||
|
|
||||||
|
getIt.registerFactory(
|
||||||
|
() => SilentPaymentsSettingsPage(getIt.get<SilentPaymentsSettingsViewModel>()));
|
||||||
|
|
||||||
getIt.registerFactory(() => OtherSettingsPage(getIt.get<OtherSettingsViewModel>()));
|
getIt.registerFactory(() => OtherSettingsPage(getIt.get<OtherSettingsViewModel>()));
|
||||||
|
|
||||||
getIt.registerFactory(() => NanoChangeRepPage(
|
getIt.registerFactory(() => NanoChangeRepPage(
|
||||||
|
@ -893,7 +900,11 @@ Future<void> setup({
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
return monero!.createMoneroWalletService(_walletInfoSource, _unspentCoinsInfoSource);
|
return monero!.createMoneroWalletService(_walletInfoSource, _unspentCoinsInfoSource);
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
return bitcoin!.createBitcoinWalletService(_walletInfoSource, _unspentCoinsInfoSource);
|
return bitcoin!.createBitcoinWalletService(
|
||||||
|
_walletInfoSource,
|
||||||
|
_unspentCoinsInfoSource,
|
||||||
|
getIt.get<SettingsStore>().silentPaymentsAlwaysScan,
|
||||||
|
);
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource);
|
return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource);
|
||||||
case WalletType.ethereum:
|
case WalletType.ethereum:
|
||||||
|
@ -1089,7 +1100,7 @@ Future<void> setup({
|
||||||
|
|
||||||
getIt.registerFactory(() => IoniaGiftCardsListViewModel(ioniaService: getIt.get<IoniaService>()));
|
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>()));
|
getIt.registerFactory(() => IoniaAuthViewModel(ioniaService: getIt.get<IoniaService>()));
|
||||||
|
|
||||||
|
|
|
@ -24,8 +24,9 @@ import 'package:collection/collection.dart';
|
||||||
|
|
||||||
const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081';
|
const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081';
|
||||||
const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
|
const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
|
||||||
const publicBitcoinTestnetElectrumAddress = 'electrum.blockstream.info';
|
const cakeWalletSilentPaymentsElectrsUri = 'electrs.cakewallet.com:50001';
|
||||||
const publicBitcoinTestnetElectrumPort = '60002';
|
const publicBitcoinTestnetElectrumAddress = 'electrs.cakewallet.com';
|
||||||
|
const publicBitcoinTestnetElectrumPort = '50002';
|
||||||
const publicBitcoinTestnetElectrumUri =
|
const publicBitcoinTestnetElectrumUri =
|
||||||
'$publicBitcoinTestnetElectrumAddress:$publicBitcoinTestnetElectrumPort';
|
'$publicBitcoinTestnetElectrumAddress:$publicBitcoinTestnetElectrumPort';
|
||||||
const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
|
const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
|
||||||
|
@ -224,6 +225,9 @@ Future<void> defaultSettingsMigration(
|
||||||
await addTronNodeList(nodes: nodes);
|
await addTronNodeList(nodes: nodes);
|
||||||
await changeTronCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
|
await changeTronCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
|
||||||
break;
|
break;
|
||||||
|
case 34:
|
||||||
|
await _addElectRsNode(nodes, sharedPreferences);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -790,7 +794,8 @@ Future<void> changeDefaultBitcoinNode(
|
||||||
final needToReplaceCurrentBitcoinNode =
|
final needToReplaceCurrentBitcoinNode =
|
||||||
currentBitcoinNode.uri.toString().contains(cakeWalletBitcoinNodeUriPattern);
|
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);
|
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(
|
Future<void> checkCurrentNodes(
|
||||||
Box<Node> nodeSource, Box<Node> powNodeSource, SharedPreferences sharedPreferences) async {
|
Box<Node> nodeSource, Box<Node> powNodeSource, SharedPreferences sharedPreferences) async {
|
||||||
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
||||||
|
@ -845,14 +870,19 @@ Future<void> checkCurrentNodes(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentBitcoinElectrumServer == null) {
|
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);
|
await nodeSource.add(cakeWalletElectrum);
|
||||||
|
final cakeWalletElectrumTestnet =
|
||||||
|
Node(uri: publicBitcoinTestnetElectrumUri, type: WalletType.bitcoin, useSSL: false);
|
||||||
|
await nodeSource.add(cakeWalletElectrumTestnet);
|
||||||
await sharedPreferences.setInt(
|
await sharedPreferences.setInt(
|
||||||
PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletElectrum.key as int);
|
PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletElectrum.key as int);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentLitecoinElectrumServer == null) {
|
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 nodeSource.add(cakeWalletElectrum);
|
||||||
await sharedPreferences.setInt(
|
await sharedPreferences.setInt(
|
||||||
PreferencesKey.currentLitecoinElectrumSererIdKey, cakeWalletElectrum.key as int);
|
PreferencesKey.currentLitecoinElectrumSererIdKey, cakeWalletElectrum.key as int);
|
||||||
|
@ -887,7 +917,8 @@ Future<void> checkCurrentNodes(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentBitcoinCashNodeServer == null) {
|
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 nodeSource.add(node);
|
||||||
await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int);
|
await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int);
|
||||||
}
|
}
|
||||||
|
@ -921,7 +952,11 @@ Future<void> resetBitcoinElectrumServer(
|
||||||
.firstWhereOrNull((node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri);
|
.firstWhereOrNull((node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri);
|
||||||
|
|
||||||
if (cakeWalletNode == null) {
|
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);
|
await nodeSource.add(cakeWalletNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,8 @@ class PreferencesKey {
|
||||||
static const polygonTransactionPriority = 'current_fee_priority_polygon';
|
static const polygonTransactionPriority = 'current_fee_priority_polygon';
|
||||||
static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash';
|
static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash';
|
||||||
static const customBitcoinFeeRate = 'custom_electrum_fee_rate';
|
static const customBitcoinFeeRate = 'custom_electrum_fee_rate';
|
||||||
|
static const silentPaymentsCardDisplay = 'silentPaymentsCardDisplay';
|
||||||
|
static const silentPaymentsAlwaysScan = 'silentPaymentsAlwaysScan';
|
||||||
static const shouldShowReceiveWarning = 'should_show_receive_warning';
|
static const shouldShowReceiveWarning = 'should_show_receive_warning';
|
||||||
static const shouldShowYatPopup = 'should_show_yat_popup';
|
static const shouldShowYatPopup = 'should_show_yat_popup';
|
||||||
static const shouldShowRepWarning = 'should_show_rep_warning';
|
static const shouldShowRepWarning = 'should_show_rep_warning';
|
||||||
|
|
|
@ -202,7 +202,7 @@ Future<void> initializeAppConfigs() async {
|
||||||
transactionDescriptions: transactionDescriptions,
|
transactionDescriptions: transactionDescriptions,
|
||||||
secureStorage: secureStorage,
|
secureStorage: secureStorage,
|
||||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||||
initialMigrationVersion: 33,
|
initialMigrationVersion: 34,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,15 +3,19 @@ import 'dart:async';
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/sync_status.dart';
|
import 'package:cw_core/sync_status.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
|
||||||
Timer? _checkConnectionTimer;
|
Timer? _checkConnectionTimer;
|
||||||
|
|
||||||
void startCheckConnectionReaction(
|
void startCheckConnectionReaction(WalletBase wallet, SettingsStore settingsStore,
|
||||||
WalletBase wallet, SettingsStore settingsStore,
|
|
||||||
{int timeInterval = 5}) {
|
{int timeInterval = 5}) {
|
||||||
_checkConnectionTimer?.cancel();
|
_checkConnectionTimer?.cancel();
|
||||||
_checkConnectionTimer =
|
_checkConnectionTimer = Timer.periodic(Duration(seconds: timeInterval), (_) async {
|
||||||
Timer.periodic(Duration(seconds: timeInterval), (_) async {
|
if (wallet.type == WalletType.bitcoin && wallet.syncStatus is SyncingSyncStatus) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final connectivityResult = await (Connectivity().checkConnectivity());
|
final connectivityResult = await (Connectivity().checkConnectivity());
|
||||||
|
|
||||||
|
@ -20,14 +24,11 @@ void startCheckConnectionReaction(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wallet.syncStatus is LostConnectionSyncStatus ||
|
if (wallet.syncStatus is LostConnectionSyncStatus || wallet.syncStatus is FailedSyncStatus) {
|
||||||
wallet.syncStatus is FailedSyncStatus) {
|
final alive = await settingsStore.getCurrentNode(wallet.type).requestNode();
|
||||||
final alive =
|
|
||||||
await settingsStore.getCurrentNode(wallet.type).requestNode();
|
|
||||||
|
|
||||||
if (alive) {
|
if (alive) {
|
||||||
await wallet.connectToNode(
|
await wallet.connectToNode(node: settingsStore.getCurrentNode(wallet.type));
|
||||||
node: settingsStore.getCurrentNode(wallet.type));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -12,12 +12,10 @@ import 'package:wakelock_plus/wakelock_plus.dart';
|
||||||
ReactionDisposer? _onWalletSyncStatusChangeReaction;
|
ReactionDisposer? _onWalletSyncStatusChangeReaction;
|
||||||
|
|
||||||
void startWalletSyncStatusChangeReaction(
|
void startWalletSyncStatusChangeReaction(
|
||||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
|
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet,
|
||||||
TransactionInfo> wallet,
|
|
||||||
FiatConversionStore fiatConversionStore) {
|
FiatConversionStore fiatConversionStore) {
|
||||||
_onWalletSyncStatusChangeReaction?.reaction.dispose();
|
_onWalletSyncStatusChangeReaction?.reaction.dispose();
|
||||||
_onWalletSyncStatusChangeReaction =
|
_onWalletSyncStatusChangeReaction = reaction((_) => wallet.syncStatus, (SyncStatus status) async {
|
||||||
reaction((_) => wallet.syncStatus, (SyncStatus status) async {
|
|
||||||
try {
|
try {
|
||||||
if (status is ConnectedSyncStatus) {
|
if (status is ConnectedSyncStatus) {
|
||||||
await wallet.startSync();
|
await wallet.startSync();
|
||||||
|
@ -32,7 +30,7 @@ void startWalletSyncStatusChangeReaction(
|
||||||
if (status is SyncedSyncStatus || status is FailedSyncStatus) {
|
if (status is SyncedSyncStatus || status is FailedSyncStatus) {
|
||||||
await WakelockPlus.disable();
|
await WakelockPlus.disable();
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
print(e.toString());
|
print(e.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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/other_settings_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/settings/privacy_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/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/tor_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/settings/trocador_providers_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';
|
import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
|
||||||
|
@ -366,6 +367,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||||
param1: settings.arguments as OnAuthenticationFinished, param2: false),
|
param1: settings.arguments as OnAuthenticationFinished, param2: false),
|
||||||
onWillPop: () async => false));
|
onWillPop: () async => false));
|
||||||
|
|
||||||
|
case Routes.silentPaymentsSettings:
|
||||||
|
return CupertinoPageRoute<void>(
|
||||||
|
fullscreenDialog: true, builder: (_) => getIt.get<SilentPaymentsSettingsPage>());
|
||||||
|
|
||||||
case Routes.connectionSync:
|
case Routes.connectionSync:
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
fullscreenDialog: true, builder: (_) => getIt.get<ConnectionSyncPage>());
|
fullscreenDialog: true, builder: (_) => getIt.get<ConnectionSyncPage>());
|
||||||
|
|
|
@ -81,6 +81,7 @@ class Routes {
|
||||||
static const ioniaMoreOptionsPage = '/ionia_more_options_page';
|
static const ioniaMoreOptionsPage = '/ionia_more_options_page';
|
||||||
static const ioniaCustomRedeemPage = '/ionia_custom_redeem_page';
|
static const ioniaCustomRedeemPage = '/ionia_custom_redeem_page';
|
||||||
static const webViewPage = '/web_view_page';
|
static const webViewPage = '/web_view_page';
|
||||||
|
static const silentPaymentsSettings = '/silent_payments_settings';
|
||||||
static const connectionSync = '/connection_sync_page';
|
static const connectionSync = '/connection_sync_page';
|
||||||
static const securityBackupPage = '/security_and_backup_page';
|
static const securityBackupPage = '/security_and_backup_page';
|
||||||
static const privacyPage = '/privacy_page';
|
static const privacyPage = '/privacy_page';
|
||||||
|
|
|
@ -46,6 +46,7 @@ class ContactPage extends BasePage {
|
||||||
final TextEditingController _nameController;
|
final TextEditingController _nameController;
|
||||||
final TextEditingController _currencyTypeController;
|
final TextEditingController _currencyTypeController;
|
||||||
final TextEditingController _addressController;
|
final TextEditingController _addressController;
|
||||||
|
bool _isEffectsApplied = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) {
|
Widget body(BuildContext context) {
|
||||||
|
@ -53,15 +54,7 @@ class ContactPage extends BasePage {
|
||||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||||
height: 8);
|
height: 8);
|
||||||
|
|
||||||
reaction((_) => contactViewModel.state, (ExecutionState state) {
|
_setEffects(context);
|
||||||
if (state is FailureState) {
|
|
||||||
_onContactSavingFailure(context, state.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state is ExecutedSuccessfullyState) {
|
|
||||||
_onContactSavedSuccessfully(context);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return Observer(
|
return Observer(
|
||||||
builder: (_) => ScrollableWithBottomSection(
|
builder: (_) => ScrollableWithBottomSection(
|
||||||
|
@ -177,4 +170,22 @@ class ContactPage extends BasePage {
|
||||||
|
|
||||||
void _onContactSavedSuccessfully(BuildContext context) =>
|
void _onContactSavedSuccessfully(BuildContext context) =>
|
||||||
Navigator.of(context).pop();
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'package:cake_wallet/entities/preferences_key.dart';
|
||||||
import 'package:cake_wallet/di.dart';
|
import 'package:cake_wallet/di.dart';
|
||||||
import 'package:cake_wallet/entities/main_actions.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/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/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart';
|
||||||
import 'package:cake_wallet/src/widgets/gradient_background.dart';
|
import 'package:cake_wallet/src/widgets/gradient_background.dart';
|
||||||
import 'package:cake_wallet/src/widgets/services_updates_widget.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/themes/extensions/sync_indicator_theme.dart';
|
||||||
import 'package:cake_wallet/utils/device_info.dart';
|
import 'package:cake_wallet/utils/device_info.dart';
|
||||||
import 'package:cake_wallet/utils/version_comparator.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/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
import 'package:cake_wallet/src/screens/yat_emoji_id.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/dashboard_page_theme.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
||||||
|
|
||||||
class DashboardPage extends StatelessWidget {
|
class DashboardPage extends StatefulWidget {
|
||||||
DashboardPage({
|
DashboardPage({
|
||||||
required this.bottomSheetService,
|
required this.bottomSheetService,
|
||||||
required this.balancePage,
|
required this.balancePage,
|
||||||
|
@ -50,43 +50,71 @@ class DashboardPage extends StatelessWidget {
|
||||||
final WalletAddressListViewModel addressListViewModel;
|
final WalletAddressListViewModel addressListViewModel;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
State<DashboardPage> createState() => _DashboardPageState();
|
||||||
final screenHeight = MediaQuery.of(context).size.height;
|
}
|
||||||
return Scaffold(
|
|
||||||
body: Builder(
|
|
||||||
builder: (_) {
|
|
||||||
final dashboardPageView = RefreshIndicator(
|
|
||||||
displacement: screenHeight * 0.1,
|
|
||||||
onRefresh: () async => await dashboardViewModel.refreshDashboard(),
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
physics: AlwaysScrollableScrollPhysics(),
|
|
||||||
child: Container(
|
|
||||||
height: screenHeight,
|
|
||||||
child: _DashboardPageView(
|
|
||||||
balancePage: balancePage,
|
|
||||||
bottomSheetService: bottomSheetService,
|
|
||||||
dashboardViewModel: dashboardViewModel,
|
|
||||||
addressListViewModel: addressListViewModel,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (DeviceInfo.instance.isDesktop) {
|
class _DashboardPageState extends State<DashboardPage> {
|
||||||
if (responsiveLayoutUtil.screenWidth >
|
@override
|
||||||
ResponsiveLayoutUtilBase.kDesktopMaxDashBoardWidthConstraint) {
|
void initState() {
|
||||||
return getIt.get<DesktopSidebarWrapper>();
|
super.initState();
|
||||||
} else {
|
|
||||||
return dashboardPageView;
|
bool isMobileLayout =
|
||||||
}
|
responsiveLayoutUtil.screenWidth < ResponsiveLayoutUtilBase.kMobileThreshold;
|
||||||
} else if (responsiveLayoutUtil.shouldRenderMobileUI) {
|
|
||||||
return dashboardPageView;
|
reaction((_) => responsiveLayoutUtil.screenWidth, (screenWidth) {
|
||||||
} else {
|
// Check if it was previously in mobile layout, and now changing to desktop
|
||||||
return getIt.get<DesktopSidebarWrapper>();
|
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) {
|
||||||
|
Widget dashboardChild;
|
||||||
|
|
||||||
|
final dashboardPageView = RefreshIndicator(
|
||||||
|
displacement: responsiveLayoutUtil.screenHeight * 0.1,
|
||||||
|
onRefresh: () async => await widget.dashboardViewModel.refreshDashboard(),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: AlwaysScrollableScrollPhysics(),
|
||||||
|
child: Container(
|
||||||
|
height: responsiveLayoutUtil.screenHeight,
|
||||||
|
child: _DashboardPageView(
|
||||||
|
balancePage: widget.balancePage,
|
||||||
|
bottomSheetService: widget.bottomSheetService,
|
||||||
|
dashboardViewModel: widget.dashboardViewModel,
|
||||||
|
addressListViewModel: widget.addressListViewModel,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (DeviceInfo.instance.isDesktop) {
|
||||||
|
if (responsiveLayoutUtil.screenWidth >
|
||||||
|
ResponsiveLayoutUtilBase.kDesktopMaxDashBoardWidthConstraint) {
|
||||||
|
dashboardChild = getIt.get<DesktopSidebarWrapper>();
|
||||||
|
} else {
|
||||||
|
dashboardChild = dashboardPageView;
|
||||||
|
}
|
||||||
|
} else if (responsiveLayoutUtil.shouldRenderMobileUI) {
|
||||||
|
dashboardChild = dashboardPageView;
|
||||||
|
} else {
|
||||||
|
dashboardChild = getIt.get<DesktopSidebarWrapper>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(body: dashboardChild);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,10 +330,10 @@ class _DashboardPageView extends BasePage {
|
||||||
if (dashboardViewModel.shouldShowMarketPlaceInDashboard) {
|
if (dashboardViewModel.shouldShowMarketPlaceInDashboard) {
|
||||||
pages.add(
|
pages.add(
|
||||||
Semantics(
|
Semantics(
|
||||||
label: S.of(context).market_place,
|
label: 'Cake ${S.of(context).features}',
|
||||||
child: MarketPlacePage(
|
child: CakeFeaturesPage(
|
||||||
dashboardViewModel: dashboardViewModel,
|
dashboardViewModel: dashboardViewModel,
|
||||||
marketPlaceViewModel: getIt.get<MarketPlaceViewModel>(),
|
cakeFeaturesViewModel: getIt.get<CakeFeaturesViewModel>(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import 'package:cake_wallet/di.dart';
|
import 'package:cake_wallet/di.dart';
|
||||||
import 'package:cake_wallet/entities/main_actions.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/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/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/material.dart';
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
|
|
||||||
|
@ -74,9 +74,9 @@ class DesktopDashboardActions extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: MarketPlacePage(
|
child: CakeFeaturesPage(
|
||||||
dashboardViewModel: dashboardViewModel,
|
dashboardViewModel: dashboardViewModel,
|
||||||
marketPlaceViewModel: getIt.get<MarketPlaceViewModel>(),
|
cakeFeaturesViewModel: getIt.get<CakeFeaturesViewModel>(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -30,6 +30,7 @@ class DesktopWalletSelectionDropDown extends StatefulWidget {
|
||||||
class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionDropDown> {
|
class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionDropDown> {
|
||||||
final moneroIcon = Image.asset('assets/images/monero_logo.png', height: 24, width: 24);
|
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 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 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 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);
|
final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
|
||||||
|
@ -68,8 +69,11 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: BoxConstraints(maxWidth: 500),
|
constraints: BoxConstraints(maxWidth: 500),
|
||||||
child: DropDownItemWidget(
|
child: DropDownItemWidget(
|
||||||
title: wallet.name,
|
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),
|
onSelected: () => _onSelectedWallet(wallet),
|
||||||
))
|
))
|
||||||
|
@ -120,16 +124,16 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
final confirmed = await showPopUp<bool>(
|
final confirmed = await showPopUp<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (dialogContext) {
|
builder: (dialogContext) {
|
||||||
return AlertWithTwoActions(
|
return AlertWithTwoActions(
|
||||||
alertTitle: S.of(context).change_wallet_alert_title,
|
alertTitle: S.of(context).change_wallet_alert_title,
|
||||||
alertContent: S.of(context).change_wallet_alert_content(selectedWallet.name),
|
alertContent: S.of(context).change_wallet_alert_content(selectedWallet.name),
|
||||||
leftButtonText: S.of(context).cancel,
|
leftButtonText: S.of(context).cancel,
|
||||||
rightButtonText: S.of(context).change,
|
rightButtonText: S.of(context).change,
|
||||||
actionLeftButton: () => Navigator.of(dialogContext).pop(false),
|
actionLeftButton: () => Navigator.of(dialogContext).pop(false),
|
||||||
actionRightButton: () => Navigator.of(dialogContext).pop(true));
|
actionRightButton: () => Navigator.of(dialogContext).pop(true));
|
||||||
}) ??
|
}) ??
|
||||||
false;
|
false;
|
||||||
|
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
|
@ -138,9 +142,12 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Image _imageFor({required WalletType type}) {
|
Image _imageFor({required WalletType type, bool? isTestnet}) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
|
if (isTestnet == true) {
|
||||||
|
return tBitcoinIcon;
|
||||||
|
}
|
||||||
return bitcoinIcon;
|
return bitcoinIcon;
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
return moneroIcon;
|
return moneroIcon;
|
||||||
|
@ -160,7 +167,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||||
return polygonIcon;
|
return polygonIcon;
|
||||||
case WalletType.solana:
|
case WalletType.solana:
|
||||||
return solanaIcon;
|
return solanaIcon;
|
||||||
case WalletType.tron:
|
case WalletType.tron:
|
||||||
return tronIcon;
|
return tronIcon;
|
||||||
default:
|
default:
|
||||||
return nonWalletTypeIcon;
|
return nonWalletTypeIcon;
|
||||||
|
@ -168,24 +175,25 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadWallet(WalletListItem wallet) async {
|
Future<void> _loadWallet(WalletListItem wallet) async {
|
||||||
widget._authService.authenticateAction(context,
|
widget._authService.authenticateAction(
|
||||||
onAuthSuccess: (isAuthenticatedSuccessfully) async {
|
context,
|
||||||
if (!isAuthenticatedSuccessfully) {
|
onAuthSuccess: (isAuthenticatedSuccessfully) async {
|
||||||
return;
|
if (!isAuthenticatedSuccessfully) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
|
changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
|
||||||
|
}
|
||||||
|
await widget.walletListViewModel.loadWallet(wallet);
|
||||||
|
hideProgressText();
|
||||||
|
setState(() {});
|
||||||
|
} catch (e) {
|
||||||
|
if (context.mounted) {
|
||||||
|
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await widget.walletListViewModel.loadWallet(wallet);
|
|
||||||
hideProgressText();
|
|
||||||
setState(() {});
|
|
||||||
} catch (e) {
|
|
||||||
if (context.mounted) {
|
|
||||||
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
conditionToDetermineIfToUse2FA:
|
conditionToDetermineIfToUse2FA:
|
||||||
widget.walletListViewModel.shouldRequireTOTP2FAForAccessingWallet,
|
widget.walletListViewModel.shouldRequireTOTP2FAForAccessingWallet,
|
||||||
|
@ -198,17 +206,16 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||||
context,
|
context,
|
||||||
route: Routes.newWallet,
|
route: Routes.newWallet,
|
||||||
arguments: widget.walletListViewModel.currentWalletType,
|
arguments: widget.walletListViewModel.currentWalletType,
|
||||||
conditionToDetermineIfToUse2FA: widget
|
conditionToDetermineIfToUse2FA:
|
||||||
.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
widget._authService.authenticateAction(
|
widget._authService.authenticateAction(
|
||||||
context,
|
context,
|
||||||
route: Routes.newWalletType,
|
route: Routes.newWalletType,
|
||||||
conditionToDetermineIfToUse2FA: widget
|
conditionToDetermineIfToUse2FA:
|
||||||
.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -187,6 +187,11 @@ class AddressPage extends BasePage {
|
||||||
}
|
}
|
||||||
|
|
||||||
reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) {
|
reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) {
|
||||||
|
if (bitcoin!.isBitcoinReceivePageOption(option)) {
|
||||||
|
addressListViewModel.setAddressType(bitcoin!.getOptionToType(option));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (option) {
|
switch (option) {
|
||||||
case ReceivePageOption.anonPayInvoice:
|
case ReceivePageOption.anonPayInvoice:
|
||||||
Navigator.pushNamed(
|
Navigator.pushNamed(
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:auto_size_text/auto_size_text.dart';
|
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/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||||
import 'package:cake_wallet/routes.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/pages/nft_listing_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.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/widgets/cake_image_widget.dart';
|
||||||
import 'package:cake_wallet/src/screens/exchange_trade/information_page.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/dashboard_card_widget.dart';
|
||||||
import 'package:cake_wallet/src/widgets/introducing_card.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/store/settings_store.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/dashboard_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:cw_core/crypto_currency.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class BalancePage extends StatelessWidget {
|
class BalancePage extends StatelessWidget {
|
||||||
BalancePage({
|
BalancePage({
|
||||||
|
@ -221,30 +225,136 @@ class CryptoBalanceWidget extends StatelessWidget {
|
||||||
itemBuilder: (__, index) {
|
itemBuilder: (__, index) {
|
||||||
final balance =
|
final balance =
|
||||||
dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index);
|
dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index);
|
||||||
return BalanceRowWidget(
|
return Observer(builder: (_) {
|
||||||
availableBalanceLabel:
|
return BalanceRowWidget(
|
||||||
'${dashboardViewModel.balanceViewModel.availableBalanceLabel}',
|
availableBalanceLabel:
|
||||||
availableBalance: balance.availableBalance,
|
'${dashboardViewModel.balanceViewModel.availableBalanceLabel}',
|
||||||
availableFiatBalance: balance.fiatAvailableBalance,
|
availableBalance: balance.availableBalance,
|
||||||
additionalBalanceLabel:
|
availableFiatBalance: balance.fiatAvailableBalance,
|
||||||
'${dashboardViewModel.balanceViewModel.additionalBalanceLabel}',
|
additionalBalanceLabel:
|
||||||
additionalBalance: balance.additionalBalance,
|
'${dashboardViewModel.balanceViewModel.additionalBalanceLabel}',
|
||||||
additionalFiatBalance: balance.fiatAdditionalBalance,
|
additionalBalance: balance.additionalBalance,
|
||||||
frozenBalance: balance.frozenBalance,
|
additionalFiatBalance: balance.fiatAdditionalBalance,
|
||||||
frozenFiatBalance: balance.fiatFrozenBalance,
|
frozenBalance: balance.frozenBalance,
|
||||||
currency: balance.asset,
|
frozenFiatBalance: balance.fiatFrozenBalance,
|
||||||
hasAdditionalBalance:
|
currency: balance.asset,
|
||||||
dashboardViewModel.balanceViewModel.hasAdditionalBalance,
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BalanceRowWidget extends StatelessWidget {
|
class BalanceRowWidget extends StatelessWidget {
|
||||||
|
@ -259,6 +369,7 @@ class BalanceRowWidget extends StatelessWidget {
|
||||||
required this.frozenFiatBalance,
|
required this.frozenFiatBalance,
|
||||||
required this.currency,
|
required this.currency,
|
||||||
required this.hasAdditionalBalance,
|
required this.hasAdditionalBalance,
|
||||||
|
required this.isTestnet,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -272,6 +383,7 @@ class BalanceRowWidget extends StatelessWidget {
|
||||||
final String frozenFiatBalance;
|
final String frozenFiatBalance;
|
||||||
final CryptoCurrency currency;
|
final CryptoCurrency currency;
|
||||||
final bool hasAdditionalBalance;
|
final bool hasAdditionalBalance;
|
||||||
|
final bool isTestnet;
|
||||||
|
|
||||||
// void _showBalanceDescription(BuildContext context) {
|
// void _showBalanceDescription(BuildContext context) {
|
||||||
// showPopUp<void>(
|
// showPopUp<void>(
|
||||||
|
@ -346,14 +458,24 @@ class BalanceRowWidget extends StatelessWidget {
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
textAlign: TextAlign.start),
|
textAlign: TextAlign.start),
|
||||||
SizedBox(height: 6),
|
SizedBox(height: 6),
|
||||||
Text('${availableFiatBalance}',
|
if (isTestnet)
|
||||||
textAlign: TextAlign.center,
|
Text(S.current.testnet_coins_no_value,
|
||||||
style: TextStyle(
|
textAlign: TextAlign.center,
|
||||||
fontSize: 16,
|
style: TextStyle(
|
||||||
fontFamily: 'Lato',
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w500,
|
fontFamily: 'Lato',
|
||||||
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
fontWeight: FontWeight.w400,
|
||||||
height: 1)),
|
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
||||||
|
height: 1)),
|
||||||
|
if (!isTestnet)
|
||||||
|
Text('${availableFiatBalance}',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontFamily: 'Lato',
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
||||||
|
height: 1)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -362,27 +484,23 @@ class BalanceRowWidget extends StatelessWidget {
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
CakeImageWidget(
|
||||||
clipBehavior: Clip.antiAlias,
|
imageUrl: currency.iconPath,
|
||||||
decoration: BoxDecoration(shape: BoxShape.circle),
|
height: 40,
|
||||||
child: CakeImageWidget(
|
width: 40,
|
||||||
imageUrl: currency.iconPath,
|
displayOnError: Container(
|
||||||
height: 40,
|
height: 30.0,
|
||||||
width: 40,
|
width: 30.0,
|
||||||
displayOnError: Container(
|
child: Center(
|
||||||
height: 30.0,
|
child: Text(
|
||||||
width: 30.0,
|
currency.title.substring(0, min(currency.title.length, 2)),
|
||||||
child: Center(
|
style: TextStyle(fontSize: 11),
|
||||||
child: Text(
|
|
||||||
currency.title.substring(0, min(currency.title.length, 2)),
|
|
||||||
style: TextStyle(fontSize: 11),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
color: Colors.grey.shade400,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: Colors.grey.shade400,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
|
@ -449,17 +567,18 @@ class BalanceRowWidget extends StatelessWidget {
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
SizedBox(height: 4),
|
SizedBox(height: 4),
|
||||||
Text(
|
if (!isTestnet)
|
||||||
frozenFiatBalance,
|
Text(
|
||||||
textAlign: TextAlign.center,
|
frozenFiatBalance,
|
||||||
style: TextStyle(
|
textAlign: TextAlign.center,
|
||||||
fontSize: 12,
|
style: TextStyle(
|
||||||
fontFamily: 'Lato',
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w400,
|
fontFamily: 'Lato',
|
||||||
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
fontWeight: FontWeight.w400,
|
||||||
height: 1,
|
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -493,17 +612,18 @@ class BalanceRowWidget extends StatelessWidget {
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
SizedBox(height: 4),
|
SizedBox(height: 4),
|
||||||
Text(
|
if (!isTestnet)
|
||||||
'${additionalFiatBalance}',
|
Text(
|
||||||
textAlign: TextAlign.center,
|
'${additionalFiatBalance}',
|
||||||
style: TextStyle(
|
textAlign: TextAlign.center,
|
||||||
fontSize: 12,
|
style: TextStyle(
|
||||||
fontFamily: 'Lato',
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w400,
|
fontFamily: 'Lato',
|
||||||
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
fontWeight: FontWeight.w400,
|
||||||
height: 1,
|
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -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/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/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:cw_core/wallet_type.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
|
||||||
class MarketPlacePage extends StatelessWidget {
|
class CakeFeaturesPage extends StatelessWidget {
|
||||||
MarketPlacePage({
|
CakeFeaturesPage({
|
||||||
required this.dashboardViewModel,
|
required this.dashboardViewModel,
|
||||||
required this.marketPlaceViewModel,
|
required this.cakeFeaturesViewModel,
|
||||||
});
|
});
|
||||||
|
|
||||||
final DashboardViewModel dashboardViewModel;
|
final DashboardViewModel dashboardViewModel;
|
||||||
final MarketPlaceViewModel marketPlaceViewModel;
|
final CakeFeaturesViewModel cakeFeaturesViewModel;
|
||||||
final _scrollController = ScrollController();
|
final _scrollController = ScrollController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -37,7 +34,7 @@ class MarketPlacePage extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: 50),
|
SizedBox(height: 50),
|
||||||
Text(
|
Text(
|
||||||
S.of(context).market_place,
|
'Cake ${S.of(context).features}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
|
@ -59,15 +56,21 @@ class MarketPlacePage extends StatelessWidget {
|
||||||
// ),
|
// ),
|
||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
DashBoardRoundedCardWidget(
|
DashBoardRoundedCardWidget(
|
||||||
|
onTap: () => _launchUrl("buy.cakepay.com"),
|
||||||
title: S.of(context).cake_pay_web_cards_title,
|
title: S.of(context).cake_pay_web_cards_title,
|
||||||
subTitle: S.of(context).cake_pay_web_cards_subtitle,
|
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(
|
DashBoardRoundedCardWidget(
|
||||||
title: "NanoGPT",
|
title: "NanoGPT",
|
||||||
subTitle: S.of(context).nanogpt_subtitle,
|
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 {
|
try {
|
||||||
launchUrl(
|
launchUrl(
|
||||||
Uri.https(url),
|
Uri.https(url),
|
||||||
mode: LaunchMode.externalApplication,
|
mode: LaunchMode.externalApplication,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (_) {}
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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_action_button.dart';
|
||||||
import 'package:cake_wallet/src/widgets/setting_actions.dart';
|
import 'package:cake_wallet/src/widgets/setting_actions.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/menu_theme.dart';
|
import 'package:cake_wallet/themes/extensions/menu_theme.dart';
|
||||||
|
@ -180,6 +181,11 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||||
|
|
||||||
final item = SettingActions.all[index];
|
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;
|
final isLastTile = index == itemCount - 1;
|
||||||
|
|
||||||
return SettingActionButton(
|
return SettingActionButton(
|
||||||
|
|
|
@ -10,8 +10,7 @@ import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
|
||||||
class PresentReceiveOptionPicker extends StatelessWidget {
|
class PresentReceiveOptionPicker extends StatelessWidget {
|
||||||
PresentReceiveOptionPicker(
|
PresentReceiveOptionPicker({required this.receiveOptionViewModel, required this.color});
|
||||||
{required this.receiveOptionViewModel, required this.color});
|
|
||||||
|
|
||||||
final ReceiveOptionViewModel receiveOptionViewModel;
|
final ReceiveOptionViewModel receiveOptionViewModel;
|
||||||
final Color color;
|
final Color color;
|
||||||
|
@ -43,17 +42,17 @@ class PresentReceiveOptionPicker extends StatelessWidget {
|
||||||
Text(
|
Text(
|
||||||
S.current.receive,
|
S.current.receive,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18.0,
|
fontSize: 18.0, fontWeight: FontWeight.bold, fontFamily: 'Lato', color: color),
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontFamily: 'Lato',
|
|
||||||
color: color),
|
|
||||||
),
|
),
|
||||||
Observer(
|
Observer(
|
||||||
builder: (_) => Text(receiveOptionViewModel.selectedReceiveOption.toString(),
|
builder: (_) => Text(
|
||||||
style: TextStyle(
|
receiveOptionViewModel.selectedReceiveOption
|
||||||
fontSize: 10.0,
|
.toString()
|
||||||
fontWeight: FontWeight.w500,
|
.replaceAll(RegExp(r'silent payments', caseSensitive: false),
|
||||||
color: color)))
|
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),
|
SizedBox(width: 5),
|
||||||
|
@ -73,65 +72,75 @@ class PresentReceiveOptionPicker extends StatelessWidget {
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
body: Stack(
|
body: Stack(
|
||||||
alignment: AlignmentDirectional.center,
|
alignment: AlignmentDirectional.center,
|
||||||
children:[ AlertBackground(
|
children: [
|
||||||
child: Column(
|
AlertBackground(
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
Spacer(),
|
children: [
|
||||||
Container(
|
Spacer(),
|
||||||
margin: EdgeInsets.symmetric(horizontal: 24),
|
Container(
|
||||||
decoration: BoxDecoration(
|
margin: EdgeInsets.symmetric(horizontal: 24),
|
||||||
borderRadius: BorderRadius.circular(30),
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.background,
|
borderRadius: BorderRadius.circular(30),
|
||||||
),
|
color: Theme.of(context).colorScheme.background,
|
||||||
child: Padding(
|
),
|
||||||
padding: const EdgeInsets.only(top: 24, bottom: 24),
|
child: Padding(
|
||||||
child: (ListView.separated(
|
padding: const EdgeInsets.only(top: 24, bottom: 24),
|
||||||
padding: EdgeInsets.zero,
|
child: (ListView.separated(
|
||||||
shrinkWrap: true,
|
padding: EdgeInsets.zero,
|
||||||
itemCount: receiveOptionViewModel.options.length,
|
shrinkWrap: true,
|
||||||
itemBuilder: (_, index) {
|
itemCount: receiveOptionViewModel.options.length,
|
||||||
final option = receiveOptionViewModel.options[index];
|
itemBuilder: (_, index) {
|
||||||
return InkWell(
|
final option = receiveOptionViewModel.options[index];
|
||||||
onTap: () {
|
return InkWell(
|
||||||
Navigator.pop(popUpContext);
|
onTap: () {
|
||||||
|
Navigator.pop(popUpContext);
|
||||||
|
|
||||||
receiveOptionViewModel.selectReceiveOption(option);
|
receiveOptionViewModel.selectReceiveOption(option);
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(left: 24, right: 24),
|
padding: const EdgeInsets.only(left: 24, right: 24),
|
||||||
child: Observer(builder: (_) {
|
child: Observer(builder: (_) {
|
||||||
final value = receiveOptionViewModel.selectedReceiveOption;
|
final value = receiveOptionViewModel.selectedReceiveOption;
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(option.toString(),
|
Text(
|
||||||
textAlign: TextAlign.left,
|
option
|
||||||
style: textSmall(
|
.toString()
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
.replaceAll(
|
||||||
).copyWith(
|
RegExp(r'silent payments', caseSensitive: false),
|
||||||
fontWeight:
|
S.current.silent_payments)
|
||||||
value == option ? FontWeight.w800 : FontWeight.w500,
|
.replaceAll(RegExp(r'default', caseSensitive: false),
|
||||||
)),
|
S.current.string_default),
|
||||||
RoundedCheckbox(
|
textAlign: TextAlign.left,
|
||||||
value: value == option,
|
style: textSmall(
|
||||||
)
|
color: Theme.of(context)
|
||||||
],
|
.extension<CakeTextTheme>()!
|
||||||
);
|
.titleColor,
|
||||||
}),
|
).copyWith(
|
||||||
),
|
fontWeight:
|
||||||
);
|
value == option ? FontWeight.w800 : FontWeight.w500,
|
||||||
},
|
)),
|
||||||
separatorBuilder: (_, index) => SizedBox(height: 30),
|
RoundedCheckbox(
|
||||||
)),
|
value: value == option,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
separatorBuilder: (_, index) => SizedBox(height: 30),
|
||||||
|
)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Spacer()
|
||||||
Spacer()
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
AlertCloseButton(onTap: () => Navigator.of(popUpContext).pop(), bottom: 40)
|
AlertCloseButton(onTap: () => Navigator.of(popUpContext).pop(), bottom: 40)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -67,8 +67,7 @@ class ReceivePage extends BasePage {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget Function(BuildContext, Widget) get rootWrapper =>
|
Widget Function(BuildContext, Widget) get rootWrapper =>
|
||||||
(BuildContext context, Widget scaffold) =>
|
(BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold);
|
||||||
GradientBackground(scaffold: scaffold);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget trailing(BuildContext context) {
|
Widget trailing(BuildContext context) {
|
||||||
|
@ -99,115 +98,144 @@ class ReceivePage extends BasePage {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) {
|
Widget body(BuildContext context) {
|
||||||
return KeyboardActions(
|
return KeyboardActions(
|
||||||
config: KeyboardActionsConfig(
|
config: KeyboardActionsConfig(
|
||||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||||
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
|
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
|
||||||
nextFocus: false,
|
nextFocus: false,
|
||||||
actions: [
|
actions: [
|
||||||
KeyboardActionsItem(
|
KeyboardActionsItem(
|
||||||
focusNode: _cryptoAmountFocus,
|
focusNode: _cryptoAmountFocus,
|
||||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||||
)
|
)
|
||||||
]),
|
]),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.fromLTRB(24, 50, 24, 24),
|
padding: EdgeInsets.fromLTRB(24, 50, 24, 24),
|
||||||
child: QRWidget(
|
child: QRWidget(
|
||||||
addressListViewModel: addressListViewModel,
|
addressListViewModel: addressListViewModel,
|
||||||
formKey: _formKey,
|
formKey: _formKey,
|
||||||
heroTag: _heroTag,
|
heroTag: _heroTag,
|
||||||
amountTextFieldFocusNode: _cryptoAmountFocus,
|
amountTextFieldFocusNode: _cryptoAmountFocus,
|
||||||
amountController: _amountController,
|
amountController: _amountController,
|
||||||
isLight: currentTheme.type == ThemeType.light),
|
isLight: currentTheme.type == ThemeType.light),
|
||||||
),
|
|
||||||
Observer(
|
|
||||||
builder: (_) => ListView.separated(
|
|
||||||
padding: EdgeInsets.all(0),
|
|
||||||
separatorBuilder: (context, _) => const HorizontalSectionDivider(),
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: NeverScrollableScrollPhysics(),
|
|
||||||
itemCount: addressListViewModel.items.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final item = addressListViewModel.items[index];
|
|
||||||
Widget cell = Container();
|
|
||||||
|
|
||||||
if (item is WalletAccountListHeader) {
|
|
||||||
cell = HeaderTile(
|
|
||||||
showTrailingButton: true,
|
|
||||||
walletAddressListViewModel: addressListViewModel,
|
|
||||||
trailingButtonTap: () async {
|
|
||||||
if (addressListViewModel.type == WalletType.monero ||
|
|
||||||
addressListViewModel.type == WalletType.haven) {
|
|
||||||
await showPopUp<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => getIt.get<MoneroAccountListPage>());
|
|
||||||
} else {
|
|
||||||
await showPopUp<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => getIt.get<NanoAccountListPage>());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
title: S.of(context).accounts,
|
|
||||||
trailingIcon: Icon(
|
|
||||||
Icons.arrow_forward_ios,
|
|
||||||
size: 14,
|
|
||||||
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item is WalletAddressListHeader) {
|
|
||||||
cell = HeaderTile(
|
|
||||||
title: S.of(context).addresses,
|
|
||||||
walletAddressListViewModel: addressListViewModel,
|
|
||||||
showTrailingButton: !addressListViewModel.isAutoGenerateSubaddressEnabled,
|
|
||||||
showSearchButton: true,
|
|
||||||
trailingButtonTap: () =>
|
|
||||||
Navigator.of(context).pushNamed(Routes.newSubaddress),
|
|
||||||
trailingIcon: Icon(
|
|
||||||
Icons.add,
|
|
||||||
size: 20,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.extension<ReceivePageTheme>()!
|
|
||||||
.iconsColor,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item is WalletAddressListItem) {
|
|
||||||
cell = Observer(builder: (_) {
|
|
||||||
final isCurrent =
|
|
||||||
item.address == addressListViewModel.address.address;
|
|
||||||
final backgroundColor = isCurrent
|
|
||||||
? 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>()!.tilesTextColor;
|
|
||||||
|
|
||||||
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));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return index != 0
|
|
||||||
? cell
|
|
||||||
: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(30),
|
|
||||||
topRight: Radius.circular(30)),
|
|
||||||
child: cell,
|
|
||||||
);
|
|
||||||
})),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
));
|
Observer(
|
||||||
|
builder: (_) => ListView.separated(
|
||||||
|
padding: EdgeInsets.all(0),
|
||||||
|
separatorBuilder: (context, _) => const HorizontalSectionDivider(),
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: addressListViewModel.items.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = addressListViewModel.items[index];
|
||||||
|
Widget cell = Container();
|
||||||
|
|
||||||
|
if (item is WalletAccountListHeader) {
|
||||||
|
cell = HeaderTile(
|
||||||
|
showTrailingButton: true,
|
||||||
|
walletAddressListViewModel: addressListViewModel,
|
||||||
|
trailingButtonTap: () async {
|
||||||
|
if (addressListViewModel.type == WalletType.monero ||
|
||||||
|
addressListViewModel.type == WalletType.haven) {
|
||||||
|
await showPopUp<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => getIt.get<MoneroAccountListPage>());
|
||||||
|
} else {
|
||||||
|
await showPopUp<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => getIt.get<NanoAccountListPage>());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title: S.of(context).accounts,
|
||||||
|
trailingIcon: Icon(
|
||||||
|
Icons.arrow_forward_ios,
|
||||||
|
size: 14,
|
||||||
|
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item is WalletAddressListHeader) {
|
||||||
|
final hasTitle = item.title != null;
|
||||||
|
|
||||||
|
cell = HeaderTile(
|
||||||
|
title: hasTitle ? item.title! : S.of(context).addresses,
|
||||||
|
walletAddressListViewModel: addressListViewModel,
|
||||||
|
showTrailingButton:
|
||||||
|
!addressListViewModel.isAutoGenerateSubaddressEnabled && !hasTitle,
|
||||||
|
showSearchButton: true,
|
||||||
|
trailingButtonTap: () =>
|
||||||
|
Navigator.of(context).pushNamed(Routes.newSubaddress),
|
||||||
|
trailingIcon: hasTitle
|
||||||
|
? null
|
||||||
|
: Icon(
|
||||||
|
Icons.add,
|
||||||
|
size: 20,
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item is WalletAddressListItem) {
|
||||||
|
cell = Observer(builder: (_) {
|
||||||
|
final isCurrent = item.address == addressListViewModel.address.address;
|
||||||
|
final backgroundColor = isCurrent
|
||||||
|
? 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>()!.tilesTextColor;
|
||||||
|
|
||||||
|
return AddressCell.fromItem(
|
||||||
|
item,
|
||||||
|
isCurrent: isCurrent,
|
||||||
|
hasBalance: addressListViewModel.isElectrumWallet,
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
textColor: textColor,
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return index != 0
|
||||||
|
? cell
|
||||||
|
: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
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)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,18 +15,22 @@ class AddressCell extends StatelessWidget {
|
||||||
required this.textColor,
|
required this.textColor,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.onEdit,
|
this.onEdit,
|
||||||
|
this.onDelete,
|
||||||
this.txCount,
|
this.txCount,
|
||||||
this.balance,
|
this.balance,
|
||||||
this.isChange = false,
|
this.isChange = false,
|
||||||
this.hasBalance = false});
|
this.hasBalance = false});
|
||||||
|
|
||||||
factory AddressCell.fromItem(WalletAddressListItem item,
|
factory AddressCell.fromItem(
|
||||||
{required bool isCurrent,
|
WalletAddressListItem item, {
|
||||||
required Color backgroundColor,
|
required bool isCurrent,
|
||||||
required Color textColor,
|
required Color backgroundColor,
|
||||||
Function(String)? onTap,
|
required Color textColor,
|
||||||
bool hasBalance = false,
|
Function(String)? onTap,
|
||||||
Function()? onEdit}) =>
|
bool hasBalance = false,
|
||||||
|
Function()? onEdit,
|
||||||
|
Function()? onDelete,
|
||||||
|
}) =>
|
||||||
AddressCell(
|
AddressCell(
|
||||||
address: item.address,
|
address: item.address,
|
||||||
name: item.name ?? '',
|
name: item.name ?? '',
|
||||||
|
@ -36,6 +40,7 @@ class AddressCell extends StatelessWidget {
|
||||||
textColor: textColor,
|
textColor: textColor,
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
onEdit: onEdit,
|
onEdit: onEdit,
|
||||||
|
onDelete: onDelete,
|
||||||
txCount: item.txCount,
|
txCount: item.txCount,
|
||||||
balance: item.balance,
|
balance: item.balance,
|
||||||
isChange: item.isChange,
|
isChange: item.isChange,
|
||||||
|
@ -49,6 +54,7 @@ class AddressCell extends StatelessWidget {
|
||||||
final Color textColor;
|
final Color textColor;
|
||||||
final Function(String)? onTap;
|
final Function(String)? onTap;
|
||||||
final Function()? onEdit;
|
final Function()? onEdit;
|
||||||
|
final Function()? onDelete;
|
||||||
final int? txCount;
|
final int? txCount;
|
||||||
final String? balance;
|
final String? balance;
|
||||||
final bool isChange;
|
final bool isChange;
|
||||||
|
@ -64,7 +70,8 @@ class AddressCell extends StatelessWidget {
|
||||||
} else {
|
} else {
|
||||||
return formatIfCashAddr.substring(0, addressPreviewLength) +
|
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,
|
mainAxisSize: MainAxisSize.max,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Balance: $balance',
|
'${S.of(context).balance}: $balance',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
|
@ -180,7 +187,7 @@ class AddressCell extends StatelessWidget {
|
||||||
|
|
||||||
ActionPane _actionPane(BuildContext context) => ActionPane(
|
ActionPane _actionPane(BuildContext context) => ActionPane(
|
||||||
motion: const ScrollMotion(),
|
motion: const ScrollMotion(),
|
||||||
extentRatio: 0.3,
|
extentRatio: onDelete != null ? 0.4 : 0.3,
|
||||||
children: [
|
children: [
|
||||||
SlidableAction(
|
SlidableAction(
|
||||||
onPressed: (_) => onEdit?.call(),
|
onPressed: (_) => onEdit?.call(),
|
||||||
|
@ -189,6 +196,14 @@ class AddressCell extends StatelessWidget {
|
||||||
icon: Icons.edit,
|
icon: Icons.edit,
|
||||||
label: S.of(context).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,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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/material.dart';
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
import 'package:cake_wallet/view_model/rescan_view_model.dart';
|
import 'package:cake_wallet/view_model/rescan_view_model.dart';
|
||||||
|
@ -11,7 +15,8 @@ class RescanPage extends BasePage {
|
||||||
: _blockchainHeightWidgetKey = GlobalKey<BlockchainHeightState>();
|
: _blockchainHeightWidgetKey = GlobalKey<BlockchainHeightState>();
|
||||||
|
|
||||||
@override
|
@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 GlobalKey<BlockchainHeightState> _blockchainHeightWidgetKey;
|
||||||
final RescanViewModel _rescanViewModel;
|
final RescanViewModel _rescanViewModel;
|
||||||
|
|
||||||
|
@ -19,20 +24,28 @@ class RescanPage extends BasePage {
|
||||||
Widget body(BuildContext context) {
|
Widget body(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||||
child:
|
child: Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||||||
Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
Observer(
|
||||||
BlockchainHeightWidget(key: _blockchainHeightWidgetKey,
|
builder: (_) => BlockchainHeightWidget(
|
||||||
onHeightOrDateEntered: (value) =>
|
key: _blockchainHeightWidgetKey,
|
||||||
_rescanViewModel.isButtonEnabled = value),
|
onHeightOrDateEntered: (value) => _rescanViewModel.isButtonEnabled = value,
|
||||||
|
isSilentPaymentsScan: _rescanViewModel.isSilentPaymentsScan,
|
||||||
|
doSingleScan: _rescanViewModel.doSingleScan,
|
||||||
|
toggleSingleScan: () =>
|
||||||
|
_rescanViewModel.doSingleScan = !_rescanViewModel.doSingleScan,
|
||||||
|
)),
|
||||||
Observer(
|
Observer(
|
||||||
builder: (_) => LoadingPrimaryButton(
|
builder: (_) => LoadingPrimaryButton(
|
||||||
isLoading:
|
isLoading: _rescanViewModel.state == RescanWalletState.rescaning,
|
||||||
_rescanViewModel.state == RescanWalletState.rescaning,
|
|
||||||
text: S.of(context).rescan,
|
text: S.of(context).rescan,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await _rescanViewModel.rescanCurrentWallet(
|
if (_rescanViewModel.isSilentPaymentsScan) {
|
||||||
restoreHeight:
|
return _toggleSilentPaymentsScanning(context);
|
||||||
_blockchainHeightWidgetKey.currentState!.height);
|
}
|
||||||
|
|
||||||
|
_rescanViewModel.rescanCurrentWallet(
|
||||||
|
restoreHeight: _blockchainHeightWidgetKey.currentState!.height);
|
||||||
|
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
color: Theme.of(context).primaryColor,
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||||
import 'package:cake_wallet/core/auth_service.dart';
|
import 'package:cake_wallet/core/auth_service.dart';
|
||||||
import 'package:cake_wallet/entities/contact_record.dart';
|
import 'package:cake_wallet/entities/contact_record.dart';
|
||||||
import 'package:cake_wallet/core/execution_state.dart';
|
import 'package:cake_wallet/core/execution_state.dart';
|
||||||
|
@ -420,6 +421,10 @@ class SendPage extends BasePage {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sendViewModel.isElectrumWallet) {
|
||||||
|
bitcoin!.updateFeeRates(sendViewModel.wallet);
|
||||||
|
}
|
||||||
|
|
||||||
reaction((_) => sendViewModel.state, (ExecutionState state) {
|
reaction((_) => sendViewModel.state, (ExecutionState state) {
|
||||||
if (dialogContext != null && dialogContext?.mounted == true) {
|
if (dialogContext != null && dialogContext?.mounted == true) {
|
||||||
Navigator.of(dialogContext!).pop();
|
Navigator.of(dialogContext!).pop();
|
||||||
|
|
|
@ -39,7 +39,9 @@ class ConnectionSyncPage extends BasePage {
|
||||||
),
|
),
|
||||||
if (dashboardViewModel.hasRescan) ...[
|
if (dashboardViewModel.hasRescan) ...[
|
||||||
SettingsCellWithArrow(
|
SettingsCellWithArrow(
|
||||||
title: S.current.rescan,
|
title: dashboardViewModel.hasSilentPayments
|
||||||
|
? S.current.silent_payments_scanning
|
||||||
|
: S.current.rescan,
|
||||||
handler: (context) => Navigator.of(context).pushNamed(Routes.rescan),
|
handler: (context) => Navigator.of(context).pushNamed(Routes.rescan),
|
||||||
),
|
),
|
||||||
if (DeviceInfo.instance.isMobile && FeatureFlag.isBackgroundSyncEnabled) ...[
|
if (DeviceInfo.instance.isMobile && FeatureFlag.isBackgroundSyncEnabled) ...[
|
||||||
|
|
|
@ -9,14 +9,13 @@ 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_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_picker_cell.dart';
|
||||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_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/utils/device_info.dart';
|
||||||
import 'package:cake_wallet/view_model/settings/security_settings_view_model.dart';
|
import 'package:cake_wallet/view_model/settings/security_settings_view_model.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
|
|
||||||
class SecurityBackupPage extends BasePage {
|
class SecurityBackupPage extends BasePage {
|
||||||
SecurityBackupPage(this._securitySettingsViewModel, this._authService);
|
SecurityBackupPage(this._securitySettingsViewModel, this._authService, [this._isHardwareWallet = false]);
|
||||||
|
|
||||||
final AuthService _authService;
|
final AuthService _authService;
|
||||||
|
|
||||||
|
@ -25,20 +24,23 @@ class SecurityBackupPage extends BasePage {
|
||||||
|
|
||||||
final SecuritySettingsViewModel _securitySettingsViewModel;
|
final SecuritySettingsViewModel _securitySettingsViewModel;
|
||||||
|
|
||||||
|
final bool _isHardwareWallet;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) {
|
Widget body(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: EdgeInsets.only(top: 10),
|
padding: EdgeInsets.only(top: 10),
|
||||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
SettingsCellWithArrow(
|
if (!_isHardwareWallet)
|
||||||
title: S.current.show_keys,
|
SettingsCellWithArrow(
|
||||||
handler: (_) => _authService.authenticateAction(
|
title: S.current.show_keys,
|
||||||
context,
|
handler: (_) => _authService.authenticateAction(
|
||||||
route: Routes.showKeys,
|
context,
|
||||||
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
|
route: Routes.showKeys,
|
||||||
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
conditionToDetermineIfToUse2FA:
|
||||||
|
_securitySettingsViewModel.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
SettingsCellWithArrow(
|
SettingsCellWithArrow(
|
||||||
title: S.current.create_backup,
|
title: S.current.create_backup,
|
||||||
handler: (_) => _authService.authenticateAction(
|
handler: (_) => _authService.authenticateAction(
|
||||||
|
|
50
lib/src/screens/settings/silent_payments_settings.dart
Normal file
50
lib/src/screens/settings/silent_payments_settings.dart
Normal 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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -57,6 +57,7 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
|
||||||
isSending: item.isSending,
|
isSending: item.isSending,
|
||||||
isFrozen: item.isFrozen,
|
isFrozen: item.isFrozen,
|
||||||
isChange: item.isChange,
|
isChange: item.isChange,
|
||||||
|
isSilentPayment: item.isSilentPayment,
|
||||||
onCheckBoxTap: item.isFrozen
|
onCheckBoxTap: item.isFrozen
|
||||||
? null
|
? null
|
||||||
: () async {
|
: () async {
|
||||||
|
|
|
@ -12,6 +12,7 @@ class UnspentCoinsListItem extends StatelessWidget {
|
||||||
required this.isSending,
|
required this.isSending,
|
||||||
required this.isFrozen,
|
required this.isFrozen,
|
||||||
required this.isChange,
|
required this.isChange,
|
||||||
|
required this.isSilentPayment,
|
||||||
this.onCheckBoxTap,
|
this.onCheckBoxTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -21,18 +22,16 @@ class UnspentCoinsListItem extends StatelessWidget {
|
||||||
final bool isSending;
|
final bool isSending;
|
||||||
final bool isFrozen;
|
final bool isFrozen;
|
||||||
final bool isChange;
|
final bool isChange;
|
||||||
|
final bool isSilentPayment;
|
||||||
final Function()? onCheckBoxTap;
|
final Function()? onCheckBoxTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final unselectedItemColor = Theme.of(context).cardColor;
|
final unselectedItemColor = Theme.of(context).cardColor;
|
||||||
final selectedItemColor = Theme.of(context).primaryColor;
|
final selectedItemColor = Theme.of(context).primaryColor;
|
||||||
final itemColor = isSending
|
final itemColor = isSending ? selectedItemColor : unselectedItemColor;
|
||||||
? selectedItemColor
|
final amountColor =
|
||||||
: unselectedItemColor;
|
isSending ? Colors.white : Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor;
|
||||||
final amountColor = isSending
|
|
||||||
? Colors.white
|
|
||||||
: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor;
|
|
||||||
final addressColor = isSending
|
final addressColor = isSending
|
||||||
? Colors.white.withOpacity(0.5)
|
? Colors.white.withOpacity(0.5)
|
||||||
: Theme.of(context).extension<CakeTextTheme>()!.buttonSecondaryTextColor;
|
: 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -96,6 +96,7 @@ class WalletListBody extends StatefulWidget {
|
||||||
class WalletListBodyState extends State<WalletListBody> {
|
class WalletListBodyState extends State<WalletListBody> {
|
||||||
final moneroIcon = Image.asset('assets/images/monero_logo.png', height: 24, width: 24);
|
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 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 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 nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
|
||||||
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
|
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
|
||||||
|
@ -162,7 +163,10 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
wallet.isEnabled
|
wallet.isEnabled
|
||||||
? _imageFor(type: wallet.type)
|
? _imageFor(
|
||||||
|
type: wallet.type,
|
||||||
|
isTestnet: wallet.isTestnet,
|
||||||
|
)
|
||||||
: nonWalletTypeIcon,
|
: nonWalletTypeIcon,
|
||||||
SizedBox(width: 10),
|
SizedBox(width: 10),
|
||||||
Flexible(
|
Flexible(
|
||||||
|
@ -297,9 +301,12 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Image _imageFor({required WalletType type}) {
|
Image _imageFor({required WalletType type, bool? isTestnet}) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
|
if (isTestnet == true) {
|
||||||
|
return tBitcoinIcon;
|
||||||
|
}
|
||||||
return bitcoinIcon;
|
return bitcoinIcon;
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
return moneroIcon;
|
return moneroIcon;
|
||||||
|
|
|
@ -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/themes/extensions/cake_text_theme.dart';
|
||||||
import 'package:cake_wallet/utils/date_picker.dart';
|
import 'package:cake_wallet/utils/date_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -12,13 +14,19 @@ class BlockchainHeightWidget extends StatefulWidget {
|
||||||
this.onHeightChange,
|
this.onHeightChange,
|
||||||
this.focusNode,
|
this.focusNode,
|
||||||
this.onHeightOrDateEntered,
|
this.onHeightOrDateEntered,
|
||||||
this.hasDatePicker = true})
|
this.hasDatePicker = true,
|
||||||
: super(key: key);
|
this.isSilentPaymentsScan = false,
|
||||||
|
this.toggleSingleScan,
|
||||||
|
this.doSingleScan = false,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
final Function(int)? onHeightChange;
|
final Function(int)? onHeightChange;
|
||||||
final Function(bool)? onHeightOrDateEntered;
|
final Function(bool)? onHeightOrDateEntered;
|
||||||
final FocusNode? focusNode;
|
final FocusNode? focusNode;
|
||||||
final bool hasDatePicker;
|
final bool hasDatePicker;
|
||||||
|
final bool isSilentPaymentsScan;
|
||||||
|
final bool doSingleScan;
|
||||||
|
final Function()? toggleSingleScan;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => BlockchainHeightState();
|
State<StatefulWidget> createState() => BlockchainHeightState();
|
||||||
|
@ -64,9 +72,10 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
||||||
child: BaseTextFormField(
|
child: BaseTextFormField(
|
||||||
focusNode: widget.focusNode,
|
focusNode: widget.focusNode,
|
||||||
controller: restoreHeightController,
|
controller: restoreHeightController,
|
||||||
keyboardType: TextInputType.numberWithOptions(
|
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: false),
|
||||||
signed: false, decimal: false),
|
hintText: widget.isSilentPaymentsScan
|
||||||
hintText: S.of(context).widgets_restore_from_blockheight,
|
? 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(
|
style: TextStyle(
|
||||||
fontSize: 16.0,
|
fontSize: 16.0,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color:
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||||
Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
|
@ -91,22 +99,47 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
||||||
child: IgnorePointer(
|
child: IgnorePointer(
|
||||||
child: BaseTextFormField(
|
child: BaseTextFormField(
|
||||||
controller: dateController,
|
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(
|
||||||
padding: EdgeInsets.only(left: 40, right: 40, top: 24),
|
padding: EdgeInsets.only(left: 40, right: 40, top: 24),
|
||||||
child: Text(
|
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,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12, fontWeight: FontWeight.normal, color: Theme.of(context).hintColor),
|
||||||
fontWeight: FontWeight.normal,
|
|
||||||
color: Theme.of(context).hintColor
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
@ -123,7 +156,12 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
||||||
lastDate: now);
|
lastDate: now);
|
||||||
|
|
||||||
if (date != null) {
|
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(() {
|
setState(() {
|
||||||
dateController.text = DateFormat('yyyy-MM-dd').format(date);
|
dateController.text = DateFormat('yyyy-MM-dd').format(date);
|
||||||
restoreHeightController.text = '$height';
|
restoreHeightController.text = '$height';
|
||||||
|
|
|
@ -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:cake_wallet/themes/extensions/sync_indicator_theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
|
||||||
class DashBoardRoundedCardWidget extends StatelessWidget {
|
class DashBoardRoundedCardWidget extends StatelessWidget {
|
||||||
DashBoardRoundedCardWidget({
|
DashBoardRoundedCardWidget({
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.subTitle,
|
required this.subTitle,
|
||||||
|
this.hint,
|
||||||
|
this.svgPicture,
|
||||||
|
this.icon,
|
||||||
this.onClose,
|
this.onClose,
|
||||||
|
this.customBorder,
|
||||||
});
|
});
|
||||||
|
|
||||||
final VoidCallback onTap;
|
final VoidCallback onTap;
|
||||||
final VoidCallback? onClose;
|
final VoidCallback? onClose;
|
||||||
final String title;
|
final String title;
|
||||||
final String subTitle;
|
final String subTitle;
|
||||||
|
final Widget? hint;
|
||||||
|
final SvgPicture? svgPicture;
|
||||||
|
final Icon? icon;
|
||||||
|
final double? customBorder;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -26,34 +35,56 @@ class DashBoardRoundedCardWidget extends StatelessWidget {
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
padding: EdgeInsets.fromLTRB(20, 20, 40, 20),
|
padding: EdgeInsets.all(20),
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
|
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(customBorder ?? 20),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
|
color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Row(
|
||||||
title,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
style: TextStyle(
|
children: [
|
||||||
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
|
Expanded(
|
||||||
fontSize: 24,
|
child: Column(
|
||||||
fontWeight: FontWeight.w900,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
),
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
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,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontFamily: 'Lato'),
|
||||||
|
softWrap: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (svgPicture != null) svgPicture!,
|
||||||
|
if (icon != null) icon!
|
||||||
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
if (hint != null) ...[
|
||||||
Text(
|
SizedBox(height: 10),
|
||||||
subTitle,
|
hint!,
|
||||||
style: TextStyle(
|
]
|
||||||
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
fontFamily: 'Lato'),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -17,6 +17,7 @@ class SettingActions {
|
||||||
connectionSettingAction,
|
connectionSettingAction,
|
||||||
walletSettingAction,
|
walletSettingAction,
|
||||||
addressBookSettingAction,
|
addressBookSettingAction,
|
||||||
|
silentPaymentsSettingAction,
|
||||||
securityBackupSettingAction,
|
securityBackupSettingAction,
|
||||||
privacySettingAction,
|
privacySettingAction,
|
||||||
displaySettingAction,
|
displaySettingAction,
|
||||||
|
@ -35,6 +36,15 @@ class SettingActions {
|
||||||
supportSettingAction,
|
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._(
|
static SettingActions connectionSettingAction = SettingActions._(
|
||||||
name: (context) => S.of(context).connection_sync,
|
name: (context) => S.of(context).connection_sync,
|
||||||
image: 'assets/images/nodes_menu.png',
|
image: 'assets/images/nodes_menu.png',
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||||
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
||||||
import 'package:cake_wallet/core/secure_storage.dart';
|
import 'package:cake_wallet/core/secure_storage.dart';
|
||||||
|
@ -108,6 +107,8 @@ abstract class SettingsStoreBase with Store {
|
||||||
required this.lookupsOpenAlias,
|
required this.lookupsOpenAlias,
|
||||||
required this.lookupsENS,
|
required this.lookupsENS,
|
||||||
required this.customBitcoinFeeRate,
|
required this.customBitcoinFeeRate,
|
||||||
|
required this.silentPaymentsCardDisplay,
|
||||||
|
required this.silentPaymentsAlwaysScan,
|
||||||
TransactionPriority? initialBitcoinTransactionPriority,
|
TransactionPriority? initialBitcoinTransactionPriority,
|
||||||
TransactionPriority? initialMoneroTransactionPriority,
|
TransactionPriority? initialMoneroTransactionPriority,
|
||||||
TransactionPriority? initialHavenTransactionPriority,
|
TransactionPriority? initialHavenTransactionPriority,
|
||||||
|
@ -518,6 +519,16 @@ abstract class SettingsStoreBase with Store {
|
||||||
(int customBitcoinFeeRate) =>
|
(int customBitcoinFeeRate) =>
|
||||||
_sharedPreferences.setInt(PreferencesKey.customBitcoinFeeRate, 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) {
|
this.nodes.observe((change) {
|
||||||
if (change.newValue != null && change.key != null) {
|
if (change.newValue != null && change.key != null) {
|
||||||
_saveCurrentNode(change.newValue!, change.key!);
|
_saveCurrentNode(change.newValue!, change.key!);
|
||||||
|
@ -713,6 +724,12 @@ abstract class SettingsStoreBase with Store {
|
||||||
@observable
|
@observable
|
||||||
int customBitcoinFeeRate;
|
int customBitcoinFeeRate;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool silentPaymentsCardDisplay;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool silentPaymentsAlwaysScan;
|
||||||
|
|
||||||
final SecureStorage _secureStorage;
|
final SecureStorage _secureStorage;
|
||||||
final SharedPreferences _sharedPreferences;
|
final SharedPreferences _sharedPreferences;
|
||||||
final BackgroundTasks _backgroundTasks;
|
final BackgroundTasks _backgroundTasks;
|
||||||
|
@ -859,6 +876,10 @@ abstract class SettingsStoreBase with Store {
|
||||||
final lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
|
final lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
|
||||||
final lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
|
final lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
|
||||||
final customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1;
|
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 no value
|
||||||
if (pinLength == null || pinLength == 0) {
|
if (pinLength == null || pinLength == 0) {
|
||||||
|
@ -1103,6 +1124,8 @@ abstract class SettingsStoreBase with Store {
|
||||||
lookupsOpenAlias: lookupsOpenAlias,
|
lookupsOpenAlias: lookupsOpenAlias,
|
||||||
lookupsENS: lookupsENS,
|
lookupsENS: lookupsENS,
|
||||||
customBitcoinFeeRate: customBitcoinFeeRate,
|
customBitcoinFeeRate: customBitcoinFeeRate,
|
||||||
|
silentPaymentsCardDisplay: silentPaymentsCardDisplay,
|
||||||
|
silentPaymentsAlwaysScan: silentPaymentsAlwaysScan,
|
||||||
initialMoneroTransactionPriority: moneroTransactionPriority,
|
initialMoneroTransactionPriority: moneroTransactionPriority,
|
||||||
initialBitcoinTransactionPriority: bitcoinTransactionPriority,
|
initialBitcoinTransactionPriority: bitcoinTransactionPriority,
|
||||||
initialHavenTransactionPriority: havenTransactionPriority,
|
initialHavenTransactionPriority: havenTransactionPriority,
|
||||||
|
@ -1242,6 +1265,10 @@ abstract class SettingsStoreBase with Store {
|
||||||
lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
|
lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
|
||||||
lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
|
lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
|
||||||
customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1;
|
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 nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
||||||
final bitcoinElectrumServerId =
|
final bitcoinElectrumServerId =
|
||||||
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
|
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
|
||||||
|
|
|
@ -6,7 +6,7 @@ part 'responsive_layout_util.g.dart';
|
||||||
class _ResponsiveLayoutUtil = ResponsiveLayoutUtilBase with _$_ResponsiveLayoutUtil;
|
class _ResponsiveLayoutUtil = ResponsiveLayoutUtilBase with _$_ResponsiveLayoutUtil;
|
||||||
|
|
||||||
abstract class ResponsiveLayoutUtilBase with Store, WidgetsBindingObserver {
|
abstract class ResponsiveLayoutUtilBase with Store, WidgetsBindingObserver {
|
||||||
static const double _kMobileThreshold = 550;
|
static const double kMobileThreshold = 550;
|
||||||
static const double kDesktopMaxWidthConstraint = 400;
|
static const double kDesktopMaxWidthConstraint = 400;
|
||||||
static const double kDesktopMaxDashBoardWidthConstraint = 900;
|
static const double kDesktopMaxDashBoardWidthConstraint = 900;
|
||||||
static const double kPopupWidth = 400;
|
static const double kPopupWidth = 400;
|
||||||
|
@ -42,13 +42,13 @@ abstract class ResponsiveLayoutUtilBase with Store, WidgetsBindingObserver {
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
bool get shouldRenderMobileUI {
|
bool get shouldRenderMobileUI {
|
||||||
return (screenWidth <= _kMobileThreshold) ||
|
return (screenWidth <= kMobileThreshold) ||
|
||||||
(orientation == Orientation.portrait && screenWidth < screenHeight) ||
|
(orientation == Orientation.portrait && screenWidth < screenHeight) ||
|
||||||
(orientation == Orientation.landscape && screenWidth < screenHeight);
|
(orientation == Orientation.landscape && screenWidth < screenHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get shouldRenderTabletUI {
|
bool get shouldRenderTabletUI {
|
||||||
return screenWidth > _kMobileThreshold && screenWidth < kDesktopMaxDashBoardWidthConstraint;
|
return screenWidth > kMobileThreshold && screenWidth < kDesktopMaxDashBoardWidthConstraint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||||
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
|
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
|
||||||
import 'package:cake_wallet/entities/contact_base.dart';
|
import 'package:cake_wallet/entities/contact_base.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_contact.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:cake_wallet/store/settings_store.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
@ -40,16 +41,19 @@ abstract class ContactListViewModelBase with Store {
|
||||||
});
|
});
|
||||||
} else if (info.addresses?.isNotEmpty == true) {
|
} else if (info.addresses?.isNotEmpty == true) {
|
||||||
info.addresses!.forEach((address, label) {
|
info.addresses!.forEach((address, label) {
|
||||||
|
if (label.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final name = _createName(info.name, label);
|
final name = _createName(info.name, label);
|
||||||
walletContacts.add(WalletContact(
|
walletContacts.add(WalletContact(
|
||||||
address,
|
address,
|
||||||
name,
|
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(
|
walletContacts.add(WalletContact(
|
||||||
info.address,
|
info.address,
|
||||||
info.name,
|
info.name,
|
||||||
|
@ -64,7 +68,9 @@ abstract class ContactListViewModelBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
String _createName(String walletName, String label) {
|
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;
|
final bool isAutoGenerateEnabled;
|
||||||
|
|
|
@ -60,6 +60,9 @@ abstract class BalanceViewModelBase with Store {
|
||||||
@observable
|
@observable
|
||||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
|
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get hasSilentPayments => wallet.type == WalletType.bitcoin;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
double get price {
|
double get price {
|
||||||
final price = fiatConvertationStore.prices[appStore.wallet!.currency];
|
final price = fiatConvertationStore.prices[appStore.wallet!.currency];
|
||||||
|
|
16
lib/view_model/dashboard/cake_features_view_model.dart
Normal file
16
lib/view_model/dashboard/cake_features_view_model.dart
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,6 +45,7 @@ import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:eth_sig_util/util/utils.dart';
|
import 'package:eth_sig_util/util/utils.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
@ -201,6 +202,14 @@ abstract class DashboardViewModelBase with Store {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (hasSilentPayments) {
|
||||||
|
silentPaymentsScanningActive = bitcoin!.getScanningActive(wallet);
|
||||||
|
|
||||||
|
reaction((_) => wallet.syncStatus, (SyncStatus syncStatus) {
|
||||||
|
silentPaymentsScanningActive = bitcoin!.getScanningActive(wallet);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
|
@ -287,11 +296,36 @@ abstract class DashboardViewModelBase with Store {
|
||||||
@observable
|
@observable
|
||||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
|
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 KeyService keyService;
|
||||||
final SharedPreferences sharedPreferences;
|
final SharedPreferences sharedPreferences;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool silentPaymentsScanningActive = false;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void setSilentPaymentsScanning(bool active) {
|
||||||
|
silentPaymentsScanningActive = active;
|
||||||
|
|
||||||
|
if (hasSilentPayments) {
|
||||||
|
bitcoin!.setScanningActive(wallet, active);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BalanceViewModel balanceViewModel;
|
BalanceViewModel balanceViewModel;
|
||||||
|
|
||||||
AppStore appStore;
|
AppStore appStore;
|
||||||
|
@ -535,24 +569,29 @@ abstract class DashboardViewModelBase with Store {
|
||||||
|
|
||||||
Future<ServicesResponse> getServicesStatus() async {
|
Future<ServicesResponse> getServicesStatus() async {
|
||||||
try {
|
try {
|
||||||
final res = await http.get(Uri.parse("https://service-api.cakewallet.com/v1/active-notices"));
|
if (isEnabledBulletinAction) {
|
||||||
|
final res = await http.get(Uri.parse("https://service-api.cakewallet.com/v1/active-notices"));
|
||||||
|
|
||||||
if (res.statusCode < 200 || res.statusCode >= 300) {
|
if (res.statusCode < 200 || res.statusCode >= 300) {
|
||||||
throw res.body;
|
throw res.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
final oldSha = sharedPreferences.getString(PreferencesKey.serviceStatusShaKey);
|
||||||
|
|
||||||
|
final hash = await Cryptography.instance.sha256().hash(utf8.encode(res.body));
|
||||||
|
final currentSha = bytesToHex(hash.bytes);
|
||||||
|
|
||||||
|
final hasUpdates = oldSha != currentSha;
|
||||||
|
|
||||||
|
return ServicesResponse.fromJson(
|
||||||
|
json.decode(res.body) as Map<String, dynamic>,
|
||||||
|
hasUpdates,
|
||||||
|
currentSha,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
final oldSha = sharedPreferences.getString(PreferencesKey.serviceStatusShaKey);
|
return ServicesResponse([], false, '');
|
||||||
|
}
|
||||||
final hash = await Cryptography.instance.sha256().hash(utf8.encode(res.body));
|
|
||||||
final currentSha = bytesToHex(hash.bytes);
|
|
||||||
|
|
||||||
final hasUpdates = oldSha != currentSha;
|
|
||||||
|
|
||||||
return ServicesResponse.fromJson(
|
|
||||||
json.decode(res.body) as Map<String, dynamic>,
|
|
||||||
hasUpdates,
|
|
||||||
currentSha,
|
|
||||||
);
|
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return ServicesResponse([], false, '');
|
return ServicesResponse([], false, '');
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -154,11 +154,11 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
||||||
ChangeNowExchangeProvider(settingsStore: _settingsStore),
|
ChangeNowExchangeProvider(settingsStore: _settingsStore),
|
||||||
SideShiftExchangeProvider(),
|
SideShiftExchangeProvider(),
|
||||||
SimpleSwapExchangeProvider(),
|
SimpleSwapExchangeProvider(),
|
||||||
TrocadorExchangeProvider(
|
|
||||||
useTorOnly: _useTorOnly, providerStates: _settingsStore.trocadorProviderStates),
|
|
||||||
ThorChainExchangeProvider(tradesStore: trades),
|
ThorChainExchangeProvider(tradesStore: trades),
|
||||||
if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(),
|
if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(),
|
||||||
QuantexExchangeProvider(),
|
QuantexExchangeProvider(),
|
||||||
|
TrocadorExchangeProvider(
|
||||||
|
useTorOnly: _useTorOnly, providerStates: _settingsStore.trocadorProviderStates),
|
||||||
];
|
];
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
part 'rescan_view_model.g.dart';
|
part 'rescan_view_model.g.dart';
|
||||||
|
@ -8,11 +10,12 @@ class RescanViewModel = RescanViewModelBase with _$RescanViewModel;
|
||||||
enum RescanWalletState { rescaning, none }
|
enum RescanWalletState { rescaning, none }
|
||||||
|
|
||||||
abstract class RescanViewModelBase with Store {
|
abstract class RescanViewModelBase with Store {
|
||||||
RescanViewModelBase(this._wallet)
|
RescanViewModelBase(this.wallet)
|
||||||
: state = RescanWalletState.none,
|
: state = RescanWalletState.none,
|
||||||
isButtonEnabled = false;
|
isButtonEnabled = false,
|
||||||
|
doSingleScan = false;
|
||||||
|
|
||||||
final WalletBase _wallet;
|
final WalletBase wallet;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
RescanWalletState state;
|
RescanWalletState state;
|
||||||
|
@ -20,11 +23,21 @@ abstract class RescanViewModelBase with Store {
|
||||||
@observable
|
@observable
|
||||||
bool isButtonEnabled;
|
bool isButtonEnabled;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool doSingleScan;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get isSilentPaymentsScan => wallet.type == WalletType.bitcoin;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> rescanCurrentWallet({required int restoreHeight}) async {
|
Future<void> rescanCurrentWallet({required int restoreHeight}) async {
|
||||||
state = RescanWalletState.rescaning;
|
state = RescanWalletState.rescaning;
|
||||||
await _wallet.rescan(height: restoreHeight);
|
if (wallet.type != WalletType.bitcoin) {
|
||||||
_wallet.transactionHistory.clear();
|
wallet.rescan(height: restoreHeight);
|
||||||
|
wallet.transactionHistory.clear();
|
||||||
|
} else {
|
||||||
|
bitcoin!.rescan(wallet, height: restoreHeight, doSingleScan: doSingleScan);
|
||||||
|
}
|
||||||
state = RescanWalletState.none;
|
state = RescanWalletState.none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,8 +139,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||||
String get pendingTransactionFeeFiatAmount {
|
String get pendingTransactionFeeFiatAmount {
|
||||||
try {
|
try {
|
||||||
if (pendingTransaction != null) {
|
if (pendingTransaction != null) {
|
||||||
final currency =
|
final currency = pendingTransactionFeeCurrency(walletType);
|
||||||
isEVMCompatibleChain(walletType) ? wallet.currency : selectedCryptoCurrency;
|
|
||||||
final fiat = calculateFiatAmount(
|
final fiat = calculateFiatAmount(
|
||||||
price: _fiatConversationStore.prices[currency]!,
|
price: _fiatConversationStore.prices[currency]!,
|
||||||
cryptoAmount: pendingTransaction!.feeFormatted);
|
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;
|
FiatCurrency get fiat => _settingsStore.fiatCurrency;
|
||||||
|
|
||||||
TransactionPriority get transactionPriority {
|
TransactionPriority get transactionPriority {
|
||||||
|
@ -218,7 +229,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||||
isFiatDisabled ? '' : pendingTransactionFeeFiatAmount + ' ' + fiat.title;
|
isFiatDisabled ? '' : pendingTransactionFeeFiatAmount + ' ' + fiat.title;
|
||||||
|
|
||||||
@computed
|
@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
|
@computed
|
||||||
List<Template> get templates => sendTemplateViewModel.templates
|
List<Template> get templates => sendTemplateViewModel.templates
|
||||||
|
@ -588,6 +602,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||||
walletType == WalletType.litecoin ||
|
walletType == WalletType.litecoin ||
|
||||||
walletType == WalletType.bitcoinCash) {
|
walletType == WalletType.bitcoinCash) {
|
||||||
if (error is TransactionWrongBalanceException) {
|
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());
|
return S.current.tx_wrong_balance_exception(currency.toString());
|
||||||
}
|
}
|
||||||
if (error is TransactionNoInputsException) {
|
if (error is TransactionNoInputsException) {
|
||||||
|
@ -614,9 +632,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||||
if (error is TransactionCommitFailedVoutNegative) {
|
if (error is TransactionCommitFailedVoutNegative) {
|
||||||
return S.current.tx_rejected_vout_negative;
|
return S.current.tx_rejected_vout_negative;
|
||||||
}
|
}
|
||||||
|
if (error is TransactionCommitFailedBIP68Final) {
|
||||||
|
return S.current.tx_rejected_bip68_final;
|
||||||
|
}
|
||||||
if (error is TransactionNoDustOnChangeException) {
|
if (error is TransactionNoDustOnChangeException) {
|
||||||
return S.current.tx_commit_exception_no_dust_on_change(error.min, error.max);
|
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;
|
return errorMessage;
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,8 @@ abstract class UnspentCoinsItemBase with Store {
|
||||||
required this.isChange,
|
required this.isChange,
|
||||||
required this.amountRaw,
|
required this.amountRaw,
|
||||||
required this.vout,
|
required this.vout,
|
||||||
required this.keyImage
|
required this.keyImage,
|
||||||
|
required this.isSilentPayment,
|
||||||
});
|
});
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
|
@ -47,4 +48,7 @@ abstract class UnspentCoinsItemBase with Store {
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
String? keyImage;
|
String? keyImage;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool isSilentPayment;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||||
import 'package:cake_wallet/monero/monero.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:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:cw_core/unspent_transaction_output.dart';
|
import 'package:cw_core/unspent_transaction_output.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
|
@ -86,22 +88,33 @@ abstract class UnspentCoinsListViewModelBase with Store {
|
||||||
@action
|
@action
|
||||||
void _updateUnspentCoinsInfo() {
|
void _updateUnspentCoinsInfo() {
|
||||||
_items.clear();
|
_items.clear();
|
||||||
_items.addAll(_getUnspents().map((elem) {
|
|
||||||
final info =
|
|
||||||
getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
|
|
||||||
|
|
||||||
return UnspentCoinsItem(
|
List<UnspentCoinsItem> unspents = [];
|
||||||
address: elem.address,
|
_getUnspents().forEach((elem) {
|
||||||
amount: '${formatAmountToString(elem.value)} ${wallet.currency.title}',
|
try {
|
||||||
hash: elem.hash,
|
final info =
|
||||||
isFrozen: info.isFrozen,
|
getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
|
||||||
note: info.note,
|
|
||||||
isSending: info.isSending,
|
unspents.add(UnspentCoinsItem(
|
||||||
amountRaw: elem.value,
|
address: elem.address,
|
||||||
vout: elem.vout,
|
amount: '${formatAmountToString(elem.value)} ${wallet.currency.title}',
|
||||||
keyImage: elem.keyImage,
|
hash: elem.hash,
|
||||||
isChange: elem.isChange,
|
isFrozen: info.isFrozen,
|
||||||
);
|
note: info.note,
|
||||||
}));
|
isSending: info.isSending,
|
||||||
|
amountRaw: elem.value,
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
import 'package:cake_wallet/utils/list_item.dart';
|
import 'package:cake_wallet/utils/list_item.dart';
|
||||||
|
|
||||||
class WalletAddressListHeader extends ListItem {}
|
class WalletAddressListHeader extends ListItem {
|
||||||
|
final String? title;
|
||||||
|
WalletAddressListHeader({this.title});
|
||||||
|
}
|
||||||
|
|
|
@ -8,8 +8,10 @@ class WalletAddressListItem extends ListItem {
|
||||||
this.name,
|
this.name,
|
||||||
this.txCount,
|
this.txCount,
|
||||||
this.balance,
|
this.balance,
|
||||||
this.isChange = false})
|
this.isChange = false,
|
||||||
: super();
|
// 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 int? id;
|
||||||
final bool isPrimary;
|
final bool isPrimary;
|
||||||
|
@ -18,7 +20,8 @@ class WalletAddressListItem extends ListItem {
|
||||||
final int? txCount;
|
final int? txCount;
|
||||||
final String? balance;
|
final String? balance;
|
||||||
final bool isChange;
|
final bool isChange;
|
||||||
|
final bool? isOneTimeReceiveAddress;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => name ?? address;
|
String toString() => name ?? address;
|
||||||
}
|
}
|
||||||
|
|
|
@ -334,20 +334,55 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isElectrumWallet) {
|
if (isElectrumWallet) {
|
||||||
final addressItems = bitcoin!.getSubAddresses(wallet).map((subaddress) {
|
if (bitcoin!.hasSelectedSilentPayments(wallet)) {
|
||||||
final isPrimary = subaddress.id == 0;
|
final addressItems = bitcoin!.getSilentPaymentAddresses(wallet).map((address) {
|
||||||
|
final isPrimary = address.id == 0;
|
||||||
|
|
||||||
return WalletAddressListItem(
|
return WalletAddressListItem(
|
||||||
id: subaddress.id,
|
id: address.id,
|
||||||
isPrimary: isPrimary,
|
isPrimary: isPrimary,
|
||||||
name: subaddress.name,
|
name: address.name,
|
||||||
address: subaddress.address,
|
address: address.address,
|
||||||
txCount: subaddress.txCount,
|
txCount: address.txCount,
|
||||||
balance: AmountConverter.amountIntToString(
|
balance: AmountConverter.amountIntToString(
|
||||||
walletTypeToCryptoCurrency(type), subaddress.balance),
|
walletTypeToCryptoCurrency(type), address.balance),
|
||||||
isChange: subaddress.isChange);
|
isChange: address.isChange,
|
||||||
});
|
);
|
||||||
addressList.addAll(addressItems);
|
});
|
||||||
|
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;
|
||||||
|
|
||||||
|
return WalletAddressListItem(
|
||||||
|
id: subaddress.id,
|
||||||
|
isPrimary: isPrimary,
|
||||||
|
name: subaddress.name,
|
||||||
|
address: subaddress.address,
|
||||||
|
txCount: subaddress.txCount,
|
||||||
|
balance: AmountConverter.amountIntToString(
|
||||||
|
walletTypeToCryptoCurrency(type), subaddress.balance),
|
||||||
|
isChange: subaddress.isChange);
|
||||||
|
});
|
||||||
|
addressList.addAll(addressItems);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wallet.type == WalletType.ethereum) {
|
if (wallet.type == WalletType.ethereum) {
|
||||||
|
@ -416,9 +451,14 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||||
wallet.type == WalletType.litecoin ||
|
wallet.type == WalletType.litecoin ||
|
||||||
wallet.type == WalletType.bitcoinCash;
|
wallet.type == WalletType.bitcoinCash;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get isSilentPayments =>
|
||||||
|
wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet);
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
bool get isAutoGenerateSubaddressEnabled =>
|
bool get isAutoGenerateSubaddressEnabled =>
|
||||||
_settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled;
|
_settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled &&
|
||||||
|
!isSilentPayments;
|
||||||
|
|
||||||
List<ListItem> _baseItems;
|
List<ListItem> _baseItems;
|
||||||
|
|
||||||
|
@ -478,4 +518,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||||
amount = '';
|
amount = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void deleteAddress(ListItem item) {
|
||||||
|
if (wallet.type == WalletType.bitcoin && item is WalletAddressListItem) {
|
||||||
|
bitcoin!.deleteSilentPaymentAddress(wallet, item.address);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ class WalletListItem {
|
||||||
required this.key,
|
required this.key,
|
||||||
this.isCurrent = false,
|
this.isCurrent = false,
|
||||||
this.isEnabled = true,
|
this.isEnabled = true,
|
||||||
|
this.isTestnet = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
|
@ -14,4 +15,5 @@ class WalletListItem {
|
||||||
final bool isCurrent;
|
final bool isCurrent;
|
||||||
final dynamic key;
|
final dynamic key;
|
||||||
final bool isEnabled;
|
final bool isEnabled;
|
||||||
|
final bool isTestnet;
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,7 @@ abstract class WalletListViewModelBase with Store {
|
||||||
key: info.key,
|
key: info.key,
|
||||||
isCurrent: info.name == _appStore.wallet?.name && info.type == _appStore.wallet?.type,
|
isCurrent: info.name == _appStore.wallet?.name && info.type == _appStore.wallet?.type,
|
||||||
isEnabled: availableWalletTypes.contains(info.type),
|
isEnabled: availableWalletTypes.contains(info.type),
|
||||||
|
isTestnet: info.network?.toLowerCase().contains('testnet') ?? false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -47,6 +47,8 @@ PODS:
|
||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- sp_scanner (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
- url_launcher_macos (0.0.1):
|
- url_launcher_macos (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- wakelock_plus (0.0.1):
|
- wakelock_plus (0.0.1):
|
||||||
|
@ -67,6 +69,7 @@ DEPENDENCIES:
|
||||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- share_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos`)
|
- share_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos`)
|
||||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- 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`)
|
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||||
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/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
|
:path: Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
||||||
|
sp_scanner:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/sp_scanner/macos
|
||||||
url_launcher_macos:
|
url_launcher_macos:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||||
wakelock_plus:
|
wakelock_plus:
|
||||||
|
@ -126,7 +131,8 @@ SPEC CHECKSUMS:
|
||||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||||
share_plus_macos: 853ee48e7dce06b633998ca0735d482dd671ade4
|
share_plus_macos: 853ee48e7dce06b633998ca0735d482dd671ade4
|
||||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||||
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
|
sp_scanner: 269d96e0ec3173e69156be7239b95182be3b8303
|
||||||
|
url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399
|
||||||
wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269
|
wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269
|
||||||
|
|
||||||
PODFILE CHECKSUM: 65ec1541137fb5b35d00490dec1bb48d4d9586bb
|
PODFILE CHECKSUM: 65ec1541137fb5b35d00490dec1bb48d4d9586bb
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue