mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-28 06:28:41 +01:00
Upstream reformatted its whole tree in 16ddc0837. Matching it removes
formatting from the conflict surface of this and every later sync.
263 lines
7.7 KiB
Dart
263 lines
7.7 KiB
Dart
import 'dart:convert';
|
|
import 'package:cw_bitcoin/electrum_derivations.dart';
|
|
import 'package:cw_core/utils/print_verbose.dart';
|
|
import 'package:mobx/mobx.dart';
|
|
|
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
|
|
|
abstract class BaseBitcoinAddressRecord {
|
|
BaseBitcoinAddressRecord(
|
|
this.address, {
|
|
required this.index,
|
|
this.accountIndex = 0,
|
|
this.isHidden = false,
|
|
this.isLegacyDerivation = false,
|
|
int txCount = 0,
|
|
int balance = 0,
|
|
String name = '',
|
|
bool isUsed = false,
|
|
required this.type,
|
|
required this.network,
|
|
}) : _txCount = txCount,
|
|
_balance = balance,
|
|
_name = name,
|
|
_isUsed = Observable(isUsed);
|
|
|
|
@override
|
|
bool operator ==(Object o) => o is BaseBitcoinAddressRecord && address == o.address;
|
|
|
|
final String address;
|
|
bool isHidden;
|
|
bool isLegacyDerivation;
|
|
final int index;
|
|
final int accountIndex;
|
|
int _txCount;
|
|
int _balance;
|
|
String _name;
|
|
final Observable<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.value;
|
|
|
|
void setAsUsed() => _isUsed.value = true;
|
|
void setNewName(String label) => _name = label;
|
|
|
|
int get hashCode => address.hashCode;
|
|
|
|
BitcoinAddressType type;
|
|
|
|
String get derivationPath;
|
|
|
|
String toJSON();
|
|
}
|
|
|
|
class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
|
|
BitcoinAddressRecord(
|
|
super.address, {
|
|
required super.index,
|
|
super.accountIndex = 0,
|
|
super.isHidden = false,
|
|
this.isHiddenChecked = false,
|
|
super.isLegacyDerivation = false,
|
|
super.txCount = 0,
|
|
super.balance = 0,
|
|
super.name = '',
|
|
super.isUsed = false,
|
|
required super.type,
|
|
String? scriptHash,
|
|
required super.network,
|
|
}) {
|
|
try {
|
|
this.scriptHash = scriptHash ??
|
|
(network != null ? BitcoinAddressUtils.scriptHash(address, network: network!) : null);
|
|
} catch (e) {
|
|
printV(e);
|
|
}
|
|
}
|
|
|
|
static bool _legacyDefaultForType(BitcoinAddressType type) {
|
|
// Some address types (p2wpkh, p2wsh) were historically derived from the same account/path as our new standard
|
|
// but using legacy formats. For these, we default to legacy = false.
|
|
if (type == SegwitAddresType.p2wpkh || type == SegwitAddresType.p2wsh) return false;
|
|
|
|
// For other types (p2pkh, p2wpkh-in-p2sh, p2tr, etc.), old wallets used the non-standard
|
|
return true;
|
|
}
|
|
|
|
factory BitcoinAddressRecord.fromJSON(String jsonSource, {BasedUtxoNetwork? network}) {
|
|
final decoded = json.decode(jsonSource) as Map;
|
|
|
|
final parsedType = decoded['type'] != null && decoded['type'] != ''
|
|
? BitcoinAddressType.values
|
|
.firstWhere((type) => type.toString() == decoded['type'] as String)
|
|
: SegwitAddresType.p2wpkh;
|
|
|
|
final parsedIsLegacy = decoded.containsKey('isLegacyDerivation')
|
|
? (decoded['isLegacyDerivation'] as bool? ?? false)
|
|
: _legacyDefaultForType(parsedType);
|
|
|
|
return BitcoinAddressRecord(
|
|
decoded['address'] as String,
|
|
index: decoded['index'] as int,
|
|
accountIndex: decoded['accountIndex'] as int? ?? 0,
|
|
isHidden: decoded['isHidden'] as bool? ?? false,
|
|
isHiddenChecked: decoded['isHiddenChecked'] as bool? ?? false,
|
|
isLegacyDerivation: parsedIsLegacy,
|
|
isUsed: decoded['isUsed'] as bool? ?? false,
|
|
txCount: decoded['txCount'] as int? ?? 0,
|
|
name: decoded['name'] as String? ?? '',
|
|
balance: decoded['balance'] as int? ?? 0,
|
|
type: parsedType,
|
|
scriptHash: decoded['scriptHash'] as String?,
|
|
network: network,
|
|
);
|
|
}
|
|
|
|
/// Whether [isHidden] has been checked against the wallet's keys. Records created from the
|
|
/// wallet's own keys are correct by construction; older ones are checked once on wallet open.
|
|
bool isHiddenChecked;
|
|
|
|
String? scriptHash;
|
|
|
|
static int _purposeForType(BitcoinAddressType type) {
|
|
if (type == P2pkhAddressType.p2pkh) return 44;
|
|
if (type == P2shAddressType.p2wpkhInP2sh) return 49;
|
|
if (type == SegwitAddresType.p2wsh) return 48;
|
|
if (type == SegwitAddresType.p2tr) return 86;
|
|
return 84;
|
|
}
|
|
|
|
int _coinTypeForNetwork() {
|
|
if (!(network?.isMainnet ?? true)) return 1;
|
|
|
|
switch (network) {
|
|
case BitcoinNetwork.mainnet:
|
|
return 0;
|
|
case LitecoinNetwork.mainnet:
|
|
return 2;
|
|
case BitcoinCashNetwork.mainnet:
|
|
return 145;
|
|
case DogecoinNetwork.mainnet:
|
|
return 3;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
String getScriptHash(BasedUtxoNetwork network) {
|
|
if (scriptHash != null) return scriptHash!;
|
|
try {
|
|
scriptHash = BitcoinAddressUtils.scriptHash(address, network: network);
|
|
} catch (e) {
|
|
return '';
|
|
}
|
|
return scriptHash!;
|
|
}
|
|
|
|
@override
|
|
String get derivationPath {
|
|
if (type == SegwitAddresType.mweb) {
|
|
return "m/1000'/$index";
|
|
}
|
|
|
|
final coinType = _coinTypeForNetwork();
|
|
final purpose = _purposeForType(type);
|
|
final accountPath =
|
|
isLegacyDerivation ? electrum_path : "m/$purpose'/$coinType'/$accountIndex'";
|
|
|
|
final chain = isHidden ? 1 : 0;
|
|
return "$accountPath/$chain/$index";
|
|
}
|
|
|
|
@override
|
|
String toJSON() => json.encode({
|
|
'address': address,
|
|
'index': index,
|
|
'accountIndex': accountIndex,
|
|
'isHidden': isHidden,
|
|
'isHiddenChecked': isHiddenChecked,
|
|
'isLegacyDerivation': isLegacyDerivation,
|
|
'isUsed': isUsed,
|
|
'txCount': txCount,
|
|
'name': name,
|
|
'balance': balance,
|
|
'type': type.toString(),
|
|
'scriptHash': scriptHash,
|
|
});
|
|
}
|
|
|
|
class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
|
|
BitcoinSilentPaymentAddressRecord(
|
|
super.address, {
|
|
required super.index,
|
|
super.accountIndex = 0,
|
|
super.isHidden = false,
|
|
super.txCount = 0,
|
|
super.balance = 0,
|
|
super.name = '',
|
|
super.isUsed = false,
|
|
required this.silentPaymentTweak,
|
|
required super.network,
|
|
required super.type,
|
|
this.spendDerivationPath = SILENT_PAYMENTS_SPEND_PATH_TESTNET,
|
|
});
|
|
|
|
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,
|
|
accountIndex: decoded['accountIndex'] as int? ?? 0,
|
|
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,
|
|
spendDerivationPath:
|
|
decoded['spend_derivation_path'] as String? ?? SILENT_PAYMENTS_SPEND_PATH_TESTNET,
|
|
);
|
|
}
|
|
|
|
final String? silentPaymentTweak;
|
|
final String spendDerivationPath;
|
|
|
|
@override
|
|
String get derivationPath => spendDerivationPath;
|
|
|
|
@override
|
|
String toJSON() => json.encode({
|
|
'address': address,
|
|
'index': index,
|
|
'accountIndex': accountIndex,
|
|
'isHidden': isHidden,
|
|
'isUsed': isUsed,
|
|
'txCount': txCount,
|
|
'name': name,
|
|
'balance': balance,
|
|
'type': type.toString(),
|
|
'network': network?.value,
|
|
'silent_payment_tweak': silentPaymentTweak,
|
|
'spend_derivation_path': spendDerivationPath,
|
|
});
|
|
}
|