mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-09-29 09:51:09 +00:00
WIP eth refactor
This commit is contained in:
parent
1653bb2096
commit
5aed55235c
17 changed files with 470 additions and 374 deletions
11
lib/models/ethereum/erc20_token.dart
Normal file
11
lib/models/ethereum/erc20_token.dart
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import 'package:stackwallet/models/ethereum/eth_token.dart';
|
||||||
|
|
||||||
|
class Erc20Token extends EthToken {
|
||||||
|
Erc20Token({
|
||||||
|
required super.contractAddress,
|
||||||
|
required super.name,
|
||||||
|
required super.symbol,
|
||||||
|
required super.decimals,
|
||||||
|
required super.balance,
|
||||||
|
});
|
||||||
|
}
|
11
lib/models/ethereum/erc721_token.dart
Normal file
11
lib/models/ethereum/erc721_token.dart
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import 'package:stackwallet/models/ethereum/eth_token.dart';
|
||||||
|
|
||||||
|
class Erc721Token extends EthToken {
|
||||||
|
Erc721Token({
|
||||||
|
required super.contractAddress,
|
||||||
|
required super.name,
|
||||||
|
required super.symbol,
|
||||||
|
required super.decimals,
|
||||||
|
required super.balance,
|
||||||
|
});
|
||||||
|
}
|
15
lib/models/ethereum/eth_token.dart
Normal file
15
lib/models/ethereum/eth_token.dart
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
class EthToken {
|
||||||
|
EthToken({
|
||||||
|
required this.contractAddress,
|
||||||
|
required this.name,
|
||||||
|
required this.symbol,
|
||||||
|
required this.decimals,
|
||||||
|
required this.balance,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String contractAddress;
|
||||||
|
final String name;
|
||||||
|
final String symbol;
|
||||||
|
final int decimals;
|
||||||
|
final int balance;
|
||||||
|
}
|
|
@ -207,5 +207,6 @@ enum TransactionSubType {
|
||||||
none,
|
none,
|
||||||
bip47Notification, // bip47 payment code notification transaction flag
|
bip47Notification, // bip47 payment code notification transaction flag
|
||||||
mint, // firo specific
|
mint, // firo specific
|
||||||
join; // firo specific
|
join, // firo specific
|
||||||
|
ethToken; // eth token
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,30 +3,22 @@ import 'dart:async';
|
||||||
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:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:stackwallet/models/ethereum/eth_token.dart';
|
||||||
|
import 'package:stackwallet/pages/token_view/all_tokens_view.dart';
|
||||||
import 'package:stackwallet/pages/token_view/sub_widgets/my_tokens_list.dart';
|
import 'package:stackwallet/pages/token_view/sub_widgets/my_tokens_list.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/services/coins/manager.dart';
|
||||||
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||||
|
|
||||||
import 'package:stackwallet/utilities/assets.dart';
|
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||||
import 'package:stackwallet/pages/token_view/all_tokens_view.dart';
|
|
||||||
|
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
|
||||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
|
||||||
|
|
||||||
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
||||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
import 'package:stackwallet/widgets/stack_text_field.dart';
|
||||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
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 {
|
class MyTokensView extends ConsumerStatefulWidget {
|
||||||
const MyTokensView({
|
const MyTokensView({
|
||||||
|
@ -41,7 +33,7 @@ class MyTokensView extends ConsumerStatefulWidget {
|
||||||
final ChangeNotifierProvider<Manager> managerProvider;
|
final ChangeNotifierProvider<Manager> managerProvider;
|
||||||
final String walletId;
|
final String walletId;
|
||||||
final String walletAddress;
|
final String walletAddress;
|
||||||
final List<dynamic> tokens;
|
final List<EthToken> tokens;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<MyTokensView> createState() => _TokenDetailsViewState();
|
ConsumerState<MyTokensView> createState() => _TokenDetailsViewState();
|
||||||
|
|
|
@ -1,49 +1,47 @@
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
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:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:stackwallet/models/ethereum/eth_token.dart';
|
||||||
import 'package:stackwallet/pages/token_view/token_view.dart';
|
import 'package:stackwallet/pages/token_view/token_view.dart';
|
||||||
import 'package:stackwallet/providers/global/secure_store_provider.dart';
|
import 'package:stackwallet/providers/global/secure_store_provider.dart';
|
||||||
import 'package:stackwallet/services/tokens/ethereum/ethereum_token.dart';
|
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||||
|
import 'package:stackwallet/services/coins/manager.dart';
|
||||||
|
import 'package:stackwallet/services/ethereum/ethereum_token_service.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';
|
||||||
|
import 'package:stackwallet/utilities/format.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.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.managerProvider,
|
||||||
required this.walletId,
|
required this.walletId,
|
||||||
required this.walletAddress,
|
required this.walletAddress,
|
||||||
required this.tokenData,
|
required this.token,
|
||||||
required})
|
required})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
final ChangeNotifierProvider<Manager> managerProvider;
|
final ChangeNotifierProvider<Manager> managerProvider;
|
||||||
final String walletId;
|
final String walletId;
|
||||||
final String walletAddress;
|
final String walletAddress;
|
||||||
final Map<dynamic, dynamic> tokenData;
|
final EthToken token;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
int balance = tokenData["balance"] as int;
|
final balanceInDecimal = Format.satoshisToEthTokenAmount(
|
||||||
int tokenDecimals = int.parse(tokenData["decimals"] as String);
|
token.balance,
|
||||||
final balanceInDecimal = (balance / (pow(10, tokenDecimals)));
|
token.decimals,
|
||||||
|
);
|
||||||
|
|
||||||
return RoundedWhiteContainer(
|
return RoundedWhiteContainer(
|
||||||
padding: const EdgeInsets.all(0),
|
padding: const EdgeInsets.all(0),
|
||||||
child: MaterialButton(
|
child: MaterialButton(
|
||||||
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
|
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
|
||||||
key: Key("walletListItemButtonKey_${tokenData["symbol"]}"),
|
key: Key("walletListItemButtonKey_${token.symbol}"),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 13),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 13),
|
||||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
|
@ -53,8 +51,8 @@ class MyTokenSelectItem extends ConsumerWidget {
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
final mnemonicList = ref.read(managerProvider).mnemonic;
|
final mnemonicList = ref.read(managerProvider).mnemonic;
|
||||||
|
|
||||||
final token = EthereumToken(
|
final tokenService = EthereumTokenService(
|
||||||
tokenData: tokenData,
|
token: token,
|
||||||
walletMnemonic: mnemonicList,
|
walletMnemonic: mnemonicList,
|
||||||
secureStore: ref.read(secureStoreProvider));
|
secureStore: ref.read(secureStoreProvider));
|
||||||
|
|
||||||
|
@ -62,11 +60,11 @@ class MyTokenSelectItem extends ConsumerWidget {
|
||||||
TokenView.routeName,
|
TokenView.routeName,
|
||||||
arguments: Tuple4(
|
arguments: Tuple4(
|
||||||
walletId,
|
walletId,
|
||||||
tokenData,
|
token,
|
||||||
ref
|
ref
|
||||||
.read(walletsChangeNotifierProvider)
|
.read(walletsChangeNotifierProvider)
|
||||||
.getManagerProvider(walletId),
|
.getManagerProvider(walletId),
|
||||||
token),
|
tokenService),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -89,12 +87,12 @@ class MyTokenSelectItem extends ConsumerWidget {
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
tokenData["name"] as String,
|
token.name,
|
||||||
style: STextStyles.titleBold12(context),
|
style: STextStyles.titleBold12(context),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Text(
|
Text(
|
||||||
"$balanceInDecimal ${tokenData["symbol"]}",
|
"$balanceInDecimal ${token.symbol}",
|
||||||
style: STextStyles.itemSubtitle(context),
|
style: STextStyles.itemSubtitle(context),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -105,7 +103,7 @@ class MyTokenSelectItem extends ConsumerWidget {
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
tokenData["symbol"] as String,
|
token.symbol,
|
||||||
style: STextStyles.itemSubtitle(context),
|
style: STextStyles.itemSubtitle(context),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
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/models/ethereum/eth_token.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';
|
import 'package:stackwallet/services/coins/manager.dart';
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ class MyTokensList extends StatelessWidget {
|
||||||
|
|
||||||
final ChangeNotifierProvider<Manager> managerProvider;
|
final ChangeNotifierProvider<Manager> managerProvider;
|
||||||
final String walletId;
|
final String walletId;
|
||||||
final List<dynamic> tokens;
|
final List<EthToken> tokens;
|
||||||
final String walletAddress;
|
final String walletAddress;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -31,7 +32,7 @@ class MyTokensList extends StatelessWidget {
|
||||||
managerProvider: managerProvider,
|
managerProvider: managerProvider,
|
||||||
walletId: walletId,
|
walletId: walletId,
|
||||||
walletAddress: walletAddress,
|
walletAddress: walletAddress,
|
||||||
tokenData: tokens[index] as Map<dynamic, dynamic>,
|
token: tokens[index],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:event_bus/event_bus.dart';
|
||||||
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:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:stackwallet/models/ethereum/eth_token.dart';
|
||||||
import 'package:stackwallet/pages/home_view/home_view.dart';
|
import 'package:stackwallet/pages/home_view/home_view.dart';
|
||||||
import 'package:stackwallet/pages/token_view/sub_widgets/token_summary.dart';
|
import 'package:stackwallet/pages/token_view/sub_widgets/token_summary.dart';
|
||||||
import 'package:stackwallet/pages/wallet_view/sub_widgets/transactions_list.dart';
|
import 'package:stackwallet/pages/wallet_view/sub_widgets/transactions_list.dart';
|
||||||
|
@ -12,10 +13,10 @@ import 'package:stackwallet/providers/global/auto_swb_service_provider.dart';
|
||||||
import 'package:stackwallet/providers/providers.dart';
|
import 'package:stackwallet/providers/providers.dart';
|
||||||
import 'package:stackwallet/providers/ui/transaction_filter_provider.dart';
|
import 'package:stackwallet/providers/ui/transaction_filter_provider.dart';
|
||||||
import 'package:stackwallet/services/coins/manager.dart';
|
import 'package:stackwallet/services/coins/manager.dart';
|
||||||
|
import 'package:stackwallet/services/ethereum/ethereum_token_service.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/event_bus/global_event_bus.dart';
|
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||||
import 'package:stackwallet/services/tokens/ethereum/ethereum_token.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/backup_frequency_type.dart';
|
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
|
||||||
|
@ -32,9 +33,9 @@ class TokenView extends ConsumerStatefulWidget {
|
||||||
const TokenView({
|
const TokenView({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.walletId,
|
required this.walletId,
|
||||||
required this.tokenData,
|
|
||||||
required this.managerProvider,
|
|
||||||
required this.token,
|
required this.token,
|
||||||
|
required this.managerProvider,
|
||||||
|
required this.tokenService,
|
||||||
this.eventBus,
|
this.eventBus,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@ -42,9 +43,9 @@ class TokenView extends ConsumerStatefulWidget {
|
||||||
static const double navBarHeight = 65.0;
|
static const double navBarHeight = 65.0;
|
||||||
|
|
||||||
final String walletId;
|
final String walletId;
|
||||||
final Map<dynamic, dynamic> tokenData;
|
final EthToken token;
|
||||||
final ChangeNotifierProvider<Manager> managerProvider;
|
final ChangeNotifierProvider<Manager> managerProvider;
|
||||||
final EthereumToken token;
|
final EthereumTokenService tokenService;
|
||||||
final EventBus? eventBus;
|
final EventBus? eventBus;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -189,7 +190,7 @@ class _TokenViewState extends ConsumerState<TokenView> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
debugPrint("BUILD: $runtimeType");
|
debugPrint("BUILD: $runtimeType");
|
||||||
widget.token.initializeExisting();
|
widget.tokenService.initializeExisting();
|
||||||
// print("MY TOTAL BALANCE IS ${widget.token.totalBalance}");
|
// print("MY TOTAL BALANCE IS ${widget.token.totalBalance}");
|
||||||
|
|
||||||
final coin = ref.watch(managerProvider.select((value) => value.coin));
|
final coin = ref.watch(managerProvider.select((value) => value.coin));
|
||||||
|
@ -221,7 +222,7 @@ class _TokenViewState extends ConsumerState<TokenView> {
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.tokenData["name"] as String,
|
widget.token.name,
|
||||||
style: STextStyles.navBarTitle(context),
|
style: STextStyles.navBarTitle(context),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:event_bus/event_bus.dart';
|
||||||
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:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:stackwallet/models/ethereum/eth_token.dart';
|
||||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||||
import 'package:stackwallet/pages/buy_view/buy_in_wallet_view.dart';
|
import 'package:stackwallet/pages/buy_view/buy_in_wallet_view.dart';
|
||||||
import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart';
|
import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart';
|
||||||
|
@ -27,6 +28,7 @@ import 'package:stackwallet/providers/wallet/public_private_balance_state_provid
|
||||||
import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.dart';
|
import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.dart';
|
||||||
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||||
import 'package:stackwallet/services/coins/manager.dart';
|
import 'package:stackwallet/services/coins/manager.dart';
|
||||||
|
import 'package:stackwallet/services/ethereum/ethereum_api.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/event_bus/global_event_bus.dart';
|
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||||
|
@ -35,7 +37,7 @@ import 'package:stackwallet/utilities/constants.dart';
|
||||||
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
|
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart';
|
import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart';
|
||||||
import 'package:stackwallet/utilities/eth_commons.dart';
|
import 'package:stackwallet/utilities/show_loading.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
import 'package:stackwallet/widgets/background.dart';
|
||||||
|
@ -783,21 +785,43 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
||||||
.read(managerProvider)
|
.read(managerProvider)
|
||||||
.currentReceivingAddress;
|
.currentReceivingAddress;
|
||||||
|
|
||||||
List<dynamic> tokens =
|
final response = await showLoading<
|
||||||
await getWalletTokens(await ref
|
EthereumResponse<List<EthToken>>>(
|
||||||
.read(managerProvider)
|
whileFuture:
|
||||||
.currentReceivingAddress);
|
EthereumAPI.getWalletTokens(
|
||||||
|
address: await ref
|
||||||
|
.read(managerProvider)
|
||||||
|
.currentReceivingAddress),
|
||||||
|
message: "Loading tokens",
|
||||||
|
isDesktop: false,
|
||||||
|
context: context,
|
||||||
|
);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
await Navigator.of(context).pushNamed(
|
if (response.value != null) {
|
||||||
MyTokensView.routeName,
|
await Navigator.of(context)
|
||||||
arguments: Tuple4(
|
.pushNamed(
|
||||||
managerProvider,
|
MyTokensView.routeName,
|
||||||
walletId,
|
arguments: Tuple4(
|
||||||
walletAddress,
|
managerProvider,
|
||||||
tokens,
|
walletId,
|
||||||
),
|
walletAddress,
|
||||||
);
|
response.value!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return StackOkDialog(
|
||||||
|
title:
|
||||||
|
"Failed to fetch tokens",
|
||||||
|
message: response.exception
|
||||||
|
.toString(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -4,8 +4,10 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:stackwallet/models/buy/response_objects/quote.dart';
|
import 'package:stackwallet/models/buy/response_objects/quote.dart';
|
||||||
import 'package:stackwallet/models/contact_address_entry.dart';
|
import 'package:stackwallet/models/contact_address_entry.dart';
|
||||||
|
import 'package:stackwallet/models/ethereum/eth_token.dart';
|
||||||
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
|
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
|
||||||
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
|
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||||
import 'package:stackwallet/models/paynym/paynym_account_lite.dart';
|
import 'package:stackwallet/models/paynym/paynym_account_lite.dart';
|
||||||
import 'package:stackwallet/models/send_view_auto_fill_data.dart';
|
import 'package:stackwallet/models/send_view_auto_fill_data.dart';
|
||||||
|
@ -126,15 +128,13 @@ import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/nodes_
|
||||||
import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/security_settings.dart';
|
import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/security_settings.dart';
|
||||||
import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/syncing_preferences_settings.dart';
|
import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/syncing_preferences_settings.dart';
|
||||||
import 'package:stackwallet/services/coins/manager.dart';
|
import 'package:stackwallet/services/coins/manager.dart';
|
||||||
|
import 'package:stackwallet/services/ethereum/ethereum_token_service.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/ethereum/ethereum_token.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';
|
||||||
|
|
||||||
import 'models/isar/models/blockchain_data/transaction.dart';
|
|
||||||
|
|
||||||
class RouteGenerator {
|
class RouteGenerator {
|
||||||
static const bool useMaterialPageRoute = true;
|
static const bool useMaterialPageRoute = true;
|
||||||
|
|
||||||
|
@ -1431,7 +1431,7 @@ class RouteGenerator {
|
||||||
|
|
||||||
case MyTokensView.routeName:
|
case MyTokensView.routeName:
|
||||||
if (args is Tuple4<ChangeNotifierProvider<Manager>, String, String,
|
if (args is Tuple4<ChangeNotifierProvider<Manager>, String, String,
|
||||||
List<dynamic>>) {
|
List<EthToken>>) {
|
||||||
return getRoute(
|
return getRoute(
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
builder: (_) => MyTokensView(
|
builder: (_) => MyTokensView(
|
||||||
|
@ -1461,15 +1461,15 @@ class RouteGenerator {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
case TokenView.routeName:
|
case TokenView.routeName:
|
||||||
if (args is Tuple4<String, Map<dynamic, dynamic>,
|
if (args is Tuple4<String, EthToken, ChangeNotifierProvider<Manager>,
|
||||||
ChangeNotifierProvider<Manager>, EthereumToken>) {
|
EthereumTokenService>) {
|
||||||
return getRoute(
|
return getRoute(
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
builder: (_) => TokenView(
|
builder: (_) => TokenView(
|
||||||
walletId: args.item1,
|
walletId: args.item1,
|
||||||
tokenData: args.item2,
|
token: args.item2,
|
||||||
managerProvider: args.item3,
|
managerProvider: args.item3,
|
||||||
token: args.item4,
|
tokenService: args.item4,
|
||||||
),
|
),
|
||||||
settings: RouteSettings(
|
settings: RouteSettings(
|
||||||
name: settings.name,
|
name: settings.name,
|
||||||
|
|
|
@ -11,6 +11,7 @@ import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||||
import 'package:stackwallet/models/node_model.dart';
|
import 'package:stackwallet/models/node_model.dart';
|
||||||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||||
import 'package:stackwallet/services/coins/coin_service.dart';
|
import 'package:stackwallet/services/coins/coin_service.dart';
|
||||||
|
import 'package:stackwallet/services/ethereum/ethereum_api.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/refresh_percent_changed_event.dart';
|
import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart';
|
||||||
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
|
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
|
||||||
|
@ -206,9 +207,7 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
||||||
Future<FeeObject> get fees => _feeObject ??= _getFees();
|
Future<FeeObject> get fees => _feeObject ??= _getFees();
|
||||||
Future<FeeObject>? _feeObject;
|
Future<FeeObject>? _feeObject;
|
||||||
|
|
||||||
Future<FeeObject> _getFees() async {
|
Future<FeeObject> _getFees() => EthereumAPI.getFees();
|
||||||
return await getFees();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Full rescan is not needed for ETH since we have a balance
|
//Full rescan is not needed for ETH since we have a balance
|
||||||
@override
|
@override
|
||||||
|
@ -536,8 +535,9 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
||||||
}
|
}
|
||||||
if (!needsRefresh) {
|
if (!needsRefresh) {
|
||||||
var allOwnAddresses = await _fetchAllOwnAddresses();
|
var allOwnAddresses = await _fetchAllOwnAddresses();
|
||||||
AddressTransaction addressTransactions = await fetchAddressTransactions(
|
AddressTransaction addressTransactions =
|
||||||
allOwnAddresses.elementAt(0).value, "txlist");
|
await EthereumAPI.fetchAddressTransactions(
|
||||||
|
allOwnAddresses.elementAt(0).value, "txlist");
|
||||||
if (addressTransactions.message == "OK") {
|
if (addressTransactions.message == "OK") {
|
||||||
final allTxs = addressTransactions.result;
|
final allTxs = addressTransactions.result;
|
||||||
for (final element in allTxs) {
|
for (final element in allTxs) {
|
||||||
|
@ -812,7 +812,7 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
||||||
String thisAddress = await currentReceivingAddress;
|
String thisAddress = await currentReceivingAddress;
|
||||||
|
|
||||||
AddressTransaction txs =
|
AddressTransaction txs =
|
||||||
await fetchAddressTransactions(thisAddress, "txlist");
|
await EthereumAPI.fetchAddressTransactions(thisAddress, "txlist");
|
||||||
|
|
||||||
if (txs.message == "OK") {
|
if (txs.message == "OK") {
|
||||||
final allTxs = txs.result;
|
final allTxs = txs.result;
|
||||||
|
|
263
lib/services/ethereum/ethereum_api.dart
Normal file
263
lib/services/ethereum/ethereum_api.dart
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:ethereum_addresses/ethereum_addresses.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:stackwallet/models/ethereum/erc721_token.dart';
|
||||||
|
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||||
|
import 'package:stackwallet/utilities/eth_commons.dart';
|
||||||
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
|
|
||||||
|
import '../../models/ethereum/erc20_token.dart';
|
||||||
|
import '../../models/ethereum/eth_token.dart';
|
||||||
|
|
||||||
|
class AbiRequestResponse {
|
||||||
|
final String message;
|
||||||
|
final String result;
|
||||||
|
final String status;
|
||||||
|
|
||||||
|
const AbiRequestResponse({
|
||||||
|
required this.message,
|
||||||
|
required this.result,
|
||||||
|
required this.status,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory AbiRequestResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AbiRequestResponse(
|
||||||
|
message: json['message'] as String,
|
||||||
|
result: json['result'] as String,
|
||||||
|
status: json['status'] as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EthereumResponse<T> {
|
||||||
|
final T? value;
|
||||||
|
final Exception? exception;
|
||||||
|
|
||||||
|
EthereumResponse(this.value, this.exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class EthereumAPI {
|
||||||
|
static const blockExplorer = "https://blockscout.com/eth/mainnet/api";
|
||||||
|
static const abiUrl =
|
||||||
|
"https://api.etherscan.io/api"; //TODO - Once our server has abi functionality update
|
||||||
|
|
||||||
|
static const gasTrackerUrl =
|
||||||
|
"https://blockscout.com/eth/mainnet/api/v1/gas-price-oracle";
|
||||||
|
|
||||||
|
static Future<AddressTransaction> fetchAddressTransactions(
|
||||||
|
String address, String action) async {
|
||||||
|
try {
|
||||||
|
final response = await get(Uri.parse(
|
||||||
|
"$blockExplorer?module=account&action=$action&address=$address"));
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
return AddressTransaction.fromJson(
|
||||||
|
jsonDecode(response.body) as Map<String, dynamic>);
|
||||||
|
} else {
|
||||||
|
throw Exception(
|
||||||
|
'ERROR GETTING TRANSACTIONS WITH STATUS ${response.statusCode}');
|
||||||
|
}
|
||||||
|
} catch (e, s) {
|
||||||
|
throw Exception('ERROR GETTING TRANSACTIONS ${e.toString()}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<EthereumResponse<List<EthToken>>> getWalletTokens({
|
||||||
|
required String address,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final uri = Uri.parse(
|
||||||
|
"$blockExplorer?module=account&action=tokenlist&address=$address",
|
||||||
|
);
|
||||||
|
final response = await get(uri);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final json = jsonDecode(response.body);
|
||||||
|
if (json["message"] == "OK") {
|
||||||
|
final result =
|
||||||
|
List<Map<String, dynamic>>.from(json["result"] as List);
|
||||||
|
final List<EthToken> tokens = [];
|
||||||
|
for (final map in result) {
|
||||||
|
if (map["type"] == "ERC-20") {
|
||||||
|
tokens.add(
|
||||||
|
Erc20Token(
|
||||||
|
balance: int.parse(map["balance"] as String),
|
||||||
|
contractAddress: map["contractAddress"] as String,
|
||||||
|
decimals: int.parse(map["decimals"] as String),
|
||||||
|
name: map["name"] as String,
|
||||||
|
symbol: map["symbol"] as String,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (map["type"] == "ERC-721") {
|
||||||
|
tokens.add(
|
||||||
|
Erc721Token(
|
||||||
|
balance: int.parse(map["balance"] as String),
|
||||||
|
contractAddress: map["contractAddress"] as String,
|
||||||
|
decimals: int.parse(map["decimals"] as String),
|
||||||
|
name: map["name"] as String,
|
||||||
|
symbol: map["symbol"] as String,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw Exception("Unsupported token type found: ${map["type"]}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return EthereumResponse(
|
||||||
|
tokens,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw Exception(json["message"] as String);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Exception(
|
||||||
|
"getWalletTokens($address) failed with status code: "
|
||||||
|
"${response.statusCode}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"getWalletTokens(): $e\n$s",
|
||||||
|
level: LogLevel.Error,
|
||||||
|
);
|
||||||
|
return EthereumResponse(
|
||||||
|
null,
|
||||||
|
Exception(e.toString()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<List<dynamic>> getWalletTokenTransactions(
|
||||||
|
String address) async {
|
||||||
|
AddressTransaction tokens =
|
||||||
|
await fetchAddressTransactions(address, "tokentx");
|
||||||
|
List<dynamic> tokensList = [];
|
||||||
|
var tokenMap = {};
|
||||||
|
if (tokens.message == "OK") {
|
||||||
|
final allTxs = tokens.result;
|
||||||
|
allTxs.forEach((element) {
|
||||||
|
print("=========================================================");
|
||||||
|
print("THING: $element");
|
||||||
|
print("=========================================================");
|
||||||
|
|
||||||
|
String key = element["tokenSymbol"] as String;
|
||||||
|
tokenMap[key] = {};
|
||||||
|
tokenMap[key]["balance"] = 0;
|
||||||
|
|
||||||
|
if (tokenMap.containsKey(key)) {
|
||||||
|
tokenMap[key]["contractAddress"] =
|
||||||
|
element["contractAddress"] as String;
|
||||||
|
tokenMap[key]["decimals"] = element["tokenDecimal"];
|
||||||
|
tokenMap[key]["name"] = element["tokenName"];
|
||||||
|
tokenMap[key]["symbol"] = element["tokenSymbol"];
|
||||||
|
if (checksumEthereumAddress(address) == address) {
|
||||||
|
tokenMap[key]["balance"] += int.parse(element["value"] as String);
|
||||||
|
} else {
|
||||||
|
tokenMap[key]["balance"] -= int.parse(element["value"] as String);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tokenMap.forEach((key, value) {
|
||||||
|
tokensList.add(value as Map<dynamic, dynamic>);
|
||||||
|
});
|
||||||
|
return tokensList;
|
||||||
|
}
|
||||||
|
return <dynamic>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<GasTracker> getGasOracle() async {
|
||||||
|
final response = await get(Uri.parse(gasTrackerUrl));
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
return GasTracker.fromJson(
|
||||||
|
json.decode(response.body) as Map<String, dynamic>);
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load gas oracle');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<FeeObject> getFees() async {
|
||||||
|
GasTracker fees = await getGasOracle();
|
||||||
|
final feesFast = fees.fast * (pow(10, 9));
|
||||||
|
final feesStandard = fees.average * (pow(10, 9));
|
||||||
|
final feesSlow = fees.slow * (pow(10, 9));
|
||||||
|
|
||||||
|
return FeeObject(
|
||||||
|
numberOfBlocksFast: 1,
|
||||||
|
numberOfBlocksAverage: 3,
|
||||||
|
numberOfBlocksSlow: 3,
|
||||||
|
fast: feesFast.toInt(),
|
||||||
|
medium: feesStandard.toInt(),
|
||||||
|
slow: feesSlow.toInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
//Validate that a custom token is valid and is ERC-20, a token will be valid
|
||||||
|
static Future<EthereumResponse<EthToken>> getTokenByContractAddress(
|
||||||
|
String contractAddress) async {
|
||||||
|
try {
|
||||||
|
final response = await get(Uri.parse(
|
||||||
|
"$blockExplorer?module=token&action=getToken&contractaddress=$contractAddress"));
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final json = jsonDecode(response.body);
|
||||||
|
if (json["message"] == "OK") {
|
||||||
|
final map = Map<String, dynamic>.from(json["result"] as Map);
|
||||||
|
EthToken? token;
|
||||||
|
if (map["type"] == "ERC-20") {
|
||||||
|
token = Erc20Token(
|
||||||
|
balance: int.parse(map["balance"] as String),
|
||||||
|
contractAddress: map["contractAddress"] as String,
|
||||||
|
decimals: int.parse(map["decimals"] as String),
|
||||||
|
name: map["name"] as String,
|
||||||
|
symbol: map["symbol"] as String,
|
||||||
|
);
|
||||||
|
} else if (map["type"] == "ERC-721") {
|
||||||
|
token = Erc721Token(
|
||||||
|
balance: int.parse(map["balance"] as String),
|
||||||
|
contractAddress: map["contractAddress"] as String,
|
||||||
|
decimals: int.parse(map["decimals"] as String),
|
||||||
|
name: map["name"] as String,
|
||||||
|
symbol: map["symbol"] as String,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw Exception("Unsupported token type found: ${map["type"]}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return EthereumResponse(
|
||||||
|
token,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw Exception(json["message"] as String);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Exception(
|
||||||
|
"getTokenByContractAddress($contractAddress) failed with status code: "
|
||||||
|
"${response.statusCode}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"getWalletTokens(): $e\n$s",
|
||||||
|
level: LogLevel.Error,
|
||||||
|
);
|
||||||
|
return EthereumResponse(
|
||||||
|
null,
|
||||||
|
Exception(e.toString()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<AbiRequestResponse> fetchTokenAbi(
|
||||||
|
String contractAddress) async {
|
||||||
|
final response = await get(Uri.parse(
|
||||||
|
"$abiUrl?module=contract&action=getabi&address=$contractAddress&apikey=EG6J7RJIQVSTP2BS59D3TY2G55YHS5F2HP"));
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
return AbiRequestResponse.fromJson(
|
||||||
|
json.decode(response.body) as Map<String, dynamic>);
|
||||||
|
} else {
|
||||||
|
throw Exception("ERROR GETTING TOKENABI ${response.reasonPhrase}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,15 @@
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:decimal/decimal.dart';
|
import 'package:decimal/decimal.dart';
|
||||||
import 'package:ethereum_addresses/ethereum_addresses.dart';
|
import 'package:ethereum_addresses/ethereum_addresses.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
|
import 'package:stackwallet/models/ethereum/eth_token.dart';
|
||||||
import 'package:stackwallet/models/node_model.dart';
|
import 'package:stackwallet/models/node_model.dart';
|
||||||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||||
import 'package:stackwallet/models/paymint/transactions_model.dart';
|
import 'package:stackwallet/models/paymint/transactions_model.dart';
|
||||||
|
import 'package:stackwallet/services/ethereum/ethereum_api.dart';
|
||||||
import 'package:stackwallet/services/node_service.dart';
|
import 'package:stackwallet/services/node_service.dart';
|
||||||
import 'package:stackwallet/services/tokens/token_service.dart';
|
|
||||||
import 'package:stackwallet/services/transaction_notification_tracker.dart';
|
import 'package:stackwallet/services/transaction_notification_tracker.dart';
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
|
||||||
import 'package:stackwallet/utilities/default_nodes.dart';
|
import 'package:stackwallet/utilities/default_nodes.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
||||||
|
@ -20,55 +19,15 @@ import 'package:stackwallet/utilities/format.dart';
|
||||||
import 'package:web3dart/web3dart.dart';
|
import 'package:web3dart/web3dart.dart';
|
||||||
import 'package:web3dart/web3dart.dart' as transaction;
|
import 'package:web3dart/web3dart.dart' as transaction;
|
||||||
|
|
||||||
class AbiRequestResponse {
|
|
||||||
final String message;
|
|
||||||
final String result;
|
|
||||||
final String status;
|
|
||||||
|
|
||||||
const AbiRequestResponse({
|
|
||||||
required this.message,
|
|
||||||
required this.result,
|
|
||||||
required this.status,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory AbiRequestResponse.fromJson(Map<String, dynamic> json) {
|
|
||||||
return AbiRequestResponse(
|
|
||||||
message: json['message'] as String,
|
|
||||||
result: json['result'] as String,
|
|
||||||
status: json['status'] as String,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TokenData {
|
|
||||||
final String message;
|
|
||||||
final Map<String, dynamic> result;
|
|
||||||
final String status;
|
|
||||||
|
|
||||||
const TokenData({
|
|
||||||
required this.message,
|
|
||||||
required this.result,
|
|
||||||
required this.status,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory TokenData.fromJson(Map<String, dynamic> json) {
|
|
||||||
return TokenData(
|
|
||||||
message: json['message'] as String,
|
|
||||||
result: json['result'] as Map<String, dynamic>,
|
|
||||||
status: json['status'] as String,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const int MINIMUM_CONFIRMATIONS = 3;
|
const int MINIMUM_CONFIRMATIONS = 3;
|
||||||
|
|
||||||
class EthereumToken extends TokenServiceAPI {
|
class EthereumTokenService {
|
||||||
@override
|
final EthToken token;
|
||||||
|
|
||||||
late bool shouldAutoSync;
|
late bool shouldAutoSync;
|
||||||
late EthereumAddress _contractAddress;
|
late EthereumAddress _contractAddress;
|
||||||
late EthPrivateKey _credentials;
|
late EthPrivateKey _credentials;
|
||||||
late DeployedContract _contract;
|
late DeployedContract _contract;
|
||||||
late Map<dynamic, dynamic> _tokenData;
|
|
||||||
late ContractFunction _balanceFunction;
|
late ContractFunction _balanceFunction;
|
||||||
late ContractFunction _sendFunction;
|
late ContractFunction _sendFunction;
|
||||||
late Future<List<String>> _walletMnemonic;
|
late Future<List<String>> _walletMnemonic;
|
||||||
|
@ -80,30 +39,16 @@ class EthereumToken extends TokenServiceAPI {
|
||||||
|
|
||||||
final _gasLimit = 200000;
|
final _gasLimit = 200000;
|
||||||
|
|
||||||
EthereumToken({
|
EthereumTokenService({
|
||||||
required Map<dynamic, dynamic> tokenData,
|
required this.token,
|
||||||
required Future<List<String>> walletMnemonic,
|
required Future<List<String>> walletMnemonic,
|
||||||
required SecureStorageInterface secureStore,
|
required SecureStorageInterface secureStore,
|
||||||
}) {
|
}) {
|
||||||
_contractAddress =
|
_contractAddress = EthereumAddress.fromHex(token.contractAddress);
|
||||||
EthereumAddress.fromHex(tokenData["contractAddress"] as String);
|
|
||||||
_walletMnemonic = walletMnemonic;
|
_walletMnemonic = walletMnemonic;
|
||||||
_tokenData = tokenData;
|
|
||||||
_secureStore = secureStore;
|
_secureStore = secureStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<AbiRequestResponse> fetchTokenAbi() async {
|
|
||||||
final response = await get(Uri.parse(
|
|
||||||
"$abiUrl?module=contract&action=getabi&address=$_contractAddress&apikey=EG6J7RJIQVSTP2BS59D3TY2G55YHS5F2HP"));
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
return AbiRequestResponse.fromJson(
|
|
||||||
json.decode(response.body) as Map<String, dynamic>);
|
|
||||||
} else {
|
|
||||||
throw Exception("ERROR GETTING TOKENABI ${response.reasonPhrase}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<String>> get allOwnAddresses =>
|
Future<List<String>> get allOwnAddresses =>
|
||||||
_allOwnAddresses ??= _fetchAllOwnAddresses();
|
_allOwnAddresses ??= _fetchAllOwnAddresses();
|
||||||
Future<List<String>>? _allOwnAddresses;
|
Future<List<String>>? _allOwnAddresses;
|
||||||
|
@ -115,21 +60,18 @@ class EthereumToken extends TokenServiceAPI {
|
||||||
return addresses;
|
return addresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Decimal> get availableBalance async {
|
Future<Decimal> get availableBalance async {
|
||||||
return await totalBalance;
|
return await totalBalance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Coin get coin => Coin.ethereum;
|
Coin get coin => Coin.ethereum;
|
||||||
|
|
||||||
@override
|
|
||||||
Future<String> confirmSend({required Map<String, dynamic> txData}) async {
|
Future<String> confirmSend({required Map<String, dynamic> txData}) async {
|
||||||
final amount = txData['recipientAmt'];
|
final amount = txData['recipientAmt'];
|
||||||
final decimalAmount =
|
final decimalAmount =
|
||||||
Format.satoshisToAmount(amount as int, coin: Coin.ethereum);
|
Format.satoshisToAmount(amount as int, coin: Coin.ethereum);
|
||||||
final bigIntAmount = amountToBigInt(
|
final bigIntAmount =
|
||||||
decimalAmount.toDouble(), int.parse(_tokenData["decimals"] as String));
|
amountToBigInt(decimalAmount.toDouble(), token.decimals);
|
||||||
|
|
||||||
final sentTx = await _client.sendTransaction(
|
final sentTx = await _client.sendTransaction(
|
||||||
_credentials,
|
_credentials,
|
||||||
|
@ -147,7 +89,6 @@ class EthereumToken extends TokenServiceAPI {
|
||||||
return sentTx;
|
return sentTx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<String> get currentReceivingAddress async {
|
Future<String> get currentReceivingAddress async {
|
||||||
final _currentReceivingAddress = await _credentials.extractAddress();
|
final _currentReceivingAddress = await _credentials.extractAddress();
|
||||||
final checkSumAddress =
|
final checkSumAddress =
|
||||||
|
@ -155,40 +96,21 @@ class EthereumToken extends TokenServiceAPI {
|
||||||
return checkSumAddress;
|
return checkSumAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<int> estimateFeeFor(int satoshiAmount, int feeRate) async {
|
Future<int> estimateFeeFor(int satoshiAmount, int feeRate) async {
|
||||||
final fee = estimateFee(
|
final fee = estimateFee(feeRate, _gasLimit, token.decimals);
|
||||||
feeRate, _gasLimit, int.parse(_tokenData["decimals"] as String));
|
|
||||||
return Format.decimalAmountToSatoshis(Decimal.parse(fee.toString()), coin);
|
return Format.decimalAmountToSatoshis(Decimal.parse(fee.toString()), coin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<FeeObject> get fees => _feeObject ??= _getFees();
|
Future<FeeObject> get fees => _feeObject ??= _getFees();
|
||||||
Future<FeeObject>? _feeObject;
|
Future<FeeObject>? _feeObject;
|
||||||
|
|
||||||
Future<FeeObject> _getFees() async {
|
Future<FeeObject> _getFees() async {
|
||||||
return await getFees();
|
return await EthereumAPI.getFees();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> initializeExisting() async {
|
Future<void> initializeExisting() async {
|
||||||
if ((await _secureStore.read(
|
_tokenAbi = (await _secureStore.read(
|
||||||
key: '${_contractAddress.toString()}_tokenAbi')) !=
|
key: '${_contractAddress.toString()}_tokenAbi'))!;
|
||||||
null) {
|
|
||||||
_tokenAbi = (await _secureStore.read(
|
|
||||||
key: '${_contractAddress.toString()}_tokenAbi'))!;
|
|
||||||
} else {
|
|
||||||
AbiRequestResponse abi = await fetchTokenAbi();
|
|
||||||
//Fetch token ABI so we can call token functions
|
|
||||||
if (abi.message == "OK") {
|
|
||||||
_tokenAbi = abi.result;
|
|
||||||
//Store abi in secure store
|
|
||||||
await _secureStore.write(
|
|
||||||
key: '${_contractAddress.toString()}_tokenAbi', value: _tokenAbi);
|
|
||||||
} else {
|
|
||||||
throw Exception('Failed to load token abi');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final mnemonic = await _walletMnemonic;
|
final mnemonic = await _walletMnemonic;
|
||||||
String mnemonicString = mnemonic.join(' ');
|
String mnemonicString = mnemonic.join(' ');
|
||||||
|
@ -199,8 +121,7 @@ class EthereumToken extends TokenServiceAPI {
|
||||||
_credentials = EthPrivateKey.fromHex(privateKey);
|
_credentials = EthPrivateKey.fromHex(privateKey);
|
||||||
|
|
||||||
_contract = DeployedContract(
|
_contract = DeployedContract(
|
||||||
ContractAbi.fromJson(_tokenAbi, _tokenData["name"] as String),
|
ContractAbi.fromJson(_tokenAbi, token.name), _contractAddress);
|
||||||
_contractAddress);
|
|
||||||
_balanceFunction = _contract.function('balanceOf');
|
_balanceFunction = _contract.function('balanceOf');
|
||||||
_sendFunction = _contract.function('transfer');
|
_sendFunction = _contract.function('transfer');
|
||||||
_client = await getEthClient();
|
_client = await getEthClient();
|
||||||
|
@ -208,15 +129,15 @@ class EthereumToken extends TokenServiceAPI {
|
||||||
// print(_credentials.p)
|
// print(_credentials.p)
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> initializeNew() async {
|
Future<void> initializeNew() async {
|
||||||
AbiRequestResponse abi = await fetchTokenAbi();
|
AbiRequestResponse abi =
|
||||||
|
await EthereumAPI.fetchTokenAbi(_contractAddress.hex);
|
||||||
//Fetch token ABI so we can call token functions
|
//Fetch token ABI so we can call token functions
|
||||||
if (abi.message == "OK") {
|
if (abi.message == "OK") {
|
||||||
_tokenAbi = abi.result;
|
_tokenAbi = abi.result;
|
||||||
//Store abi in secure store
|
//Store abi in secure store
|
||||||
await _secureStore.write(
|
await _secureStore.write(
|
||||||
key: '${_contractAddress.toString()}_tokenAbi', value: _tokenAbi);
|
key: '${_contractAddress.hex}_tokenAbi', value: _tokenAbi);
|
||||||
} else {
|
} else {
|
||||||
throw Exception('Failed to load token abi');
|
throw Exception('Failed to load token abi');
|
||||||
}
|
}
|
||||||
|
@ -230,25 +151,21 @@ class EthereumToken extends TokenServiceAPI {
|
||||||
_credentials = EthPrivateKey.fromHex(privateKey);
|
_credentials = EthPrivateKey.fromHex(privateKey);
|
||||||
|
|
||||||
_contract = DeployedContract(
|
_contract = DeployedContract(
|
||||||
ContractAbi.fromJson(_tokenAbi, _tokenData["name"] as String),
|
ContractAbi.fromJson(_tokenAbi, token.name), _contractAddress);
|
||||||
_contractAddress);
|
|
||||||
_balanceFunction = _contract.function('balanceOf');
|
_balanceFunction = _contract.function('balanceOf');
|
||||||
_sendFunction = _contract.function('transfer');
|
_sendFunction = _contract.function('transfer');
|
||||||
_client = await getEthClient();
|
_client = await getEthClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
// TODO: implement isRefreshing
|
// TODO: implement isRefreshing
|
||||||
bool get isRefreshing => throw UnimplementedError();
|
bool get isRefreshing => throw UnimplementedError();
|
||||||
|
|
||||||
@override
|
|
||||||
Future<int> get maxFee async {
|
Future<int> get maxFee async {
|
||||||
final fee = (await fees).fast;
|
final fee = (await fees).fast;
|
||||||
final feeEstimate = await estimateFeeFor(0, fee);
|
final feeEstimate = await estimateFeeFor(0, fee);
|
||||||
return feeEstimate;
|
return feeEstimate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Map<String, dynamic>> prepareSend(
|
Future<Map<String, dynamic>> prepareSend(
|
||||||
{required String address,
|
{required String address,
|
||||||
required int satoshiAmount,
|
required int satoshiAmount,
|
||||||
|
@ -292,13 +209,11 @@ class EthereumToken extends TokenServiceAPI {
|
||||||
return txData;
|
return txData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> refresh() {
|
Future<void> refresh() {
|
||||||
// TODO: implement refresh
|
// TODO: implement refresh
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Decimal> get totalBalance async {
|
Future<Decimal> get totalBalance async {
|
||||||
final balanceRequest = await _client.call(
|
final balanceRequest = await _client.call(
|
||||||
contract: _contract,
|
contract: _contract,
|
||||||
|
@ -306,40 +221,35 @@ class EthereumToken extends TokenServiceAPI {
|
||||||
params: [_credentials.address]);
|
params: [_credentials.address]);
|
||||||
|
|
||||||
String balance = balanceRequest.first.toString();
|
String balance = balanceRequest.first.toString();
|
||||||
int tokenDecimals = int.parse(_tokenData["decimals"] as String);
|
final balanceInDecimal = Format.satoshisToEthTokenAmount(
|
||||||
final balanceInDecimal = (int.parse(balance) / (pow(10, tokenDecimals)));
|
int.parse(balance),
|
||||||
|
token.decimals,
|
||||||
|
);
|
||||||
return Decimal.parse(balanceInDecimal.toString());
|
return Decimal.parse(balanceInDecimal.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<TransactionData> get transactionData =>
|
Future<TransactionData> get transactionData =>
|
||||||
_transactionData ??= _fetchTransactionData();
|
_transactionData ??= _fetchTransactionData();
|
||||||
Future<TransactionData>? _transactionData;
|
Future<TransactionData>? _transactionData;
|
||||||
|
|
||||||
Future<TransactionData> _fetchTransactionData() async {
|
Future<TransactionData> _fetchTransactionData() async {
|
||||||
String thisAddress = await currentReceivingAddress;
|
String thisAddress = await currentReceivingAddress;
|
||||||
// final cachedTransactions = {} as TransactionData?;
|
|
||||||
int latestTxnBlockHeight = 0;
|
|
||||||
|
|
||||||
// final priceData =
|
|
||||||
// await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency);
|
|
||||||
Decimal currentPrice = Decimal.zero;
|
|
||||||
final List<Map<String, dynamic>> midSortedArray = [];
|
final List<Map<String, dynamic>> midSortedArray = [];
|
||||||
|
|
||||||
AddressTransaction txs =
|
AddressTransaction txs =
|
||||||
await fetchAddressTransactions(thisAddress, "tokentx");
|
await EthereumAPI.fetchAddressTransactions(thisAddress, "tokentx");
|
||||||
|
|
||||||
if (txs.message == "OK") {
|
if (txs.message == "OK") {
|
||||||
final allTxs = txs.result;
|
final allTxs = txs.result;
|
||||||
allTxs.forEach((element) {
|
for (var element in allTxs) {
|
||||||
Map<String, dynamic> midSortedTx = {};
|
Map<String, dynamic> midSortedTx = {};
|
||||||
// create final tx map
|
// create final tx map
|
||||||
midSortedTx["txid"] = element["hash"];
|
midSortedTx["txid"] = element["hash"];
|
||||||
int confirmations = int.parse(element['confirmations'].toString());
|
int confirmations = int.parse(element['confirmations'].toString());
|
||||||
|
|
||||||
int transactionAmount = int.parse(element['value'].toString());
|
int transactionAmount = int.parse(element['value'].toString());
|
||||||
int decimal = int.parse(
|
int decimal = token.decimals; //Eth has up to 18 decimal places
|
||||||
_tokenData["decimals"] as String); //Eth has up to 18 decimal places
|
|
||||||
final transactionAmountInDecimal =
|
final transactionAmountInDecimal =
|
||||||
transactionAmount / (pow(10, decimal));
|
transactionAmount / (pow(10, decimal));
|
||||||
|
|
||||||
|
@ -360,18 +270,12 @@ class EthereumToken extends TokenServiceAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
midSortedTx["amount"] = satAmount;
|
midSortedTx["amount"] = satAmount;
|
||||||
final String worthNow = ((currentPrice * Decimal.fromInt(satAmount)) /
|
|
||||||
Decimal.fromInt(Constants.satsPerCoin(coin)))
|
|
||||||
.toDecimal(scaleOnInfinitePrecision: 2)
|
|
||||||
.toStringAsFixed(2);
|
|
||||||
|
|
||||||
//Calculate fees (GasLimit * gasPrice)
|
//Calculate fees (GasLimit * gasPrice)
|
||||||
int txFee = int.parse(element['gasPrice'].toString()) *
|
int txFee = int.parse(element['gasPrice'].toString()) *
|
||||||
int.parse(element['gasUsed'].toString());
|
int.parse(element['gasUsed'].toString());
|
||||||
final txFeeDecimal = txFee / (pow(10, decimal));
|
final txFeeDecimal = txFee / (pow(10, decimal));
|
||||||
|
|
||||||
midSortedTx["worthNow"] = worthNow;
|
|
||||||
midSortedTx["worthAtBlockTimestamp"] = worthNow;
|
|
||||||
midSortedTx["aliens"] = <dynamic>[];
|
midSortedTx["aliens"] = <dynamic>[];
|
||||||
midSortedTx["fees"] = Format.decimalAmountToSatoshis(
|
midSortedTx["fees"] = Format.decimalAmountToSatoshis(
|
||||||
Decimal.parse(txFeeDecimal.toString()), coin);
|
Decimal.parse(txFeeDecimal.toString()), coin);
|
||||||
|
@ -383,7 +287,7 @@ class EthereumToken extends TokenServiceAPI {
|
||||||
midSortedTx["height"] = int.parse(element['blockNumber'].toString());
|
midSortedTx["height"] = int.parse(element['blockNumber'].toString());
|
||||||
|
|
||||||
midSortedArray.add(midSortedTx);
|
midSortedArray.add(midSortedTx);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
midSortedArray.sort((a, b) =>
|
midSortedArray.sort((a, b) =>
|
||||||
|
@ -428,39 +332,10 @@ class EthereumToken extends TokenServiceAPI {
|
||||||
return txModel;
|
return txModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
bool validateAddress(String address) {
|
bool validateAddress(String address) {
|
||||||
return isValidEthereumAddress(address);
|
return isValidEthereumAddress(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Validate that a custom token is valid and is ERC-20, a token will be valid
|
|
||||||
@override
|
|
||||||
Future<TokenData> getTokenByContractAddress(String contractAddress) async {
|
|
||||||
final response = await get(Uri.parse(
|
|
||||||
"$blockExplorer?module=token&action=getToken&contractaddress=$contractAddress"));
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
return TokenData.fromJson(
|
|
||||||
json.decode(response.body) as Map<String, dynamic>);
|
|
||||||
} else {
|
|
||||||
throw Exception("ERROR GETTING TOKEN ${response.reasonPhrase}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Validate that a custom token is valid and is ERC-20
|
|
||||||
@override
|
|
||||||
Future<bool> isValidToken(String contractAddress) async {
|
|
||||||
TokenData tokenData = await getTokenByContractAddress(contractAddress);
|
|
||||||
|
|
||||||
if (tokenData.message == "OK") {
|
|
||||||
final result = tokenData.result;
|
|
||||||
if (result["type"] == "ERC-20") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<NodeModel> getCurrentNode() async {
|
Future<NodeModel> getCurrentNode() async {
|
||||||
return NodeService(secureStorageInterface: _secureStore)
|
return NodeService(secureStorageInterface: _secureStore)
|
||||||
.getPrimaryNodeFor(coin: coin) ??
|
.getPrimaryNodeFor(coin: coin) ??
|
|
@ -1,62 +0,0 @@
|
||||||
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(
|
|
||||||
Map<dynamic, dynamic> tokenData,
|
|
||||||
Future<List<String>> walletMnemonic,
|
|
||||||
SecureStorageInterface secureStorageInterface,
|
|
||||||
TransactionNotificationTracker tracker,
|
|
||||||
Prefs prefs,
|
|
||||||
) {
|
|
||||||
return EthereumToken(
|
|
||||||
tokenData: tokenData,
|
|
||||||
walletMnemonic: walletMnemonic,
|
|
||||||
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<Decimal> get availableBalance;
|
|
||||||
Future<Decimal> get totalBalance;
|
|
||||||
|
|
||||||
Future<List<String>> get allOwnAddresses;
|
|
||||||
|
|
||||||
Future<TransactionData> get transactionData;
|
|
||||||
|
|
||||||
Future<void> refresh();
|
|
||||||
|
|
||||||
bool validateAddress(String address);
|
|
||||||
|
|
||||||
Future<void> initializeNew();
|
|
||||||
Future<void> initializeExisting();
|
|
||||||
|
|
||||||
Future<int> estimateFeeFor(int satoshiAmount, int feeRate);
|
|
||||||
|
|
||||||
Future<bool> isValidToken(String contractAddress);
|
|
||||||
}
|
|
|
@ -1,12 +1,8 @@
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:bip32/bip32.dart' as bip32;
|
import 'package:bip32/bip32.dart' as bip32;
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
import 'package:ethereum_addresses/ethereum_addresses.dart';
|
|
||||||
import "package:hex/hex.dart";
|
import "package:hex/hex.dart";
|
||||||
import 'package:http/http.dart';
|
|
||||||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
|
||||||
|
|
||||||
class AddressTransaction {
|
class AddressTransaction {
|
||||||
final String message;
|
final String message;
|
||||||
|
@ -58,62 +54,7 @@ class GasTracker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const blockExplorer = "https://blockscout.com/eth/mainnet/api";
|
|
||||||
const abiUrl =
|
|
||||||
"https://api.etherscan.io/api"; //TODO - Once our server has abi functionality update
|
|
||||||
const hdPathEthereum = "m/44'/60'/0'/0";
|
const hdPathEthereum = "m/44'/60'/0'/0";
|
||||||
const _gasTrackerUrl =
|
|
||||||
"https://blockscout.com/eth/mainnet/api/v1/gas-price-oracle";
|
|
||||||
|
|
||||||
Future<AddressTransaction> fetchAddressTransactions(
|
|
||||||
String address, String action) async {
|
|
||||||
try {
|
|
||||||
final response = await get(Uri.parse(
|
|
||||||
"$blockExplorer?module=account&action=$action&address=$address"));
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
return AddressTransaction.fromJson(
|
|
||||||
json.decode(response.body) as Map<String, dynamic>);
|
|
||||||
} else {
|
|
||||||
throw Exception(
|
|
||||||
'ERROR GETTING TRANSACTIONS WITH STATUS ${response.statusCode}');
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
throw Exception('ERROR GETTING TRANSACTIONS ${e.toString()}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<dynamic>> getWalletTokens(String address) async {
|
|
||||||
AddressTransaction tokens =
|
|
||||||
await fetchAddressTransactions(address, "tokentx");
|
|
||||||
List<dynamic> tokensList = [];
|
|
||||||
var tokenMap = {};
|
|
||||||
if (tokens.message == "OK") {
|
|
||||||
final allTxs = tokens.result;
|
|
||||||
allTxs.forEach((element) {
|
|
||||||
String key = element["tokenSymbol"] as String;
|
|
||||||
tokenMap[key] = {};
|
|
||||||
tokenMap[key]["balance"] = 0;
|
|
||||||
|
|
||||||
if (tokenMap.containsKey(key)) {
|
|
||||||
tokenMap[key]["contractAddress"] = element["contractAddress"] as String;
|
|
||||||
tokenMap[key]["decimals"] = element["tokenDecimal"];
|
|
||||||
tokenMap[key]["name"] = element["tokenName"];
|
|
||||||
tokenMap[key]["symbol"] = element["tokenSymbol"];
|
|
||||||
if (checksumEthereumAddress(address) == address) {
|
|
||||||
tokenMap[key]["balance"] += int.parse(element["value"] as String);
|
|
||||||
} else {
|
|
||||||
tokenMap[key]["balance"] -= int.parse(element["value"] as String);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
tokenMap.forEach((key, value) {
|
|
||||||
tokensList.add(value as Map<dynamic, dynamic>);
|
|
||||||
});
|
|
||||||
return tokensList;
|
|
||||||
}
|
|
||||||
return <dynamic>[];
|
|
||||||
}
|
|
||||||
|
|
||||||
String getPrivateKey(String mnemonic, String mnemonicPassphrase) {
|
String getPrivateKey(String mnemonic, String mnemonicPassphrase) {
|
||||||
final isValidMnemonic = bip39.validateMnemonic(mnemonic);
|
final isValidMnemonic = bip39.validateMnemonic(mnemonic);
|
||||||
|
@ -129,31 +70,6 @@ String getPrivateKey(String mnemonic, String mnemonicPassphrase) {
|
||||||
return HEX.encode(addressAtIndex.privateKey as List<int>);
|
return HEX.encode(addressAtIndex.privateKey as List<int>);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<GasTracker> getGasOracle() async {
|
|
||||||
final response = await get(Uri.parse(_gasTrackerUrl));
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
return GasTracker.fromJson(
|
|
||||||
json.decode(response.body) as Map<String, dynamic>);
|
|
||||||
} else {
|
|
||||||
throw Exception('Failed to load gas oracle');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<FeeObject> getFees() async {
|
|
||||||
GasTracker fees = await getGasOracle();
|
|
||||||
final feesFast = fees.fast * (pow(10, 9));
|
|
||||||
final feesStandard = fees.average * (pow(10, 9));
|
|
||||||
final feesSlow = fees.slow * (pow(10, 9));
|
|
||||||
|
|
||||||
return FeeObject(
|
|
||||||
numberOfBlocksFast: 1,
|
|
||||||
numberOfBlocksAverage: 3,
|
|
||||||
numberOfBlocksSlow: 3,
|
|
||||||
fast: feesFast.toInt(),
|
|
||||||
medium: feesStandard.toInt(),
|
|
||||||
slow: feesSlow.toInt());
|
|
||||||
}
|
|
||||||
|
|
||||||
double estimateFee(int feeRate, int gasLimit, int decimals) {
|
double estimateFee(int feeRate, int gasLimit, int decimals) {
|
||||||
final gweiAmount = feeRate / (pow(10, 9));
|
final gweiAmount = feeRate / (pow(10, 9));
|
||||||
final fee = gasLimit * gweiAmount;
|
final fee = gasLimit * gweiAmount;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:math';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:decimal/decimal.dart';
|
import 'package:decimal/decimal.dart';
|
||||||
|
@ -19,6 +20,12 @@ abstract class Format {
|
||||||
scaleOnInfinitePrecision: Constants.decimalPlacesForCoin(coin));
|
scaleOnInfinitePrecision: Constants.decimalPlacesForCoin(coin));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Decimal satoshisToEthTokenAmount(int sats, int decimalPlaces) {
|
||||||
|
return (Decimal.fromInt(sats) /
|
||||||
|
Decimal.fromInt(pow(10, decimalPlaces).toInt()))
|
||||||
|
.toDecimal(scaleOnInfinitePrecision: decimalPlaces);
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
static String satoshiAmountToPrettyString(
|
static String satoshiAmountToPrettyString(
|
||||||
int sats, String locale, Coin coin) {
|
int sats, String locale, Coin coin) {
|
||||||
|
|
43
lib/utilities/show_loading.dart
Normal file
43
lib/utilities/show_loading.dart
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/widgets/custom_loading_overlay.dart';
|
||||||
|
|
||||||
|
Future<T> showLoading<T>({
|
||||||
|
required Future<T> whileFuture,
|
||||||
|
required BuildContext context,
|
||||||
|
required String message,
|
||||||
|
String? subMessage,
|
||||||
|
bool isDesktop = false,
|
||||||
|
}) async {
|
||||||
|
unawaited(
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (_) => WillPopScope(
|
||||||
|
onWillPop: () async => false,
|
||||||
|
child: Container(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.overlay
|
||||||
|
.withOpacity(0.6),
|
||||||
|
child: CustomLoadingOverlay(
|
||||||
|
message: message,
|
||||||
|
subMessage: subMessage,
|
||||||
|
eventBus: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = await whileFuture;
|
||||||
|
|
||||||
|
// TODO: update to flutter 3.7.x to take advantage of context.mounted
|
||||||
|
// if (mounted) {
|
||||||
|
Navigator.of(context, rootNavigator: isDesktop).pop();
|
||||||
|
// }
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
Loading…
Reference in a new issue