This commit is contained in:
Rafael Saes 2024-11-27 19:01:10 -03:00
parent 3ee697b4c1
commit 95bb566d09
14 changed files with 207 additions and 151 deletions

View file

@ -84,7 +84,11 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
throw ArgumentError('either scriptHash or network must be provided');
}
this.scriptHash = scriptHash ?? BitcoinAddressUtils.scriptHash(address, network: network!);
try {
this.scriptHash = scriptHash ?? BitcoinAddressUtils.scriptHash(address, network: network!);
} catch (_) {
this.scriptHash = '';
}
}
factory BitcoinAddressRecord.fromJSON(String jsonSource) {

View file

@ -424,6 +424,7 @@ abstract class ElectrumWalletBase
uri: node.uri,
useSSL: node.useSSL ?? false,
network: network,
walletType: walletInfo.type,
).toJson(),
);
} else {

View file

@ -414,11 +414,11 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
required BitcoinAddressType addressType,
required BitcoinDerivationInfo derivationInfo,
}) async {
final count = (isChange
final count = isChange
? ElectrumWalletAddressesBase.defaultChangeAddressesCount
: ElectrumWalletAddressesBase.defaultReceiveAddressesCount);
: ElectrumWalletAddressesBase.defaultReceiveAddressesCount;
final startIndex = (isChange ? receiveAddresses : changeAddresses)
final startIndex = (isChange ? changeAddresses : receiveAddresses)
.where((addr) => addr.derivationType == derivationType && addr.addressType == addressType)
.length;

View file

@ -23,9 +23,11 @@ import 'package:sp_scanner/sp_scanner.dart';
class ElectrumWorker {
final SendPort sendPort;
ElectrumApiProvider? _electrumClient;
BasedUtxoNetwork? _network;
BehaviorSubject<Map<String, dynamic>>? _scanningStream;
BasedUtxoNetwork? _network;
WalletType? _walletType;
ElectrumWorker._(this.sendPort, {ElectrumApiProvider? electrumClient})
: _electrumClient = electrumClient;
@ -134,6 +136,7 @@ class ElectrumWorker {
Future<void> _handleConnect(ElectrumWorkerConnectionRequest request) async {
_network = request.network;
_walletType = request.walletType;
try {
_electrumClient = await ElectrumApiProvider.connect(
@ -198,6 +201,10 @@ class ElectrumWorker {
// https://electrumx.readthedocs.io/en/latest/protocol-basics.html#status
// The status of the script hash is the hash of the tx history, or null if the string is empty because there are no transactions
stream.listen((status) async {
if (status == null) {
return;
}
print("status: $status");
_sendResponse(ElectrumWorkerScripthashesSubscribeResponse(
@ -213,6 +220,10 @@ class ElectrumWorker {
final addresses = result.addresses;
await Future.wait(addresses.map((addressRecord) async {
if (addressRecord.scriptHash.isEmpty) {
return;
}
final history = await _electrumClient!.request(ElectrumScriptHashGetHistory(
scriptHash: addressRecord.scriptHash,
));
@ -313,6 +324,10 @@ class ElectrumWorker {
final balanceFutures = <Future<Map<String, dynamic>>>[];
for (final scripthash in request.scripthashes) {
if (scripthash.isEmpty) {
continue;
}
final balanceFuture = _electrumClient!.request(
ElectrumGetScriptHashBalance(scriptHash: scripthash),
);
@ -347,9 +362,15 @@ class ElectrumWorker {
final unspents = <String, List<ElectrumUtxo>>{};
await Future.wait(request.scripthashes.map((scriptHash) async {
final scriptHashUnspents = await _electrumClient!.request(
ElectrumScriptHashListUnspent(scriptHash: scriptHash),
);
if (scriptHash.isEmpty) {
return;
}
final scriptHashUnspents = await _electrumClient!
.request(
ElectrumScriptHashListUnspent(scriptHash: scriptHash),
)
.timeout(const Duration(seconds: 3));
if (scriptHashUnspents.isNotEmpty) {
unspents[scriptHash] = scriptHashUnspents;
@ -389,68 +410,79 @@ class ElectrumWorker {
int? height;
bool? isDateValidated;
final transactionHex = await _electrumClient!.request(
ElectrumGetTransactionHex(transactionHash: hash),
final transactionVerbose = await _electrumClient!.request(
ElectrumGetTransactionVerbose(transactionHash: hash),
);
String transactionHex;
if (getTime) {
if (mempoolAPIEnabled) {
try {
// TODO: mempool api class
final txVerbose = await http
.get(
Uri.parse(
"https://mempool.cakewallet.com/api/v1/tx/$hash/status",
),
)
.timeout(const Duration(seconds: 5));
if (transactionVerbose.isNotEmpty) {
transactionHex = transactionVerbose['hex'] as String;
time = transactionVerbose['time'] as int?;
confirmations = transactionVerbose['confirmations'] as int?;
} else {
transactionHex = await _electrumClient!.request(
ElectrumGetTransactionHex(transactionHash: hash),
);
if (txVerbose.statusCode == 200 &&
txVerbose.body.isNotEmpty &&
jsonDecode(txVerbose.body) != null) {
height = jsonDecode(txVerbose.body)['block_height'] as int;
final blockHash = await http
if (getTime && _walletType == WalletType.bitcoin) {
if (mempoolAPIEnabled) {
try {
// TODO: mempool api class
final txVerbose = await http
.get(
Uri.parse(
"https://mempool.cakewallet.com/api/v1/block-height/$height",
"https://mempool.cakewallet.com/api/v1/tx/$hash/status",
),
)
.timeout(const Duration(seconds: 5));
if (blockHash.statusCode == 200 && blockHash.body.isNotEmpty) {
final blockResponse = await http
if (txVerbose.statusCode == 200 &&
txVerbose.body.isNotEmpty &&
jsonDecode(txVerbose.body) != null) {
height = jsonDecode(txVerbose.body)['block_height'] as int;
final blockHash = await http
.get(
Uri.parse(
"https://mempool.cakewallet.com/api/v1/block/${blockHash.body}",
"https://mempool.cakewallet.com/api/v1/block-height/$height",
),
)
.timeout(const Duration(seconds: 5));
if (blockResponse.statusCode == 200 &&
blockResponse.body.isNotEmpty &&
jsonDecode(blockResponse.body)['timestamp'] != null) {
time = int.parse(jsonDecode(blockResponse.body)['timestamp'].toString());
if (blockHash.statusCode == 200 && blockHash.body.isNotEmpty) {
final blockResponse = await http
.get(
Uri.parse(
"https://mempool.cakewallet.com/api/v1/block/${blockHash.body}",
),
)
.timeout(const Duration(seconds: 5));
if (date != null) {
final newDate = DateTime.fromMillisecondsSinceEpoch(time * 1000);
isDateValidated = newDate == date;
if (blockResponse.statusCode == 200 &&
blockResponse.body.isNotEmpty &&
jsonDecode(blockResponse.body)['timestamp'] != null) {
time = int.parse(jsonDecode(blockResponse.body)['timestamp'].toString());
if (date != null) {
final newDate = DateTime.fromMillisecondsSinceEpoch(time * 1000);
isDateValidated = newDate == date;
}
}
}
}
}
} catch (_) {}
}
if (confirmations == null && height != null) {
final tip = currentChainTip;
if (tip > 0 && height > 0) {
// Add one because the block itself is the first confirmation
confirmations = tip - height + 1;
} catch (_) {}
}
}
}
if (confirmations == null && height != null) {
final tip = currentChainTip;
if (tip > 0 && height > 0) {
// Add one because the block itself is the first confirmation
confirmations = tip - height + 1;
}
}
final original = BtcTransaction.fromRaw(transactionHex);
final ins = <BtcTransaction>[];

View file

@ -5,12 +5,14 @@ class ElectrumWorkerConnectionRequest implements ElectrumWorkerRequest {
required this.uri,
required this.network,
required this.useSSL,
required this.walletType,
this.id,
});
final Uri uri;
final bool useSSL;
final BasedUtxoNetwork network;
final WalletType walletType;
final int? id;
@override
@ -23,6 +25,9 @@ class ElectrumWorkerConnectionRequest implements ElectrumWorkerRequest {
network: BasedUtxoNetwork.values.firstWhere(
(e) => e.toString() == json['network'] as String,
),
walletType: WalletType.values.firstWhere(
(e) => e.toString() == json['walletType'] as String,
),
useSSL: json['useSSL'] as bool,
id: json['id'] as int?,
);
@ -34,6 +39,7 @@ class ElectrumWorkerConnectionRequest implements ElectrumWorkerRequest {
'method': method,
'uri': uri.toString(),
'network': network.toString(),
'walletType': walletType.toString(),
'useSSL': useSSL,
};
}

View file

@ -6,6 +6,7 @@ import 'dart:math';
import 'package:collection/collection.dart';
import 'package:crypto/crypto.dart';
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
// import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/mweb_utxo.dart';
@ -58,7 +59,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required EncryptionFileUtils encryptionFileUtils,
Uint8List? seedBytes,
List<int>? seedBytes,
String? mnemonic,
String? xpub,
String? passphrase,
@ -155,20 +156,61 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
Map<String, int>? initialChangeAddressIndex,
required bool mempoolAPIEnabled,
}) async {
late Uint8List seedBytes;
List<int>? seedBytes = null;
final Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets = {};
switch (walletInfo.derivationInfo?.derivationType) {
case DerivationType.bip39:
seedBytes = await bip39.mnemonicToSeed(
mnemonic,
passphrase: passphrase ?? "",
);
break;
case DerivationType.electrum:
default:
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
break;
if (walletInfo.isRecovery) {
for (final derivation in walletInfo.derivations ?? <DerivationInfo>[]) {
if (derivation.description?.contains("SP") ?? false) {
continue;
}
if (derivation.derivationType == DerivationType.bip39) {
seedBytes = Bip39SeedGenerator.generateFromString(mnemonic, passphrase);
hdWallets[CWBitcoinDerivationType.bip39] = Bip32Slip10Secp256k1.fromSeed(seedBytes);
break;
} else {
try {
seedBytes = ElectrumV2SeedGenerator.generateFromString(mnemonic, passphrase);
hdWallets[CWBitcoinDerivationType.electrum] = Bip32Slip10Secp256k1.fromSeed(seedBytes);
} catch (e) {
print("electrum_v2 seed error: $e");
try {
seedBytes = ElectrumV1SeedGenerator(mnemonic).generate();
hdWallets[CWBitcoinDerivationType.electrum] =
Bip32Slip10Secp256k1.fromSeed(seedBytes);
} catch (e) {
print("electrum_v1 seed error: $e");
}
}
break;
}
}
if (hdWallets[CWBitcoinDerivationType.bip39] != null) {
hdWallets[CWBitcoinDerivationType.old_bip39] = hdWallets[CWBitcoinDerivationType.bip39]!;
}
if (hdWallets[CWBitcoinDerivationType.electrum] != null) {
hdWallets[CWBitcoinDerivationType.old_electrum] =
hdWallets[CWBitcoinDerivationType.electrum]!;
}
} else {
switch (walletInfo.derivationInfo?.derivationType) {
case DerivationType.bip39:
seedBytes = await Bip39SeedGenerator.generateFromString(mnemonic, passphrase);
hdWallets[CWBitcoinDerivationType.bip39] = Bip32Slip10Secp256k1.fromSeed(seedBytes);
break;
case DerivationType.electrum:
default:
seedBytes = await ElectrumV2SeedGenerator.generateFromString(mnemonic, passphrase);
hdWallets[CWBitcoinDerivationType.electrum] = Bip32Slip10Secp256k1.fromSeed(seedBytes);
break;
}
}
return LitecoinWallet(
mnemonic: mnemonic,
password: password,
@ -232,7 +274,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? ELECTRUM_PATH;
walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum;
Uint8List? seedBytes = null;
List<int>? seedBytes = null;
final mnemonic = keysData.mnemonic;
final passphrase = keysData.passphrase;

View file

@ -117,26 +117,26 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
await ensureMwebAddressUpToIndexExists(20);
return;
}
}
@override
BitcoinBaseAddress generateAddress({
required CWBitcoinDerivationType derivationType,
required bool isChange,
required int index,
required BitcoinAddressType addressType,
required BitcoinDerivationInfo derivationInfo,
}) {
if (addressType == SegwitAddresType.mweb) {
return MwebAddress.fromAddress(address: mwebAddrs[0], network: network);
}
return P2wpkhAddress.fromDerivation(
bip32: hdWallet,
derivationInfo: derivationInfo,
isChange: isChange,
index: index,
);
@override
BitcoinBaseAddress generateAddress({
required CWBitcoinDerivationType derivationType,
required bool isChange,
required int index,
required BitcoinAddressType addressType,
required BitcoinDerivationInfo derivationInfo,
}) {
if (addressType == SegwitAddresType.mweb) {
return MwebAddress.fromAddress(address: mwebAddrs[0], network: network);
}
return P2wpkhAddress.fromDerivation(
bip32: hdWallets[derivationType]!,
derivationInfo: derivationInfo,
isChange: isChange,
index: index,
);
}
@override

View file

@ -86,20 +86,16 @@ packages:
bitcoin_base:
dependency: "direct overridden"
description:
path: "."
ref: cake-update-v15
resolved-ref: "49db5748d2edc73c0c8213e11ab6a39fa3a7ff7f"
url: "https://github.com/cake-tech/bitcoin_base.git"
source: git
path: "/home/rafael/Working/bitcoin_base"
relative: false
source: path
version: "4.7.0"
blockchain_utils:
dependency: "direct main"
description:
path: "."
ref: cake-update-v3
resolved-ref: "9b64c43bcfe129e7f01300a63607fde083dd0357"
url: "https://github.com/cake-tech/blockchain_utils"
source: git
path: "/home/rafael/Working/blockchain_utils"
relative: false
source: path
version: "3.3.0"
bluez:
dependency: transitive
@ -415,10 +411,10 @@ packages:
dependency: transitive
description:
name: google_identity_services_web
sha256: "0c56c2c5d60d6dfaf9725f5ad4699f04749fb196ee5a70487a46ef184837ccf6"
sha256: "55580f436822d64c8ff9a77e37d61f5fb1e6c7ec9d632a43ee324e2a05c3c6c9"
url: "https://pub.dev"
source: hosted
version: "0.3.0+2"
version: "0.3.3"
googleapis_auth:
dependency: transitive
description:
@ -471,10 +467,10 @@ packages:
dependency: "direct main"
description:
name: http
sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "1.2.2"
http2:
dependency: transitive
description:
@ -849,10 +845,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_web
sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
url: "https://pub.dev"
source: hosted
version: "2.2.2"
version: "2.4.2"
shared_preferences_windows:
dependency: transitive
description:
@ -917,11 +913,9 @@ packages:
sp_scanner:
dependency: "direct main"
description:
path: "."
ref: cake-update-v3
resolved-ref: "2c21e53fd652e0aee1ee5fcd891376c10334237b"
url: "https://github.com/cake-tech/sp_scanner.git"
source: git
path: "/home/rafael/Working/sp_scanner"
relative: false
source: path
version: "0.0.1"
stack_trace:
dependency: transitive
@ -1047,18 +1041,26 @@ packages:
dependency: transitive
description:
name: web
sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05"
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
url: "https://pub.dev"
source: hosted
version: "0.4.2"
version: "1.1.0"
web_socket:
dependency: transitive
description:
name: web_socket
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
url: "https://pub.dev"
source: hosted
version: "0.1.6"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: "939ab60734a4f8fa95feacb55804fa278de28bdeef38e616dc08e44a84adea23"
sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
url: "https://pub.dev"
source: hosted
version: "2.4.3"
version: "3.0.1"
xdg_directories:
dependency: transitive
description:

View file

@ -27,16 +27,12 @@ dependencies:
rxdart: ^0.28.0
cryptography: ^2.0.5
blockchain_utils:
git:
url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v3
path: /home/rafael/Working/blockchain_utils
cw_mweb:
path: ../cw_mweb
grpc: ^3.2.4
sp_scanner:
git:
url: https://github.com/cake-tech/sp_scanner.git
ref: cake-update-v3
path: /home/rafael/Working/sp_scanner
bech32:
git:
url: https://github.com/cake-tech/bech32.git
@ -62,13 +58,9 @@ dependency_overrides:
watcher: ^1.1.0
protobuf: ^3.1.0
bitcoin_base:
git:
url: https://github.com/cake-tech/bitcoin_base.git
ref: cake-update-v15
path: /home/rafael/Working/bitcoin_base
blockchain_utils:
git:
url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v3
path: /home/rafael/Working/blockchain_utils
pointycastle: 3.7.4
ffi: 2.1.0

View file

@ -26,9 +26,7 @@ dependencies:
url: https://github.com/cake-tech/bitbox-flutter.git
ref: Add-Support-For-OP-Return-data
blockchain_utils:
git:
url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v3
path: /home/rafael/Working/blockchain_utils
dev_dependencies:
flutter_test:
@ -40,13 +38,9 @@ dev_dependencies:
dependency_overrides:
watcher: ^1.1.0
bitcoin_base:
git:
url: https://github.com/cake-tech/bitcoin_base.git
ref: cake-update-v15
path: /home/rafael/Working/bitcoin_base
blockchain_utils:
git:
url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v3
path: /home/rafael/Working/blockchain_utils
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

View file

@ -16,13 +16,9 @@ dependencies:
cw_evm:
path: ../cw_evm
on_chain:
git:
url: https://github.com/cake-tech/on_chain.git
ref: cake-update-v3
path: /home/rafael/Working/On_chain
blockchain_utils:
git:
url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v3
path: /home/rafael/Working/blockchain_utils
mobx: ^2.3.0+1
bip39: ^1.0.6
hive: ^2.2.3
@ -37,9 +33,7 @@ dev_dependencies:
dependency_overrides:
blockchain_utils:
git:
url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v3
path: /home/rafael/Working/blockchain_utils
flutter:
# assets:

View file

@ -372,8 +372,6 @@ class CWBitcoin extends Bitcoin {
);
}
oldList.addAll(bitcoin!.getOldSPDerivationInfos());
return oldList;
}
@ -385,24 +383,13 @@ class CWBitcoin extends Bitcoin {
}) async {
final list = <DerivationInfo>[];
late BasedUtxoNetwork network;
switch (node.type) {
case WalletType.litecoin:
network = LitecoinNetwork.mainnet;
break;
case WalletType.bitcoin:
default:
network = BitcoinNetwork.mainnet;
break;
}
var electrumSeedBytes;
try {
electrumSeedBytes = ElectrumV2SeedGenerator.generateFromString(mnemonic, passphrase);
} catch (e) {
print("electrum_v2 seed error: $e");
if (passphrase != null && passphrase.isEmpty) {
if (passphrase == null || passphrase.isEmpty) {
try {
// TODO: language pick
electrumSeedBytes = ElectrumV1SeedGenerator(mnemonic).generate();

View file

@ -268,6 +268,10 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
// is restoring? = add old used derivations
final oldList = bitcoin!.getOldDerivationInfos(list);
if (walletType == WalletType.bitcoin) {
oldList.addAll(bitcoin!.getOldSPDerivationInfos());
}
return oldList;
case WalletType.nano:
String? mnemonic = credentials['seed'] as String?;

View file

@ -142,9 +142,7 @@ dependency_overrides:
flutter_secure_storage_platform_interface: 1.0.2
protobuf: ^3.1.0
bitcoin_base:
git:
url: https://github.com/cake-tech/bitcoin_base.git
ref: cake-update-v15
path: /home/rafael/Working/bitcoin_base
ffi: 2.1.0
flutter_icons: