mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-12-23 03:49:22 +00:00
WIP: Add wallet tokens
This commit is contained in:
parent
706cbbfa39
commit
4efd432de6
15 changed files with 1874 additions and 1673 deletions
|
@ -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/utilities/eth_commons.dart';
|
||||
|
||||
import 'package:stackwallet/services/coins/manager.dart';
|
||||
|
||||
class MyTokensView extends ConsumerStatefulWidget {
|
||||
const MyTokensView({
|
||||
Key? key,
|
||||
required this.managerProvider,
|
||||
required this.walletId,
|
||||
required this.walletAddress,
|
||||
required this.tokens,
|
||||
required this.walletName,
|
||||
}) : super(key: key);
|
||||
|
||||
static const String routeName = "/myTokens";
|
||||
final ChangeNotifierProvider<Manager> managerProvider;
|
||||
final String walletId;
|
||||
final String walletAddress;
|
||||
final List<dynamic> tokens;
|
||||
final String walletName;
|
||||
|
||||
@override
|
||||
ConsumerState<MyTokensView> createState() => _TokenDetailsViewState();
|
||||
|
@ -45,12 +49,13 @@ class MyTokensView extends ConsumerStatefulWidget {
|
|||
|
||||
class _TokenDetailsViewState extends ConsumerState<MyTokensView> {
|
||||
late final String walletAddress;
|
||||
// late final String walletName;
|
||||
late final TextEditingController _searchController;
|
||||
final searchFieldFocusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
walletAddress = widget.walletAddress;
|
||||
// walletAddress = widget.walletAddress;
|
||||
_searchController = TextEditingController();
|
||||
|
||||
super.initState();
|
||||
|
@ -101,7 +106,7 @@ class _TokenDetailsViewState extends ConsumerState<MyTokensView> {
|
|||
width: 12,
|
||||
),
|
||||
Text(
|
||||
"${widget.walletName} Tokens",
|
||||
"${ref.read(widget.managerProvider).walletName} Tokens",
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
],
|
||||
|
@ -123,7 +128,7 @@ class _TokenDetailsViewState extends ConsumerState<MyTokensView> {
|
|||
},
|
||||
),
|
||||
title: Text(
|
||||
"${widget.walletName} Tokens",
|
||||
"${ref.read(widget.managerProvider).walletName} Tokens",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
actions: [
|
||||
|
@ -261,7 +266,10 @@ class _TokenDetailsViewState extends ConsumerState<MyTokensView> {
|
|||
),
|
||||
Expanded(
|
||||
child: MyTokensList(
|
||||
tokens: widget.tokens, walletAddress: walletAddress),
|
||||
managerProvider: widget.managerProvider,
|
||||
walletId: widget.walletId,
|
||||
tokens: widget.tokens,
|
||||
walletAddress: widget.walletAddress),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.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/constants.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:tuple/tuple.dart';
|
||||
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
|
||||
import 'package:stackwallet/services/coins/manager.dart';
|
||||
|
||||
class MyTokenSelectItem extends ConsumerWidget {
|
||||
const MyTokenSelectItem(
|
||||
{Key? key,
|
||||
required this.managerProvider,
|
||||
required this.walletId,
|
||||
required this.walletAddress,
|
||||
required this.tokenData,
|
||||
required})
|
||||
: super(key: key);
|
||||
|
||||
final ChangeNotifierProvider<Manager> managerProvider;
|
||||
final String walletId;
|
||||
final String walletAddress;
|
||||
final Map<String, String> tokenData;
|
||||
|
||||
|
@ -42,9 +51,38 @@ class MyTokenSelectItem extends ConsumerWidget {
|
|||
BorderRadius.circular(Constants.size.circularBorderRadius),
|
||||
),
|
||||
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(
|
||||
TokenView.routeName,
|
||||
arguments: Tuple2(walletAddress, tokenData["contractAddress"]),
|
||||
arguments: Tuple2(
|
||||
walletId,
|
||||
ref.read(tokensChangeNotifierProvider).getManagerProvider(
|
||||
tokenData["contractAddress"] as String)),
|
||||
);
|
||||
},
|
||||
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.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 {
|
||||
const MyTokensList({
|
||||
Key? key,
|
||||
required this.managerProvider,
|
||||
required this.walletId,
|
||||
required this.tokens,
|
||||
required this.walletAddress,
|
||||
}) : super(key: key);
|
||||
|
||||
final ChangeNotifierProvider<Manager> managerProvider;
|
||||
final String walletId;
|
||||
final List<dynamic> tokens;
|
||||
final String walletAddress;
|
||||
|
||||
|
@ -23,6 +28,8 @@ class MyTokensList extends StatelessWidget {
|
|||
return Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: MyTokenSelectItem(
|
||||
managerProvider: managerProvider,
|
||||
walletId: walletId,
|
||||
walletAddress: walletAddress,
|
||||
tokenData: tokens[index] as Map<String, String>,
|
||||
),
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -783,18 +783,17 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
.read(managerProvider)
|
||||
.currentReceivingAddress;
|
||||
|
||||
final walletName = ref
|
||||
.read(managerProvider)
|
||||
.walletName;
|
||||
// String walletTokens = await
|
||||
|
||||
List<dynamic> tokens =
|
||||
await getWalletTokens(
|
||||
walletAddress);
|
||||
await getWalletTokens(await ref
|
||||
.read(managerProvider)
|
||||
.currentReceivingAddress);
|
||||
|
||||
await Navigator.of(context).pushNamed(
|
||||
MyTokensView.routeName,
|
||||
arguments: Tuple3(walletAddress,
|
||||
tokens, walletName),
|
||||
arguments: Tuple4(managerProvider,
|
||||
walletId, walletAddress, tokens),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
23
lib/providers/global/tokens_provider.dart
Normal file
23
lib/providers/global/tokens_provider.dart
Normal 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;
|
||||
});
|
20
lib/providers/global/tokens_service_provider.dart
Normal file
20
lib/providers/global/tokens_service_provider.dart
Normal 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),
|
||||
);
|
||||
});
|
|
@ -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/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/tokens/token_manager.dart';
|
||||
import 'package:stackwallet/utilities/enums/add_wallet_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
@ -1295,13 +1296,15 @@ class RouteGenerator {
|
|||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case MyTokensView.routeName:
|
||||
if (args is Tuple3<String, List<dynamic>, String>) {
|
||||
if (args is Tuple4<ChangeNotifierProvider<Manager>, String, String,
|
||||
List<dynamic>>) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => MyTokensView(
|
||||
walletAddress: args.item1,
|
||||
tokens: args.item2,
|
||||
walletName: args.item3),
|
||||
managerProvider: args.item1,
|
||||
walletId: args.item2,
|
||||
walletAddress: args.item3,
|
||||
tokens: args.item4),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
|
@ -1309,13 +1312,29 @@ class RouteGenerator {
|
|||
}
|
||||
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:
|
||||
if (args is Tuple2<String, String>) {
|
||||
if (args is Tuple2<String, ChangeNotifierProvider<TokenManager>>) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => TokenView(
|
||||
walletAddress: args.item1,
|
||||
contractAddress: args.item2,
|
||||
contractAddress: args.item1,
|
||||
managerProvider: args.item2,
|
||||
// walletAddress: args.item3,
|
||||
// contractAddress: args.item4,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -84,9 +84,19 @@ class GasTracker {
|
|||
class EthereumWallet extends CoinServiceAPI {
|
||||
NodeModel? _ethNode;
|
||||
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";
|
||||
|
||||
@override
|
||||
String get walletId => _walletId;
|
||||
late String _walletId;
|
||||
|
||||
late String _walletName;
|
||||
late Coin _coin;
|
||||
Timer? timer;
|
||||
Timer? _networkAliveTimer;
|
||||
|
||||
@override
|
||||
set isFavorite(bool markFavorite) {
|
||||
DB.instance.put<dynamic>(
|
||||
|
@ -140,7 +150,6 @@ class EthereumWallet extends CoinServiceAPI {
|
|||
_walletId = walletId;
|
||||
_walletName = walletName;
|
||||
_coin = coin;
|
||||
|
||||
_priceAPI = priceAPI ?? PriceAPI(Client());
|
||||
_secureStore = secureStore;
|
||||
}
|
||||
|
@ -167,11 +176,6 @@ class EthereumWallet extends CoinServiceAPI {
|
|||
|
||||
@override
|
||||
String get walletName => _walletName;
|
||||
late String _walletName;
|
||||
|
||||
late Coin _coin;
|
||||
Timer? timer;
|
||||
Timer? _networkAliveTimer;
|
||||
|
||||
@override
|
||||
Future<List<String>> get allOwnAddresses =>
|
||||
|
@ -339,7 +343,9 @@ class EthereumWallet extends CoinServiceAPI {
|
|||
await DB.instance
|
||||
.put<dynamic>(boxName: walletId, key: "id", value: _walletId);
|
||||
await DB.instance.put<dynamic>(
|
||||
boxName: walletId, key: 'receivingAddresses', value: ["0"]);
|
||||
boxName: walletId,
|
||||
key: 'receivingAddresses',
|
||||
value: [_credentials.address.toString()]);
|
||||
await DB.instance
|
||||
.put<dynamic>(boxName: walletId, key: "receivingIndex", value: 0);
|
||||
await DB.instance
|
||||
|
@ -480,6 +486,46 @@ class EthereumWallet extends CoinServiceAPI {
|
|||
|
||||
_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
|
||||
.put<dynamic>(boxName: walletId, key: "id", value: _walletId);
|
||||
await DB.instance
|
||||
|
@ -528,8 +574,8 @@ class EthereumWallet extends CoinServiceAPI {
|
|||
}
|
||||
if (!needsRefresh) {
|
||||
var allOwnAddresses = await _fetchAllOwnAddresses();
|
||||
AddressTransaction addressTransactions =
|
||||
await fetchAddressTransactions(allOwnAddresses.elementAt(0));
|
||||
AddressTransaction addressTransactions = await fetchAddressTransactions(
|
||||
allOwnAddresses.elementAt(0), "txlist");
|
||||
final txData = await transactionData;
|
||||
if (addressTransactions.message == "OK") {
|
||||
final allTxs = addressTransactions.result;
|
||||
|
@ -854,9 +900,10 @@ class EthereumWallet extends CoinServiceAPI {
|
|||
return isValidEthereumAddress(address);
|
||||
}
|
||||
|
||||
Future<AddressTransaction> fetchAddressTransactions(String address) async {
|
||||
Future<AddressTransaction> fetchAddressTransactions(
|
||||
String address, String action) async {
|
||||
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) {
|
||||
return AddressTransaction.fromJson(
|
||||
|
@ -881,7 +928,8 @@ class EthereumWallet extends CoinServiceAPI {
|
|||
Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
|
||||
final List<Map<String, dynamic>> midSortedArray = [];
|
||||
|
||||
AddressTransaction txs = await fetchAddressTransactions(thisAddress);
|
||||
AddressTransaction txs =
|
||||
await fetchAddressTransactions(thisAddress, "txlist");
|
||||
|
||||
if (txs.message == "OK") {
|
||||
final allTxs = txs.result;
|
||||
|
@ -991,13 +1039,11 @@ class EthereumWallet extends CoinServiceAPI {
|
|||
return txModel;
|
||||
}
|
||||
|
||||
@override
|
||||
String get walletId => _walletId;
|
||||
late String _walletId;
|
||||
|
||||
@override
|
||||
set walletName(String newName) => _walletName = newName;
|
||||
|
||||
// Future<String>
|
||||
|
||||
void stopNetworkAlivePinging() {
|
||||
_networkAliveTimer?.cancel();
|
||||
_networkAliveTimer = null;
|
||||
|
|
374
lib/services/tokens.dart
Normal file
374
lib/services/tokens.dart
Normal 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();
|
||||
// }
|
||||
// }
|
||||
}
|
127
lib/services/tokens/ethereum/ethereum_token.dart
Normal file
127
lib/services/tokens/ethereum/ethereum_token.dart
Normal 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();
|
||||
}
|
||||
}
|
130
lib/services/tokens/token_manager.dart
Normal file
130
lib/services/tokens/token_manager.dart
Normal 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);
|
||||
}
|
||||
}
|
73
lib/services/tokens/token_service.dart
Normal file
73
lib/services/tokens/token_service.dart
Normal 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);
|
||||
}
|
432
lib/services/tokens_service.dart
Normal file
432
lib/services/tokens_service.dart
Normal 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();
|
||||
// }
|
||||
}
|
Loading…
Reference in a new issue