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
uses: subosito/flutter-action@v1
with:
flutter-version: "3.19.5"
flutter-version: "3.19.6"
channel: stable
- name: Install package dependencies
@ -151,6 +151,7 @@ jobs:
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 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 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

View file

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

View file

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

View file

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

View file

@ -1,23 +1,24 @@
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:convert/convert.dart';
import 'dart:convert';
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_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_core/crypto_currency.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:ledger_bitcoin/ledger_bitcoin.dart';
import 'package:ledger_flutter/ledger_flutter.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';
@ -38,22 +39,28 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex,
String? passphrase,
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
int initialSilentAddressIndex = 0,
bool? alwaysScan,
}) : super(
mnemonic: mnemonic,
passphrase: passphrase,
xpub: xpub,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
networkType: networkParam == null
? bitcoin.bitcoin
: networkParam == BitcoinNetwork.mainnet
mnemonic: mnemonic,
passphrase: passphrase,
xpub: xpub,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
networkType: networkParam == null
? bitcoin.bitcoin
: bitcoin.testnet,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: seedBytes,
currency: CryptoCurrency.btc) {
: networkParam == BitcoinNetwork.mainnet
? bitcoin.bitcoin
: bitcoin.testnet,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: seedBytes,
currency:
networkParam == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc,
alwaysScan: alwaysScan,
) {
// in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here)
// the sideHd derivation path = m/84'/0'/0'/1 (account 1, index unspecified here)
// String derivationPath = walletInfo.derivationInfo!.derivationPath!;
@ -61,14 +68,18 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
// final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType);
walletAddresses = BitcoinWalletAddresses(
walletInfo,
electrumClient: electrumClient,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
initialSilentAddresses: initialSilentAddresses,
initialSilentAddressIndex: initialSilentAddressIndex,
mainHd: hd,
sideHd: accountHD.derive(1),
network: networkParam ?? network,
masterHd:
seedBytes != null ? bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) : null,
);
autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
});
@ -83,9 +94,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
String? addressPageType,
BasedUtxoNetwork? network,
List<BitcoinAddressRecord>? initialAddresses,
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
ElectrumBalance? initialBalance,
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex,
int initialSilentAddressIndex = 0,
}) async {
late Uint8List seedBytes;
@ -108,6 +121,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses,
initialSilentAddresses: initialSilentAddresses,
initialSilentAddressIndex: initialSilentAddressIndex,
initialBalance: initialBalance,
seedBytes: seedBytes,
initialRegularAddressIndex: initialRegularAddressIndex,
@ -122,6 +137,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required String password,
required bool alwaysScan,
}) async {
final network = walletInfo.network != null
? BasedUtxoNetwork.fromName(walletInfo.network!)
@ -162,12 +178,15 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses,
initialSilentAddresses: snp.silentAddresses,
initialSilentAddressIndex: snp.silentAddressIndex,
initialBalance: snp.balance,
seedBytes: seedBytes,
initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex,
addressPageType: snp.addressPageType,
networkParam: network,
alwaysScan: alwaysScan,
);
}
@ -178,7 +197,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) {
_ledger = setLedger;
_ledgerDevice = setLedgerDevice;
_bitcoinLedgerApp = BitcoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!);
_bitcoinLedgerApp =
BitcoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!);
}
@override
@ -201,18 +221,38 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
psbtReadyInputs.add(PSBTReadyUtxoWithAddress(
utxo: utxo.utxo,
rawTx: rawTx,
ownerDetails: utxo.ownerDetails,
ownerDerivationPath: publicKeyAndDerivationPath.derivationPath,
ownerMasterFingerprint: masterFingerprint,
ownerPublicKey: publicKeyAndDerivationPath.publicKey,
utxo: utxo.utxo,
rawTx: rawTx,
ownerDetails: utxo.ownerDetails,
ownerDerivationPath: publicKeyAndDerivationPath.derivationPath,
ownerMasterFingerprint: masterFingerprint,
ownerPublicKey: publicKeyAndDerivationPath.publicKey,
));
}
final psbt = PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
final psbt =
PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt);
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.sideHd,
required super.network,
required super.electrumClient,
super.initialAddresses,
super.initialRegularAddressIndex,
super.initialChangeAddressIndex,
super.initialSilentAddresses,
super.initialSilentAddressIndex = 0,
super.masterHd,
}) : super(walletInfo);
@override

View file

@ -19,10 +19,11 @@ class BitcoinWalletService extends WalletService<
BitcoinRestoreWalletFromSeedCredentials,
BitcoinRestoreWalletFromWIFCredentials,
BitcoinRestoreWalletFromHardware> {
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan);
final Box<WalletInfo> walletInfoSource;
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
final bool alwaysScan;
@override
WalletType getType() => WalletType.bitcoin;
@ -55,20 +56,24 @@ class BitcoinWalletService extends WalletService<
.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
try {
final wallet = await BitcoinWalletBase.open(
password: password,
name: name,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource);
password: password,
name: name,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource,
alwaysScan: alwaysScan,
);
await wallet.init();
saveBackup(name);
return wallet;
} catch (_) {
await restoreWalletFilesFromBackup(name);
final wallet = await BitcoinWalletBase.open(
password: password,
name: name,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource);
password: password,
name: name,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource,
alwaysScan: alwaysScan,
);
await wallet.init();
return wallet;
}
@ -87,10 +92,12 @@ class BitcoinWalletService extends WalletService<
final currentWalletInfo = walletInfoSource.values
.firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!;
final currentWallet = await BitcoinWalletBase.open(
password: password,
name: currentName,
walletInfo: currentWalletInfo,
unspentCoinsInfo: unspentCoinsInfoSource);
password: password,
name: currentName,
walletInfo: currentWalletInfo,
unspentCoinsInfo: unspentCoinsInfoSource,
alwaysScan: alwaysScan,
);
await currentWallet.renameWalletFiles(newName);
await saveBackup(newName);
@ -105,12 +112,13 @@ class BitcoinWalletService extends WalletService<
@override
Future<BitcoinWallet> restoreFromHardwareWallet(BitcoinRestoreWalletFromHardware credentials,
{bool? isTestnet}) async {
final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
credentials.walletInfo?.network = network.value;
credentials.walletInfo?.derivationInfo?.derivationPath = credentials.hwAccountData.derivationPath;
credentials.walletInfo?.derivationInfo?.derivationPath =
credentials.hwAccountData.derivationPath;
final wallet = await BitcoinWallet(password: credentials.password!,
final wallet = await BitcoinWallet(
password: credentials.password!,
xpub: credentials.hwAccountData.xpub,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
@ -123,7 +131,7 @@ class BitcoinWalletService extends WalletService<
@override
Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials,
{bool? isTestnet}) async =>
{bool? isTestnet}) async =>
throw UnimplementedError();
@override

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

@ -14,7 +14,6 @@ import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/litecoin_network.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bip39/bip39.dart' as bip39;
part 'litecoin_wallet.g.dart';
@ -45,7 +44,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
currency: CryptoCurrency.ltc) {
walletAddresses = LitecoinWalletAddresses(
walletInfo,
electrumClient: electrumClient,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,

View file

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

View file

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

View file

@ -29,10 +29,10 @@ packages:
dependency: transitive
description:
name: asn1lib
sha256: c9c85fedbe2188b95133cbe960e16f5f448860f7133330e272edbbca5893ddc6
sha256: "58082b3f0dca697204dbab0ef9ff208bfaea7767ea771076af9a343488428dda"
url: "https://pub.dev"
source: hosted
version: "1.5.2"
version: "1.5.3"
async:
dependency: transitive
description:
@ -79,11 +79,11 @@ packages:
dependency: "direct main"
description:
path: "."
ref: cake-update-v2
resolved-ref: "01d844a5f5a520a31df5254e34169af4664aa769"
url: "https://github.com/cake-tech/bitcoin_base.git"
ref: cake-update-v3
resolved-ref: cc99eedb1d28ee9376dda0465ef72aa627ac6149
url: "https://github.com/cake-tech/bitcoin_base"
source: git
version: "4.2.0"
version: "4.2.1"
bitcoin_flutter:
dependency: "direct main"
description:
@ -96,11 +96,12 @@ packages:
blockchain_utils:
dependency: "direct main"
description:
name: blockchain_utils
sha256: "38ef5f4a22441ac4370aed9071dc71c460acffc37c79b344533f67d15f24c13c"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
path: "."
ref: cake-update-v1
resolved-ref: cabd7e0e16c4da9920338c76eff3aeb8af0211f3
url: "https://github.com/cake-tech/blockchain_utils"
source: git
version: "2.1.2"
boolean_selector:
dependency: transitive
description:
@ -197,6 +198,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.3"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19
url: "https://pub.dev"
source: hosted
version: "0.4.1"
clock:
dependency: transitive
description:
@ -217,10 +226,10 @@ packages:
dependency: transitive
description:
name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.17.1"
version: "1.18.0"
convert:
dependency: transitive
description:
@ -241,10 +250,10 @@ packages:
dependency: "direct main"
description:
name: cryptography
sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35
sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05
url: "https://pub.dev"
source: hosted
version: "2.5.0"
version: "2.7.0"
cw_core:
dependency: "direct main"
description:
@ -288,10 +297,18 @@ packages:
dependency: transitive
description:
name: ffi
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.1.2"
ffigen:
dependency: transitive
description:
name: ffigen
sha256: d3e76c2ad48a4e7f93a29a162006f00eba46ce7c08194a77bb5c5e97d1b5ff0a
url: "https://pub.dev"
source: hosted
version: "8.0.2"
file:
dependency: transitive
description:
@ -346,10 +363,10 @@ packages:
dependency: transitive
description:
name: functional_data
sha256: aefdec4365452283b2a7cf420a3169654d51d3e9553069a22d76680d7a9d7c3d
sha256: "76d17dc707c40e552014f5a49c0afcc3f1e3f05e800cd6b7872940bfe41a5039"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
version: "1.2.0"
glob:
dependency: transitive
description:
@ -394,10 +411,10 @@ packages:
dependency: "direct main"
description:
name: http
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.2.1"
http_multi_server:
dependency: transitive
description:
@ -442,19 +459,43 @@ packages:
dependency: transitive
description:
name: json_annotation
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.8.1"
version: "4.9.0"
leak_tracker:
dependency: transitive
description:
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:
dependency: "direct main"
description:
path: "."
ref: HEAD
resolved-ref: b6ed573cbeb57d5f0d39dfe4254bf9d15b620ab6
url: "https://github.com/cake-tech/ledger-bitcoin.git"
resolved-ref: f819d37e235e239c315e93856abbf5e5d3b71dab
url: "https://github.com/cake-tech/ledger-bitcoin"
source: git
version: "0.0.1"
version: "0.0.2"
ledger_flutter:
dependency: "direct main"
description:
@ -483,34 +524,34 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.15"
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
version: "0.8.0"
meta:
dependency: transitive
description:
name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.11.0"
mime:
dependency: transitive
description:
name: mime
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
version: "1.0.5"
mobx:
dependency: "direct main"
description:
@ -547,34 +588,34 @@ packages:
dependency: transitive
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
version: "1.9.0"
path_provider:
dependency: "direct main"
description:
name: path_provider
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.3"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
url: "https://pub.dev"
source: hosted
version: "2.2.2"
version: "2.2.4"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.4.0"
path_provider_linux:
dependency: transitive
description:
@ -619,10 +660,10 @@ packages:
dependency: transitive
description:
name: pointycastle
sha256: "70fe966348fe08c34bf929582f1d8247d9d9408130723206472b4687227e4333"
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
url: "https://pub.dev"
source: hosted
version: "3.8.0"
version: "3.9.1"
pool:
dependency: transitive
description:
@ -663,6 +704,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.3"
quiver:
dependency: transitive
description:
name: quiver
sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47
url: "https://pub.dev"
source: hosted
version: "3.2.1"
reactive_ble_mobile:
dependency: transitive
description:
@ -712,10 +761,10 @@ packages:
dependency: transitive
description:
name: socks5_proxy
sha256: "1d21b5606169654bbf4cfb904e8e6ed897e9f763358709f87310c757096d909a"
sha256: "045cbba84f6e2b01c1c77634a63e926352bf110ef5f07fc462c6d43bbd4b6a83"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
version: "1.0.5+dev.2"
source_gen:
dependency: transitive
description:
@ -736,26 +785,35 @@ packages:
dependency: transitive
description:
name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.10.0"
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:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
stream_transform:
dependency: transitive
description:
@ -784,10 +842,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "0.6.1"
timing:
dependency: transitive
description:
@ -820,30 +878,46 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
watcher:
vm_service:
dependency: transitive
description:
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev"
source: hosted
version: "13.0.0"
watcher:
dependency: "direct overridden"
description:
name: watcher
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
web:
dependency: transitive
description:
name: web
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.4.5"
win32:
dependency: transitive
description:
name: win32
sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
url: "https://pub.dev"
source: hosted
version: "5.0.9"
version: "5.5.0"
xdg_directories:
dependency: transitive
description:
@ -860,6 +934,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.2"
yaml_edit:
dependency: transitive
description:
name: yaml_edit
sha256: e9c1a3543d2da0db3e90270dbb1e4eebc985ee5e3ffe468d83224472b2194a5f
url: "https://pub.dev"
source: hosted
version: "2.2.1"
sdks:
dart: ">=3.0.6 <4.0.0"
flutter: ">=3.10.0"
dart: ">=3.3.0 <4.0.0"
flutter: ">=3.16.6"

View file

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

View file

@ -46,7 +46,6 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
currency: CryptoCurrency.bch) {
walletAddresses = BitcoinCashWalletAddresses(
walletInfo,
electrumClient: electrumClient,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,

View file

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

View file

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

View file

@ -104,6 +104,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
CryptoCurrency.digibyte,
CryptoCurrency.usdtSol,
CryptoCurrency.usdcTrc20,
CryptoCurrency.tbtc,
];
static const havenCurrencies = [
@ -218,7 +219,8 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
static const kaspa = CryptoCurrency(title: 'KAS', fullName: 'Kaspa', raw: 89, name: 'kas', iconPath: 'assets/images/kaspa_icon.png', decimals: 8);
static const digibyte = CryptoCurrency(title: 'DGB', fullName: 'DigiByte', raw: 90, name: 'dgb', iconPath: 'assets/images/digibyte.png', decimals: 8);
static const usdtSol = CryptoCurrency(title: 'USDT', tag: 'SOL', fullName: 'USDT Tether', raw: 91, name: 'usdtsol', iconPath: 'assets/images/usdt_icon.png', decimals: 6);
static const usdcTrc20 = CryptoCurrency(title: 'USDC', tag: 'TRX', fullName: 'USDC Coin', raw: 92, name: 'usdctrc20', iconPath: 'assets/images/usdc_icon.png', decimals: 6);
static const usdcTrc20 = CryptoCurrency(title: 'USDC', tag: 'TRX', fullName: 'USDC Coin', raw: 92, name: 'usdctrc20', iconPath: 'assets/images/usdc_icon.png', decimals: 6);
static const tbtc = CryptoCurrency(title: 'tBTC', fullName: 'Testnet Bitcoin', raw: 93, name: 'tbtc', iconPath: 'assets/images/tbtc.png', decimals: 8);
static final Map<int, CryptoCurrency> _rawCurrencyMap =
@ -253,7 +255,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
static CryptoCurrency fromString(String name, {CryptoCurrency? walletCurrency}) {
try {
return CryptoCurrency.all.firstWhere((element) =>
element.title.toLowerCase() == name &&
element.title.toLowerCase() == name.toLowerCase() &&
(element.tag == null ||
element.tag == walletCurrency?.title ||
element.tag == walletCurrency?.tag));

View file

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

View file

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

View file

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

View file

@ -244,8 +244,12 @@ class Node extends HiveObject with Keyable {
Future<bool> requestElectrumServer() async {
try {
await SecureSocket.connect(uri.host, uri.port,
timeout: Duration(seconds: 5), onBadCertificate: (_) => true);
if (useSSL == true) {
await SecureSocket.connect(uri.host, uri.port,
timeout: Duration(seconds: 5), onBadCertificate: (_) => true);
} else {
await Socket.connect(uri.host, uri.port, timeout: Duration(seconds: 5));
}
return true;
} catch (_) {
return false;

View file

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

View file

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

View file

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

View file

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

View file

@ -66,21 +66,21 @@ class DerivationInfo extends HiveObject {
@HiveType(typeId: WalletInfo.typeId)
class WalletInfo extends HiveObject {
WalletInfo(
this.id,
this.name,
this.type,
this.isRecovery,
this.restoreHeight,
this.timestamp,
this.dirPath,
this.path,
this.address,
this.yatEid,
this.yatLastUsedAddressRaw,
this.showIntroCakePayCard,
this.derivationInfo,
this.hardwareWalletType,
): _yatLastUsedAddressController = StreamController<String>.broadcast();
this.id,
this.name,
this.type,
this.isRecovery,
this.restoreHeight,
this.timestamp,
this.dirPath,
this.path,
this.address,
this.yatEid,
this.yatLastUsedAddressRaw,
this.showIntroCakePayCard,
this.derivationInfo,
this.hardwareWalletType,
) : _yatLastUsedAddressController = StreamController<String>.broadcast();
factory WalletInfo.external({
required String id,
@ -207,4 +207,9 @@ class WalletInfo extends HiveObject {
Stream<String> get yatLastUsedAddressStream => _yatLastUsedAddressController.stream;
StreamController<String> _yatLastUsedAddressController;
Future<void> updateRestoreHeight(int height) async {
restoreHeight = height;
await save();
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -173,10 +173,10 @@ packages:
dependency: transitive
description:
name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.17.1"
version: "1.18.0"
convert:
dependency: transitive
description:
@ -399,6 +399,30 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct main"
description:
@ -419,26 +443,26 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.15"
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
version: "0.8.0"
meta:
dependency: transitive
description:
name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.11.0"
mime:
dependency: transitive
description:
@ -492,10 +516,10 @@ packages:
dependency: transitive
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
version: "1.9.0"
path_provider:
dependency: transitive
description:
@ -713,26 +737,26 @@ packages:
dependency: transitive
description:
name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.10.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
stream_transform:
dependency: transitive
description:
@ -761,10 +785,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "0.6.1"
timing:
dependency: transitive
description:
@ -789,8 +813,16 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
watcher:
vm_service:
dependency: transitive
description:
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev"
source: hosted
version: "13.0.0"
watcher:
dependency: "direct overridden"
description:
name: watcher
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
@ -830,5 +862,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.0.0 <4.0.0"
dart: ">=3.2.0-0 <4.0.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',
enabled: true,
),
SPLToken(
name: 'Wrapped Ethereum (Sollet)',
symbol: 'soETH',
mintAddress: '2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk',
decimal: 6,
mint: 'soEth',
iconPath: 'assets/images/eth_icon.png',
),
SPLToken(
name: 'Wrapped SOL',
symbol: 'WSOL',
mintAddress: 'So11111111111111111111111111111111111111112',
decimal: 9,
mint: 'WSOL',
iconPath: 'assets/images/sol_icon.png',
),
SPLToken(
name: 'Wrapped Bitcoin (Sollet)',
symbol: 'BTC',
mintAddress: '9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E',
decimal: 6,
mint: 'btcsol',
iconPath: 'assets/images/btc.png',
),
SPLToken(
name: 'Bonk',
symbol: 'Bonk',
@ -50,21 +26,7 @@ class DefaultSPLTokens {
decimal: 5,
mint: 'Bonk',
iconPath: 'assets/images/bonk_icon.png',
),
SPLToken(
name: 'Helium Network Token',
symbol: 'HNT',
mintAddress: 'hntyVP6YFm1Hg25TN9WGLqM12b8TQmcknKrdu1oxWux',
decimal: 8,
mint: 'hnt',
iconPath: 'assets/images/hnt_icon.png',
),
SPLToken(
name: 'Pyth Network',
symbol: 'PYTH',
mintAddress: 'HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3',
decimal: 6,
mint: 'pyth',
enabled: true,
),
SPLToken(
name: 'Raydium',
@ -73,6 +35,51 @@ class DefaultSPLTokens {
decimal: 6,
mint: 'ray',
iconPath: 'assets/images/ray_icon.png',
enabled: true,
),
SPLToken(
name: 'Wrapped Ethereum (Sollet)',
symbol: 'soETH',
mintAddress: '2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk',
decimal: 6,
mint: 'soEth',
iconPath: 'assets/images/eth_icon.png',
enabled: false,
),
SPLToken(
name: 'Wrapped SOL',
symbol: 'WSOL',
mintAddress: 'So11111111111111111111111111111111111111112',
decimal: 9,
mint: 'WSOL',
iconPath: 'assets/images/sol_icon.png',
enabled: false,
),
SPLToken(
name: 'Wrapped Bitcoin (Sollet)',
symbol: 'BTC',
mintAddress: '9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E',
decimal: 6,
mint: 'btcsol',
iconPath: 'assets/images/btc.png',
enabled: false,
),
SPLToken(
name: 'Helium Network Token',
symbol: 'HNT',
mintAddress: 'hntyVP6YFm1Hg25TN9WGLqM12b8TQmcknKrdu1oxWux',
decimal: 8,
mint: 'hnt',
iconPath: 'assets/images/hnt_icon.png',
enabled: false,
),
SPLToken(
name: 'Pyth Network',
symbol: 'PYTH',
mintAddress: 'HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3',
decimal: 6,
mint: 'pyth',
enabled: false,
),
SPLToken(
name: 'GMT',
@ -81,6 +88,7 @@ class DefaultSPLTokens {
decimal: 6,
mint: 'ray',
iconPath: 'assets/images/gmt_icon.png',
enabled: false,
),
SPLToken(
name: 'AvocadoCoin',
@ -89,6 +97,7 @@ class DefaultSPLTokens {
decimal: 8,
mint: 'avdo',
iconPath: 'assets/images/avdo_icon.png',
enabled: false,
),
];

View file

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

View file

@ -130,11 +130,6 @@ class TronClient {
final energyInSun = chainParams.getEnergyFee!;
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(
rawData: rawTransaction,
signature: [Uint8List(65)],
@ -185,17 +180,6 @@ class TronClient {
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');
return totalBurn;
@ -224,7 +208,7 @@ class TronClient {
TransactionContract(type: contract.contractType, parameter: parameter);
// 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
TransactionRaw rawTransaction = TransactionRaw(
@ -385,7 +369,7 @@ class TronClient {
TransactionContract(type: contract.contractType, parameter: parameter);
// 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
TransactionRaw rawTransaction = TransactionRaw(
@ -403,7 +387,7 @@ class TronClient {
if (feeLimit > tronBalanceInt) {
final feeInTrx = TronHelper.fromSun(BigInt.parse(feeLimit.toString()));
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) {
final feeInTrx = TronHelper.fromSun(BigInt.parse(feeLimit.toString()));
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
cw_evm:
path: ../cw_evm
on_chain: ^3.0.1
blockchain_utils: ^2.1.1
on_chain:
git:
url: https://github.com/cake-tech/On_chain
ref: cake-update-v1
blockchain_utils:
git:
url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v1
mobx: ^2.3.0+1
bip39: ^1.0.6
hive: ^2.2.3

View file

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

View file

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

View file

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

View file

@ -281,17 +281,19 @@ class MoonPayProvider extends BuyProvider {
throw Exception('Could not launch URL');
}
} catch (e) {
await showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: 'MoonPay',
alertContent: 'The MoonPay service is currently unavailable: $e',
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
);
},
);
if (context.mounted) {
await showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: 'MoonPay',
alertContent: 'The MoonPay service is currently unavailable: $e',
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
);
},
);
}
}
}

View file

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

View file

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

View file

@ -58,7 +58,7 @@ class WalletCreationService {
checkIfExists(credentials.name);
final password = generateWalletPassword();
credentials.password = password;
if (type == WalletType.bitcoinCash || type == WalletType.ethereum) {
if (_hasSeedPhraseLengthOption) {
credentials.seedPhraseLength = settingsStore.seedPhraseLength.value;
}
await keyService.saveWalletPassword(password: password, walletName: credentials.name);
@ -72,6 +72,25 @@ class WalletCreationService {
return wallet;
}
bool get _hasSeedPhraseLengthOption {
switch (type) {
case WalletType.ethereum:
case WalletType.bitcoinCash:
case WalletType.polygon:
case WalletType.solana:
case WalletType.tron:
return true;
case WalletType.monero:
case WalletType.none:
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.haven:
case WalletType.nano:
case WalletType.banano:
return false;
}
}
Future<WalletBase> restoreFromKeys(WalletCredentials credentials, {bool? isTestnet}) async {
checkIfExists(credentials.name);
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/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart';
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.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/backup_service.dart';
import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:cake_wallet/core/totp_request_details.dart';
import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart';
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
@ -26,10 +29,6 @@ import 'package:cake_wallet/entities/contact.dart';
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
import 'package:cw_core/receive_page_option.dart';
import 'package:cake_wallet/entities/qr_view_data.dart';
import 'package:cake_wallet/entities/template.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_template_page.dart';
import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart';
import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/domain_lookups_page.dart';
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
import 'package:cake_wallet/src/screens/settings/silent_payments_settings.dart';
import 'package:cake_wallet/src/screens/settings/tor_page.dart';
import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
@ -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/yat/yat_store.dart';
import 'package:cake_wallet/themes/theme_list.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/payment_request.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_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/desktop_sidebar_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart';
@ -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_payment_status_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
@ -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/privacy_settings_view_model.dart';
import 'package:cake_wallet/view_model/settings/security_settings_view_model.dart';
import 'package:cake_wallet/view_model/settings/silent_payments_settings_view_model.dart';
import 'package:cake_wallet/view_model/settings/trocador_providers_view_model.dart';
import 'package:cake_wallet/view_model/setup_pin_code_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:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:get_it/get_it.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.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;
@ -339,7 +312,6 @@ Future<void> setup({
getIt.registerFactory<Box<Node>>(() => _powNodeSource, instanceName: Node.boxName + "pow");
getIt.registerSingleton(AuthenticationStore());
getIt.registerSingleton(LedgerViewModel());
getIt.registerSingleton<WalletListStore>(WalletListStore());
getIt.registerSingleton(NodeListStoreBase.instance);
getIt.registerSingleton<SettingsStore>(settingsStore);
@ -359,11 +331,12 @@ Future<void> setup({
getIt.registerSingleton<ExchangeTemplateStore>(
ExchangeTemplateStore(templateSource: _exchangeTemplates));
getIt.registerSingleton<YatStore>(
YatStore(appStore: getIt.get<AppStore>(), secureStorage: getIt.get<SecureStorage>())
..init());
YatStore(appStore: getIt.get<AppStore>(), secureStorage: getIt.get<SecureStorage>())..init());
getIt.registerSingleton<AnonpayTransactionsStore>(
AnonpayTransactionsStore(anonpayInvoiceInfoSource: _anonpayInvoiceInfoSource));
getIt.registerLazySingleton(() => LedgerViewModel());
final secretStore = await SecretStoreBase.load(getIt.get<SecureStorage>());
getIt.registerSingleton<SecretStore>(secretStore);
@ -600,7 +573,7 @@ Future<void> setup({
getIt.registerFactory<Modify2FAPage>(
() => Modify2FAPage(setup2FAViewModel: getIt.get<Setup2FAViewModel>()));
getIt.registerFactory<DesktopSettingsPage>(() => DesktopSettingsPage());
getIt.registerFactory<DesktopSettingsPage>(() => DesktopSettingsPage(getIt.get<DashboardViewModel>()));
getIt.registerFactoryParam<ReceiveOptionViewModel, ReceivePageOption?, void>(
(pageOption, _) => ReceiveOptionViewModel(getIt.get<AppStore>().wallet!, pageOption));
@ -655,7 +628,7 @@ Future<void> setup({
getIt.get<BalanceViewModel>(),
getIt.get<ContactListViewModel>(),
_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>());
});
getIt.registerFactory(() =>
SilentPaymentsSettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!));
getIt.registerFactory(() {
return PrivacySettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!);
});
@ -825,8 +801,8 @@ Future<void> setup({
getIt.registerFactory(() => ConnectionSyncPage(getIt.get<DashboardViewModel>()));
getIt.registerFactory(
() => SecurityBackupPage(getIt.get<SecuritySettingsViewModel>(), getIt.get<AuthService>()));
getIt.registerFactory(() => SecurityBackupPage(getIt.get<SecuritySettingsViewModel>(),
getIt.get<AuthService>(), getIt.get<AppStore>().wallet!.isHardwareWallet));
getIt.registerFactory(() => PrivacyPage(getIt.get<PrivacySettingsViewModel>()));
@ -836,6 +812,9 @@ Future<void> setup({
getIt.registerFactory(() => DisplaySettingsPage(getIt.get<DisplaySettingsViewModel>()));
getIt.registerFactory(
() => SilentPaymentsSettingsPage(getIt.get<SilentPaymentsSettingsViewModel>()));
getIt.registerFactory(() => OtherSettingsPage(getIt.get<OtherSettingsViewModel>()));
getIt.registerFactory(() => NanoChangeRepPage(
@ -862,10 +841,14 @@ Future<void> setup({
isSelected: isSelected));
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(
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(
settingsStore: getIt.get<AppStore>().settingsStore,
@ -919,7 +902,11 @@ Future<void> setup({
case WalletType.monero:
return monero!.createMoneroWalletService(_walletInfoSource, _unspentCoinsInfoSource);
case WalletType.bitcoin:
return bitcoin!.createBitcoinWalletService(_walletInfoSource, _unspentCoinsInfoSource);
return bitcoin!.createBitcoinWalletService(
_walletInfoSource,
_unspentCoinsInfoSource,
getIt.get<SettingsStore>().silentPaymentsAlwaysScan,
);
case WalletType.litecoin:
return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource);
case WalletType.ethereum:
@ -966,9 +953,9 @@ Future<void> setup({
(derivations, _) => WalletRestoreChooseDerivationViewModel(derivationInfos: derivations));
getIt.registerFactoryParam<WalletRestoreChooseDerivationPage, List<DerivationInfo>, void>(
(credentials, _) =>
(derivations, _) =>
WalletRestoreChooseDerivationPage(getIt.get<WalletRestoreChooseDerivationViewModel>(
param1: credentials,
param1: derivations,
)));
getIt.registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void>(
@ -1017,8 +1004,8 @@ Future<void> setup({
getIt.registerFactory(() => BackupPage(getIt.get<BackupViewModel>()));
getIt.registerFactory(() =>
EditBackupPasswordViewModel(getIt.get<SecureStorage>(), getIt.get<SecretStore>()));
getIt.registerFactory(
() => EditBackupPasswordViewModel(getIt.get<SecureStorage>(), getIt.get<SecretStore>()));
getIt.registerFactory(() => EditBackupPasswordPage(getIt.get<EditBackupPasswordViewModel>()));
@ -1066,8 +1053,8 @@ Future<void> setup({
getIt.registerFactory(() => SupportPage(getIt.get<SupportViewModel>()));
getIt.registerFactory(() => SupportChatPage(getIt.get<SupportViewModel>(),
secureStorage: getIt.get<SecureStorage>()));
getIt.registerFactory(() =>
SupportChatPage(getIt.get<SupportViewModel>(), secureStorage: getIt.get<SecureStorage>()));
getIt.registerFactory(() => SupportOtherLinksPage(getIt.get<SupportViewModel>()));
@ -1116,7 +1103,7 @@ Future<void> setup({
getIt.registerFactory(() => IoniaGiftCardsListViewModel(ioniaService: getIt.get<IoniaService>()));
getIt.registerFactory(() => MarketPlaceViewModel(getIt.get<IoniaService>()));
getIt.registerFactory(() => CakeFeaturesViewModel(getIt.get<IoniaService>()));
getIt.registerFactory(() => IoniaAuthViewModel(ioniaService: getIt.get<IoniaService>()));

View file

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

View file

@ -44,6 +44,8 @@ class PreferencesKey {
static const polygonTransactionPriority = 'current_fee_priority_polygon';
static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash';
static const customBitcoinFeeRate = 'custom_electrum_fee_rate';
static const silentPaymentsCardDisplay = 'silentPaymentsCardDisplay';
static const silentPaymentsAlwaysScan = 'silentPaymentsAlwaysScan';
static const shouldShowReceiveWarning = 'should_show_receive_warning';
static const shouldShowYatPopup = 'should_show_yat_popup';
static const shouldShowRepWarning = 'should_show_rep_warning';
@ -65,6 +67,7 @@ class PreferencesKey {
static const lookupsUnstoppableDomains = 'looks_up_unstoppable_domain';
static const lookupsOpenAlias = 'looks_up_open_alias';
static const lookupsENS = 'looks_up_ens';
static const showCameraConsent = 'show_camera_consent';
static String moneroWalletUpdateV1Key(String 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');
static const exolix =
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 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}) {
switch (raw) {
@ -43,10 +44,12 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
return trocador;
case 6:
return exolix;
case 8:
return thorChain;
case 7:
return all;
case 8:
return thorChain;
case 9:
return quantex;
default:
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:hive/hive.dart';
part 'trade.g.dart';
@HiveType(typeId: Trade.typeId)
class Trade extends HiveObject {
Trade({
required this.id,
@ -32,6 +29,7 @@ class Trade extends HiveObject {
this.txId,
this.isRefund,
this.isSendAll,
this.router,
}) {
if (provider != null) providerRaw = provider.raw;
@ -121,21 +119,26 @@ class Trade extends HiveObject {
@HiveField(21)
bool? isSendAll;
@HiveField(22)
String? router;
static Trade fromMap(Map<String, Object?> map) {
return Trade(
id: map['id'] as String,
provider: ExchangeProviderDescription.deserialize(raw: map['provider'] as int),
from: CryptoCurrency.deserialize(raw: map['input'] as int),
to: CryptoCurrency.deserialize(raw: map['output'] as int),
createdAt:
map['date'] != null ? DateTime.fromMillisecondsSinceEpoch(map['date'] as int) : null,
amount: map['amount'] as String,
walletId: map['wallet_id'] as String,
fromWalletAddress: map['from_wallet_address'] as String?,
memo: map['memo'] as String?,
txId: map['tx_id'] as String?,
isRefund: map['isRefund'] as bool?,
isSendAll: map['isSendAll'] as bool?);
id: map['id'] as String,
provider: ExchangeProviderDescription.deserialize(raw: map['provider'] as int),
from: CryptoCurrency.deserialize(raw: map['input'] as int),
to: CryptoCurrency.deserialize(raw: map['output'] as int),
createdAt:
map['date'] != null ? DateTime.fromMillisecondsSinceEpoch(map['date'] as int) : null,
amount: map['amount'] as String,
walletId: map['wallet_id'] as String,
fromWalletAddress: map['from_wallet_address'] as String?,
memo: map['memo'] as String?,
txId: map['tx_id'] as String?,
isRefund: map['isRefund'] as bool?,
isSendAll: map['isSendAll'] as bool?,
router: map['router'] as String?,
);
}
Map<String, dynamic> toMap() {
@ -152,8 +155,111 @@ class Trade extends HiveObject {
'tx_id': txId,
'isRefund': isRefund,
'isSendAll': isSendAll,
'router': router,
};
}
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');
static const failed = TradeState(raw: 'failed', title: 'Failed');
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 settled = TradeState(raw: 'settled', title: 'Settlement completed');
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 sending = TradeState(raw: 'sending', title: 'Sending');
static const success = TradeState(raw: 'success', title: 'Success');
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) {
case 'NOT_FOUND':
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/buy/order.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/exception_handler.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/exchange_template.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:cake_wallet/monero/monero.dart';
import 'package:cw_core/cake_hive.dart';
@ -46,9 +44,10 @@ import 'package:cw_core/window_size.dart';
final navigatorKey = GlobalKey<NavigatorState>();
final rootKey = GlobalKey<RootState>();
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
final RouteObserver<PageRoute<dynamic>> routeObserver = RouteObserver<PageRoute<dynamic>>();
Future<void> main() async {
bool isAppRunning = false;
await runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized();
@ -63,13 +62,42 @@ Future<void> main() async {
};
await setDefaultMinimumWindowSize();
await CakeHive.close();
await initializeAppConfigs();
runApp(App());
isAppRunning = true;
}, (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));
});
}
@ -174,7 +202,7 @@ Future<void> initializeAppConfigs() async {
transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 33,
initialMigrationVersion: 34,
);
}
@ -229,61 +257,6 @@ class App extends StatefulWidget {
}
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
Widget build(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:cw_core/wallet_base.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/store/settings_store.dart';
Timer? _checkConnectionTimer;
void startCheckConnectionReaction(
WalletBase wallet, SettingsStore settingsStore,
void startCheckConnectionReaction(WalletBase wallet, SettingsStore settingsStore,
{int timeInterval = 5}) {
_checkConnectionTimer?.cancel();
_checkConnectionTimer =
Timer.periodic(Duration(seconds: timeInterval), (_) async {
_checkConnectionTimer = Timer.periodic(Duration(seconds: timeInterval), (_) async {
if (wallet.type == WalletType.bitcoin && wallet.syncStatus is SyncingSyncStatus) {
return;
}
try {
final connectivityResult = await (Connectivity().checkConnectivity());
@ -20,14 +24,11 @@ void startCheckConnectionReaction(
return;
}
if (wallet.syncStatus is LostConnectionSyncStatus ||
wallet.syncStatus is FailedSyncStatus) {
final alive =
await settingsStore.getCurrentNode(wallet.type).requestNode();
if (wallet.syncStatus is LostConnectionSyncStatus || wallet.syncStatus is FailedSyncStatus) {
final alive = await settingsStore.getCurrentNode(wallet.type).requestNode();
if (alive) {
await wallet.connectToNode(
node: settingsStore.getCurrentNode(wallet.type));
await wallet.connectToNode(node: settingsStore.getCurrentNode(wallet.type));
}
}
} catch (e) {

View file

@ -26,29 +26,32 @@ void startWalletSyncStatusChangeReaction(
SettingsStore settingsStore,
Box<TransactionDescription> transactionDescription) {
_onWalletSyncStatusChangeReaction?.reaction.dispose();
_onWalletSyncStatusChangeReaction = reaction((_) => wallet.syncStatus, (SyncStatus status) async {
if (status is ConnectedSyncStatus) {
await wallet.startSync();
if (wallet.type == WalletType.haven) {
await updateHavenRate(fiatConversionStore);
try {
if (status is ConnectedSyncStatus) {
await wallet.startSync();
if (wallet.type == WalletType.haven) {
await updateHavenRate(fiatConversionStore);
}
}
}
if (status is SyncingSyncStatus) {
await WakelockPlus.enable();
}
if (status is SyncedSyncStatus || status is FailedSyncStatus) {
await WakelockPlus.disable();
if (status is SyncingSyncStatus) {
await WakelockPlus.enable();
}
if (status is SyncedSyncStatus || status is FailedSyncStatus) {
await WakelockPlus.disable();
if (status is SyncedSyncStatus &&
(settingsStore.fiatApiMode != FiatApiMode.disabled ||
settingsStore.showHistoricalFiatAmount)) {
_debounceTimer?.cancel();
_debounceTimer = Timer(Duration(milliseconds: 100), () async {
await historicalRateUpdate(
wallet, settingsStore, fiatConversionStore, transactionDescription);
});
if (status is SyncedSyncStatus &&
(settingsStore.fiatApiMode != FiatApiMode.disabled ||
settingsStore.showHistoricalFiatAmount)) {
_debounceTimer?.cancel();
_debounceTimer = Timer(Duration(milliseconds: 100), () async {
await historicalRateUpdate(
wallet, settingsStore, fiatConversionStore, transactionDescription);
});
}
}
} 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/privacy_page.dart';
import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
import 'package:cake_wallet/src/screens/settings/silent_payments_settings.dart';
import 'package:cake_wallet/src/screens/settings/tor_page.dart';
import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
@ -366,6 +367,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
param1: settings.arguments as OnAuthenticationFinished, param2: false),
onWillPop: () async => false));
case Routes.silentPaymentsSettings:
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<SilentPaymentsSettingsPage>());
case Routes.connectionSync:
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<ConnectionSyncPage>());

View file

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

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/route_aware.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/di.dart';
@ -32,6 +33,14 @@ abstract class BasePage extends StatelessWidget {
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;
Widget Function(BuildContext, Widget)? get rootWrapper => null;
@ -162,15 +171,21 @@ abstract class BasePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final root = Scaffold(
key: _scaffoldKey,
backgroundColor: pageBackgroundColor(context),
resizeToAvoidBottomInset: resizeToAvoidBottomInset,
extendBodyBehindAppBar: extendBodyBehindAppBar,
endDrawer: endDrawer,
appBar: appBar(context),
body: body(context),
floatingActionButton: floatingActionButton(context));
final root = RouteAwareWidget(
child: Scaffold(
key: _scaffoldKey,
backgroundColor: pageBackgroundColor(context),
resizeToAvoidBottomInset: resizeToAvoidBottomInset,
extendBodyBehindAppBar: extendBodyBehindAppBar,
endDrawer: endDrawer,
appBar: appBar(context),
body: body(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;
}

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/src/screens/base_page.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:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart';
class WebViewPage extends BasePage {
WebViewPage(this._title, this._url);
@ -42,8 +46,9 @@ class WebViewPageBodyState extends State<WebViewPageBody> {
),
initialUrlRequest: URLRequest(url: WebUri.uri(widget.uri)),
onPermissionRequest: (controller, request) async {
bool permissionGranted = await Permission.camera.status == PermissionStatus.granted;
if (!permissionGranted) {
final sharedPrefs = getIt.get<SharedPreferences>();
if (sharedPrefs.getBool(PreferencesKey.showCameraConsent) ?? true) {
final bool userConsent = await showPopUp<bool>(
context: 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(
resources: request.resources,
action: permissionGranted

View file

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

View file

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

View file

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

View file

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

View file

@ -30,6 +30,7 @@ class DesktopWalletSelectionDropDown extends StatefulWidget {
class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionDropDown> {
final moneroIcon = Image.asset('assets/images/monero_logo.png', height: 24, width: 24);
final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
final tBitcoinIcon = Image.asset('assets/images/tbtc.png', height: 24, width: 24);
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
@ -68,8 +69,11 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 500),
child: DropDownItemWidget(
title: wallet.name,
image: wallet.isEnabled ? _imageFor(type: wallet.type) : nonWalletTypeIcon),
title: wallet.name,
image: wallet.isEnabled
? _imageFor(type: wallet.type, isTestnet: wallet.isTestnet)
: nonWalletTypeIcon,
),
),
onSelected: () => _onSelectedWallet(wallet),
))
@ -117,27 +121,33 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
if (selectedWallet.isCurrent || !selectedWallet.isEnabled) {
return;
}
final confirmed = await showPopUp<bool>(
context: context,
builder: (dialogContext) {
return AlertWithTwoActions(
alertTitle: S.of(context).change_wallet_alert_title,
alertContent: S.of(context).change_wallet_alert_content(selectedWallet.name),
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).change,
actionLeftButton: () => Navigator.of(dialogContext).pop(false),
actionRightButton: () => Navigator.of(dialogContext).pop(true));
}) ??
false;
if (confirmed) {
await _loadWallet(selectedWallet);
}
WidgetsBinding.instance.addPostFrameCallback((_) async {
final confirmed = await showPopUp<bool>(
context: context,
builder: (dialogContext) {
return AlertWithTwoActions(
alertTitle: S.of(context).change_wallet_alert_title,
alertContent: S.of(context).change_wallet_alert_content(selectedWallet.name),
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).change,
actionLeftButton: () => Navigator.of(dialogContext).pop(false),
actionRightButton: () => Navigator.of(dialogContext).pop(true));
}) ??
false;
if (confirmed) {
await _loadWallet(selectedWallet);
}
});
}
Image _imageFor({required WalletType type}) {
Image _imageFor({required WalletType type, bool? isTestnet}) {
switch (type) {
case WalletType.bitcoin:
if (isTestnet == true) {
return tBitcoinIcon;
}
return bitcoinIcon;
case WalletType.monero:
return moneroIcon;
@ -157,7 +167,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
return polygonIcon;
case WalletType.solana:
return solanaIcon;
case WalletType.tron:
case WalletType.tron:
return tronIcon;
default:
return nonWalletTypeIcon;
@ -165,24 +175,25 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
}
Future<void> _loadWallet(WalletListItem wallet) async {
widget._authService.authenticateAction(context,
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (!isAuthenticatedSuccessfully) {
return;
}
widget._authService.authenticateAction(
context,
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (!isAuthenticatedSuccessfully) {
return;
}
try {
if (context.mounted) {
changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
try {
if (context.mounted) {
changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
}
await widget.walletListViewModel.loadWallet(wallet);
hideProgressText();
setState(() {});
} catch (e) {
if (context.mounted) {
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
}
}
await widget.walletListViewModel.loadWallet(wallet);
hideProgressText();
setState(() {});
} catch (e) {
if (context.mounted) {
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
}
}
},
conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForAccessingWallet,
@ -195,17 +206,16 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
context,
route: Routes.newWallet,
arguments: widget.walletListViewModel.currentWalletType,
conditionToDetermineIfToUse2FA: widget
.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
);
} else {
widget._authService.authenticateAction(
context,
route: Routes.newWalletType,
conditionToDetermineIfToUse2FA: widget
.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
);
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -10,8 +10,7 @@ import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
class PresentReceiveOptionPicker extends StatelessWidget {
PresentReceiveOptionPicker(
{required this.receiveOptionViewModel, required this.color});
PresentReceiveOptionPicker({required this.receiveOptionViewModel, required this.color});
final ReceiveOptionViewModel receiveOptionViewModel;
final Color color;
@ -43,17 +42,17 @@ class PresentReceiveOptionPicker extends StatelessWidget {
Text(
S.current.receive,
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
fontFamily: 'Lato',
color: color),
fontSize: 18.0, fontWeight: FontWeight.bold, fontFamily: 'Lato', color: color),
),
Observer(
builder: (_) => Text(receiveOptionViewModel.selectedReceiveOption.toString(),
style: TextStyle(
fontSize: 10.0,
fontWeight: FontWeight.w500,
color: color)))
builder: (_) => Text(
receiveOptionViewModel.selectedReceiveOption
.toString()
.replaceAll(RegExp(r'silent payments', caseSensitive: false),
S.current.silent_payments)
.replaceAll(
RegExp(r'default', caseSensitive: false), S.current.string_default),
style: TextStyle(fontSize: 10.0, fontWeight: FontWeight.w500, color: color)))
],
),
SizedBox(width: 5),
@ -73,65 +72,75 @@ class PresentReceiveOptionPicker extends StatelessWidget {
backgroundColor: Colors.transparent,
body: Stack(
alignment: AlignmentDirectional.center,
children:[ AlertBackground(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Spacer(),
Container(
margin: EdgeInsets.symmetric(horizontal: 24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: Theme.of(context).colorScheme.background,
),
child: Padding(
padding: const EdgeInsets.only(top: 24, bottom: 24),
child: (ListView.separated(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: receiveOptionViewModel.options.length,
itemBuilder: (_, index) {
final option = receiveOptionViewModel.options[index];
return InkWell(
onTap: () {
Navigator.pop(popUpContext);
children: [
AlertBackground(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Spacer(),
Container(
margin: EdgeInsets.symmetric(horizontal: 24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: Theme.of(context).colorScheme.background,
),
child: Padding(
padding: const EdgeInsets.only(top: 24, bottom: 24),
child: (ListView.separated(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: receiveOptionViewModel.options.length,
itemBuilder: (_, index) {
final option = receiveOptionViewModel.options[index];
return InkWell(
onTap: () {
Navigator.pop(popUpContext);
receiveOptionViewModel.selectReceiveOption(option);
},
child: Padding(
padding: const EdgeInsets.only(left: 24, right: 24),
child: Observer(builder: (_) {
final value = receiveOptionViewModel.selectedReceiveOption;
receiveOptionViewModel.selectReceiveOption(option);
},
child: Padding(
padding: const EdgeInsets.only(left: 24, right: 24),
child: Observer(builder: (_) {
final value = receiveOptionViewModel.selectedReceiveOption;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(option.toString(),
textAlign: TextAlign.left,
style: textSmall(
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
).copyWith(
fontWeight:
value == option ? FontWeight.w800 : FontWeight.w500,
)),
RoundedCheckbox(
value: value == option,
)
],
);
}),
),
);
},
separatorBuilder: (_, index) => SizedBox(height: 30),
)),
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
option
.toString()
.replaceAll(
RegExp(r'silent payments', caseSensitive: false),
S.current.silent_payments)
.replaceAll(RegExp(r'default', caseSensitive: false),
S.current.string_default),
textAlign: TextAlign.left,
style: textSmall(
color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor,
).copyWith(
fontWeight:
value == option ? FontWeight.w800 : FontWeight.w500,
)),
RoundedCheckbox(
value: value == option,
)
],
);
}),
),
);
},
separatorBuilder: (_, index) => SizedBox(height: 30),
)),
),
),
),
Spacer()
],
Spacer()
],
),
),
),
AlertCloseButton(onTap: () => Navigator.of(popUpContext).pop(), bottom: 40)
],
),

View file

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

View file

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

View file

@ -38,6 +38,14 @@ class NewWalletPage extends BasePage {
@override
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
Widget body(BuildContext context) => WalletNameForm(
_walletNewVM,

View file

@ -34,6 +34,14 @@ class NewWalletTypePage extends BasePage {
String get title =>
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
Widget body(BuildContext context) => WalletTypeForm(
onTypeSelected: onTypeSelected,

View file

@ -67,8 +67,7 @@ class ReceivePage extends BasePage {
@override
Widget Function(BuildContext, Widget) get rootWrapper =>
(BuildContext context, Widget scaffold) =>
GradientBackground(scaffold: scaffold);
(BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold);
@override
Widget trailing(BuildContext context) {
@ -99,115 +98,144 @@ class ReceivePage extends BasePage {
@override
Widget body(BuildContext context) {
return KeyboardActions(
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _cryptoAmountFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
)
]),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(24, 50, 24, 24),
child: QRWidget(
addressListViewModel: addressListViewModel,
formKey: _formKey,
heroTag: _heroTag,
amountTextFieldFocusNode: _cryptoAmountFocus,
amountController: _amountController,
isLight: currentTheme.type == ThemeType.light),
),
Observer(
builder: (_) => ListView.separated(
padding: EdgeInsets.all(0),
separatorBuilder: (context, _) => const HorizontalSectionDivider(),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: addressListViewModel.items.length,
itemBuilder: (context, index) {
final item = addressListViewModel.items[index];
Widget cell = Container();
if (item is WalletAccountListHeader) {
cell = HeaderTile(
showTrailingButton: true,
walletAddressListViewModel: addressListViewModel,
trailingButtonTap: () async {
if (addressListViewModel.type == WalletType.monero ||
addressListViewModel.type == WalletType.haven) {
await showPopUp<void>(
context: context,
builder: (_) => getIt.get<MoneroAccountListPage>());
} else {
await showPopUp<void>(
context: context,
builder: (_) => getIt.get<NanoAccountListPage>());
}
},
title: S.of(context).accounts,
trailingIcon: Icon(
Icons.arrow_forward_ios,
size: 14,
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
));
}
if (item is WalletAddressListHeader) {
cell = HeaderTile(
title: S.of(context).addresses,
walletAddressListViewModel: addressListViewModel,
showTrailingButton: !addressListViewModel.isAutoGenerateSubaddressEnabled,
showSearchButton: true,
trailingButtonTap: () =>
Navigator.of(context).pushNamed(Routes.newSubaddress),
trailingIcon: Icon(
Icons.add,
size: 20,
color: Theme.of(context)
.extension<ReceivePageTheme>()!
.iconsColor,
));
}
if (item is WalletAddressListItem) {
cell = Observer(builder: (_) {
final isCurrent =
item.address == addressListViewModel.address.address;
final backgroundColor = isCurrent
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileBackgroundColor
: Theme.of(context).extension<ReceivePageTheme>()!.tilesBackgroundColor;
final textColor = isCurrent
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileTextColor
: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor;
return AddressCell.fromItem(item,
isCurrent: isCurrent,
hasBalance: addressListViewModel.isElectrumWallet,
backgroundColor: backgroundColor,
textColor: textColor,
onTap: (_) => addressListViewModel.setAddress(item),
onEdit: () => Navigator.of(context)
.pushNamed(Routes.newSubaddress, arguments: item));
});
}
return index != 0
? cell
: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30)),
child: cell,
);
})),
],
return KeyboardActions(
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _cryptoAmountFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
)
]),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(24, 50, 24, 24),
child: QRWidget(
addressListViewModel: addressListViewModel,
formKey: _formKey,
heroTag: _heroTag,
amountTextFieldFocusNode: _cryptoAmountFocus,
amountController: _amountController,
isLight: currentTheme.type == ThemeType.light),
),
));
Observer(
builder: (_) => ListView.separated(
padding: EdgeInsets.all(0),
separatorBuilder: (context, _) => const HorizontalSectionDivider(),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: addressListViewModel.items.length,
itemBuilder: (context, index) {
final item = addressListViewModel.items[index];
Widget cell = Container();
if (item is WalletAccountListHeader) {
cell = HeaderTile(
showTrailingButton: true,
walletAddressListViewModel: addressListViewModel,
trailingButtonTap: () async {
if (addressListViewModel.type == WalletType.monero ||
addressListViewModel.type == WalletType.haven) {
await showPopUp<void>(
context: context,
builder: (_) => getIt.get<MoneroAccountListPage>());
} else {
await showPopUp<void>(
context: context,
builder: (_) => getIt.get<NanoAccountListPage>());
}
},
title: S.of(context).accounts,
trailingIcon: Icon(
Icons.arrow_forward_ios,
size: 14,
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
));
}
if (item is WalletAddressListHeader) {
final hasTitle = item.title != null;
cell = HeaderTile(
title: hasTitle ? item.title! : S.of(context).addresses,
walletAddressListViewModel: addressListViewModel,
showTrailingButton:
!addressListViewModel.isAutoGenerateSubaddressEnabled && !hasTitle,
showSearchButton: true,
trailingButtonTap: () =>
Navigator.of(context).pushNamed(Routes.newSubaddress),
trailingIcon: hasTitle
? null
: Icon(
Icons.add,
size: 20,
color:
Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
),
);
}
if (item is WalletAddressListItem) {
cell = Observer(builder: (_) {
final isCurrent = item.address == addressListViewModel.address.address;
final backgroundColor = isCurrent
? Theme.of(context)
.extension<ReceivePageTheme>()!
.currentTileBackgroundColor
: Theme.of(context)
.extension<ReceivePageTheme>()!
.tilesBackgroundColor;
final textColor = isCurrent
? Theme.of(context)
.extension<ReceivePageTheme>()!
.currentTileTextColor
: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor;
return AddressCell.fromItem(
item,
isCurrent: isCurrent,
hasBalance: addressListViewModel.isElectrumWallet,
backgroundColor: backgroundColor,
textColor: textColor,
onTap: item.isOneTimeReceiveAddress == true
? null
: (_) => addressListViewModel.setAddress(item),
onEdit: item.isOneTimeReceiveAddress == true || item.isPrimary
? null
: () => Navigator.of(context)
.pushNamed(Routes.newSubaddress, arguments: item),
onDelete: !addressListViewModel.isSilentPayments || item.isPrimary
? null
: () => addressListViewModel.deleteAddress(item),
);
});
}
return index != 0
? cell
: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30), topRight: Radius.circular(30)),
child: cell,
);
})),
Padding(
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
child: Text(
addressListViewModel.isSilentPayments
? S.of(context).silent_payments_disclaimer
: S.of(context).electrum_address_disclaimer,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
color: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor)),
),
],
),
));
}
}

View file

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

View file

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

View file

@ -21,6 +21,14 @@ class RestoreFromBackupPage extends BasePage {
@override
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
Widget body(BuildContext context) {
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/di.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/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/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:permission_handler/permission_handler.dart';
@ -24,6 +29,19 @@ class RestoreOptionsPage extends BasePage {
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
Widget body(BuildContext context) {
final imageColor = Theme.of(context).extension<OptionTileTheme>()!.titleColor;
@ -57,7 +75,7 @@ class RestoreOptionsPage extends BasePage {
description: S.of(context).restore_description_from_backup,
),
),
if (DeviceInfo.instance.isMobile)
if (_doesSupportHardwareWallets)
Padding(
padding: EdgeInsets.only(top: 24),
child: OptionTile(

View file

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

View file

@ -1,10 +1,7 @@
import 'dart:async';
import 'package:cake_wallet/core/auth_service.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/payment_request.dart';
import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cw_core/wallet_base.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/authentication_store.dart';
import 'package:cake_wallet/entities/qr_scanner.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:mobx/mobx.dart';
import 'package:uni_links/uni_links.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/entities/contact_record.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:mobx/mobx.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:url_launcher/url_launcher.dart';
class SendPage extends BasePage {
@ -66,6 +66,14 @@ class SendPage extends BasePage {
@override
bool get extendBodyBehindAppBar => true;
@override
Function(BuildContext)? get pushToNextWidget => (context) {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.focusedChild?.unfocus();
}
};
@override
Widget? leading(BuildContext context) {
final _backButton = Icon(
@ -373,17 +381,17 @@ class SendPage extends BasePage {
}
if (sendViewModel.wallet.isHardwareWallet) {
if (!sendViewModel.ledgerViewModel.isConnected) {
if (!sendViewModel.ledgerViewModel!.isConnected) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: sendViewModel.walletType,
onConnectDevice: (BuildContext context, _) {
sendViewModel.ledgerViewModel.setLedger(sendViewModel.wallet);
sendViewModel.ledgerViewModel!.setLedger(sendViewModel.wallet);
Navigator.of(context).pop();
},
));
} else {
sendViewModel.ledgerViewModel.setLedger(sendViewModel.wallet);
sendViewModel.ledgerViewModel!.setLedger(sendViewModel.wallet);
}
}
@ -421,6 +429,10 @@ class SendPage extends BasePage {
return;
}
if (sendViewModel.isElectrumWallet) {
bitcoin!.updateFeeRates(sendViewModel.wallet);
}
reaction((_) => sendViewModel.state, (ExecutionState state) {
if (dialogContext != null && dialogContext?.mounted == true) {
Navigator.of(dialogContext!).pop();

View file

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

View file

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

View file

@ -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_actions.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:cake_wallet/router.dart' as Router;
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>();
class DesktopSettingsPage extends StatefulWidget {
const DesktopSettingsPage({super.key});
const DesktopSettingsPage(this.dashboardViewModel, {super.key});
final DashboardViewModel dashboardViewModel;
@override
State<DesktopSettingsPage> createState() => _DesktopSettingsPageState();
@ -51,6 +54,12 @@ class _DesktopSettingsPageState extends State<DesktopSettingsPage> {
padding: EdgeInsets.only(top: 0),
itemBuilder: (_, 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;
return SettingActionButton(
isLastTile: isLastTile,

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