* feat: rebase btc-addr-types, migrate to bitcoin_base

* feat: allow scanning elect-rs using get_tweaks

* feat: scanning and adding addresses working with getTweaks, add btc SP address type

* chore: pubspec.lock

* chore: pubspec.lock

* fix: scan when switching, fix multiple unspents in same tx

* fix: initial scan

* fix: initial scan

* fix: scanning issues

* fix: sync, storing silent unspents

* chore: deps

* fix: label issues, clear spent utxo

* chore: deps

* fix: build

* fix: missing types

* feat: new electrs API & changes, fixes for last block scanning

* feat: Scan Silent Payments homepage toggle

* chore: build configure

* feat: generic fixes, testnet UI improvements, useSSL on bitcoin nodes

* fix: invalid Object in sendData

* feat: improve addresses page & address book displays

* feat: silent payments labeled addresses disclaimer

* fix: missing i18n

* chore: print

* feat: single block scan, rescan by date working for btc mainnet

* feat: new cake features page replace market page, move sp scan toggle, auto switch node pop up alert

* feat: delete silent addresses

* fix: red dot in non ssl nodes

* fix: inconsistent connection states, fix tx history

* fix: tx & balance displays, cpfp sending

* feat: new rust lib

* chore: node path

* fix: check node based on network

* fix: missing txcount from addresses

* style: padding in feature page cards

* fix: restore not getting all wallet addresses by type

* fix: auto switch node broken

* fix: silent payment txs not being restored

* feat: change scanning to subscription model, sync improvements

* fix: scan re-subscription

* fix: default nodes

* fix: improve scanning by date, fix single block scan

* refactor: common function for input tx selection

* fix: nodes & build

* fix: send all with multiple outs

* refactor: unchanged file

* Update pr_test_build.yml

* chore: upgrade

* chore: merge changes

* refactor: unchanged files [skip ci]

* fix: scan fixes, add date, allow sending while scanning

* feat: sync fixes, sp settings

* feat: fix resyncing

* fix: date from height logic, status disconnected & chain tip get

* fix: params

* feat: electrum migration if using cake electrum

* fix nodes
update versions

* re-enable tron

* update sp_scanner to work on iOS [skip ci]

* fix: wrong socket for old electrum nodes

* Fix unchecked wallet type call

* fix: double balance

* feat: node domain

* fix: menu name

* fix: update tip on set scanning

* fix: connection switching back and forth

* feat: check if node is electrs, and supports sp

* chore: fix build

* minor enhancements

* fixes and enhancements

* solve conflicts with main

* fix: status toggle

* minor enhancement

* Monero.com fixes

* update sp_scanner to include windows and linux

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
Rafael 2024-05-29 11:43:48 -03:00 committed by GitHub
parent faa49d21e8
commit 96b9b60f50
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
122 changed files with 3889 additions and 1252 deletions

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 =

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;