Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Electrum sp refactors #1781

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e47846b
feat: begin delegated scan, big refactors
rafael-xmr Oct 4, 2024
7339b78
refactor: init
rafael-xmr Oct 28, 2024
64caf84
fix: restore flow slow, checking unspents
rafael-xmr Oct 29, 2024
433686b
feat: derivationinfo to address records
rafael-xmr Oct 30, 2024
f3a0ff7
feat: init electrum worker
rafael-xmr Oct 30, 2024
02fabf8
feat: electrum worker types
rafael-xmr Oct 31, 2024
4a4250a
feat: tx history worker
rafael-xmr Nov 1, 2024
a3e131d
feat: all address derivations
rafael-xmr Nov 4, 2024
c9a5023
feat: unspents and tweaks subscribe method
rafael-xmr Nov 5, 2024
a4561d2
chore: deps
rafael-xmr Nov 5, 2024
7964b2a
chore: deps
rafael-xmr Nov 5, 2024
884a822
fix: fee and addresses
rafael-xmr Nov 6, 2024
28804b8
Improve sending tx for electrum (#1790)
OmarHatem28 Nov 6, 2024
243f734
Merge branch 'main' into electrum-sp-refactors
OmarHatem28 Nov 6, 2024
57f4860
fix: tx dates
rafael-xmr Nov 7, 2024
a169db7
Merge remote-tracking branch 'origin/electrum-sp-refactors' into elec…
rafael-xmr Nov 7, 2024
6e8b3d7
misc
rafael-xmr Nov 7, 2024
d4b0165
Merge remote-tracking branch 'origin/main' into electrum-sp-refactors
rafael-xmr Nov 12, 2024
3950f6c
refactor: misc
rafael-xmr Nov 16, 2024
cc853b9
Merge remote-tracking branch 'origin/main' into electrum-sp-refactors
rafael-xmr Nov 16, 2024
75aaa6f
chore: build scripts
rafael-xmr Nov 17, 2024
378e160
Merge remote-tracking branch 'origin/main' into electrum-sp-refactors
rafael-xmr Nov 17, 2024
aa6f932
chore: build errors
rafael-xmr Nov 17, 2024
b9f76bd
fix: btc create
rafael-xmr Nov 17, 2024
9bb3d9f
fix: btc restore
rafael-xmr Nov 17, 2024
c35dec0
feat: restore & scan imp
rafael-xmr Nov 23, 2024
b7ff9ab
fix: api & create
rafael-xmr Nov 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ integration_test/playground.dart

# Monero.dart (Monero_C)
scripts/monero_c
scripts/android/app_env.fish
# iOS generated framework bin
ios/MoneroWallet.framework/MoneroWallet
ios/WowneroWallet.framework/WowneroWallet
23 changes: 0 additions & 23 deletions cw_bitcoin/lib/address_from_output.dart

This file was deleted.

181 changes: 135 additions & 46 deletions cw_bitcoin/lib/bitcoin_address_record.dart
Original file line number Diff line number Diff line change
@@ -1,34 +1,39 @@
import 'dart:convert';

import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';

abstract class BaseBitcoinAddressRecord {
BaseBitcoinAddressRecord(
this.address, {
required this.index,
this.isHidden = false,
bool isChange = false,
int txCount = 0,
int balance = 0,
String name = '',
bool isUsed = false,
required this.type,
required this.network,
required this.addressType,
bool? isHidden,
}) : _txCount = txCount,
_balance = balance,
_name = name,
_isUsed = isUsed;
_isUsed = isUsed,
_isHidden = isHidden ?? isChange,
_isChange = isChange;

@override
bool operator ==(Object o) => o is BaseBitcoinAddressRecord && address == o.address;

final String address;
bool isHidden;
bool _isHidden;
bool get isHidden => _isHidden;
final bool _isChange;
bool get isChange => _isChange;
final int index;
int _txCount;
int _balance;
String _name;
bool _isUsed;
BasedUtxoNetwork? network;

int get txCount => _txCount;

Expand All @@ -42,123 +47,207 @@ abstract class BaseBitcoinAddressRecord {

bool get isUsed => _isUsed;

void setAsUsed() => _isUsed = true;
void setAsUsed() {
_isUsed = true;
_isHidden = true;
}

void setNewName(String label) => _name = label;

int get hashCode => address.hashCode;

BitcoinAddressType type;
BitcoinAddressType addressType;

String toJSON();
}

class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
final BitcoinDerivationInfo derivationInfo;
final CWBitcoinDerivationType derivationType;

BitcoinAddressRecord(
super.address, {
required super.index,
super.isHidden = false,
required this.derivationInfo,
required this.derivationType,
super.isHidden,
super.isChange = false,
super.txCount = 0,
super.balance = 0,
super.name = '',
super.isUsed = false,
required super.type,
required super.addressType,
String? scriptHash,
required super.network,
}) : scriptHash = scriptHash ??
(network != null ? BitcoinAddressUtils.scriptHash(address, network: network) : null);
BasedUtxoNetwork? network,
}) {
if (scriptHash == null && network == null) {
throw ArgumentError('either scriptHash or network must be provided');
}

factory BitcoinAddressRecord.fromJSON(String jsonSource, {BasedUtxoNetwork? network}) {
this.scriptHash = scriptHash ?? BitcoinAddressUtils.scriptHash(address, network: network!);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we are sure that network won't be null, then shouldn't we make it required?

you are not setting it in the fromJson function so it will be null

}

factory BitcoinAddressRecord.fromJSON(String jsonSource) {
final decoded = json.decode(jsonSource) as Map;

return BitcoinAddressRecord(
decoded['address'] as String,
index: decoded['index'] as int,
derivationInfo: BitcoinDerivationInfo.fromJSON(
decoded['derivationInfo'] as Map<String, dynamic>,
),
derivationType: CWBitcoinDerivationType.values[decoded['derivationType'] as int],
isHidden: decoded['isHidden'] as bool? ?? false,
isChange: decoded['isChange'] 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,
type: decoded['type'] != null && decoded['type'] != ''
addressType: decoded['type'] != null && decoded['type'] != ''
? BitcoinAddressType.values
.firstWhere((type) => type.toString() == decoded['type'] as String)
: SegwitAddresType.p2wpkh,
scriptHash: decoded['scriptHash'] as String?,
network: network,
);
}

String? scriptHash;

String getScriptHash(BasedUtxoNetwork network) {
if (scriptHash != null) return scriptHash!;
scriptHash = BitcoinAddressUtils.scriptHash(address, network: network);
return scriptHash!;
}
late String scriptHash;

@override
String toJSON() => json.encode({
'address': address,
'index': index,
'derivationInfo': derivationInfo.toJSON(),
'derivationType': derivationType.index,
'isHidden': isHidden,
'isChange': isChange,
'isUsed': isUsed,
'txCount': txCount,
'name': name,
'balance': balance,
'type': type.toString(),
'type': addressType.toString(),
'scriptHash': scriptHash,
});

@override
operator ==(Object other) {
if (identical(this, other)) return true;

return other is BitcoinAddressRecord &&
other.address == address &&
other.index == index &&
other.derivationInfo == derivationInfo &&
other.scriptHash == scriptHash &&
other.addressType == addressType &&
other.derivationType == derivationType;
}

@override
int get hashCode =>
address.hashCode ^
index.hashCode ^
derivationInfo.hashCode ^
scriptHash.hashCode ^
addressType.hashCode ^
derivationType.hashCode;
}

class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
final String derivationPath;
int get labelIndex => index;
final String? labelHex;

static bool isChangeAddress(int labelIndex) => labelIndex == 0;

BitcoinSilentPaymentAddressRecord(
super.address, {
required super.index,
super.isHidden = false,
required int labelIndex,
this.derivationPath = BitcoinDerivationPaths.SILENT_PAYMENTS_SPEND,
super.txCount = 0,
super.balance = 0,
super.name = '',
super.isUsed = false,
required this.silentPaymentTweak,
required super.network,
required super.type,
}) : super();
super.addressType = SilentPaymentsAddresType.p2sp,
super.isHidden,
this.labelHex,
}) : super(index: labelIndex, isChange: isChangeAddress(labelIndex)) {
if (labelIndex != 1 && labelHex == null) {
throw ArgumentError('label must be provided for silent address index != 1');
}
}

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,
derivationPath: decoded['derivationPath'] as String,
labelIndex: decoded['labelIndex'] as int,
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?,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we no longer saving the tweak? isn't it needed for sending

type: decoded['type'] != null && decoded['type'] != ''
? BitcoinAddressType.values
.firstWhere((type) => type.toString() == decoded['type'] as String)
: SilentPaymentsAddresType.p2sp,
labelHex: decoded['labelHex'] as String?,
);
}

final String? silentPaymentTweak;
@override
String toJSON() => json.encode({
'address': address,
'derivationPath': derivationPath,
'labelIndex': labelIndex,
'isUsed': isUsed,
'txCount': txCount,
'name': name,
'balance': balance,
'type': addressType.toString(),
'labelHex': labelHex,
});
}

class BitcoinReceivedSPAddressRecord extends BitcoinSilentPaymentAddressRecord {
final ECPrivate spendKey;

BitcoinReceivedSPAddressRecord(
super.address, {
required super.labelIndex,
super.txCount = 0,
super.balance = 0,
super.name = '',
super.isUsed = false,
required this.spendKey,
super.addressType = SegwitAddresType.p2tr,
super.labelHex,
}) : super(isHidden: true);

factory BitcoinReceivedSPAddressRecord.fromJSON(String jsonSource, {BasedUtxoNetwork? network}) {
final decoded = json.decode(jsonSource) as Map;

return BitcoinReceivedSPAddressRecord(
decoded['address'] as String,
labelIndex: decoded['index'] as int? ?? 1,
isUsed: decoded['isUsed'] as bool? ?? false,
txCount: decoded['txCount'] as int? ?? 0,
name: decoded['name'] as String? ?? '',
balance: decoded['balance'] as int? ?? 0,
labelHex: decoded['label'] as String?,
spendKey: (decoded['spendKey'] as String?) == null
? ECPrivate.random()
: ECPrivate.fromHex(decoded['spendKey'] as String),
);
}

@override
String toJSON() => json.encode({
'address': address,
'index': index,
'isHidden': isHidden,
'labelIndex': labelIndex,
'isUsed': isUsed,
'txCount': txCount,
'name': name,
'balance': balance,
'type': type.toString(),
'network': network?.value,
'silent_payment_tweak': silentPaymentTweak,
'type': addressType.toString(),
'labelHex': labelHex,
'spend_key': spendKey.toString(),
});
}
26 changes: 0 additions & 26 deletions cw_bitcoin/lib/bitcoin_amount_format.dart

This file was deleted.

19 changes: 9 additions & 10 deletions cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import 'dart:async';

import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/hardware/hardware_account_data.dart';
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
Expand All @@ -12,8 +11,7 @@ class BitcoinHardwareWalletService {

final LedgerConnection ledgerConnection;

Future<List<HardwareAccountData>> getAvailableAccounts(
{int index = 0, int limit = 5}) async {
Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
final bitcoinLedgerApp = BitcoinLedgerApp(ledgerConnection);

final masterFp = await bitcoinLedgerApp.getMasterFingerprint();
Expand All @@ -23,13 +21,14 @@ class BitcoinHardwareWalletService {

for (final i in indexRange) {
final derivationPath = "m/84'/0'/$i'";
final xpub =
await bitcoinLedgerApp.getXPubKey(derivationPath: derivationPath);
Bip32Slip10Secp256k1 hd =
Bip32Slip10Secp256k1.fromExtendedKey(xpub).childKey(Bip32KeyIndex(0));

final address = generateP2WPKHAddress(
hd: hd, index: 0, network: BitcoinNetwork.mainnet);
final xpub = await bitcoinLedgerApp.getXPubKey(derivationPath: derivationPath);
final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub)
.childKey(Bip32KeyIndex(0))
.childKey(Bip32KeyIndex(index));
Comment on lines 23 to +27
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to double check this with you and Konstantin


final address = ECPublic.fromBip32(
hd.publicKey,
).toP2wpkhAddress().toAddress(BitcoinNetwork.mainnet);

accounts.add(HardwareAccountData(
address: address,
Expand Down
Loading
Loading