WIP: Add wallet tokens

This commit is contained in:
likho 2023-01-25 11:29:20 +02:00
parent 706cbbfa39
commit 4efd432de6
15 changed files with 1874 additions and 1673 deletions

View file

@ -26,18 +26,22 @@ import 'package:stackwallet/widgets/textfield_icon_button.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/no_transactions_found.dart'; import 'package:stackwallet/pages/wallet_view/sub_widgets/no_transactions_found.dart';
import 'package:stackwallet/utilities/eth_commons.dart'; import 'package:stackwallet/utilities/eth_commons.dart';
import 'package:stackwallet/services/coins/manager.dart';
class MyTokensView extends ConsumerStatefulWidget { class MyTokensView extends ConsumerStatefulWidget {
const MyTokensView({ const MyTokensView({
Key? key, Key? key,
required this.managerProvider,
required this.walletId,
required this.walletAddress, required this.walletAddress,
required this.tokens, required this.tokens,
required this.walletName,
}) : super(key: key); }) : super(key: key);
static const String routeName = "/myTokens"; static const String routeName = "/myTokens";
final ChangeNotifierProvider<Manager> managerProvider;
final String walletId;
final String walletAddress; final String walletAddress;
final List<dynamic> tokens; final List<dynamic> tokens;
final String walletName;
@override @override
ConsumerState<MyTokensView> createState() => _TokenDetailsViewState(); ConsumerState<MyTokensView> createState() => _TokenDetailsViewState();
@ -45,12 +49,13 @@ class MyTokensView extends ConsumerStatefulWidget {
class _TokenDetailsViewState extends ConsumerState<MyTokensView> { class _TokenDetailsViewState extends ConsumerState<MyTokensView> {
late final String walletAddress; late final String walletAddress;
// late final String walletName;
late final TextEditingController _searchController; late final TextEditingController _searchController;
final searchFieldFocusNode = FocusNode(); final searchFieldFocusNode = FocusNode();
@override @override
void initState() { void initState() {
walletAddress = widget.walletAddress; // walletAddress = widget.walletAddress;
_searchController = TextEditingController(); _searchController = TextEditingController();
super.initState(); super.initState();
@ -101,7 +106,7 @@ class _TokenDetailsViewState extends ConsumerState<MyTokensView> {
width: 12, width: 12,
), ),
Text( Text(
"${widget.walletName} Tokens", "${ref.read(widget.managerProvider).walletName} Tokens",
style: STextStyles.desktopH3(context), style: STextStyles.desktopH3(context),
), ),
], ],
@ -123,7 +128,7 @@ class _TokenDetailsViewState extends ConsumerState<MyTokensView> {
}, },
), ),
title: Text( title: Text(
"${widget.walletName} Tokens", "${ref.read(widget.managerProvider).walletName} Tokens",
style: STextStyles.navBarTitle(context), style: STextStyles.navBarTitle(context),
), ),
actions: [ actions: [
@ -261,7 +266,10 @@ class _TokenDetailsViewState extends ConsumerState<MyTokensView> {
), ),
Expanded( Expanded(
child: MyTokensList( child: MyTokensList(
tokens: widget.tokens, walletAddress: walletAddress), managerProvider: widget.managerProvider,
walletId: widget.walletId,
tokens: widget.tokens,
walletAddress: widget.walletAddress),
), ),
], ],
), ),

View file

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/token_view/token_view.dart'; import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/providers/global/tokens_provider.dart';
import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart';
@ -13,14 +14,22 @@ import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/services/coins/manager.dart';
class MyTokenSelectItem extends ConsumerWidget { class MyTokenSelectItem extends ConsumerWidget {
const MyTokenSelectItem( const MyTokenSelectItem(
{Key? key, {Key? key,
required this.managerProvider,
required this.walletId,
required this.walletAddress, required this.walletAddress,
required this.tokenData, required this.tokenData,
required}) required})
: super(key: key); : super(key: key);
final ChangeNotifierProvider<Manager> managerProvider;
final String walletId;
final String walletAddress; final String walletAddress;
final Map<String, String> tokenData; final Map<String, String> tokenData;
@ -42,9 +51,38 @@ class MyTokenSelectItem extends ConsumerWidget {
BorderRadius.circular(Constants.size.circularBorderRadius), BorderRadius.circular(Constants.size.circularBorderRadius),
), ),
onPressed: () { onPressed: () {
// ref
// .read(walletsChangeNotifierProvider)
// .getManagerProvider(walletId)
// final walletId = ref
// .read(managerProvider)
// .walletName;
// final manager = ref
// .read(walletsChangeNotifierProvider)
// .getManagerProvider(walletId)
// arguments: Tuple2(walletId, managerProvider, walletAddress,
// tokenData["contractAddress"])
// arguments: Tuple2(
// walletId,
// ref
// .read(tokensChangeNotifierProvider)
// .getManagerProvider(walletId)
// arguments: Tuple2(
// walletId,
// ref
// .read(tokensChangeNotifierProvider)
// .getManagerProvider(walletId)
Navigator.of(context).pushNamed( Navigator.of(context).pushNamed(
TokenView.routeName, TokenView.routeName,
arguments: Tuple2(walletAddress, tokenData["contractAddress"]), arguments: Tuple2(
walletId,
ref.read(tokensChangeNotifierProvider).getManagerProvider(
tokenData["contractAddress"] as String)),
); );
}, },

View file

@ -1,14 +1,19 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/pages/token_view/sub_widgets/my_token_select_item.dart'; import 'package:stackwallet/pages/token_view/sub_widgets/my_token_select_item.dart';
import 'package:stackwallet/services/coins/manager.dart';
class MyTokensList extends StatelessWidget { class MyTokensList extends StatelessWidget {
const MyTokensList({ const MyTokensList({
Key? key, Key? key,
required this.managerProvider,
required this.walletId,
required this.tokens, required this.tokens,
required this.walletAddress, required this.walletAddress,
}) : super(key: key); }) : super(key: key);
final ChangeNotifierProvider<Manager> managerProvider;
final String walletId;
final List<dynamic> tokens; final List<dynamic> tokens;
final String walletAddress; final String walletAddress;
@ -23,6 +28,8 @@ class MyTokensList extends StatelessWidget {
return Padding( return Padding(
padding: const EdgeInsets.all(4), padding: const EdgeInsets.all(4),
child: MyTokenSelectItem( child: MyTokenSelectItem(
managerProvider: managerProvider,
walletId: walletId,
walletAddress: walletAddress, walletAddress: walletAddress,
tokenData: tokens[index] as Map<String, String>, tokenData: tokens[index] as Map<String, String>,
), ),

File diff suppressed because it is too large Load diff

View file

@ -783,18 +783,17 @@ class _WalletViewState extends ConsumerState<WalletView> {
.read(managerProvider) .read(managerProvider)
.currentReceivingAddress; .currentReceivingAddress;
final walletName = ref // String walletTokens = await
.read(managerProvider)
.walletName;
List<dynamic> tokens = List<dynamic> tokens =
await getWalletTokens( await getWalletTokens(await ref
walletAddress); .read(managerProvider)
.currentReceivingAddress);
await Navigator.of(context).pushNamed( await Navigator.of(context).pushNamed(
MyTokensView.routeName, MyTokensView.routeName,
arguments: Tuple3(walletAddress, arguments: Tuple4(managerProvider,
tokens, walletName), walletId, walletAddress, tokens),
); );
}, },
), ),

View file

@ -0,0 +1,23 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/providers/global/node_service_provider.dart';
import 'package:stackwallet/providers/global/tokens_service_provider.dart';
import 'package:stackwallet/providers/global/wallets_service_provider.dart';
import 'package:stackwallet/services/tokens.dart';
import 'package:stackwallet/services/wallets.dart';
int _count = 0;
final tokensChangeNotifierProvider = ChangeNotifierProvider<Tokens>((ref) {
if (kDebugMode) {
_count++;
debugPrint("tokensChangeNotifierProvider instantiation count: $_count");
}
final tokensService = ref.read(tokensServiceChangeNotifierProvider);
// final nodeService = ref.read(nodeServiceChangeNotifierProvider);
final tokens = Tokens.sharedInstance;
tokens.tokensService = tokensService;
return tokens;
});

View file

@ -0,0 +1,20 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/providers/global/secure_store_provider.dart';
import 'package:stackwallet/services/tokens_service.dart';
import 'package:stackwallet/services/wallets_service.dart';
int _count = 0;
final tokensServiceChangeNotifierProvider =
ChangeNotifierProvider<TokensService>((ref) {
if (kDebugMode) {
_count++;
debugPrint(
"tokensServiceChangeNotifierProvider instantiation count: $_count");
}
return TokensService(
secureStorageInterface: ref.read(secureStoreProvider),
);
});

View file

@ -117,6 +117,7 @@ import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/syncin
import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/services/coins/manager.dart';
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
import 'package:stackwallet/services/tokens/token_manager.dart';
import 'package:stackwallet/utilities/enums/add_wallet_type_enum.dart'; import 'package:stackwallet/utilities/enums/add_wallet_type_enum.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
@ -1295,13 +1296,15 @@ class RouteGenerator {
return _routeError("${settings.name} invalid args: ${args.toString()}"); return _routeError("${settings.name} invalid args: ${args.toString()}");
case MyTokensView.routeName: case MyTokensView.routeName:
if (args is Tuple3<String, List<dynamic>, String>) { if (args is Tuple4<ChangeNotifierProvider<Manager>, String, String,
List<dynamic>>) {
return getRoute( return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute, shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => MyTokensView( builder: (_) => MyTokensView(
walletAddress: args.item1, managerProvider: args.item1,
tokens: args.item2, walletId: args.item2,
walletName: args.item3), walletAddress: args.item3,
tokens: args.item4),
settings: RouteSettings( settings: RouteSettings(
name: settings.name, name: settings.name,
), ),
@ -1309,13 +1312,29 @@ class RouteGenerator {
} }
return _routeError("${settings.name} invalid args: ${args.toString()}"); return _routeError("${settings.name} invalid args: ${args.toString()}");
// case WalletView.routeName:
// if (args is Tuple2<String, ChangeNotifierProvider<Manager>>) {
// return getRoute(
// shouldUseMaterialRoute: useMaterialPageRoute,
// builder: (_) => WalletView(
// walletId: args.item1,
// managerProvider: args.item2,
// ),
// settings: RouteSettings(
// name: settings.name,
// ),
// );
// }
case TokenView.routeName: case TokenView.routeName:
if (args is Tuple2<String, String>) { if (args is Tuple2<String, ChangeNotifierProvider<TokenManager>>) {
return getRoute( return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute, shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => TokenView( builder: (_) => TokenView(
walletAddress: args.item1, contractAddress: args.item1,
contractAddress: args.item2, managerProvider: args.item2,
// walletAddress: args.item3,
// contractAddress: args.item4,
), ),
settings: RouteSettings( settings: RouteSettings(
name: settings.name, name: settings.name,

File diff suppressed because it is too large Load diff

View file

@ -84,9 +84,19 @@ class GasTracker {
class EthereumWallet extends CoinServiceAPI { class EthereumWallet extends CoinServiceAPI {
NodeModel? _ethNode; NodeModel? _ethNode;
final _gasLimit = 21000; final _gasLimit = 21000;
final _blockExplorer = "https://blockscout.com/eth/mainnet/api?"; // final _blockExplorer = "https://blockscout.com/eth/mainnet/api?";
final _blockExplorer = "https://api.etherscan.io/api?";
final _gasTrackerUrl = "https://beaconcha.in/api/v1/execution/gasnow"; final _gasTrackerUrl = "https://beaconcha.in/api/v1/execution/gasnow";
@override
String get walletId => _walletId;
late String _walletId;
late String _walletName;
late Coin _coin;
Timer? timer;
Timer? _networkAliveTimer;
@override @override
set isFavorite(bool markFavorite) { set isFavorite(bool markFavorite) {
DB.instance.put<dynamic>( DB.instance.put<dynamic>(
@ -140,7 +150,6 @@ class EthereumWallet extends CoinServiceAPI {
_walletId = walletId; _walletId = walletId;
_walletName = walletName; _walletName = walletName;
_coin = coin; _coin = coin;
_priceAPI = priceAPI ?? PriceAPI(Client()); _priceAPI = priceAPI ?? PriceAPI(Client());
_secureStore = secureStore; _secureStore = secureStore;
} }
@ -167,11 +176,6 @@ class EthereumWallet extends CoinServiceAPI {
@override @override
String get walletName => _walletName; String get walletName => _walletName;
late String _walletName;
late Coin _coin;
Timer? timer;
Timer? _networkAliveTimer;
@override @override
Future<List<String>> get allOwnAddresses => Future<List<String>> get allOwnAddresses =>
@ -339,7 +343,9 @@ class EthereumWallet extends CoinServiceAPI {
await DB.instance await DB.instance
.put<dynamic>(boxName: walletId, key: "id", value: _walletId); .put<dynamic>(boxName: walletId, key: "id", value: _walletId);
await DB.instance.put<dynamic>( await DB.instance.put<dynamic>(
boxName: walletId, key: 'receivingAddresses', value: ["0"]); boxName: walletId,
key: 'receivingAddresses',
value: [_credentials.address.toString()]);
await DB.instance await DB.instance
.put<dynamic>(boxName: walletId, key: "receivingIndex", value: 0); .put<dynamic>(boxName: walletId, key: "receivingIndex", value: 0);
await DB.instance await DB.instance
@ -480,6 +486,46 @@ class EthereumWallet extends CoinServiceAPI {
_credentials = EthPrivateKey.fromHex(StringToHex.toHexString(mnemonic)); _credentials = EthPrivateKey.fromHex(StringToHex.toHexString(mnemonic));
print(_credentials.address);
//Get ERC-20 transactions for wallet (So we can get the and save wallet's ERC-20 TOKENS
AddressTransaction tokenTransactions = await fetchAddressTransactions(
_credentials.address.toString(), "tokentx");
var tokenMap = {};
List<Map<dynamic, dynamic>> tokensList = [];
if (tokenTransactions.message == "OK") {
final allTxs = tokenTransactions.result;
print("RESULT IS $allTxs");
allTxs.forEach((element) {
String key = element["tokenSymbol"] as String;
tokenMap[key] = {};
tokenMap[key]["balance"] = 0;
if (tokenMap.containsKey(key)) {
tokenMap[key]["contractAddress"] = element["contractAddress"];
tokenMap[key]["decimals"] = element["tokenDecimal"];
tokenMap[key]["name"] = element["tokenName"];
tokenMap[key]["symbol"] = element["tokenSymbol"];
if (element["to"] == _credentials.address.toString()) {
tokenMap[key]["balance"] += int.parse(element["value"] as String);
} else {
tokenMap[key]["balance"] -= int.parse(element["value"] as String);
}
}
});
tokenMap.forEach((key, value) {
//Create New token
tokensList.add(value as Map<dynamic, dynamic>);
});
await _secureStore.write(
key: '${_walletId}_tokens', value: tokensList.toString());
}
print("THIS WALLET TOKENS IS $tokenMap");
print("ALL TOKENS LIST IS $tokensList");
await DB.instance await DB.instance
.put<dynamic>(boxName: walletId, key: "id", value: _walletId); .put<dynamic>(boxName: walletId, key: "id", value: _walletId);
await DB.instance await DB.instance
@ -528,8 +574,8 @@ class EthereumWallet extends CoinServiceAPI {
} }
if (!needsRefresh) { if (!needsRefresh) {
var allOwnAddresses = await _fetchAllOwnAddresses(); var allOwnAddresses = await _fetchAllOwnAddresses();
AddressTransaction addressTransactions = AddressTransaction addressTransactions = await fetchAddressTransactions(
await fetchAddressTransactions(allOwnAddresses.elementAt(0)); allOwnAddresses.elementAt(0), "txlist");
final txData = await transactionData; final txData = await transactionData;
if (addressTransactions.message == "OK") { if (addressTransactions.message == "OK") {
final allTxs = addressTransactions.result; final allTxs = addressTransactions.result;
@ -854,9 +900,10 @@ class EthereumWallet extends CoinServiceAPI {
return isValidEthereumAddress(address); return isValidEthereumAddress(address);
} }
Future<AddressTransaction> fetchAddressTransactions(String address) async { Future<AddressTransaction> fetchAddressTransactions(
String address, String action) async {
final response = await get(Uri.parse( final response = await get(Uri.parse(
"${_blockExplorer}module=account&action=txlist&address=$address")); "${_blockExplorer}module=account&action=$action&address=$address&apikey=EG6J7RJIQVSTP2BS59D3TY2G55YHS5F2HP"));
if (response.statusCode == 200) { if (response.statusCode == 200) {
return AddressTransaction.fromJson( return AddressTransaction.fromJson(
@ -881,7 +928,8 @@ class EthereumWallet extends CoinServiceAPI {
Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
final List<Map<String, dynamic>> midSortedArray = []; final List<Map<String, dynamic>> midSortedArray = [];
AddressTransaction txs = await fetchAddressTransactions(thisAddress); AddressTransaction txs =
await fetchAddressTransactions(thisAddress, "txlist");
if (txs.message == "OK") { if (txs.message == "OK") {
final allTxs = txs.result; final allTxs = txs.result;
@ -991,13 +1039,11 @@ class EthereumWallet extends CoinServiceAPI {
return txModel; return txModel;
} }
@override
String get walletId => _walletId;
late String _walletId;
@override @override
set walletName(String newName) => _walletName = newName; set walletName(String newName) => _walletName = newName;
// Future<String>
void stopNetworkAlivePinging() { void stopNetworkAlivePinging() {
_networkAliveTimer?.cancel(); _networkAliveTimer?.cancel();
_networkAliveTimer = null; _networkAliveTimer = null;

374
lib/services/tokens.dart Normal file
View file

@ -0,0 +1,374 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/hive/db.dart';
import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/services/coins/coin_service.dart';
import 'package:stackwallet/services/coins/manager.dart';
import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/services/tokens/token_manager.dart';
import 'package:stackwallet/services/tokens_service.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart';
import 'package:stackwallet/services/wallets_service.dart';
import 'package:stackwallet/utilities/default_nodes.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/sync_type_enum.dart';
import 'package:stackwallet/utilities/listenable_list.dart';
import 'package:stackwallet/utilities/listenable_map.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
import 'package:tuple/tuple.dart';
// final ListenableList<ChangeNotifierProvider<Manager>> _nonFavorites =
// ListenableList();
// ListenableList<ChangeNotifierProvider<Manager>> get nonFavorites =>
// _nonFavorites;
//
// final ListenableList<ChangeNotifierProvider<Manager>> _favorites =
// ListenableList();
// ListenableList<ChangeNotifierProvider<Manager>> get favorites => _favorites;
class Tokens extends ChangeNotifier {
Tokens._private();
@override
dispose() {
debugPrint("Tokens dispose was called!!");
super.dispose();
}
static final Tokens _sharedInstance = Tokens._private();
static Tokens get sharedInstance => _sharedInstance;
late TokensService tokensService;
// late NodeService nodeService;
// mirrored maps for access to reading managers without using riverpod ref
static final ListenableMap<String, ChangeNotifierProvider<TokenManager>>
_managerProviderMap = ListenableMap();
static final ListenableMap<String, TokenManager> _managerMap =
ListenableMap();
// bool get hasWallets => _managerProviderMap.isNotEmpty;
List<ChangeNotifierProvider<TokenManager>> get managerProviders =>
_managerProviderMap.values.toList(growable: false);
List<TokenManager> get managers => _managerMap.values.toList(growable: false);
// List<String> getWalletIdsFor({required Coin coin}) {
// final List<String> result = [];
// for (final manager in _managerMap.values) {
// if (manager.coin == coin) {
// result.add(manager.walletId);
// }
// }
// return result;
// }
// Map<Coin, List<ChangeNotifierProvider<Manager>>> getManagerProvidersByCoin() {
// print("DOES THIS GET HERE?????");
// Map<Coin, List<ChangeNotifierProvider<Manager>>> result = {};
// for (final manager in _managerMap.values) {
// if (result[manager.coin] == null) {
// result[manager.coin] = [];
// }
// result[manager.coin]!.add(_managerProviderMap[manager.walletId]
// as ChangeNotifierProvider<Manager>);
// }
// return result;
// }
// List<ChangeNotifierProvider<Manager>> getManagerProvidersForCoin(Coin coin) {
// List<ChangeNotifierProvider<Manager>> result = [];
// for (final manager in _managerMap.values) {
// if (manager.coin == coin) {
// result.add(_managerProviderMap[manager.walletId]
// as ChangeNotifierProvider<Manager>);
// }
// }
// return result;
// }
ChangeNotifierProvider<TokenManager> getManagerProvider(
String contractAddress) {
print("WALLET ID HERE IS ${_managerProviderMap.length}");
return _managerProviderMap[contractAddress]
as ChangeNotifierProvider<TokenManager>;
}
TokenManager getManager(String contractAddress) {
return _managerMap[contractAddress] as TokenManager;
}
void addToken(
{required String contractAddress, required TokenManager manager}) {
_managerMap.add(contractAddress, manager, true);
_managerProviderMap.add(contractAddress,
ChangeNotifierProvider<TokenManager>((_) => manager), true);
notifyListeners();
}
//
// void removeWallet({required String walletId}) {
// if (_managerProviderMap[walletId] == null) {
// Logging.instance.log(
// "Wallets.removeWallet($walletId) failed. ManagerProvider with $walletId not found!",
// level: LogLevel.Warning);
// return;
// }
//
// final provider = _managerProviderMap[walletId]!;
//
// // in both non and favorites for removal
// _favorites.remove(provider, true);
// _nonFavorites.remove(provider, true);
//
// _managerProviderMap.remove(walletId, true);
// _managerMap.remove(walletId, true)!.exitCurrentWallet();
//
// notifyListeners();
// }
static bool hasLoaded = false;
Future<void> _initLinearly(
List<Tuple2<TokenManager, bool>> tuples,
) async {
for (final tuple in tuples) {
await tuple.item1.initializeExisting();
if (tuple.item2 && !tuple.item1.shouldAutoSync) {
tuple.item1.shouldAutoSync = true;
}
}
}
static int _count = 0;
Future<void> load(Prefs prefs) async {
debugPrint("++++++++++++++ Tokens().load() called: ${++_count} times");
if (hasLoaded) {
return;
}
hasLoaded = true;
// clear out any wallet hive boxes where the wallet was deleted in previous app run
// for (final walletId in DB.instance
// .values<String>(boxName: DB.boxNameWalletsToDeleteOnStart)) {
// await DB.instance.deleteBoxFromDisk(boxName: walletId);
// }
// // clear list
// await DB.instance
// .deleteAll<String>(boxName: DB.boxNameWalletsToDeleteOnStart);
//
// final map = await walletsService.walletNames;
// List<Future<dynamic>> walletInitFutures = [];
// List<Tuple2<Manager, bool>> walletsToInitLinearly = [];
// final favIdList = await walletsService.getFavoriteWalletIds();
// List<String> walletIdsToEnableAutoSync = [];
// bool shouldAutoSyncAll = false;
// switch (prefs.syncType) {
// case SyncingType.currentWalletOnly:
// // do nothing as this will be set when going into a wallet from the main screen
// break;
// case SyncingType.selectedWalletsAtStartup:
// walletIdsToEnableAutoSync.addAll(prefs.walletIdsSyncOnStartup);
// break;
// case SyncingType.allWalletsOnStartup:
// shouldAutoSyncAll = true;
// break;
// }
// for (final entry in map.entries) {
// try {
// final walletId = entry.value.walletId;
//
// late final bool isVerified;
// try {
// isVerified =
// await walletsService.isMnemonicVerified(walletId: walletId);
// } catch (e, s) {
// Logging.instance.log("$e $s", level: LogLevel.Warning);
// isVerified = false;
// }
//
// Logging.instance.log(
// "LOADING WALLET: ${entry.value.toString()} IS VERIFIED: $isVerified",
// level: LogLevel.Info);
// if (isVerified) {
// if (_managerMap[walletId] == null &&
// _managerProviderMap[walletId] == null) {
// final coin = entry.value.coin;
// NodeModel node = nodeService.getPrimaryNodeFor(coin: coin) ??
// DefaultNodes.getNodeFor(coin);
// // ElectrumXNode? node = await nodeService.getCurrentNode(coin: coin);
//
// // folowing shouldn't be needed as the defaults get saved on init
// // if (node == null) {
// // node = DefaultNodes.getNodeFor(coin);
// //
// // // save default node
// // nodeService.add(node, false);
// // }
//
// final txTracker =
// TransactionNotificationTracker(walletId: walletId);
//
// final failovers = nodeService.failoverNodesFor(coin: coin);
//
// // load wallet
// final wallet = CoinServiceAPI.from(
// coin,
// walletId,
// entry.value.name,
// nodeService.secureStorageInterface,
// node,
// txTracker,
// prefs,
// failovers,
// );
//
// final manager = Manager(wallet);
//
// final shouldSetAutoSync = shouldAutoSyncAll ||
// walletIdsToEnableAutoSync.contains(manager.walletId);
//
// if (manager.coin == Coin.monero || manager.coin == Coin.wownero) {
// walletsToInitLinearly.add(Tuple2(manager, shouldSetAutoSync));
// } else {
// walletInitFutures.add(manager.initializeExisting().then((value) {
// if (shouldSetAutoSync) {
// manager.shouldAutoSync = true;
// }
// }));
// }
//
// _managerMap.add(walletId, manager, false);
//
// final managerProvider =
// ChangeNotifierProvider<Manager>((_) => manager);
// _managerProviderMap.add(walletId, managerProvider, false);
//
// final favIndex = favIdList.indexOf(walletId);
//
// if (favIndex == -1) {
// _nonFavorites.add(managerProvider, true);
// } else {
// // it is a favorite
// if (favIndex >= _favorites.length) {
// _favorites.add(managerProvider, true);
// } else {
// _favorites.insert(favIndex, managerProvider, true);
// }
// }
// }
// } else {
// // wallet creation was not completed by user so we remove it completely
// await walletsService.deleteWallet(entry.value.name, false);
// }
// } catch (e, s) {
// Logging.instance.log("$e $s", level: LogLevel.Fatal);
// continue;
// }
// }
// if (walletInitFutures.isNotEmpty && walletsToInitLinearly.isNotEmpty) {
// await Future.wait([
// _initLinearly(walletsToInitLinearly),
// ...walletInitFutures,
// ]);
// notifyListeners();
// } else if (walletInitFutures.isNotEmpty) {
// await Future.wait(walletInitFutures);
// notifyListeners();
// } else if (walletsToInitLinearly.isNotEmpty) {
// await _initLinearly(walletsToInitLinearly);
// notifyListeners();
// }
}
// Future<void> loadAfterStackRestore(
// Prefs prefs, List<Manager> managers) async {
// List<Future<dynamic>> walletInitFutures = [];
// List<Tuple2<Manager, bool>> walletsToInitLinearly = [];
//
// final favIdList = await walletsService.getFavoriteWalletIds();
//
// List<String> walletIdsToEnableAutoSync = [];
// bool shouldAutoSyncAll = false;
// switch (prefs.syncType) {
// case SyncingType.currentWalletOnly:
// // do nothing as this will be set when going into a wallet from the main screen
// break;
// case SyncingType.selectedWalletsAtStartup:
// walletIdsToEnableAutoSync.addAll(prefs.walletIdsSyncOnStartup);
// break;
// case SyncingType.allWalletsOnStartup:
// shouldAutoSyncAll = true;
// break;
// }
//
// for (final manager in managers) {
// final walletId = manager.walletId;
//
// final isVerified =
// await walletsService.isMnemonicVerified(walletId: walletId);
// debugPrint(
// "LOADING RESTORED WALLET: ${manager.walletName} ${manager.walletId} IS VERIFIED: $isVerified");
//
// if (isVerified) {
// if (_managerMap[walletId] == null &&
// _managerProviderMap[walletId] == null) {
// final shouldSetAutoSync = shouldAutoSyncAll ||
// walletIdsToEnableAutoSync.contains(manager.walletId);
//
// if (manager.coin == Coin.monero || manager.coin == Coin.wownero) {
// walletsToInitLinearly.add(Tuple2(manager, shouldSetAutoSync));
// } else {
// walletInitFutures.add(manager.initializeExisting().then((value) {
// if (shouldSetAutoSync) {
// manager.shouldAutoSync = true;
// }
// }));
// }
//
// _managerMap.add(walletId, manager, false);
//
// final managerProvider =
// ChangeNotifierProvider<Manager>((_) => manager);
// _managerProviderMap.add(walletId, managerProvider, false);
//
// final favIndex = favIdList.indexOf(walletId);
//
// if (favIndex == -1) {
// _nonFavorites.add(managerProvider, true);
// } else {
// // it is a favorite
// if (favIndex >= _favorites.length) {
// _favorites.add(managerProvider, true);
// } else {
// _favorites.insert(favIndex, managerProvider, true);
// }
// }
// }
// } else {
// // wallet creation was not completed by user so we remove it completely
// await walletsService.deleteWallet(manager.walletName, false);
// }
// }
//
// if (walletInitFutures.isNotEmpty && walletsToInitLinearly.isNotEmpty) {
// await Future.wait([
// _initLinearly(walletsToInitLinearly),
// ...walletInitFutures,
// ]);
// notifyListeners();
// } else if (walletInitFutures.isNotEmpty) {
// await Future.wait(walletInitFutures);
// notifyListeners();
// } else if (walletsToInitLinearly.isNotEmpty) {
// await _initLinearly(walletsToInitLinearly);
// notifyListeners();
// }
// }
}

View file

@ -0,0 +1,127 @@
import 'package:decimal/decimal.dart';
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/models/paymint/transactions_model.dart';
import 'package:stackwallet/services/tokens/token_service.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart';
class EthereumToken extends TokenServiceAPI {
@override
late bool shouldAutoSync;
late String _walletId;
late String _contractAddress;
late SecureStorageInterface _secureStore;
late final TransactionNotificationTracker txTracker;
EthereumToken({
required String contractAddress,
required String walletId,
required SecureStorageInterface secureStore,
required TransactionNotificationTracker tracker,
}) {
txTracker = tracker;
_walletId = walletId;
_contractAddress = contractAddress;
_secureStore = secureStore;
}
@override
// TODO: implement allOwnAddresses
Future<List<String>> get allOwnAddresses => throw UnimplementedError();
@override
// TODO: implement availableBalance
Future<Decimal> get availableBalance => throw UnimplementedError();
@override
// TODO: implement balanceMinusMaxFee
Future<Decimal> get balanceMinusMaxFee => throw UnimplementedError();
@override
// TODO: implement coin
Coin get coin => throw UnimplementedError();
@override
Future<String> confirmSend({required Map<String, dynamic> txData}) {
// TODO: implement confirmSend
throw UnimplementedError();
}
@override
// TODO: implement currentReceivingAddress
Future<String> get currentReceivingAddress => throw UnimplementedError();
@override
Future<int> estimateFeeFor(int satoshiAmount, int feeRate) {
// TODO: implement estimateFeeFor
throw UnimplementedError();
}
@override
// TODO: implement fees
Future<FeeObject> get fees => throw UnimplementedError();
@override
Future<void> initializeExisting() {
// TODO: implement initializeExisting
throw UnimplementedError();
}
@override
Future<void> initializeNew() async {
throw UnimplementedError();
}
@override
// TODO: implement isConnected
bool get isConnected => throw UnimplementedError();
@override
// TODO: implement isRefreshing
bool get isRefreshing => throw UnimplementedError();
@override
// TODO: implement maxFee
Future<int> get maxFee => throw UnimplementedError();
@override
// TODO: implement pendingBalance
Future<Decimal> get pendingBalance => throw UnimplementedError();
@override
Future<Map<String, dynamic>> prepareSend(
{required String address,
required int satoshiAmount,
Map<String, dynamic>? args}) {
// TODO: implement prepareSend
throw UnimplementedError();
}
@override
Future<void> refresh() {
// TODO: implement refresh
throw UnimplementedError();
}
@override
// TODO: implement totalBalance
Future<Decimal> get totalBalance => throw UnimplementedError();
@override
// TODO: implement transactionData
Future<TransactionData> get transactionData => throw UnimplementedError();
@override
Future<void> updateSentCachedTxData(Map<String, dynamic> txData) {
// TODO: implement updateSentCachedTxData
throw UnimplementedError();
}
@override
bool validateAddress(String address) {
// TODO: implement validateAddress
throw UnimplementedError();
}
}

View file

@ -0,0 +1,130 @@
import 'dart:async';
import 'package:decimal/decimal.dart';
import 'package:event_bus/event_bus.dart';
import 'package:flutter/material.dart';
import 'package:stackwallet/models/models.dart';
import 'package:stackwallet/services/coins/coin_service.dart';
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.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/tokens/token_service.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
class TokenManager with ChangeNotifier {
final TokenServiceAPI _currentToken;
StreamSubscription<dynamic>? _backgroundRefreshListener;
/// optional eventbus parameter for testing only
TokenManager(this._currentToken, [EventBus? globalEventBusForTesting]) {
final bus = globalEventBusForTesting ?? GlobalEventBus.instance;
_backgroundRefreshListener = bus.on<UpdatedInBackgroundEvent>().listen(
(event) async {
// if (event.walletId == walletId) {
// notifyListeners();
// Logging.instance.log(
// "UpdatedInBackgroundEvent activated notifyListeners() in Manager instance $hashCode $walletName with: ${event.message}",
// level: LogLevel.Info);
// }
},
);
}
TokenServiceAPI get token => _currentToken;
bool get hasBackgroundRefreshListener => _backgroundRefreshListener != null;
bool get isRefreshing => _currentToken.isRefreshing;
bool get shouldAutoSync => _currentToken.shouldAutoSync;
set shouldAutoSync(bool shouldAutoSync) =>
_currentToken.shouldAutoSync = shouldAutoSync;
Future<Map<String, dynamic>> prepareSend({
required String address,
required int satoshiAmount,
Map<String, dynamic>? args,
}) async {
try {
final txInfo = await _currentToken.prepareSend(
address: address,
satoshiAmount: satoshiAmount,
args: args,
);
// notifyListeners();
return txInfo;
} catch (e) {
// rethrow to pass error in alert
rethrow;
}
}
Future<String> confirmSend({required Map<String, dynamic> txData}) async {
try {
final txid = await _currentToken.confirmSend(txData: txData);
txData["txid"] = txid;
await _currentToken.updateSentCachedTxData(txData);
notifyListeners();
return txid;
} catch (e) {
// rethrow to pass error in alert
rethrow;
}
}
Future<FeeObject> get fees => _currentToken.fees;
Future<int> get maxFee => _currentToken.maxFee;
Future<String> get currentReceivingAddress =>
_currentToken.currentReceivingAddress;
// Future<String> get currentLegacyReceivingAddress =>
// _currentWallet.currentLegacyReceivingAddress;
Future<Decimal> get availableBalance async {
_cachedAvailableBalance = await _currentToken.availableBalance;
return _cachedAvailableBalance;
}
Decimal _cachedAvailableBalance = Decimal.zero;
Decimal get cachedAvailableBalance => _cachedAvailableBalance;
Future<Decimal> get pendingBalance => _currentToken.pendingBalance;
Future<Decimal> get balanceMinusMaxFee => _currentToken.balanceMinusMaxFee;
Future<Decimal> get totalBalance async {
_cachedTotalBalance = await _currentToken.totalBalance;
return _cachedTotalBalance;
}
Decimal _cachedTotalBalance = Decimal.zero;
Decimal get cachedTotalBalance => _cachedTotalBalance;
Future<List<String>> get allOwnAddresses => _currentToken.allOwnAddresses;
Future<TransactionData> get transactionData => _currentToken.transactionData;
Future<void> refresh() async {
await _currentToken.refresh();
notifyListeners();
}
bool validateAddress(String address) =>
_currentToken.validateAddress(address);
Future<void> initializeNew() => _currentToken.initializeNew();
Future<void> initializeExisting() => _currentToken.initializeExisting();
Future<bool> isOwnAddress(String address) async {
final allOwnAddresses = await this.allOwnAddresses;
return allOwnAddresses.contains(address);
}
bool get isConnected => _currentToken.isConnected;
Future<int> estimateFeeFor(int satoshiAmount, int feeRate) async {
return _currentToken.estimateFeeFor(satoshiAmount, feeRate);
}
}

View file

@ -0,0 +1,73 @@
import 'package:decimal/decimal.dart';
import 'package:stackwallet/models/models.dart';
import 'package:stackwallet/services/tokens/ethereum/ethereum_token.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/utilities/prefs.dart';
abstract class TokenServiceAPI {
TokenServiceAPI();
factory TokenServiceAPI.from(
String contractAddress,
String walletId,
SecureStorageInterface secureStorageInterface,
TransactionNotificationTracker tracker,
Prefs prefs,
) {
return EthereumToken(
contractAddress: contractAddress,
walletId: walletId,
secureStore: secureStorageInterface,
tracker: tracker,
);
}
Coin get coin;
bool get isRefreshing;
bool get shouldAutoSync;
set shouldAutoSync(bool shouldAutoSync);
Future<Map<String, dynamic>> prepareSend({
required String address,
required int satoshiAmount,
Map<String, dynamic>? args,
});
Future<String> confirmSend({required Map<String, dynamic> txData});
Future<FeeObject> get fees;
Future<int> get maxFee;
Future<String> get currentReceivingAddress;
// Future<String> get currentLegacyReceivingAddress;
Future<Decimal> get availableBalance;
Future<Decimal> get pendingBalance;
Future<Decimal> get totalBalance;
Future<Decimal> get balanceMinusMaxFee;
Future<List<String>> get allOwnAddresses;
Future<TransactionData> get transactionData;
Future<void> refresh();
// String get walletName;
// String get walletId;
bool validateAddress(String address);
Future<void> initializeNew();
Future<void> initializeExisting();
// void Function(bool isActive)? onIsActiveWalletChanged;
bool get isConnected;
Future<int> estimateFeeFor(int satoshiAmount, int feeRate);
// used for electrumx coins
Future<void> updateSentCachedTxData(Map<String, dynamic> txData);
}

View file

@ -0,0 +1,432 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_libmonero/monero/monero.dart';
import 'package:flutter_libmonero/wownero/wownero.dart';
import 'package:stackwallet/hive/db.dart';
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
import 'package:stackwallet/services/notifications_service.dart';
import 'package:stackwallet/services/trade_sent_from_stack_service.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:uuid/uuid.dart';
class TokenInfo {
final Coin coin;
final String walletId;
final String contractAddress;
const TokenInfo(
{required this.coin,
required this.walletId,
required this.contractAddress});
factory TokenInfo.fromJson(Map<String, dynamic> jsonObject) {
return TokenInfo(
coin: Coin.values.byName(jsonObject["coin"] as String),
walletId: jsonObject["id"] as String,
contractAddress: jsonObject["contractAddress"] as String,
);
}
Map<String, String> toMap() {
return {
"contractAddress": contractAddress,
"walletId": walletId,
"coin": coin.name,
};
}
String toJsonString() {
return jsonEncode(toMap());
}
@override
String toString() {
return "TokenInfo: ${toJsonString()}";
}
}
class TokensService extends ChangeNotifier {
late final SecureStorageInterface _secureStore;
// Future<Map<String, TokenInfo>>? _walletNames;
// Future<Map<String, TokenInfo>> get walletNames =>
// _walletNames ??= _fetchWalletNames();
TokensService({
required SecureStorageInterface secureStorageInterface,
}) {
_secureStore = secureStorageInterface;
}
// Future<Coin> getWalletCryptoCurrency({required String walletName}) async {
// final id = await getWalletId(walletName);
// final currency = DB.instance.get<dynamic>(
// boxName: DB.boxNameAllWalletsData, key: "${id}_cryptoCurrency");
// return Coin.values.byName(currency as String);
// }
// Future<bool> renameWallet({
// required String from,
// required String to,
// required bool shouldNotifyListeners,
// }) async {
// if (from == to) {
// return true;
// }
//
// final walletInfo = DB.instance
// .get<dynamic>(boxName: DB.boxNameAllWalletsData, key: 'names') as Map;
//
// final info = walletInfo.values.firstWhere(
// (element) => element['name'] == from,
// orElse: () => <String, String>{}) as Map;
//
// if (info.isEmpty) {
// // tried to rename a non existing wallet
// Logging.instance
// .log("Tried to rename a non existing wallet!", level: LogLevel.Error);
// return false;
// }
//
// if (from != to &&
// (walletInfo.values.firstWhere((element) => element['name'] == to,
// orElse: () => <String, String>{}) as Map)
// .isNotEmpty) {
// // name already exists
// Logging.instance.log("wallet with name \"$to\" already exists!",
// level: LogLevel.Error);
// return false;
// }
//
// info["name"] = to;
// walletInfo[info['id']] = info;
//
// await DB.instance.put<dynamic>(
// boxName: DB.boxNameAllWalletsData, key: 'names', value: walletInfo);
// await refreshWallets(shouldNotifyListeners);
// return true;
// }
// Future<Map<String, WalletInfo>> _fetchWalletNames() async {
// final names = DB.instance
// .get<dynamic>(boxName: DB.boxNameAllWalletsData, key: 'names') as Map?;
// if (names == null) {
// Logging.instance.log(
// "Fetched wallet 'names' returned null. Setting initializing 'names'",
// level: LogLevel.Info);
// await DB.instance.put<dynamic>(
// boxName: DB.boxNameAllWalletsData,
// key: 'names',
// value: <String, dynamic>{});
// return {};
// }
// Logging.instance.log("Fetched wallet names: $names", level: LogLevel.Info);
// final mapped = Map<String, dynamic>.from(names);
// mapped.removeWhere((name, dyn) {
// final jsonObject = Map<String, dynamic>.from(dyn as Map);
// try {
// Coin.values.byName(jsonObject["coin"] as String);
// return false;
// } catch (e, s) {
// Logging.instance.log("Error, ${jsonObject["coin"]} does not exist",
// level: LogLevel.Error);
// return true;
// }
// });
//
// return mapped.map((name, dyn) => MapEntry(
// name, WalletInfo.fromJson(Map<String, dynamic>.from(dyn as Map))));
// }
// Future<void> addExistingStackWallet({
// required String name,
// required String walletId,
// required Coin coin,
// required bool shouldNotifyListeners,
// }) async {
// final _names = DB.instance
// .get<dynamic>(boxName: DB.boxNameAllWalletsData, key: 'names') as Map?;
//
// Map<String, dynamic> names;
// if (_names == null) {
// names = {};
// } else {
// names = Map<String, dynamic>.from(_names);
// }
//
// if (names.keys.contains(walletId)) {
// throw Exception("Wallet with walletId \"$walletId\" already exists!");
// }
// if (names.values.where((element) => element['name'] == name).isNotEmpty) {
// throw Exception("Wallet with name \"$name\" already exists!");
// }
//
// names[walletId] = {
// "id": walletId,
// "coin": coin.name,
// "name": name,
// };
//
// await DB.instance.put<dynamic>(
// boxName: DB.boxNameAllWalletsData, key: 'names', value: names);
// await DB.instance.put<dynamic>(
// boxName: DB.boxNameAllWalletsData,
// key: "${walletId}_cryptoCurrency",
// value: coin.name);
// await DB.instance.put<dynamic>(
// boxName: DB.boxNameAllWalletsData,
// key: "${walletId}_mnemonicHasBeenVerified",
// value: false);
// await DB.instance.addWalletBox(walletId: walletId);
// await refreshWallets(shouldNotifyListeners);
// }
// /// returns the new walletId if successful, otherwise null
// Future<String?> addNewWallet({
// required String name,
// required Coin coin,
// required bool shouldNotifyListeners,
// }) async {
// final _names = DB.instance
// .get<dynamic>(boxName: DB.boxNameAllWalletsData, key: 'names') as Map?;
//
// Map<String, dynamic> names;
// if (_names == null) {
// names = {};
// } else {
// names = Map<String, dynamic>.from(_names);
// }
//
// // Prevent overwriting or storing empty names
// if (name.isEmpty ||
// names.values.where((element) => element['name'] == name).isNotEmpty) {
// return null;
// }
//
// final id = const Uuid().v1();
// names[id] = {
// "id": id,
// "coin": coin.name,
// "name": name,
// };
//
// await DB.instance.put<dynamic>(
// boxName: DB.boxNameAllWalletsData, key: 'names', value: names);
// await DB.instance.put<dynamic>(
// boxName: DB.boxNameAllWalletsData,
// key: "${id}_cryptoCurrency",
// value: coin.name);
// await DB.instance.put<dynamic>(
// boxName: DB.boxNameAllWalletsData,
// key: "${id}_mnemonicHasBeenVerified",
// value: false);
// await DB.instance.addWalletBox(walletId: id);
// await refreshWallets(shouldNotifyListeners);
// return id;
// }
// Future<List<String>> getFavoriteWalletIds() async {
// return DB.instance
// .values<String>(boxName: DB.boxNameFavoriteWallets)
// .toList();
// }
// Future<void> saveFavoriteWalletIds(List<String> walletIds) async {
// await DB.instance.deleteAll<String>(boxName: DB.boxNameFavoriteWallets);
// await DB.instance
// .addAll(boxName: DB.boxNameFavoriteWallets, values: walletIds);
// debugPrint("saveFavoriteWalletIds list: $walletIds");
// }
//
// Future<void> addFavorite(String walletId) async {
// final list = await getFavoriteWalletIds();
// if (!list.contains(walletId)) {
// list.add(walletId);
// }
// await saveFavoriteWalletIds(list);
// }
//
// Future<void> removeFavorite(String walletId) async {
// final list = await getFavoriteWalletIds();
// list.remove(walletId);
// await saveFavoriteWalletIds(list);
// }
//
// Future<void> moveFavorite({
// required int fromIndex,
// required int toIndex,
// }) async {
// final list = await getFavoriteWalletIds();
// if (fromIndex < toIndex) {
// toIndex -= 1;
// }
// final walletId = list.removeAt(fromIndex);
// list.insert(toIndex, walletId);
// await saveFavoriteWalletIds(list);
// }
//
// Future<bool> checkForDuplicate(String name) async {
// final names = DB.instance
// .get<dynamic>(boxName: DB.boxNameAllWalletsData, key: 'names') as Map?;
// if (names == null) return false;
//
// return names.values.where((element) => element['name'] == name).isNotEmpty;
// }
Future<String?> getWalletId(String walletName) async {
final names = DB.instance
.get<dynamic>(boxName: DB.boxNameAllWalletsData, key: 'names') as Map;
final shells =
names.values.where((element) => element['name'] == walletName);
if (shells.isEmpty) {
return null;
}
return shells.first["id"] as String;
}
// Future<bool> isMnemonicVerified({required String walletId}) async {
// final isVerified = DB.instance.get<dynamic>(
// boxName: DB.boxNameAllWalletsData,
// key: "${walletId}_mnemonicHasBeenVerified") as bool?;
//
// if (isVerified == null) {
// Logging.instance.log(
// "isMnemonicVerified(walletId: $walletId) returned null which should never happen!",
// level: LogLevel.Error,
// );
// throw Exception(
// "isMnemonicVerified(walletId: $walletId) returned null which should never happen!");
// } else {
// return isVerified;
// }
// }
//
// Future<void> setMnemonicVerified({required String walletId}) async {
// final isVerified = DB.instance.get<dynamic>(
// boxName: DB.boxNameAllWalletsData,
// key: "${walletId}_mnemonicHasBeenVerified") as bool?;
//
// if (isVerified == null) {
// Logging.instance.log(
// "setMnemonicVerified(walletId: $walletId) tried running on non existent wallet!",
// level: LogLevel.Error,
// );
// throw Exception(
// "setMnemonicVerified(walletId: $walletId) tried running on non existent wallet!");
// } else if (isVerified) {
// Logging.instance.log(
// "setMnemonicVerified(walletId: $walletId) tried running on already verified wallet!",
// level: LogLevel.Error,
// );
// throw Exception(
// "setMnemonicVerified(walletId: $walletId) tried running on already verified wallet!");
// } else {
// await DB.instance.put<dynamic>(
// boxName: DB.boxNameAllWalletsData,
// key: "${walletId}_mnemonicHasBeenVerified",
// value: true);
// Logging.instance.log(
// "setMnemonicVerified(walletId: $walletId) successful",
// level: LogLevel.Error,
// );
// }
// }
//
// // pin + mnemonic as well as anything else in secureStore
// Future<int> deleteWallet(String name, bool shouldNotifyListeners) async {
// final names = DB.instance.get<dynamic>(
// boxName: DB.boxNameAllWalletsData, key: 'names') as Map? ??
// {};
//
// final walletId = await getWalletId(name);
// if (walletId == null) {
// return 3;
// }
//
// Logging.instance.log(
// "deleteWallet called with name=$name and id=$walletId",
// level: LogLevel.Warning,
// );
//
// final shell = names.remove(walletId);
//
// if (shell == null) {
// return 0;
// }
//
// // TODO delete derivations!!!
// await _secureStore.delete(key: "${walletId}_pin");
// await _secureStore.delete(key: "${walletId}_mnemonic");
//
// await DB.instance.delete<dynamic>(
// boxName: DB.boxNameAllWalletsData, key: "${walletId}_cryptoCurrency");
// await DB.instance.delete<dynamic>(
// boxName: DB.boxNameAllWalletsData,
// key: "${walletId}_mnemonicHasBeenVerified");
// if (coinFromPrettyName(shell['coin'] as String) == Coin.wownero) {
// final wowService =
// wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox);
// await wowService.remove(walletId);
// Logging.instance
// .log("monero wallet: $walletId deleted", level: LogLevel.Info);
// } else if (coinFromPrettyName(shell['coin'] as String) == Coin.monero) {
// final xmrService =
// monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox);
// await xmrService.remove(walletId);
// Logging.instance
// .log("monero wallet: $walletId deleted", level: LogLevel.Info);
// } else if (coinFromPrettyName(shell['coin'] as String) == Coin.epicCash) {
// final deleteResult =
// await deleteEpicWallet(walletId: walletId, secureStore: _secureStore);
// Logging.instance.log(
// "epic wallet: $walletId deleted with result: $deleteResult",
// level: LogLevel.Info);
// }
//
// // box data may currently still be read/written to if wallet was refreshing
// // when delete was requested so instead of deleting now we mark the wallet
// // as needs delete by adding it's id to a list which gets checked on app start
// await DB.instance.add<String>(
// boxName: DB.boxNameWalletsToDeleteOnStart, value: walletId);
//
// final lookupService = TradeSentFromStackService();
// for (final lookup in lookupService.all) {
// if (lookup.walletIds.contains(walletId)) {
// // update lookup data to reflect deleted wallet
// await lookupService.save(
// tradeWalletLookup: lookup.copyWith(
// walletIds: lookup.walletIds.where((id) => id != walletId).toList(),
// ),
// );
// }
// }
//
// // delete notifications tied to deleted wallet
// for (final notification in NotificationsService.instance.notifications) {
// if (notification.walletId == walletId) {
// await NotificationsService.instance.delete(notification, false);
// }
// }
//
// if (names.isEmpty) {
// await DB.instance.deleteAll<dynamic>(boxName: DB.boxNameAllWalletsData);
// _walletNames = Future(() => {});
// notifyListeners();
// return 2; // error code no wallets on device
// }
//
// await DB.instance.put<dynamic>(
// boxName: DB.boxNameAllWalletsData, key: 'names', value: names);
// await refreshWallets(shouldNotifyListeners);
// return 0;
// }
//
// Future<void> refreshWallets(bool shouldNotifyListeners) async {
// final newNames = await _fetchWalletNames();
// _walletNames = Future(() => newNames);
// if (shouldNotifyListeners) notifyListeners();
// }
}