From 20d78d617a6bfce710ba8777e86b778642a24170 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 16 Nov 2023 15:30:01 -0600 Subject: [PATCH] Firo wallet skeleton --- .../crypto_currency/coins/bitcoin.dart | 10 +- lib/wallets/crypto_currency/coins/firo.dart | 137 ++++++++++++++++++ lib/wallets/wallet/impl/bitcoin_wallet.dart | 3 +- lib/wallets/wallet/impl/firo_wallet.dart | 115 +++++++++++++++ .../wallet/mixins/lelantus_interface.dart | 11 ++ .../wallet/mixins/spark_interface.dart | 11 ++ lib/wallets/wallet/wallet.dart | 6 + 7 files changed, 286 insertions(+), 7 deletions(-) create mode 100644 lib/wallets/crypto_currency/coins/firo.dart create mode 100644 lib/wallets/wallet/impl/firo_wallet.dart create mode 100644 lib/wallets/wallet/mixins/lelantus_interface.dart create mode 100644 lib/wallets/wallet/mixins/spark_interface.dart diff --git a/lib/wallets/crypto_currency/coins/bitcoin.dart b/lib/wallets/crypto_currency/coins/bitcoin.dart index 1d5841931..d51aec647 100644 --- a/lib/wallets/crypto_currency/coins/bitcoin.dart +++ b/lib/wallets/crypto_currency/coins/bitcoin.dart @@ -18,6 +18,10 @@ class Bitcoin extends Bip39HDCurrency { } } + @override + // change this to change the number of confirms a tx needs in order to show as confirmed + int get minConfirms => 1; + @override List get supportedDerivationPathTypes => [ DerivePathType.bip44, @@ -96,7 +100,7 @@ class Bitcoin extends Bip39HDCurrency { throw Exception("Invalid Bitcoin network wif used!"); } - int purpose; + final int purpose; switch (derivePathType) { case DerivePathType.bip44: purpose = 44; @@ -162,10 +166,6 @@ class Bitcoin extends Bip39HDCurrency { } } - @override - // change this to change the number of confirms a tx needs in order to show as confirmed - int get minConfirms => 1; - @override bool validateAddress(String address) { try { diff --git a/lib/wallets/crypto_currency/coins/firo.dart b/lib/wallets/crypto_currency/coins/firo.dart new file mode 100644 index 000000000..09fe1ddda --- /dev/null +++ b/lib/wallets/crypto_currency/coins/firo.dart @@ -0,0 +1,137 @@ +import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib; +import 'package:stackwallet/models/isar/models/blockchain_data/address.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/enums/coin_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/intermediate/bip39_hd_currency.dart'; + +class Firo extends Bip39HDCurrency { + Firo(super.network) { + switch (network) { + case CryptoCurrencyNetwork.main: + coin = Coin.firo; + case CryptoCurrencyNetwork.test: + coin = Coin.firoTestNet; + default: + throw Exception("Unsupported network: $network"); + } + } + + @override + int get minConfirms => 1; + + @override + List get supportedDerivationPathTypes => [ + DerivePathType.bip44, + ]; + + @override + String get genesisHash { + switch (network) { + case CryptoCurrencyNetwork.main: + return "4381deb85b1b2c9843c222944b616d997516dcbd6a964e1eaf0def0830695233"; + case CryptoCurrencyNetwork.test: + return "aa22adcc12becaf436027ffe62a8fb21b234c58c23865291e5dc52cf53f64fca"; + default: + throw Exception("Unsupported network: $network"); + } + } + + @override + Amount get dustLimit => Amount( + rawValue: BigInt.from(1000), + fractionDigits: fractionDigits, + ); + + @override + coinlib.NetworkParams get networkParams { + switch (network) { + case CryptoCurrencyNetwork.main: + return const coinlib.NetworkParams( + wifPrefix: 0xd2, + p2pkhPrefix: 0x52, + p2shPrefix: 0x07, + privHDPrefix: 0x0488ade4, + pubHDPrefix: 0x0488b21e, + bech32Hrp: "bc", + messagePrefix: '\x18Zcoin Signed Message:\n', + ); + case CryptoCurrencyNetwork.test: + return const coinlib.NetworkParams( + wifPrefix: 0xb9, + p2pkhPrefix: 0x41, + p2shPrefix: 0xb2, + privHDPrefix: 0x04358394, + pubHDPrefix: 0x043587cf, + bech32Hrp: "tb", + messagePrefix: "\x18Zcoin Signed Message:\n", + ); + default: + throw Exception("Unsupported network: $network"); + } + } + + @override + String constructDerivePath({ + required DerivePathType derivePathType, + int account = 0, + required int chain, + required int index, + }) { + String coinType; + + switch (networkParams.wifPrefix) { + case 0xd2: // firo mainnet wif + coinType = "136"; // firo mainnet + break; + case 0xb9: // firo testnet wif + coinType = "1"; // firo testnet + break; + default: + throw Exception("Invalid Firo network wif used!"); + } + + final int purpose; + switch (derivePathType) { + case DerivePathType.bip44: + purpose = 44; + break; + + default: + throw Exception("DerivePathType $derivePathType not supported"); + } + + return "m/$purpose'/$coinType'/$account'/$chain/$index"; + } + + @override + ({coinlib.Address address, AddressType addressType}) getAddressForPublicKey({ + required coinlib.ECPublicKey publicKey, + required DerivePathType derivePathType, + }) { + switch (derivePathType) { + case DerivePathType.bip44: + final addr = coinlib.P2PKHAddress.fromPublicKey( + publicKey, + version: networkParams.p2pkhPrefix, + ); + + return (address: addr, addressType: AddressType.p2pkh); + + default: + throw Exception("DerivePathType $derivePathType not supported"); + } + } + + @override + bool validateAddress(String address) { + try { + coinlib.Address.fromString(address, networkParams); + return true; + } catch (_) { + return false; + } + // TODO: implement validateAddress for spark addresses? + } +} diff --git a/lib/wallets/wallet/impl/bitcoin_wallet.dart b/lib/wallets/wallet/impl/bitcoin_wallet.dart index e17130273..9db22bf2d 100644 --- a/lib/wallets/wallet/impl/bitcoin_wallet.dart +++ b/lib/wallets/wallet/impl/bitcoin_wallet.dart @@ -1,7 +1,6 @@ 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/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/extensions/extensions.dart'; import 'package:stackwallet/wallets/crypto_currency/coins/bitcoin.dart'; import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart'; @@ -107,7 +106,7 @@ class BitcoinWallet extends Bip39HDWallet rawValue: BigInt.from( ((42 + (272 * inputCount) + (128 * outputCount)) / 4).ceil() * (feeRatePerKB / 1000).ceil()), - fractionDigits: info.coin.decimals, + fractionDigits: cryptoCurrency.fractionDigits, ); } diff --git a/lib/wallets/wallet/impl/firo_wallet.dart b/lib/wallets/wallet/impl/firo_wallet.dart new file mode 100644 index 000000000..0d620ab9d --- /dev/null +++ b/lib/wallets/wallet/impl/firo_wallet.dart @@ -0,0 +1,115 @@ +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/firo.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/mixins/electrumx.dart'; +import 'package:stackwallet/wallets/wallet/mixins/lelantus_interface.dart'; +import 'package:stackwallet/wallets/wallet/mixins/spark_interface.dart'; + +class FiroWallet extends Bip39HDWallet + with ElectrumX, LelantusInterface, SparkInterface { + FiroWallet(CryptoCurrencyNetwork network) : super(Firo(network)); + + @override + FilterOperation? get changeAddressFilterOperation => + FilterGroup.and(standardChangeAddressFilters); + + @override + FilterOperation? get receivingAddressFilterOperation => + FilterGroup.and(standardReceivingAddressFilters); + + // =========================================================================== + + @override + Future> fetchAllOwnAddresses() async { + final allAddresses = await mainDB + .getAddresses(walletId) + .filter() + .not() + .group( + (q) => q + .typeEqualTo(AddressType.nonWallet) + .or() + .subTypeEqualTo(AddressSubType.nonWallet), + ) + .findAll(); + return allAddresses; + } + + // =========================================================================== + + @override + Future updateTransactions() async { + throw UnimplementedError(); + // final currentChainHeight = await fetchChainHeight(); + // + // // TODO: [prio=med] switch to V2 transactions + // final data = await fetchTransactionsV1( + // addresses: await fetchAllOwnAddresses(), + // currentChainHeight: currentChainHeight, + // ); + // + // await mainDB.addNewTransactionData( + // data + // .map((e) => Tuple2( + // e.transaction, + // e.address, + // )) + // .toList(), + // walletId, + // ); + } + + @override + ({String? blockedReason, bool blocked}) checkBlockUTXO( + Map jsonUTXO, + String? scriptPubKeyHex, + Map? jsonTX, + ) { + throw UnimplementedError(); + // bool blocked = false; + // String? blockedReason; + // + // if (jsonTX != null) { + // // check for bip47 notification + // final outputs = jsonTX["vout"] as List; + // for (final output in outputs) { + // List? scriptChunks = + // (output['scriptPubKey']?['asm'] as String?)?.split(" "); + // if (scriptChunks?.length == 2 && scriptChunks?[0] == "OP_RETURN") { + // final blindedPaymentCode = scriptChunks![1]; + // final bytes = blindedPaymentCode.toUint8ListFromHex; + // + // // https://en.bitcoin.it/wiki/BIP_0047#Sending + // if (bytes.length == 80 && bytes.first == 1) { + // blocked = true; + // blockedReason = "Paynym notification output. Incautious " + // "handling of outputs from notification transactions " + // "may cause unintended loss of privacy."; + // break; + // } + // } + // } + // } + // + // return (blockedReason: blockedReason, blocked: blocked); + } + + @override + Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + return Amount( + rawValue: BigInt.from(((181 * inputCount) + (34 * outputCount) + 10) * + (feeRatePerKB / 1000).ceil()), + fractionDigits: cryptoCurrency.fractionDigits, + ); + } + + @override + int estimateTxFee({required int vSize, required int feeRatePerKB}) { + return vSize * (feeRatePerKB / 1000).ceil(); + } + + // =========================================================================== +} diff --git a/lib/wallets/wallet/mixins/lelantus_interface.dart b/lib/wallets/wallet/mixins/lelantus_interface.dart new file mode 100644 index 000000000..e7b277198 --- /dev/null +++ b/lib/wallets/wallet/mixins/lelantus_interface.dart @@ -0,0 +1,11 @@ +import 'package:stackwallet/wallets/models/tx_data.dart'; +import 'package:stackwallet/wallets/wallet/intermediate/bip39_hd_wallet.dart'; +import 'package:stackwallet/wallets/wallet/mixins/electrumx.dart'; + +mixin LelantusInterface on Bip39HDWallet, ElectrumX { + Future prepareSendLelantus({ + required TxData txData, + }) async { + throw UnimplementedError(); + } +} diff --git a/lib/wallets/wallet/mixins/spark_interface.dart b/lib/wallets/wallet/mixins/spark_interface.dart new file mode 100644 index 000000000..615f6994c --- /dev/null +++ b/lib/wallets/wallet/mixins/spark_interface.dart @@ -0,0 +1,11 @@ +import 'package:stackwallet/wallets/models/tx_data.dart'; +import 'package:stackwallet/wallets/wallet/intermediate/bip39_hd_wallet.dart'; +import 'package:stackwallet/wallets/wallet/mixins/electrumx.dart'; + +mixin SparkInterface on Bip39HDWallet, ElectrumX { + Future prepareSendSpark({ + required TxData txData, + }) async { + throw UnimplementedError(); + } +} diff --git a/lib/wallets/wallet/wallet.dart b/lib/wallets/wallet/wallet.dart index f05607160..4d09582f2 100644 --- a/lib/wallets/wallet/wallet.dart +++ b/lib/wallets/wallet/wallet.dart @@ -28,6 +28,7 @@ import 'package:stackwallet/wallets/wallet/impl/bitcoincash_wallet.dart'; import 'package:stackwallet/wallets/wallet/impl/dogecoin_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/firo_wallet.dart'; import 'package:stackwallet/wallets/wallet/impl/nano_wallet.dart'; import 'package:stackwallet/wallets/wallet/impl/wownero_wallet.dart'; import 'package:stackwallet/wallets/wallet/mixins/electrumx.dart'; @@ -264,6 +265,11 @@ abstract class Wallet { case Coin.epicCash: return EpiccashWallet(CryptoCurrencyNetwork.main); + case Coin.firo: + return FiroWallet(CryptoCurrencyNetwork.main); + case Coin.firoTestNet: + return FiroWallet(CryptoCurrencyNetwork.main); + case Coin.nano: return NanoWallet(CryptoCurrencyNetwork.main);