untested: ltc refactor

This commit is contained in:
julian 2024-01-04 18:37:46 -06:00
parent 611237ecdf
commit af25da5a59
20 changed files with 3899 additions and 3674 deletions

View file

@ -13,11 +13,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/ordinals/widgets/ordinals_list.dart'; import 'package:stackwallet/pages/ordinals/widgets/ordinals_list.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/services/mixins/ordinals_interface.dart';
import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/show_loading.dart'; import 'package:stackwallet/utilities/show_loading.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/ordinals_interface.dart';
import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';

View file

@ -13,11 +13,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/ordinals/widgets/ordinals_list.dart'; import 'package:stackwallet/pages/ordinals/widgets/ordinals_list.dart';
import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/mixins/ordinals_interface.dart';
import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/show_loading.dart'; import 'package:stackwallet/utilities/show_loading.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/ordinals_interface.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
@ -213,7 +213,6 @@ class _DesktopOrdinals extends ConsumerState<DesktopOrdinalsView> {
isDesktop: true, isDesktop: true,
whileFuture: Future.wait<void>([ whileFuture: Future.wait<void>([
Future.delayed(const Duration(seconds: 2)), Future.delayed(const Duration(seconds: 2)),
// TODO: [prio=high] FIX CAST as ISSUE
(ref.read(pWallets).getWallet(widget.walletId) (ref.read(pWallets).getWallet(widget.walletId)
as OrdinalsInterface) as OrdinalsInterface)
.refreshInscriptions() .refreshInscriptions()

View file

@ -16,7 +16,6 @@ import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/models/paymint/fee_object_model.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart'; import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart';
import 'package:stackwallet/services/coins/litecoin/litecoin_wallet.dart';
import 'package:stackwallet/services/coins/monero/monero_wallet.dart'; import 'package:stackwallet/services/coins/monero/monero_wallet.dart';
import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'; import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart';
import 'package:stackwallet/services/coins/particl/particl_wallet.dart'; import 'package:stackwallet/services/coins/particl/particl_wallet.dart';
@ -85,26 +84,10 @@ abstract class CoinServiceAPI {
throw UnimplementedError("moved"); throw UnimplementedError("moved");
case Coin.litecoin: case Coin.litecoin:
return LitecoinWallet( throw UnimplementedError("moved");
walletId: walletId,
walletName: walletName,
coin: coin,
secureStore: secureStorageInterface,
client: client,
cachedClient: cachedClient,
tracker: tracker,
);
case Coin.litecoinTestNet: case Coin.litecoinTestNet:
return LitecoinWallet( throw UnimplementedError("moved");
walletId: walletId,
walletName: walletName,
coin: coin,
secureStore: secureStorageInterface,
client: client,
cachedClient: cachedClient,
tracker: tracker,
);
case Coin.bitcoinTestNet: case Coin.bitcoinTestNet:
throw UnimplementedError("moved"); throw UnimplementedError("moved");

File diff suppressed because it is too large Load diff

View file

@ -1,94 +1,48 @@
import 'dart:async';
import 'package:isar/isar.dart';
import 'package:stackwallet/db/isar/main_db.dart';
import 'package:stackwallet/dto/ordinals/inscription_data.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart';
import 'package:stackwallet/models/isar/ordinal.dart';
import 'package:stackwallet/services/litescribe_api.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
mixin OrdinalsInterface { mixin OrdinalsInterface {
late final String _walletId; // late final String _walletId;
late final Coin _coin; // late final Coin _coin;
late final MainDB _db; // late final MainDB _db;
//
void initOrdinalsInterface({ // void initOrdinalsInterface({
required String walletId, // required String walletId,
required Coin coin, // required Coin coin,
required MainDB db, // required MainDB db,
}) { // }) {
_walletId = walletId; // _walletId = walletId;
_coin = coin; // _coin = coin;
_db = db; // _db = db;
} // }
//
final LitescribeAPI litescribeAPI = // final LitescribeAPI litescribeAPI =
LitescribeAPI(baseUrl: 'https://litescribe.io/api'); // LitescribeAPI(baseUrl: 'https://litescribe.io/api');
//
Future<void> refreshInscriptions() async { //
final uniqueAddresses = await _db //
.getUTXOs(_walletId) //
.filter() //
.addressIsNotNull() // // // check if an inscription is in a given <UTXO> output
.distinctByAddress() // // Future<bool> inscriptionInOutput(UTXO output) async {
.addressProperty() // // if (output.address != null) {
.findAll(); // // var inscriptions =
final inscriptions = // // await litescribeAPI.getInscriptionsByAddress("${output.address}");
await _getInscriptionDataFromAddresses(uniqueAddresses.cast<String>()); // // if (inscriptions.isNotEmpty) {
// // return true;
final ords = inscriptions // // } else {
.map((e) => Ordinal.fromInscriptionData(e, _walletId)) // // return false;
.toList(); // // }
// // } else {
await _db.isar.writeTxn(() async { // // throw UnimplementedError(
await _db.isar.ordinals // // 'TODO look up utxo without address. utxo->txid:output->address');
.where() // // }
.filter() // // }
.walletIdEqualTo(_walletId) //
.deleteAll(); // // check if an inscription is in a given <UTXO> output
await _db.isar.ordinals.putAll(ords); // Future<bool> inscriptionInAddress(String address) async {
}); // var inscriptions = await litescribeAPI.getInscriptionsByAddress(address);
} // if (inscriptions.isNotEmpty) {
// return true;
Future<List<InscriptionData>> _getInscriptionDataFromAddresses( // } else {
List<String> addresses) async { // return false;
List<InscriptionData> allInscriptions = []; // }
for (String address in addresses) { // }
try {
var inscriptions =
await litescribeAPI.getInscriptionsByAddress(address);
allInscriptions.addAll(inscriptions);
} catch (e) {
throw Exception("Error fetching inscriptions for address $address: $e");
}
}
return allInscriptions;
}
// check if an inscription is in a given <UTXO> output
Future<bool> inscriptionInOutput(UTXO output) async {
if (output.address != null) {
var inscriptions =
await litescribeAPI.getInscriptionsByAddress("${output.address}");
if (inscriptions.isNotEmpty) {
return true;
} else {
return false;
}
} else {
throw UnimplementedError(
'TODO look up utxo without address. utxo->txid:output->address');
}
}
// check if an inscription is in a given <UTXO> output
Future<bool> inscriptionInAddress(String address) async {
var inscriptions = await litescribeAPI.getInscriptionsByAddress(address);
if (inscriptions.isNotEmpty) {
return true;
} else {
return false;
}
}
} }

View file

@ -67,11 +67,13 @@ class BitcoinWallet extends Bip39HDWallet
} }
@override @override
({String? blockedReason, bool blocked}) checkBlockUTXO( Future<({String? blockedReason, bool blocked, String? utxoLabel})>
checkBlockUTXO(
Map<String, dynamic> jsonUTXO, Map<String, dynamic> jsonUTXO,
String? scriptPubKeyHex, String? scriptPubKeyHex,
Map<String, dynamic>? jsonTX, Map<String, dynamic>? jsonTX,
) { String? utxoOwnerAddress,
) async {
bool blocked = false; bool blocked = false;
String? blockedReason; String? blockedReason;
@ -97,7 +99,7 @@ class BitcoinWallet extends Bip39HDWallet
} }
} }
return (blockedReason: blockedReason, blocked: blocked); return (blockedReason: blockedReason, blocked: blocked, utxoLabel: null);
} }
@override @override

View file

@ -303,11 +303,13 @@ class BitcoincashWallet extends Bip39HDWallet
} }
@override @override
({String? blockedReason, bool blocked}) checkBlockUTXO( Future<({String? blockedReason, bool blocked, String? utxoLabel})>
checkBlockUTXO(
Map<String, dynamic> jsonUTXO, Map<String, dynamic> jsonUTXO,
String? scriptPubKeyHex, String? scriptPubKeyHex,
Map<String, dynamic> jsonTX, Map<String, dynamic> jsonTX,
) { String? utxoOwnerAddress,
) async {
bool blocked = false; bool blocked = false;
String? blockedReason; String? blockedReason;
@ -337,7 +339,7 @@ class BitcoincashWallet extends Bip39HDWallet
} }
} }
return (blockedReason: blockedReason, blocked: blocked); return (blockedReason: blockedReason, blocked: blocked, utxoLabel: null);
} }
// TODO: correct formula for bch? // TODO: correct formula for bch?

View file

@ -63,11 +63,13 @@ class DogecoinWallet extends Bip39HDWallet
} }
@override @override
({String? blockedReason, bool blocked}) checkBlockUTXO( Future<({String? blockedReason, bool blocked, String? utxoLabel})>
checkBlockUTXO(
Map<String, dynamic> jsonUTXO, Map<String, dynamic> jsonUTXO,
String? scriptPubKeyHex, String? scriptPubKeyHex,
Map<String, dynamic> jsonTX, Map<String, dynamic> jsonTX,
) { String? utxoOwnerAddress,
) async {
bool blocked = false; bool blocked = false;
String? blockedReason; String? blockedReason;
@ -91,7 +93,7 @@ class DogecoinWallet extends Bip39HDWallet
} }
} }
return (blockedReason: blockedReason, blocked: blocked); return (blockedReason: blockedReason, blocked: blocked, utxoLabel: null);
} }
@override @override

View file

@ -298,11 +298,13 @@ class EcashWallet extends Bip39HDWallet
} }
@override @override
({String? blockedReason, bool blocked}) checkBlockUTXO( Future<({String? blockedReason, bool blocked, String? utxoLabel})>
checkBlockUTXO(
Map<String, dynamic> jsonUTXO, Map<String, dynamic> jsonUTXO,
String? scriptPubKeyHex, String? scriptPubKeyHex,
Map<String, dynamic> jsonTX, Map<String, dynamic> jsonTX,
) { String? utxoOwnerAddress,
) async {
bool blocked = false; bool blocked = false;
String? blockedReason; String? blockedReason;
@ -332,7 +334,7 @@ class EcashWallet extends Bip39HDWallet
} }
} }
return (blockedReason: blockedReason, blocked: blocked); return (blockedReason: blockedReason, blocked: blocked, utxoLabel: null);
} }
// TODO: correct formula for ecash? // TODO: correct formula for ecash?

View file

@ -57,13 +57,13 @@ class EpiccashWallet extends Bip39Wallet {
} }
@override @override
Future<void> updateUTXOs() { Future<bool> updateUTXOs() {
// TODO: implement updateUTXOs // TODO: implement updateUTXOs
throw UnimplementedError(); throw UnimplementedError();
} }
@override @override
Future<void> updateNode() { Future<bool> updateNode() {
// TODO: implement updateNode // TODO: implement updateNode
throw UnimplementedError(); throw UnimplementedError();
} }

View file

@ -494,11 +494,13 @@ class FiroWallet extends Bip39HDWallet
} }
@override @override
({String? blockedReason, bool blocked}) checkBlockUTXO( Future<({String? blockedReason, bool blocked, String? utxoLabel})>
checkBlockUTXO(
Map<String, dynamic> jsonUTXO, Map<String, dynamic> jsonUTXO,
String? scriptPubKeyHex, String? scriptPubKeyHex,
Map<String, dynamic>? jsonTX, Map<String, dynamic>? jsonTX,
) { String? utxoOwnerAddress,
) async {
bool blocked = false; bool blocked = false;
String? blockedReason; String? blockedReason;
// //
@ -524,7 +526,7 @@ class FiroWallet extends Bip39HDWallet
// } // }
// } // }
// //
return (blockedReason: blockedReason, blocked: blocked); return (blockedReason: blockedReason, blocked: blocked, utxoLabel: null);
} }
@override @override

View file

@ -0,0 +1,143 @@
import 'package:isar/isar.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/litecoin.dart';
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.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/electrumx_interface.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/ordinals_interface.dart';
import 'package:tuple/tuple.dart';
class LitecoinWallet extends Bip39HDWallet
with ElectrumXInterface, CoinControlInterface, OrdinalsInterface {
@override
int get isarTransactionVersion => 1; // TODO actually set this to 2
LitecoinWallet(CryptoCurrencyNetwork network) : super(Litecoin(network));
@override
FilterOperation? get changeAddressFilterOperation =>
FilterGroup.and(standardChangeAddressFilters);
@override
FilterOperation? get receivingAddressFilterOperation =>
FilterGroup.and(standardReceivingAddressFilters);
// ===========================================================================
@override
Future<List<Address>> fetchAddressesForElectrumXScan() async {
final allAddresses = await mainDB
.getAddresses(walletId)
.filter()
.not()
.group(
(q) => q
.typeEqualTo(AddressType.nonWallet)
.or()
.subTypeEqualTo(AddressSubType.nonWallet),
)
.findAll();
return allAddresses;
}
// ===========================================================================
@override
Future<void> updateTransactions() async {
final currentChainHeight = await fetchChainHeight();
// TODO: [prio=med] switch to V2 transactions
final data = await fetchTransactionsV1(
addresses: await fetchAddressesForElectrumXScan(),
currentChainHeight: currentChainHeight,
);
await mainDB.addNewTransactionData(
data
.map((e) => Tuple2(
e.transaction,
e.address,
))
.toList(),
walletId,
);
}
@override
Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) {
return Amount(
rawValue: BigInt.from(
((42 + (272 * inputCount) + (128 * outputCount)) / 4).ceil() *
(feeRatePerKB / 1000).ceil()),
fractionDigits: cryptoCurrency.fractionDigits,
);
}
@override
int estimateTxFee({required int vSize, required int feeRatePerKB}) {
return vSize * (feeRatePerKB / 1000).ceil();
}
//
// @override
// Future<TxData> coinSelection({required TxData txData}) async {
// final isCoinControl = txData.utxos != null;
// final isSendAll = txData.amount == info.cachedBalance.spendable;
//
// final utxos =
// txData.utxos?.toList() ?? await mainDB.getUTXOs(walletId).findAll();
//
// final currentChainHeight = await chainHeight;
// final List<UTXO> spendableOutputs = [];
// int spendableSatoshiValue = 0;
//
// // Build list of spendable outputs and totaling their satoshi amount
// for (final utxo in utxos) {
// if (utxo.isBlocked == false &&
// utxo.isConfirmed(currentChainHeight, cryptoCurrency.minConfirms) &&
// utxo.used != true) {
// spendableOutputs.add(utxo);
// spendableSatoshiValue += utxo.value;
// }
// }
//
// if (isCoinControl && spendableOutputs.length < utxos.length) {
// throw ArgumentError("Attempted to use an unavailable utxo");
// }
//
// if (spendableSatoshiValue < txData.amount!.raw.toInt()) {
// throw Exception("Insufficient balance");
// } else if (spendableSatoshiValue == txData.amount!.raw.toInt() &&
// !isSendAll) {
// throw Exception("Insufficient balance to pay transaction fee");
// }
//
// if (isCoinControl) {
// } else {
// final selection = cs.coinSelection(
// spendableOutputs
// .map((e) => cs.InputModel(
// i: e.vout,
// txid: e.txid,
// value: e.value,
// address: e.address,
// ))
// .toList(),
// txData.recipients!
// .map((e) => cs.OutputModel(
// address: e.address,
// value: e.amount.raw.toInt(),
// ))
// .toList(),
// txData.feeRateAmount!,
// 10, // TODO: ???????????????????????????????
// );
//
// // .inputs and .outputs will be null if no solution was found
// if (selection.inputs!.isEmpty || selection.outputs!.isEmpty) {
// throw Exception("coin selection failed");
// }
// }
// }
}

View file

@ -599,7 +599,8 @@ class TezosWallet extends Bip39Wallet<Tezos> {
} }
@override @override
Future<void> updateUTXOs() async { Future<bool> updateUTXOs() async {
// do nothing. Not used in tezos // do nothing. Not used in tezos
return false;
} }
} }

View file

@ -1,7 +1,7 @@
import 'package:stackwallet/wallets/crypto_currency/intermediate/cryptonote_currency.dart'; import 'package:stackwallet/wallets/crypto_currency/intermediate/cryptonote_currency.dart';
import 'package:stackwallet/wallets/models/tx_data.dart'; import 'package:stackwallet/wallets/models/tx_data.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart';
import 'package:stackwallet/wallets/wallet/wallet.dart'; import 'package:stackwallet/wallets/wallet/wallet.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart';
abstract class CryptonoteWallet<T extends CryptonoteCurrency> extends Wallet<T> abstract class CryptonoteWallet<T extends CryptonoteCurrency> extends Wallet<T>
with MnemonicInterface<T> { with MnemonicInterface<T> {
@ -28,7 +28,8 @@ abstract class CryptonoteWallet<T extends CryptonoteCurrency> extends Wallet<T>
} }
@override @override
Future<void> updateUTXOs() async { Future<bool> updateUTXOs() async {
// do nothing for now // do nothing for now
return false;
} }
} }

View file

@ -29,6 +29,7 @@ import 'package:stackwallet/wallets/wallet/impl/dogecoin_wallet.dart';
import 'package:stackwallet/wallets/wallet/impl/ecash_wallet.dart'; import 'package:stackwallet/wallets/wallet/impl/ecash_wallet.dart';
import 'package:stackwallet/wallets/wallet/impl/epiccash_wallet.dart'; import 'package:stackwallet/wallets/wallet/impl/epiccash_wallet.dart';
import 'package:stackwallet/wallets/wallet/impl/firo_wallet.dart'; import 'package:stackwallet/wallets/wallet/impl/firo_wallet.dart';
import 'package:stackwallet/wallets/wallet/impl/litecoin_wallet.dart';
import 'package:stackwallet/wallets/wallet/impl/nano_wallet.dart'; import 'package:stackwallet/wallets/wallet/impl/nano_wallet.dart';
import 'package:stackwallet/wallets/wallet/impl/tezos_wallet.dart'; import 'package:stackwallet/wallets/wallet/impl/tezos_wallet.dart';
import 'package:stackwallet/wallets/wallet/impl/wownero_wallet.dart'; import 'package:stackwallet/wallets/wallet/impl/wownero_wallet.dart';
@ -273,6 +274,11 @@ abstract class Wallet<T extends CryptoCurrency> {
case Coin.firoTestNet: case Coin.firoTestNet:
return FiroWallet(CryptoCurrencyNetwork.test); return FiroWallet(CryptoCurrencyNetwork.test);
case Coin.litecoin:
return LitecoinWallet(CryptoCurrencyNetwork.main);
case Coin.litecoinTestNet:
return LitecoinWallet(CryptoCurrencyNetwork.test);
case Coin.nano: case Coin.nano:
return NanoWallet(CryptoCurrencyNetwork.main); return NanoWallet(CryptoCurrencyNetwork.main);
@ -360,9 +366,11 @@ abstract class Wallet<T extends CryptoCurrency> {
Future<void> updateNode(); Future<void> updateNode();
Future<void> updateTransactions(); Future<void> updateTransactions();
Future<void> updateUTXOs();
Future<void> updateBalance(); Future<void> updateBalance();
// returns true if new utxos were added to local db
Future<bool> updateUTXOs();
/// updates the wallet info's cachedChainHeight /// updates the wallet info's cachedChainHeight
Future<void> updateChainHeight(); Future<void> updateChainHeight();

View file

@ -647,6 +647,7 @@ mixin ElectrumXInterface on Bip39HDWallet {
utxoSigningData[i].utxo.vout, utxoSigningData[i].utxo.vout,
null, null,
utxoSigningData[i].output!, utxoSigningData[i].output!,
cryptoCurrency.networkParams.bech32Hrp,
); );
} }
@ -655,6 +656,7 @@ mixin ElectrumXInterface on Bip39HDWallet {
txb.addOutput( txb.addOutput(
txData.recipients![i].address, txData.recipients![i].address,
txData.recipients![i].amount.raw.toInt(), txData.recipients![i].amount.raw.toInt(),
cryptoCurrency.networkParams.bech32Hrp,
); );
} }
@ -666,6 +668,7 @@ mixin ElectrumXInterface on Bip39HDWallet {
keyPair: utxoSigningData[i].keyPair!, keyPair: utxoSigningData[i].keyPair!,
witnessValue: utxoSigningData[i].utxo.value, witnessValue: utxoSigningData[i].utxo.value,
redeemScript: utxoSigningData[i].redeemScript, redeemScript: utxoSigningData[i].redeemScript,
overridePrefix: cryptoCurrency.networkParams.bech32Hrp,
); );
} }
} catch (e, s) { } catch (e, s) {
@ -674,7 +677,7 @@ mixin ElectrumXInterface on Bip39HDWallet {
rethrow; rethrow;
} }
final builtTx = txb.build(); final builtTx = txb.build(cryptoCurrency.networkParams.bech32Hrp);
final vSize = builtTx.virtualSize(); final vSize = builtTx.virtualSize();
return txData.copyWith( return txData.copyWith(
@ -1051,14 +1054,19 @@ mixin ElectrumXInterface on Bip39HDWallet {
} }
} }
final checkBlockResult = checkBlockUTXO(jsonUTXO, scriptPubKey, txn); final checkBlockResult = await checkBlockUTXO(
jsonUTXO,
scriptPubKey,
txn,
utxoOwnerAddress,
);
final utxo = UTXO( final utxo = UTXO(
walletId: walletId, walletId: walletId,
txid: txn["txid"] as String, txid: txn["txid"] as String,
vout: vout, vout: vout,
value: jsonUTXO["value"] as int, value: jsonUTXO["value"] as int,
name: "", name: checkBlockResult.utxoLabel ?? "",
isBlocked: checkBlockResult.blocked, isBlocked: checkBlockResult.blocked,
blockedReason: checkBlockResult.blockedReason, blockedReason: checkBlockResult.blockedReason,
isCoinbase: txn["is_coinbase"] as bool? ?? false, isCoinbase: txn["is_coinbase"] as bool? ?? false,
@ -1650,7 +1658,7 @@ mixin ElectrumXInterface on Bip39HDWallet {
} }
@override @override
Future<void> updateUTXOs() async { Future<bool> updateUTXOs() async {
final allAddresses = await fetchAddressesForElectrumXScan(); final allAddresses = await fetchAddressesForElectrumXScan();
try { try {
@ -1710,12 +1718,13 @@ mixin ElectrumXInterface on Bip39HDWallet {
} }
} }
await mainDB.updateUTXOs(walletId, outputArray); return await mainDB.updateUTXOs(walletId, outputArray);
} catch (e, s) { } catch (e, s) {
Logging.instance.log( Logging.instance.log(
"Output fetch unsuccessful: $e\n$s", "Output fetch unsuccessful: $e\n$s",
level: LogLevel.Error, level: LogLevel.Error,
); );
return false;
} }
} }
@ -1870,10 +1879,12 @@ mixin ElectrumXInterface on Bip39HDWallet {
/// Certain coins need to check if the utxo should be marked /// Certain coins need to check if the utxo should be marked
/// as blocked as well as give a reason. /// as blocked as well as give a reason.
({String? blockedReason, bool blocked}) checkBlockUTXO( Future<({String? blockedReason, bool blocked, String? utxoLabel})>
checkBlockUTXO(
Map<String, dynamic> jsonUTXO, Map<String, dynamic> jsonUTXO,
String? scriptPubKeyHex, String? scriptPubKeyHex,
Map<String, dynamic> jsonTX, Map<String, dynamic> jsonTX,
String? utxoOwnerAddress,
); );
// =========================================================================== // ===========================================================================

View file

@ -651,8 +651,9 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
FilterGroup.and(standardReceivingAddressFilters); FilterGroup.and(standardReceivingAddressFilters);
@override @override
Future<void> updateUTXOs() async { Future<bool> updateUTXOs() async {
// do nothing for nano based coins // do nothing for nano based coins
return false;
} }
@override @override

View file

@ -0,0 +1,109 @@
import 'package:isar/isar.dart';
import 'package:stackwallet/dto/ordinals/inscription_data.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart';
import 'package:stackwallet/models/isar/ordinal.dart';
import 'package:stackwallet/services/litescribe_api.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart';
mixin OrdinalsInterface on ElectrumXInterface {
final LitescribeAPI litescribeAPI =
LitescribeAPI(baseUrl: 'https://litescribe.io/api');
// check if an inscription is in a given <UTXO> output
Future<bool> _inscriptionInAddress(String address) async {
var inscriptions = await litescribeAPI.getInscriptionsByAddress(address);
if (inscriptions.isNotEmpty) {
return true;
} else {
return false;
}
}
Future<void> refreshInscriptions() async {
final uniqueAddresses = await mainDB
.getUTXOs(walletId)
.filter()
.addressIsNotNull()
.distinctByAddress()
.addressProperty()
.findAll();
final inscriptions =
await _getInscriptionDataFromAddresses(uniqueAddresses.cast<String>());
final ords = inscriptions
.map((e) => Ordinal.fromInscriptionData(e, walletId))
.toList();
await mainDB.isar.writeTxn(() async {
await mainDB.isar.ordinals
.where()
.filter()
.walletIdEqualTo(walletId)
.deleteAll();
await mainDB.isar.ordinals.putAll(ords);
});
}
// =================== Overrides =============================================
@override
Future<({bool blocked, String? blockedReason, String? utxoLabel})>
checkBlockUTXO(
Map<String, dynamic> jsonUTXO,
String? scriptPubKeyHex,
Map<String, dynamic> jsonTX,
String? utxoOwnerAddress,
) async {
bool shouldBlock = false;
String? blockReason;
String? label;
final utxoAmount = jsonUTXO["value"] as int;
// TODO: [prio=med] check following 3 todos
// TODO check the specific output, not just the address in general
// TODO optimize by freezing output in OrdinalsInterface, so one ordinal API calls is made (or at least many less)
if (utxoOwnerAddress != null) {
if (await _inscriptionInAddress(utxoOwnerAddress)) {
shouldBlock = true;
blockReason = "Ordinal";
label = "Ordinal detected at address";
}
} else {
// TODO implement inscriptionInOutput
if (utxoAmount <= 10000) {
shouldBlock = true;
blockReason = "May contain ordinal";
label = "Possible ordinal";
}
}
return (blockedReason: blockReason, blocked: shouldBlock, utxoLabel: label);
}
@override
Future<bool> updateUTXOs() async {
final newUtxosAdded = await super.updateUTXOs();
if (newUtxosAdded) {
await refreshInscriptions();
}
return newUtxosAdded;
}
// ===================== Private =============================================
Future<List<InscriptionData>> _getInscriptionDataFromAddresses(
List<String> addresses) async {
List<InscriptionData> allInscriptions = [];
for (String address in addresses) {
try {
var inscriptions =
await litescribeAPI.getInscriptionsByAddress(address);
allInscriptions.addAll(inscriptions);
} catch (e) {
throw Exception("Error fetching inscriptions for address $address: $e");
}
}
return allInscriptions;
}
}

View file

@ -280,7 +280,7 @@ packages:
source: git source: git
version: "1.1.0" version: "1.1.0"
coinlib_flutter: coinlib_flutter:
dependency: "direct overridden" dependency: "direct dev"
description: description:
path: coinlib_flutter path: coinlib_flutter
ref: "4f549b8b511a63fdc1f44796ab43b10f586635cd" ref: "4f549b8b511a63fdc1f44796ab43b10f586635cd"

View file

@ -186,6 +186,11 @@ dev_dependencies:
import_sorter: ^4.6.0 import_sorter: ^4.6.0
flutter_lints: ^2.0.1 flutter_lints: ^2.0.1
isar_generator: 3.0.5 isar_generator: 3.0.5
coinlib_flutter:
git:
url: https://github.com/cypherstack/coinlib.git
path: coinlib_flutter
ref: 4f549b8b511a63fdc1f44796ab43b10f586635cd
flutter_launcher_icons: flutter_launcher_icons:
android: true android: true