diff --git a/mobile-app/assets/v2/axe.svg b/mobile-app/assets/v2/axe.svg new file mode 100644 index 00000000..8c267333 --- /dev/null +++ b/mobile-app/assets/v2/axe.svg @@ -0,0 +1,3 @@ + + + diff --git a/mobile-app/lib/features/components/get_started.dart b/mobile-app/lib/features/components/get_started.dart index fef34d44..58e6f1ae 100644 --- a/mobile-app/lib/features/components/get_started.dart +++ b/mobile-app/lib/features/components/get_started.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; +import 'package:resonance_network_wallet/shared/utils/open_external_url.dart'; import 'package:resonance_network_wallet/utils/url_utils.dart'; -import 'package:url_launcher/url_launcher.dart'; class GetStarted extends StatelessWidget { const GetStarted({super.key}); @@ -33,26 +33,17 @@ class GetStarted extends StatelessWidget { ), const SizedBox(height: 25), GestureDetector( - onTap: () { - final Uri url = Uri.parse(AppConstants.tutorialsAndGuidesUrl); - launchUrl(url); - }, + onTap: () => openUrl(AppConstants.tutorialsAndGuidesUrl), child: Text('Tutorials & Guides →', style: context.themeText.smallParagraph), ), const SizedBox(height: 25), GestureDetector( - onTap: () { - final Uri url = Uri.parse(AppConstants.communityUrl); - launchUrl(url); - }, + onTap: () => openUrl(AppConstants.communityUrl), child: Text('Community →', style: context.themeText.smallParagraph), ), const SizedBox(height: 25), GestureDetector( - onTap: () { - final Uri url = Uri.parse(AppConstants.techSupportUrl); - launchUrl(url); - }, + onTap: () => openUrl(AppConstants.techSupportUrl), child: Text('Tech Support →', style: context.themeText.smallParagraph), ), ], diff --git a/mobile-app/lib/features/components/link_text.dart b/mobile-app/lib/features/components/link_text.dart index e9331513..567f8537 100644 --- a/mobile-app/lib/features/components/link_text.dart +++ b/mobile-app/lib/features/components/link_text.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; -import 'package:url_launcher/url_launcher.dart'; +import 'package:resonance_network_wallet/shared/utils/open_external_url.dart'; class LinkText extends StatelessWidget { final String label; @@ -19,7 +19,7 @@ class LinkText extends StatelessWidget { child: Text(label, style: effectiveTextStyle), onTap: () { final Uri uri = Uri.parse(url); - launchUrl(uri); + openUrl(uri.toString()); }, ); } diff --git a/mobile-app/lib/features/components/reversible_transaction_action_sheet.dart b/mobile-app/lib/features/components/reversible_transaction_action_sheet.dart index ef04518f..822e4028 100644 --- a/mobile-app/lib/features/components/reversible_transaction_action_sheet.dart +++ b/mobile-app/lib/features/components/reversible_transaction_action_sheet.dart @@ -16,8 +16,8 @@ import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/services/reversible_transfer_monitoring_service.dart'; import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart'; import 'package:resonance_network_wallet/shared/extensions/toaster_extensions.dart'; +import 'package:resonance_network_wallet/shared/utils/open_external_url.dart'; import 'package:resonance_network_wallet/shared/utils/tx_filter_family_provider.dart'; -import 'package:url_launcher/url_launcher.dart'; enum ReversibleTransactionMode { reversible, guardianIntercept } @@ -310,7 +310,7 @@ class _ReversibleTransactionActionSheetState extends ConsumerState((ref) => MiningRewardsService()); @@ -8,11 +9,28 @@ final miningRewardsServiceProvider = Provider((ref) => Min final miningRewardsProvider = FutureProvider((ref) async { final service = ref.watch(miningRewardsServiceProvider); final accounts = ref.watch(accountsProvider).value; + if (accounts == null || accounts.isEmpty) { - return const MiningRewardsData(resonanceBlocks: 0, schrodingerBlocks: 0, diracBlocks: 0, planckBlocks: 0); + return MiningRewardsData( + resonanceBlocks: 0, + schrodingerBlocks: 0, + diracBlocks: 0, + planckBlocks: 0, + planckRewards: BigInt.zero, + redeemedRewards: BigInt.zero, + redeemableRewards: BigInt.zero, + ); + } + + final mnemonic = await ref.watch(settingsServiceProvider).getMnemonic(0); + if (mnemonic == null) { + throw Exception('Mnemonic not found!'); } + final keyPair = ref.watch(hdWalletServiceProvider).deriveWormholeKeyPair(mnemonic: mnemonic); + final oldMiningAccountId = await TaskmasterService().getOldMiningAccountId(); final accountsList = accounts.map((a) => a.accountId).toList(); accountsList.add(oldMiningAccountId); - return service.getMiningRewards(accountsList); + + return service.getMiningRewards(ref, keyPair, accountsList); }); diff --git a/mobile-app/lib/providers/wallet_providers.dart b/mobile-app/lib/providers/wallet_providers.dart index 7ee0fd05..f04e57b9 100644 --- a/mobile-app/lib/providers/wallet_providers.dart +++ b/mobile-app/lib/providers/wallet_providers.dart @@ -53,6 +53,14 @@ final highSecurityServiceProvider = Provider((ref) { return HighSecurityService(); }); +final hdWalletServiceProvider = Provider((ref) { + return HdWalletService(); +}); + +final wormholeUtxoServiceProvider = Provider((ref) { + return WormholeUtxoService(); +}); + final isHighSecurityProvider = FutureProvider.family((ref, account) async { final highSecurityService = ref.watch(highSecurityServiceProvider); return await highSecurityService.isHighSecurity(account); diff --git a/mobile-app/lib/services/mining_rewards_service.dart b/mobile-app/lib/services/mining_rewards_service.dart index 6bc6b4f1..074f17bf 100644 --- a/mobile-app/lib/services/mining_rewards_service.dart +++ b/mobile-app/lib/services/mining_rewards_service.dart @@ -1,7 +1,9 @@ import 'dart:convert'; import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/utils/env_utils.dart'; class MiningRewardsData { @@ -9,12 +11,18 @@ class MiningRewardsData { final int schrodingerBlocks; final int diracBlocks; final int planckBlocks; + final BigInt planckRewards; + final BigInt redeemedRewards; + final BigInt redeemableRewards; const MiningRewardsData({ required this.resonanceBlocks, required this.schrodingerBlocks, required this.diracBlocks, required this.planckBlocks, + required this.planckRewards, + required this.redeemedRewards, + required this.redeemableRewards, }); int get totalBlocks => resonanceBlocks + schrodingerBlocks + diracBlocks + planckBlocks; @@ -33,8 +41,9 @@ class MiningRewardsService { _cachedAccountIds = null; } - Future getMiningRewards(List currentAccountIds) async { + Future getMiningRewards(Ref ref, WormholeKeyPair keyPair, List currentAccountIds) async { print('[MiningRewards] Current account IDs: $currentAccountIds'); + final wormholeUtxoService = ref.read(wormholeUtxoServiceProvider); final miners = >{}; for (final entry in _assets.entries) { @@ -49,22 +58,23 @@ class MiningRewardsService { final resonance = _countBlocks('resonance', miners['resonance']!, allAccountIds); final schrodinger = _countBlocks('schrodinger', miners['schrodinger']!, allAccountIds); final dirac = _countBlocks('dirac', miners['dirac']!, allAccountIds); - final planck = await _fetchPlanckBlocks(allAccountIds); + final (planckStats, redeemableRewards) = await ( + TaskmasterService().getMinerStats(), + wormholeUtxoService.getUnspentBalance(wormholeAddress: keyPair.address, secretHex: keyPair.secretHex), + ).wait; + final redeemedRewards = planckStats.totalRewards - redeemableRewards; - print('[MiningRewards] Resonance: $resonance, Schrödinger: $schrodinger, Dirac: $dirac, Planck: $planck'); return MiningRewardsData( resonanceBlocks: resonance, schrodingerBlocks: schrodinger, diracBlocks: dirac, - planckBlocks: planck, + planckBlocks: planckStats.totalMinedBlocks, + planckRewards: planckStats.totalRewards, + redeemedRewards: redeemedRewards, + redeemableRewards: redeemableRewards, ); } - Future _fetchPlanckBlocks(Set accountIds) async { - final minerStats = await TaskmasterService().getMinerStats(); - return minerStats.totalMinedBlocks; - } - List<_MinerEntry> _parseMiners(String jsonStr) { final decoded = jsonDecode(jsonStr); final stats = decoded['data']['minerStats'] as List; diff --git a/mobile-app/lib/shared/utils/open_external_url.dart b/mobile-app/lib/shared/utils/open_external_url.dart new file mode 100644 index 00000000..6140c55a --- /dev/null +++ b/mobile-app/lib/shared/utils/open_external_url.dart @@ -0,0 +1,13 @@ +import 'package:url_launcher/url_launcher.dart'; + +Future openUrl(String urlString, {LaunchMode mode = LaunchMode.platformDefault}) async { + final uri = Uri.parse(urlString); + try { + final launched = await launchUrl(uri, mode: mode); + if (!launched) { + print('launchUrl returned false: $urlString'); + } + } catch (e, st) { + print('launchUrl failed: $urlString error=$e\n$st'); + } +} diff --git a/mobile-app/lib/v2/screens/accounts/account_ready_screen.dart b/mobile-app/lib/v2/screens/accounts/account_ready_screen.dart index 2c173c72..fc4b1959 100644 --- a/mobile-app/lib/v2/screens/accounts/account_ready_screen.dart +++ b/mobile-app/lib/v2/screens/accounts/account_ready_screen.dart @@ -25,7 +25,6 @@ class AccountReadyScreen extends StatelessWidget { final String checksumPhrase; final String accountId; - static const _galleryLargeTitle = Color(0xFFEBEBEB); static const _successRingSize = 78.0; static const _checkIconSize = 32.0; static const _borderWidth = 2.0; @@ -92,7 +91,7 @@ class AccountReadyScreen extends StatelessWidget { Text( headline, textAlign: TextAlign.center, - style: text.paragraph?.copyWith(fontSize: 32, color: _galleryLargeTitle, height: 1.0), + style: text.paragraph?.copyWith(fontSize: 32, color: colors.textLightGray, height: 1.0), ), ], ), diff --git a/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart b/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart index fbc0a4c8..ef5ef693 100644 --- a/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart +++ b/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart @@ -5,11 +5,11 @@ import 'package:resonance_network_wallet/features/components/dotted_border.dart' import 'package:resonance_network_wallet/providers/currency_display_provider.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/shared/extensions/transaction_event_extension.dart'; +import 'package:resonance_network_wallet/shared/utils/open_external_url.dart'; import 'package:resonance_network_wallet/v2/components/amount_display_with_conversion.dart'; import 'package:resonance_network_wallet/v2/components/bottom_sheet_container.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; -import 'package:url_launcher/url_launcher.dart'; void showTransactionDetailSheet(BuildContext context, TransactionEvent tx, String activeAccountId) { BottomSheetContainer.show( @@ -213,6 +213,6 @@ class _ExplorerLink extends StatelessWidget { path = '$transactionType/${tx.blockHash}'; } - if (path != null) launchUrl(Uri.parse('${AppConstants.explorerEndpoint}/$path')); + if (path != null) openUrl('${AppConstants.explorerEndpoint}/$path'); } } diff --git a/mobile-app/lib/v2/screens/home/activity_section.dart b/mobile-app/lib/v2/screens/home/activity_section.dart index 48033b6f..275479c0 100644 --- a/mobile-app/lib/v2/screens/home/activity_section.dart +++ b/mobile-app/lib/v2/screens/home/activity_section.dart @@ -7,14 +7,11 @@ import 'package:resonance_network_wallet/models/combined_transactions_list.dart' import 'package:resonance_network_wallet/providers/active_account_transactions_provider.dart'; import 'package:resonance_network_wallet/providers/currency_display_provider.dart'; import 'package:resonance_network_wallet/services/transaction_service.dart'; -import 'package:resonance_network_wallet/utils/url_utils.dart'; import 'package:resonance_network_wallet/v2/screens/activity/activity_screen.dart'; import 'package:resonance_network_wallet/v2/screens/activity/transaction_detail_sheet.dart'; import 'package:resonance_network_wallet/v2/screens/activity/tx_item.dart'; -import 'package:resonance_network_wallet/v2/screens/settings/testnet_rewards_screen.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; -import 'package:url_launcher/url_launcher.dart'; class ActivitySection extends ConsumerStatefulWidget { final AsyncValue txAsync; @@ -28,8 +25,6 @@ class ActivitySection extends ConsumerStatefulWidget { } class _ActivitySectionState extends ConsumerState { - bool _getStartedExpanded = true; - @override Widget build(BuildContext context) { final formatTxAmount = ref.watch(txAmountDisplayProvider); @@ -49,13 +44,7 @@ class _ActivitySectionState extends ConsumerState { if (all.isEmpty) { return Column( - children: [ - const SizedBox(height: 40), - _header(colors, text, context), - _emptyState(text, colors), - const SizedBox(height: 40), - _getStartedSection(text, colors), - ], + children: [const SizedBox(height: 40), _header(colors, text, context), _emptyState(text, colors)], ); } @@ -130,90 +119,21 @@ class _ActivitySectionState extends ConsumerState { padding: const EdgeInsets.symmetric(vertical: 24), child: Column( children: [ - Text('No Transactions Yet', style: text.smallParagraph?.copyWith(color: colors.textPrimary)), - const SizedBox(height: 8), - Text('Your activity will appear here', style: text.detail?.copyWith(color: colors.textSecondary)), - ], - ), - ); - } - - Widget _getStartedSection(AppTextTheme text, AppColorsV2 colors) { - const links = [ - ('Get Testnet Tokens', AppConstants.faucetUrl), - ('Community', AppConstants.communityUrl), - // ('Tech Support', AppConstants.techSupportUrl), - ]; - - return Column( - children: [ - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () => setState(() => _getStartedExpanded = !_getStartedExpanded), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('Get Started', style: text.smallTitle), - Icon( - _getStartedExpanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down, - color: colors.textSecondary, - size: 16, - ), - ], + Text( + 'No Transactions Yet', + style: text.mediumTitle?.copyWith(color: colors.textMuted, fontWeight: FontWeight.w400), ), - ), - AnimatedCrossFade( - firstChild: Padding( - padding: const EdgeInsets.only(top: 24), - child: Container( - width: double.infinity, - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(14), - ), - child: Column( - children: [ - for (var i = 0; i < links.length; i++) ...[ - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () => links[i].$2 == AppConstants.faucetUrl - ? launchXPost(links[i].$2) - : launchUrl(Uri.parse(links[i].$2)), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(links[i].$1, style: text.smallParagraph?.copyWith(color: colors.textPrimary)), - Icon(Icons.arrow_outward, color: colors.textPrimary, size: 20), - ], - ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 12), - child: Divider(color: colors.separator, height: 0), - ), - ], - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () => - Navigator.push(context, MaterialPageRoute(builder: (_) => const TestnetRewardsScreen())), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('Testnet Rewards', style: text.smallParagraph?.copyWith(color: colors.textPrimary)), - Icon(Icons.chevron_right, color: colors.textPrimary, size: 20), - ], - ), - ), - ], - ), + const SizedBox(height: 8), + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 240), + child: Text( + 'Your activity will appear here once you send or receive QUAN.', + textAlign: TextAlign.center, + style: text.smallParagraph?.copyWith(color: colors.txItemIconDefault), ), ), - secondChild: const SizedBox.shrink(), - crossFadeState: _getStartedExpanded ? CrossFadeState.showFirst : CrossFadeState.showSecond, - duration: const Duration(milliseconds: 200), - ), - ], + ], + ), ); } diff --git a/mobile-app/lib/v2/screens/home/home_screen.dart b/mobile-app/lib/v2/screens/home/home_screen.dart index 672ba9a0..0df0ed2a 100644 --- a/mobile-app/lib/v2/screens/home/home_screen.dart +++ b/mobile-app/lib/v2/screens/home/home_screen.dart @@ -6,10 +6,12 @@ import 'package:resonance_network_wallet/features/components/dotted_border.dart' import 'package:resonance_network_wallet/features/components/skeleton.dart'; import 'package:resonance_network_wallet/features/components/shared_address_action_sheet.dart'; import 'package:resonance_network_wallet/providers/remote_config_provider.dart'; +import 'package:resonance_network_wallet/utils/url_utils.dart'; import 'package:resonance_network_wallet/v2/components/amount_display_with_conversion.dart'; import 'package:resonance_network_wallet/v2/components/loader.dart'; import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; import 'package:resonance_network_wallet/v2/components/quantus_icon_button.dart'; +import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/open_accounts_management_button.dart'; import 'package:resonance_network_wallet/v2/screens/receive/receive_screen.dart'; import 'package:resonance_network_wallet/v2/screens/send/input_amount_screen.dart'; @@ -87,6 +89,7 @@ class _HomeScreenState extends ConsumerState { }); final isPosMode = ref.watch(posModeProvider); + final balanceAsync = ref.watch(balanceProvider); final accountAsync = ref.watch(activeAccountProvider); final txAsync = ref.watch(activeAccountTransactionsProvider(TransactionFilter.all)); final colors = context.colors; @@ -110,6 +113,18 @@ class _HomeScreenState extends ConsumerState { ActivitySection(txAsync: txAsync, activeAccount: active.account, onRetry: _refresh), SizedBox(height: isPosMode ? 120 : 58), ], + bottomContent: balanceAsync + .whenData( + (balance) => balance == BigInt.zero + ? ScaffoldBaseBottomContent( + child: QuantusButton.simple( + label: 'Get Testnet Tokens ↗', + onTap: () => launchXPost(AppConstants.faucetUrl), + ), + ) + : null, + ) + .value, ); }, ); diff --git a/mobile-app/lib/v2/screens/settings/about_quantus_screen.dart b/mobile-app/lib/v2/screens/settings/about_quantus_screen.dart index c0534e64..e9c369b0 100644 --- a/mobile-app/lib/v2/screens/settings/about_quantus_screen.dart +++ b/mobile-app/lib/v2/screens/settings/about_quantus_screen.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/generated/version.g.dart'; +import 'package:resonance_network_wallet/shared/utils/open_external_url.dart'; import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_divider.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_tappable_row.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; -import 'package:url_launcher/url_launcher.dart'; class AboutQuantusScreenV2 extends StatelessWidget { const AboutQuantusScreenV2({super.key}); @@ -49,7 +49,7 @@ class AboutQuantusScreenV2 extends StatelessWidget { SettingsTappableRow( title: entry.value.title, subtitle: entry.value.subtitle, - onTap: () => launchUrl(_uriForAboutLink(entry.value)), + onTap: () => openUrl(_uriForAboutLink(entry.value).toString()), trailing: SettingsTappableRowUtils.externalLink(colors), ), if (entry.key < _externalLinks.length - 1) const SettingsDivider(), diff --git a/mobile-app/lib/v2/screens/settings/help_and_support_screen.dart b/mobile-app/lib/v2/screens/settings/help_and_support_screen.dart index 3c75102c..2dc2c32b 100644 --- a/mobile-app/lib/v2/screens/settings/help_and_support_screen.dart +++ b/mobile-app/lib/v2/screens/settings/help_and_support_screen.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/shared/utils/open_external_url.dart'; import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_divider.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_tappable_row.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:url_launcher/url_launcher.dart'; class HelpAndSupportScreenV2 extends StatelessWidget { const HelpAndSupportScreenV2({super.key}); @@ -22,13 +22,13 @@ class HelpAndSupportScreenV2 extends StatelessWidget { title: 'Email Support', subtitle: AppConstants.emailSupport, colors: colors, - onTap: () => launchUrl(Uri.parse('mailto:${AppConstants.emailSupport}')), + onTap: () => openUrl('mailto:${AppConstants.emailSupport}'), ), _contactBlock( title: 'Telegram', subtitle: AppConstants.telegramHandle, colors: colors, - onTap: () => launchUrl(Uri.parse(AppConstants.communityUrl)), + onTap: () => openUrl(AppConstants.communityUrl), showBottomDivider: false, ), ], diff --git a/mobile-app/lib/v2/screens/settings/mining_rewards_screen.dart b/mobile-app/lib/v2/screens/settings/mining_rewards_screen.dart new file mode 100644 index 00000000..a00f8645 --- /dev/null +++ b/mobile-app/lib/v2/screens/settings/mining_rewards_screen.dart @@ -0,0 +1,420 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/features/components/skeleton.dart'; +import 'package:resonance_network_wallet/providers/mining_rewards_provider.dart'; +import 'package:resonance_network_wallet/providers/wallet_providers.dart'; +import 'package:resonance_network_wallet/services/mining_rewards_service.dart'; +import 'package:resonance_network_wallet/shared/utils/open_external_url.dart'; +import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; +import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; +import 'package:resonance_network_wallet/v2/components/split_card.dart'; +import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +class MiningRewardsScreen extends ConsumerWidget { + const MiningRewardsScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final miningAsync = ref.watch(miningRewardsProvider); + final colors = context.colors; + final text = context.themeText; + + return ScaffoldBase.refreshable( + appBar: const V2AppBar(title: 'Mining Rewards'), + onRefresh: () async => ref.invalidate(miningRewardsProvider), + slivers: [ + miningAsync.when( + data: (data) => data.totalBlocks > 0 ? _WithRewards(data: data) : const _NoRewards(), + loading: () => const _NoRewards(isLoading: true), + error: (err, _) => + _ErrorState(colors: colors, text: text, onRetry: () => ref.invalidate(miningRewardsProvider)), + ), + ], + bottomContent: miningAsync.when( + data: (data) => data.totalBlocks > 0 + ? const ScaffoldBaseBottomContent(child: QuantusButton.simple(label: 'Redeem', onTap: null)) + : null, + loading: () => null, + error: (err, _) => null, + ), + ); + } +} + +class _WithRewards extends ConsumerWidget { + final MiningRewardsData data; + + const _WithRewards({required this.data}); + + static const _resonanceSince = 'Jul 2025'; + static const _schrodingerSince = 'Oct 2025'; + static const _diracSince = 'Nov 2025'; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final numberFmt = ref.watch(numberFormattingServiceProvider); + final quanEarned = numberFmt.formatBalance(data.planckRewards, addSymbol: true); + final redeemedRewards = numberFmt.formatBalance(data.redeemedRewards, addSymbol: true); + final redeemableRewards = numberFmt.formatBalance(data.redeemableRewards, addSymbol: true); + + final colors = context.colors; + final text = context.themeText; + + final testnets = [ + _TestnetEntry('Dirac', _diracSince, data.diracBlocks), + _TestnetEntry('Schrödinger', _schrodingerSince, data.schrodingerBlocks), + _TestnetEntry('Resonance', _resonanceSince, data.resonanceBlocks), + ]; + + final miningSummaryPairRows = [ + _StatPairRow( + left: _MiningStatCell(label: 'TESTNET BLOCKS', value: '${data.totalBlocks}', valueColor: colors.textLightGray), + right: _MiningStatCell(label: 'TESTNET REWARDS', value: quanEarned, valueColor: colors.accentOrange), + ), + _StatPairRow( + left: _MiningStatCell(label: 'REDEEMED', value: redeemedRewards, valueColor: colors.textLightGray), + right: _MiningStatCell(label: 'REDEEMABLE', value: redeemableRewards, valueColor: colors.success), + ), + ]; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SplitCard( + topChild: _CardTopSection( + totalBlocks: data.totalBlocks, + totalBlocksColor: colors.textLightGray, + statusLabel: 'Mining', + statusColor: colors.success, + ), + bottomChild: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (var i = 0; i < miningSummaryPairRows.length; i++) ...[ + if (i > 0) const SizedBox(height: 24), + miningSummaryPairRows[i], + ], + ], + ), + ), + const SizedBox(height: 32), + for (var i = 0; i < testnets.length; i++) ...[ + _TestnetRow(entry: testnets[i]), + if (i < testnets.length - 1) Divider(color: colors.toasterBackground, height: 1, thickness: 1), + ], + const SizedBox(height: 48), + Center( + child: _OrangeLinkButton( + label: 'View Telemetry ↗', + text: text, + onTap: () => openUrl(AppConstants.telemetryUrl), + ), + ), + const SizedBox(height: 24), + ], + ); + } +} + +class _NoRewards extends StatelessWidget { + final bool isLoading; + + const _NoRewards({this.isLoading = false}); + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SplitCard( + topChild: _CardTopSection( + totalBlocks: 0, + totalBlocksColor: colors.textTertiary, + statusLabel: 'Pending', + statusColor: colors.textTertiary, + isLoading: isLoading, + ), + bottomChild: _StatColumn( + label: 'QUAN EARNED', + value: '0.00', + valueColor: colors.textTertiary, + isLoading: isLoading, + ), + ), + if (isLoading) ...[ + const SizedBox(height: 32), + for (var i = 0; i < 4; i++) ...[ + const Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [Skeleton(width: 100), SizedBox(height: 8), Skeleton(width: 72)], + ), + ), + Spacer(), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [Skeleton(width: 72), SizedBox(height: 8), Skeleton(width: 56)], + ), + ), + ], + ), + const SizedBox(height: 16), + if (i < 3) Divider(color: colors.toasterBackground, height: 1, thickness: 1), + const SizedBox(height: 24), + ], + ] else ...[ + const SizedBox(height: 64), + Text( + 'No mining data yet', + style: text.mediumTitle?.copyWith(fontWeight: FontWeight.w400, color: colors.textMuted), + ), + const SizedBox(height: 8), + Text( + 'Set up a Quantus mining node to start earning rewards.', + textAlign: TextAlign.center, + style: text.smallParagraph?.copyWith(color: colors.txItemIconDefault, height: 1.35), + ), + const SizedBox(height: 64), + _OrangeLinkButton( + label: 'Mining Setup Guide ↗', + text: text, + onTap: () => openUrl(AppConstants.miningSetupGuideUrl), + ), + const SizedBox(height: 24), + ], + ], + ); + } +} + +class _CardTopSection extends StatelessWidget { + final int totalBlocks; + final Color totalBlocksColor; + final String statusLabel; + final Color statusColor; + final bool isLoading; + + const _CardTopSection({ + required this.totalBlocks, + required this.totalBlocksColor, + required this.statusLabel, + required this.statusColor, + this.isLoading = false, + }); + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('BLOCKS MINED', style: text.receiveLabel?.copyWith(color: colors.textLabel)), + Row( + children: [ + Container( + width: 4, + height: 4, + decoration: BoxDecoration(color: statusColor, shape: BoxShape.circle), + ), + const SizedBox(width: 4), + if (isLoading) + const Skeleton(width: 100, height: 24) + else + Text(statusLabel, style: text.smallParagraph?.copyWith(color: statusColor)), + ], + ), + ], + ), + const SizedBox(height: 8), + if (isLoading) + const Skeleton(width: 100, height: 24) + else + Text('$totalBlocks', style: text.totalMinedBlocks?.copyWith(color: totalBlocksColor)), + const SizedBox(height: 4), + Text('blocks across all testnets', style: text.detail?.copyWith(color: colors.textMuted)), + ], + ); + } +} + +class _MiningStatCell { + const _MiningStatCell({required this.label, required this.value, required this.valueColor}); + + final String label; + final String value; + final Color valueColor; +} + +class _StatPairRow extends StatelessWidget { + const _StatPairRow({required this.left, required this.right}); + + final _MiningStatCell left; + final _MiningStatCell right; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: _StatColumn(label: left.label, value: left.value, valueColor: left.valueColor), + ), + const SizedBox(width: 12), + Expanded( + child: _StatColumn(label: right.label, value: right.value, valueColor: right.valueColor), + ), + ], + ); + } +} + +class _StatColumn extends StatelessWidget { + final String label; + final String value; + final Color valueColor; + final bool isLoading; + + const _StatColumn({required this.label, required this.value, required this.valueColor, this.isLoading = false}); + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, style: text.receiveLabel?.copyWith(color: colors.textLabel)), + const SizedBox(height: 8), + if (isLoading) + const Skeleton(width: 100, height: 24) + else + Text(value, style: text.sendSectionLabel?.copyWith(color: valueColor)), + ], + ); + } +} + +class _TestnetRow extends StatelessWidget { + final _TestnetEntry entry; + + const _TestnetRow({required this.entry}); + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + final countColor = colors.textLightGray; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(entry.name, style: text.smallTitle?.copyWith(fontWeight: FontWeight.w400)), + const SizedBox(height: 8), + Text(entry.subtitle, style: text.smallParagraph?.copyWith(color: colors.textTertiary)), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + '${entry.blocks}', + style: text.smallTitle?.copyWith( + fontFamily: AppTextTheme.fontFamilySecondary, + fontWeight: FontWeight.w400, + color: countColor, + ), + ), + const SizedBox(height: 4), + Text('blocks', style: text.detail?.copyWith(color: colors.textMuted)), + ], + ), + ], + ), + ); + } +} + +class _OrangeLinkButton extends StatelessWidget { + final String label; + final AppTextTheme text; + final VoidCallback onTap; + + const _OrangeLinkButton({required this.label, required this.text, required this.onTap}); + + @override + Widget build(BuildContext context) { + final colors = context.colors; + + return GestureDetector( + onTap: onTap, + child: Container( + decoration: BoxDecoration( + border: Border(bottom: BorderSide(color: colors.accentOrange, width: 1)), + ), + padding: const EdgeInsets.only(bottom: 3), + child: Text(label, style: text.smallParagraph?.copyWith(color: colors.accentOrange)), + ), + ); + } +} + +class _ErrorState extends StatelessWidget { + final AppColorsV2 colors; + final AppTextTheme text; + final VoidCallback onRetry; + + const _ErrorState({required this.colors, required this.text, required this.onRetry}); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 200, + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('Failed to load mining rewards', style: text.paragraph?.copyWith(color: colors.textPrimary)), + const SizedBox(height: 8), + Text('Please check your connection', style: text.detail?.copyWith(color: colors.textTertiary)), + const SizedBox(height: 20), + GestureDetector( + onTap: onRetry, + child: Text( + 'Try Again', + style: text.smallParagraph?.copyWith(color: colors.accentGreen, fontWeight: FontWeight.w600), + ), + ), + ], + ), + ), + ); + } +} + +class _TestnetEntry { + final String name; + final String subtitle; + final int blocks; + + const _TestnetEntry(this.name, this.subtitle, this.blocks); +} diff --git a/mobile-app/lib/v2/screens/settings/settings_screen.dart b/mobile-app/lib/v2/screens/settings/settings_screen.dart index 905f929e..718bf4ab 100644 --- a/mobile-app/lib/v2/screens/settings/settings_screen.dart +++ b/mobile-app/lib/v2/screens/settings/settings_screen.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:resonance_network_wallet/generated/version.g.dart'; +import 'package:resonance_network_wallet/providers/mining_rewards_provider.dart'; import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/settings/about_quantus_screen.dart'; @@ -9,14 +11,24 @@ import 'package:resonance_network_wallet/v2/screens/settings/help_and_support_sc import 'package:resonance_network_wallet/v2/screens/settings/preferences_settings_screen.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_divider.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_tappable_row.dart'; +import 'package:resonance_network_wallet/v2/screens/settings/mining_rewards_screen.dart'; import 'package:resonance_network_wallet/v2/screens/settings/wallet_settings_screen.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -class SettingsScreenV2 extends StatelessWidget { +const _miningRewardsTitle = 'Mining Rewards'; + +class SettingsScreenV2 extends ConsumerStatefulWidget { const SettingsScreenV2({super.key}); + @override + ConsumerState createState() => _SettingsScreenV2State(); +} + +class _SettingsScreenV2State extends ConsumerState { @override Widget build(BuildContext context) { + final miningAsync = ref.watch(miningRewardsProvider); + final colors = context.colors; final trailing = SettingsTappableRowUtils.chevron(colors); final entries = _settingsHubItems(colors); @@ -26,19 +38,34 @@ class SettingsScreenV2 extends StatelessWidget { mainContent: ListView( children: [ for (final e in entries.asMap().entries) ...[ - SettingsTappableRow( - leading: e.value.leading, - title: e.value.title, - subtitle: e.value.subtitle, - onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => e.value.page)), - trailing: trailing, - ), + if (e.value.title == _miningRewardsTitle) + miningAsync.when( + data: (data) => + _buildTappableRow(e.value, subtitle: '${data.totalBlocks} blocks mined', trailing: trailing), + loading: () => _buildTappableRow(e.value, subtitle: 'Loading...', trailing: trailing), + error: (err, st) { + debugPrint('Error getting mining rewards: ${err.toString()}'); + debugPrint('Stack trace: ${st.toString()}'); + + return _buildTappableRow(e.value, subtitle: 'Error getting mining rewards', trailing: trailing); + }, + ) + else + _buildTappableRow(e.value, trailing: trailing), if (e.key < entries.length - 1) const SettingsDivider(), ], ], ), ); } + + Widget _buildTappableRow(_SettingsHubItem item, {required Widget trailing, String? subtitle}) => SettingsTappableRow( + leading: item.leading, + title: item.title, + subtitle: subtitle ?? item.subtitle, + onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => item.page)), + trailing: trailing, + ); } class _SettingsHubItem { @@ -64,6 +91,12 @@ List<_SettingsHubItem> _settingsHubItems(AppColorsV2 colors) { subtitle: 'Currency, POS mode, notifications', page: const PreferencesSettingsScreenV2(), ), + _SettingsHubItem( + leading: _settingsHubIcon(colors, svg: SvgPicture.asset('assets/v2/axe.svg', width: 18, height: 18)), + title: _miningRewardsTitle, + subtitle: 'Loading...', + page: const MiningRewardsScreen(), + ), _SettingsHubItem( leading: _settingsHubIcon(colors, icon: Icons.shield_outlined), title: 'Account Type', diff --git a/mobile-app/lib/v2/theme/app_colors.dart b/mobile-app/lib/v2/theme/app_colors.dart index 89cd2b90..6be68c7d 100644 --- a/mobile-app/lib/v2/theme/app_colors.dart +++ b/mobile-app/lib/v2/theme/app_colors.dart @@ -17,6 +17,7 @@ class AppColorsV2 extends ThemeExtension { final Color textMuted; final Color textError; final Color textLabel; + final Color textLightGray; // Accents final Color accentOrange; @@ -83,6 +84,7 @@ class AppColorsV2 extends ThemeExtension { required this.textMuted, required this.textError, required this.textLabel, + required this.textLightGray, required this.accentOrange, required this.accentGreen, required this.checksum, @@ -132,6 +134,7 @@ class AppColorsV2 extends ThemeExtension { textMuted: const Color(0xFF888888), textError: const Color(0xFFC0392B), textLabel: const Color(0xFF787878), + textLightGray: const Color(0xFFEBEBEB), accentOrange: const Color(0xFFFF6B35), accentGreen: const Color(0xFF34C759), checksum: const Color(0xFF95A7FB), @@ -173,6 +176,7 @@ class AppColorsV2 extends ThemeExtension { Color? textMuted, Color? textError, Color? textLabel, + Color? textLightGray, Color? accentOrange, Color? accentGreen, Color? checksum, @@ -224,6 +228,7 @@ class AppColorsV2 extends ThemeExtension { textMuted: textMuted ?? this.textMuted, textError: textError ?? this.textError, textLabel: textLabel ?? this.textLabel, + textLightGray: textLightGray ?? this.textLightGray, accentOrange: accentOrange ?? this.accentOrange, accentGreen: accentGreen ?? this.accentGreen, checksum: checksum ?? this.checksum, @@ -275,6 +280,7 @@ class AppColorsV2 extends ThemeExtension { textMuted: Color.lerp(textMuted, other.textMuted, t) ?? textMuted, textError: Color.lerp(textError, other.textError, t) ?? textError, textLabel: Color.lerp(textLabel, other.textLabel, t) ?? textLabel, + textLightGray: Color.lerp(textLightGray, other.textLightGray, t) ?? textLightGray, accentOrange: Color.lerp(accentOrange, other.accentOrange, t) ?? accentOrange, accentGreen: Color.lerp(accentGreen, other.accentGreen, t) ?? accentGreen, checksum: Color.lerp(checksum, other.checksum, t) ?? checksum, diff --git a/mobile-app/lib/v2/theme/app_text_styles.dart b/mobile-app/lib/v2/theme/app_text_styles.dart index 382d9b0f..441eb64c 100644 --- a/mobile-app/lib/v2/theme/app_text_styles.dart +++ b/mobile-app/lib/v2/theme/app_text_styles.dart @@ -18,6 +18,7 @@ class AppTextTheme extends ThemeExtension { final TextStyle? timer; final TextStyle? detail; final TextStyle? tiny; + final TextStyle? totalMinedBlocks; final TextStyle? transactionDetailAmountPrimary; final TextStyle? transactionDetailAmountSymbol; final TextStyle? transactionDetailRowLabel; @@ -39,6 +40,7 @@ class AppTextTheme extends ThemeExtension { this.timer, this.detail, this.tiny, + this.totalMinedBlocks, this.transactionDetailAmountPrimary, this.transactionDetailAmountSymbol, this.transactionDetailRowLabel, @@ -62,6 +64,12 @@ class AppTextTheme extends ThemeExtension { timer: const TextStyle(fontSize: 28, fontWeight: FontWeight.w600, fontFamily: fontFamily), detail: const TextStyle(fontSize: 12, fontWeight: FontWeight.w400, fontFamily: fontFamily), tiny: const TextStyle(fontSize: 11, fontWeight: FontWeight.w400, fontFamily: fontFamily), + totalMinedBlocks: const TextStyle( + fontFamily: fontFamilySecondary, + fontSize: 56, + fontWeight: FontWeight.w400, + height: 1.0, + ), transactionDetailAmountPrimary: const TextStyle( fontFamily: fontFamilySecondary, fontSize: 64, @@ -113,6 +121,12 @@ class AppTextTheme extends ThemeExtension { timer: const TextStyle(fontSize: 36, fontWeight: FontWeight.w600, fontFamily: fontFamily), detail: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, fontFamily: fontFamily), tiny: const TextStyle(fontSize: 15, fontWeight: FontWeight.w400, fontFamily: fontFamily), + totalMinedBlocks: const TextStyle( + fontFamily: fontFamilySecondary, + fontSize: 60, + fontWeight: FontWeight.w400, + height: 1.0, + ), transactionDetailAmountPrimary: const TextStyle( fontFamily: fontFamilySecondary, fontSize: 80, @@ -164,6 +178,7 @@ class AppTextTheme extends ThemeExtension { TextStyle? timer, TextStyle? detail, TextStyle? tiny, + TextStyle? totalMinedBlocks, TextStyle? transactionDetailAmountPrimary, TextStyle? transactionDetailAmountSymbol, TextStyle? transactionDetailRowLabel, @@ -185,6 +200,7 @@ class AppTextTheme extends ThemeExtension { timer: timer ?? this.timer, detail: detail ?? this.detail, tiny: tiny ?? this.tiny, + totalMinedBlocks: totalMinedBlocks ?? this.totalMinedBlocks, transactionDetailAmountPrimary: transactionDetailAmountPrimary ?? this.transactionDetailAmountPrimary, transactionDetailAmountSymbol: transactionDetailAmountSymbol ?? this.transactionDetailAmountSymbol, transactionDetailRowLabel: transactionDetailRowLabel ?? this.transactionDetailRowLabel, @@ -211,6 +227,7 @@ class AppTextTheme extends ThemeExtension { timer: TextStyle.lerp(timer, other.timer, t), detail: TextStyle.lerp(detail, other.detail, t), tiny: TextStyle.lerp(tiny, other.tiny, t), + totalMinedBlocks: TextStyle.lerp(totalMinedBlocks, other.totalMinedBlocks, t), transactionDetailAmountPrimary: TextStyle.lerp( transactionDetailAmountPrimary, other.transactionDetailAmountPrimary, diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index 849ff865..bf81af5e 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -107,6 +107,7 @@ flutter: - assets/v2/action_swap.svg - assets/v2/uppercase_q.svg - assets/v2/uppercase_q.png + - assets/v2/axe.svg - assets/v2/uppercase_q_black_bg.png - assets/v2/swap_arrows_down_up.svg - assets/v2/swap_clock_counter_clockwise.svg diff --git a/quantus_sdk/lib/src/constants/app_constants.dart b/quantus_sdk/lib/src/constants/app_constants.dart index a8a0d3df..94b75f44 100644 --- a/quantus_sdk/lib/src/constants/app_constants.dart +++ b/quantus_sdk/lib/src/constants/app_constants.dart @@ -39,6 +39,8 @@ class AppConstants { static const String raidQuestsPageUrl = 'https://www.quantus.com/quests/raid'; static const String communityUrl = 'https://t.me/quantusnetwork'; static const String faucetUrl = 'https://x.com/QuantusNetwork/status/2033738875827589221'; + static const String miningSetupGuideUrl = 'https://docs.quantus.com/guides/mining'; + static const String telemetryUrl = 'https://telemetry.quantus.cat'; // Development accounts static const String crystalAlice = '//Crystal Alice'; diff --git a/quantus_sdk/lib/src/services/number_formatting_service.dart b/quantus_sdk/lib/src/services/number_formatting_service.dart index 3a3cb721..d30b6671 100644 --- a/quantus_sdk/lib/src/services/number_formatting_service.dart +++ b/quantus_sdk/lib/src/services/number_formatting_service.dart @@ -9,7 +9,8 @@ class NumberFormattingService { final LocaleNumberConfig _localeConfig; - NumberFormattingService({LocaleNumberConfig? localeConfig}) : _localeConfig = localeConfig ?? LocaleNumberConfig.fromDefaultLocale(); + NumberFormattingService({LocaleNumberConfig? localeConfig}) + : _localeConfig = localeConfig ?? LocaleNumberConfig.fromDefaultLocale(); /// Formats a raw BigInt balance (representing the smallest unit) into a /// user-readable string with a specified number of decimal places. diff --git a/quantus_sdk/lib/src/services/taskmaster_service.dart b/quantus_sdk/lib/src/services/taskmaster_service.dart index 4bb16e54..394ac8b8 100644 --- a/quantus_sdk/lib/src/services/taskmaster_service.dart +++ b/quantus_sdk/lib/src/services/taskmaster_service.dart @@ -566,8 +566,6 @@ class TaskmasterService { final Map data = responseBody['data']; - print('data $data'); - final List? minerStatsList = data['minerStats']; if (minerStatsList == null || minerStatsList.isEmpty) { return MinerStats(totalMinedBlocks: 0, totalRewards: BigInt.zero); diff --git a/quantus_sdk/lib/src/services/wormhole_utxo_service.dart b/quantus_sdk/lib/src/services/wormhole_utxo_service.dart index 04cb3624..376f24bd 100644 --- a/quantus_sdk/lib/src/services/wormhole_utxo_service.dart +++ b/quantus_sdk/lib/src/services/wormhole_utxo_service.dart @@ -198,7 +198,7 @@ class WormholeUtxoService { query TransfersToAddress($to: String!, $limit: Int!, $offset: Int!, $afterBlock: Int) { transfers: transfer( where: { to: { id: {_eq: $to } }, block: { height: {_gt: $afterBlock } } } - order_by: {block_height: asc} + order_by: {block: {height: asc}} limit: $limit offset: $offset ) {