Merge branch 'main' into CW-292-Save-historical-fiat-API-rate

This commit is contained in:
Serhii 2024-06-02 10:59:53 +03:00
commit 67bc9a7c9f
167 changed files with 5302 additions and 1871 deletions

View file

@ -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
@ -151,6 +151,7 @@ jobs:
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart
echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
echo "const quantexExchangeMarkup = '${{ secrets.QUANTEX_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart
echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart

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/quantex.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
assets/images/tbtc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -1 +1 @@
Generic bug fixes and enhancements Bug fixes and generic enhancements

View file

@ -1,4 +1 @@
Hardware wallets support for Bitcoin, Ethereum and Polygon
Add Tron wallet
Security enhancements
Bug fixes and generic enhancements Bug fixes and generic enhancements

View file

@ -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,
});
}

View file

@ -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;

View file

@ -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;
} }

View file

@ -1,23 +1,24 @@
import 'package:bitcoin_base/bitcoin_base.dart'; import 'dart:convert';
import 'package:convert/convert.dart';
import 'package:bip39/bip39.dart' as bip39;
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:convert/convert.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
import 'package:cw_bitcoin/psbt_transaction_builder.dart'; import 'package:cw_bitcoin/psbt_transaction_builder.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.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:flutter/foundation.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:ledger_bitcoin/ledger_bitcoin.dart'; import 'package:ledger_bitcoin/ledger_bitcoin.dart';
import 'package:ledger_flutter/ledger_flutter.dart'; import 'package:ledger_flutter/ledger_flutter.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:flutter/foundation.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
import 'package:bip39/bip39.dart' as bip39;
part 'bitcoin_wallet.g.dart'; part 'bitcoin_wallet.g.dart';
@ -38,6 +39,9 @@ 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,
@ -53,7 +57,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
initialBalance: initialBalance, initialBalance: initialBalance,
seedBytes: seedBytes, seedBytes: seedBytes,
currency: CryptoCurrency.btc) { 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!;
@ -61,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;
}); });
@ -83,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;
@ -108,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,
@ -122,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!)
@ -162,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,
); );
} }
@ -178,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
@ -210,9 +230,29 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
)); ));
} }
final psbt = PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF); final psbt =
PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt); final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt);
return BtcTransaction.fromRaw(hex.encode(rawHex)); return BtcTransaction.fromRaw(hex.encode(rawHex));
} }
@override
Future<String> signMessage(String message, {String? address = null}) async {
if (walletInfo.isHardwareWallet) {
final addressEntry = address != null
? walletAddresses.allAddresses.firstWhere((element) => element.address == address)
: null;
final index = addressEntry?.index ?? 0;
final isChange = addressEntry?.isHidden == true ? 1 : 0;
final accountPath = walletInfo.derivationInfo?.derivationPath;
final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null;
final signature = await _bitcoinLedgerApp!
.signMessage(_ledgerDevice!, message: ascii.encode(message), signDerivationPath: derivationPath);
return base64Encode(signature);
}
return super.signMessage(message, address: address);
}
} }

View file

@ -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

View file

@ -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;
@ -58,7 +59,9 @@ class BitcoinWalletService extends WalletService<
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;
@ -68,7 +71,9 @@ class BitcoinWalletService extends WalletService<
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;
} }
@ -90,7 +95,9 @@ class BitcoinWalletService extends WalletService<
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,

View file

@ -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 (_) {}
if (useSSL == false) {
socket = await Socket.connect(host, port, timeout: connectionTimeout);
} else {
socket = await SecureSocket.connect(host, port, socket = await SecureSocket.connect(host, port,
timeout: connectionTimeout, onBadCertificate: (_) => true); 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();
try {
await socket?.close(); 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) {

View file

@ -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

View file

@ -28,6 +28,12 @@ Map<DerivationType, List<DerivationInfo>> electrum_derivations = {
description: "Standard BIP84 native segwit", description: "Standard BIP84 native segwit",
scriptType: "p2wpkh", scriptType: "p2wpkh",
), ),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/86'/0'/0'",
description: "Standard BIP86 Taproot",
scriptType: "p2tr",
),
DerivationInfo( DerivationInfo(
derivationType: DerivationType.bip39, derivationType: DerivationType.bip39,
derivationPath: "m/0'", derivationPath: "m/0'",

View file

@ -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;
} }

View file

@ -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,42 +157,13 @@ 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>? ?? [];
final outputAddresses = data['outputAddresses'] as List<dynamic>? ?? [];
final unspents = data['unspents'] as List<dynamic>? ?? [];
return ElectrumTransactionInfo(
type,
id: data['id'] as String, id: data['id'] as String,
height: data['height'] as int, height: data['height'] as int,
amount: data['amount'] as int, amount: data['amount'] as int,
@ -196,9 +171,17 @@ class ElectrumTransactionInfo extends TransactionInfo {
direction: parseTransactionDirectionFromInt(data['direction'] as int), direction: parseTransactionDirectionFromInt(data['direction'] as int),
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int), date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
isPending: data['isPending'] as bool, isPending: data['isPending'] as bool,
inputAddresses: data['inputAddresses'] as List<String>, confirmations: data['confirmations'] as int,
outputAddresses: data['outputAddresses'] as List<String>, inputAddresses:
confirmations: data['confirmations'] as int); 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

View file

@ -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();
}
} }

View file

@ -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,
); );
} }
} }

View file

@ -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 {}

View file

@ -14,7 +14,6 @@ import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/litecoin_network.dart'; import 'package:cw_bitcoin/litecoin_network.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bip39/bip39.dart' as bip39; import 'package:bip39/bip39.dart' as bip39;
part 'litecoin_wallet.g.dart'; part 'litecoin_wallet.g.dart';
@ -45,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,

View file

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

View file

@ -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);
} }

View file

@ -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:
@ -217,10 +226,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.1" version: "1.18.0"
convert: convert:
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,19 +459,43 @@ 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:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
ledger_bitcoin: ledger_bitcoin:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: HEAD ref: HEAD
resolved-ref: b6ed573cbeb57d5f0d39dfe4254bf9d15b620ab6 resolved-ref: f819d37e235e239c315e93856abbf5e5d3b71dab
url: "https://github.com/cake-tech/ledger-bitcoin.git" url: "https://github.com/cake-tech/ledger-bitcoin"
source: git source: git
version: "0.0.1" version: "0.0.2"
ledger_flutter: ledger_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
@ -483,34 +524,34 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.15" version: "0.12.16+1"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.0" version: "0.8.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.11.0"
mime: mime:
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:
@ -547,34 +588,34 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.3" version: "1.9.0"
path_provider: path_provider:
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:
@ -619,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:
@ -663,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:
@ -712,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:
@ -736,26 +785,35 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_span name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.10.0"
sp_scanner:
dependency: "direct main"
description:
path: "."
ref: "sp_v2.0.0"
resolved-ref: "62c152b9086cd968019128845371072f7e1168de"
url: "https://github.com/cake-tech/sp_scanner"
source: git
version: "0.0.1"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.0" version: "1.11.1"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
stream_transform: stream_transform:
dependency: transitive dependency: transitive
description: description:
@ -784,10 +842,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.1" version: "0.6.1"
timing: timing:
dependency: transitive dependency: transitive
description: description:
@ -820,30 +878,46 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
watcher: vm_service:
dependency: transitive dependency: transitive
description:
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev"
source: hosted
version: "13.0.0"
watcher:
dependency: "direct overridden"
description: description:
name: watcher name: watcher
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
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:
@ -860,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.0.6 <4.0.0" dart: ">=3.3.0 <4.0.0"
flutter: ">=3.10.0" flutter: ">=3.16.6"

View file

@ -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.git url: https://github.com/cake-tech/ledger-bitcoin
sp_scanner:
git:
url: https://github.com/cake-tech/sp_scanner
ref: sp_v2.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View file

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

View file

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

View file

@ -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:

View file

@ -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 = [
@ -219,6 +220,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
static const digibyte = CryptoCurrency(title: 'DGB', fullName: 'DigiByte', raw: 90, name: 'dgb', iconPath: 'assets/images/digibyte.png', decimals: 8); static const 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));

View file

@ -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;

View file

@ -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 {}

View file

@ -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;
}

View file

@ -244,8 +244,12 @@ class Node extends HiveObject with Keyable {
Future<bool> requestElectrumServer() async { Future<bool> requestElectrumServer() async {
try { try {
if (useSSL == true) {
await SecureSocket.connect(uri.host, uri.port, await SecureSocket.connect(uri.host, uri.port,
timeout: Duration(seconds: 5), onBadCertificate: (_) => true); 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;

View file

@ -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';
} }

View file

@ -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;
@ -56,6 +57,9 @@ class UnspentCoinsInfo extends HiveObject {
@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;

View file

@ -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');
} }

View file

@ -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;

View file

@ -80,7 +80,7 @@ class WalletInfo extends HiveObject {
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();
}
} }

View file

@ -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;

View file

@ -149,10 +149,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.1" version: "1.18.0"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -331,6 +331,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.8.1" version: "4.8.1"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@ -343,26 +367,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.15" version: "0.12.16+1"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.0" version: "0.8.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.11.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@ -407,10 +431,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.3" version: "1.9.0"
path_provider: path_provider:
dependency: "direct main" dependency: "direct main"
description: description:
@ -564,26 +588,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_span name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.10.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.0" version: "1.11.1"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
stream_transform: stream_transform:
dependency: transitive dependency: transitive
description: description:
@ -612,10 +636,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.1" version: "0.6.1"
timing: timing:
dependency: transitive dependency: transitive
description: description:
@ -640,8 +664,16 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
watcher: vm_service:
dependency: transitive dependency: transitive
description:
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev"
source: hosted
version: "13.0.0"
watcher:
dependency: "direct overridden"
description: description:
name: watcher name: watcher
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
@ -681,5 +713,5 @@ packages:
source: hosted source: hosted
version: "3.1.2" version: "3.1.2"
sdks: sdks:
dart: ">=3.0.0 <4.0.0" dart: ">=3.2.0-0 <4.0.0"
flutter: ">=3.10.0" flutter: ">=3.10.0"

View file

@ -149,10 +149,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.2" version: "1.18.0"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -338,6 +338,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.8.0" version: "4.8.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@ -350,26 +374,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.16" version: "0.12.16+1"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.0" version: "0.8.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.11.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@ -406,10 +430,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.3" version: "1.9.0"
path_provider: path_provider:
dependency: "direct main" dependency: "direct main"
description: description:
@ -571,18 +595,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.0" version: "1.11.1"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
stream_transform: stream_transform:
dependency: transitive dependency: transitive
description: description:
@ -611,10 +635,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.0" version: "0.6.1"
timing: timing:
dependency: transitive dependency: transitive
description: description:
@ -639,6 +663,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev"
source: hosted
version: "13.0.0"
watcher: watcher:
dependency: "direct overridden" dependency: "direct overridden"
description: description:
@ -647,14 +679,6 @@ 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: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
@ -688,5 +712,5 @@ packages:
source: hosted source: hosted
version: "3.1.1" version: "3.1.1"
sdks: sdks:
dart: ">=3.1.0-185.0.dev <4.0.0" dart: ">=3.2.0-0 <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.7.0"

View file

@ -53,10 +53,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.2" version: "1.18.0"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -201,6 +201,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.7" version: "0.6.7"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@ -213,26 +237,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.16" version: "0.12.16+1"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.0" version: "0.8.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.11.0"
mobx: mobx:
dependency: transitive dependency: transitive
description: description:
@ -245,10 +269,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.3" version: "1.9.0"
path_provider: path_provider:
dependency: transitive dependency: transitive
description: description:
@ -362,18 +386,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.0" version: "1.11.1"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
string_scanner: string_scanner:
dependency: transitive dependency: transitive
description: description:
@ -394,10 +418,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.0" version: "0.6.1"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -414,14 +438,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
web: vm_service:
dependency: transitive dependency: transitive
description: description:
name: web name: vm_service
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.4-beta" version: "13.0.0"
win32: win32:
dependency: transitive dependency: transitive
description: description:
@ -439,5 +463,5 @@ packages:
source: hosted source: hosted
version: "0.2.0+3" version: "0.2.0+3"
sdks: sdks:
dart: ">=3.1.0-185.0.dev <4.0.0" dart: ">=3.2.0-0 <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.7.0"

View file

@ -464,6 +464,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()
{ {

View file

@ -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);

View file

@ -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>);

View file

@ -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();
@ -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));
@ -242,17 +233,13 @@ 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)
@ -260,7 +247,6 @@ class SyncListener {
_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;
}

View file

@ -149,10 +149,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.2" version: "1.18.0"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -354,6 +354,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.8.0" version: "4.8.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@ -366,26 +390,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.16" version: "0.12.16+1"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.0" version: "0.8.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.11.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@ -422,10 +446,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.3" version: "1.9.0"
path_provider: path_provider:
dependency: "direct main" dependency: "direct main"
description: description:
@ -595,18 +619,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.0" version: "1.11.1"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
stream_transform: stream_transform:
dependency: transitive dependency: transitive
description: description:
@ -635,10 +659,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.0" version: "0.6.1"
timing: timing:
dependency: transitive dependency: transitive
description: description:
@ -663,6 +687,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev"
source: hosted
version: "13.0.0"
watcher: watcher:
dependency: "direct overridden" dependency: "direct overridden"
description: description:
@ -671,14 +703,6 @@ 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: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
@ -712,5 +736,5 @@ packages:
source: hosted source: hosted
version: "3.1.1" version: "3.1.1"
sdks: sdks:
dart: ">=3.1.0-185.0.dev <4.0.0" dart: ">=3.2.0-0 <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.7.0"

View file

@ -173,10 +173,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.1" version: "1.18.0"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -399,6 +399,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.8.1" version: "4.8.1"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
libcrypto: libcrypto:
dependency: "direct main" dependency: "direct main"
description: description:
@ -419,26 +443,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.15" version: "0.12.16+1"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.0" version: "0.8.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.11.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@ -492,10 +516,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.3" version: "1.9.0"
path_provider: path_provider:
dependency: transitive dependency: transitive
description: description:
@ -713,26 +737,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_span name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.10.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.0" version: "1.11.1"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
stream_transform: stream_transform:
dependency: transitive dependency: transitive
description: description:
@ -761,10 +785,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.1" version: "0.6.1"
timing: timing:
dependency: transitive dependency: transitive
description: description:
@ -789,8 +813,16 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
watcher: vm_service:
dependency: transitive dependency: transitive
description:
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev"
source: hosted
version: "13.0.0"
watcher:
dependency: "direct overridden"
description: description:
name: watcher name: watcher
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
@ -830,5 +862,5 @@ packages:
source: hosted source: hosted
version: "3.1.2" version: "3.1.2"
sdks: sdks:
dart: ">=3.0.0 <4.0.0" dart: ">=3.2.0-0 <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.7.0"

View file

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

View file

@ -19,30 +19,6 @@ class DefaultSPLTokens {
mint: 'usdcsol', 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,
), ),
]; ];

View file

@ -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;
} }

View file

@ -130,11 +130,6 @@ class TronClient {
final energyInSun = chainParams.getEnergyFee!; final energyInSun = chainParams.getEnergyFee!;
log('Energy In Sun: $energyInSun'); log('Energy In Sun: $energyInSun');
log(
'Create Account Fee In System Contract for Chain: ${chainParams.getCreateNewAccountFeeInSystemContract!}',
);
log('Create Account Fee for Chain: ${chainParams.getCreateAccountFee}');
final fakeTransaction = Transaction( final fakeTransaction = Transaction(
rawData: rawTransaction, rawData: rawTransaction,
signature: [Uint8List(65)], signature: [Uint8List(65)],
@ -185,17 +180,6 @@ class TronClient {
totalBurn += chainParams.getMemoFee!; totalBurn += chainParams.getMemoFee!;
} }
// Check if receiver's account is active
final receiverAccountInfo =
await _provider!.request(TronRequestGetAccount(address: receiverAddress));
/// Calculate the resources required to create a new account.
if (receiverAccountInfo == null) {
totalBurn += chainParams.getCreateNewAccountFeeInSystemContract!;
totalBurn += (chainParams.getCreateAccountFee! * bandWidthInSun);
}
log('Final total burn: $totalBurn'); log('Final total burn: $totalBurn');
return totalBurn; return totalBurn;
@ -224,7 +208,7 @@ class TronClient {
TransactionContract(type: contract.contractType, parameter: parameter); TransactionContract(type: contract.contractType, parameter: parameter);
// Set the transaction expiration time (maximum 24 hours) // Set the transaction expiration time (maximum 24 hours)
final expireTime = DateTime.now().toUtc().add(const Duration(hours: 24)); final expireTime = DateTime.now().add(const Duration(minutes: 30));
// Create a raw transaction // Create a raw transaction
TransactionRaw rawTransaction = TransactionRaw( TransactionRaw rawTransaction = TransactionRaw(
@ -385,7 +369,7 @@ class TronClient {
TransactionContract(type: contract.contractType, parameter: parameter); TransactionContract(type: contract.contractType, parameter: parameter);
// Set the transaction expiration time (maximum 24 hours) // Set the transaction expiration time (maximum 24 hours)
final expireTime = DateTime.now().toUtc().add(const Duration(hours: 24)); final expireTime = DateTime.now().add(const Duration(minutes: 30));
// Create a raw transaction // Create a raw transaction
TransactionRaw rawTransaction = TransactionRaw( TransactionRaw rawTransaction = TransactionRaw(
@ -403,7 +387,7 @@ class TronClient {
if (feeLimit > tronBalanceInt) { if (feeLimit > tronBalanceInt) {
final feeInTrx = TronHelper.fromSun(BigInt.parse(feeLimit.toString())); final feeInTrx = TronHelper.fromSun(BigInt.parse(feeLimit.toString()));
throw Exception( throw Exception(
'You don\'t have enough TRX to cover the transaction fee for this transaction. Kindly top up.\nTransaction fee: $feeInTrx TRX', 'You don\'t have enough TRX to cover the transaction fee for this transaction. Please top up.\nTransaction fee: $feeInTrx TRX',
); );
} }
@ -460,7 +444,7 @@ class TronClient {
if (feeLimit > tronBalanceInt) { if (feeLimit > tronBalanceInt) {
final feeInTrx = TronHelper.fromSun(BigInt.parse(feeLimit.toString())); final feeInTrx = TronHelper.fromSun(BigInt.parse(feeLimit.toString()));
throw Exception( throw Exception(
'You don\'t have enough TRX to cover the transaction fee for this transaction. Kindly top up. Transaction fee: $feeInTrx TRX', 'You don\'t have enough TRX to cover the transaction fee for this transaction. Please top up. Transaction fee: $feeInTrx TRX',
); );
} }

View file

@ -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

View file

@ -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!

View file

@ -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,12 +307,13 @@ 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
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
UnstoppableDomainsResolution: c3c67f4d0a5e2437cb00d4bd50c2e00d6e743841 UnstoppableDomainsResolution: c3c67f4d0a5e2437cb00d4bd50c2e00d6e743841
url_launcher_ios: 6116280ddcfe98ab8820085d8d76ae7449447586 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47 wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6 workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6

View file

@ -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;
@ -371,6 +369,9 @@ class CWBitcoin extends Bitcoin {
case "p2wpkh-p2sh": case "p2wpkh-p2sh":
address = generateP2SHAddress(hd: hd, network: network); address = generateP2SHAddress(hd: hd, network: network);
break; break;
case "p2tr":
address = generateP2TRAddress(hd: hd, network: network);
break;
default: default:
continue; continue;
} }
@ -462,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();
}
} }

View file

@ -281,6 +281,7 @@ class MoonPayProvider extends BuyProvider {
throw Exception('Could not launch URL'); throw Exception('Could not launch URL');
} }
} catch (e) { } catch (e) {
if (context.mounted) {
await showDialog<void>( await showDialog<void>(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
@ -294,6 +295,7 @@ class MoonPayProvider extends BuyProvider {
); );
} }
} }
}
String _normalizeCurrency(CryptoCurrency currency) { String _normalizeCurrency(CryptoCurrency currency) {
if (currency == CryptoCurrency.maticpoly) { if (currency == CryptoCurrency.maticpoly) {

View file

@ -26,7 +26,7 @@ class AddressValidator extends TextValidator {
return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$' 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]|\$)'

View file

@ -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 '';
} }

View file

@ -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();

View file

@ -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';
@ -148,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';
@ -161,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';
@ -180,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';
@ -200,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';
@ -231,41 +235,10 @@ import 'package:cw_core/wallet_service.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:get_it/get_it.dart'; 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 'package:cake_wallet/core/wallet_creation_service.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
import 'package:cake_wallet/store/authentication_store.dart';
import 'package:cake_wallet/store/dashboard/trades_store.dart';
import 'package:cake_wallet/store/dashboard/trade_filter_store.dart';
import 'package:cake_wallet/store/dashboard/transaction_filter_store.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/templates/send_template_store.dart';
import 'package:cake_wallet/store/templates/exchange_template_store.dart';
import 'package:cake_wallet/entities/template.dart';
import 'package:cake_wallet/exchange/exchange_template.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/src/screens/dashboard/pages/address_page.dart';
import 'package:cake_wallet/anypay/anypay_api.dart';
import 'package:cake_wallet/view_model/ionia/ionia_gift_card_details_view_model.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_payment_status_page.dart';
import 'package:cake_wallet/view_model/ionia/ionia_payment_status_view_model.dart';
import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart';
import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart';
import 'package:cake_wallet/core/wallet_loading_service.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/entities/qr_view_data.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;
@ -339,7 +312,6 @@ Future<void> setup({
getIt.registerFactory<Box<Node>>(() => _powNodeSource, instanceName: Node.boxName + "pow"); getIt.registerFactory<Box<Node>>(() => _powNodeSource, instanceName: Node.boxName + "pow");
getIt.registerSingleton(AuthenticationStore()); getIt.registerSingleton(AuthenticationStore());
getIt.registerSingleton(LedgerViewModel());
getIt.registerSingleton<WalletListStore>(WalletListStore()); getIt.registerSingleton<WalletListStore>(WalletListStore());
getIt.registerSingleton(NodeListStoreBase.instance); getIt.registerSingleton(NodeListStoreBase.instance);
getIt.registerSingleton<SettingsStore>(settingsStore); getIt.registerSingleton<SettingsStore>(settingsStore);
@ -359,11 +331,12 @@ Future<void> setup({
getIt.registerSingleton<ExchangeTemplateStore>( getIt.registerSingleton<ExchangeTemplateStore>(
ExchangeTemplateStore(templateSource: _exchangeTemplates)); ExchangeTemplateStore(templateSource: _exchangeTemplates));
getIt.registerSingleton<YatStore>( getIt.registerSingleton<YatStore>(
YatStore(appStore: getIt.get<AppStore>(), secureStorage: getIt.get<SecureStorage>()) YatStore(appStore: getIt.get<AppStore>(), secureStorage: getIt.get<SecureStorage>())..init());
..init());
getIt.registerSingleton<AnonpayTransactionsStore>( getIt.registerSingleton<AnonpayTransactionsStore>(
AnonpayTransactionsStore(anonpayInvoiceInfoSource: _anonpayInvoiceInfoSource)); AnonpayTransactionsStore(anonpayInvoiceInfoSource: _anonpayInvoiceInfoSource));
getIt.registerLazySingleton(() => LedgerViewModel());
final secretStore = await SecretStoreBase.load(getIt.get<SecureStorage>()); final secretStore = await SecretStoreBase.load(getIt.get<SecureStorage>());
getIt.registerSingleton<SecretStore>(secretStore); getIt.registerSingleton<SecretStore>(secretStore);
@ -600,7 +573,7 @@ Future<void> setup({
getIt.registerFactory<Modify2FAPage>( getIt.registerFactory<Modify2FAPage>(
() => Modify2FAPage(setup2FAViewModel: getIt.get<Setup2FAViewModel>())); () => Modify2FAPage(setup2FAViewModel: getIt.get<Setup2FAViewModel>()));
getIt.registerFactory<DesktopSettingsPage>(() => DesktopSettingsPage()); getIt.registerFactory<DesktopSettingsPage>(() => DesktopSettingsPage(getIt.get<DashboardViewModel>()));
getIt.registerFactoryParam<ReceiveOptionViewModel, ReceivePageOption?, void>( getIt.registerFactoryParam<ReceiveOptionViewModel, ReceivePageOption?, void>(
(pageOption, _) => ReceiveOptionViewModel(getIt.get<AppStore>().wallet!, pageOption)); (pageOption, _) => ReceiveOptionViewModel(getIt.get<AppStore>().wallet!, pageOption));
@ -655,7 +628,7 @@ Future<void> setup({
getIt.get<BalanceViewModel>(), getIt.get<BalanceViewModel>(),
getIt.get<ContactListViewModel>(), getIt.get<ContactListViewModel>(),
_transactionDescriptionBox, _transactionDescriptionBox,
getIt.get<LedgerViewModel>(), getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null,
), ),
); );
@ -775,6 +748,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!);
}); });
@ -825,8 +801,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>()));
@ -836,6 +812,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(
@ -862,10 +841,14 @@ Future<void> setup({
isSelected: isSelected)); isSelected: isSelected));
getIt.registerFactory<RobinhoodBuyProvider>(() => RobinhoodBuyProvider( getIt.registerFactory<RobinhoodBuyProvider>(() => RobinhoodBuyProvider(
wallet: getIt.get<AppStore>().wallet!, ledgerVM: getIt.get<LedgerViewModel>())); wallet: getIt.get<AppStore>().wallet!,
ledgerVM:
getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null));
getIt.registerFactory<DFXBuyProvider>(() => DFXBuyProvider( getIt.registerFactory<DFXBuyProvider>(() => DFXBuyProvider(
wallet: getIt.get<AppStore>().wallet!, ledgerVM: getIt.get<LedgerViewModel>())); wallet: getIt.get<AppStore>().wallet!,
ledgerVM:
getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null));
getIt.registerFactory<MoonPayProvider>(() => MoonPayProvider( getIt.registerFactory<MoonPayProvider>(() => MoonPayProvider(
settingsStore: getIt.get<AppStore>().settingsStore, settingsStore: getIt.get<AppStore>().settingsStore,
@ -919,7 +902,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:
@ -966,9 +953,9 @@ Future<void> setup({
(derivations, _) => WalletRestoreChooseDerivationViewModel(derivationInfos: derivations)); (derivations, _) => WalletRestoreChooseDerivationViewModel(derivationInfos: derivations));
getIt.registerFactoryParam<WalletRestoreChooseDerivationPage, List<DerivationInfo>, void>( getIt.registerFactoryParam<WalletRestoreChooseDerivationPage, List<DerivationInfo>, void>(
(credentials, _) => (derivations, _) =>
WalletRestoreChooseDerivationPage(getIt.get<WalletRestoreChooseDerivationViewModel>( WalletRestoreChooseDerivationPage(getIt.get<WalletRestoreChooseDerivationViewModel>(
param1: credentials, param1: derivations,
))); )));
getIt.registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void>( getIt.registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void>(
@ -1017,8 +1004,8 @@ Future<void> setup({
getIt.registerFactory(() => BackupPage(getIt.get<BackupViewModel>())); getIt.registerFactory(() => BackupPage(getIt.get<BackupViewModel>()));
getIt.registerFactory(() => getIt.registerFactory(
EditBackupPasswordViewModel(getIt.get<SecureStorage>(), getIt.get<SecretStore>())); () => EditBackupPasswordViewModel(getIt.get<SecureStorage>(), getIt.get<SecretStore>()));
getIt.registerFactory(() => EditBackupPasswordPage(getIt.get<EditBackupPasswordViewModel>())); getIt.registerFactory(() => EditBackupPasswordPage(getIt.get<EditBackupPasswordViewModel>()));
@ -1066,8 +1053,8 @@ Future<void> setup({
getIt.registerFactory(() => SupportPage(getIt.get<SupportViewModel>())); getIt.registerFactory(() => SupportPage(getIt.get<SupportViewModel>()));
getIt.registerFactory(() => SupportChatPage(getIt.get<SupportViewModel>(), getIt.registerFactory(() =>
secureStorage: getIt.get<SecureStorage>())); SupportChatPage(getIt.get<SupportViewModel>(), secureStorage: getIt.get<SecureStorage>()));
getIt.registerFactory(() => SupportOtherLinksPage(getIt.get<SupportViewModel>())); getIt.registerFactory(() => SupportOtherLinksPage(getIt.get<SupportViewModel>()));
@ -1116,7 +1103,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>()));

View file

@ -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);
} }

View file

@ -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';
@ -65,6 +67,7 @@ class PreferencesKey {
static const lookupsUnstoppableDomains = 'looks_up_unstoppable_domain'; static const lookupsUnstoppableDomains = 'looks_up_unstoppable_domain';
static const lookupsOpenAlias = 'looks_up_open_alias'; static const lookupsOpenAlias = 'looks_up_open_alias';
static const lookupsENS = 'looks_up_ens'; static const lookupsENS = 'looks_up_ens';
static const showCameraConsent = 'show_camera_consent';
static String moneroWalletUpdateV1Key(String name) => static String moneroWalletUpdateV1Key(String name) =>
'${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}'; '${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}';

View file

@ -22,10 +22,11 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
ExchangeProviderDescription(title: 'Trocador', raw: 5, image: 'assets/images/trocador.png'); ExchangeProviderDescription(title: 'Trocador', raw: 5, image: 'assets/images/trocador.png');
static const exolix = static const exolix =
ExchangeProviderDescription(title: 'Exolix', raw: 6, image: 'assets/images/exolix.png'); ExchangeProviderDescription(title: 'Exolix', raw: 6, image: 'assets/images/exolix.png');
static const thorChain =
ExchangeProviderDescription(title: 'ThorChain' , raw: 8, image: 'assets/images/thorchain.png');
static const all = ExchangeProviderDescription(title: 'All trades', raw: 7, image: ''); static const all = ExchangeProviderDescription(title: 'All trades', raw: 7, image: '');
static const thorChain =
ExchangeProviderDescription(title: 'ThorChain', raw: 8, image: 'assets/images/thorchain.png');
static const quantex =
ExchangeProviderDescription(title: 'Quantex', raw: 9, image: 'assets/images/quantex.png');
static ExchangeProviderDescription deserialize({required int raw}) { static ExchangeProviderDescription deserialize({required int raw}) {
switch (raw) { switch (raw) {
@ -43,10 +44,12 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
return trocador; return trocador;
case 6: case 6:
return exolix; return exolix;
case 8:
return thorChain;
case 7: case 7:
return all; return all;
case 8:
return thorChain;
case 9:
return quantex;
default: default:
throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize'); throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize');
} }

View file

@ -0,0 +1,252 @@
import 'dart:convert';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_not_created_exception.dart';
import 'package:cake_wallet/exchange/trade_not_found_exception.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:http/http.dart';
class QuantexExchangeProvider extends ExchangeProvider {
QuantexExchangeProvider() : super(pairList: supportedPairs(_notSupported));
static final List<CryptoCurrency> _notSupported = [
...(CryptoCurrency.all
.where((element) => ![
CryptoCurrency.btc,
CryptoCurrency.sol,
CryptoCurrency.eth,
CryptoCurrency.ltc,
CryptoCurrency.ada,
CryptoCurrency.bch,
CryptoCurrency.usdt,
CryptoCurrency.bnb,
CryptoCurrency.xmr,
].contains(element))
.toList())
];
static final markup = secrets.quantexExchangeMarkup;
static const apiAuthority = 'api.myquantex.com';
static const getRate = '/api/swap/get-rate';
static const getCoins = '/api/swap/get-coins';
static const createOrder = '/api/swap/create-order';
@override
String get title => 'Quantex';
@override
bool get isAvailable => true;
@override
bool get isEnabled => true;
@override
bool get supportsFixedRate => false;
@override
ExchangeProviderDescription get description => ExchangeProviderDescription.quantex;
@override
Future<bool> checkIsAvailable() async => true;
@override
Future<Limits> fetchLimits({
required CryptoCurrency from,
required CryptoCurrency to,
required bool isFixedRateMode,
}) async {
try {
final uri = Uri.https(apiAuthority, getCoins);
final response = await get(uri);
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
if (response.statusCode != 200)
throw Exception('Unexpected http status: ${response.statusCode}');
final coinsInfo = responseJSON['data'] as List<dynamic>;
for (var coin in coinsInfo) {
if (coin['id'].toString().toUpperCase() == _normalizeCurrency(from)) {
return Limits(
min: double.parse(coin['min'].toString()),
max: double.parse(coin['max'].toString()),
);
}
}
// coin not found:
return Limits(min: 0, max: 0);
} catch (e) {
print(e.toString());
return Limits(min: 0, max: 0);
}
}
@override
Future<double> fetchRate({
required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount,
}) async {
try {
if (amount == 0) return 0.0;
final headers = <String, String>{};
final params = <String, dynamic>{};
final body = <String, String>{
'coin_send': _normalizeCurrency(from),
'coin_receive': _normalizeCurrency(to),
'ref': 'cake',
};
final uri = Uri.https(apiAuthority, getRate, params);
final response = await post(uri, body: body, headers: headers);
final responseBody = json.decode(response.body) as Map<String, dynamic>;
if (response.statusCode != 200)
throw Exception('Unexpected http status: ${response.statusCode}');
final data = responseBody['data'] as Map<String, dynamic>;
double rate = double.parse(data['price'].toString());
return rate;
} catch (e) {
print("error fetching rate: ${e.toString()}");
return 0.0;
}
}
@override
Future<Trade> createTrade({
required TradeRequest request,
required bool isFixedRateMode,
required bool isSendAll,
}) async {
try {
final headers = <String, String>{};
final params = <String, dynamic>{};
var body = <String, dynamic>{
'coin_send': _normalizeCurrency(request.fromCurrency),
'coin_receive': _normalizeCurrency(request.toCurrency),
'amount_send': request.fromAmount,
'recipient': request.toAddress,
'ref': 'cake',
'markup': markup,
};
String? fromNetwork = _networkFor(request.fromCurrency);
String? toNetwork = _networkFor(request.toCurrency);
if (fromNetwork != null) body['coin_send_network'] = fromNetwork;
if (toNetwork != null) body['coin_receive_network'] = toNetwork;
final uri = Uri.https(apiAuthority, createOrder, params);
final response = await post(uri, body: body, headers: headers);
final responseBody = json.decode(response.body) as Map<String, dynamic>;
if (response.statusCode == 400 || responseBody["success"] == false) {
final error = responseBody['errors'][0]['msg'] as String;
throw TradeNotCreatedException(description, description: error);
}
if (response.statusCode != 200)
throw Exception('Unexpected http status: ${response.statusCode}');
final responseData = responseBody['data'] as Map<String, dynamic>;
return Trade(
id: responseData["order_id"] as String,
inputAddress: responseData["server_address"] as String,
amount: request.fromAmount,
from: request.fromCurrency,
to: request.toCurrency,
provider: description,
createdAt: DateTime.now(),
state: TradeState.created,
payoutAddress: request.toAddress,
isSendAll: isSendAll,
);
} catch (e) {
print("error creating trade: ${e.toString()}");
throw TradeNotCreatedException(description, description: e.toString());
}
}
@override
Future<Trade> findTradeById({required String id}) async {
try {
final headers = <String, String>{};
final params = <String, dynamic>{};
var body = <String, dynamic>{
'order_id': id,
};
final uri = Uri.https(apiAuthority, createOrder, params);
final response = await post(uri, body: body, headers: headers);
final responseBody = json.decode(response.body) as Map<String, dynamic>;
if (response.statusCode == 400 || responseBody["success"] == false) {
final error = responseBody['errors'][0]['msg'] as String;
throw TradeNotCreatedException(description, description: error);
}
if (response.statusCode != 200)
throw Exception('Unexpected http status: ${response.statusCode}');
final responseData = responseBody['data'] as Map<String, dynamic>;
final fromCurrency = responseData['coin_send'] as String;
final from = CryptoCurrency.fromString(fromCurrency);
final toCurrency = responseData['coin_receive'] as String;
final to = CryptoCurrency.fromString(toCurrency);
final inputAddress = responseData['server_address'] as String;
final status = responseData['status'] as String;
final state = TradeState.deserialize(raw: status);
final response_id = responseData['order_id'] as String;
final expectedSendAmount = responseData['amount_send'] as String;
return Trade(
id: response_id,
from: from,
to: to,
provider: description,
inputAddress: inputAddress,
amount: expectedSendAmount,
state: state,
);
} catch (e) {
print("error getting trade: ${e.toString()}");
throw TradeNotFoundException(
id,
provider: description,
description: e.toString(),
);
}
}
String _normalizeCurrency(CryptoCurrency currency) {
switch (currency) {
default:
return currency.title.toUpperCase();
}
}
String? _networkFor(CryptoCurrency currency) {
switch (currency) {
case CryptoCurrency.usdt:
return "USDT_ERC20";
case CryptoCurrency.bnb:
return "BNB_BSC";
default:
return null;
}
}
}

View file

@ -5,9 +5,6 @@ import 'package:cw_core/format_amount.dart';
import 'package:cw_core/hive_type_ids.dart'; import 'package:cw_core/hive_type_ids.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
part 'trade.g.dart';
@HiveType(typeId: Trade.typeId)
class Trade extends HiveObject { class Trade extends HiveObject {
Trade({ Trade({
required this.id, required this.id,
@ -32,6 +29,7 @@ class Trade extends HiveObject {
this.txId, this.txId,
this.isRefund, this.isRefund,
this.isSendAll, this.isSendAll,
this.router,
}) { }) {
if (provider != null) providerRaw = provider.raw; if (provider != null) providerRaw = provider.raw;
@ -121,6 +119,9 @@ class Trade extends HiveObject {
@HiveField(21) @HiveField(21)
bool? isSendAll; bool? isSendAll;
@HiveField(22)
String? router;
static Trade fromMap(Map<String, Object?> map) { static Trade fromMap(Map<String, Object?> map) {
return Trade( return Trade(
id: map['id'] as String, id: map['id'] as String,
@ -135,7 +136,9 @@ class Trade extends HiveObject {
memo: map['memo'] as String?, memo: map['memo'] as String?,
txId: map['tx_id'] as String?, txId: map['tx_id'] as String?,
isRefund: map['isRefund'] as bool?, isRefund: map['isRefund'] as bool?,
isSendAll: map['isSendAll'] as bool?); isSendAll: map['isSendAll'] as bool?,
router: map['router'] as String?,
);
} }
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
@ -152,8 +155,111 @@ class Trade extends HiveObject {
'tx_id': txId, 'tx_id': txId,
'isRefund': isRefund, 'isRefund': isRefund,
'isSendAll': isSendAll, 'isSendAll': isSendAll,
'router': router,
}; };
} }
String amountFormatted() => formatAmount(amount); String amountFormatted() => formatAmount(amount);
} }
class TradeAdapter extends TypeAdapter<Trade> {
@override
final int typeId = Trade.typeId;
@override
Trade read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{};
for (int i = 0; i < numOfFields; i++) {
try {
fields[reader.readByte()] = reader.read();
} catch (_) {}
}
return Trade(
id: fields[0] == null ? '' : fields[0] as String,
amount: fields[7] == null ? '' : fields[7] as String,
createdAt: fields[5] as DateTime?,
expiredAt: fields[6] as DateTime?,
inputAddress: fields[8] as String?,
extraId: fields[9] as String?,
outputTransaction: fields[10] as String?,
refundAddress: fields[11] as String?,
walletId: fields[12] as String?,
payoutAddress: fields[13] as String?,
password: fields[14] as String?,
providerId: fields[15] as String?,
providerName: fields[16] as String?,
fromWalletAddress: fields[17] as String?,
memo: fields[18] as String?,
txId: fields[19] as String?,
isRefund: fields[20] as bool?,
isSendAll: fields[21] as bool?,
router: fields[22] as String?,
)
..providerRaw = fields[1] == null ? 0 : fields[1] as int
..fromRaw = fields[2] == null ? 0 : fields[2] as int
..toRaw = fields[3] == null ? 0 : fields[3] as int
..stateRaw = fields[4] == null ? '' : fields[4] as String;
}
@override
void write(BinaryWriter writer, Trade obj) {
writer
..writeByte(23)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.providerRaw)
..writeByte(2)
..write(obj.fromRaw)
..writeByte(3)
..write(obj.toRaw)
..writeByte(4)
..write(obj.stateRaw)
..writeByte(5)
..write(obj.createdAt)
..writeByte(6)
..write(obj.expiredAt)
..writeByte(7)
..write(obj.amount)
..writeByte(8)
..write(obj.inputAddress)
..writeByte(9)
..write(obj.extraId)
..writeByte(10)
..write(obj.outputTransaction)
..writeByte(11)
..write(obj.refundAddress)
..writeByte(12)
..write(obj.walletId)
..writeByte(13)
..write(obj.payoutAddress)
..writeByte(14)
..write(obj.password)
..writeByte(15)
..write(obj.providerId)
..writeByte(16)
..write(obj.providerName)
..writeByte(17)
..write(obj.fromWalletAddress)
..writeByte(18)
..write(obj.memo)
..writeByte(19)
..write(obj.txId)
..writeByte(20)
..write(obj.isRefund)
..writeByte(21)
..write(obj.isSendAll)
..writeByte(22)
..write(obj.router);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is TradeAdapter && runtimeType == other.runtimeType && typeId == other.typeId;
}

View file

@ -28,6 +28,7 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
TradeState(raw: 'waitingAuthorization', title: 'Waiting authorization'); TradeState(raw: 'waitingAuthorization', title: 'Waiting authorization');
static const failed = TradeState(raw: 'failed', title: 'Failed'); static const failed = TradeState(raw: 'failed', title: 'Failed');
static const completed = TradeState(raw: 'completed', title: 'Completed'); static const completed = TradeState(raw: 'completed', title: 'Completed');
static const expired = TradeState(raw: 'expired', title: 'Expired');
static const settling = TradeState(raw: 'settling', title: 'Settlement in progress'); static const settling = TradeState(raw: 'settling', title: 'Settlement in progress');
static const settled = TradeState(raw: 'settled', title: 'Settlement completed'); static const settled = TradeState(raw: 'settled', title: 'Settlement completed');
static const wait = TradeState(raw: 'wait', title: 'Waiting'); static const wait = TradeState(raw: 'wait', title: 'Waiting');
@ -39,7 +40,33 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
static const exchanging = TradeState(raw: 'exchanging', title: 'Exchanging'); static const exchanging = TradeState(raw: 'exchanging', title: 'Exchanging');
static const sending = TradeState(raw: 'sending', title: 'Sending'); static const sending = TradeState(raw: 'sending', title: 'Sending');
static const success = TradeState(raw: 'success', title: 'Success'); static const success = TradeState(raw: 'success', title: 'Success');
static TradeState deserialize({required String raw}) { static TradeState deserialize({required String raw}) {
switch (raw) {
case '1':
return unpaid;
case '2':
return paidUnconfirmed;
case '3':
return sending;
case '4':
return confirmed;
case '5':
case '6':
return exchanging;
case '7':
return sending;
case '8':
return complete;
case '9':
return expired;
case '10':
return underpaid;
case '11':
return failed;
}
switch (raw) { switch (raw) {
case 'NOT_FOUND': case 'NOT_FOUND':
return notFound; return notFound;

View file

@ -5,7 +5,6 @@ import 'package:cake_wallet/core/secure_storage.dart';
import 'package:cake_wallet/entities/language_service.dart'; import 'package:cake_wallet/entities/language_service.dart';
import 'package:cake_wallet/buy/order.dart'; import 'package:cake_wallet/buy/order.dart';
import 'package:cake_wallet/locales/locale.dart'; import 'package:cake_wallet/locales/locale.dart';
import 'package:cake_wallet/store/yat/yat_store.dart';
import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/exception_handler.dart'; import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cake_wallet/view_model/link_view_model.dart';
@ -38,7 +37,6 @@ import 'package:cake_wallet/entities/template.dart';
import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/exchange_template.dart'; import 'package:cake_wallet/exchange/exchange_template.dart';
import 'package:cake_wallet/src/screens/root/root.dart'; import 'package:cake_wallet/src/screens/root/root.dart';
import 'package:uni_links/uni_links.dart';
import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/unspent_coins_info.dart';
import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/monero/monero.dart';
import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/cake_hive.dart';
@ -46,9 +44,10 @@ import 'package:cw_core/window_size.dart';
final navigatorKey = GlobalKey<NavigatorState>(); final navigatorKey = GlobalKey<NavigatorState>();
final rootKey = GlobalKey<RootState>(); final rootKey = GlobalKey<RootState>();
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>(); final RouteObserver<PageRoute<dynamic>> routeObserver = RouteObserver<PageRoute<dynamic>>();
Future<void> main() async { Future<void> main() async {
bool isAppRunning = false;
await runZonedGuarded(() async { await runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
@ -69,7 +68,36 @@ Future<void> main() async {
await initializeAppConfigs(); await initializeAppConfigs();
runApp(App()); runApp(App());
isAppRunning = true;
}, (error, stackTrace) async { }, (error, stackTrace) async {
if (!isAppRunning) {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: SingleChildScrollView(
child: Container(
margin: EdgeInsets.only(top: 50, left: 20, right: 20, bottom: 20),
child: Column(
children: [
Text(
'Error:\n${error.toString()}',
style: TextStyle(fontSize: 22),
),
Text(
'Stack trace:\n${stackTrace.toString()}',
style: TextStyle(fontSize: 16),
),
],
),
),
),
),
),
);
}
ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stackTrace)); ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stackTrace));
}); });
} }
@ -174,7 +202,7 @@ Future<void> initializeAppConfigs() async {
transactionDescriptions: transactionDescriptions, transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage, secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo, anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 33, initialMigrationVersion: 34,
); );
} }
@ -229,61 +257,6 @@ class App extends StatefulWidget {
} }
class AppState extends State<App> with SingleTickerProviderStateMixin { class AppState extends State<App> with SingleTickerProviderStateMixin {
AppState() : yatStore = getIt.get<YatStore>();
YatStore yatStore;
StreamSubscription? stream;
@override
void initState() {
super.initState();
//_handleIncomingLinks();
//_handleInitialUri();
}
Future<void> _handleInitialUri() async {
try {
final uri = await getInitialUri();
print('uri: $uri');
if (uri == null) {
return;
}
if (!mounted) return;
//_fetchEmojiFromUri(uri);
} catch (e) {
if (!mounted) return;
print(e.toString());
}
}
void _handleIncomingLinks() {
if (!kIsWeb) {
stream = getUriLinksStream().listen((Uri? uri) {
print('uri: $uri');
if (!mounted) return;
//_fetchEmojiFromUri(uri);
}, onError: (Object error) {
if (!mounted) return;
print('Error: $error');
});
}
}
void _fetchEmojiFromUri(Uri uri) {
//final queryParameters = uri.queryParameters;
//if (queryParameters?.isEmpty ?? true) {
// return;
//}
//final emoji = queryParameters['eid'];
//final refreshToken = queryParameters['refresh_token'];
//if ((emoji?.isEmpty ?? true)||(refreshToken?.isEmpty ?? true)) {
// return;
//}
//yatStore.emoji = emoji;
//yatStore.refreshToken = refreshToken;
//yatStore.emojiIncommingSC.add(emoji);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Observer(builder: (BuildContext context) { return Observer(builder: (BuildContext context) {

View file

@ -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) {

View file

@ -26,8 +26,8 @@ void startWalletSyncStatusChangeReaction(
SettingsStore settingsStore, SettingsStore settingsStore,
Box<TransactionDescription> transactionDescription) { Box<TransactionDescription> transactionDescription) {
_onWalletSyncStatusChangeReaction?.reaction.dispose(); _onWalletSyncStatusChangeReaction?.reaction.dispose();
_onWalletSyncStatusChangeReaction = reaction((_) => wallet.syncStatus, (SyncStatus status) async { _onWalletSyncStatusChangeReaction = reaction((_) => wallet.syncStatus, (SyncStatus status) async {
try {
if (status is ConnectedSyncStatus) { if (status is ConnectedSyncStatus) {
await wallet.startSync(); await wallet.startSync();
if (wallet.type == WalletType.haven) { if (wallet.type == WalletType.haven) {
@ -50,5 +50,8 @@ void startWalletSyncStatusChangeReaction(
}); });
} }
} }
} catch (e) {
print(e.toString());
}
}); });
} }

View file

@ -76,6 +76,7 @@ import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart'; import 'package:cake_wallet/src/screens/settings/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>());

View file

@ -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';

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/route_aware.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/di.dart';
@ -32,6 +33,14 @@ abstract class BasePage extends StatelessWidget {
Widget? get endDrawer => null; Widget? get endDrawer => null;
Function(BuildContext context)? get pushToWidget => null;
Function(BuildContext context)? get pushToNextWidget => null;
Function(BuildContext context)? get popWidget => null;
Function(BuildContext context)? get popNextWidget => null;
AppBarStyle get appBarStyle => AppBarStyle.regular; AppBarStyle get appBarStyle => AppBarStyle.regular;
Widget Function(BuildContext, Widget)? get rootWrapper => null; Widget Function(BuildContext, Widget)? get rootWrapper => null;
@ -162,7 +171,8 @@ abstract class BasePage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final root = Scaffold( final root = RouteAwareWidget(
child: Scaffold(
key: _scaffoldKey, key: _scaffoldKey,
backgroundColor: pageBackgroundColor(context), backgroundColor: pageBackgroundColor(context),
resizeToAvoidBottomInset: resizeToAvoidBottomInset, resizeToAvoidBottomInset: resizeToAvoidBottomInset,
@ -170,7 +180,12 @@ abstract class BasePage extends StatelessWidget {
endDrawer: endDrawer, endDrawer: endDrawer,
appBar: appBar(context), appBar: appBar(context),
body: body(context), body: body(context),
floatingActionButton: floatingActionButton(context)); floatingActionButton: floatingActionButton(context)),
pushToWidget: (context) => pushToWidget?.call(context),
pushToNextWidget: (context) => pushToNextWidget?.call(context),
popWidget: (context) => popWidget?.call(context),
popNextWidget: (context) => popNextWidget?.call(context),
);
return rootWrapper?.call(context, root) ?? root; return rootWrapper?.call(context, root) ?? root;
} }

View file

@ -1,10 +1,14 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/permission_handler.dart';
import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart';
class WebViewPage extends BasePage { class WebViewPage extends BasePage {
WebViewPage(this._title, this._url); WebViewPage(this._title, this._url);
@ -42,8 +46,9 @@ class WebViewPageBodyState extends State<WebViewPageBody> {
), ),
initialUrlRequest: URLRequest(url: WebUri.uri(widget.uri)), initialUrlRequest: URLRequest(url: WebUri.uri(widget.uri)),
onPermissionRequest: (controller, request) async { onPermissionRequest: (controller, request) async {
bool permissionGranted = await Permission.camera.status == PermissionStatus.granted; final sharedPrefs = getIt.get<SharedPreferences>();
if (!permissionGranted) {
if (sharedPrefs.getBool(PreferencesKey.showCameraConsent) ?? true) {
final bool userConsent = await showPopUp<bool>( final bool userConsent = await showPopUp<bool>(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
@ -65,9 +70,12 @@ class WebViewPageBodyState extends State<WebViewPageBody> {
); );
} }
permissionGranted = await Permission.camera.request().isGranted; sharedPrefs.setBool(PreferencesKey.showCameraConsent, false);
} }
bool permissionGranted =
await PermissionHandler.checkPermission(Permission.camera, context);
return PermissionResponse( return PermissionResponse(
resources: request.resources, resources: request.resources,
action: permissionGranted action: permissionGranted

View file

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/connect_device/debug_device_page.dart';
import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart'; import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.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/responsive_layout_util.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart';
@ -78,15 +79,13 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
Future.delayed( WidgetsBinding.instance.addPostFrameCallback((_) {
Duration(seconds: 1), _bleRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices());
() => _bleRefresh = ledger.scan().listen((device) => setState(() => bleDevices.add(device))),
);
// _bleRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices());
if (Platform.isAndroid) { if (Platform.isAndroid) {
_usbRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices()); _usbRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices());
} }
});
} }
@override @override
@ -103,14 +102,16 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
} }
Future<void> _refreshBleDevices() async { Future<void> _refreshBleDevices() async {
final isBleEnabled = await Permission.bluetooth.serviceStatus.isEnabled; try {
_bleRefresh = ledger.scan().listen((device) => setState(() => bleDevices.add(device)))
setState(() => bleIsEnabled = isBleEnabled); ..onError((e) {
throw e.toString();
if (isBleEnabled) { });
_bleRefresh = ledger.scan().listen((device) => setState(() => bleDevices.add(device))); setState(() => bleIsEnabled = true);
_bleRefreshTimer?.cancel(); _bleRefreshTimer?.cancel();
_bleRefreshTimer = null; _bleRefreshTimer = null;
} catch (e) {
setState(() => bleIsEnabled = false);
} }
} }
@ -142,6 +143,15 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
// DeviceTile(
// onPressed: () => Navigator.of(context).push(
// MaterialPageRoute<void>(
// builder: (BuildContext context) => DebugDevicePage(),
// ),
// ),
// title: "Debug Ledger",
// leading: imageLedger,
// ),
if (!bleIsEnabled) if (!bleIsEnabled)
Padding( Padding(
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20), padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),

View file

@ -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);
}
});
}
} }

View file

@ -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,
@ -49,24 +49,53 @@ class DashboardPage extends StatelessWidget {
final DashboardViewModel dashboardViewModel; final DashboardViewModel dashboardViewModel;
final WalletAddressListViewModel addressListViewModel; final WalletAddressListViewModel addressListViewModel;
@override
State<DashboardPage> createState() => _DashboardPageState();
}
class _DashboardPageState extends State<DashboardPage> {
@override
void initState() {
super.initState();
bool isMobileLayout =
responsiveLayoutUtil.screenWidth < ResponsiveLayoutUtilBase.kMobileThreshold;
reaction((_) => responsiveLayoutUtil.screenWidth, (screenWidth) {
// Check if it was previously in mobile layout, and now changing to desktop
if (isMobileLayout &&
screenWidth > ResponsiveLayoutUtilBase.kDesktopMaxDashBoardWidthConstraint) {
setState(() {
isMobileLayout = false;
});
}
// Check if it was previously in desktop layout, and now changing to mobile
if (!isMobileLayout &&
screenWidth <= ResponsiveLayoutUtilBase.kDesktopMaxDashBoardWidthConstraint) {
setState(() {
isMobileLayout = true;
});
}
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final screenHeight = MediaQuery.of(context).size.height; Widget dashboardChild;
return Scaffold(
body: Observer(
builder: (_) {
final dashboardPageView = RefreshIndicator( final dashboardPageView = RefreshIndicator(
displacement: screenHeight * 0.1, displacement: responsiveLayoutUtil.screenHeight * 0.1,
onRefresh: () async => await dashboardViewModel.refreshDashboard(), onRefresh: () async => await widget.dashboardViewModel.refreshDashboard(),
child: SingleChildScrollView( child: SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(), physics: AlwaysScrollableScrollPhysics(),
child: Container( child: Container(
height: screenHeight, height: responsiveLayoutUtil.screenHeight,
child: _DashboardPageView( child: _DashboardPageView(
balancePage: balancePage, balancePage: widget.balancePage,
bottomSheetService: bottomSheetService, bottomSheetService: widget.bottomSheetService,
dashboardViewModel: dashboardViewModel, dashboardViewModel: widget.dashboardViewModel,
addressListViewModel: addressListViewModel, addressListViewModel: widget.addressListViewModel,
), ),
), ),
), ),
@ -75,18 +104,17 @@ class DashboardPage extends StatelessWidget {
if (DeviceInfo.instance.isDesktop) { if (DeviceInfo.instance.isDesktop) {
if (responsiveLayoutUtil.screenWidth > if (responsiveLayoutUtil.screenWidth >
ResponsiveLayoutUtilBase.kDesktopMaxDashBoardWidthConstraint) { ResponsiveLayoutUtilBase.kDesktopMaxDashBoardWidthConstraint) {
return getIt.get<DesktopSidebarWrapper>(); dashboardChild = getIt.get<DesktopSidebarWrapper>();
} else { } else {
return dashboardPageView; dashboardChild = dashboardPageView;
} }
} else if (responsiveLayoutUtil.shouldRenderMobileUI) { } else if (responsiveLayoutUtil.shouldRenderMobileUI) {
return dashboardPageView; dashboardChild = dashboardPageView;
} else { } else {
return getIt.get<DesktopSidebarWrapper>(); 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>(),
), ),
), ),
); );

View file

@ -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>(),
), ),
), ),
], ],

View file

@ -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);
@ -69,7 +70,10 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
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),
)) ))
@ -117,6 +121,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
if (selectedWallet.isCurrent || !selectedWallet.isEnabled) { if (selectedWallet.isCurrent || !selectedWallet.isEnabled) {
return; return;
} }
WidgetsBinding.instance.addPostFrameCallback((_) async {
final confirmed = await showPopUp<bool>( final confirmed = await showPopUp<bool>(
context: context, context: context,
builder: (dialogContext) { builder: (dialogContext) {
@ -133,11 +139,15 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
if (confirmed) { if (confirmed) {
await _loadWallet(selectedWallet); await _loadWallet(selectedWallet);
} }
});
} }
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;
@ -165,7 +175,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
} }
Future<void> _loadWallet(WalletListItem wallet) async { Future<void> _loadWallet(WalletListItem wallet) async {
widget._authService.authenticateAction(context, widget._authService.authenticateAction(
context,
onAuthSuccess: (isAuthenticatedSuccessfully) async { onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (!isAuthenticatedSuccessfully) { if (!isAuthenticatedSuccessfully) {
return; return;
@ -195,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,
); );
} }
} }

View file

@ -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(

View file

@ -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({
@ -69,14 +73,14 @@ class BalancePage extends StatelessWidget {
), ),
labelColor: labelColor:
Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor, Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
dividerColor: dividerColor: Colors.transparent,
Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
indicatorColor: indicatorColor:
Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor, Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
unselectedLabelColor: Theme.of(context) unselectedLabelColor: Theme.of(context)
.extension<DashboardPageTheme>()! .extension<DashboardPageTheme>()!
.pageTitleTextColor .pageTitleTextColor
.withOpacity(0.5), .withOpacity(0.5),
tabAlignment: TabAlignment.start,
tabs: [ tabs: [
Tab(text: 'My Crypto'), Tab(text: 'My Crypto'),
Tab(text: 'My NFTs'), Tab(text: 'My NFTs'),
@ -221,6 +225,7 @@ class CryptoBalanceWidget extends StatelessWidget {
itemBuilder: (__, index) { itemBuilder: (__, index) {
final balance = final balance =
dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index); dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index);
return Observer(builder: (_) {
return BalanceRowWidget( return BalanceRowWidget(
availableBalanceLabel: availableBalanceLabel:
'${dashboardViewModel.balanceViewModel.availableBalanceLabel}', '${dashboardViewModel.balanceViewModel.availableBalanceLabel}',
@ -235,15 +240,120 @@ class CryptoBalanceWidget extends StatelessWidget {
currency: balance.asset, currency: balance.asset,
hasAdditionalBalance: hasAdditionalBalance:
dashboardViewModel.balanceViewModel.hasAdditionalBalance, dashboardViewModel.balanceViewModel.hasAdditionalBalance,
isTestnet: dashboardViewModel.isTestnet,
); );
});
}, },
); );
}, },
),
Observer(builder: (context) {
return Column(
children: [
if (dashboardViewModel.showSilentPaymentsCard) ...[
SizedBox(height: 10),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: DashBoardRoundedCardWidget(
customBorder: 30,
title: S.of(context).silent_payments,
subTitle: S.of(context).enable_silent_payments_scanning,
hint: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => launchUrl(
Uri.parse(
"https://guides.cakewallet.com/docs/cryptos/bitcoin/#silent-payments"),
mode: LaunchMode.externalApplication,
),
child: Row(
children: [
Text(
S.of(context).what_is_silent_payments,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
height: 1,
),
softWrap: true,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Icon(Icons.help_outline,
size: 16,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor),
) )
], ],
), ),
), ),
Observer(
builder: (_) => StandardSwitch(
value: dashboardViewModel.silentPaymentsScanningActive,
onTaped: () => _toggleSilentPaymentsScanning(context),
),
)
],
),
],
),
onTap: () => _toggleSilentPaymentsScanning(context),
icon: Icon(
Icons.lock,
color:
Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
size: 50,
),
),
),
]
],
); );
}),
],
),
),
);
}
Future<void> _toggleSilentPaymentsScanning(BuildContext context) async {
final isSilentPaymentsScanningActive = dashboardViewModel.silentPaymentsScanningActive;
final newValue = !isSilentPaymentsScanningActive;
dashboardViewModel.silentPaymentsScanningActive = newValue;
final needsToSwitch = !isSilentPaymentsScanningActive &&
await bitcoin!.getNodeIsElectrsSPEnabled(dashboardViewModel.wallet) == false;
if (needsToSwitch) {
return showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithTwoActions(
alertTitle: S.of(context).change_current_node_title,
alertContent: S.of(context).confirm_silent_payments_switch_node,
rightButtonText: S.of(context).ok,
leftButtonText: S.of(context).cancel,
actionRightButton: () {
dashboardViewModel.setSilentPaymentsScanning(newValue);
Navigator.of(context).pop();
},
actionLeftButton: () {
dashboardViewModel.silentPaymentsScanningActive = isSilentPaymentsScanningActive;
Navigator.of(context).pop();
},
));
}
return dashboardViewModel.setSilentPaymentsScanning(newValue);
} }
} }
@ -259,6 +369,7 @@ class BalanceRowWidget extends StatelessWidget {
required this.frozenFiatBalance, required this.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,6 +458,16 @@ class BalanceRowWidget extends StatelessWidget {
maxLines: 1, maxLines: 1,
textAlign: TextAlign.start), textAlign: TextAlign.start),
SizedBox(height: 6), SizedBox(height: 6),
if (isTestnet)
Text(S.current.testnet_coins_no_value,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
height: 1)),
if (!isTestnet)
Text('${availableFiatBalance}', Text('${availableFiatBalance}',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
@ -362,10 +484,7 @@ class BalanceRowWidget extends StatelessWidget {
child: Center( child: Center(
child: Column( child: Column(
children: [ children: [
Container( CakeImageWidget(
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(shape: BoxShape.circle),
child: CakeImageWidget(
imageUrl: currency.iconPath, imageUrl: currency.iconPath,
height: 40, height: 40,
width: 40, width: 40,
@ -384,7 +503,6 @@ class BalanceRowWidget extends StatelessWidget {
), ),
), ),
), ),
),
const SizedBox(height: 10), const SizedBox(height: 10),
Text( Text(
currency.title, currency.title,
@ -449,6 +567,7 @@ class BalanceRowWidget extends StatelessWidget {
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
SizedBox(height: 4), SizedBox(height: 4),
if (!isTestnet)
Text( Text(
frozenFiatBalance, frozenFiatBalance,
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -493,6 +612,7 @@ class BalanceRowWidget extends StatelessWidget {
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
SizedBox(height: 4), SizedBox(height: 4),
if (!isTestnet)
Text( Text(
'${additionalFiatBalance}', '${additionalFiatBalance}',
textAlign: TextAlign.center, textAlign: TextAlign.center,

View file

@ -1,23 +1,20 @@
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart'; import 'package:cake_wallet/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);
});
}
} }
} }

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/setting_action_button.dart'; import 'package:cake_wallet/src/widgets/setting_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(

View file

@ -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,7 +72,8 @@ class PresentReceiveOptionPicker extends StatelessWidget {
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
body: Stack( body: Stack(
alignment: AlignmentDirectional.center, alignment: AlignmentDirectional.center,
children:[ AlertBackground( children: [
AlertBackground(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -107,10 +107,19 @@ class PresentReceiveOptionPicker extends StatelessWidget {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text(option.toString(), Text(
option
.toString()
.replaceAll(
RegExp(r'silent payments', caseSensitive: false),
S.current.silent_payments)
.replaceAll(RegExp(r'default', caseSensitive: false),
S.current.string_default),
textAlign: TextAlign.left, textAlign: TextAlign.left,
style: textSmall( style: textSmall(
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor, color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor,
).copyWith( ).copyWith(
fontWeight: fontWeight:
value == option ? FontWeight.w800 : FontWeight.w500, value == option ? FontWeight.w800 : FontWeight.w500,

View file

@ -99,6 +99,14 @@ class ExchangePage extends BasePage {
@override @override
AppBarStyle get appBarStyle => AppBarStyle.transparent; AppBarStyle get appBarStyle => AppBarStyle.transparent;
@override
Function(BuildContext)? get pushToNextWidget => (context) {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.focusedChild?.unfocus();
}
};
@override @override
Widget middle(BuildContext context) => Row( Widget middle(BuildContext context) => Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,

View file

@ -84,12 +84,7 @@ class IoniaGiftCardDetailPage extends BasePage {
} }
}); });
return RouteAwareWidget( return ScrollableWithBottomSection(
pushToWidget: ()=> viewModel.increaseBrightness(),
pushToNextWidget: ()=> DeviceDisplayBrightness.setBrightness(viewModel.brightness),
popNextWidget: ()=> viewModel.increaseBrightness(),
popWidget: ()=> DeviceDisplayBrightness.setBrightness(viewModel.brightness),
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.all(24), contentPadding: EdgeInsets.all(24),
content: Column( content: Column(
children: [ children: [
@ -168,7 +163,7 @@ class IoniaGiftCardDetailPage extends BasePage {
}, },
), ),
), ),
)); );
} }
Widget buildIoniaTile(BuildContext context, {required String title, required String subTitle}) { Widget buildIoniaTile(BuildContext context, {required String title, required String subTitle}) {

View file

@ -38,6 +38,14 @@ class NewWalletPage extends BasePage {
@override @override
String get title => S.current.new_wallet; String get title => S.current.new_wallet;
@override
Function(BuildContext)? get pushToNextWidget => (context) {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.focusedChild?.unfocus();
}
};
@override @override
Widget body(BuildContext context) => WalletNameForm( Widget body(BuildContext context) => WalletNameForm(
_walletNewVM, _walletNewVM,

View file

@ -34,6 +34,14 @@ class NewWalletTypePage extends BasePage {
String get title => String get title =>
isCreate ? S.current.wallet_list_create_new_wallet : S.current.wallet_list_restore_wallet; isCreate ? S.current.wallet_list_create_new_wallet : S.current.wallet_list_restore_wallet;
@override
Function(BuildContext)? get pushToNextWidget => (context) {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.focusedChild?.unfocus();
}
};
@override @override
Widget body(BuildContext context) => WalletTypeForm( Widget body(BuildContext context) => WalletTypeForm(
onTypeSelected: onTypeSelected, onTypeSelected: onTypeSelected,

View file

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

View file

@ -15,18 +15,22 @@ class AddressCell extends StatelessWidget {
required this.textColor, 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 bool isCurrent,
required Color backgroundColor, required Color backgroundColor,
required Color textColor, required Color textColor,
Function(String)? onTap, Function(String)? onTap,
bool hasBalance = false, bool hasBalance = false,
Function()? onEdit}) => 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,
),
], ],
); );
} }

View file

@ -1,3 +1,7 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart'; import 'package:flutter/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);
}
} }

View file

@ -21,6 +21,14 @@ class RestoreFromBackupPage extends BasePage {
@override @override
String get title => S.current.restore_title_from_backup; String get title => S.current.restore_title_from_backup;
@override
Function(BuildContext)? get pushToNextWidget => (context) {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.focusedChild?.unfocus();
}
};
@override @override
Widget body(BuildContext context) { Widget body(BuildContext context) {
reaction((_) => restoreFromBackupViewModel.state, (ExecutionState state) { reaction((_) => restoreFromBackupViewModel.state, (ExecutionState state) {

View file

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
@ -13,6 +15,9 @@ import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart'; import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart';
import 'package:cake_wallet/view_model/restore/wallet_restore_from_qr_code.dart'; import 'package:cake_wallet/view_model/restore/wallet_restore_from_qr_code.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/hardware/device_connection_type.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
@ -24,6 +29,19 @@ class RestoreOptionsPage extends BasePage {
final bool isNewInstall; final bool isNewInstall;
bool get _doesSupportHardwareWallets {
if (!DeviceInfo.instance.isMobile) {
return false;
}
if (isMoneroOnly) {
return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS)
.isNotEmpty;
}
return true;
}
@override @override
Widget body(BuildContext context) { Widget body(BuildContext context) {
final imageColor = Theme.of(context).extension<OptionTileTheme>()!.titleColor; final imageColor = Theme.of(context).extension<OptionTileTheme>()!.titleColor;
@ -57,7 +75,7 @@ class RestoreOptionsPage extends BasePage {
description: S.of(context).restore_description_from_backup, description: S.of(context).restore_description_from_backup,
), ),
), ),
if (DeviceInfo.instance.isMobile) if (_doesSupportHardwareWallets)
Padding( Padding(
padding: EdgeInsets.only(top: 24), padding: EdgeInsets.only(top: 24),
child: OptionTile( child: OptionTile(

View file

@ -101,6 +101,14 @@ class WalletRestorePage extends BasePage {
// String? derivationPath = null; // String? derivationPath = null;
DerivationInfo? derivationInfo; DerivationInfo? derivationInfo;
@override
Function(BuildContext)? get pushToNextWidget => (context) {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.focusedChild?.unfocus();
}
};
@override @override
Widget body(BuildContext context) { Widget body(BuildContext context) {
reaction((_) => walletRestoreViewModel.state, (ExecutionState state) { reaction((_) => walletRestoreViewModel.state, (ExecutionState state) {

View file

@ -1,10 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/totp_request_details.dart'; import 'package:cake_wallet/core/totp_request_details.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/reactions/wallet_connect.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/view_model/link_view_model.dart'; import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -13,7 +10,6 @@ import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/authentication_store.dart'; import 'package:cake_wallet/store/authentication_store.dart';
import 'package:cake_wallet/entities/qr_scanner.dart'; import 'package:cake_wallet/entities/qr_scanner.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:uni_links/uni_links.dart'; import 'package:uni_links/uni_links.dart';
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart'; import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/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';
@ -35,7 +36,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class SendPage extends BasePage { class SendPage extends BasePage {
@ -66,6 +66,14 @@ class SendPage extends BasePage {
@override @override
bool get extendBodyBehindAppBar => true; bool get extendBodyBehindAppBar => true;
@override
Function(BuildContext)? get pushToNextWidget => (context) {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.focusedChild?.unfocus();
}
};
@override @override
Widget? leading(BuildContext context) { Widget? leading(BuildContext context) {
final _backButton = Icon( final _backButton = Icon(
@ -373,17 +381,17 @@ class SendPage extends BasePage {
} }
if (sendViewModel.wallet.isHardwareWallet) { if (sendViewModel.wallet.isHardwareWallet) {
if (!sendViewModel.ledgerViewModel.isConnected) { if (!sendViewModel.ledgerViewModel!.isConnected) {
await Navigator.of(context).pushNamed(Routes.connectDevices, await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams( arguments: ConnectDevicePageParams(
walletType: sendViewModel.walletType, walletType: sendViewModel.walletType,
onConnectDevice: (BuildContext context, _) { onConnectDevice: (BuildContext context, _) {
sendViewModel.ledgerViewModel.setLedger(sendViewModel.wallet); sendViewModel.ledgerViewModel!.setLedger(sendViewModel.wallet);
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
)); ));
} else { } else {
sendViewModel.ledgerViewModel.setLedger(sendViewModel.wallet); sendViewModel.ledgerViewModel!.setLedger(sendViewModel.wallet);
} }
} }
@ -421,6 +429,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();

View file

@ -32,6 +32,14 @@ class SendTemplatePage extends BasePage {
@override @override
AppBarStyle get appBarStyle => AppBarStyle.transparent; AppBarStyle get appBarStyle => AppBarStyle.transparent;
@override
Function(BuildContext)? get pushToNextWidget => (context) {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.focusedChild?.unfocus();
}
};
@override @override
Widget trailing(context) => Observer(builder: (_) { Widget trailing(context) => Observer(builder: (_) {
return sendTemplateViewModel.recipients.length > 1 return sendTemplateViewModel.recipients.length > 1

View file

@ -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) ...[

View file

@ -3,6 +3,7 @@ import 'package:cake_wallet/routes.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/typography.dart'; import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/router.dart' as Router; import 'package:cake_wallet/router.dart' as Router;
import 'package:cake_wallet/themes/extensions/menu_theme.dart'; import 'package:cake_wallet/themes/extensions/menu_theme.dart';
@ -10,7 +11,9 @@ import 'package:cake_wallet/themes/extensions/menu_theme.dart';
final _settingsNavigatorKey = GlobalKey<NavigatorState>(); final _settingsNavigatorKey = GlobalKey<NavigatorState>();
class DesktopSettingsPage extends StatefulWidget { class DesktopSettingsPage extends StatefulWidget {
const DesktopSettingsPage({super.key}); const DesktopSettingsPage(this.dashboardViewModel, {super.key});
final DashboardViewModel dashboardViewModel;
@override @override
State<DesktopSettingsPage> createState() => _DesktopSettingsPageState(); State<DesktopSettingsPage> createState() => _DesktopSettingsPageState();
@ -51,6 +54,12 @@ class _DesktopSettingsPageState extends State<DesktopSettingsPage> {
padding: EdgeInsets.only(top: 0), padding: EdgeInsets.only(top: 0),
itemBuilder: (_, index) { itemBuilder: (_, index) {
final item = SettingActions.desktopSettings[index]; final item = SettingActions.desktopSettings[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(
isLastTile: isLastTile, isLastTile: isLastTile,

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