mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-17 01:37:54 +00:00
WIP very rough refactoring wip
This commit is contained in:
parent
a0a653b088
commit
f30785616b
22 changed files with 1682 additions and 315 deletions
|
@ -26,12 +26,13 @@ class DB {
|
|||
@Deprecated("Left over for migration from old versions of Stack Wallet")
|
||||
static const String boxNameAddressBook = "addressBook";
|
||||
static const String boxNameTrades = "exchangeTransactionsBox";
|
||||
static const String boxNameAllWalletsData = "wallets";
|
||||
static const String boxNameFavoriteWallets = "favoriteWallets";
|
||||
|
||||
// in use
|
||||
// TODO: migrate
|
||||
static const String boxNameNodeModels = "nodeModels";
|
||||
static const String boxNamePrimaryNodes = "primaryNodes";
|
||||
static const String boxNameAllWalletsData = "wallets";
|
||||
static const String boxNameNotifications = "notificationModels";
|
||||
static const String boxNameWatchedTransactions =
|
||||
"watchedTxNotificationModels";
|
||||
|
@ -39,7 +40,6 @@ class DB {
|
|||
static const String boxNameTradesV2 = "exchangeTradesBox";
|
||||
static const String boxNameTradeNotes = "tradeNotesBox";
|
||||
static const String boxNameTradeLookup = "tradeToTxidLookUpBox";
|
||||
static const String boxNameFavoriteWallets = "favoriteWallets";
|
||||
static const String boxNameWalletsToDeleteOnStart = "walletsToDeleteOnStart";
|
||||
static const String boxNamePriceCache = "priceAPIPrice24hCache";
|
||||
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
import 'package:coinlib/coinlib.dart' as coinlib;
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
|
||||
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
|
||||
import 'package:stackwallet/wallets/coin/crypto_currency.dart';
|
||||
|
||||
abstract class Bip39HDCurrency extends CryptoCurrency {
|
||||
Bip39HDCurrency(super.network);
|
||||
|
||||
coinlib.NetworkParams get networkParams;
|
||||
|
||||
String constructDerivePath({
|
||||
required DerivePathType derivePathType,
|
||||
int account = 0,
|
||||
required int chain,
|
||||
required int index,
|
||||
});
|
||||
|
||||
({coinlib.Address address, AddressType addressType}) getAddressForPublicKey({
|
||||
required coinlib.ECPublicKey publicKey,
|
||||
required DerivePathType derivePathType,
|
||||
});
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
import 'package:coinlib/coinlib.dart';
|
||||
|
||||
abstract class CoinParams {
|
||||
static const bitcoin = BitcoinParams();
|
||||
}
|
||||
|
||||
class BitcoinParams {
|
||||
const BitcoinParams();
|
||||
|
||||
final NetworkParams mainNet = const NetworkParams(
|
||||
wifPrefix: 0x80,
|
||||
p2pkhPrefix: 0x00,
|
||||
p2shPrefix: 0x05,
|
||||
privHDPrefix: 0x0488ade4,
|
||||
pubHDPrefix: 0x0488b21e,
|
||||
bech32Hrp: "bc",
|
||||
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||
);
|
||||
|
||||
final NetworkParams testNet = const NetworkParams(
|
||||
wifPrefix: 0xef,
|
||||
p2pkhPrefix: 0x6f,
|
||||
p2shPrefix: 0xc4,
|
||||
privHDPrefix: 0x04358394,
|
||||
pubHDPrefix: 0x043587cf,
|
||||
bech32Hrp: "tb",
|
||||
messagePrefix: "\x18Bitcoin Signed Message:\n",
|
||||
);
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
||||
enum CryptoCurrencyNetwork {
|
||||
main,
|
||||
test,
|
||||
stage;
|
||||
}
|
||||
|
||||
abstract class CryptoCurrency {
|
||||
@Deprecated("Should eventually move away from Coin enum")
|
||||
late final Coin coin;
|
||||
|
||||
final CryptoCurrencyNetwork network;
|
||||
|
||||
CryptoCurrency(this.network);
|
||||
}
|
5
lib/wallets/crypto_currency/bip39_currency.dart
Normal file
5
lib/wallets/crypto_currency/bip39_currency.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
||||
|
||||
abstract class Bip39Currency extends CryptoCurrency {
|
||||
Bip39Currency(super.network);
|
||||
}
|
51
lib/wallets/crypto_currency/bip39_hd_currency.dart
Normal file
51
lib/wallets/crypto_currency/bip39_hd_currency.dart
Normal file
|
@ -0,0 +1,51 @@
|
|||
import 'package:coinlib/coinlib.dart' as coinlib;
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/bip39_currency.dart';
|
||||
|
||||
abstract class Bip39HDCurrency extends Bip39Currency {
|
||||
Bip39HDCurrency(super.network);
|
||||
|
||||
coinlib.NetworkParams get networkParams;
|
||||
|
||||
Amount get dustLimit;
|
||||
|
||||
String constructDerivePath({
|
||||
required DerivePathType derivePathType,
|
||||
int account = 0,
|
||||
required int chain,
|
||||
required int index,
|
||||
});
|
||||
|
||||
({coinlib.Address address, AddressType addressType}) getAddressForPublicKey({
|
||||
required coinlib.ECPublicKey publicKey,
|
||||
required DerivePathType derivePathType,
|
||||
});
|
||||
|
||||
String addressToScriptHash({required String address}) {
|
||||
try {
|
||||
final addr = coinlib.Address.fromString(address, networkParams);
|
||||
return convertBytesToScriptHash(addr.program.script.compiled);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
static String convertBytesToScriptHash(Uint8List bytes) {
|
||||
final hash = sha256.convert(bytes.toList(growable: false)).toString();
|
||||
|
||||
final chars = hash.split("");
|
||||
final List<String> reversedPairs = [];
|
||||
// TODO find a better/faster way to do this?
|
||||
int i = chars.length - 1;
|
||||
while (i > 0) {
|
||||
reversedPairs.add(chars[i - 1]);
|
||||
reversedPairs.add(chars[i]);
|
||||
i -= 2;
|
||||
}
|
||||
return reversedPairs.join("");
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
import 'package:coinlib/coinlib.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/coin/bip39_hd_currency.dart';
|
||||
import 'package:stackwallet/wallets/coin/coin_params.dart';
|
||||
import 'package:stackwallet/wallets/coin/crypto_currency.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/bip39_hd_currency.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
||||
|
||||
class Bitcoin extends Bip39HDCurrency {
|
||||
Bitcoin(super.network) {
|
||||
|
@ -18,13 +18,40 @@ class Bitcoin extends Bip39HDCurrency {
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Amount get dustLimit => Amount(
|
||||
rawValue: BigInt.from(294),
|
||||
fractionDigits: fractionDigits,
|
||||
);
|
||||
|
||||
Amount get dustLimitP2PKH => Amount(
|
||||
rawValue: BigInt.from(546),
|
||||
fractionDigits: fractionDigits,
|
||||
);
|
||||
|
||||
@override
|
||||
coinlib.NetworkParams get networkParams {
|
||||
switch (network) {
|
||||
case CryptoCurrencyNetwork.main:
|
||||
return CoinParams.bitcoin.mainNet;
|
||||
return const coinlib.NetworkParams(
|
||||
wifPrefix: 0x80,
|
||||
p2pkhPrefix: 0x00,
|
||||
p2shPrefix: 0x05,
|
||||
privHDPrefix: 0x0488ade4,
|
||||
pubHDPrefix: 0x0488b21e,
|
||||
bech32Hrp: "bc",
|
||||
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||
);
|
||||
case CryptoCurrencyNetwork.test:
|
||||
return CoinParams.bitcoin.testNet;
|
||||
return const coinlib.NetworkParams(
|
||||
wifPrefix: 0xef,
|
||||
p2pkhPrefix: 0x6f,
|
||||
p2shPrefix: 0xc4,
|
||||
privHDPrefix: 0x04358394,
|
||||
pubHDPrefix: 0x043587cf,
|
||||
bech32Hrp: "tb",
|
||||
messagePrefix: "\x18Bitcoin Signed Message:\n",
|
||||
);
|
||||
default:
|
||||
throw Exception("Unsupported network: $network");
|
||||
}
|
||||
|
@ -39,13 +66,15 @@ class Bitcoin extends Bip39HDCurrency {
|
|||
}) {
|
||||
String coinType;
|
||||
|
||||
if (networkParams.wifPrefix == CoinParams.bitcoin.mainNet.wifPrefix) {
|
||||
coinType = "0"; // btc mainnet
|
||||
} else if (networkParams.wifPrefix ==
|
||||
CoinParams.bitcoin.testNet.wifPrefix) {
|
||||
coinType = "1"; // btc testnet
|
||||
} else {
|
||||
throw Exception("Invalid Bitcoin network wif used!");
|
||||
switch (networkParams.wifPrefix) {
|
||||
case 0x80:
|
||||
coinType = "0"; // btc mainnet
|
||||
break;
|
||||
case 0xef:
|
||||
coinType = "1"; // btc testnet
|
||||
break;
|
||||
default:
|
||||
throw Exception("Invalid Bitcoin network wif used!");
|
||||
}
|
||||
|
||||
int purpose;
|
||||
|
@ -66,6 +95,7 @@ class Bitcoin extends Bip39HDCurrency {
|
|||
return "m/$purpose'/$coinType'/$account'/$chain/$index";
|
||||
}
|
||||
|
||||
@override
|
||||
({coinlib.Address address, AddressType addressType}) getAddressForPublicKey({
|
||||
required coinlib.ECPublicKey publicKey,
|
||||
required DerivePathType derivePathType,
|
||||
|
@ -112,4 +142,14 @@ class Bitcoin extends Bip39HDCurrency {
|
|||
throw Exception("DerivePathType $derivePathType not supported");
|
||||
}
|
||||
}
|
||||
|
||||
@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) {
|
||||
// TODO: implement validateAddress
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
44
lib/wallets/crypto_currency/coins/epiccash.dart
Normal file
44
lib/wallets/crypto_currency/coins/epiccash.dart
Normal file
|
@ -0,0 +1,44 @@
|
|||
import 'package:flutter_libepiccash/epic_cash.dart' as lib_epiccash;
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/bip39_currency.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
||||
|
||||
class Epiccash extends Bip39Currency {
|
||||
Epiccash(super.network) {
|
||||
switch (network) {
|
||||
case CryptoCurrencyNetwork.main:
|
||||
coin = Coin.epicCash;
|
||||
default:
|
||||
throw Exception("Unsupported network: $network");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
// change this to change the number of confirms a tx needs in order to show as confirmed
|
||||
int get minConfirms => 3;
|
||||
|
||||
@override
|
||||
bool validateAddress(String address) {
|
||||
// Invalid address that contains HTTP and epicbox domain
|
||||
if ((address.startsWith("http://") || address.startsWith("https://")) &&
|
||||
address.contains("@")) {
|
||||
return false;
|
||||
}
|
||||
if (address.startsWith("http://") || address.startsWith("https://")) {
|
||||
if (Uri.tryParse(address) != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
final String validate = lib_epiccash.validateSendAddress(address);
|
||||
if (int.parse(validate) == 1) {
|
||||
// Check if address contains a domain
|
||||
if (address.contains("@")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
25
lib/wallets/crypto_currency/crypto_currency.dart
Normal file
25
lib/wallets/crypto_currency/crypto_currency.dart
Normal file
|
@ -0,0 +1,25 @@
|
|||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
||||
enum CryptoCurrencyNetwork {
|
||||
main,
|
||||
test,
|
||||
stage;
|
||||
}
|
||||
|
||||
abstract class CryptoCurrency {
|
||||
@Deprecated("[prio=low] Should eventually move away from Coin enum")
|
||||
late final Coin coin;
|
||||
|
||||
final CryptoCurrencyNetwork network;
|
||||
|
||||
CryptoCurrency(this.network);
|
||||
|
||||
// TODO: [prio=low] require these be overridden in concrete implementations to remove reliance on [coin]
|
||||
int get fractionDigits => coin.decimals;
|
||||
BigInt get satsPerCoin => Constants.satsPerCoin(coin);
|
||||
|
||||
int get minConfirms;
|
||||
|
||||
bool validateAddress(String address);
|
||||
}
|
|
@ -7,7 +7,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
|||
|
||||
part 'wallet_info.g.dart';
|
||||
|
||||
@Collection(accessor: "walletInfo")
|
||||
@Collection(accessor: "walletInfo", inheritance: false)
|
||||
class WalletInfo {
|
||||
Id id = Isar.autoIncrement;
|
||||
|
||||
|
@ -30,10 +30,11 @@ class WalletInfo {
|
|||
// Only exposed for isar to avoid dealing with storing enums as Coin can change
|
||||
final String coinName;
|
||||
|
||||
final bool isFavourite;
|
||||
|
||||
/// User set favourites ordering. No restrictions are placed on uniqueness.
|
||||
/// Reordering logic in the ui code should ensure this is unique.
|
||||
///
|
||||
/// Also represents if the wallet is a favourite. Any number greater then -1
|
||||
/// denotes a favourite. Any number less than 0 means it is not a favourite.
|
||||
final int favouriteOrderIndex;
|
||||
|
||||
/// Wallets without this flag set to true should be deleted on next app run
|
||||
|
@ -43,13 +44,37 @@ class WalletInfo {
|
|||
/// The highest block height the wallet has scanned.
|
||||
final int cachedChainHeight;
|
||||
|
||||
/// Wallet creation chain height. Applies to select coin only.
|
||||
final int creationHeight;
|
||||
// TODO: store these in other data s
|
||||
// Should contain specific things based on certain coins only
|
||||
|
||||
/// Wallet restore chain height. Applies to select coin only.
|
||||
final int restoreHeight;
|
||||
// /// Wallet creation chain height. Applies to select coin only.
|
||||
// final int creationHeight;
|
||||
//
|
||||
// /// Wallet restore chain height. Applies to select coin only.
|
||||
// final int restoreHeight;
|
||||
|
||||
final String? otherDataJsonString;
|
||||
|
||||
//============================================================================
|
||||
//=============== Getters ====================================================
|
||||
|
||||
bool get isFavourite => favouriteOrderIndex > -1;
|
||||
|
||||
List<String> get tokenContractAddresses =>
|
||||
otherData[WalletInfoKeys.tokenContractAddresses] as List<String>? ?? [];
|
||||
|
||||
/// Special case for coins such as firo
|
||||
@ignore
|
||||
Balance get cachedSecondaryBalance {
|
||||
try {
|
||||
return Balance.fromJson(
|
||||
otherData[WalletInfoKeys.cachedSecondaryBalance] as String? ?? "",
|
||||
coin.decimals,
|
||||
);
|
||||
} catch (_) {
|
||||
return Balance.zeroForCoin(coin: coin);
|
||||
}
|
||||
}
|
||||
|
||||
@ignore
|
||||
Coin get coin => Coin.values.byName(coinName);
|
||||
|
@ -63,19 +88,24 @@ class WalletInfo {
|
|||
}
|
||||
}
|
||||
|
||||
@ignore
|
||||
Map<String, dynamic> get otherData => otherDataJsonString == null
|
||||
? {}
|
||||
: Map<String, dynamic>.from(jsonDecode(otherDataJsonString!) as Map);
|
||||
|
||||
//============================================================================
|
||||
|
||||
WalletInfo({
|
||||
required this.coinName,
|
||||
required this.walletId,
|
||||
required this.name,
|
||||
required this.walletType,
|
||||
required this.mainAddressType,
|
||||
this.isFavourite = false,
|
||||
this.favouriteOrderIndex = 0,
|
||||
this.cachedChainHeight = 0,
|
||||
this.creationHeight = 0,
|
||||
this.restoreHeight = 0,
|
||||
this.isMnemonicVerified = false,
|
||||
this.cachedBalanceString,
|
||||
this.otherDataJsonString,
|
||||
}) : assert(
|
||||
Coin.values.map((e) => e.name).contains(coinName),
|
||||
);
|
||||
|
@ -83,13 +113,11 @@ class WalletInfo {
|
|||
WalletInfo copyWith({
|
||||
String? coinName,
|
||||
String? name,
|
||||
bool? isFavourite,
|
||||
int? favouriteOrderIndex,
|
||||
int? cachedChainHeight,
|
||||
int? creationHeight,
|
||||
int? restoreHeight,
|
||||
bool? isMnemonicVerified,
|
||||
String? cachedBalanceString,
|
||||
Map<String, dynamic>? otherData,
|
||||
}) {
|
||||
return WalletInfo(
|
||||
coinName: coinName ?? this.coinName,
|
||||
|
@ -97,13 +125,12 @@ class WalletInfo {
|
|||
name: name ?? this.name,
|
||||
walletType: walletType,
|
||||
mainAddressType: mainAddressType,
|
||||
isFavourite: isFavourite ?? this.isFavourite,
|
||||
favouriteOrderIndex: favouriteOrderIndex ?? this.favouriteOrderIndex,
|
||||
cachedChainHeight: cachedChainHeight ?? this.cachedChainHeight,
|
||||
creationHeight: creationHeight ?? this.creationHeight,
|
||||
restoreHeight: restoreHeight ?? this.restoreHeight,
|
||||
isMnemonicVerified: isMnemonicVerified ?? this.isMnemonicVerified,
|
||||
cachedBalanceString: cachedBalanceString ?? this.cachedBalanceString,
|
||||
otherDataJsonString:
|
||||
otherData == null ? otherDataJsonString : jsonEncode(otherData),
|
||||
)..id = id;
|
||||
}
|
||||
|
||||
|
@ -140,10 +167,17 @@ class WalletInfo {
|
|||
}
|
||||
}
|
||||
|
||||
abstract class WalletInfoKeys {
|
||||
static const String tokenContractAddresses = "tokenContractAddressesKey";
|
||||
static const String cachedSecondaryBalance = "cachedSecondaryBalanceKey";
|
||||
static const String epiccashData = "epiccashDataKey";
|
||||
}
|
||||
|
||||
// Used in Isar db and stored there as int indexes so adding/removing values
|
||||
// in this definition should be done extremely carefully in production
|
||||
enum WalletType {
|
||||
bip39,
|
||||
bip39HD,
|
||||
cryptonote,
|
||||
privateKeyBased;
|
||||
}
|
||||
|
|
|
@ -32,41 +32,41 @@ const WalletInfoSchema = CollectionSchema(
|
|||
name: r'coinName',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'creationHeight': PropertySchema(
|
||||
id: 3,
|
||||
name: r'creationHeight',
|
||||
type: IsarType.long,
|
||||
),
|
||||
r'favouriteOrderIndex': PropertySchema(
|
||||
id: 4,
|
||||
id: 3,
|
||||
name: r'favouriteOrderIndex',
|
||||
type: IsarType.long,
|
||||
),
|
||||
r'isFavourite': PropertySchema(
|
||||
id: 5,
|
||||
id: 4,
|
||||
name: r'isFavourite',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'isMnemonicVerified': PropertySchema(
|
||||
id: 6,
|
||||
id: 5,
|
||||
name: r'isMnemonicVerified',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'mainAddressType': PropertySchema(
|
||||
id: 7,
|
||||
id: 6,
|
||||
name: r'mainAddressType',
|
||||
type: IsarType.byte,
|
||||
enumMap: _WalletInfomainAddressTypeEnumValueMap,
|
||||
),
|
||||
r'name': PropertySchema(
|
||||
id: 8,
|
||||
id: 7,
|
||||
name: r'name',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'restoreHeight': PropertySchema(
|
||||
r'otherDataJsonString': PropertySchema(
|
||||
id: 8,
|
||||
name: r'otherDataJsonString',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'tokenContractAddresses': PropertySchema(
|
||||
id: 9,
|
||||
name: r'restoreHeight',
|
||||
type: IsarType.long,
|
||||
name: r'tokenContractAddresses',
|
||||
type: IsarType.stringList,
|
||||
),
|
||||
r'walletId': PropertySchema(
|
||||
id: 10,
|
||||
|
@ -122,6 +122,19 @@ int _walletInfoEstimateSize(
|
|||
}
|
||||
bytesCount += 3 + object.coinName.length * 3;
|
||||
bytesCount += 3 + object.name.length * 3;
|
||||
{
|
||||
final value = object.otherDataJsonString;
|
||||
if (value != null) {
|
||||
bytesCount += 3 + value.length * 3;
|
||||
}
|
||||
}
|
||||
bytesCount += 3 + object.tokenContractAddresses.length * 3;
|
||||
{
|
||||
for (var i = 0; i < object.tokenContractAddresses.length; i++) {
|
||||
final value = object.tokenContractAddresses[i];
|
||||
bytesCount += value.length * 3;
|
||||
}
|
||||
}
|
||||
bytesCount += 3 + object.walletId.length * 3;
|
||||
return bytesCount;
|
||||
}
|
||||
|
@ -135,13 +148,13 @@ void _walletInfoSerialize(
|
|||
writer.writeString(offsets[0], object.cachedBalanceString);
|
||||
writer.writeLong(offsets[1], object.cachedChainHeight);
|
||||
writer.writeString(offsets[2], object.coinName);
|
||||
writer.writeLong(offsets[3], object.creationHeight);
|
||||
writer.writeLong(offsets[4], object.favouriteOrderIndex);
|
||||
writer.writeBool(offsets[5], object.isFavourite);
|
||||
writer.writeBool(offsets[6], object.isMnemonicVerified);
|
||||
writer.writeByte(offsets[7], object.mainAddressType.index);
|
||||
writer.writeString(offsets[8], object.name);
|
||||
writer.writeLong(offsets[9], object.restoreHeight);
|
||||
writer.writeLong(offsets[3], object.favouriteOrderIndex);
|
||||
writer.writeBool(offsets[4], object.isFavourite);
|
||||
writer.writeBool(offsets[5], object.isMnemonicVerified);
|
||||
writer.writeByte(offsets[6], object.mainAddressType.index);
|
||||
writer.writeString(offsets[7], object.name);
|
||||
writer.writeString(offsets[8], object.otherDataJsonString);
|
||||
writer.writeStringList(offsets[9], object.tokenContractAddresses);
|
||||
writer.writeString(offsets[10], object.walletId);
|
||||
writer.writeByte(offsets[11], object.walletType.index);
|
||||
}
|
||||
|
@ -156,15 +169,13 @@ WalletInfo _walletInfoDeserialize(
|
|||
cachedBalanceString: reader.readStringOrNull(offsets[0]),
|
||||
cachedChainHeight: reader.readLongOrNull(offsets[1]) ?? 0,
|
||||
coinName: reader.readString(offsets[2]),
|
||||
creationHeight: reader.readLongOrNull(offsets[3]) ?? 0,
|
||||
favouriteOrderIndex: reader.readLongOrNull(offsets[4]) ?? 0,
|
||||
isFavourite: reader.readBoolOrNull(offsets[5]) ?? false,
|
||||
isMnemonicVerified: reader.readBoolOrNull(offsets[6]) ?? false,
|
||||
favouriteOrderIndex: reader.readLongOrNull(offsets[3]) ?? 0,
|
||||
isMnemonicVerified: reader.readBoolOrNull(offsets[5]) ?? false,
|
||||
mainAddressType: _WalletInfomainAddressTypeValueEnumMap[
|
||||
reader.readByteOrNull(offsets[7])] ??
|
||||
reader.readByteOrNull(offsets[6])] ??
|
||||
AddressType.p2pkh,
|
||||
name: reader.readString(offsets[8]),
|
||||
restoreHeight: reader.readLongOrNull(offsets[9]) ?? 0,
|
||||
name: reader.readString(offsets[7]),
|
||||
otherDataJsonString: reader.readStringOrNull(offsets[8]),
|
||||
walletId: reader.readString(offsets[10]),
|
||||
walletType:
|
||||
_WalletInfowalletTypeValueEnumMap[reader.readByteOrNull(offsets[11])] ??
|
||||
|
@ -190,19 +201,19 @@ P _walletInfoDeserializeProp<P>(
|
|||
case 3:
|
||||
return (reader.readLongOrNull(offset) ?? 0) as P;
|
||||
case 4:
|
||||
return (reader.readLongOrNull(offset) ?? 0) as P;
|
||||
return (reader.readBool(offset)) as P;
|
||||
case 5:
|
||||
return (reader.readBoolOrNull(offset) ?? false) as P;
|
||||
case 6:
|
||||
return (reader.readBoolOrNull(offset) ?? false) as P;
|
||||
case 7:
|
||||
return (_WalletInfomainAddressTypeValueEnumMap[
|
||||
reader.readByteOrNull(offset)] ??
|
||||
AddressType.p2pkh) as P;
|
||||
case 8:
|
||||
case 7:
|
||||
return (reader.readString(offset)) as P;
|
||||
case 8:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 9:
|
||||
return (reader.readLongOrNull(offset) ?? 0) as P;
|
||||
return (reader.readStringList(offset) ?? []) as P;
|
||||
case 10:
|
||||
return (reader.readString(offset)) as P;
|
||||
case 11:
|
||||
|
@ -240,13 +251,15 @@ const _WalletInfomainAddressTypeValueEnumMap = {
|
|||
};
|
||||
const _WalletInfowalletTypeEnumValueMap = {
|
||||
'bip39': 0,
|
||||
'cryptonote': 1,
|
||||
'privateKeyBased': 2,
|
||||
'bip39HD': 1,
|
||||
'cryptonote': 2,
|
||||
'privateKeyBased': 3,
|
||||
};
|
||||
const _WalletInfowalletTypeValueEnumMap = {
|
||||
0: WalletType.bip39,
|
||||
1: WalletType.cryptonote,
|
||||
2: WalletType.privateKeyBased,
|
||||
1: WalletType.bip39HD,
|
||||
2: WalletType.cryptonote,
|
||||
3: WalletType.privateKeyBased,
|
||||
};
|
||||
|
||||
Id _walletInfoGetId(WalletInfo object) {
|
||||
|
@ -784,62 +797,6 @@ extension WalletInfoQueryFilter
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
creationHeightEqualTo(int value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'creationHeight',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
creationHeightGreaterThan(
|
||||
int value, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'creationHeight',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
creationHeightLessThan(
|
||||
int value, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'creationHeight',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
creationHeightBetween(
|
||||
int lower,
|
||||
int upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.between(
|
||||
property: r'creationHeight',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
favouriteOrderIndexEqualTo(int value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
|
@ -1156,58 +1113,383 @@ extension WalletInfoQueryFilter
|
|||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
restoreHeightEqualTo(int value) {
|
||||
otherDataJsonStringIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'restoreHeight',
|
||||
value: value,
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
property: r'otherDataJsonString',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
restoreHeightGreaterThan(
|
||||
int value, {
|
||||
otherDataJsonStringIsNotNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||
property: r'otherDataJsonString',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
otherDataJsonStringEqualTo(
|
||||
String? value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'otherDataJsonString',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
otherDataJsonStringGreaterThan(
|
||||
String? value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'restoreHeight',
|
||||
property: r'otherDataJsonString',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
restoreHeightLessThan(
|
||||
int value, {
|
||||
otherDataJsonStringLessThan(
|
||||
String? value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'restoreHeight',
|
||||
property: r'otherDataJsonString',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
restoreHeightBetween(
|
||||
otherDataJsonStringBetween(
|
||||
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'otherDataJsonString',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
otherDataJsonStringStartsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.startsWith(
|
||||
property: r'otherDataJsonString',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
otherDataJsonStringEndsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.endsWith(
|
||||
property: r'otherDataJsonString',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
otherDataJsonStringContains(String value, {bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.contains(
|
||||
property: r'otherDataJsonString',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
otherDataJsonStringMatches(String pattern, {bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.matches(
|
||||
property: r'otherDataJsonString',
|
||||
wildcard: pattern,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
otherDataJsonStringIsEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'otherDataJsonString',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
otherDataJsonStringIsNotEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
property: r'otherDataJsonString',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
tokenContractAddressesElementEqualTo(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'tokenContractAddresses',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
tokenContractAddressesElementGreaterThan(
|
||||
String value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'tokenContractAddresses',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
tokenContractAddressesElementLessThan(
|
||||
String value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'tokenContractAddresses',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
tokenContractAddressesElementBetween(
|
||||
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'tokenContractAddresses',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
tokenContractAddressesElementStartsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.startsWith(
|
||||
property: r'tokenContractAddresses',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
tokenContractAddressesElementEndsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.endsWith(
|
||||
property: r'tokenContractAddresses',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
tokenContractAddressesElementContains(String value,
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.contains(
|
||||
property: r'tokenContractAddresses',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
tokenContractAddressesElementMatches(String pattern,
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.matches(
|
||||
property: r'tokenContractAddresses',
|
||||
wildcard: pattern,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
tokenContractAddressesElementIsEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'tokenContractAddresses',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
tokenContractAddressesElementIsNotEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
property: r'tokenContractAddresses',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
tokenContractAddressesLengthEqualTo(int length) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.listLength(
|
||||
r'tokenContractAddresses',
|
||||
length,
|
||||
true,
|
||||
length,
|
||||
true,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
tokenContractAddressesIsEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.listLength(
|
||||
r'tokenContractAddresses',
|
||||
0,
|
||||
true,
|
||||
0,
|
||||
true,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
tokenContractAddressesIsNotEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.listLength(
|
||||
r'tokenContractAddresses',
|
||||
0,
|
||||
false,
|
||||
999999,
|
||||
true,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
tokenContractAddressesLengthLessThan(
|
||||
int length, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.listLength(
|
||||
r'tokenContractAddresses',
|
||||
0,
|
||||
true,
|
||||
length,
|
||||
include,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
tokenContractAddressesLengthGreaterThan(
|
||||
int length, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.listLength(
|
||||
r'tokenContractAddresses',
|
||||
length,
|
||||
include,
|
||||
999999,
|
||||
true,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterFilterCondition>
|
||||
tokenContractAddressesLengthBetween(
|
||||
int lower,
|
||||
int upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.between(
|
||||
property: r'restoreHeight',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
));
|
||||
return query.listLength(
|
||||
r'tokenContractAddresses',
|
||||
lower,
|
||||
includeLower,
|
||||
upper,
|
||||
includeUpper,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1448,19 +1730,6 @@ extension WalletInfoQuerySortBy
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterSortBy> sortByCreationHeight() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'creationHeight', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterSortBy>
|
||||
sortByCreationHeightDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'creationHeight', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterSortBy>
|
||||
sortByFavouriteOrderIndex() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
|
@ -1526,15 +1795,17 @@ extension WalletInfoQuerySortBy
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterSortBy> sortByRestoreHeight() {
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterSortBy>
|
||||
sortByOtherDataJsonString() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'restoreHeight', Sort.asc);
|
||||
return query.addSortBy(r'otherDataJsonString', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterSortBy> sortByRestoreHeightDesc() {
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterSortBy>
|
||||
sortByOtherDataJsonStringDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'restoreHeight', Sort.desc);
|
||||
return query.addSortBy(r'otherDataJsonString', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1604,19 +1875,6 @@ extension WalletInfoQuerySortThenBy
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterSortBy> thenByCreationHeight() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'creationHeight', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterSortBy>
|
||||
thenByCreationHeightDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'creationHeight', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterSortBy>
|
||||
thenByFavouriteOrderIndex() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
|
@ -1694,15 +1952,17 @@ extension WalletInfoQuerySortThenBy
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterSortBy> thenByRestoreHeight() {
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterSortBy>
|
||||
thenByOtherDataJsonString() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'restoreHeight', Sort.asc);
|
||||
return query.addSortBy(r'otherDataJsonString', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterSortBy> thenByRestoreHeightDesc() {
|
||||
QueryBuilder<WalletInfo, WalletInfo, QAfterSortBy>
|
||||
thenByOtherDataJsonStringDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'restoreHeight', Sort.desc);
|
||||
return query.addSortBy(r'otherDataJsonString', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1755,12 +2015,6 @@ extension WalletInfoQueryWhereDistinct
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QDistinct> distinctByCreationHeight() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'creationHeight');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QDistinct>
|
||||
distinctByFavouriteOrderIndex() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
|
@ -1794,9 +2048,18 @@ extension WalletInfoQueryWhereDistinct
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QDistinct> distinctByRestoreHeight() {
|
||||
QueryBuilder<WalletInfo, WalletInfo, QDistinct> distinctByOtherDataJsonString(
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'restoreHeight');
|
||||
return query.addDistinctBy(r'otherDataJsonString',
|
||||
caseSensitive: caseSensitive);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, WalletInfo, QDistinct>
|
||||
distinctByTokenContractAddresses() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'tokenContractAddresses');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1841,12 +2104,6 @@ extension WalletInfoQueryProperty
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, int, QQueryOperations> creationHeightProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'creationHeight');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, int, QQueryOperations>
|
||||
favouriteOrderIndexProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
|
@ -1880,9 +2137,17 @@ extension WalletInfoQueryProperty
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, int, QQueryOperations> restoreHeightProperty() {
|
||||
QueryBuilder<WalletInfo, String?, QQueryOperations>
|
||||
otherDataJsonStringProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'restoreHeight');
|
||||
return query.addPropertyName(r'otherDataJsonString');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<WalletInfo, List<String>, QQueryOperations>
|
||||
tokenContractAddressesProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'tokenContractAddresses');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
214
lib/wallets/migration/migrate_wallets.dart
Normal file
214
lib/wallets/migration/migrate_wallets.dart
Normal file
|
@ -0,0 +1,214 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:stackwallet/db/hive/db.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||
import 'package:stackwallet/wallets/isar_models/wallet_info.dart';
|
||||
import 'package:stackwallet/wallets/wallet/supporting/epiccash_wallet_info_extension.dart';
|
||||
|
||||
void migrateWallets({
|
||||
required SecureStorageInterface secureStore,
|
||||
}) async {
|
||||
final allWalletsBox = await Hive.openBox<dynamic>(DB.boxNameAllWalletsData);
|
||||
|
||||
final names = DB.instance
|
||||
.get<dynamic>(boxName: DB.boxNameAllWalletsData, key: 'names') as Map?;
|
||||
|
||||
if (names == null) {
|
||||
// no wallets to migrate
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Parse the old data from the Hive map into a nice list
|
||||
//
|
||||
final List<
|
||||
({
|
||||
Coin coin,
|
||||
String name,
|
||||
String walletId,
|
||||
})> oldInfo = Map<String, dynamic>.from(names).values.map((e) {
|
||||
final map = e as Map;
|
||||
return (
|
||||
coin: Coin.values.byName(map["coin"] as String),
|
||||
walletId: map["id"] as String,
|
||||
name: map["name"] as String,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
//
|
||||
// Get current ordered list of favourite wallet Ids
|
||||
//
|
||||
final List<String> favourites =
|
||||
(await Hive.openBox<String>(DB.boxNameFavoriteWallets)).values.toList();
|
||||
|
||||
final List<WalletInfo> newInfo = [];
|
||||
|
||||
//
|
||||
// Convert each old info into the new Isar WalletInfo
|
||||
//
|
||||
for (final old in oldInfo) {
|
||||
final walletBox = await Hive.openBox<dynamic>(old.walletId);
|
||||
|
||||
//
|
||||
// Set other data values
|
||||
//
|
||||
Map<String, dynamic> otherData = {};
|
||||
|
||||
otherData[WalletInfoKeys.cachedSecondaryBalance] = walletBox.get(
|
||||
DBKeys.cachedBalanceSecondary,
|
||||
) as String?;
|
||||
|
||||
otherData[WalletInfoKeys.tokenContractAddresses] = walletBox.get(
|
||||
DBKeys.ethTokenContracts,
|
||||
) as List<String>?;
|
||||
|
||||
// epiccash specifics
|
||||
if (old.coin == Coin.epicCash) {
|
||||
final epicWalletInfo = ExtraEpiccashWalletInfo.fromMap({
|
||||
"receivingIndex": walletBox.get("receivingIndex") as int? ?? 0,
|
||||
"changeIndex": walletBox.get("changeIndex") as int? ?? 0,
|
||||
"slate_to_address": walletBox.get("slate_to_address") as Map? ?? {},
|
||||
"slatesToCommits": walletBox.get("slatesToCommits") as Map? ?? {},
|
||||
"lastScannedBlock": walletBox.get("lastScannedBlock") as int? ?? 0,
|
||||
"restoreHeight": walletBox.get("restoreHeight") as int? ?? 0,
|
||||
"creationHeight": walletBox.get("creationHeight") as int? ?? 0,
|
||||
});
|
||||
otherData[WalletInfoKeys.epiccashData] = jsonEncode(
|
||||
epicWalletInfo.toMap(),
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Clear out any keys with null values as they are not needed
|
||||
//
|
||||
otherData.removeWhere((key, value) => value == null);
|
||||
|
||||
final info = WalletInfo(
|
||||
coinName: old.coin.name,
|
||||
walletId: old.walletId,
|
||||
name: old.name,
|
||||
walletType: _walletTypeForCoin(old.coin),
|
||||
mainAddressType: _addressTypeForCoin(old.coin),
|
||||
favouriteOrderIndex: favourites.indexOf(old.walletId),
|
||||
cachedChainHeight: walletBox.get(
|
||||
DBKeys.storedChainHeight,
|
||||
) as int? ??
|
||||
0,
|
||||
cachedBalanceString: walletBox.get(
|
||||
DBKeys.cachedBalance,
|
||||
) as String?,
|
||||
otherDataJsonString: jsonEncode(otherData),
|
||||
);
|
||||
|
||||
newInfo.add(info);
|
||||
}
|
||||
}
|
||||
|
||||
void _cleanupOnSuccess({required List<String> walletIds}) async {
|
||||
await Hive.deleteBoxFromDisk(DB.boxNameFavoriteWallets);
|
||||
await Hive.deleteBoxFromDisk(DB.boxNameAllWalletsData);
|
||||
for (final walletId in walletIds) {
|
||||
await Hive.deleteBoxFromDisk(walletId);
|
||||
}
|
||||
}
|
||||
|
||||
WalletType _walletTypeForCoin(Coin coin) {
|
||||
WalletType walletType;
|
||||
switch (coin) {
|
||||
case Coin.bitcoin:
|
||||
case Coin.bitcoinTestNet:
|
||||
case Coin.bitcoincash:
|
||||
case Coin.bitcoincashTestnet:
|
||||
case Coin.litecoin:
|
||||
case Coin.dogecoin:
|
||||
case Coin.firo:
|
||||
case Coin.namecoin:
|
||||
case Coin.particl:
|
||||
case Coin.litecoinTestNet:
|
||||
case Coin.firoTestNet:
|
||||
case Coin.dogecoinTestNet:
|
||||
case Coin.eCash:
|
||||
walletType = WalletType.bip39HD;
|
||||
break;
|
||||
|
||||
case Coin.monero:
|
||||
case Coin.wownero:
|
||||
walletType = WalletType.cryptonote;
|
||||
break;
|
||||
|
||||
case Coin.epicCash:
|
||||
case Coin.ethereum:
|
||||
case Coin.tezos:
|
||||
case Coin.nano:
|
||||
case Coin.banano:
|
||||
case Coin.stellar:
|
||||
case Coin.stellarTestnet:
|
||||
walletType = WalletType.bip39;
|
||||
break;
|
||||
}
|
||||
|
||||
return walletType;
|
||||
}
|
||||
|
||||
AddressType _addressTypeForCoin(Coin coin) {
|
||||
AddressType addressType;
|
||||
switch (coin) {
|
||||
case Coin.bitcoin:
|
||||
case Coin.bitcoinTestNet:
|
||||
case Coin.litecoin:
|
||||
case Coin.litecoinTestNet:
|
||||
addressType = AddressType.p2wpkh;
|
||||
break;
|
||||
|
||||
case Coin.eCash:
|
||||
case Coin.bitcoincash:
|
||||
case Coin.bitcoincashTestnet:
|
||||
case Coin.dogecoin:
|
||||
case Coin.firo:
|
||||
case Coin.firoTestNet:
|
||||
case Coin.namecoin:
|
||||
case Coin.particl:
|
||||
case Coin.dogecoinTestNet:
|
||||
addressType = AddressType.p2pkh;
|
||||
break;
|
||||
|
||||
case Coin.monero:
|
||||
case Coin.wownero:
|
||||
addressType = AddressType.cryptonote;
|
||||
break;
|
||||
|
||||
case Coin.epicCash:
|
||||
addressType = AddressType.mimbleWimble;
|
||||
break;
|
||||
|
||||
case Coin.ethereum:
|
||||
addressType = AddressType.ethereum;
|
||||
break;
|
||||
|
||||
case Coin.tezos:
|
||||
// should not be unknown but since already used in prod changing
|
||||
// this requires a migrate
|
||||
addressType = AddressType.unknown;
|
||||
break;
|
||||
|
||||
case Coin.nano:
|
||||
addressType = AddressType.nano;
|
||||
break;
|
||||
|
||||
case Coin.banano:
|
||||
addressType = AddressType.banano;
|
||||
break;
|
||||
|
||||
case Coin.stellar:
|
||||
case Coin.stellarTestnet:
|
||||
// should not be unknown but since already used in prod changing
|
||||
// this requires a migrate
|
||||
addressType = AddressType.unknown;
|
||||
break;
|
||||
}
|
||||
|
||||
return addressType;
|
||||
}
|
|
@ -1,14 +1,13 @@
|
|||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:coinlib/coinlib.dart' as coinlib;
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/exceptions/sw_exception.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
|
||||
import 'package:stackwallet/wallets/coin/bip39_hd_currency.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/bip39_hd_currency.dart';
|
||||
import 'package:stackwallet/wallets/models/tx_data.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet.dart';
|
||||
import 'package:stackwallet/wallets/wallet/bip39_wallet.dart';
|
||||
|
||||
class Bip39HDWallet<T extends Bip39HDCurrency> extends Wallet<T> {
|
||||
abstract class Bip39HDWallet<T extends Bip39HDCurrency> extends Bip39Wallet<T> {
|
||||
Bip39HDWallet(super.cryptoCurrency);
|
||||
|
||||
/// Generates a receiving address of [walletInfo.mainAddressType]. If none
|
||||
|
@ -50,30 +49,6 @@ class Bip39HDWallet<T extends Bip39HDCurrency> extends Wallet<T> {
|
|||
return address;
|
||||
}
|
||||
|
||||
Future<String> getMnemonic() async {
|
||||
final mnemonic = await secureStorageInterface.read(
|
||||
key: Wallet.mnemonicKey(walletId: walletInfo.walletId),
|
||||
);
|
||||
|
||||
if (mnemonic == null) {
|
||||
throw SWException("mnemonic has not been set");
|
||||
}
|
||||
|
||||
return mnemonic;
|
||||
}
|
||||
|
||||
Future<String> getMnemonicPassphrase() async {
|
||||
final mnemonicPassphrase = await secureStorageInterface.read(
|
||||
key: Wallet.mnemonicPassphraseKey(walletId: walletInfo.walletId),
|
||||
);
|
||||
|
||||
if (mnemonicPassphrase == null) {
|
||||
throw SWException("mnemonicPassphrase has not been set");
|
||||
}
|
||||
|
||||
return mnemonicPassphrase;
|
||||
}
|
||||
|
||||
// ========== Private ========================================================
|
||||
|
||||
Future<Address?> get _currentReceivingAddress async =>
|
||||
|
@ -149,4 +124,10 @@ class Bip39HDWallet<T extends Bip39HDCurrency> extends Wallet<T> {
|
|||
// TODO: implement prepareSend
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> recover({required bool isRescan}) {
|
||||
// TODO: implement recover
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
53
lib/wallets/wallet/bip39_wallet.dart
Normal file
53
lib/wallets/wallet/bip39_wallet.dart
Normal file
|
@ -0,0 +1,53 @@
|
|||
import 'package:stackwallet/exceptions/sw_exception.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/bip39_currency.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet.dart';
|
||||
|
||||
abstract class Bip39Wallet<T extends Bip39Currency> extends Wallet<T> {
|
||||
Bip39Wallet(super.cryptoCurrency);
|
||||
|
||||
Future<String> getMnemonic() async {
|
||||
final mnemonic = await secureStorageInterface.read(
|
||||
key: Wallet.mnemonicKey(walletId: walletInfo.walletId),
|
||||
);
|
||||
|
||||
if (mnemonic == null) {
|
||||
throw SWException("mnemonic has not been set");
|
||||
}
|
||||
|
||||
return mnemonic;
|
||||
}
|
||||
|
||||
Future<String> getMnemonicPassphrase() async {
|
||||
final mnemonicPassphrase = await secureStorageInterface.read(
|
||||
key: Wallet.mnemonicPassphraseKey(walletId: walletInfo.walletId),
|
||||
);
|
||||
|
||||
if (mnemonicPassphrase == null) {
|
||||
throw SWException("mnemonicPassphrase has not been set");
|
||||
}
|
||||
|
||||
return mnemonicPassphrase;
|
||||
}
|
||||
|
||||
// ========== Private ========================================================
|
||||
|
||||
// ========== Overrides ======================================================
|
||||
|
||||
// @override
|
||||
// Future<TxData> confirmSend({required TxData txData}) {
|
||||
// // TODO: implement confirmSend
|
||||
// throw UnimplementedError();
|
||||
// }
|
||||
//
|
||||
// @override
|
||||
// Future<TxData> prepareSend({required TxData txData}) {
|
||||
// // TODO: implement prepareSend
|
||||
// throw UnimplementedError();
|
||||
// }
|
||||
//
|
||||
// @override
|
||||
// Future<void> recover({required bool isRescan}) {
|
||||
// // TODO: implement recover
|
||||
// throw UnimplementedError();
|
||||
// }
|
||||
}
|
|
@ -2,7 +2,7 @@ import 'package:stackwallet/exceptions/sw_exception.dart';
|
|||
import 'package:stackwallet/wallets/models/tx_data.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet.dart';
|
||||
|
||||
class CryptonoteWallet extends Wallet {
|
||||
abstract class CryptonoteWallet extends Wallet {
|
||||
CryptonoteWallet(super.cryptoCurrency);
|
||||
|
||||
Future<String> getMnemonic() async {
|
||||
|
@ -30,4 +30,10 @@ class CryptonoteWallet extends Wallet {
|
|||
// TODO: implement prepareSend
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> recover({required bool isRescan}) {
|
||||
// TODO: implement recover
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
95
lib/wallets/wallet/impl/bitcoin_wallet.dart
Normal file
95
lib/wallets/wallet/impl/bitcoin_wallet.dart
Normal file
|
@ -0,0 +1,95 @@
|
|||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||
import 'package:stackwallet/services/node_service.dart';
|
||||
import 'package:stackwallet/utilities/prefs.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/coins/bitcoin.dart';
|
||||
import 'package:stackwallet/wallets/wallet/bip39_hd_wallet.dart';
|
||||
import 'package:stackwallet/wallets/wallet/mixins/electrumx_mixin.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class BitcoinWallet extends Bip39HDWallet with ElectrumXMixin {
|
||||
BitcoinWallet(
|
||||
super.cryptoCurrency, {
|
||||
required NodeService nodeService,
|
||||
required Prefs prefs,
|
||||
}) {
|
||||
// TODO: [prio=low] ensure this hack isn't needed
|
||||
assert(cryptoCurrency is Bitcoin);
|
||||
|
||||
this.prefs = prefs;
|
||||
this.nodeService = nodeService;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
|
||||
Future<List<Address>> _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<void> refresh() {
|
||||
// TODO: implement refresh
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateBalance() {
|
||||
// TODO: implement updateBalance
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateTransactions() async {
|
||||
final currentChainHeight = await fetchChainHeight();
|
||||
|
||||
final data = await fetchTransactions(
|
||||
addresses: await _fetchAllOwnAddresses(),
|
||||
currentChainHeight: currentChainHeight,
|
||||
);
|
||||
|
||||
await mainDB.addNewTransactionData(
|
||||
data
|
||||
.map(
|
||||
(e) => Tuple2(
|
||||
e.transaction,
|
||||
e.address,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
walletId,
|
||||
);
|
||||
|
||||
// TODO: [prio=med] get rid of this and watch isar instead
|
||||
// quick hack to notify manager to call notifyListeners if
|
||||
// transactions changed
|
||||
if (data.isNotEmpty) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Transactions updated/added for: $walletId ${walletInfo.name}",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateUTXOs() {
|
||||
// TODO: implement updateUTXOs
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
54
lib/wallets/wallet/impl/epiccash_wallet.dart
Normal file
54
lib/wallets/wallet/impl/epiccash_wallet.dart
Normal file
|
@ -0,0 +1,54 @@
|
|||
import 'package:stackwallet/wallets/models/tx_data.dart';
|
||||
import 'package:stackwallet/wallets/wallet/bip39_wallet.dart';
|
||||
|
||||
class EpiccashWallet extends Bip39Wallet {
|
||||
EpiccashWallet(super.cryptoCurrency);
|
||||
|
||||
@override
|
||||
Future<TxData> confirmSend({required TxData txData}) {
|
||||
// TODO: implement confirmSend
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TxData> prepareSend({required TxData txData}) {
|
||||
// TODO: implement prepareSend
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> recover({required bool isRescan}) {
|
||||
// TODO: implement recover
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> refresh() {
|
||||
// TODO: implement refresh
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateBalance() {
|
||||
// TODO: implement updateBalance
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateNode() {
|
||||
// TODO: implement updateNode
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateTransactions() {
|
||||
// TODO: implement updateTransactions
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateUTXOs() {
|
||||
// TODO: implement updateUTXOs
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
431
lib/wallets/wallet/mixins/electrumx_mixin.dart
Normal file
431
lib/wallets/wallet/mixins/electrumx_mixin.dart
Normal file
|
@ -0,0 +1,431 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:bip47/src/util.dart';
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/input.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/output.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
|
||||
import 'package:stackwallet/services/node_service.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/default_nodes.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/wallets/wallet/bip39_hd_wallet.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
mixin ElectrumXMixin on Bip39HDWallet {
|
||||
late ElectrumX electrumX;
|
||||
late CachedElectrumX electrumXCached;
|
||||
late NodeService nodeService;
|
||||
|
||||
Future<int> fetchChainHeight() async {
|
||||
try {
|
||||
final result = await electrumX.getBlockHeadTip();
|
||||
return result["height"] as int;
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<({Transaction transaction, Address address})>> fetchTransactions({
|
||||
required List<Address> addresses,
|
||||
required int currentChainHeight,
|
||||
}) async {
|
||||
final List<({String txHash, int height, String address})> allTxHashes =
|
||||
(await _fetchHistory(addresses.map((e) => e.value).toList()))
|
||||
.map(
|
||||
(e) => (
|
||||
txHash: e["tx_hash"] as String,
|
||||
height: e["height"] as int,
|
||||
address: e["address"] as String,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
List<Map<String, dynamic>> allTransactions = [];
|
||||
|
||||
for (final data in allTxHashes) {
|
||||
final tx = await electrumXCached.getTransaction(
|
||||
txHash: data.txHash,
|
||||
verbose: true,
|
||||
coin: cryptoCurrency.coin,
|
||||
);
|
||||
|
||||
if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) {
|
||||
tx["address"] = addresses.firstWhere((e) => e.value == data.address);
|
||||
tx["height"] = data.height;
|
||||
allTransactions.add(tx);
|
||||
}
|
||||
}
|
||||
|
||||
final List<({Transaction transaction, Address address})> txnsData = [];
|
||||
|
||||
for (final txObject in allTransactions) {
|
||||
final data = await parseTransaction(
|
||||
txObject,
|
||||
addresses,
|
||||
);
|
||||
|
||||
txnsData.add(data);
|
||||
}
|
||||
|
||||
return txnsData;
|
||||
}
|
||||
|
||||
Future<ElectrumXNode> getCurrentNode() async {
|
||||
final node = nodeService.getPrimaryNodeFor(coin: cryptoCurrency.coin) ??
|
||||
DefaultNodes.getNodeFor(cryptoCurrency.coin);
|
||||
|
||||
return ElectrumXNode(
|
||||
address: node.host,
|
||||
port: node.port,
|
||||
name: node.name,
|
||||
useSSL: node.useSSL,
|
||||
id: node.id,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> updateElectrumX({required ElectrumXNode newNode}) 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 getCurrentNode();
|
||||
electrumX = ElectrumX.from(
|
||||
node: newNode,
|
||||
prefs: prefs,
|
||||
failovers: failovers,
|
||||
);
|
||||
electrumXCached = CachedElectrumX.from(
|
||||
electrumXClient: electrumX,
|
||||
);
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
|
||||
bool _duplicateTxCheck(
|
||||
List<Map<String, dynamic>> allTransactions, String txid) {
|
||||
for (int i = 0; i < allTransactions.length; i++) {
|
||||
if (allTransactions[i]["txid"] == txid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> _fetchHistory(
|
||||
List<String> allAddresses,
|
||||
) async {
|
||||
try {
|
||||
List<Map<String, dynamic>> allTxHashes = [];
|
||||
|
||||
final Map<int, Map<String, List<dynamic>>> batches = {};
|
||||
final Map<String, String> requestIdToAddressMap = {};
|
||||
const batchSizeMax = 100;
|
||||
int batchNumber = 0;
|
||||
for (int i = 0; i < allAddresses.length; i++) {
|
||||
if (batches[batchNumber] == null) {
|
||||
batches[batchNumber] = {};
|
||||
}
|
||||
final scriptHash = cryptoCurrency.addressToScriptHash(
|
||||
address: allAddresses[i],
|
||||
);
|
||||
final id = Logger.isTestEnv ? "$i" : const Uuid().v1();
|
||||
requestIdToAddressMap[id] = allAddresses[i];
|
||||
batches[batchNumber]!.addAll({
|
||||
id: [scriptHash]
|
||||
});
|
||||
if (i % batchSizeMax == batchSizeMax - 1) {
|
||||
batchNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < batches.length; i++) {
|
||||
final response = await electrumX.getBatchHistory(args: batches[i]!);
|
||||
for (final entry in response.entries) {
|
||||
for (int j = 0; j < entry.value.length; j++) {
|
||||
entry.value[j]["address"] = requestIdToAddressMap[entry.key];
|
||||
if (!allTxHashes.contains(entry.value[j])) {
|
||||
allTxHashes.add(entry.value[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allTxHashes;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("_fetchHistory: $e\n$s", level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<({Transaction transaction, Address address})> parseTransaction(
|
||||
Map<String, dynamic> txData,
|
||||
List<Address> myAddresses,
|
||||
) async {
|
||||
Set<String> receivingAddresses = myAddresses
|
||||
.where((e) =>
|
||||
e.subType == AddressSubType.receiving ||
|
||||
e.subType == AddressSubType.paynymReceive ||
|
||||
e.subType == AddressSubType.paynymNotification)
|
||||
.map((e) => e.value)
|
||||
.toSet();
|
||||
Set<String> changeAddresses = myAddresses
|
||||
.where((e) => e.subType == AddressSubType.change)
|
||||
.map((e) => e.value)
|
||||
.toSet();
|
||||
|
||||
Set<String> inputAddresses = {};
|
||||
Set<String> outputAddresses = {};
|
||||
|
||||
Amount totalInputValue = Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.coin.decimals,
|
||||
);
|
||||
Amount totalOutputValue = Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.coin.decimals,
|
||||
);
|
||||
|
||||
Amount amountSentFromWallet = Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.coin.decimals,
|
||||
);
|
||||
Amount amountReceivedInWallet = Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.coin.decimals,
|
||||
);
|
||||
Amount changeAmount = Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.coin.decimals,
|
||||
);
|
||||
|
||||
// parse inputs
|
||||
for (final input in txData["vin"] as List) {
|
||||
final prevTxid = input["txid"] as String;
|
||||
final prevOut = input["vout"] as int;
|
||||
|
||||
// fetch input tx to get address
|
||||
final inputTx = await electrumXCached.getTransaction(
|
||||
txHash: prevTxid,
|
||||
coin: cryptoCurrency.coin,
|
||||
);
|
||||
|
||||
for (final output in inputTx["vout"] as List) {
|
||||
// check matching output
|
||||
if (prevOut == output["n"]) {
|
||||
// get value
|
||||
final value = Amount.fromDecimal(
|
||||
Decimal.parse(output["value"].toString()),
|
||||
fractionDigits: cryptoCurrency.coin.decimals,
|
||||
);
|
||||
|
||||
// add value to total
|
||||
totalInputValue += value;
|
||||
|
||||
// get input(prevOut) address
|
||||
final address = output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||
output["scriptPubKey"]?["address"] as String?;
|
||||
|
||||
if (address != null) {
|
||||
inputAddresses.add(address);
|
||||
|
||||
// if input was from my wallet, add value to amount sent
|
||||
if (receivingAddresses.contains(address) ||
|
||||
changeAddresses.contains(address)) {
|
||||
amountSentFromWallet += value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parse outputs
|
||||
for (final output in txData["vout"] as List) {
|
||||
// get value
|
||||
final value = Amount.fromDecimal(
|
||||
Decimal.parse(output["value"].toString()),
|
||||
fractionDigits: cryptoCurrency.coin.decimals,
|
||||
);
|
||||
|
||||
// add value to total
|
||||
totalOutputValue += value;
|
||||
|
||||
// get output address
|
||||
final address = output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||
output["scriptPubKey"]?["address"] as String?;
|
||||
if (address != null) {
|
||||
outputAddresses.add(address);
|
||||
|
||||
// if output was to my wallet, add value to amount received
|
||||
if (receivingAddresses.contains(address)) {
|
||||
amountReceivedInWallet += value;
|
||||
} else if (changeAddresses.contains(address)) {
|
||||
changeAmount += value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final mySentFromAddresses = [
|
||||
...receivingAddresses.intersection(inputAddresses),
|
||||
...changeAddresses.intersection(inputAddresses)
|
||||
];
|
||||
final myReceivedOnAddresses =
|
||||
receivingAddresses.intersection(outputAddresses);
|
||||
final myChangeReceivedOnAddresses =
|
||||
changeAddresses.intersection(outputAddresses);
|
||||
|
||||
final fee = totalInputValue - totalOutputValue;
|
||||
|
||||
// this is the address initially used to fetch the txid
|
||||
Address transactionAddress = txData["address"] as Address;
|
||||
|
||||
TransactionType type;
|
||||
Amount amount;
|
||||
if (mySentFromAddresses.isNotEmpty && myReceivedOnAddresses.isNotEmpty) {
|
||||
// tx is sent to self
|
||||
type = TransactionType.sentToSelf;
|
||||
|
||||
// should be 0
|
||||
amount =
|
||||
amountSentFromWallet - amountReceivedInWallet - fee - changeAmount;
|
||||
} else if (mySentFromAddresses.isNotEmpty) {
|
||||
// outgoing tx
|
||||
type = TransactionType.outgoing;
|
||||
amount = amountSentFromWallet - changeAmount - fee;
|
||||
|
||||
// non wallet addresses found in tx outputs
|
||||
final nonWalletOutAddresses = outputAddresses.difference(
|
||||
myChangeReceivedOnAddresses,
|
||||
);
|
||||
|
||||
if (nonWalletOutAddresses.isNotEmpty) {
|
||||
final possible = nonWalletOutAddresses.first;
|
||||
|
||||
if (transactionAddress.value != possible) {
|
||||
transactionAddress = Address(
|
||||
walletId: myAddresses.first.walletId,
|
||||
value: possible,
|
||||
derivationIndex: -1,
|
||||
derivationPath: null,
|
||||
subType: AddressSubType.nonWallet,
|
||||
type: AddressType.nonWallet,
|
||||
publicKey: [],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// some other type of tx where the receiving address is
|
||||
// one of my change addresses
|
||||
|
||||
type = TransactionType.sentToSelf;
|
||||
amount = changeAmount;
|
||||
}
|
||||
} else {
|
||||
// incoming tx
|
||||
type = TransactionType.incoming;
|
||||
amount = amountReceivedInWallet;
|
||||
}
|
||||
|
||||
List<Output> outs = [];
|
||||
List<Input> ins = [];
|
||||
|
||||
for (final json in txData["vin"] as List) {
|
||||
bool isCoinBase = json['coinbase'] != null;
|
||||
String? witness;
|
||||
if (json['witness'] != null && json['witness'] is String) {
|
||||
witness = json['witness'] as String;
|
||||
} else if (json['txinwitness'] != null) {
|
||||
if (json['txinwitness'] is List) {
|
||||
witness = jsonEncode(json['txinwitness']);
|
||||
}
|
||||
}
|
||||
final input = Input(
|
||||
txid: json['txid'] as String,
|
||||
vout: json['vout'] as int? ?? -1,
|
||||
scriptSig: json['scriptSig']?['hex'] as String?,
|
||||
scriptSigAsm: json['scriptSig']?['asm'] as String?,
|
||||
isCoinbase: isCoinBase ? isCoinBase : json['is_coinbase'] as bool?,
|
||||
sequence: json['sequence'] as int?,
|
||||
innerRedeemScriptAsm: json['innerRedeemscriptAsm'] as String?,
|
||||
witness: witness,
|
||||
);
|
||||
ins.add(input);
|
||||
}
|
||||
|
||||
for (final json in txData["vout"] as List) {
|
||||
final output = Output(
|
||||
scriptPubKey: json['scriptPubKey']?['hex'] as String?,
|
||||
scriptPubKeyAsm: json['scriptPubKey']?['asm'] as String?,
|
||||
scriptPubKeyType: json['scriptPubKey']?['type'] as String?,
|
||||
scriptPubKeyAddress:
|
||||
json["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||
json['scriptPubKey']?['type'] as String? ??
|
||||
"",
|
||||
value: Amount.fromDecimal(
|
||||
Decimal.parse(json["value"].toString()),
|
||||
fractionDigits: cryptoCurrency.coin.decimals,
|
||||
).raw.toInt(),
|
||||
);
|
||||
outs.add(output);
|
||||
}
|
||||
|
||||
TransactionSubType txSubType = TransactionSubType.none;
|
||||
if (this is PaynymWalletInterface && outs.length > 1 && ins.isNotEmpty) {
|
||||
for (int i = 0; i < outs.length; i++) {
|
||||
List<String>? scriptChunks = outs[i].scriptPubKeyAsm?.split(" ");
|
||||
if (scriptChunks?.length == 2 && scriptChunks?[0] == "OP_RETURN") {
|
||||
final blindedPaymentCode = scriptChunks![1];
|
||||
final bytes = blindedPaymentCode.fromHex;
|
||||
|
||||
// https://en.bitcoin.it/wiki/BIP_0047#Sending
|
||||
if (bytes.length == 80 && bytes.first == 1) {
|
||||
txSubType = TransactionSubType.bip47Notification;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final tx = Transaction(
|
||||
walletId: myAddresses.first.walletId,
|
||||
txid: txData["txid"] as String,
|
||||
timestamp: txData["blocktime"] as int? ??
|
||||
(DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
||||
type: type,
|
||||
subType: txSubType,
|
||||
// amount may overflow. Deprecated. Use amountString
|
||||
amount: amount.raw.toInt(),
|
||||
amountString: amount.toJsonString(),
|
||||
fee: fee.raw.toInt(),
|
||||
height: txData["height"] as int?,
|
||||
isCancelled: false,
|
||||
isLelantus: false,
|
||||
slateId: null,
|
||||
otherData: null,
|
||||
nonce: null,
|
||||
inputs: ins,
|
||||
outputs: outs,
|
||||
numberOfMessages: null,
|
||||
);
|
||||
|
||||
return (transaction: tx, address: transactionAddress);
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
|
||||
@override
|
||||
Future<void> updateNode() async {
|
||||
final node = await getCurrentNode();
|
||||
await updateElectrumX(newNode: node);
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ import 'package:stackwallet/exceptions/sw_exception.dart';
|
|||
import 'package:stackwallet/wallets/models/tx_data.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet.dart';
|
||||
|
||||
class PrivateKeyBasedWallet extends Wallet {
|
||||
abstract class PrivateKeyBasedWallet extends Wallet {
|
||||
PrivateKeyBasedWallet(super.cryptoCurrency);
|
||||
|
||||
Future<String> getPrivateKey() async {
|
||||
|
@ -30,4 +30,10 @@ class PrivateKeyBasedWallet extends Wallet {
|
|||
// TODO: implement prepareSend
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> recover({required bool isRescan}) {
|
||||
// TODO: implement recover
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/wallets/isar_models/wallet_info.dart';
|
||||
|
||||
extension EpiccashWalletInfoExtension on WalletInfo {
|
||||
ExtraEpiccashWalletInfo? get epicData {
|
||||
final String? data = otherData[WalletInfoKeys.epiccashData] as String?;
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return ExtraEpiccashWalletInfo.fromMap(
|
||||
Map<String, dynamic>.from(
|
||||
jsonDecode(data) as Map,
|
||||
),
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"ExtraEpiccashWalletInfo.fromMap failed: $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds data previously stored in hive
|
||||
class ExtraEpiccashWalletInfo {
|
||||
final int receivingIndex;
|
||||
final int changeIndex;
|
||||
|
||||
// TODO [prio=low] strongly type these maps at some point
|
||||
final Map<dynamic, dynamic> slatesToAddresses;
|
||||
final Map<dynamic, dynamic> slatesToCommits;
|
||||
|
||||
final int lastScannedBlock;
|
||||
final int restoreHeight;
|
||||
final int creationHeight;
|
||||
|
||||
ExtraEpiccashWalletInfo({
|
||||
required this.receivingIndex,
|
||||
required this.changeIndex,
|
||||
required this.slatesToAddresses,
|
||||
required this.slatesToCommits,
|
||||
required this.lastScannedBlock,
|
||||
required this.restoreHeight,
|
||||
required this.creationHeight,
|
||||
});
|
||||
|
||||
// Convert the object to JSON
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'receivingIndex': receivingIndex,
|
||||
'changeIndex': changeIndex,
|
||||
'slatesToAddresses': slatesToAddresses,
|
||||
'slatesToCommits': slatesToCommits,
|
||||
'lastScannedBlock': lastScannedBlock,
|
||||
'restoreHeight': restoreHeight,
|
||||
'creationHeight': creationHeight,
|
||||
};
|
||||
}
|
||||
|
||||
ExtraEpiccashWalletInfo.fromMap(Map<String, dynamic> json)
|
||||
: receivingIndex = json['receivingIndex'] as int,
|
||||
changeIndex = json['changeIndex'] as int,
|
||||
slatesToAddresses = json['slatesToAddresses'] as Map,
|
||||
slatesToCommits = json['slatesToCommits'] as Map,
|
||||
lastScannedBlock = json['lastScannedBlock'] as int,
|
||||
restoreHeight = json['restoreHeight'] as int,
|
||||
creationHeight = json['creationHeight'] as int;
|
||||
|
||||
ExtraEpiccashWalletInfo copyWith({
|
||||
int? receivingIndex,
|
||||
int? changeIndex,
|
||||
Map<dynamic, dynamic>? slatesToAddresses,
|
||||
Map<dynamic, dynamic>? slatesToCommits,
|
||||
int? lastScannedBlock,
|
||||
int? restoreHeight,
|
||||
int? creationHeight,
|
||||
}) {
|
||||
return ExtraEpiccashWalletInfo(
|
||||
receivingIndex: receivingIndex ?? this.receivingIndex,
|
||||
changeIndex: changeIndex ?? this.changeIndex,
|
||||
slatesToAddresses: slatesToAddresses ?? this.slatesToAddresses,
|
||||
slatesToCommits: slatesToCommits ?? this.slatesToCommits,
|
||||
lastScannedBlock: lastScannedBlock ?? this.lastScannedBlock,
|
||||
restoreHeight: restoreHeight ?? this.restoreHeight,
|
||||
creationHeight: creationHeight ?? this.creationHeight,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return toMap().toString();
|
||||
}
|
||||
}
|
|
@ -1,14 +1,15 @@
|
|||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/db/isar/main_db.dart';
|
||||
import 'package:stackwallet/services/node_service.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||
import 'package:stackwallet/wallets/coin/bip39_hd_currency.dart';
|
||||
import 'package:stackwallet/wallets/coin/coins/bitcoin.dart';
|
||||
import 'package:stackwallet/wallets/coin/crypto_currency.dart';
|
||||
import 'package:stackwallet/utilities/prefs.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/coins/bitcoin.dart';
|
||||
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/bip39_hd_wallet.dart';
|
||||
import 'package:stackwallet/wallets/wallet/private_key_based_wallet.dart';
|
||||
import 'package:stackwallet/wallets/wallet/impl/bitcoin_wallet.dart';
|
||||
import 'package:stackwallet/wallets/wallet/mixins/electrumx_mixin.dart';
|
||||
|
||||
abstract class Wallet<T extends CryptoCurrency> {
|
||||
Wallet(this.cryptoCurrency);
|
||||
|
@ -21,6 +22,7 @@ abstract class Wallet<T extends CryptoCurrency> {
|
|||
late final MainDB mainDB;
|
||||
late final SecureStorageInterface secureStorageInterface;
|
||||
late final WalletInfo walletInfo;
|
||||
late final Prefs prefs;
|
||||
|
||||
//============================================================================
|
||||
// ========== Wallet Info Convenience Getters ================================
|
||||
|
@ -36,19 +38,23 @@ abstract class Wallet<T extends CryptoCurrency> {
|
|||
required WalletInfo walletInfo,
|
||||
required MainDB mainDB,
|
||||
required SecureStorageInterface secureStorageInterface,
|
||||
required NodeService nodeService,
|
||||
required Prefs prefs,
|
||||
String? mnemonic,
|
||||
String? mnemonicPassphrase,
|
||||
String? privateKey,
|
||||
int? startDate,
|
||||
}) async {
|
||||
final Wallet wallet = await _construct(
|
||||
walletInfo: walletInfo,
|
||||
mainDB: mainDB,
|
||||
secureStorageInterface: secureStorageInterface,
|
||||
nodeService: nodeService,
|
||||
prefs: prefs,
|
||||
);
|
||||
|
||||
switch (walletInfo.walletType) {
|
||||
case WalletType.bip39:
|
||||
case WalletType.bip39HD:
|
||||
await secureStorageInterface.write(
|
||||
key: mnemonicKey(walletId: walletInfo.walletId),
|
||||
value: mnemonic,
|
||||
|
@ -77,6 +83,8 @@ abstract class Wallet<T extends CryptoCurrency> {
|
|||
required String walletId,
|
||||
required MainDB mainDB,
|
||||
required SecureStorageInterface secureStorageInterface,
|
||||
required NodeService nodeService,
|
||||
required Prefs prefs,
|
||||
}) async {
|
||||
final walletInfo = await mainDB.isar.walletInfo
|
||||
.where()
|
||||
|
@ -93,22 +101,27 @@ abstract class Wallet<T extends CryptoCurrency> {
|
|||
walletInfo: walletInfo!,
|
||||
mainDB: mainDB,
|
||||
secureStorageInterface: secureStorageInterface,
|
||||
nodeService: nodeService,
|
||||
prefs: prefs,
|
||||
);
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
// ========== Static Util ====================================================
|
||||
|
||||
// secure storage key
|
||||
static String mnemonicKey({
|
||||
required String walletId,
|
||||
}) =>
|
||||
"${walletId}_mnemonic";
|
||||
|
||||
// secure storage key
|
||||
static String mnemonicPassphraseKey({
|
||||
required String walletId,
|
||||
}) =>
|
||||
"${walletId}_mnemonicPassphrase";
|
||||
|
||||
// secure storage key
|
||||
static String privateKeyKey({
|
||||
required String walletId,
|
||||
}) =>
|
||||
|
@ -122,23 +135,18 @@ abstract class Wallet<T extends CryptoCurrency> {
|
|||
required WalletInfo walletInfo,
|
||||
required MainDB mainDB,
|
||||
required SecureStorageInterface secureStorageInterface,
|
||||
required NodeService nodeService,
|
||||
required Prefs prefs,
|
||||
}) async {
|
||||
final Wallet wallet;
|
||||
final Wallet wallet = _loadWallet(
|
||||
walletInfo: walletInfo,
|
||||
nodeService: nodeService,
|
||||
prefs: prefs,
|
||||
);
|
||||
|
||||
final cryptoCurrency = _loadCurrency(walletInfo: walletInfo);
|
||||
|
||||
switch (walletInfo.walletType) {
|
||||
case WalletType.bip39:
|
||||
wallet = Bip39HDWallet(cryptoCurrency as Bip39HDCurrency);
|
||||
break;
|
||||
|
||||
case WalletType.cryptonote:
|
||||
wallet = PrivateKeyBasedWallet(cryptoCurrency);
|
||||
break;
|
||||
|
||||
case WalletType.privateKeyBased:
|
||||
wallet = PrivateKeyBasedWallet(cryptoCurrency);
|
||||
break;
|
||||
if (wallet is ElectrumXMixin) {
|
||||
// initialize electrumx instance
|
||||
await wallet.updateNode();
|
||||
}
|
||||
|
||||
return wallet
|
||||
|
@ -147,18 +155,28 @@ abstract class Wallet<T extends CryptoCurrency> {
|
|||
..walletInfo = walletInfo;
|
||||
}
|
||||
|
||||
static CryptoCurrency _loadCurrency({
|
||||
static Wallet _loadWallet({
|
||||
required WalletInfo walletInfo,
|
||||
required NodeService nodeService,
|
||||
required Prefs prefs,
|
||||
}) {
|
||||
switch (walletInfo.coin) {
|
||||
case Coin.bitcoin:
|
||||
return Bitcoin(CryptoCurrencyNetwork.main);
|
||||
return BitcoinWallet(
|
||||
Bitcoin(CryptoCurrencyNetwork.main),
|
||||
nodeService: nodeService,
|
||||
prefs: prefs,
|
||||
);
|
||||
case Coin.bitcoinTestNet:
|
||||
return Bitcoin(CryptoCurrencyNetwork.test);
|
||||
return BitcoinWallet(
|
||||
Bitcoin(CryptoCurrencyNetwork.test),
|
||||
nodeService: nodeService,
|
||||
prefs: prefs,
|
||||
);
|
||||
|
||||
default:
|
||||
// should never hit in reality
|
||||
throw Exception("Unknown cryupto currency");
|
||||
throw Exception("Unknown crypto currency");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,4 +189,22 @@ abstract class Wallet<T extends CryptoCurrency> {
|
|||
/// Broadcast transaction to network. On success update local wallet state to
|
||||
/// reflect updated balance, transactions, utxos, etc.
|
||||
Future<TxData> confirmSend({required TxData txData});
|
||||
|
||||
/// Recover a wallet by scanning the blockchain. If called on a new wallet a
|
||||
/// normal recovery should occur. When called on an existing wallet and
|
||||
/// [isRescan] is false then it should throw. Otherwise this function should
|
||||
/// delete all locally stored blockchain data and refetch it.
|
||||
Future<void> recover({required bool isRescan});
|
||||
|
||||
Future<void> updateTransactions();
|
||||
Future<void> updateUTXOs();
|
||||
Future<void> updateBalance();
|
||||
|
||||
// Should probably call the above 3 functions
|
||||
// Should fire events
|
||||
Future<void> refresh();
|
||||
|
||||
//===========================================
|
||||
|
||||
Future<void> updateNode();
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
mixin ElectrumXMixin {
|
||||
//
|
||||
}
|
Loading…
Reference in a new issue