diff --git a/lib/models/isar/models/blockchain_data/address.dart b/lib/models/isar/models/blockchain_data/address.dart index a26ef94b6..3ada44746 100644 --- a/lib/models/isar/models/blockchain_data/address.dart +++ b/lib/models/isar/models/blockchain_data/address.dart @@ -148,7 +148,7 @@ class Address extends CryptoCurrencyAddress { } } -// do not modify +// do not modify unless you know what the consequences are enum AddressType { p2pkh, p2sh, @@ -159,7 +159,9 @@ enum AddressType { nonWallet, ethereum, nano, - banano; + banano, + spark, + ; String get readableName { switch (this) { @@ -183,6 +185,8 @@ enum AddressType { return "Nano"; case AddressType.banano: return "Banano"; + case AddressType.spark: + return "Spark"; } } } diff --git a/lib/wallets/wallet/impl/firo_wallet.dart b/lib/wallets/wallet/impl/firo_wallet.dart index f0adf5224..bcb90ac1b 100644 --- a/lib/wallets/wallet/impl/firo_wallet.dart +++ b/lib/wallets/wallet/impl/firo_wallet.dart @@ -18,8 +18,13 @@ import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/lelantus_inte import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; import 'package:tuple/tuple.dart'; +const sparkStartBlock = 819300; // (approx 18 Jan 2024) + class FiroWallet extends Bip39HDWallet with ElectrumXInterface, LelantusInterface, SparkInterface { + // IMPORTANT: The order of the above mixins matters. + // SparkInterface MUST come after LelantusInterface. + FiroWallet(CryptoCurrencyNetwork network) : super(Firo(network)); @override diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart index 5b5686ad5..3ca5ad462 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart @@ -1,11 +1,147 @@ +import 'dart:typed_data'; + +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/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'; mixin SparkInterface on Bip39HDWallet, ElectrumXInterface { + Future<Address?> getCurrentReceivingSparkAddress() async { + return await mainDB.isar.addresses + .where() + .walletIdEqualTo(walletId) + .filter() + .typeEqualTo(AddressType.spark) + .sortByDerivationIndexDesc() + .findFirst(); + } + + Future<Uint8List> _getSpendKey() async { + final mnemonic = await getMnemonic(); + final mnemonicPassphrase = await getMnemonicPassphrase(); + + // TODO call ffi lib to generate spend key + + throw UnimplementedError(); + } + + Future<Address> generateNextSparkAddress() async { + final highestStoredDiversifier = + (await getCurrentReceivingSparkAddress())?.derivationIndex; + + // default to starting at 1 if none found + final int diversifier = (highestStoredDiversifier ?? 0) + 1; + + // TODO: use real data + final String derivationPath = ""; + final Uint8List publicKey = Uint8List(0); // incomingViewKey? + final String addressString = ""; + + return Address( + walletId: walletId, + value: addressString, + publicKey: publicKey, + derivationIndex: diversifier, + derivationPath: DerivationPath()..value = derivationPath, + type: AddressType.spark, + subType: AddressSubType.receiving, + ); + } + + Future<Amount> estimateFeeForSpark(Amount amount) async { + throw UnimplementedError(); + } + Future<TxData> prepareSendSpark({ required TxData txData, }) async { throw UnimplementedError(); } + + Future<TxData> confirmSendSpark({ + required TxData txData, + }) async { + throw UnimplementedError(); + } + + // TODO lots of room for performance improvements here. Should be similar to + // recoverSparkWallet but only fetch and check anonymity set data that we + // have not yet parsed. + Future<void> refreshSparkData() async { + try { + final latestSparkCoinId = await electrumXClient.getSparkLatestCoinId(); + + // TODO improve performance by adding this call to the cached client + final anonymitySet = await electrumXClient.getSparkAnonymitySet( + coinGroupId: latestSparkCoinId.toString(), + ); + + // TODO loop over set and see which coins are ours using the FFI call `identifyCoin` + List myCoins = []; + + // fetch metadata for myCoins + + // create list of Spark Coin isar objects + + // update wallet spark coins in isar + + // refresh spark balance? + + throw UnimplementedError(); + } catch (e, s) { + // todo logging + + rethrow; + } + } + + /// Should only be called within the standard wallet [recover] function due to + /// mutex locking. Otherwise behaviour MAY be undefined. + Future<void> recoverSparkWallet( + // { + // required int latestSetId, + // required Map<dynamic, dynamic> setDataMap, + // required Set<String> usedSerialNumbers, + // } + ) async { + try { + // do we need to generate any spark address(es) here? + + final latestSparkCoinId = await electrumXClient.getSparkLatestCoinId(); + + // TODO improve performance by adding this call to the cached client + final anonymitySet = await electrumXClient.getSparkAnonymitySet( + coinGroupId: latestSparkCoinId.toString(), + ); + + // TODO loop over set and see which coins are ours using the FFI call `identifyCoin` + List myCoins = []; + + // fetch metadata for myCoins + + // create list of Spark Coin isar objects + + // update wallet spark coins in isar + + throw UnimplementedError(); + } catch (e, s) { + // todo logging + + rethrow; + } + } + + @override + Future<void> updateBalance() async { + // call to super to update transparent balance (and lelantus balance if + // what ever class this mixin is used on uses LelantusInterface as well) + final normalBalanceFuture = super.updateBalance(); + + throw UnimplementedError(); + + // wait for normalBalanceFuture to complete before returning + await normalBalanceFuture; + } }