WIP bitcoin frost wallet addition

This commit is contained in:
julian 2024-01-18 17:47:06 -06:00
parent 755cc049b0
commit 85b66fd849
26 changed files with 1492 additions and 61 deletions

View file

@ -163,6 +163,7 @@ enum AddressType {
spark,
stellar,
tezos,
frostMS,
;
String get readableName {
@ -193,6 +194,8 @@ enum AddressType {
return "Stellar";
case AddressType.tezos:
return "Tezos";
case AddressType.frostMS:
return "FrostMS";
}
}
}

View file

@ -266,6 +266,7 @@ const _AddresstypeEnumValueMap = {
'spark': 10,
'stellar': 11,
'tezos': 12,
'frostMS': 13,
};
const _AddresstypeValueEnumMap = {
0: AddressType.p2pkh,
@ -281,6 +282,7 @@ const _AddresstypeValueEnumMap = {
10: AddressType.spark,
11: AddressType.stellar,
12: AddressType.tezos,
13: AddressType.frostMS,
};
Id _addressGetId(Address object) {

View file

@ -26,6 +26,7 @@ import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/wallets/isar/providers/eth/current_token_wallet_provider.dart';
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/wallets/wallet/impl/firo_wallet.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart';
import 'package:stackwallet/widgets/animated_text.dart';
final feeSheetSessionCacheProvider =
@ -697,7 +698,7 @@ class _TransactionFeeSelectionSheetState
const SizedBox(
height: 24,
),
if (coin.isElectrumXCoin)
if (wallet is ElectrumXInterface)
GestureDetector(
onTap: () {
final state =
@ -766,7 +767,7 @@ class _TransactionFeeSelectionSheetState
),
),
),
if (coin.isElectrumXCoin)
if (wallet is ElectrumXInterface)
const SizedBox(
height: 24,
),

View file

@ -166,6 +166,8 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
case Coin.firo:
case Coin.namecoin:
case Coin.particl:
case Coin.bitcoinFrost:
case Coin.bitcoinFrostTestNet:
case Coin.bitcoinTestNet:
case Coin.litecoinTestNet:
case Coin.bitcoincashTestnet:
@ -757,6 +759,8 @@ class _NodeFormState extends ConsumerState<NodeForm> {
case Coin.eCash:
case Coin.stellar:
case Coin.stellarTestnet:
case Coin.bitcoinFrost:
case Coin.bitcoinFrostTestNet:
return false;
case Coin.ethereum:

View file

@ -148,6 +148,8 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
case Coin.litecoinTestNet:
case Coin.bitcoincashTestnet:
case Coin.eCash:
case Coin.bitcoinFrost:
case Coin.bitcoinFrostTestNet:
final client = ElectrumXClient(
host: node!.host,
port: node.port,

View file

@ -52,6 +52,7 @@ import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/wallets/models/tx_data.dart';
import 'package:stackwallet/wallets/wallet/impl/firo_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/paynym_interface.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
import 'package:stackwallet/widgets/animated_text.dart';
@ -1566,7 +1567,8 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
if (!([Coin.nano, Coin.banano, Coin.epicCash, Coin.tezos]
.contains(coin)))
ConditionalParent(
condition: coin.isElectrumXCoin &&
condition: ref.watch(pWallets).getWallet(walletId)
is ElectrumXInterface &&
!(((coin == Coin.firo || coin == Coin.firoTestNet) &&
(ref.watch(publicPrivateBalanceStateProvider.state).state ==
FiroType.lelantus ||

View file

@ -24,6 +24,7 @@ import 'package:stackwallet/services/wallets.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart';
import 'exchange/exchange.dart';
@ -123,7 +124,7 @@ class NotificationsService extends ChangeNotifier {
final node = nodeService.getPrimaryNodeFor(coin: coin);
if (node != null) {
if (coin.isElectrumXCoin) {
if (wallet is ElectrumXInterface) {
final eNode = ElectrumXNode(
address: node.host,
port: node.port,

View file

@ -37,6 +37,8 @@ class CoinThemeColorDefault {
switch (coin) {
case Coin.bitcoin:
case Coin.bitcoinTestNet:
case Coin.bitcoinFrost:
case Coin.bitcoinFrostTestNet:
return bitcoin;
case Coin.litecoin:
case Coin.litecoinTestNet:

View file

@ -1680,6 +1680,8 @@ class StackColors extends ThemeExtension<StackColors> {
switch (coin) {
case Coin.bitcoin:
case Coin.bitcoinTestNet:
case Coin.bitcoinFrost:
case Coin.bitcoinFrostTestNet:
return _coin.bitcoin;
case Coin.litecoin:
case Coin.litecoinTestNet:

View file

@ -40,6 +40,8 @@ enum AmountUnit {
case Coin.litecoin:
case Coin.particl:
case Coin.namecoin:
case Coin.bitcoinFrost:
case Coin.bitcoinFrostTestNet:
case Coin.bitcoinTestNet:
case Coin.litecoinTestNet:
case Coin.bitcoincashTestnet:

View file

@ -18,6 +18,7 @@ Uri getDefaultBlockExplorerUrlFor({
required String txid,
}) {
switch (coin) {
case Coin.bitcoinFrost:
case Coin.bitcoin:
return Uri.parse("https://mempool.space/tx/$txid");
case Coin.litecoin:
@ -25,6 +26,7 @@ Uri getDefaultBlockExplorerUrlFor({
case Coin.litecoinTestNet:
return Uri.parse("https://chain.so/tx/LTCTEST/$txid");
case Coin.bitcoinTestNet:
case Coin.bitcoinFrostTestNet:
return Uri.parse("https://mempool.space/testnet/tx/$txid");
case Coin.dogecoin:
return Uri.parse("https://chain.so/tx/DOGE/$txid");

View file

@ -69,6 +69,7 @@ abstract class Constants {
static BigInt satsPerCoin(Coin coin) {
switch (coin) {
case Coin.bitcoin:
case Coin.bitcoinFrost:
case Coin.litecoin:
case Coin.litecoinTestNet:
case Coin.bitcoincash:
@ -76,6 +77,7 @@ abstract class Constants {
case Coin.dogecoin:
case Coin.firo:
case Coin.bitcoinTestNet:
case Coin.bitcoinFrostTestNet:
case Coin.dogecoinTestNet:
case Coin.firoTestNet:
case Coin.epicCash:
@ -113,6 +115,7 @@ abstract class Constants {
static int decimalPlacesForCoin(Coin coin) {
switch (coin) {
case Coin.bitcoin:
case Coin.bitcoinFrost:
case Coin.litecoin:
case Coin.litecoinTestNet:
case Coin.bitcoincash:
@ -120,6 +123,7 @@ abstract class Constants {
case Coin.dogecoin:
case Coin.firo:
case Coin.bitcoinTestNet:
case Coin.bitcoinFrostTestNet:
case Coin.dogecoinTestNet:
case Coin.firoTestNet:
case Coin.epicCash:
@ -189,6 +193,10 @@ abstract class Constants {
case Coin.wownero:
values.addAll([14, 25]);
break;
case Coin.bitcoinFrost:
case Coin.bitcoinFrostTestNet:
throw ArgumentError("Frost mnemonic lengths unsupported");
}
return values;
}
@ -198,6 +206,8 @@ abstract class Constants {
switch (coin) {
case Coin.bitcoin:
case Coin.bitcoinTestNet:
case Coin.bitcoinFrost:
case Coin.bitcoinFrostTestNet:
case Coin.bitcoincash:
case Coin.bitcoincashTestnet:
case Coin.eCash:
@ -277,6 +287,10 @@ abstract class Constants {
case Coin.monero:
return 25;
case Coin.bitcoinFrost:
case Coin.bitcoinFrostTestNet:
throw ArgumentError("Frost mnemonic length unsupported");
//
// default:
// -1;

View file

@ -312,6 +312,7 @@ abstract class DefaultNodes {
static NodeModel getNodeFor(Coin coin) {
switch (coin) {
case Coin.bitcoin:
case Coin.bitcoinFrost:
return bitcoin;
case Coin.litecoin:
@ -360,6 +361,7 @@ abstract class DefaultNodes {
return tezos;
case Coin.bitcoinTestNet:
case Coin.bitcoinFrostTestNet:
return bitcoinTestnet;
case Coin.litecoinTestNet:

View file

@ -13,6 +13,7 @@ import 'package:stackwallet/utilities/constants.dart';
enum Coin {
bitcoin,
bitcoinFrost,
monero,
banano,
bitcoincash,
@ -35,6 +36,7 @@ enum Coin {
///
bitcoinTestNet,
bitcoinFrostTestNet,
bitcoincashTestnet,
dogecoinTestNet,
firoTestNet,
@ -47,6 +49,8 @@ extension CoinExt on Coin {
switch (this) {
case Coin.bitcoin:
return "Bitcoin";
case Coin.bitcoinFrost:
return "Bitcoin Frost";
case Coin.litecoin:
return "Litecoin";
case Coin.bitcoincash:
@ -79,6 +83,8 @@ extension CoinExt on Coin {
return "Banano";
case Coin.bitcoinTestNet:
return "tBitcoin";
case Coin.bitcoinFrostTestNet:
return "tBitcoin Frost";
case Coin.litecoinTestNet:
return "tLitecoin";
case Coin.bitcoincashTestnet:
@ -95,6 +101,7 @@ extension CoinExt on Coin {
String get ticker {
switch (this) {
case Coin.bitcoin:
case Coin.bitcoinFrost:
return "BTC";
case Coin.litecoin:
return "LTC";
@ -127,6 +134,7 @@ extension CoinExt on Coin {
case Coin.banano:
return "BAN";
case Coin.bitcoinTestNet:
case Coin.bitcoinFrostTestNet:
return "tBTC";
case Coin.litecoinTestNet:
return "tLTC";
@ -144,6 +152,7 @@ extension CoinExt on Coin {
String get uriScheme {
switch (this) {
case Coin.bitcoin:
case Coin.bitcoinFrost:
return "bitcoin";
case Coin.litecoin:
return "litecoin";
@ -177,6 +186,7 @@ extension CoinExt on Coin {
case Coin.banano:
return "ban";
case Coin.bitcoinTestNet:
case Coin.bitcoinFrostTestNet:
return "bitcoin";
case Coin.litecoinTestNet:
return "litecoin";
@ -191,36 +201,6 @@ extension CoinExt on Coin {
}
}
bool get isElectrumXCoin {
switch (this) {
case Coin.bitcoin:
case Coin.litecoin:
case Coin.bitcoincash:
case Coin.dogecoin:
case Coin.firo:
case Coin.namecoin:
case Coin.particl:
case Coin.bitcoinTestNet:
case Coin.litecoinTestNet:
case Coin.bitcoincashTestnet:
case Coin.firoTestNet:
case Coin.dogecoinTestNet:
case Coin.eCash:
return true;
case Coin.epicCash:
case Coin.ethereum:
case Coin.monero:
case Coin.tezos:
case Coin.wownero:
case Coin.nano:
case Coin.banano:
case Coin.stellar:
case Coin.stellarTestnet:
return false;
}
}
bool get hasMnemonicPassphraseSupport {
switch (this) {
case Coin.bitcoin:
@ -241,6 +221,8 @@ extension CoinExt on Coin {
case Coin.stellarTestnet:
return true;
case Coin.bitcoinFrost:
case Coin.bitcoinFrostTestNet:
case Coin.epicCash:
case Coin.monero:
case Coin.wownero:
@ -260,6 +242,8 @@ extension CoinExt on Coin {
case Coin.ethereum:
return true;
case Coin.bitcoinFrost:
case Coin.bitcoinFrostTestNet:
case Coin.firo:
case Coin.namecoin:
case Coin.particl:
@ -284,6 +268,7 @@ extension CoinExt on Coin {
bool get isTestNet {
switch (this) {
case Coin.bitcoin:
case Coin.bitcoinFrost:
case Coin.litecoin:
case Coin.bitcoincash:
case Coin.dogecoin:
@ -303,6 +288,7 @@ extension CoinExt on Coin {
case Coin.dogecoinTestNet:
case Coin.bitcoinTestNet:
case Coin.bitcoinFrostTestNet:
case Coin.litecoinTestNet:
case Coin.bitcoincashTestnet:
case Coin.firoTestNet:
@ -314,6 +300,7 @@ extension CoinExt on Coin {
Coin get mainNetVersion {
switch (this) {
case Coin.bitcoin:
case Coin.bitcoinFrost:
case Coin.litecoin:
case Coin.bitcoincash:
case Coin.dogecoin:
@ -337,6 +324,9 @@ extension CoinExt on Coin {
case Coin.bitcoinTestNet:
return Coin.bitcoin;
case Coin.bitcoinFrostTestNet:
return Coin.bitcoinFrost;
case Coin.litecoinTestNet:
return Coin.litecoin;
@ -364,6 +354,10 @@ extension CoinExt on Coin {
case Coin.particl:
return AddressType.p2wpkh;
case Coin.bitcoinFrost:
case Coin.bitcoinFrostTestNet:
return AddressType.frostMS;
case Coin.eCash:
case Coin.bitcoincash:
case Coin.bitcoincashTestnet:

View file

@ -44,6 +44,8 @@ extension DerivePathTypeExt on DerivePathType {
case Coin.ethereum: // TODO: do we need something here?
return DerivePathType.eth;
case Coin.bitcoinFrost:
case Coin.bitcoinFrostTestNet:
case Coin.epicCash:
case Coin.monero:
case Coin.wownero:

View file

@ -170,30 +170,10 @@ class Bitcoin extends Bip39HDCurrency with PaynymCurrencyInterface {
NodeModel get defaultNode {
switch (network) {
case CryptoCurrencyNetwork.main:
return NodeModel(
host: "bitcoin.stackwallet.com",
port: 50002,
name: DefaultNodes.defaultName,
id: DefaultNodes.buildId(Coin.bitcoin),
useSSL: true,
enabled: true,
coinName: Coin.bitcoin.name,
isFailover: true,
isDown: false,
);
return DefaultNodes.bitcoin;
case CryptoCurrencyNetwork.test:
return NodeModel(
host: "bitcoin-testnet.stackwallet.com",
port: 51002,
name: DefaultNodes.defaultName,
id: DefaultNodes.buildId(Coin.bitcoinTestNet),
useSSL: true,
enabled: true,
coinName: Coin.bitcoinTestNet.name,
isFailover: true,
isDown: false,
);
return DefaultNodes.bitcoinTestnet;
default:
throw UnimplementedError();

View file

@ -0,0 +1,65 @@
import 'dart:typed_data';
import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/utilities/default_nodes.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
import 'package:stackwallet/wallets/crypto_currency/intermediate/bip39_hd_currency.dart';
import 'package:stackwallet/wallets/crypto_currency/intermediate/private_key_currency.dart';
class BitcoinFrost extends FrostCurrency {
BitcoinFrost(super.network) {
switch (network) {
case CryptoCurrencyNetwork.main:
coin = Coin.bitcoin;
case CryptoCurrencyNetwork.test:
coin = Coin.bitcoinTestNet;
default:
throw Exception("Unsupported network: $network");
}
}
@override
int get minConfirms => 1;
@override
NodeModel get defaultNode {
switch (network) {
case CryptoCurrencyNetwork.main:
return DefaultNodes.bitcoin;
case CryptoCurrencyNetwork.test:
return DefaultNodes.bitcoinTestnet;
default:
throw UnimplementedError();
}
}
@override
String get genesisHash {
switch (network) {
case CryptoCurrencyNetwork.main:
return "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f";
case CryptoCurrencyNetwork.test:
return "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943";
default:
throw Exception("Unsupported network: $network");
}
}
@override
String pubKeyToScriptHash({required Uint8List pubKey}) {
try {
return Bip39HDCurrency.convertBytesToScriptHash(pubKey);
} catch (e) {
rethrow;
}
}
@override
bool validateAddress(String address) {
// TODO: implement validateAddress for frost addresses
return true;
}
}

View file

@ -0,0 +1,9 @@
import 'dart:typed_data';
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
abstract class FrostCurrency extends CryptoCurrency {
FrostCurrency(super.network);
String pubKeyToScriptHash({required Uint8List pubKey});
}

View file

@ -0,0 +1,38 @@
import 'package:isar/isar.dart';
import 'package:stackwallet/wallets/isar/isar_id_interface.dart';
part 'frost_wallet_info.g.dart';
@Collection(accessor: "frostWalletInfo", inheritance: false)
class FrostWalletInfo implements IsarId {
@override
Id id = Isar.autoIncrement;
@Index(unique: true, replace: false)
final String walletId;
final List<String> knownSalts;
FrostWalletInfo({
required this.walletId,
required this.knownSalts,
});
FrostWalletInfo copyWith({
List<String>? knownSalts,
}) {
return FrostWalletInfo(
walletId: walletId,
knownSalts: knownSalts ?? this.knownSalts,
);
}
Future<void> updateKnownSalts(
List<String> knownSalts, {
required Isar isar,
}) async {
// await isar.writeTxn(() async {
// await isar.
// })
}
}

View file

@ -0,0 +1,818 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'frost_wallet_info.dart';
// **************************************************************************
// IsarCollectionGenerator
// **************************************************************************
// coverage:ignore-file
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
extension GetFrostWalletInfoCollection on Isar {
IsarCollection<FrostWalletInfo> get frostWalletInfo => this.collection();
}
const FrostWalletInfoSchema = CollectionSchema(
name: r'FrostWalletInfo',
id: -4182879703273806681,
properties: {
r'knownSalts': PropertySchema(
id: 0,
name: r'knownSalts',
type: IsarType.stringList,
),
r'walletId': PropertySchema(
id: 1,
name: r'walletId',
type: IsarType.string,
)
},
estimateSize: _frostWalletInfoEstimateSize,
serialize: _frostWalletInfoSerialize,
deserialize: _frostWalletInfoDeserialize,
deserializeProp: _frostWalletInfoDeserializeProp,
idName: r'id',
indexes: {
r'walletId': IndexSchema(
id: -1783113319798776304,
name: r'walletId',
unique: true,
replace: false,
properties: [
IndexPropertySchema(
name: r'walletId',
type: IndexType.hash,
caseSensitive: true,
)
],
)
},
links: {},
embeddedSchemas: {},
getId: _frostWalletInfoGetId,
getLinks: _frostWalletInfoGetLinks,
attach: _frostWalletInfoAttach,
version: '3.0.5',
);
int _frostWalletInfoEstimateSize(
FrostWalletInfo object,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
var bytesCount = offsets.last;
bytesCount += 3 + object.knownSalts.length * 3;
{
for (var i = 0; i < object.knownSalts.length; i++) {
final value = object.knownSalts[i];
bytesCount += value.length * 3;
}
}
bytesCount += 3 + object.walletId.length * 3;
return bytesCount;
}
void _frostWalletInfoSerialize(
FrostWalletInfo object,
IsarWriter writer,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
writer.writeStringList(offsets[0], object.knownSalts);
writer.writeString(offsets[1], object.walletId);
}
FrostWalletInfo _frostWalletInfoDeserialize(
Id id,
IsarReader reader,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
final object = FrostWalletInfo(
knownSalts: reader.readStringList(offsets[0]) ?? [],
walletId: reader.readString(offsets[1]),
);
object.id = id;
return object;
}
P _frostWalletInfoDeserializeProp<P>(
IsarReader reader,
int propertyId,
int offset,
Map<Type, List<int>> allOffsets,
) {
switch (propertyId) {
case 0:
return (reader.readStringList(offset) ?? []) as P;
case 1:
return (reader.readString(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
}
}
Id _frostWalletInfoGetId(FrostWalletInfo object) {
return object.id;
}
List<IsarLinkBase<dynamic>> _frostWalletInfoGetLinks(FrostWalletInfo object) {
return [];
}
void _frostWalletInfoAttach(
IsarCollection<dynamic> col, Id id, FrostWalletInfo object) {
object.id = id;
}
extension FrostWalletInfoByIndex on IsarCollection<FrostWalletInfo> {
Future<FrostWalletInfo?> getByWalletId(String walletId) {
return getByIndex(r'walletId', [walletId]);
}
FrostWalletInfo? getByWalletIdSync(String walletId) {
return getByIndexSync(r'walletId', [walletId]);
}
Future<bool> deleteByWalletId(String walletId) {
return deleteByIndex(r'walletId', [walletId]);
}
bool deleteByWalletIdSync(String walletId) {
return deleteByIndexSync(r'walletId', [walletId]);
}
Future<List<FrostWalletInfo?>> getAllByWalletId(List<String> walletIdValues) {
final values = walletIdValues.map((e) => [e]).toList();
return getAllByIndex(r'walletId', values);
}
List<FrostWalletInfo?> getAllByWalletIdSync(List<String> walletIdValues) {
final values = walletIdValues.map((e) => [e]).toList();
return getAllByIndexSync(r'walletId', values);
}
Future<int> deleteAllByWalletId(List<String> walletIdValues) {
final values = walletIdValues.map((e) => [e]).toList();
return deleteAllByIndex(r'walletId', values);
}
int deleteAllByWalletIdSync(List<String> walletIdValues) {
final values = walletIdValues.map((e) => [e]).toList();
return deleteAllByIndexSync(r'walletId', values);
}
Future<Id> putByWalletId(FrostWalletInfo object) {
return putByIndex(r'walletId', object);
}
Id putByWalletIdSync(FrostWalletInfo object, {bool saveLinks = true}) {
return putByIndexSync(r'walletId', object, saveLinks: saveLinks);
}
Future<List<Id>> putAllByWalletId(List<FrostWalletInfo> objects) {
return putAllByIndex(r'walletId', objects);
}
List<Id> putAllByWalletIdSync(List<FrostWalletInfo> objects,
{bool saveLinks = true}) {
return putAllByIndexSync(r'walletId', objects, saveLinks: saveLinks);
}
}
extension FrostWalletInfoQueryWhereSort
on QueryBuilder<FrostWalletInfo, FrostWalletInfo, QWhere> {
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterWhere> anyId() {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(const IdWhereClause.any());
});
}
}
extension FrostWalletInfoQueryWhere
on QueryBuilder<FrostWalletInfo, FrostWalletInfo, QWhereClause> {
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterWhereClause> idEqualTo(
Id id) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IdWhereClause.between(
lower: id,
upper: id,
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterWhereClause>
idNotEqualTo(Id id) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query
.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: false),
)
.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: false),
);
} else {
return query
.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: false),
)
.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: false),
);
}
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterWhereClause>
idGreaterThan(Id id, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: include),
);
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterWhereClause> idLessThan(
Id id,
{bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: include),
);
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterWhereClause> idBetween(
Id lowerId,
Id upperId, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IdWhereClause.between(
lower: lowerId,
includeLower: includeLower,
upper: upperId,
includeUpper: includeUpper,
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterWhereClause>
walletIdEqualTo(String walletId) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.equalTo(
indexName: r'walletId',
value: [walletId],
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterWhereClause>
walletIdNotEqualTo(String walletId) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query
.addWhereClause(IndexWhereClause.between(
indexName: r'walletId',
lower: [],
upper: [walletId],
includeUpper: false,
))
.addWhereClause(IndexWhereClause.between(
indexName: r'walletId',
lower: [walletId],
includeLower: false,
upper: [],
));
} else {
return query
.addWhereClause(IndexWhereClause.between(
indexName: r'walletId',
lower: [walletId],
includeLower: false,
upper: [],
))
.addWhereClause(IndexWhereClause.between(
indexName: r'walletId',
lower: [],
upper: [walletId],
includeUpper: false,
));
}
});
}
}
extension FrostWalletInfoQueryFilter
on QueryBuilder<FrostWalletInfo, FrostWalletInfo, QFilterCondition> {
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
idEqualTo(Id value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'id',
value: value,
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
idGreaterThan(
Id value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'id',
value: value,
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
idLessThan(
Id value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'id',
value: value,
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
idBetween(
Id lower,
Id upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'id',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
knownSaltsElementEqualTo(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'knownSalts',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
knownSaltsElementGreaterThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'knownSalts',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
knownSaltsElementLessThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'knownSalts',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
knownSaltsElementBetween(
String lower,
String upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'knownSalts',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
knownSaltsElementStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.startsWith(
property: r'knownSalts',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
knownSaltsElementEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.endsWith(
property: r'knownSalts',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
knownSaltsElementContains(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'knownSalts',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
knownSaltsElementMatches(String pattern, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'knownSalts',
wildcard: pattern,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
knownSaltsElementIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'knownSalts',
value: '',
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
knownSaltsElementIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
property: r'knownSalts',
value: '',
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
knownSaltsLengthEqualTo(int length) {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'knownSalts',
length,
true,
length,
true,
);
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
knownSaltsIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'knownSalts',
0,
true,
0,
true,
);
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
knownSaltsIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'knownSalts',
0,
false,
999999,
true,
);
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
knownSaltsLengthLessThan(
int length, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'knownSalts',
0,
true,
length,
include,
);
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
knownSaltsLengthGreaterThan(
int length, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'knownSalts',
length,
include,
999999,
true,
);
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
knownSaltsLengthBetween(
int lower,
int upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'knownSalts',
lower,
includeLower,
upper,
includeUpper,
);
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
walletIdEqualTo(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'walletId',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
walletIdGreaterThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'walletId',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
walletIdLessThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'walletId',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
walletIdBetween(
String lower,
String upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'walletId',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
walletIdStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.startsWith(
property: r'walletId',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
walletIdEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.endsWith(
property: r'walletId',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
walletIdContains(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'walletId',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
walletIdMatches(String pattern, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'walletId',
wildcard: pattern,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
walletIdIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'walletId',
value: '',
));
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterFilterCondition>
walletIdIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
property: r'walletId',
value: '',
));
});
}
}
extension FrostWalletInfoQueryObject
on QueryBuilder<FrostWalletInfo, FrostWalletInfo, QFilterCondition> {}
extension FrostWalletInfoQueryLinks
on QueryBuilder<FrostWalletInfo, FrostWalletInfo, QFilterCondition> {}
extension FrostWalletInfoQuerySortBy
on QueryBuilder<FrostWalletInfo, FrostWalletInfo, QSortBy> {
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterSortBy>
sortByWalletId() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'walletId', Sort.asc);
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterSortBy>
sortByWalletIdDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'walletId', Sort.desc);
});
}
}
extension FrostWalletInfoQuerySortThenBy
on QueryBuilder<FrostWalletInfo, FrostWalletInfo, QSortThenBy> {
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterSortBy> thenById() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.asc);
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterSortBy> thenByIdDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.desc);
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterSortBy>
thenByWalletId() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'walletId', Sort.asc);
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QAfterSortBy>
thenByWalletIdDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'walletId', Sort.desc);
});
}
}
extension FrostWalletInfoQueryWhereDistinct
on QueryBuilder<FrostWalletInfo, FrostWalletInfo, QDistinct> {
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QDistinct>
distinctByKnownSalts() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'knownSalts');
});
}
QueryBuilder<FrostWalletInfo, FrostWalletInfo, QDistinct> distinctByWalletId(
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'walletId', caseSensitive: caseSensitive);
});
}
}
extension FrostWalletInfoQueryProperty
on QueryBuilder<FrostWalletInfo, FrostWalletInfo, QQueryProperty> {
QueryBuilder<FrostWalletInfo, int, QQueryOperations> idProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'id');
});
}
QueryBuilder<FrostWalletInfo, List<String>, QQueryOperations>
knownSaltsProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'knownSalts');
});
}
QueryBuilder<FrostWalletInfo, String, QQueryOperations> walletIdProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'walletId');
});
}
}

View file

@ -265,6 +265,7 @@ const _WalletInfomainAddressTypeEnumValueMap = {
'spark': 10,
'stellar': 11,
'tezos': 12,
'frostMS': 13,
};
const _WalletInfomainAddressTypeValueEnumMap = {
0: AddressType.p2pkh,
@ -280,6 +281,7 @@ const _WalletInfomainAddressTypeValueEnumMap = {
10: AddressType.spark,
11: AddressType.stellar,
12: AddressType.tezos,
13: AddressType.frostMS,
};
Id _walletInfoGetId(WalletInfo object) {

View file

@ -0,0 +1,475 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:frostdart/frostdart.dart' as frost;
import 'package:frostdart/frostdart_bindings_generated.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/electrumx_rpc/cached_electrumx_client.dart';
import 'package:stackwallet/electrumx_rpc/electrumx_client.dart';
import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart';
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/extensions/extensions.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/bitcoin_frost.dart';
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
import 'package:stackwallet/wallets/crypto_currency/intermediate/private_key_currency.dart';
import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart';
import 'package:stackwallet/wallets/models/tx_data.dart';
import 'package:stackwallet/wallets/wallet/wallet.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/private_key_interface.dart';
class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
with PrivateKeyInterface {
FrostWalletInfo get frostInfo => throw UnimplementedError();
late ElectrumXClient electrumXClient;
late CachedElectrumXClient electrumXCachedClient;
@override
int get isarTransactionVersion => 2;
BitcoinFrostWallet(CryptoCurrencyNetwork network)
: super(BitcoinFrost(network) as T);
@override
FilterOperation? get changeAddressFilterOperation => FilterGroup.and(
[
FilterCondition.equalTo(
property: r"type",
value: info.mainAddressType,
),
const FilterCondition.equalTo(
property: r"subType",
value: AddressSubType.change,
),
],
);
@override
FilterOperation? get receivingAddressFilterOperation => FilterGroup.and(
[
FilterCondition.equalTo(
property: r"type",
value: info.mainAddressType,
),
const FilterCondition.equalTo(
property: r"subType",
value: AddressSubType.receiving,
),
],
);
// Future<List<Address>> fetchAddressesForElectrumXScan() async {
// final allAddresses = await mainDB
// .getAddresses(walletId)
// .filter()
// .typeEqualTo(AddressType.frostMS)
// .and()
// .group(
// (q) => q
// .subTypeEqualTo(AddressSubType.receiving)
// .or()
// .subTypeEqualTo(AddressSubType.change),
// )
// .findAll();
// return allAddresses;
// }
@override
Future<void> updateTransactions() {
// TODO: implement updateTransactions
throw UnimplementedError();
}
int estimateTxFee({required int vSize, required int feeRatePerKB}) {
return vSize * (feeRatePerKB / 1000).ceil();
}
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
Future<void> checkSaveInitialReceivingAddress() {
// TODO: implement checkSaveInitialReceivingAddress
throw UnimplementedError();
}
@override
Future<TxData> confirmSend({required TxData txData}) {
// TODO: implement confirmSend
throw UnimplementedError();
}
@override
Future<Amount> estimateFeeFor(Amount amount, int feeRate) {
// TODO: implement estimateFeeFor
throw UnimplementedError();
}
@override
// TODO: implement fees
Future<FeeObject> get fees => throw UnimplementedError();
@override
Future<TxData> prepareSend({required TxData txData}) {
// TODO: implement prepareSendpu
throw UnimplementedError();
}
@override
Future<void> recover({
required bool isRescan,
String? serializedKeys,
String? multisigConfig,
}) async {
if (serializedKeys == null || multisigConfig == null) {
throw Exception(
"Failed to recover $runtimeType: "
"Missing serializedKeys and/or multisigConfig.",
);
}
try {
await refreshMutex.protect(() async {
if (!isRescan) {
final salt = frost
.multisigSalt(
multisigConfig: multisigConfig,
)
.toHex;
final knownSalts = frostInfo.knownSalts;
if (knownSalts.contains(salt)) {
throw Exception("Known frost multisig salt found!");
}
knownSalts.add(salt);
await frostInfo.updateKnownSalts(knownSalts, isar: mainDB.isar);
}
final keys = frost.deserializeKeys(keys: serializedKeys);
await _saveSerializedKeys(serializedKeys);
await _saveMultisigConfig(multisigConfig);
final addressString = frost.addressForKeys(
network: cryptoCurrency.network == CryptoCurrencyNetwork.main
? Network.Mainnet
: Network.Testnet,
keys: keys,
);
final publicKey = frost.scriptPubKeyForKeys(keys: keys);
final address = Address(
walletId: walletId,
value: addressString,
publicKey: publicKey.toUint8ListFromHex,
derivationIndex: 0,
derivationPath: null,
subType: AddressSubType.receiving,
type: AddressType.frostMS,
);
await mainDB.updateOrPutAddresses([address]);
});
unawaited(refresh());
} catch (e, s) {
Logging.instance.log(
"recoverFromSerializedKeys failed: $e\n$s",
level: LogLevel.Fatal,
);
rethrow;
}
}
@override
Future<void> updateBalance() async {
final utxos = await mainDB.getUTXOs(walletId).findAll();
final currentChainHeight = await chainHeight;
Amount satoshiBalanceTotal = Amount(
rawValue: BigInt.zero,
fractionDigits: cryptoCurrency.fractionDigits,
);
Amount satoshiBalancePending = Amount(
rawValue: BigInt.zero,
fractionDigits: cryptoCurrency.fractionDigits,
);
Amount satoshiBalanceSpendable = Amount(
rawValue: BigInt.zero,
fractionDigits: cryptoCurrency.fractionDigits,
);
Amount satoshiBalanceBlocked = Amount(
rawValue: BigInt.zero,
fractionDigits: cryptoCurrency.fractionDigits,
);
for (final utxo in utxos) {
final utxoAmount = Amount(
rawValue: BigInt.from(utxo.value),
fractionDigits: cryptoCurrency.fractionDigits,
);
satoshiBalanceTotal += utxoAmount;
if (utxo.isBlocked) {
satoshiBalanceBlocked += utxoAmount;
} else {
if (utxo.isConfirmed(
currentChainHeight,
cryptoCurrency.minConfirms,
)) {
satoshiBalanceSpendable += utxoAmount;
} else {
satoshiBalancePending += utxoAmount;
}
}
}
final balance = Balance(
total: satoshiBalanceTotal,
spendable: satoshiBalanceSpendable,
blockedTotal: satoshiBalanceBlocked,
pendingSpendable: satoshiBalancePending,
);
await info.updateBalance(newBalance: balance, isar: mainDB.isar);
}
@override
Future<void> updateChainHeight() async {
final int height;
try {
final result = await electrumXClient.getBlockHeadTip();
height = result["height"] as int;
} catch (e) {
rethrow;
}
await info.updateCachedChainHeight(
newHeight: height,
isar: mainDB.isar,
);
}
@override
Future<bool> pingCheck() async {
try {
final result = await electrumXClient.ping();
return result;
} catch (_) {
return false;
}
}
@override
Future<void> updateNode() async {
await _updateElectrumX();
}
@override
Future<bool> updateUTXOs() async {
final address = await getCurrentReceivingAddress();
try {
final scriptHash = cryptoCurrency.pubKeyToScriptHash(
pubKey: Uint8List.fromList(address!.publicKey),
);
final utxos = await electrumXClient.getUTXOs(scripthash: scriptHash);
final List<UTXO> outputArray = [];
for (int i = 0; i < utxos.length; i++) {
final utxo = await _parseUTXO(
jsonUTXO: utxos[i],
);
outputArray.add(utxo);
}
return await mainDB.updateUTXOs(walletId, outputArray);
} catch (e, s) {
Logging.instance.log(
"Output fetch unsuccessful: $e\n$s",
level: LogLevel.Error,
);
return false;
}
}
// =================== Secure storage ========================================
Future<String?> get getSerializedKeys async =>
await secureStorageInterface.read(
key: "{$walletId}_serializedFROSTKeys",
);
Future<void> _saveSerializedKeys(String keys) async {
final current = await getSerializedKeys;
if (current == null) {
// do nothing
} else if (current == keys) {
// should never occur
} else {
// save current as prev gen before updating current
await secureStorageInterface.write(
key: "{$walletId}_serializedFROSTKeysPrevGen",
value: current,
);
}
await secureStorageInterface.write(
key: "{$walletId}_serializedFROSTKeys",
value: keys,
);
}
Future<String?> get getSerializedKeysPrevGen async =>
await secureStorageInterface.read(
key: "{$walletId}_serializedFROSTKeysPrevGen",
);
Future<String?> get multisigConfig async => await secureStorageInterface.read(
key: "{$walletId}_multisigConfig",
);
Future<String?> get multisigConfigPrevGen async =>
await secureStorageInterface.read(
key: "{$walletId}_multisigConfigPrevGen",
);
Future<void> _saveMultisigConfig(String multisigConfig) async {
final current = await this.multisigConfig;
if (current == null) {
// do nothing
} else if (current == multisigConfig) {
// should never occur
} else {
// save current as prev gen before updating current
await secureStorageInterface.write(
key: "{$walletId}_multisigConfigPrevGen",
value: current,
);
}
await secureStorageInterface.write(
key: "{$walletId}_multisigConfig",
value: multisigConfig,
);
}
Future<Uint8List?> get multisigId async {
final id = await secureStorageInterface.read(
key: "{$walletId}_multisigIdFROST",
);
if (id == null) {
return null;
} else {
return id.toUint8ListFromHex;
}
}
Future<void> saveMultisigId(Uint8List id) async =>
await secureStorageInterface.write(
key: "{$walletId}_multisigIdFROST",
value: id.toHex,
);
Future<String?> get recoveryString async => await secureStorageInterface.read(
key: "{$walletId}_recoveryStringFROST",
);
Future<void> saveRecoveryString(String recoveryString) async =>
await secureStorageInterface.write(
key: "{$walletId}_recoveryStringFROST",
value: recoveryString,
);
// =================== Private ===============================================
Future<ElectrumXNode> _getCurrentElectrumXNode() async {
final node = getCurrentNode();
return ElectrumXNode(
address: node.host,
port: node.port,
name: node.name,
useSSL: node.useSSL,
id: node.id,
);
}
Future<void> _updateElectrumX() async {
final failovers = nodeService
.failoverNodesFor(coin: cryptoCurrency.coin)
.map((e) => ElectrumXNode(
address: e.host,
port: e.port,
name: e.name,
id: e.id,
useSSL: e.useSSL,
))
.toList();
final newNode = await _getCurrentElectrumXNode();
electrumXClient = ElectrumXClient.from(
node: newNode,
prefs: prefs,
failovers: failovers,
);
electrumXCachedClient = CachedElectrumXClient.from(
electrumXClient: electrumXClient,
);
}
Future<UTXO> _parseUTXO({
required Map<String, dynamic> jsonUTXO,
}) async {
final txn = await electrumXCachedClient.getTransaction(
txHash: jsonUTXO["tx_hash"] as String,
verbose: true,
coin: cryptoCurrency.coin,
);
final vout = jsonUTXO["tx_pos"] as int;
final outputs = txn["vout"] as List;
String? scriptPubKey;
String? utxoOwnerAddress;
// get UTXO owner address
for (final output in outputs) {
if (output["n"] == vout) {
scriptPubKey = output["scriptPubKey"]?["hex"] as String?;
utxoOwnerAddress =
output["scriptPubKey"]?["addresses"]?[0] as String? ??
output["scriptPubKey"]?["address"] as String?;
}
}
final utxo = UTXO(
walletId: walletId,
txid: txn["txid"] as String,
vout: vout,
value: jsonUTXO["value"] as int,
name: "",
isBlocked: false,
blockedReason: null,
isCoinbase: txn["is_coinbase"] as bool? ?? false,
blockHash: txn["blockhash"] as String?,
blockHeight: jsonUTXO["height"] as int?,
blockTime: txn["blocktime"] as int?,
address: utxoOwnerAddress,
);
return utxo;
}
}

View file

@ -25,6 +25,7 @@ import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
import 'package:stackwallet/wallets/isar/models/wallet_info.dart';
import 'package:stackwallet/wallets/models/tx_data.dart';
import 'package:stackwallet/wallets/wallet/impl/banano_wallet.dart';
import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
import 'package:stackwallet/wallets/wallet/impl/bitcoin_wallet.dart';
import 'package:stackwallet/wallets/wallet/impl/bitcoincash_wallet.dart';
import 'package:stackwallet/wallets/wallet/impl/dogecoin_wallet.dart';
@ -311,6 +312,11 @@ abstract class Wallet<T extends CryptoCurrency> {
case Coin.bitcoinTestNet:
return BitcoinWallet(CryptoCurrencyNetwork.test);
case Coin.bitcoinFrost:
return BitcoinFrostWallet(CryptoCurrencyNetwork.main);
case Coin.bitcoinFrostTestNet:
return BitcoinFrostWallet(CryptoCurrencyNetwork.test);
case Coin.bitcoincash:
return BitcoincashWallet(CryptoCurrencyNetwork.main);
case Coin.bitcoincashTestnet:

View file

@ -832,7 +832,7 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
}
}
Future<ElectrumXNode> getCurrentElectrumXNode() async {
Future<ElectrumXNode> _getCurrentElectrumXNode() async {
final node = getCurrentNode();
return ElectrumXNode(
@ -844,7 +844,7 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
);
}
Future<void> updateElectrumX({required ElectrumXNode newNode}) async {
Future<void> updateElectrumX() async {
final failovers = nodeService
.failoverNodesFor(coin: cryptoCurrency.coin)
.map((e) => ElectrumXNode(
@ -856,7 +856,7 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
))
.toList();
final newNode = await getCurrentElectrumXNode();
final newNode = await _getCurrentElectrumXNode();
electrumXClient = ElectrumXClient.from(
node: newNode,
prefs: prefs,
@ -1160,8 +1160,7 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
@override
Future<void> updateNode() async {
final node = await getCurrentElectrumXNode();
await updateElectrumX(newNode: node);
await updateElectrumX();
}
FeeObject? _cachedFees;

View file

@ -169,6 +169,8 @@ class _NodeCardState extends ConsumerState<NodeCard> {
case Coin.namecoin:
case Coin.bitcoincashTestnet:
case Coin.eCash:
case Coin.bitcoinFrost:
case Coin.bitcoinFrostTestNet:
final client = ElectrumXClient(
host: node.host,
port: node.port,

View file

@ -151,6 +151,8 @@ class NodeOptionsSheet extends ConsumerWidget {
case Coin.namecoin:
case Coin.bitcoincashTestnet:
case Coin.eCash:
case Coin.bitcoinFrost:
case Coin.bitcoinFrostTestNet:
final client = ElectrumXClient(
host: node.host,
port: node.port,