dirty hack for showing firo transactions right away until we can add functionality to sparkmobile

This commit is contained in:
julian 2024-01-05 12:59:01 -06:00
parent 3cf0d82085
commit b11694220b
5 changed files with 282 additions and 28 deletions

View file

@ -1,4 +1,5 @@
import 'package:cw_wownero/pending_wownero_transaction.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/paynym/paynym_account_lite.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
@ -64,6 +65,8 @@ class TxData {
})>? sparkRecipients;
final List<TxData>? sparkMints;
final TransactionV2? tempTx;
TxData({
this.feeRateType,
this.feeRateAmount,
@ -96,6 +99,7 @@ class TxData {
this.tezosOperationsList,
this.sparkRecipients,
this.sparkMints,
this.tempTx,
});
Amount? get amount => recipients != null && recipients!.isNotEmpty
@ -153,6 +157,7 @@ class TxData {
})>?
sparkRecipients,
List<TxData>? sparkMints,
TransactionV2? tempTx,
}) {
return TxData(
feeRateType: feeRateType ?? this.feeRateType,
@ -187,6 +192,7 @@ class TxData {
tezosOperationsList: tezosOperationsList ?? this.tezosOperationsList,
sparkRecipients: sparkRecipients ?? this.sparkRecipients,
sparkMints: sparkMints ?? this.sparkMints,
tempTx: tempTx ?? this.tempTx,
);
}
@ -223,5 +229,6 @@ class TxData {
'tezosOperationsList: $tezosOperationsList, '
'sparkRecipients: $sparkRecipients, '
'sparkMints: $sparkMints, '
'tempTx: $tempTx, '
'}';
}

View file

@ -16,6 +16,7 @@ import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/firo.dart';
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
import 'package:stackwallet/wallets/isar/models/spark_coin.dart';
import 'package:stackwallet/wallets/models/tx_data.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/lelantus_interface.dart';
@ -41,8 +42,23 @@ class FiroWallet extends Bip39HDWallet
FilterOperation? get receivingAddressFilterOperation =>
FilterGroup.and(standardReceivingAddressFilters);
final Set<String> _unconfirmedTxids = {};
// ===========================================================================
@override
Future<TxData> updateSentCachedTxData({required TxData txData}) async {
if (txData.tempTx != null) {
await mainDB.updateOrPutTransactionV2s([txData.tempTx!]);
_unconfirmedTxids.add(txData.tempTx!.txid);
Logging.instance.log(
"Added firo unconfirmed: ${txData.tempTx!.txid}",
level: LogLevel.Info,
);
}
return txData;
}
@override
Future<void> updateTransactions() async {
List<Address> allAddressesOld = await fetchAddressesForElectrumXScan();
@ -487,7 +503,16 @@ class FiroWallet extends Bip39HDWallet
otherData: otherData,
);
if (_unconfirmedTxids.contains(tx.txid)) {
if (tx.isConfirmed(await chainHeight, cryptoCurrency.minConfirms)) {
txns.add(tx);
_unconfirmedTxids.removeWhere((e) => e == tx.txid);
} else {
// don't update in db until confirmed
}
} else {
txns.add(tx);
}
}
await mainDB.updateOrPutTransactionV2s(txns);

View file

@ -368,7 +368,7 @@ abstract class Wallet<T extends CryptoCurrency> {
Future<void> updateTransactions();
Future<void> updateBalance();
// returns true if new utxos were added to local db
/// returns true if new utxos were added to local db
Future<bool> updateUTXOs();
/// updates the wallet info's cachedChainHeight
@ -381,6 +381,14 @@ abstract class Wallet<T extends CryptoCurrency> {
Future<bool> pingCheck();
//===========================================
/// add transaction to local db temporarily. Used for quickly updating ui
/// before refresh can fetch data from server
Future<TxData> updateSentCachedTxData({required TxData txData}) async {
if (txData.tempTx != null) {
await mainDB.updateOrPutTransactionV2s([txData.tempTx!]);
}
return txData;
}
NodeModel getCurrentNode() {
final node = nodeService.getPrimaryNodeFor(coin: cryptoCurrency.coin) ??

View file

@ -8,6 +8,9 @@ import 'package:decimal/decimal.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/electrumx_rpc/cached_electrumx_client.dart';
import 'package:stackwallet/electrumx_rpc/electrumx_client.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_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/paymint/fee_object_model.dart';
import 'package:stackwallet/models/signing_data.dart';
@ -172,7 +175,7 @@ mixin ElectrumXInterface on Bip39HDWallet {
);
}
final int vSizeForOneOutput = buildTransaction(
final int vSizeForOneOutput = (await buildTransaction(
utxoSigningData: utxoSigningData,
txData: txData.copyWith(
recipients: _helperRecipientsConvert(
@ -180,7 +183,8 @@ mixin ElectrumXInterface on Bip39HDWallet {
[satoshisBeingUsed - 1],
),
),
).vSize!;
))
.vSize!;
int feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
@ -200,7 +204,7 @@ mixin ElectrumXInterface on Bip39HDWallet {
}
final int amount = satoshiAmountToSend - feeForOneOutput;
final data = buildTransaction(
final data = await buildTransaction(
txData: txData.copyWith(
recipients: _helperRecipientsConvert(
[recipientAddress],
@ -221,7 +225,7 @@ mixin ElectrumXInterface on Bip39HDWallet {
final int vSizeForOneOutput;
try {
vSizeForOneOutput = buildTransaction(
vSizeForOneOutput = (await buildTransaction(
utxoSigningData: utxoSigningData,
txData: txData.copyWith(
recipients: _helperRecipientsConvert(
@ -229,7 +233,8 @@ mixin ElectrumXInterface on Bip39HDWallet {
[satoshisBeingUsed - 1],
),
),
).vSize!;
))
.vSize!;
} catch (e) {
Logging.instance.log("vSizeForOneOutput: $e", level: LogLevel.Error);
rethrow;
@ -237,7 +242,7 @@ mixin ElectrumXInterface on Bip39HDWallet {
final int vSizeForTwoOutPuts;
try {
vSizeForTwoOutPuts = buildTransaction(
vSizeForTwoOutPuts = (await buildTransaction(
utxoSigningData: utxoSigningData,
txData: txData.copyWith(
recipients: _helperRecipientsConvert(
@ -248,7 +253,8 @@ mixin ElectrumXInterface on Bip39HDWallet {
],
),
),
).vSize!;
))
.vSize!;
} catch (e) {
Logging.instance.log("vSizeForTwoOutPuts: $e", level: LogLevel.Error);
rethrow;
@ -312,7 +318,7 @@ mixin ElectrumXInterface on Bip39HDWallet {
Logging.instance
.log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info);
var txn = buildTransaction(
var txn = await buildTransaction(
utxoSigningData: utxoSigningData,
txData: txData.copyWith(
recipients: _helperRecipientsConvert(
@ -343,7 +349,7 @@ mixin ElectrumXInterface on Bip39HDWallet {
level: LogLevel.Info);
Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs',
level: LogLevel.Info);
txn = buildTransaction(
txn = await buildTransaction(
utxoSigningData: utxoSigningData,
txData: txData.copyWith(
recipients: _helperRecipientsConvert(
@ -374,7 +380,7 @@ mixin ElectrumXInterface on Bip39HDWallet {
level: LogLevel.Info);
Logging.instance
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
final txn = buildTransaction(
final txn = await buildTransaction(
utxoSigningData: utxoSigningData,
txData: txData.copyWith(
recipients: _helperRecipientsConvert(
@ -406,7 +412,7 @@ mixin ElectrumXInterface on Bip39HDWallet {
level: LogLevel.Info);
Logging.instance
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
final txn = buildTransaction(
final txn = await buildTransaction(
utxoSigningData: utxoSigningData,
txData: txData.copyWith(
recipients: _helperRecipientsConvert(
@ -438,7 +444,7 @@ mixin ElectrumXInterface on Bip39HDWallet {
level: LogLevel.Info);
Logging.instance
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
final txn = buildTransaction(
final txn = await buildTransaction(
utxoSigningData: utxoSigningData,
txData: txData.copyWith(
recipients: _helperRecipientsConvert(
@ -615,10 +621,10 @@ mixin ElectrumXInterface on Bip39HDWallet {
}
/// Builds and signs a transaction
TxData buildTransaction({
Future<TxData> buildTransaction({
required TxData txData,
required List<SigningData> utxoSigningData,
}) {
}) async {
Logging.instance
.log("Starting buildTransaction ----------", level: LogLevel.Info);
@ -637,7 +643,12 @@ mixin ElectrumXInterface on Bip39HDWallet {
wif: cryptoCurrency.networkParams.wifPrefix,
),
);
txb.setVersion(1); // TODO possibly override this for certain coins?
const version = 1; // TODO possibly override this for certain coins?
txb.setVersion(version);
// temp tx data to show in gui while waiting for real data from server
final List<InputV2> tempInputs = [];
final List<OutputV2> tempOutputs = [];
// Add transaction inputs
for (var i = 0; i < utxoSigningData.length; i++) {
@ -649,6 +660,25 @@ mixin ElectrumXInterface on Bip39HDWallet {
utxoSigningData[i].output!,
cryptoCurrency.networkParams.bech32Hrp,
);
tempInputs.add(
InputV2.isarCantDoRequiredInDefaultConstructor(
scriptSigHex: txb.inputs.first.script?.toHex,
sequence: 0xffffffff - 1,
outpoint: OutpointV2.isarCantDoRequiredInDefaultConstructor(
txid: utxoSigningData[i].utxo.txid,
vout: utxoSigningData[i].utxo.vout,
),
addresses: utxoSigningData[i].utxo.address == null
? []
: [utxoSigningData[i].utxo.address!],
valueStringSats: utxoSigningData[i].utxo.value.toString(),
witness: null,
innerRedeemScriptAsm: null,
coinbase: null,
walletOwns: true,
),
);
}
// Add transaction output
@ -658,6 +688,24 @@ mixin ElectrumXInterface on Bip39HDWallet {
txData.recipients![i].amount.raw.toInt(),
cryptoCurrency.networkParams.bech32Hrp,
);
tempOutputs.add(
OutputV2.isarCantDoRequiredInDefaultConstructor(
scriptPubKeyHex: "000000",
valueStringSats: txData.recipients![i].amount.raw.toString(),
addresses: [
txData.recipients![i].address.toString(),
],
walletOwns: (await mainDB.isar.addresses
.where()
.walletIdEqualTo(walletId)
.filter()
.valueEqualTo(txData.recipients![i].address)
.valueProperty()
.findFirst()) !=
null,
),
);
}
try {
@ -683,6 +731,22 @@ mixin ElectrumXInterface on Bip39HDWallet {
return txData.copyWith(
raw: builtTx.toHex(),
vSize: vSize,
tempTx: TransactionV2(
walletId: walletId,
blockHash: null,
hash: builtTx.getId(),
txid: builtTx.getId(),
height: null,
timestamp: DateTime.timestamp().millisecondsSinceEpoch ~/ 1000,
inputs: List.unmodifiable(tempInputs),
outputs: List.unmodifiable(tempOutputs),
version: version,
type: tempOutputs.map((e) => e.walletOwns).fold(true, (p, e) => p &= e)
? TransactionType.sentToSelf
: TransactionType.outgoing,
subType: TransactionSubType.none,
otherData: null,
),
);
}
@ -1749,7 +1813,7 @@ mixin ElectrumXInterface on Bip39HDWallet {
// mark utxos as used
await mainDB.putUTXOs(txData.usedUTXOs!);
return txData;
return await updateSentCachedTxData(txData: txData);
} catch (e, s) {
Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s",
level: LogLevel.Error);

View file

@ -7,6 +7,9 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_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/signing_data.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
@ -331,6 +334,10 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
);
}
// temp tx data to show in gui while waiting for real data from server
final List<InputV2> tempInputs = [];
final List<OutputV2> tempOutputs = [];
for (int i = 0; i < (txData.recipients?.length ?? 0); i++) {
if (txData.recipients![i].amount.raw == BigInt.zero) {
continue;
@ -354,8 +361,62 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
scriptPubKey,
recipientsWithFeeSubtracted[i].amount.raw.toInt(),
);
tempOutputs.add(
OutputV2.isarCantDoRequiredInDefaultConstructor(
scriptPubKeyHex: scriptPubKey.toHex,
valueStringSats: recipientsWithFeeSubtracted[i].amount.raw.toString(),
addresses: [
recipientsWithFeeSubtracted[i].address.toString(),
],
walletOwns: (await mainDB.isar.addresses
.where()
.walletIdEqualTo(walletId)
.filter()
.valueEqualTo(recipientsWithFeeSubtracted[i].address)
.valueProperty()
.findFirst()) !=
null,
),
);
}
if (sparkRecipientsWithFeeSubtracted != null) {
for (final recip in sparkRecipientsWithFeeSubtracted) {
tempOutputs.add(
OutputV2.isarCantDoRequiredInDefaultConstructor(
scriptPubKeyHex: Uint8List.fromList([OP_SPARKSMINT]).toHex,
valueStringSats: recip.amount.raw.toString(),
addresses: [
recip.address.toString(),
],
walletOwns: (await mainDB.isar.addresses
.where()
.walletIdEqualTo(walletId)
.filter()
.valueEqualTo(recip.address)
.valueProperty()
.findFirst()) !=
null,
),
);
}
}
tempInputs.add(
InputV2.isarCantDoRequiredInDefaultConstructor(
scriptSigHex: "d3",
sequence: 0xffffffff,
outpoint: null,
addresses: [],
valueStringSats: "0",
witness: null,
innerRedeemScriptAsm: null,
coinbase: null,
walletOwns: true,
),
);
final extractedTx = txb.buildIncomplete();
extractedTx.addInput(
'0000000000000000000000000000000000000000000000000000000000000000'
@ -412,12 +473,33 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
);
}
final fee = Amount(
rawValue: BigInt.from(spend.fee),
fractionDigits: cryptoCurrency.fractionDigits,
);
return txData.copyWith(
raw: rawTxHex,
vSize: extractedTx.virtualSize(),
fee: Amount(
rawValue: BigInt.from(spend.fee),
fractionDigits: cryptoCurrency.fractionDigits,
fee: fee,
tempTx: TransactionV2(
walletId: walletId,
blockHash: null,
hash: extractedTx.getId(),
txid: extractedTx.getId(),
timestamp: DateTime.timestamp().millisecondsSinceEpoch ~/ 1000,
inputs: List.unmodifiable(tempInputs),
outputs: List.unmodifiable(tempOutputs),
type: tempOutputs.map((e) => e.walletOwns).fold(true, (p, e) => p &= e)
? TransactionType.sentToSelf
: TransactionType.outgoing,
subType: TransactionSubType.sparkSpend,
otherData: jsonEncode(
{
"anonFees": fee.toJsonString(),
},
),
height: null,
version: 3,
),
// TODO used coins
);
@ -448,7 +530,7 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
// // mark utxos as used
// await mainDB.putUTXOs(txData.usedUTXOs!);
return txData;
return await updateSentCachedTxData(txData: txData);
} catch (e, s) {
Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s",
level: LogLevel.Error);
@ -702,7 +784,7 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
: currentHeight;
const txVersion = 1;
final List<SigningData> vin = [];
final List<(dynamic, int)> vout = [];
final List<(dynamic, int, String?)> vout = [];
BigInt nFeeRet = BigInt.zero;
@ -821,13 +903,15 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
final dummyTxb = btc.TransactionBuilder(network: _bitcoinDartNetwork);
dummyTxb.setVersion(txVersion);
dummyTxb.setLockTime(lockTime);
for (final recipient in dummyRecipients) {
for (int i = 0; i < dummyRecipients.length; i++) {
final recipient = dummyRecipients[i];
if (recipient.amount < cryptoCurrency.dustLimit.raw.toInt()) {
throw Exception("Output amount too small");
}
vout.add((
recipient.scriptPubKey,
recipient.amount,
singleTxOutputs[i].address,
));
}
@ -860,7 +944,7 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
final changeAddress = await getCurrentChangeAddress();
vout.insert(
nChangePosInOut,
(changeAddress!.value, nChange.toInt()),
(changeAddress!.value, nChange.toInt(), null),
);
}
}
@ -942,8 +1026,13 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
);
int i = 0;
for (final recipient in recipients) {
final out = (recipient.scriptPubKey, recipient.amount);
for (int i = 0; i < recipients.length; i++) {
final recipient = recipients[i];
final out = (
recipient.scriptPubKey,
recipient.amount,
singleTxOutputs[i].address,
);
while (i < vout.length) {
if (vout[i].$1 is Uint8List &&
(vout[i].$1 as Uint8List).isNotEmpty &&
@ -973,6 +1062,10 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
continue;
}
// temp tx data to show in gui while waiting for real data from server
final List<InputV2> tempInputs = [];
final List<OutputV2> tempOutputs = [];
// sign
final txb = btc.TransactionBuilder(network: _bitcoinDartNetwork);
txb.setVersion(txVersion);
@ -985,10 +1078,50 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
1, // minus 1 is important. 0xffffffff on its own will burn funds
input.output,
);
tempInputs.add(
InputV2.isarCantDoRequiredInDefaultConstructor(
scriptSigHex: txb.inputs.first.script?.toHex,
sequence: 0xffffffff - 1,
outpoint: OutpointV2.isarCantDoRequiredInDefaultConstructor(
txid: input.utxo.txid,
vout: input.utxo.vout,
),
addresses: input.utxo.address == null ? [] : [input.utxo.address!],
valueStringSats: input.utxo.value.toString(),
witness: null,
innerRedeemScriptAsm: null,
coinbase: null,
walletOwns: true,
),
);
}
for (final output in vout) {
txb.addOutput(output.$1, output.$2);
final addressOrScript = output.$1;
final value = output.$2;
txb.addOutput(addressOrScript, value);
tempOutputs.add(
OutputV2.isarCantDoRequiredInDefaultConstructor(
scriptPubKeyHex:
addressOrScript is Uint8List ? addressOrScript.toHex : "000000",
valueStringSats: value.toString(),
addresses: [
if (addressOrScript is String) addressOrScript.toString(),
],
walletOwns: (await mainDB.isar.addresses
.where()
.walletIdEqualTo(walletId)
.filter()
.valueEqualTo(addressOrScript is Uint8List
? output.$3!
: addressOrScript as String)
.valueProperty()
.findFirst()) !=
null,
),
);
}
try {
@ -1035,6 +1168,23 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
fractionDigits: cryptoCurrency.fractionDigits,
),
usedUTXOs: vin.map((e) => e.utxo).toList(),
tempTx: TransactionV2(
walletId: walletId,
blockHash: null,
hash: builtTx.getId(),
txid: builtTx.getId(),
timestamp: DateTime.timestamp().millisecondsSinceEpoch ~/ 1000,
inputs: List.unmodifiable(tempInputs),
outputs: List.unmodifiable(tempOutputs),
type:
tempOutputs.map((e) => e.walletOwns).fold(true, (p, e) => p &= e)
? TransactionType.sentToSelf
: TransactionType.outgoing,
subType: TransactionSubType.sparkMint,
otherData: null,
height: null,
version: 3,
),
);
if (nFeeRet.toInt() < data.vSize!) {