paynym refactor to tx v2

This commit is contained in:
julian 2024-01-13 15:49:29 -06:00
parent e9300c9208
commit a69b4f8ed5
10 changed files with 220 additions and 128 deletions

View file

@ -44,6 +44,7 @@ class OutpointV2 {
@Embedded() @Embedded()
class InputV2 { class InputV2 {
late final String? scriptSigHex; late final String? scriptSigHex;
late final String? scriptSigAsm;
late final int? sequence; late final int? sequence;
late final OutpointV2? outpoint; late final OutpointV2? outpoint;
late final List<String> addresses; late final List<String> addresses;
@ -63,6 +64,7 @@ class InputV2 {
static InputV2 isarCantDoRequiredInDefaultConstructor({ static InputV2 isarCantDoRequiredInDefaultConstructor({
required String? scriptSigHex, required String? scriptSigHex,
String? scriptSigAsm,
required int? sequence, required int? sequence,
required OutpointV2? outpoint, required OutpointV2? outpoint,
required List<String> addresses, required List<String> addresses,
@ -74,6 +76,7 @@ class InputV2 {
}) => }) =>
InputV2() InputV2()
..scriptSigHex = scriptSigHex ..scriptSigHex = scriptSigHex
..scriptSigAsm = scriptSigAsm
..sequence = sequence ..sequence = sequence
..outpoint = outpoint ..outpoint = outpoint
..addresses = List.unmodifiable(addresses) ..addresses = List.unmodifiable(addresses)
@ -85,6 +88,7 @@ class InputV2 {
InputV2 copyWith({ InputV2 copyWith({
String? scriptSigHex, String? scriptSigHex,
String? scriptSigAsm,
int? sequence, int? sequence,
OutpointV2? outpoint, OutpointV2? outpoint,
List<String>? addresses, List<String>? addresses,
@ -96,6 +100,7 @@ class InputV2 {
}) { }) {
return InputV2.isarCantDoRequiredInDefaultConstructor( return InputV2.isarCantDoRequiredInDefaultConstructor(
scriptSigHex: scriptSigHex ?? this.scriptSigHex, scriptSigHex: scriptSigHex ?? this.scriptSigHex,
scriptSigAsm: scriptSigAsm ?? this.scriptSigAsm,
sequence: sequence ?? this.sequence, sequence: sequence ?? this.sequence,
outpoint: outpoint ?? this.outpoint, outpoint: outpoint ?? this.outpoint,
addresses: addresses ?? this.addresses, addresses: addresses ?? this.addresses,
@ -111,6 +116,7 @@ class InputV2 {
String toString() { String toString() {
return 'InputV2(\n' return 'InputV2(\n'
' scriptSigHex: $scriptSigHex,\n' ' scriptSigHex: $scriptSigHex,\n'
' scriptSigAsm: $scriptSigAsm,\n'
' sequence: $sequence,\n' ' sequence: $sequence,\n'
' outpoint: $outpoint,\n' ' outpoint: $outpoint,\n'
' addresses: $addresses,\n' ' addresses: $addresses,\n'

View file

@ -6,6 +6,7 @@ part 'output_v2.g.dart';
@Embedded() @Embedded()
class OutputV2 { class OutputV2 {
late final String scriptPubKeyHex; late final String scriptPubKeyHex;
late final String? scriptPubKeyAsm;
late final String valueStringSats; late final String valueStringSats;
late final List<String> addresses; late final List<String> addresses;
@ -18,24 +19,28 @@ class OutputV2 {
static OutputV2 isarCantDoRequiredInDefaultConstructor({ static OutputV2 isarCantDoRequiredInDefaultConstructor({
required String scriptPubKeyHex, required String scriptPubKeyHex,
String? scriptPubKeyAsm,
required String valueStringSats, required String valueStringSats,
required List<String> addresses, required List<String> addresses,
required bool walletOwns, required bool walletOwns,
}) => }) =>
OutputV2() OutputV2()
..scriptPubKeyHex = scriptPubKeyHex ..scriptPubKeyHex = scriptPubKeyHex
..scriptPubKeyAsm = scriptPubKeyAsm
..valueStringSats = valueStringSats ..valueStringSats = valueStringSats
..walletOwns = walletOwns ..walletOwns = walletOwns
..addresses = List.unmodifiable(addresses); ..addresses = List.unmodifiable(addresses);
OutputV2 copyWith({ OutputV2 copyWith({
String? scriptPubKeyHex, String? scriptPubKeyHex,
String? scriptPubKeyAsm,
String? valueStringSats, String? valueStringSats,
List<String>? addresses, List<String>? addresses,
bool? walletOwns, bool? walletOwns,
}) { }) {
return OutputV2.isarCantDoRequiredInDefaultConstructor( return OutputV2.isarCantDoRequiredInDefaultConstructor(
scriptPubKeyHex: scriptPubKeyHex ?? this.scriptPubKeyHex, scriptPubKeyHex: scriptPubKeyHex ?? this.scriptPubKeyHex,
scriptPubKeyAsm: scriptPubKeyAsm ?? this.scriptPubKeyAsm,
valueStringSats: valueStringSats ?? this.valueStringSats, valueStringSats: valueStringSats ?? this.valueStringSats,
addresses: addresses ?? this.addresses, addresses: addresses ?? this.addresses,
walletOwns: walletOwns ?? this.walletOwns, walletOwns: walletOwns ?? this.walletOwns,
@ -61,6 +66,7 @@ class OutputV2 {
return OutputV2.isarCantDoRequiredInDefaultConstructor( return OutputV2.isarCantDoRequiredInDefaultConstructor(
scriptPubKeyHex: json["scriptPubKey"]["hex"] as String, scriptPubKeyHex: json["scriptPubKey"]["hex"] as String,
scriptPubKeyAsm: json["scriptPubKey"]["asm"] as String?,
valueStringSats: parseOutputAmountString( valueStringSats: parseOutputAmountString(
json["value"].toString(), json["value"].toString(),
decimalPlaces: decimalPlaces, decimalPlaces: decimalPlaces,
@ -100,27 +106,10 @@ class OutputV2 {
String toString() { String toString() {
return 'OutputV2(\n' return 'OutputV2(\n'
' scriptPubKeyHex: $scriptPubKeyHex,\n' ' scriptPubKeyHex: $scriptPubKeyHex,\n'
' scriptPubKeyAsm: $scriptPubKeyAsm,\n'
' value: $value,\n' ' value: $value,\n'
' walletOwns: $walletOwns,\n' ' walletOwns: $walletOwns,\n'
' addresses: $addresses,\n' ' addresses: $addresses,\n'
')'; ')';
} }
} }
bool _listEquals<T, U>(List<T> a, List<U> b) {
if (T != U) {
return false;
}
if (a.length != b.length) {
return false;
}
for (int i = 0; i < a.length; i++) {
if (a[i] != b[i]) {
return false;
}
}
return true;
}

View file

@ -11,12 +11,12 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:bip47/src/util.dart'; import 'package:bip47/src/util.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/output.dart'; import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart'; import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
abstract class Bip47Utils { abstract class Bip47Utils {
/// looks at tx outputs and returns a blinded payment code if found /// looks at tx outputs and returns a blinded payment code if found
static Uint8List? getBlindedPaymentCodeBytesFrom(Transaction transaction) { static Uint8List? getBlindedPaymentCodeBytesFrom(TransactionV2 transaction) {
for (int i = 0; i < transaction.outputs.length; i++) { for (int i = 0; i < transaction.outputs.length; i++) {
final bytes = getBlindedPaymentCodeBytesFromOutput( final bytes = getBlindedPaymentCodeBytesFromOutput(
transaction.outputs.elementAt(i)); transaction.outputs.elementAt(i));
@ -28,7 +28,7 @@ abstract class Bip47Utils {
return null; return null;
} }
static Uint8List? getBlindedPaymentCodeBytesFromOutput(Output output) { static Uint8List? getBlindedPaymentCodeBytesFromOutput(OutputV2 output) {
Uint8List? blindedCodeBytes; Uint8List? blindedCodeBytes;
List<String>? scriptChunks = output.scriptPubKeyAsm?.split(" "); List<String>? scriptChunks = output.scriptPubKeyAsm?.split(" ");

View file

@ -6,9 +6,10 @@ import 'package:stackwallet/utilities/default_nodes.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart'; import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
import 'package:stackwallet/wallets/crypto_currency/interfaces/paynym_currency_interface.dart';
import 'package:stackwallet/wallets/crypto_currency/intermediate/bip39_hd_currency.dart'; import 'package:stackwallet/wallets/crypto_currency/intermediate/bip39_hd_currency.dart';
class Bitcoin extends Bip39HDCurrency { class Bitcoin extends Bip39HDCurrency with PaynymCurrencyInterface {
Bitcoin(super.network) { Bitcoin(super.network) {
switch (network) { switch (network) {
case CryptoCurrencyNetwork.main: case CryptoCurrencyNetwork.main:
@ -49,11 +50,6 @@ class Bitcoin extends Bip39HDCurrency {
fractionDigits: fractionDigits, fractionDigits: fractionDigits,
); );
Amount get dustLimitP2PKH => Amount(
rawValue: BigInt.from(546),
fractionDigits: fractionDigits,
);
@override @override
coinlib.NetworkParams get networkParams { coinlib.NetworkParams get networkParams {
switch (network) { switch (network) {

View file

@ -0,0 +1,9 @@
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/wallets/crypto_currency/intermediate/bip39_hd_currency.dart';
mixin PaynymCurrencyInterface on Bip39HDCurrency {
Amount get dustLimitP2PKH => Amount(
rawValue: BigInt.from(546),
fractionDigits: fractionDigits,
);
}

View file

@ -9,17 +9,18 @@ import 'package:stackwallet/utilities/extensions/extensions.dart';
import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/bitcoin.dart'; import 'package:stackwallet/wallets/crypto_currency/coins/bitcoin.dart';
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart'; import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
import 'package:stackwallet/wallets/crypto_currency/interfaces/paynym_currency_interface.dart';
import 'package:stackwallet/wallets/wallet/intermediate/bip39_hd_wallet.dart'; import 'package:stackwallet/wallets/wallet/intermediate/bip39_hd_wallet.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/coin_control_interface.dart'; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/coin_control_interface.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart'; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart'; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart';
class BitcoinWallet extends Bip39HDWallet class BitcoinWallet<T extends PaynymCurrencyInterface> extends Bip39HDWallet<T>
with ElectrumXInterface, CoinControlInterface, PaynymInterface { with ElectrumXInterface<T>, CoinControlInterface, PaynymInterface<T> {
@override @override
int get isarTransactionVersion => 2; int get isarTransactionVersion => 2;
BitcoinWallet(CryptoCurrencyNetwork network) : super(Bitcoin(network)); BitcoinWallet(CryptoCurrencyNetwork network) : super(Bitcoin(network) as T);
@override @override
FilterOperation? get changeAddressFilterOperation => FilterOperation? get changeAddressFilterOperation =>
@ -153,6 +154,7 @@ class BitcoinWallet extends Bip39HDWallet
InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor( InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor(
scriptSigHex: map["scriptSig"]?["hex"] as String?, scriptSigHex: map["scriptSig"]?["hex"] as String?,
scriptSigAsm: map["scriptSig"]?["asm"] as String?,
sequence: map["sequence"] as int?, sequence: map["sequence"] as int?,
outpoint: outpoint, outpoint: outpoint,
valueStringSats: valueStringSats, valueStringSats: valueStringSats,

View file

@ -19,6 +19,7 @@ import 'package:stackwallet/utilities/default_nodes.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/paynym_is_api.dart';
import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/prefs.dart';
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart'; import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
import 'package:stackwallet/wallets/isar/models/wallet_info.dart'; import 'package:stackwallet/wallets/isar/models/wallet_info.dart';
@ -45,6 +46,7 @@ import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/electrumx_int
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart'; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart'; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart'; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/private_key_interface.dart'; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/private_key_interface.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
@ -471,6 +473,24 @@ abstract class Wallet<T extends CryptoCurrency> {
), ),
); );
// TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided.
final Set<String> codesToCheck = {};
if (this is PaynymInterface) {
// isSegwit does not matter here at all
final myCode =
await (this as PaynymInterface).getPaymentCode(isSegwit: false);
final nym = await PaynymIsApi().nym(myCode.toString());
if (nym.value != null) {
for (final follower in nym.value!.followers) {
codesToCheck.add(follower.code);
}
for (final following in nym.value!.following) {
codesToCheck.add(following.code);
}
}
}
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId)); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId));
await updateChainHeight(); await updateChainHeight();
@ -505,6 +525,14 @@ abstract class Wallet<T extends CryptoCurrency> {
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.70, walletId)); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.70, walletId));
await fetchFuture; await fetchFuture;
// TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided.
if (this is PaynymInterface && codesToCheck.isNotEmpty) {
await (this as PaynymInterface)
.checkForNotificationTransactionsTo(codesToCheck);
// check utxos again for notification outputs
await updateUTXOs();
}
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.80, walletId)); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.80, walletId));
// await getAllTxsToWatch(); // await getAllTxsToWatch();

View file

@ -1,6 +1,7 @@
import 'package:stackwallet/wallets/crypto_currency/intermediate/bip39_hd_currency.dart';
import 'package:stackwallet/wallets/wallet/intermediate/bip39_hd_wallet.dart'; import 'package:stackwallet/wallets/wallet/intermediate/bip39_hd_wallet.dart';
mixin CoinControlInterface on Bip39HDWallet { mixin CoinControlInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
// any required here? // any required here?
// currently only used to id which wallets support coin control // currently only used to id which wallets support coin control
} }

View file

@ -20,12 +20,13 @@ import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/firo.dart'; import 'package:stackwallet/wallets/crypto_currency/coins/firo.dart';
import 'package:stackwallet/wallets/crypto_currency/intermediate/bip39_hd_currency.dart';
import 'package:stackwallet/wallets/models/tx_data.dart'; import 'package:stackwallet/wallets/models/tx_data.dart';
import 'package:stackwallet/wallets/wallet/intermediate/bip39_hd_wallet.dart'; import 'package:stackwallet/wallets/wallet/intermediate/bip39_hd_wallet.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart'; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
mixin ElectrumXInterface on Bip39HDWallet { mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
late ElectrumXClient electrumXClient; late ElectrumXClient electrumXClient;
late CachedElectrumXClient electrumXCachedClient; late CachedElectrumXClient electrumXCachedClient;
@ -533,20 +534,29 @@ mixin ElectrumXInterface on Bip39HDWallet {
final address = await mainDB.getAddress(walletId, sd.utxo.address!); final address = await mainDB.getAddress(walletId, sd.utxo.address!);
if (address?.derivationPath != null) { if (address?.derivationPath != null) {
if (address!.subType == AddressSubType.paynymReceive) { if (address!.subType == AddressSubType.paynymReceive) {
// TODO paynym if (this is PaynymInterface) {
// final code = await paymentCodeStringByKey(address.otherData!); final code = await (this as PaynymInterface)
// .paymentCodeStringByKey(address.otherData!);
// final bip47base = await getBip47BaseNode();
// final bip47base =
// final privateKey = await getPrivateKeyForPaynymReceivingAddress( await (this as PaynymInterface).getBip47BaseNode();
// paymentCodeString: code!,
// index: address.derivationIndex, final privateKey = await (this as PaynymInterface)
// ); .getPrivateKeyForPaynymReceivingAddress(
// paymentCodeString: code!,
// keys = coinlib.HDPrivateKey.fromKeyAndChainCode( index: address.derivationIndex,
// privateKey, );
// bip47base.chainCode,
// ); keys = coinlib.HDPrivateKey.fromKeyAndChainCode(
coinlib.ECPrivateKey.fromHex(privateKey.toHex),
bip47base.chainCode,
);
} else {
throw Exception(
"$runtimeType tried to fetchBuildTxData for a paynym address"
" in a non PaynymInterface wallet",
);
}
} else { } else {
keys = root.derivePath(address.derivationPath!.value); keys = root.derivePath(address.derivationPath!.value);
} }

View file

@ -12,6 +12,8 @@ import 'package:isar/isar.dart';
import 'package:pointycastle/digests/sha256.dart'; import 'package:pointycastle/digests/sha256.dart';
import 'package:stackwallet/exceptions/wallet/insufficient_balance_exception.dart'; import 'package:stackwallet/exceptions/wallet/insufficient_balance_exception.dart';
import 'package:stackwallet/exceptions/wallet/paynym_send_exception.dart'; import 'package:stackwallet/exceptions/wallet/paynym_send_exception.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart'; import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/models/signing_data.dart'; import 'package:stackwallet/models/signing_data.dart';
import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/amount/amount.dart';
@ -20,6 +22,7 @@ import 'package:stackwallet/utilities/bip47_utils.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/format.dart';
import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/wallets/crypto_currency/interfaces/paynym_currency_interface.dart';
import 'package:stackwallet/wallets/models/tx_data.dart'; import 'package:stackwallet/wallets/models/tx_data.dart';
import 'package:stackwallet/wallets/wallet/intermediate/bip39_hd_wallet.dart'; import 'package:stackwallet/wallets/wallet/intermediate/bip39_hd_wallet.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart'; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart';
@ -43,12 +46,8 @@ String _sendPaynymAddressDerivationPath(
}) => }) =>
"${_basePaynymDerivePath(testnet: testnet)}/0/$index"; "${_basePaynymDerivePath(testnet: testnet)}/0/$index";
mixin PaynymInterface on Bip39HDWallet, ElectrumXInterface { mixin PaynymInterface<T extends PaynymCurrencyInterface>
Amount get _dustLimitP2PKH => Amount( on Bip39HDWallet<T>, ElectrumXInterface<T> {
rawValue: BigInt.from(546),
fractionDigits: cryptoCurrency.fractionDigits,
);
btc_dart.NetworkType get networkType => btc_dart.NetworkType( btc_dart.NetworkType get networkType => btc_dart.NetworkType(
messagePrefix: cryptoCurrency.networkParams.messagePrefix, messagePrefix: cryptoCurrency.networkParams.messagePrefix,
bech32: cryptoCurrency.networkParams.bech32Hrp, bech32: cryptoCurrency.networkParams.bech32Hrp,
@ -447,9 +446,7 @@ mixin PaynymInterface on Bip39HDWallet, ElectrumXInterface {
List<UTXO>? utxos, List<UTXO>? utxos,
}) async { }) async {
try { try {
// final amountToSend = cryptoCurrency.dustLimitP2PKH; final amountToSend = cryptoCurrency.dustLimitP2PKH;
final amountToSend = _dustLimitP2PKH;
final List<UTXO> availableOutputs = final List<UTXO> availableOutputs =
utxos ?? await mainDB.getUTXOs(walletId).findAll(); utxos ?? await mainDB.getUTXOs(walletId).findAll();
final List<UTXO> spendableOutputs = []; final List<UTXO> spendableOutputs = [];
@ -550,14 +547,14 @@ mixin PaynymInterface on Bip39HDWallet, ElectrumXInterface {
} }
if (satoshisBeingUsed - amountToSend.raw > if (satoshisBeingUsed - amountToSend.raw >
feeForNoChange + _dustLimitP2PKH.raw) { feeForNoChange + cryptoCurrency.dustLimitP2PKH.raw) {
// try to add change output due to "left over" amount being greater than // try to add change output due to "left over" amount being greater than
// the estimated fee + the dust limit // the estimated fee + the dust limit
BigInt changeAmount = BigInt changeAmount =
satoshisBeingUsed - amountToSend.raw - feeForWithChange; satoshisBeingUsed - amountToSend.raw - feeForWithChange;
// check estimates are correct and build notification tx // check estimates are correct and build notification tx
if (changeAmount >= _dustLimitP2PKH.raw && if (changeAmount >= cryptoCurrency.dustLimitP2PKH.raw &&
satoshisBeingUsed - amountToSend.raw - changeAmount == satoshisBeingUsed - amountToSend.raw - changeAmount ==
feeForWithChange) { feeForWithChange) {
var txn = await _createNotificationTx( var txn = await _createNotificationTx(
@ -746,7 +743,7 @@ mixin PaynymInterface on Bip39HDWallet, ElectrumXInterface {
txb.addOutput( txb.addOutput(
notificationAddress, notificationAddress,
(overrideAmountForTesting ?? _dustLimitP2PKH.raw).toInt(), (overrideAmountForTesting ?? cryptoCurrency.dustLimitP2PKH.raw).toInt(),
); );
txb.addOutput(opReturnScript, 0); txb.addOutput(opReturnScript, 0);
@ -854,44 +851,67 @@ mixin PaynymInterface on Bip39HDWallet, ElectrumXInterface {
final myNotificationAddress = await getMyNotificationAddress(); final myNotificationAddress = await getMyNotificationAddress();
final txns = await mainDB final txns = await mainDB.isar.transactionV2s
.getTransactions(walletId) .where()
.walletIdEqualTo(walletId)
.filter() .filter()
.subTypeEqualTo(TransactionSubType.bip47Notification) .subTypeEqualTo(TransactionSubType.bip47Notification)
.findAll(); .findAll();
for (final tx in txns) { for (final tx in txns) {
if (tx.type == TransactionType.incoming && switch (tx.type) {
tx.address.value?.value == myNotificationAddress.value) { case TransactionType.incoming:
final unBlindedPaymentCode = await unBlindedPaymentCodeFromTransaction( for (final output in tx.outputs) {
transaction: tx, for (final outputAddress in output.addresses) {
); if (outputAddress == myNotificationAddress.value) {
final unBlindedPaymentCode =
await unBlindedPaymentCodeFromTransaction(
transaction: tx,
);
if (unBlindedPaymentCode != null && if (unBlindedPaymentCode != null &&
paymentCodeString == unBlindedPaymentCode.toString()) { paymentCodeString == unBlindedPaymentCode.toString()) {
// await _setConnectedCache(paymentCodeString, true); // await _setConnectedCache(paymentCodeString, true);
return true; return true;
} }
final unBlindedPaymentCodeBad = final unBlindedPaymentCodeBad =
await unBlindedPaymentCodeFromTransactionBad( await unBlindedPaymentCodeFromTransactionBad(
transaction: tx, transaction: tx,
); );
if (unBlindedPaymentCodeBad != null && if (unBlindedPaymentCodeBad != null &&
paymentCodeString == unBlindedPaymentCodeBad.toString()) { paymentCodeString == unBlindedPaymentCodeBad.toString()) {
// await _setConnectedCache(paymentCodeString, true); // await _setConnectedCache(paymentCodeString, true);
return true; return true;
} }
} else if (tx.type == TransactionType.outgoing) { }
if (tx.address.value?.otherData != null) { }
final code =
await paymentCodeStringByKey(tx.address.value!.otherData!);
if (code == paymentCodeString) {
// await _setConnectedCache(paymentCodeString, true);
return true;
} }
}
case TransactionType.outgoing:
for (final output in tx.outputs) {
for (final outputAddress in output.addresses) {
final address = await mainDB.isar.addresses
.where()
.walletIdEqualTo(walletId)
.filter()
.subTypeEqualTo(AddressSubType.paynymNotification)
.and()
.valueEqualTo(outputAddress)
.findFirst();
if (address?.otherData != null) {
final code = await paymentCodeStringByKey(address!.otherData!);
if (code == paymentCodeString) {
// await _setConnectedCache(paymentCodeString, true);
return true;
}
}
}
}
default:
break;
} }
} }
@ -900,7 +920,7 @@ mixin PaynymInterface on Bip39HDWallet, ElectrumXInterface {
return false; return false;
} }
Uint8List? _pubKeyFromInput(Input input) { Uint8List? _pubKeyFromInput(InputV2 input) {
final scriptSigComponents = input.scriptSigAsm?.split(" ") ?? []; final scriptSigComponents = input.scriptSigAsm?.split(" ") ?? [];
if (scriptSigComponents.length > 1) { if (scriptSigComponents.length > 1) {
return scriptSigComponents[1].fromHex; return scriptSigComponents[1].fromHex;
@ -919,7 +939,7 @@ mixin PaynymInterface on Bip39HDWallet, ElectrumXInterface {
} }
Future<PaymentCode?> unBlindedPaymentCodeFromTransaction({ Future<PaymentCode?> unBlindedPaymentCodeFromTransaction({
required Transaction transaction, required TransactionV2 transaction,
}) async { }) async {
try { try {
final blindedCodeBytes = final blindedCodeBytes =
@ -932,8 +952,8 @@ mixin PaynymInterface on Bip39HDWallet, ElectrumXInterface {
final designatedInput = transaction.inputs.first; final designatedInput = transaction.inputs.first;
final txPoint = designatedInput.txid.fromHex.reversed.toList(); final txPoint = designatedInput.outpoint!.txid.fromHex.reversed.toList();
final txPointIndex = designatedInput.vout; final txPointIndex = designatedInput.outpoint!.vout;
final rev = Uint8List(txPoint.length + 4); final rev = Uint8List(txPoint.length + 4);
Util.copyBytes(Uint8List.fromList(txPoint), 0, rev, 0, txPoint.length); Util.copyBytes(Uint8List.fromList(txPoint), 0, rev, 0, txPoint.length);
@ -970,7 +990,7 @@ mixin PaynymInterface on Bip39HDWallet, ElectrumXInterface {
} }
Future<PaymentCode?> unBlindedPaymentCodeFromTransactionBad({ Future<PaymentCode?> unBlindedPaymentCodeFromTransactionBad({
required Transaction transaction, required TransactionV2 transaction,
}) async { }) async {
try { try {
final blindedCodeBytes = final blindedCodeBytes =
@ -983,8 +1003,8 @@ mixin PaynymInterface on Bip39HDWallet, ElectrumXInterface {
final designatedInput = transaction.inputs.first; final designatedInput = transaction.inputs.first;
final txPoint = designatedInput.txid.fromHex.toList(); final txPoint = designatedInput.outpoint!.txid.fromHex.toList();
final txPointIndex = designatedInput.vout; final txPointIndex = designatedInput.outpoint!.vout;
final rev = Uint8List(txPoint.length + 4); final rev = Uint8List(txPoint.length + 4);
Util.copyBytes(Uint8List.fromList(txPoint), 0, rev, 0, txPoint.length); Util.copyBytes(Uint8List.fromList(txPoint), 0, rev, 0, txPoint.length);
@ -1022,8 +1042,9 @@ mixin PaynymInterface on Bip39HDWallet, ElectrumXInterface {
Future<List<PaymentCode>> Future<List<PaymentCode>>
getAllPaymentCodesFromNotificationTransactions() async { getAllPaymentCodesFromNotificationTransactions() async {
final txns = await mainDB final txns = await mainDB.isar.transactionV2s
.getTransactions(walletId) .where()
.walletIdEqualTo(walletId)
.filter() .filter()
.subTypeEqualTo(TransactionSubType.bip47Notification) .subTypeEqualTo(TransactionSubType.bip47Notification)
.findAll(); .findAll();
@ -1032,18 +1053,33 @@ mixin PaynymInterface on Bip39HDWallet, ElectrumXInterface {
for (final tx in txns) { for (final tx in txns) {
// tx is sent so we can check the address's otherData for the code String // tx is sent so we can check the address's otherData for the code String
if (tx.type == TransactionType.outgoing && if (tx.type == TransactionType.outgoing) {
tx.address.value?.otherData != null) { for (final output in tx.outputs) {
final codeString = for (final outputAddress
await paymentCodeStringByKey(tx.address.value!.otherData!); in output.addresses.where((e) => e.isNotEmpty)) {
if (codeString != null && final address = await mainDB.isar.addresses
codes.where((e) => e.toString() == codeString).isEmpty) { .where()
codes.add( .walletIdEqualTo(walletId)
PaymentCode.fromPaymentCode( .filter()
codeString, .subTypeEqualTo(AddressSubType.paynymNotification)
networkType: networkType, .and()
), .valueEqualTo(outputAddress)
); .findFirst();
if (address?.otherData != null) {
final codeString =
await paymentCodeStringByKey(address!.otherData!);
if (codeString != null &&
codes.where((e) => e.toString() == codeString).isEmpty) {
codes.add(
PaymentCode.fromPaymentCode(
codeString,
networkType: networkType,
),
);
}
}
}
} }
} else { } else {
// otherwise we need to un blind the code // otherwise we need to un blind the code
@ -1072,8 +1108,9 @@ mixin PaynymInterface on Bip39HDWallet, ElectrumXInterface {
Future<void> checkForNotificationTransactionsTo( Future<void> checkForNotificationTransactionsTo(
Set<String> otherCodeStrings) async { Set<String> otherCodeStrings) async {
final sentNotificationTransactions = await mainDB final sentNotificationTransactions = await mainDB.isar.transactionV2s
.getTransactions(walletId) .where()
.walletIdEqualTo(walletId)
.filter() .filter()
.subTypeEqualTo(TransactionSubType.bip47Notification) .subTypeEqualTo(TransactionSubType.bip47Notification)
.and() .and()
@ -1087,23 +1124,37 @@ mixin PaynymInterface on Bip39HDWallet, ElectrumXInterface {
} }
for (final tx in sentNotificationTransactions) { for (final tx in sentNotificationTransactions) {
if (tx.address.value != null && tx.address.value!.otherData == null) { for (final output in tx.outputs) {
final oldAddress = for (final outputAddress in output.addresses) {
await mainDB.getAddress(walletId, tx.address.value!.value); if (outputAddress.isNotEmpty) {
for (final code in codes) { for (final code in codes) {
final notificationAddress = code.notificationAddressP2PKH(); final notificationAddress = code.notificationAddressP2PKH();
if (notificationAddress == oldAddress!.value) {
final address = Address( if (outputAddress == notificationAddress) {
walletId: walletId, Address? storedAddress =
value: notificationAddress, await mainDB.getAddress(walletId, outputAddress);
publicKey: [], if (storedAddress == null) {
derivationIndex: 0, // most likely not mine
derivationPath: oldAddress.derivationPath, storedAddress = Address(
type: oldAddress.type, walletId: walletId,
subType: AddressSubType.paynymNotification, value: notificationAddress,
otherData: await storeCode(code.toString()), publicKey: [],
); derivationIndex: 0,
await mainDB.updateAddress(oldAddress, address); derivationPath: null,
type: AddressType.unknown,
subType: AddressSubType.paynymNotification,
otherData: await storeCode(code.toString()),
);
} else {
storedAddress = storedAddress.copyWith(
subType: AddressSubType.paynymNotification,
otherData: await storeCode(code.toString()),
);
}
await mainDB.updateOrPutAddresses([storedAddress]);
}
}
} }
} }
} }