mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-02-03 03:36:42 +00:00
untested: ltc refactor
This commit is contained in:
parent
611237ecdf
commit
af25da5a59
20 changed files with 3899 additions and 3674 deletions
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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?
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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?
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
143
lib/wallets/wallet/impl/litecoin_wallet.dart
Normal file
143
lib/wallets/wallet/impl/litecoin_wallet.dart
Normal 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");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue