mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-23 19:05:51 +00:00
WIP: Add test ETH Token functionality in stack
This commit is contained in:
parent
d4653ea794
commit
dbcbfe342c
13 changed files with 861 additions and 1144 deletions
|
@ -4,8 +4,6 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:stackwallet/pages/token_view/token_view.dart';
|
import 'package:stackwallet/pages/token_view/token_view.dart';
|
||||||
import 'package:stackwallet/pages/wallets_view/wallets_view.dart';
|
|
||||||
import 'package:stackwallet/providers/global/tokens_provider.dart';
|
|
||||||
import 'package:stackwallet/services/tokens/ethereum/ethereum_token.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';
|
||||||
|
@ -57,13 +55,15 @@ class MyTokenSelectItem extends ConsumerWidget {
|
||||||
final mnemonicList = ref.read(managerProvider).mnemonic;
|
final mnemonicList = ref.read(managerProvider).mnemonic;
|
||||||
|
|
||||||
final token = EthereumToken(
|
final token = EthereumToken(
|
||||||
contractAddress: tokenData["contractAddress"] as String,
|
// contractAddress: tokenData["contractAddress"] as String,
|
||||||
|
tokenData: tokenData,
|
||||||
walletMnemonic: mnemonicList);
|
walletMnemonic: mnemonicList);
|
||||||
|
|
||||||
Navigator.of(context).pushNamed(
|
Navigator.of(context).pushNamed(
|
||||||
TokenView.routeName,
|
TokenView.routeName,
|
||||||
arguments: Tuple3(
|
arguments: Tuple4(
|
||||||
walletId,
|
walletId,
|
||||||
|
tokenData,
|
||||||
ref
|
ref
|
||||||
.read(walletsChangeNotifierProvider)
|
.read(walletsChangeNotifierProvider)
|
||||||
.getManagerProvider(walletId),
|
.getManagerProvider(walletId),
|
||||||
|
|
128
lib/pages/token_view/sub_widgets/token_summary.dart
Normal file
128
lib/pages/token_view/sub_widgets/token_summary.dart
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:stackwallet/pages/token_view/sub_widgets/token_summary_info.dart';
|
||||||
|
import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_summary_info.dart';
|
||||||
|
import 'package:stackwallet/services/coins/manager.dart';
|
||||||
|
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
||||||
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
|
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||||
|
|
||||||
|
class TokenSummary extends StatelessWidget {
|
||||||
|
const TokenSummary({
|
||||||
|
Key? key,
|
||||||
|
required this.walletId,
|
||||||
|
required this.managerProvider,
|
||||||
|
required this.initialSyncStatus,
|
||||||
|
this.aspectRatio = 2.0,
|
||||||
|
this.minHeight = 100.0,
|
||||||
|
this.minWidth = 200.0,
|
||||||
|
this.maxHeight = 250.0,
|
||||||
|
this.maxWidth = 400.0,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final String walletId;
|
||||||
|
final ChangeNotifierProvider<Manager> managerProvider;
|
||||||
|
final WalletSyncStatus initialSyncStatus;
|
||||||
|
|
||||||
|
final double aspectRatio;
|
||||||
|
final double minHeight;
|
||||||
|
final double minWidth;
|
||||||
|
final double maxHeight;
|
||||||
|
final double maxWidth;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AspectRatio(
|
||||||
|
aspectRatio: aspectRatio,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
minHeight: minHeight,
|
||||||
|
minWidth: minWidth,
|
||||||
|
maxHeight: maxHeight,
|
||||||
|
maxWidth: minWidth,
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Consumer(
|
||||||
|
builder: (_, ref, __) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.colorForCoin(ref.watch(
|
||||||
|
managerProvider.select((value) => value.coin))),
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Positioned.fill(
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Spacer(
|
||||||
|
flex: 5,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 6,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
Assets.svg.ellipse1,
|
||||||
|
// fit: BoxFit.fitWidth,
|
||||||
|
// clipBehavior: Clip.none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 25,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Positioned.fill(
|
||||||
|
// child:
|
||||||
|
// Column(
|
||||||
|
// mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
// children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Spacer(
|
||||||
|
flex: 1,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
Assets.svg.ellipse2,
|
||||||
|
// fit: BoxFit.f,
|
||||||
|
// clipBehavior: Clip.none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 13,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
Positioned.fill(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: TokenSummaryInfo(
|
||||||
|
walletId: walletId,
|
||||||
|
managerProvider: managerProvider,
|
||||||
|
initialSyncStatus: initialSyncStatus,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
298
lib/pages/token_view/sub_widgets/token_summary_info.dart
Normal file
298
lib/pages/token_view/sub_widgets/token_summary_info.dart
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
import 'package:decimal/decimal.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart';
|
||||||
|
import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_refresh_button.dart';
|
||||||
|
import 'package:stackwallet/providers/providers.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/manager.dart';
|
||||||
|
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
||||||
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
|
import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart';
|
||||||
|
import 'package:stackwallet/utilities/format.dart';
|
||||||
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
|
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/widgets/animated_text.dart';
|
||||||
|
|
||||||
|
class TokenSummaryInfo extends StatefulWidget {
|
||||||
|
const TokenSummaryInfo({
|
||||||
|
Key? key,
|
||||||
|
required this.walletId,
|
||||||
|
required this.managerProvider,
|
||||||
|
required this.initialSyncStatus,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final String walletId;
|
||||||
|
final ChangeNotifierProvider<Manager> managerProvider;
|
||||||
|
final WalletSyncStatus initialSyncStatus;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TokenSummaryInfo> createState() => _TokenSummaryInfoState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TokenSummaryInfoState extends State<TokenSummaryInfo> {
|
||||||
|
late final String walletId;
|
||||||
|
late final ChangeNotifierProvider<Manager> managerProvider;
|
||||||
|
|
||||||
|
void showSheet() {
|
||||||
|
showModalBottomSheet<dynamic>(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
context: context,
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.vertical(
|
||||||
|
top: Radius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
builder: (_) => WalletBalanceToggleSheet(walletId: walletId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Decimal? _balanceTotalCached;
|
||||||
|
Decimal? _balanceCached;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
walletId = widget.walletId;
|
||||||
|
managerProvider = widget.managerProvider;
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
debugPrint("BUILD: $runtimeType");
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Consumer(
|
||||||
|
builder: (_, ref, __) {
|
||||||
|
final Coin coin =
|
||||||
|
ref.watch(managerProvider.select((value) => value.coin));
|
||||||
|
final externalCalls = ref.watch(prefsChangeNotifierProvider
|
||||||
|
.select((value) => value.externalCalls));
|
||||||
|
|
||||||
|
Future<Decimal>? totalBalanceFuture;
|
||||||
|
Future<Decimal>? availableBalanceFuture;
|
||||||
|
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||||
|
final firoWallet =
|
||||||
|
ref.watch(managerProvider.select((value) => value.wallet))
|
||||||
|
as FiroWallet;
|
||||||
|
totalBalanceFuture = firoWallet.availablePublicBalance();
|
||||||
|
availableBalanceFuture = firoWallet.availablePrivateBalance();
|
||||||
|
} else {
|
||||||
|
totalBalanceFuture = ref.watch(
|
||||||
|
managerProvider.select((value) => value.totalBalance));
|
||||||
|
|
||||||
|
availableBalanceFuture = ref.watch(
|
||||||
|
managerProvider.select((value) => value.availableBalance));
|
||||||
|
}
|
||||||
|
|
||||||
|
final locale = ref.watch(localeServiceChangeNotifierProvider
|
||||||
|
.select((value) => value.locale));
|
||||||
|
|
||||||
|
final baseCurrency = ref.watch(prefsChangeNotifierProvider
|
||||||
|
.select((value) => value.currency));
|
||||||
|
|
||||||
|
final priceTuple = ref.watch(priceAnd24hChangeNotifierProvider
|
||||||
|
.select((value) => value.getPrice(coin)));
|
||||||
|
|
||||||
|
final _showAvailable =
|
||||||
|
ref.watch(walletBalanceToggleStateProvider.state).state ==
|
||||||
|
WalletBalanceToggleState.available;
|
||||||
|
|
||||||
|
return FutureBuilder(
|
||||||
|
future: _showAvailable
|
||||||
|
? availableBalanceFuture
|
||||||
|
: totalBalanceFuture,
|
||||||
|
builder: (fbContext, AsyncSnapshot<Decimal> snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done &&
|
||||||
|
snapshot.hasData &&
|
||||||
|
snapshot.data != null) {
|
||||||
|
if (_showAvailable) {
|
||||||
|
_balanceCached = snapshot.data!;
|
||||||
|
} else {
|
||||||
|
_balanceTotalCached = snapshot.data!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Decimal? balanceToShow =
|
||||||
|
_showAvailable ? _balanceCached : _balanceTotalCached;
|
||||||
|
|
||||||
|
if (balanceToShow != null) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: showSheet,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
if (coin == Coin.firo || coin == Coin.firoTestNet)
|
||||||
|
Text(
|
||||||
|
"${_showAvailable ? "Private" : "Public"} Balance",
|
||||||
|
style:
|
||||||
|
STextStyles.subtitle500(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFavoriteCard,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"${_showAvailable ? "Available" : "Full"} Balance",
|
||||||
|
style:
|
||||||
|
STextStyles.subtitle500(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFavoriteCard,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 4,
|
||||||
|
),
|
||||||
|
SvgPicture.asset(
|
||||||
|
Assets.svg.chevronDown,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFavoriteCard,
|
||||||
|
width: 8,
|
||||||
|
height: 4,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Text(
|
||||||
|
"${Format.localizedStringAsFixed(
|
||||||
|
value: balanceToShow,
|
||||||
|
locale: locale,
|
||||||
|
decimalPlaces: 8,
|
||||||
|
)} ${coin.ticker}",
|
||||||
|
style: STextStyles.pageTitleH1(context).copyWith(
|
||||||
|
fontSize: 24,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFavoriteCard,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (externalCalls)
|
||||||
|
Text(
|
||||||
|
"${Format.localizedStringAsFixed(
|
||||||
|
value: priceTuple.item1 * balanceToShow,
|
||||||
|
locale: locale,
|
||||||
|
decimalPlaces: 2,
|
||||||
|
)} $baseCurrency",
|
||||||
|
style: STextStyles.subtitle500(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFavoriteCard,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: showSheet,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
if (coin == Coin.firo || coin == Coin.firoTestNet)
|
||||||
|
Text(
|
||||||
|
"${_showAvailable ? "Private" : "Public"} Balance",
|
||||||
|
style:
|
||||||
|
STextStyles.subtitle500(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFavoriteCard,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (coin != Coin.firo && coin != Coin.firoTestNet)
|
||||||
|
Text(
|
||||||
|
"${_showAvailable ? "Available" : "Full"} Balance",
|
||||||
|
style:
|
||||||
|
STextStyles.subtitle500(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFavoriteCard,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 4,
|
||||||
|
),
|
||||||
|
SvgPicture.asset(
|
||||||
|
Assets.svg.chevronDown,
|
||||||
|
width: 8,
|
||||||
|
height: 4,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFavoriteCard,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
AnimatedText(
|
||||||
|
stringsToLoopThrough: const [
|
||||||
|
"Loading balance",
|
||||||
|
"Loading balance.",
|
||||||
|
"Loading balance..",
|
||||||
|
"Loading balance..."
|
||||||
|
],
|
||||||
|
style: STextStyles.pageTitleH1(context).copyWith(
|
||||||
|
fontSize: 24,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFavoriteCard,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AnimatedText(
|
||||||
|
stringsToLoopThrough: const [
|
||||||
|
"Loading balance",
|
||||||
|
"Loading balance.",
|
||||||
|
"Loading balance..",
|
||||||
|
"Loading balance..."
|
||||||
|
],
|
||||||
|
style: STextStyles.subtitle500(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFavoriteCard,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Consumer(
|
||||||
|
builder: (_, ref, __) {
|
||||||
|
return SvgPicture.asset(
|
||||||
|
Assets.svg.iconFor(
|
||||||
|
coin: ref.watch(
|
||||||
|
managerProvider.select((value) => value.coin),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
WalletRefreshButton(
|
||||||
|
walletId: walletId,
|
||||||
|
initialSyncStatus: widget.initialSyncStatus,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ import 'package:stackwallet/pages/send_view/send_view.dart';
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart';
|
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart';
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_view.dart';
|
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_view.dart';
|
||||||
import 'package:stackwallet/pages/token_view/my_tokens_view.dart';
|
import 'package:stackwallet/pages/token_view/my_tokens_view.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';
|
||||||
import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart';
|
import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart';
|
||||||
import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_summary.dart';
|
import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_summary.dart';
|
||||||
|
@ -25,7 +26,6 @@ import 'package:stackwallet/providers/ui/transaction_filter_provider.dart';
|
||||||
import 'package:stackwallet/providers/ui/unread_notifications_provider.dart';
|
import 'package:stackwallet/providers/ui/unread_notifications_provider.dart';
|
||||||
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
|
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
|
||||||
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/manager.dart';
|
import 'package:stackwallet/services/coins/manager.dart';
|
||||||
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
|
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
|
||||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
||||||
|
@ -55,6 +55,7 @@ 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.managerProvider,
|
||||||
required this.token,
|
required this.token,
|
||||||
this.eventBus,
|
this.eventBus,
|
||||||
|
@ -64,6 +65,7 @@ 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 ChangeNotifierProvider<Manager> managerProvider;
|
final ChangeNotifierProvider<Manager> managerProvider;
|
||||||
final EthereumToken token;
|
final EthereumToken token;
|
||||||
final EventBus? eventBus;
|
final EventBus? eventBus;
|
||||||
|
@ -209,52 +211,11 @@ class _TokenViewState extends ConsumerState<TokenView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildNetworkIcon(WalletSyncStatus status) {
|
|
||||||
switch (status) {
|
|
||||||
case WalletSyncStatus.unableToSync:
|
|
||||||
return SvgPicture.asset(
|
|
||||||
Assets.svg.radioProblem,
|
|
||||||
color: Theme.of(context).extension<StackColors>()!.accentColorRed,
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
);
|
|
||||||
case WalletSyncStatus.synced:
|
|
||||||
return SvgPicture.asset(
|
|
||||||
Assets.svg.radio,
|
|
||||||
color: Theme.of(context).extension<StackColors>()!.accentColorGreen,
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
);
|
|
||||||
case WalletSyncStatus.syncing:
|
|
||||||
return SvgPicture.asset(
|
|
||||||
Assets.svg.radioSyncing,
|
|
||||||
color: Theme.of(context).extension<StackColors>()!.accentColorYellow,
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onExchangePressed(BuildContext context) async {
|
void _onExchangePressed(BuildContext context) async {
|
||||||
unawaited(_cnLoadingService.loadAll(ref));
|
unawaited(_cnLoadingService.loadAll(ref));
|
||||||
|
|
||||||
final coin = ref.read(managerProvider).coin;
|
final coin = ref.read(managerProvider).coin;
|
||||||
|
|
||||||
if (coin == Coin.epicCash) {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => const StackOkDialog(
|
|
||||||
title: "Exchange not available for Epic Cash",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else if (coin.name.endsWith("TestNet")) {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => const StackOkDialog(
|
|
||||||
title: "Exchange not available for test net coins",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ref.read(currentExchangeNameStateProvider.state).state =
|
ref.read(currentExchangeNameStateProvider.state).state =
|
||||||
ChangeNowExchange.exchangeName;
|
ChangeNowExchange.exchangeName;
|
||||||
final walletId = ref.read(managerProvider).walletId;
|
final walletId = ref.read(managerProvider).walletId;
|
||||||
|
@ -279,8 +240,7 @@ class _TokenViewState extends ConsumerState<TokenView> {
|
||||||
.currencies
|
.currencies
|
||||||
.firstWhere(
|
.firstWhere(
|
||||||
(element) =>
|
(element) =>
|
||||||
element.ticker.toLowerCase() !=
|
element.ticker.toLowerCase() != coin.ticker.toLowerCase(),
|
||||||
coin.ticker.toLowerCase(),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -298,73 +258,6 @@ class _TokenViewState extends ConsumerState<TokenView> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> attemptAnonymize() async {
|
|
||||||
bool shouldPop = false;
|
|
||||||
unawaited(
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => WillPopScope(
|
|
||||||
child: const CustomLoadingOverlay(
|
|
||||||
message: "Anonymizing balance",
|
|
||||||
eventBus: null,
|
|
||||||
),
|
|
||||||
onWillPop: () async => shouldPop,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final firoWallet = ref.read(managerProvider).wallet as FiroWallet;
|
|
||||||
|
|
||||||
final publicBalance = await firoWallet.availablePublicBalance();
|
|
||||||
if (publicBalance <= Decimal.zero) {
|
|
||||||
shouldPop = true;
|
|
||||||
if (mounted) {
|
|
||||||
Navigator.of(context).popUntil(
|
|
||||||
ModalRoute.withName(TokenView.routeName),
|
|
||||||
);
|
|
||||||
unawaited(
|
|
||||||
showFloatingFlushBar(
|
|
||||||
type: FlushBarType.info,
|
|
||||||
message: "No funds available to anonymize!",
|
|
||||||
context: context,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await firoWallet.anonymizeAllPublicFunds();
|
|
||||||
shouldPop = true;
|
|
||||||
if (mounted) {
|
|
||||||
Navigator.of(context).popUntil(
|
|
||||||
ModalRoute.withName(TokenView.routeName),
|
|
||||||
);
|
|
||||||
unawaited(
|
|
||||||
showFloatingFlushBar(
|
|
||||||
type: FlushBarType.success,
|
|
||||||
message: "Anonymize transaction submitted",
|
|
||||||
context: context,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
shouldPop = true;
|
|
||||||
if (mounted) {
|
|
||||||
Navigator.of(context).popUntil(
|
|
||||||
ModalRoute.withName(TokenView.routeName),
|
|
||||||
);
|
|
||||||
await showDialog<dynamic>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => StackOkDialog(
|
|
||||||
title: "Anonymize all failed",
|
|
||||||
message: "Reason: $e",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _loadCNData() {
|
void _loadCNData() {
|
||||||
// unawaited future
|
// unawaited future
|
||||||
|
@ -379,6 +272,8 @@ class _TokenViewState extends ConsumerState<TokenView> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
debugPrint("BUILD: $runtimeType");
|
debugPrint("BUILD: $runtimeType");
|
||||||
|
widget.token.initializeExisting();
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
@ -407,148 +302,23 @@ class _TokenViewState extends ConsumerState<TokenView> {
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 16,
|
width: 16,
|
||||||
),
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
widget.tokenData["name"] as String,
|
||||||
|
style: STextStyles.navBarTitle(context),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
ref.watch(
|
ref.watch(
|
||||||
managerProvider.select((value) => value.walletName)),
|
managerProvider.select((value) => value.coin.ticker)),
|
||||||
style: STextStyles.navBarTitle(context),
|
style: STextStyles.navBarTitle(context),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
top: 10,
|
|
||||||
bottom: 10,
|
|
||||||
right: 10,
|
|
||||||
),
|
|
||||||
child: AspectRatio(
|
|
||||||
aspectRatio: 1,
|
|
||||||
child: AppBarIconButton(
|
|
||||||
key: const Key("TokenViewRadioButton"),
|
|
||||||
size: 36,
|
|
||||||
shadows: const [],
|
|
||||||
color:
|
|
||||||
Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
icon: _buildNetworkIcon(_currentSyncStatus),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pushNamed(
|
|
||||||
WalletNetworkSettingsView.routeName,
|
|
||||||
arguments: Tuple3(
|
|
||||||
walletId,
|
|
||||||
_currentSyncStatus,
|
|
||||||
_currentNodeStatus,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
top: 10,
|
|
||||||
bottom: 10,
|
|
||||||
right: 10,
|
|
||||||
),
|
|
||||||
child: AspectRatio(
|
|
||||||
aspectRatio: 1,
|
|
||||||
child: AppBarIconButton(
|
|
||||||
key: const Key("TokenViewAlertsButton"),
|
|
||||||
size: 36,
|
|
||||||
shadows: const [],
|
|
||||||
color:
|
|
||||||
Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
icon: SvgPicture.asset(
|
|
||||||
ref.watch(notificationsProvider.select((value) =>
|
|
||||||
value.hasUnreadNotificationsFor(walletId)))
|
|
||||||
? Assets.svg.bellNew(context)
|
|
||||||
: Assets.svg.bell,
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
color: ref.watch(notificationsProvider.select((value) =>
|
|
||||||
value.hasUnreadNotificationsFor(walletId)))
|
|
||||||
? null
|
|
||||||
: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.topNavIconPrimary,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
// reset unread state
|
|
||||||
ref.refresh(unreadNotificationsStateProvider);
|
|
||||||
|
|
||||||
Navigator.of(context)
|
|
||||||
.pushNamed(
|
|
||||||
NotificationsView.routeName,
|
|
||||||
arguments: walletId,
|
|
||||||
)
|
|
||||||
.then((_) {
|
|
||||||
final Set<int> unreadNotificationIds = ref
|
|
||||||
.read(unreadNotificationsStateProvider.state)
|
|
||||||
.state;
|
|
||||||
if (unreadNotificationIds.isEmpty) return;
|
|
||||||
|
|
||||||
List<Future<dynamic>> futures = [];
|
|
||||||
for (int i = 0;
|
|
||||||
i < unreadNotificationIds.length - 1;
|
|
||||||
i++) {
|
|
||||||
futures.add(ref
|
|
||||||
.read(notificationsProvider)
|
|
||||||
.markAsRead(
|
|
||||||
unreadNotificationIds.elementAt(i), false));
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for multiple to update if any
|
|
||||||
Future.wait(futures).then((_) {
|
|
||||||
// only notify listeners once
|
|
||||||
ref
|
|
||||||
.read(notificationsProvider)
|
|
||||||
.markAsRead(unreadNotificationIds.last, true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
top: 10,
|
|
||||||
bottom: 10,
|
|
||||||
right: 10,
|
|
||||||
),
|
|
||||||
child: AspectRatio(
|
|
||||||
aspectRatio: 1,
|
|
||||||
child: AppBarIconButton(
|
|
||||||
key: const Key("TokenViewSettingsButton"),
|
|
||||||
size: 36,
|
|
||||||
shadows: const [],
|
|
||||||
color:
|
|
||||||
Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
icon: SvgPicture.asset(
|
|
||||||
Assets.svg.bars,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.accentColorDark,
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
debugPrint("wallet view settings tapped");
|
|
||||||
Navigator.of(context).pushNamed(
|
|
||||||
WalletSettingsView.routeName,
|
|
||||||
arguments: Tuple4(
|
|
||||||
walletId,
|
|
||||||
ref.read(managerProvider).coin,
|
|
||||||
_currentSyncStatus,
|
|
||||||
_currentNodeStatus,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Container(
|
child: Container(
|
||||||
|
@ -561,7 +331,7 @@ class _TokenViewState extends ConsumerState<TokenView> {
|
||||||
Center(
|
Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: WalletSummary(
|
child: TokenSummary(
|
||||||
walletId: walletId,
|
walletId: walletId,
|
||||||
managerProvider: managerProvider,
|
managerProvider: managerProvider,
|
||||||
initialSyncStatus: ref.watch(managerProvider
|
initialSyncStatus: ref.watch(managerProvider
|
||||||
|
@ -571,72 +341,6 @@ class _TokenViewState extends ConsumerState<TokenView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (coin == Coin.firo)
|
|
||||||
const SizedBox(
|
|
||||||
height: 10,
|
|
||||||
),
|
|
||||||
if (coin == Coin.firo)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: TextButton(
|
|
||||||
style: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.getSecondaryEnabledButtonColor(context),
|
|
||||||
onPressed: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => StackDialog(
|
|
||||||
title: "Attention!",
|
|
||||||
message:
|
|
||||||
"You're about to anonymize all of your public funds.",
|
|
||||||
leftButton: TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
"Cancel",
|
|
||||||
style: STextStyles.button(context)
|
|
||||||
.copyWith(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.accentColorDark,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
rightButton: TextButton(
|
|
||||||
onPressed: () async {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
|
|
||||||
unawaited(attemptAnonymize());
|
|
||||||
},
|
|
||||||
style: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.getPrimaryEnabledButtonColor(
|
|
||||||
context),
|
|
||||||
child: Text(
|
|
||||||
"Continue",
|
|
||||||
style: STextStyles.button(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
"Anonymize funds",
|
|
||||||
style: STextStyles.button(context).copyWith(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.buttonTextSecondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 20,
|
height: 20,
|
||||||
),
|
),
|
||||||
|
@ -715,91 +419,91 @@ class _TokenViewState extends ConsumerState<TokenView> {
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
// Padding(
|
||||||
padding: const EdgeInsets.only(
|
// padding: const EdgeInsets.only(
|
||||||
bottom: 14,
|
// bottom: 14,
|
||||||
left: 16,
|
// left: 16,
|
||||||
right: 16,
|
// right: 16,
|
||||||
),
|
// ),
|
||||||
child: SizedBox(
|
// child: SizedBox(
|
||||||
height: TokenView.navBarHeight,
|
// height: TokenView.navBarHeight,
|
||||||
child: WalletNavigationBar(
|
// child: WalletNavigationBar(
|
||||||
enableExchange:
|
// enableExchange:
|
||||||
Constants.enableExchange &&
|
// Constants.enableExchange &&
|
||||||
ref.watch(managerProvider.select(
|
// ref.watch(managerProvider.select(
|
||||||
(value) => value.coin)) !=
|
// (value) => value.coin)) !=
|
||||||
Coin.epicCash,
|
// Coin.epicCash,
|
||||||
height: TokenView.navBarHeight,
|
// height: TokenView.navBarHeight,
|
||||||
onExchangePressed: () =>
|
// onExchangePressed: () =>
|
||||||
_onExchangePressed(context),
|
// _onExchangePressed(context),
|
||||||
onReceivePressed: () async {
|
// onReceivePressed: () async {
|
||||||
final coin =
|
// final coin =
|
||||||
ref.read(managerProvider).coin;
|
// ref.read(managerProvider).coin;
|
||||||
if (mounted) {
|
// if (mounted) {
|
||||||
unawaited(
|
// unawaited(
|
||||||
Navigator.of(context).pushNamed(
|
// Navigator.of(context).pushNamed(
|
||||||
ReceiveView.routeName,
|
// ReceiveView.routeName,
|
||||||
arguments: Tuple2(
|
// arguments: Tuple2(
|
||||||
walletId,
|
// walletId,
|
||||||
coin,
|
// coin,
|
||||||
),
|
// ),
|
||||||
));
|
// ));
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
onSendPressed: () {
|
// onSendPressed: () {
|
||||||
final walletId =
|
// final walletId =
|
||||||
ref.read(managerProvider).walletId;
|
// ref.read(managerProvider).walletId;
|
||||||
final coin =
|
// final coin =
|
||||||
ref.read(managerProvider).coin;
|
// ref.read(managerProvider).coin;
|
||||||
switch (ref
|
// switch (ref
|
||||||
.read(
|
// .read(
|
||||||
walletBalanceToggleStateProvider
|
// walletBalanceToggleStateProvider
|
||||||
.state)
|
// .state)
|
||||||
.state) {
|
// .state) {
|
||||||
case WalletBalanceToggleState.full:
|
// case WalletBalanceToggleState.full:
|
||||||
ref
|
// ref
|
||||||
.read(
|
// .read(
|
||||||
publicPrivateBalanceStateProvider
|
// publicPrivateBalanceStateProvider
|
||||||
.state)
|
// .state)
|
||||||
.state = "Public";
|
// .state = "Public";
|
||||||
break;
|
// break;
|
||||||
case WalletBalanceToggleState
|
// case WalletBalanceToggleState
|
||||||
.available:
|
// .available:
|
||||||
ref
|
// ref
|
||||||
.read(
|
// .read(
|
||||||
publicPrivateBalanceStateProvider
|
// publicPrivateBalanceStateProvider
|
||||||
.state)
|
// .state)
|
||||||
.state = "Private";
|
// .state = "Private";
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
Navigator.of(context).pushNamed(
|
// Navigator.of(context).pushNamed(
|
||||||
SendView.routeName,
|
// SendView.routeName,
|
||||||
arguments: Tuple2(
|
// arguments: Tuple2(
|
||||||
walletId,
|
// walletId,
|
||||||
coin,
|
// coin,
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
onBuyPressed: () {},
|
// onBuyPressed: () {},
|
||||||
onTokensPressed: () async {
|
// onTokensPressed: () async {
|
||||||
final walletAddress = await ref
|
// final walletAddress = await ref
|
||||||
.read(managerProvider)
|
// .read(managerProvider)
|
||||||
.currentReceivingAddress;
|
// .currentReceivingAddress;
|
||||||
|
//
|
||||||
List<dynamic> tokens =
|
// List<dynamic> tokens =
|
||||||
await getWalletTokens(await ref
|
// await getWalletTokens(await ref
|
||||||
.read(managerProvider)
|
// .read(managerProvider)
|
||||||
.currentReceivingAddress);
|
// .currentReceivingAddress);
|
||||||
|
//
|
||||||
await Navigator.of(context).pushNamed(
|
// await Navigator.of(context).pushNamed(
|
||||||
MyTokensView.routeName,
|
// MyTokensView.routeName,
|
||||||
arguments: Tuple4(managerProvider,
|
// arguments: Tuple4(managerProvider,
|
||||||
walletId, walletAddress, tokens),
|
// walletId, walletAddress, tokens),
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
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;
|
|
||||||
});
|
|
|
@ -1,20 +0,0 @@
|
||||||
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),
|
|
||||||
);
|
|
||||||
});
|
|
|
@ -118,7 +118,6 @@ import 'package:stackwallet/services/coins/manager.dart';
|
||||||
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
|
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
|
||||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
||||||
import 'package:stackwallet/services/tokens/ethereum/ethereum_token.dart';
|
import 'package:stackwallet/services/tokens/ethereum/ethereum_token.dart';
|
||||||
import 'package:stackwallet/services/tokens/token_manager.dart';
|
|
||||||
import 'package:stackwallet/utilities/enums/add_wallet_type_enum.dart';
|
import 'package:stackwallet/utilities/enums/add_wallet_type_enum.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
@ -1328,14 +1327,15 @@ class RouteGenerator {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
case TokenView.routeName:
|
case TokenView.routeName:
|
||||||
if (args
|
if (args is Tuple4<String, Map<dynamic, dynamic>,
|
||||||
is Tuple3<String, ChangeNotifierProvider<Manager>, EthereumToken>) {
|
ChangeNotifierProvider<Manager>, EthereumToken>) {
|
||||||
return getRoute(
|
return getRoute(
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
builder: (_) => TokenView(
|
builder: (_) => TokenView(
|
||||||
walletId: args.item1,
|
walletId: args.item1,
|
||||||
managerProvider: args.item2,
|
tokenData: args.item2,
|
||||||
token: args.item3,
|
managerProvider: args.item3,
|
||||||
|
token: args.item4,
|
||||||
),
|
),
|
||||||
settings: RouteSettings(
|
settings: RouteSettings(
|
||||||
name: settings.name,
|
name: settings.name,
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'package:bip32/bip32.dart' as bip32;
|
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
|
|
||||||
import "package:hex/hex.dart";
|
|
||||||
import 'package:decimal/decimal.dart';
|
import 'package:decimal/decimal.dart';
|
||||||
import 'package:devicelocale/devicelocale.dart';
|
import 'package:devicelocale/devicelocale.dart';
|
||||||
import 'package:ethereum_addresses/ethereum_addresses.dart';
|
import 'package:ethereum_addresses/ethereum_addresses.dart';
|
||||||
|
@ -22,7 +20,6 @@ import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||||
import 'package:stackwallet/utilities/format.dart';
|
import 'package:stackwallet/utilities/format.dart';
|
||||||
import 'package:stackwallet/utilities/prefs.dart';
|
import 'package:stackwallet/utilities/prefs.dart';
|
||||||
import 'package:web3dart/web3dart.dart';
|
import 'package:web3dart/web3dart.dart';
|
||||||
import 'package:web3dart/web3dart.dart' as web3;
|
|
||||||
import 'package:web3dart/web3dart.dart' as Transaction;
|
import 'package:web3dart/web3dart.dart' as Transaction;
|
||||||
import 'package:stackwallet/models/models.dart' as models;
|
import 'package:stackwallet/models/models.dart' as models;
|
||||||
|
|
||||||
|
@ -66,30 +63,10 @@ class AddressTransaction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GasTracker {
|
|
||||||
final int code;
|
|
||||||
final Map<String, dynamic> data;
|
|
||||||
|
|
||||||
const GasTracker({
|
|
||||||
required this.code,
|
|
||||||
required this.data,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory GasTracker.fromJson(Map<String, dynamic> json) {
|
|
||||||
return GasTracker(
|
|
||||||
code: json['code'] as int,
|
|
||||||
data: json['data'] as Map<String, dynamic>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EthereumWallet extends CoinServiceAPI {
|
class EthereumWallet extends CoinServiceAPI {
|
||||||
NodeModel? _ethNode;
|
NodeModel? _ethNode;
|
||||||
final _gasLimit = 21000;
|
final _gasLimit = 21000;
|
||||||
// final _blockExplorer = "https://blockscout.com/eth/mainnet/api?";
|
final _blockExplorer = "https://blockscout.com/eth/mainnet/api?";
|
||||||
final _blockExplorer = "https://api.etherscan.io/api?";
|
|
||||||
final _gasTrackerUrl = "https://beaconcha.in/api/v1/execution/gasnow";
|
|
||||||
final _hdPath = "m/44'/60'/0'/0";
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get walletId => _walletId;
|
String get walletId => _walletId;
|
||||||
|
@ -213,7 +190,8 @@ class EthereumWallet extends CoinServiceAPI {
|
||||||
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(decimalAmount.toDouble());
|
const decimal = 18; //Eth has up to 18 decimal places
|
||||||
|
final bigIntAmount = amountToBigInt(decimalAmount.toDouble(), decimal);
|
||||||
|
|
||||||
final tx = Transaction.Transaction(
|
final tx = Transaction.Transaction(
|
||||||
to: EthereumAddress.fromHex(txData['address'] as String),
|
to: EthereumAddress.fromHex(txData['address'] as String),
|
||||||
|
@ -227,12 +205,6 @@ class EthereumWallet extends CoinServiceAPI {
|
||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
BigInt amountToBigInt(num amount) {
|
|
||||||
const decimal = 18; //Eth has up to 18 decimal places
|
|
||||||
final amountToSendinDecimal = amount * (pow(10, decimal));
|
|
||||||
return BigInt.from(amountToSendinDecimal);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> get currentReceivingAddress async {
|
Future<String> get currentReceivingAddress async {
|
||||||
final _currentReceivingAddress = _credentials.address;
|
final _currentReceivingAddress = _credentials.address;
|
||||||
|
@ -243,14 +215,8 @@ class EthereumWallet extends CoinServiceAPI {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> estimateFeeFor(int satoshiAmount, int feeRate) async {
|
Future<int> estimateFeeFor(int satoshiAmount, int feeRate) async {
|
||||||
final gweiAmount = feeRate / (pow(10, 9));
|
final fee = estimateFee(feeRate, _gasLimit, 18);
|
||||||
final fee = _gasLimit * gweiAmount;
|
return Format.decimalAmountToSatoshis(Decimal.parse(fee.toString()), coin);
|
||||||
|
|
||||||
//Convert gwei to ETH
|
|
||||||
final feeInWei = fee * (pow(10, 9));
|
|
||||||
final ethAmount = feeInWei / (pow(10, 18));
|
|
||||||
return Format.decimalAmountToSatoshis(
|
|
||||||
Decimal.parse(ethAmount.toString()), coin);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -266,26 +232,7 @@ class EthereumWallet extends CoinServiceAPI {
|
||||||
Future<FeeObject>? _feeObject;
|
Future<FeeObject>? _feeObject;
|
||||||
|
|
||||||
Future<FeeObject> _getFees() async {
|
Future<FeeObject> _getFees() async {
|
||||||
GasTracker fees = await getGasOracle();
|
return await getFees();
|
||||||
final feesMap = fees.data;
|
|
||||||
return FeeObject(
|
|
||||||
numberOfBlocksFast: 1,
|
|
||||||
numberOfBlocksAverage: 3,
|
|
||||||
numberOfBlocksSlow: 3,
|
|
||||||
fast: feesMap['fast'] as int,
|
|
||||||
medium: feesMap['standard'] as int,
|
|
||||||
slow: feesMap['slow'] as 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');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Full rescan is not needed for ETH since we have a balance
|
//Full rescan is not needed for ETH since we have a balance
|
||||||
|
@ -331,20 +278,6 @@ class EthereumWallet extends CoinServiceAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String getPrivateKey(String mnemonic) {
|
|
||||||
final isValidMnemonic = bip39.validateMnemonic(mnemonic);
|
|
||||||
if (!isValidMnemonic) {
|
|
||||||
throw 'Invalid mnemonic';
|
|
||||||
}
|
|
||||||
|
|
||||||
final seed = bip39.mnemonicToSeed(mnemonic);
|
|
||||||
final root = bip32.BIP32.fromSeed(seed);
|
|
||||||
const index = 0;
|
|
||||||
final addressAtIndex = root.derivePath("$_hdPath/$index");
|
|
||||||
|
|
||||||
return HEX.encode(addressAtIndex.privateKey as List<int>);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> initializeNew() async {
|
Future<void> initializeNew() async {
|
||||||
await _prefs.init();
|
await _prefs.init();
|
||||||
|
@ -467,15 +400,11 @@ class EthereumWallet extends CoinServiceAPI {
|
||||||
isSendAll = true;
|
isSendAll = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
print("SATOSHI AMOUNT BEFORE $satoshiAmount");
|
|
||||||
print("FEE IS $fee");
|
|
||||||
if (isSendAll) {
|
if (isSendAll) {
|
||||||
//Subtract fee amount from send amount
|
//Subtract fee amount from send amount
|
||||||
satoshiAmount -= feeEstimate;
|
satoshiAmount -= feeEstimate;
|
||||||
}
|
}
|
||||||
|
|
||||||
print("SATOSHI AMOUNT AFTER $satoshiAmount");
|
|
||||||
|
|
||||||
Map<String, dynamic> txData = {
|
Map<String, dynamic> txData = {
|
||||||
"fee": feeEstimate,
|
"fee": feeEstimate,
|
||||||
"feeInWei": fee,
|
"feeInWei": fee,
|
||||||
|
@ -507,7 +436,6 @@ class EthereumWallet extends CoinServiceAPI {
|
||||||
String privateKey = getPrivateKey(mnemonic);
|
String privateKey = getPrivateKey(mnemonic);
|
||||||
_credentials = EthPrivateKey.fromHex(privateKey);
|
_credentials = EthPrivateKey.fromHex(privateKey);
|
||||||
|
|
||||||
// print(_credentials.address);
|
|
||||||
//Get ERC-20 transactions for wallet (So we can get the and save wallet's ERC-20 TOKENS
|
//Get ERC-20 transactions for wallet (So we can get the and save wallet's ERC-20 TOKENS
|
||||||
AddressTransaction tokenTransactions = await fetchAddressTransactions(
|
AddressTransaction tokenTransactions = await fetchAddressTransactions(
|
||||||
_credentials.address.toString(), "tokentx");
|
_credentials.address.toString(), "tokentx");
|
||||||
|
@ -515,7 +443,7 @@ class EthereumWallet extends CoinServiceAPI {
|
||||||
List<Map<dynamic, dynamic>> tokensList = [];
|
List<Map<dynamic, dynamic>> tokensList = [];
|
||||||
if (tokenTransactions.message == "OK") {
|
if (tokenTransactions.message == "OK") {
|
||||||
final allTxs = tokenTransactions.result;
|
final allTxs = tokenTransactions.result;
|
||||||
print("RESULT IS $allTxs");
|
|
||||||
allTxs.forEach((element) {
|
allTxs.forEach((element) {
|
||||||
String key = element["tokenSymbol"] as String;
|
String key = element["tokenSymbol"] as String;
|
||||||
tokenMap[key] = {};
|
tokenMap[key] = {};
|
||||||
|
@ -544,9 +472,6 @@ class EthereumWallet extends CoinServiceAPI {
|
||||||
key: '${_walletId}_tokens', value: tokensList.toString());
|
key: '${_walletId}_tokens', value: tokensList.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
print("THIS WALLET TOKENS IS $tokenMap");
|
|
||||||
print("ALL TOKENS LIST IS $tokensList");
|
|
||||||
|
|
||||||
await DB.instance
|
await DB.instance
|
||||||
.put<dynamic>(boxName: walletId, key: "id", value: _walletId);
|
.put<dynamic>(boxName: walletId, key: "id", value: _walletId);
|
||||||
await DB.instance
|
await DB.instance
|
||||||
|
@ -976,7 +901,9 @@ class EthereumWallet extends CoinServiceAPI {
|
||||||
|
|
||||||
if (checksumEthereumAddress(element["from"].toString()) ==
|
if (checksumEthereumAddress(element["from"].toString()) ==
|
||||||
thisAddress) {
|
thisAddress) {
|
||||||
midSortedTx["txType"] = "Sent";
|
midSortedTx["txType"] = (int.parse(element["isError"] as String) == 0)
|
||||||
|
? "Sent"
|
||||||
|
: "Send Failed";
|
||||||
} else {
|
} else {
|
||||||
midSortedTx["txType"] = "Received";
|
midSortedTx["txType"] = "Received";
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,374 +0,0 @@
|
||||||
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();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
|
@ -1,16 +1,20 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:math';
|
||||||
import 'package:decimal/decimal.dart';
|
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
|
import 'package:decimal/decimal.dart';
|
||||||
|
import 'package:stackwallet/utilities/eth_commons.dart';
|
||||||
|
import 'package:ethereum_addresses/ethereum_addresses.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/tokens/token_service.dart';
|
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:stackwallet/utilities/eth_commons.dart';
|
|
||||||
|
|
||||||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||||
import 'package:stackwallet/services/transaction_notification_tracker.dart';
|
import 'package:stackwallet/services/transaction_notification_tracker.dart';
|
||||||
|
import 'package:stackwallet/services/tokens/token_service.dart';
|
||||||
|
import 'package:stackwallet/utilities/format.dart';
|
||||||
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
|
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
||||||
import 'package:web3dart/web3dart.dart';
|
import 'package:web3dart/web3dart.dart';
|
||||||
|
import 'package:web3dart/web3dart.dart' as transaction;
|
||||||
|
|
||||||
class AbiRequestResponse {
|
class AbiRequestResponse {
|
||||||
final String message;
|
final String message;
|
||||||
|
@ -35,118 +39,222 @@ class AbiRequestResponse {
|
||||||
class EthereumToken extends TokenServiceAPI {
|
class EthereumToken extends TokenServiceAPI {
|
||||||
@override
|
@override
|
||||||
late bool shouldAutoSync;
|
late bool shouldAutoSync;
|
||||||
late String _contractAddress;
|
late EthereumAddress _contractAddress;
|
||||||
late EthPrivateKey _credentials;
|
late EthPrivateKey _credentials;
|
||||||
|
late DeployedContract _contract;
|
||||||
|
late Map<dynamic, dynamic> _tokenData;
|
||||||
|
late ContractFunction _balanceFunction;
|
||||||
|
late ContractFunction _sendFunction;
|
||||||
late Future<List<String>> _walletMnemonic;
|
late Future<List<String>> _walletMnemonic;
|
||||||
late SecureStorageInterface _secureStore;
|
late SecureStorageInterface _secureStore;
|
||||||
late String _tokenAbi;
|
late String _tokenAbi;
|
||||||
|
late Web3Client _client;
|
||||||
late final TransactionNotificationTracker txTracker;
|
late final TransactionNotificationTracker txTracker;
|
||||||
|
|
||||||
String rpcUrl =
|
String rpcUrl =
|
||||||
'https://mainnet.infura.io/v3/22677300bf774e49a458b73313ee56ba';
|
'https://mainnet.infura.io/v3/22677300bf774e49a458b73313ee56ba';
|
||||||
|
final _gasLimit = 200000;
|
||||||
|
|
||||||
EthereumToken({
|
EthereumToken({
|
||||||
required String contractAddress,
|
required Map<dynamic, dynamic> tokenData,
|
||||||
required Future<List<String>> walletMnemonic,
|
required Future<List<String>> walletMnemonic,
|
||||||
// required SecureStorageInterface secureStore,
|
// required SecureStorageInterface secureStore,
|
||||||
}) {
|
}) {
|
||||||
_contractAddress = contractAddress;
|
_contractAddress =
|
||||||
|
EthereumAddress.fromHex(tokenData["contractAddress"] as String);
|
||||||
_walletMnemonic = walletMnemonic;
|
_walletMnemonic = walletMnemonic;
|
||||||
|
_tokenData = tokenData;
|
||||||
// _secureStore = secureStore;
|
// _secureStore = secureStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<AbiRequestResponse> fetchTokenAbi() async {
|
Future<AbiRequestResponse> fetchTokenAbi() async {
|
||||||
|
print(
|
||||||
|
"$blockExplorer?module=contract&action=getabi&address=$_contractAddress&apikey=EG6J7RJIQVSTP2BS59D3TY2G55YHS5F2HP");
|
||||||
final response = await get(Uri.parse(
|
final response = await get(Uri.parse(
|
||||||
"https://api.etherscan.io/api?module=contract&action=getabi&address=$_contractAddress&apikey=EG6J7RJIQVSTP2BS59D3TY2G55YHS5F2HP"));
|
"$blockExplorer?module=contract&action=getabi&address=$_contractAddress&apikey=EG6J7RJIQVSTP2BS59D3TY2G55YHS5F2HP"));
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return AbiRequestResponse.fromJson(
|
return AbiRequestResponse.fromJson(
|
||||||
json.decode(response.body) as Map<String, dynamic>);
|
json.decode(response.body) as Map<String, dynamic>);
|
||||||
} else {
|
} else {
|
||||||
throw Exception('Failed to load transactions');
|
throw Exception('Failed to load token abi');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// TODO: implement allOwnAddresses
|
Future<List<String>> get allOwnAddresses =>
|
||||||
Future<List<String>> get allOwnAddresses => throw UnimplementedError();
|
_allOwnAddresses ??= _fetchAllOwnAddresses();
|
||||||
|
Future<List<String>>? _allOwnAddresses;
|
||||||
|
|
||||||
@override
|
Future<List<String>> _fetchAllOwnAddresses() async {
|
||||||
// TODO: implement availableBalance
|
List<String> addresses = [];
|
||||||
Future<Decimal> get availableBalance => throw UnimplementedError();
|
final ownAddress = _credentials.address;
|
||||||
|
addresses.add(ownAddress.toString());
|
||||||
@override
|
return addresses;
|
||||||
// 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
|
@override
|
||||||
// TODO: implement currentReceivingAddress
|
Future<Decimal> get availableBalance async {
|
||||||
Future<String> get currentReceivingAddress => throw UnimplementedError();
|
return await totalBalance;
|
||||||
|
|
||||||
@override
|
|
||||||
Future<int> estimateFeeFor(int satoshiAmount, int feeRate) {
|
|
||||||
// TODO: implement estimateFeeFor
|
|
||||||
throw UnimplementedError();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// TODO: implement fees
|
Future<Decimal> get balanceMinusMaxFee async =>
|
||||||
Future<FeeObject> get fees => throw UnimplementedError();
|
(await availableBalance) -
|
||||||
|
(Decimal.fromInt((await maxFee)) /
|
||||||
|
Decimal.fromInt(Constants.satsPerCoin(coin)))
|
||||||
|
.toDecimal();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Coin get coin => Coin.ethereum;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> confirmSend({required Map<String, dynamic> txData}) async {
|
||||||
|
final amount = txData['recipientAmt'];
|
||||||
|
final decimalAmount =
|
||||||
|
Format.satoshisToAmount(amount as int, coin: Coin.ethereum);
|
||||||
|
final bigIntAmount = amountToBigInt(
|
||||||
|
decimalAmount.toDouble(), int.parse(_tokenData["decimals"] as String));
|
||||||
|
|
||||||
|
final sentTx = await _client.sendTransaction(
|
||||||
|
_credentials,
|
||||||
|
transaction.Transaction.callContract(
|
||||||
|
contract: _contract,
|
||||||
|
function: _sendFunction,
|
||||||
|
parameters: [
|
||||||
|
EthereumAddress.fromHex(txData['address'] as String),
|
||||||
|
bigIntAmount
|
||||||
|
],
|
||||||
|
maxGas: _gasLimit,
|
||||||
|
gasPrice: EtherAmount.fromUnitAndValue(
|
||||||
|
EtherUnit.wei, txData['feeInWei'])));
|
||||||
|
|
||||||
|
return sentTx;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> get currentReceivingAddress async {
|
||||||
|
final _currentReceivingAddress = await _credentials.extractAddress();
|
||||||
|
final checkSumAddress =
|
||||||
|
checksumEthereumAddress(_currentReceivingAddress.toString());
|
||||||
|
return checkSumAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> estimateFeeFor(int satoshiAmount, int feeRate) async {
|
||||||
|
final fee = estimateFee(
|
||||||
|
feeRate, _gasLimit, int.parse(_tokenData["decimals"] as String));
|
||||||
|
return Format.decimalAmountToSatoshis(Decimal.parse(fee.toString()), coin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<FeeObject> get fees => _feeObject ??= _getFees();
|
||||||
|
Future<FeeObject>? _feeObject;
|
||||||
|
|
||||||
|
Future<FeeObject> _getFees() async {
|
||||||
|
return await getFees();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> initializeExisting() async {
|
Future<void> initializeExisting() async {
|
||||||
|
//TODO - GET abi FROM secure store
|
||||||
AbiRequestResponse abi = await fetchTokenAbi();
|
AbiRequestResponse abi = await fetchTokenAbi();
|
||||||
//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;
|
||||||
}
|
}
|
||||||
|
|
||||||
final mnemonic = await _walletMnemonic;
|
final mnemonic = await _walletMnemonic;
|
||||||
String mnemonicString = mnemonic.join(' ');
|
String mnemonicString = mnemonic.join(' ');
|
||||||
|
|
||||||
//Get private key for given mnemonic
|
//Get private key for given mnemonic
|
||||||
String privateKey = getPrivateKey(mnemonicString);
|
String privateKey = getPrivateKey(mnemonicString);
|
||||||
// TODO: implement initializeExisting
|
_credentials = EthPrivateKey.fromHex(privateKey);
|
||||||
throw UnimplementedError();
|
|
||||||
|
_contract = DeployedContract(
|
||||||
|
ContractAbi.fromJson(_tokenAbi, _tokenData["name"] as String),
|
||||||
|
_contractAddress);
|
||||||
|
_balanceFunction = _contract.function('balanceOf');
|
||||||
|
_sendFunction = _contract.function('transfer');
|
||||||
|
_client = await getEthClient();
|
||||||
|
print("${await totalBalance}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> initializeNew() async {
|
Future<void> initializeNew() async {
|
||||||
throw UnimplementedError();
|
//TODO - Save abi in secure store
|
||||||
|
AbiRequestResponse abi = await fetchTokenAbi();
|
||||||
|
//Fetch token ABI so we can call token functions
|
||||||
|
if (abi.message == "OK") {
|
||||||
|
_tokenAbi = abi.result;
|
||||||
}
|
}
|
||||||
|
final mnemonic = await _walletMnemonic;
|
||||||
|
String mnemonicString = mnemonic.join(' ');
|
||||||
|
|
||||||
@override
|
//Get private key for given mnemonic
|
||||||
// TODO: implement isConnected
|
String privateKey = getPrivateKey(mnemonicString);
|
||||||
bool get isConnected => throw UnimplementedError();
|
_credentials = EthPrivateKey.fromHex(privateKey);
|
||||||
|
|
||||||
|
_contract = DeployedContract(
|
||||||
|
ContractAbi.fromJson(_tokenAbi, _tokenData["name"] as String),
|
||||||
|
_contractAddress);
|
||||||
|
_balanceFunction = _contract.function('balanceOf');
|
||||||
|
_sendFunction = _contract.function('transfer');
|
||||||
|
_client = await getEthClient();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// TODO: implement isRefreshing
|
// TODO: implement isRefreshing
|
||||||
bool get isRefreshing => throw UnimplementedError();
|
bool get isRefreshing => throw UnimplementedError();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// TODO: implement maxFee
|
Future<int> get maxFee async {
|
||||||
Future<int> get maxFee => throw UnimplementedError();
|
final fee = (await fees).fast;
|
||||||
|
final feeEstimate = await estimateFeeFor(0, fee);
|
||||||
@override
|
return feeEstimate;
|
||||||
// TODO: implement pendingBalance
|
}
|
||||||
Future<Decimal> get pendingBalance => throw UnimplementedError();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map<String, dynamic>> prepareSend(
|
Future<Map<String, dynamic>> prepareSend(
|
||||||
{required String address,
|
{required String address,
|
||||||
required int satoshiAmount,
|
required int satoshiAmount,
|
||||||
Map<String, dynamic>? args}) {
|
Map<String, dynamic>? args}) async {
|
||||||
// TODO: implement prepareSend
|
final feeRateType = args?["feeRate"];
|
||||||
throw UnimplementedError();
|
int fee = 0;
|
||||||
|
final feeObject = await fees;
|
||||||
|
switch (feeRateType) {
|
||||||
|
case FeeRateType.fast:
|
||||||
|
fee = feeObject.fast;
|
||||||
|
break;
|
||||||
|
case FeeRateType.average:
|
||||||
|
fee = feeObject.medium;
|
||||||
|
break;
|
||||||
|
case FeeRateType.slow:
|
||||||
|
fee = feeObject.slow;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
final feeEstimate = await estimateFeeFor(satoshiAmount, fee);
|
||||||
|
|
||||||
|
bool isSendAll = false;
|
||||||
|
final balance =
|
||||||
|
Format.decimalAmountToSatoshis(await availableBalance, coin);
|
||||||
|
if (satoshiAmount == balance) {
|
||||||
|
isSendAll = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSendAll) {
|
||||||
|
//Subtract fee amount from send amount
|
||||||
|
satoshiAmount -= feeEstimate;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> txData = {
|
||||||
|
"fee": feeEstimate,
|
||||||
|
"feeInWei": fee,
|
||||||
|
"address": address,
|
||||||
|
"recipientAmt": satoshiAmount,
|
||||||
|
};
|
||||||
|
|
||||||
|
print("TX DATA TO BE SENT IS $txData");
|
||||||
|
return txData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -156,22 +264,69 @@ class EthereumToken extends TokenServiceAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// TODO: implement totalBalance
|
Future<Decimal> get totalBalance async {
|
||||||
Future<Decimal> get totalBalance => throw UnimplementedError();
|
final balanceRequest = await _client.call(
|
||||||
|
contract: _contract,
|
||||||
|
function: _balanceFunction,
|
||||||
|
params: [_credentials.address]);
|
||||||
|
|
||||||
|
String balance = balanceRequest.first.toString();
|
||||||
|
int tokenDecimals = int.parse(_tokenData["decimals"] as String);
|
||||||
|
final balanceInDecimal = (int.parse(balance) / (pow(10, tokenDecimals)));
|
||||||
|
return Decimal.parse(balanceInDecimal.toString());
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// TODO: implement transactionData
|
// TODO: implement transactionData
|
||||||
Future<TransactionData> get transactionData => throw UnimplementedError();
|
Future<TransactionData> get transactionData => throw UnimplementedError();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateSentCachedTxData(Map<String, dynamic> txData) {
|
Future<void> updateSentCachedTxData(Map<String, dynamic> txData) async {
|
||||||
// TODO: implement updateSentCachedTxData
|
Decimal currentPrice = Decimal.parse(0.0 as String);
|
||||||
throw UnimplementedError();
|
final locale = await Devicelocale.currentLocale;
|
||||||
|
final String worthNow = Format.localizedStringAsFixed(
|
||||||
|
value:
|
||||||
|
((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) /
|
||||||
|
Decimal.fromInt(Constants.satsPerCoin(coin)))
|
||||||
|
.toDecimal(scaleOnInfinitePrecision: 2),
|
||||||
|
decimalPlaces: 2,
|
||||||
|
locale: locale!);
|
||||||
|
|
||||||
|
final tx = models.Transaction(
|
||||||
|
txid: txData["txid"] as String,
|
||||||
|
confirmedStatus: false,
|
||||||
|
timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||||
|
txType: "Sent",
|
||||||
|
amount: txData["recipientAmt"] as int,
|
||||||
|
worthNow: worthNow,
|
||||||
|
worthAtBlockTimestamp: worthNow,
|
||||||
|
fees: txData["fee"] as int,
|
||||||
|
inputSize: 0,
|
||||||
|
outputSize: 0,
|
||||||
|
inputs: [],
|
||||||
|
outputs: [],
|
||||||
|
address: txData["address"] as String,
|
||||||
|
height: -1,
|
||||||
|
confirmations: 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (cachedTxData == null) {
|
||||||
|
final data = await _fetchTransactionData();
|
||||||
|
_transactionData = Future(() => data);
|
||||||
|
} else {
|
||||||
|
final transactions = cachedTxData!.getAllTransactions();
|
||||||
|
transactions[tx.txid] = tx;
|
||||||
|
cachedTxData = models.TransactionData.fromMap(transactions);
|
||||||
|
_transactionData = Future(() => cachedTxData!);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool validateAddress(String address) {
|
bool validateAddress(String address) {
|
||||||
// TODO: implement validateAddress
|
return isValidEthereumAddress(address);
|
||||||
throw UnimplementedError();
|
}
|
||||||
|
|
||||||
|
Future<Web3Client> getEthClient() async {
|
||||||
|
return Web3Client(rpcUrl, Client());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,130 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,17 +10,17 @@ abstract class TokenServiceAPI {
|
||||||
TokenServiceAPI();
|
TokenServiceAPI();
|
||||||
|
|
||||||
factory TokenServiceAPI.from(
|
factory TokenServiceAPI.from(
|
||||||
String contractAddress,
|
Map<dynamic, dynamic> tokenData,
|
||||||
String walletId,
|
Future<List<String>> walletMnemonic,
|
||||||
SecureStorageInterface secureStorageInterface,
|
SecureStorageInterface secureStorageInterface,
|
||||||
TransactionNotificationTracker tracker,
|
TransactionNotificationTracker tracker,
|
||||||
Prefs prefs,
|
Prefs prefs,
|
||||||
) {
|
) {
|
||||||
return EthereumToken(
|
return EthereumToken(
|
||||||
contractAddress: contractAddress,
|
tokenData: tokenData,
|
||||||
walletId: walletId,
|
walletMnemonic: walletMnemonic,
|
||||||
secureStore: secureStorageInterface,
|
// secureStore: secureStorageInterface,
|
||||||
tracker: tracker,
|
// tracker: tracker,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,6 @@ abstract class TokenServiceAPI {
|
||||||
// Future<String> get currentLegacyReceivingAddress;
|
// Future<String> get currentLegacyReceivingAddress;
|
||||||
|
|
||||||
Future<Decimal> get availableBalance;
|
Future<Decimal> get availableBalance;
|
||||||
Future<Decimal> get pendingBalance;
|
|
||||||
Future<Decimal> get totalBalance;
|
Future<Decimal> get totalBalance;
|
||||||
Future<Decimal> get balanceMinusMaxFee;
|
Future<Decimal> get balanceMinusMaxFee;
|
||||||
|
|
||||||
|
@ -62,10 +61,6 @@ abstract class TokenServiceAPI {
|
||||||
Future<void> initializeNew();
|
Future<void> initializeNew();
|
||||||
Future<void> initializeExisting();
|
Future<void> initializeExisting();
|
||||||
|
|
||||||
// void Function(bool isActive)? onIsActiveWalletChanged;
|
|
||||||
|
|
||||||
bool get isConnected;
|
|
||||||
|
|
||||||
Future<int> estimateFeeFor(int satoshiAmount, int feeRate);
|
Future<int> estimateFeeFor(int satoshiAmount, int feeRate);
|
||||||
|
|
||||||
// used for electrumx coins
|
// used for electrumx coins
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:ethereum_addresses/ethereum_addresses.dart';
|
import 'package:ethereum_addresses/ethereum_addresses.dart';
|
||||||
|
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||||
import 'flutter_secure_storage_interface.dart';
|
import 'flutter_secure_storage_interface.dart';
|
||||||
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;
|
||||||
|
@ -27,13 +29,31 @@ class AccountModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const _blockExplorer = "https://api.etherscan.io/api?";
|
class GasTracker {
|
||||||
late SecureStorageInterface _secureStore;
|
final int code;
|
||||||
|
final Map<String, dynamic> data;
|
||||||
|
|
||||||
|
const GasTracker({
|
||||||
|
required this.code,
|
||||||
|
required this.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory GasTracker.fromJson(Map<String, dynamic> json) {
|
||||||
|
return GasTracker(
|
||||||
|
code: json['code'] as int,
|
||||||
|
data: json['data'] as Map<String, dynamic>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const blockExplorer = "https://blockscout.com/eth/mainnet/api";
|
||||||
|
const blockExplorer = "https://api.etherscan.io/api";
|
||||||
const _hdPath = "m/44'/60'/0'/0";
|
const _hdPath = "m/44'/60'/0'/0";
|
||||||
|
const _gasTrackerUrl = "https://beaconcha.in/api/v1/execution/gasnow";
|
||||||
|
|
||||||
Future<AccountModule> fetchAccountModule(String action, String address) async {
|
Future<AccountModule> fetchAccountModule(String action, String address) async {
|
||||||
final response = await get(Uri.parse(
|
final response = await get(Uri.parse(
|
||||||
"${_blockExplorer}module=account&action=$action&address=$address&apikey=EG6J7RJIQVSTP2BS59D3TY2G55YHS5F2HP"));
|
"${blockExplorer}module=account&action=$action&address=$address&apikey=EG6J7RJIQVSTP2BS59D3TY2G55YHS5F2HP"));
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return AccountModule.fromJson(
|
return AccountModule.fromJson(
|
||||||
json.decode(response.body) as Map<String, dynamic>);
|
json.decode(response.body) as Map<String, dynamic>);
|
||||||
|
@ -48,7 +68,6 @@ Future<List<dynamic>> getWalletTokens(String address) async {
|
||||||
var tokenMap = {};
|
var tokenMap = {};
|
||||||
if (tokens.message == "OK") {
|
if (tokens.message == "OK") {
|
||||||
final allTxs = tokens.result;
|
final allTxs = tokens.result;
|
||||||
print("RESULT IS $allTxs");
|
|
||||||
allTxs.forEach((element) {
|
allTxs.forEach((element) {
|
||||||
String key = element["tokenSymbol"] as String;
|
String key = element["tokenSymbol"] as String;
|
||||||
tokenMap[key] = {};
|
tokenMap[key] = {};
|
||||||
|
@ -88,3 +107,41 @@ String getPrivateKey(String mnemonic) {
|
||||||
|
|
||||||
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 feesMap = fees.data;
|
||||||
|
return FeeObject(
|
||||||
|
numberOfBlocksFast: 1,
|
||||||
|
numberOfBlocksAverage: 3,
|
||||||
|
numberOfBlocksSlow: 3,
|
||||||
|
fast: feesMap['fast'] as int,
|
||||||
|
medium: feesMap['standard'] as int,
|
||||||
|
slow: feesMap['slow'] as int);
|
||||||
|
}
|
||||||
|
|
||||||
|
double estimateFee(int feeRate, int gasLimit, int decimals) {
|
||||||
|
final gweiAmount = feeRate / (pow(10, 9));
|
||||||
|
final fee = gasLimit * gweiAmount;
|
||||||
|
|
||||||
|
//Convert gwei to ETH
|
||||||
|
final feeInWei = fee * (pow(10, 9));
|
||||||
|
final ethAmount = feeInWei / (pow(10, decimals));
|
||||||
|
return ethAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
BigInt amountToBigInt(num amount, int decimal) {
|
||||||
|
final amountToSendinDecimal = amount * (pow(10, decimal));
|
||||||
|
return BigInt.from(amountToSendinDecimal);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue