diff --git a/lib/services/coins/particl/particl_wallet.dart b/lib/services/coins/particl/particl_wallet.dart index 197afb02d..b40fdc952 100644 --- a/lib/services/coins/particl/particl_wallet.dart +++ b/lib/services/coins/particl/particl_wallet.dart @@ -2495,9 +2495,9 @@ class ParticlWallet extends CoinServiceAPI } } - // int estimateTxFee({required int vSize, required int feeRatePerKB}) { - // return vSize * (feeRatePerKB / 1000).ceil(); - // } + int estimateTxFee({required int vSize, required int feeRatePerKB}) { + return vSize * (feeRatePerKB / 1000).ceil(); + } /// The coinselection algorithm decides whether or not the user is eligible to make the transaction /// with [satoshiAmountToSend] and [selectedTxFeeRate]. If so, it will call buildTrasaction() and return @@ -3463,14 +3463,14 @@ class ParticlWallet extends CoinServiceAPI } } - // Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { - // return Amount( - // rawValue: BigInt.from( - // ((42 + (272 * inputCount) + (128 * outputCount)) / 4).ceil() * - // (feeRatePerKB / 1000).ceil()), - // fractionDigits: coin.decimals, - // ); - // } + Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + return Amount( + rawValue: BigInt.from( + ((42 + (272 * inputCount) + (128 * outputCount)) / 4).ceil() * + (feeRatePerKB / 1000).ceil()), + fractionDigits: coin.decimals, + ); + } Future sweepAllEstimate(int feeRate) async { int available = 0; diff --git a/lib/wallets/crypto_currency/coins/particl.dart b/lib/wallets/crypto_currency/coins/particl.dart index 8f85448e9..6b9abab5b 100644 --- a/lib/wallets/crypto_currency/coins/particl.dart +++ b/lib/wallets/crypto_currency/coins/particl.dart @@ -19,17 +19,39 @@ class Particl extends Bip39HDCurrency { } @override - // TODO: implement minConfirms - int get minConfirms => throw UnimplementedError(); + // See https://github.com/cypherstack/stack_wallet/blob/d08b5c9b22b58db800ad07b2ceeb44c6d05f9cf3/lib/services/coins/particl/particl_wallet.dart#L57 + int get minConfirms => 1; @override + // See https://github.com/cypherstack/stack_wallet/blob/d08b5c9b22b58db800ad07b2ceeb44c6d05f9cf3/lib/services/coins/particl/particl_wallet.dart#L68 String constructDerivePath( {required DerivePathType derivePathType, int account = 0, required int chain, required int index}) { - // TODO: implement constructDerivePath - throw UnimplementedError(); + String coinType; + switch (networkParams.wifPrefix) { + case 0x6c: // PART mainnet wif. + coinType = "44"; // PART mainnet. + break; + // TODO: [prio=low] Add testnet. + default: + throw Exception("Invalid Particl network wif used!"); + } + + int purpose; + switch (derivePathType) { + case DerivePathType.bip44: + purpose = 44; + break; + case DerivePathType.bip84: + purpose = 84; + break; + default: + throw Exception("DerivePathType $derivePathType not supported"); + } + + return "m/$purpose'/$coinType'/$account'/$chain/$index"; } @override @@ -47,40 +69,97 @@ class Particl extends Bip39HDCurrency { isFailover: true, isDown: false, ); - + // case CryptoCurrencyNetwork.test: + // TODO: [prio=low] Add testnet. default: throw UnimplementedError(); } } @override - // TODO: implement dustLimit - Amount get dustLimit => throw UnimplementedError(); + // See https://github.com/cypherstack/stack_wallet/blob/d08b5c9b22b58db800ad07b2ceeb44c6d05f9cf3/lib/services/coins/particl/particl_wallet.dart#L58 + Amount get dustLimit => Amount( + rawValue: BigInt.from(294), + fractionDigits: Coin.particl.decimals, + ); @override - // TODO: implement genesisHash - String get genesisHash => throw UnimplementedError(); + // See https://github.com/cypherstack/stack_wallet/blob/d08b5c9b22b58db800ad07b2ceeb44c6d05f9cf3/lib/services/coins/particl/particl_wallet.dart#L63 + String get genesisHash { + switch (network) { + case CryptoCurrencyNetwork.main: + return "0000ee0784c195317ac95623e22fddb8c7b8825dc3998e0bb924d66866eccf4c"; + case CryptoCurrencyNetwork.test: + return "0000594ada5310b367443ee0afd4fa3d0bbd5850ea4e33cdc7d6a904a7ec7c90"; + default: + throw Exception("Unsupported network: $network"); + } + } @override ({coinlib.Address address, AddressType addressType}) getAddressForPublicKey( {required coinlib.ECPublicKey publicKey, required DerivePathType derivePathType}) { - // TODO: implement getAddressForPublicKey - throw UnimplementedError(); + switch (derivePathType) { + case DerivePathType.bip44: + final addr = coinlib.P2PKHAddress.fromPublicKey( + publicKey, + version: networkParams.p2pkhPrefix, + ); + + return (address: addr, addressType: AddressType.p2pkh); + + // case DerivePathType.bip49: + + case DerivePathType.bip84: + final addr = coinlib.P2WPKHAddress.fromPublicKey( + publicKey, + hrp: networkParams.bech32Hrp, + ); + + return (address: addr, addressType: AddressType.p2wpkh); + + default: + throw Exception("DerivePathType $derivePathType not supported"); + } } @override - // TODO: implement networkParams - coinlib.NetworkParams get networkParams => throw UnimplementedError(); + // See https://github.com/cypherstack/stack_wallet/blob/d08b5c9b22b58db800ad07b2ceeb44c6d05f9cf3/lib/services/coins/particl/particl_wallet.dart#L3532 + coinlib.NetworkParams get networkParams { + switch (network) { + case CryptoCurrencyNetwork.main: + return const coinlib.NetworkParams( + wifPrefix: 0x6c, + p2pkhPrefix: 0x38, + p2shPrefix: 0x3c, + privHDPrefix: 0x8f1daeb8, + pubHDPrefix: 0x696e82d1, + bech32Hrp: "pw", + messagePrefix: '\x18Bitcoin Signed Message:\n', + ); + // case CryptoCurrencyNetwork.test: + // TODO: [prio=low] Add testnet. + default: + throw Exception("Unsupported network: $network"); + } + } @override // TODO: implement supportedDerivationPathTypes - List get supportedDerivationPathTypes => - throw UnimplementedError(); + List get supportedDerivationPathTypes => [ + DerivePathType.bip44, + // DerivePathType.bip49, + DerivePathType.bip84, + ]; @override bool validateAddress(String address) { - // TODO: implement validateAddress - throw UnimplementedError(); + try { + coinlib.Address.fromString(address, networkParams); + return true; + } catch (_) { + return false; + } } } diff --git a/lib/wallets/wallet/impl/particl_wallet.dart b/lib/wallets/wallet/impl/particl_wallet.dart index 4e9f24f28..0c97b6b67 100644 --- a/lib/wallets/wallet/impl/particl_wallet.dart +++ b/lib/wallets/wallet/impl/particl_wallet.dart @@ -6,6 +6,7 @@ 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:tuple/tuple.dart'; class ParticlWallet extends Bip39HDWallet with ElectrumXInterface, CoinControlInterface { @@ -46,9 +47,11 @@ class ParticlWallet extends Bip39HDWallet @override Future<({bool blocked, String? blockedReason, String? utxoLabel})> checkBlockUTXO(Map jsonUTXO, String? scriptPubKeyHex, - Map jsonTX, String? utxoOwnerAddress) { - // TODO: implement checkBlockUTXO - throw UnimplementedError(); + Map jsonTX, String? utxoOwnerAddress) async { + // Particl doesn't have special outputs like tokens, ordinals, etc. + // But it may have special types of outputs which we shouldn't or can't spend. + // TODO: [prio=low] Check for special Particl outputs. + return (blocked: false, blockedReason: null, utxoLabel: null); } @override @@ -67,8 +70,23 @@ class ParticlWallet extends Bip39HDWallet } @override - Future updateTransactions() { - // TODO: implement updateTransactions - throw UnimplementedError(); + Future updateTransactions() async { + final currentChainHeight = await fetchChainHeight(); + + // TODO: [prio=low] 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, + ); } }