diff --git a/miner-app/lib/features/miner/miner_balance_card.dart b/miner-app/lib/features/miner/miner_balance_card.dart index 7d17db739..7f0a0f87a 100644 --- a/miner-app/lib/features/miner/miner_balance_card.dart +++ b/miner-app/lib/features/miner/miner_balance_card.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:quantus_miner/src/config/miner_config.dart'; import 'package:quantus_miner/src/services/log_stream_processor.dart'; +import 'package:quantus_miner/src/services/miner_settings_service.dart'; import 'package:quantus_miner/src/services/miner_wallet_service.dart'; import 'package:quantus_miner/src/services/mining_orchestrator.dart'; import 'package:quantus_miner/src/shared/extensions/snackbar_extensions.dart'; @@ -24,7 +25,7 @@ class MinerBalanceCard extends StatefulWidget { class _MinerBalanceCardState extends State { final _walletService = MinerWalletService(); - final _utxoService = WormholeUtxoService(); + final _settingsService = MinerSettingsService(); String? _address; BigInt? _balance; bool _loading = true; @@ -112,7 +113,8 @@ class _MinerBalanceCardState extends State { setState(() => _balanceLoading = true); _log.i('Fetching unspent balance for $address ...'); try { - final balance = await _utxoService.getUnspentBalance(wormholeAddress: address, secretHex: secretHex); + final utxoService = await _settingsService.utxoService(); + final balance = await utxoService.getUnspentBalance(wormholeAddress: address, secretHex: secretHex); _log.i('Unspent balance: $balance token units (${_formatter.formatBalance(balance, addSymbol: true)})'); if (!mounted) return; setState(() { diff --git a/miner-app/lib/features/withdrawal/claim_rewards_dialog.dart b/miner-app/lib/features/withdrawal/claim_rewards_dialog.dart index cac1eceb6..cc825da9d 100644 --- a/miner-app/lib/features/withdrawal/claim_rewards_dialog.dart +++ b/miner-app/lib/features/withdrawal/claim_rewards_dialog.dart @@ -26,7 +26,7 @@ enum _Screen { input, confirm, progress } class _ClaimRewardsDialogState extends State<_ClaimRewardsDialog> { final _addressController = TextEditingController(); - final _wormholeSendService = WormholeSendService(); + WormholeSendService? _wormholeSendService; final _walletService = MinerWalletService(); final _settingsService = MinerSettingsService(); @@ -118,7 +118,9 @@ class _ClaimRewardsDialogState extends State<_ClaimRewardsDialog> { final rpcUrl = chainConfig.rpcUrl; _log.i('Starting claim for ${keyPair.address} to ${_addressController.text.trim()}'); - final result = await _wormholeSendService.claimRewards( + final sendService = WormholeSendService(utxoService: await _settingsService.utxoService()); + _wormholeSendService = sendService; + final result = await sendService.claimRewards( wormholeAddress: keyPair.address, secretHex: keyPair.secretHex, destinationAddress: _addressController.text.trim(), @@ -163,7 +165,7 @@ class _ClaimRewardsDialogState extends State<_ClaimRewardsDialog> { } void _cancelClaim() { - _wormholeSendService.cancel(); + _wormholeSendService?.cancel(); } @override diff --git a/miner-app/lib/features/withdrawal/withdrawal_screen.dart b/miner-app/lib/features/withdrawal/withdrawal_screen.dart index 155ea00ba..5a6c331b4 100644 --- a/miner-app/lib/features/withdrawal/withdrawal_screen.dart +++ b/miner-app/lib/features/withdrawal/withdrawal_screen.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:quantus_miner/src/services/miner_settings_service.dart'; import 'package:quantus_miner/src/services/miner_wallet_service.dart'; import 'package:quantus_miner/src/shared/extensions/snackbar_extensions.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; import 'claim_rewards_dialog.dart'; @@ -17,7 +17,7 @@ class WithdrawalScreen extends StatefulWidget { class _WithdrawalScreenState extends State { final _walletService = MinerWalletService(); - final _utxoService = WormholeUtxoService(); + final _settingsService = MinerSettingsService(); BigInt? _balance; bool _loading = true; bool _canWithdraw = false; @@ -34,7 +34,8 @@ class _WithdrawalScreenState extends State { BigInt? balance; if (keyPair != null && keyPair.secretHex.isNotEmpty) { try { - balance = await _utxoService.getUnspentBalance(wormholeAddress: keyPair.address, secretHex: keyPair.secretHex); + final utxoService = await _settingsService.utxoService(); + balance = await utxoService.getUnspentBalance(wormholeAddress: keyPair.address, secretHex: keyPair.secretHex); } catch (_) {} } if (!mounted) return; diff --git a/miner-app/lib/src/config/miner_config.dart b/miner-app/lib/src/config/miner_config.dart index 60684c267..a1bb7d411 100644 --- a/miner-app/lib/src/config/miner_config.dart +++ b/miner-app/lib/src/config/miner_config.dart @@ -141,7 +141,15 @@ class MinerConfig { displayName: 'Planck Testnet', description: 'Planck testnet', rpcUrl: 'https://a1-planck.quantus.cat', - subsquidUrl: 'http://127.0.0.1:4000/graphql', // Local Subsquid for testing + subsquidUrl: 'https://sub2.quantus.com/v1/graphql', + isDefault: false, + ), + ChainConfig( + id: 'mainnet', + displayName: 'Mainnet', + description: 'Quantus mainnet', + rpcUrl: 'https://rpc1-mainnet.quantus.com', + subsquidUrl: 'https://subsquid-mainnet-app-1.quantus.com/v1/graphql', isDefault: true, ), ]; @@ -207,7 +215,7 @@ class ChainConfig { final String displayName; final String description; final String rpcUrl; - final String? subsquidUrl; + final String subsquidUrl; final bool isDefault; const ChainConfig({ @@ -215,7 +223,7 @@ class ChainConfig { required this.displayName, required this.description, required this.rpcUrl, - this.subsquidUrl, + required this.subsquidUrl, this.isDefault = false, }); diff --git a/miner-app/lib/src/services/miner_settings_service.dart b/miner-app/lib/src/services/miner_settings_service.dart index be6f26295..a73ace1c3 100644 --- a/miner-app/lib/src/services/miner_settings_service.dart +++ b/miner-app/lib/src/services/miner_settings_service.dart @@ -42,34 +42,9 @@ class MinerSettingsService { return prefs.getInt(_keyGpuDevices); } - /// Save the selected chain ID and configure endpoints accordingly. Future saveChainId(String chainId) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString(_keyChainId, chainId); - // Update GraphQL endpoint for the selected chain - _configureEndpointsForChain(chainId); - } - - /// Configure RPC and GraphQL endpoints based on chain ID. - void _configureEndpointsForChain(String chainId) { - final chain = MinerConfig.getChainById(chainId); - _log.i('Configuring endpoints for chain $chainId:'); - _log.i(' RPC: ${chain.rpcUrl}'); - _log.i(' GraphQL: ${chain.subsquidUrl ?? 'not configured'}'); - - // Configure RPC endpoint for SubstrateService - final rpcService = RpcEndpointService(); - _log.i(' RPC endpoints before: ${rpcService.endpoints.length}'); - // rpcService.setEndpoints([chain.rpcUrl]); - // _log.i(' RPC endpoints after: ${rpcService.endpoints.length}'); - // _log.i(' Best RPC endpoint: ${rpcService.bestEndpointUrl}'); - - // Configure GraphQL endpoint (for any remaining Subsquid usage) - // if (chain.subsquidUrl != null) { - // GraphQlEndpointService().setEndpoints([chain.subsquidUrl!]); - // } else { - // GraphQlEndpointService().setEndpoints([]); - // } } /// Get the saved chain ID, returns default if not set. @@ -89,8 +64,6 @@ class MinerSettingsService { chainId = savedChainId; } } - // Configure endpoints for this chain - _configureEndpointsForChain(chainId); return chainId; } @@ -100,6 +73,16 @@ class MinerSettingsService { return MinerConfig.getChainById(chainId); } + /// UTXO discovery bound to the selected chain, so balances, claims and proofs + /// all talk to the same network instead of the SDK's app-wide endpoints. + Future utxoService() async { + final chain = await getChainConfig(); + return WormholeUtxoService( + graphQl: GraphQlEndpointService.forUrls([chain.subsquidUrl]), + rpc: RpcEndpointService.forUrls([chain.rpcUrl]), + ); + } + Future logout() async { _log.i('Starting app logout/reset...'); diff --git a/mobile-app/assets/testnet_data/dirac_miners.json b/mobile-app/assets/testnet_data/dirac_miners.json deleted file mode 100644 index 61395f476..000000000 --- a/mobile-app/assets/testnet_data/dirac_miners.json +++ /dev/null @@ -1,2001 +0,0 @@ -{ - "data": { - "minerStats": [ - { - "id": "qzmssQyhfreQ7t41spcPP2vv1XBUsC1A4915ogJW4bXfCvx4D", - "totalMinedBlocks": 89778, - "totalRewards": "877676606141581253" - }, - { - "id": "qzjfAe19wbQStkPuCZxiST4dy5ghtXBNgyNiBRu7N9RdEpZuA", - "totalMinedBlocks": 75806, - "totalRewards": "736282492892638973" - }, - { - "id": "qzngDmhUrTbdCUUMyzoWFTVer4dVEqxEGJbMwDzgq1vtFDnYu", - "totalMinedBlocks": 47456, - "totalRewards": "466488432074306629" - }, - { - "id": "qzkVESSdXeQ8Jb6wYC7CtCj5hQqPqzFgUjgvDjc9E1gQ7LHoK", - "totalMinedBlocks": 41450, - "totalRewards": "395933986280903282" - }, - { - "id": "qzpsKarxVcw79pH1zKpRHeX2tiBZdqpJxbFBiDuo3ocRvd5MT", - "totalMinedBlocks": 40121, - "totalRewards": "390535114418194789" - }, - { - "id": "qzpdQxGyLBtNC4hYE4XzH96KAqyfBBEbSAMGvLAZta4KMWNqx", - "totalMinedBlocks": 38785, - "totalRewards": "376833756387786351" - }, - { - "id": "qzqAn1M81bJ4BThz2BfRRhNhX82hhBnYbs65wAnh6re9kyo33", - "totalMinedBlocks": 31392, - "totalRewards": "303970828211702981" - }, - { - "id": "qzkUpykcRNm4R5T4jQELTP7WDNkMJ557yHKYgpLqobdbBETcp", - "totalMinedBlocks": 27954, - "totalRewards": "273594426168281683" - }, - { - "id": "qzk4ECfeSFbQfJ9zNsAweryn46TttfkMwDmDEw97w4cJoTGzu", - "totalMinedBlocks": 22887, - "totalRewards": "220559029126989393" - }, - { - "id": "qzpffwT52TxEg2G8hmZ4NcJ1vjw788kyXX329VJzSSNuREkVU", - "totalMinedBlocks": 22265, - "totalRewards": "218863497175014825" - }, - { - "id": "qzjykZBSbNb6JUE4gSamRXbPReSSJi2Jc6Y8M51ALJLBs8odt", - "totalMinedBlocks": 22146, - "totalRewards": "217243145103436307" - }, - { - "id": "qzjabKoiGqb2EQKgjadKdcPXeXER7ad3C41ChdWP5YkMiYPBP", - "totalMinedBlocks": 21635, - "totalRewards": "210286642956426625" - }, - { - "id": "qzoa1vPquWNDpZckmSu8b3rp2S7KNbMrtMJ7NWkucfV48vTcF", - "totalMinedBlocks": 20389, - "totalRewards": "200162848660982469" - }, - { - "id": "qzod6rwhRxPVjueF2EZ46iKAWMHdgYLeATdgSPtKG8bJ3i7tr", - "totalMinedBlocks": 19624, - "totalRewards": "188578629886128238" - }, - { - "id": "qzkMLLy5HaHcUfCHfdEoeiFEAieP2RfKQLcjiTnwCTuiP3kFg", - "totalMinedBlocks": 19440, - "totalRewards": "186821271690995597" - }, - { - "id": "qzoAcbQXeGT4LZXdZPcT8qkBJGU8jkDWFiMrodvcUzPDEbumz", - "totalMinedBlocks": 16182, - "totalRewards": "151233653228587877" - }, - { - "id": "qznvwUzPoZ72bDwaoQyYbabME6dZCQSz1WoY6DtcY7NYv6MQH", - "totalMinedBlocks": 15093, - "totalRewards": "146135247753870005" - }, - { - "id": "qzq2NpQ4YEM8UedKmT6grZFJCwBtcfZWfnNNACZRtLr5oFn9J", - "totalMinedBlocks": 13642, - "totalRewards": "130515947449848489" - }, - { - "id": "qzm1ezdaBRnevMFanLZt9fRTZ7kBMxBeHABQ4ZWuQ7hVocECY", - "totalMinedBlocks": 12730, - "totalRewards": "125007866978463003" - }, - { - "id": "qznsWNgxqNFqNGJHproQZEZm89oih1AebS8dZSFfGTLVspbEf", - "totalMinedBlocks": 11929, - "totalRewards": "114457702629304150" - }, - { - "id": "qzmReSdCef1efdr8cyoqiCqRKd16LGgyLm6do2Ssgv35YhZYm", - "totalMinedBlocks": 11446, - "totalRewards": "112107212666775562" - }, - { - "id": "qzkRPsN9EcqGh5YBY9ZtYWKENTe1wyjsF8ETXdXRLg8cUmMip", - "totalMinedBlocks": 11272, - "totalRewards": "109733291564927345" - }, - { - "id": "qznsgpVYgbmySquQpRWCNWwLotYLcVN4GNPECxfhfTmESjPeu", - "totalMinedBlocks": 11262, - "totalRewards": "110331753175948322" - }, - { - "id": "qzoRj4BB9yZU592N39bVDaihtRTD3dGSVHuf5B7VB4ogTwDSG", - "totalMinedBlocks": 10944, - "totalRewards": "105207181685294194" - }, - { - "id": "qznuv4ayRPVmsQP1YAuSdRFyraHgk5JUV59GhokWY4tJNLpMX", - "totalMinedBlocks": 9865, - "totalRewards": "95054578068557919" - }, - { - "id": "qzpxVNHtbHLCT4p8bCdMsoN59y1Nk5ohrTBJL2CqBMHahzz9T", - "totalMinedBlocks": 9788, - "totalRewards": "94596196170288761" - }, - { - "id": "qzmjL8bskGfg99oyjz3EzaoZQGUdkuC1PQUFpbvkzHmsavaad", - "totalMinedBlocks": 9563, - "totalRewards": "92154999132232896" - }, - { - "id": "qznnipanj2hXNrTfa6UUaPkQRX3mxtFkF1FKiNFokKgCTFQsv", - "totalMinedBlocks": 9463, - "totalRewards": "93380768090314843" - }, - { - "id": "qzn9vq9RmqLTke7rgWhSooFXCTF1c37xqqogQh2Skx8TAwkCn", - "totalMinedBlocks": 9408, - "totalRewards": "92081578395152333" - }, - { - "id": "qzpR14A35zqXJvZwv7ELpjyiaKgeJ9VY3yU1618Z8TLwWJjzU", - "totalMinedBlocks": 9346, - "totalRewards": "91771235531875804" - }, - { - "id": "qzqDVTrpK1iB1r4fDmaDt17kReGere5S7QTJnw7e9d6avsfi1", - "totalMinedBlocks": 8967, - "totalRewards": "85449540888775065" - }, - { - "id": "qzjuRcQLRnipu9foN527EmNYwgBrsKzDHLmpJY5dv7muU14qc", - "totalMinedBlocks": 8779, - "totalRewards": "85867986050634249" - }, - { - "id": "qznYQKUeV5un22rXh7CCQB7Bsac74jynVDs2qbHk1hpPMjocB", - "totalMinedBlocks": 8688, - "totalRewards": "86560315390157117" - }, - { - "id": "qzjV3hAsSpehwEH7ufz8CSS9nDjB5BqSirYhNTcZUzsHmZn7Y", - "totalMinedBlocks": 8455, - "totalRewards": "80828703635798164" - }, - { - "id": "qzoVJ5Ptbw8udqmB2BdqWqUy2zKhbSPHqn4tjevabMBKWqVgH", - "totalMinedBlocks": 7431, - "totalRewards": "72784207824336048" - }, - { - "id": "qznfAogbxPq6HBesBUgrehTAXPs7D4JMJ4ZKGGjq6pVsozCkY", - "totalMinedBlocks": 7222, - "totalRewards": "69123453410918732" - }, - { - "id": "qzm4THzkiamY4WAEWBRQvSHNnuF1646QHcLCWWfQkPx8G4spW", - "totalMinedBlocks": 6682, - "totalRewards": "65121074851614352" - }, - { - "id": "qzjou3gFoc43pNcj7di1LRVBnbeRGtS3YHuzH3ansrxM7uNjJ", - "totalMinedBlocks": 6660, - "totalRewards": "65161521749603682" - }, - { - "id": "qzp6hcZtKGMh7rAmkwis6TaumBB8Ejaf6E4MGbxeNTT84yGe4", - "totalMinedBlocks": 6597, - "totalRewards": "62876962433532843" - }, - { - "id": "qzkstn8F4jNj43Q7xYHERVM2xJKNjqc4HEDfkstajAsNFGomM", - "totalMinedBlocks": 5666, - "totalRewards": "53983018693563043" - }, - { - "id": "qzoF1k2refbU5jqLNJ63ETmEyUC6ddhcbmwbz8KQF5p7KjEn5", - "totalMinedBlocks": 5502, - "totalRewards": "53102575513011054" - }, - { - "id": "qzmdSNDcuWexG7Cz19taf3XfT6fCRX12qDQU69vNzdDpRgjJv", - "totalMinedBlocks": 5477, - "totalRewards": "52064819235084504" - }, - { - "id": "qzoKAaM69LJqXhBHB5pBroW2H8jRaFt7tu7rrLFGypMacpsiM", - "totalMinedBlocks": 5320, - "totalRewards": "51422029488616233" - }, - { - "id": "qzmj2vv3dxRXvQQEBp5crY2AQe6DwbNQ7ww7m5p17BXv9sxV7", - "totalMinedBlocks": 5269, - "totalRewards": "51091890226678413" - }, - { - "id": "qzm34PtyvSroN8Y1Ewn9rRWEPpXAn68TBaXEJMFpqSwxLn4wh", - "totalMinedBlocks": 5202, - "totalRewards": "50022616150677636" - }, - { - "id": "qzpf27tsP8fBRRgGGyAnDc4qc4T6EBQ7FHE198E9BQhwBfXNG", - "totalMinedBlocks": 5182, - "totalRewards": "50932671296014756" - }, - { - "id": "qzoXu7mBS3hVPuBSWpg5vMbAmqC21k6D6JZMTAgATJFwnmv4a", - "totalMinedBlocks": 5166, - "totalRewards": "49961939134919863" - }, - { - "id": "qzq5jpZzkD1g6S8q8do8YXEH5kbLJxr5ZH8iLC1fRMH1cDCwr", - "totalMinedBlocks": 4711, - "totalRewards": "46111080552863665" - }, - { - "id": "qzpPXV9GBTPNZt5ykFT2ccSdXVgtU2DeUWtFnFnmzKeQBZVpd", - "totalMinedBlocks": 4671, - "totalRewards": "44941874786508739" - }, - { - "id": "qzq9iYKZ32ymzRXQAdWq6dj1wzYgLi4pcbahkUQE8hURzqmJf", - "totalMinedBlocks": 4621, - "totalRewards": "43776051609685196" - }, - { - "id": "qzjtKf8PuSQKssmaJBrKzF8NqXxNgiWoABuwn2xazbx5Qx5Wh", - "totalMinedBlocks": 4601, - "totalRewards": "43734181586906892" - }, - { - "id": "qzoXFFL5N6jPuH8A4wm8tFT3WKecSXdHUPtvSkQcbMSVGKiQF", - "totalMinedBlocks": 4587, - "totalRewards": "44522864874914055" - }, - { - "id": "qzq8TijbtG5eV9Z6RDfQf8Mr2pzFWV5PT6YDuaehHD4TX5vfo", - "totalMinedBlocks": 4516, - "totalRewards": "43553054271751656" - }, - { - "id": "qzoSU3H1AfoCXaP2Jwqghz2PsBssxbmQkD8vNvs4w1ng9PtDx", - "totalMinedBlocks": 4464, - "totalRewards": "42962246174396492" - }, - { - "id": "qzmFxRvMgBN2DhcwTyoYdt4DWm6ygqkym89pVY8c4J1Hh9TZz", - "totalMinedBlocks": 4437, - "totalRewards": "43241452999405977" - }, - { - "id": "qzjckpfCNf78VKLTdBAi4qmLXVdq7wR4i8cFgJikAYHvLHDe7", - "totalMinedBlocks": 4429, - "totalRewards": "42246809732487036" - }, - { - "id": "qzmRgTnJ6XNFCKheSrnzKiaW34ubPENCX7MqSreQKgTsZkEBQ", - "totalMinedBlocks": 4427, - "totalRewards": "42771815440449404" - }, - { - "id": "qzp2iCiP2eiMrT3evvYRSBUNX1hh7rbwUokchqDg69kzjgPBy", - "totalMinedBlocks": 4399, - "totalRewards": "42811133568302695" - }, - { - "id": "qzq7P4bPaptUpY3F6YC1RvWFS7Ee7MfGmzoi2yaSYWqWahZUR", - "totalMinedBlocks": 4303, - "totalRewards": "41412372639408065" - }, - { - "id": "qzkqpVQfNPJdtLy11Lipq7S4FweRWfhCMrfVUohDgaKifUQAZ", - "totalMinedBlocks": 4234, - "totalRewards": "41071541456981202" - }, - { - "id": "qzo92RobTAxfxz6fFNQZ3qqRCkkHt28oJiHMgz2bHyMHBE78S", - "totalMinedBlocks": 4220, - "totalRewards": "40074646282425689" - }, - { - "id": "qzmZzKgzPHZFpJ4pT3NW68rN4rD6xj9GdaTMHVkDywwdgdb1M", - "totalMinedBlocks": 4197, - "totalRewards": "40201969994025342" - }, - { - "id": "qzoTppcZM2nZg2qBNhXCe2KbiGiCFXpXFyMkzu8qy9qDRuNhx", - "totalMinedBlocks": 4193, - "totalRewards": "39495273648443330" - }, - { - "id": "qzodyHDA4JPnTVCYp9CPczHLTyvV2cg5wwSDzgNnedkehaAaG", - "totalMinedBlocks": 4122, - "totalRewards": "38963682983914714" - }, - { - "id": "qzmoeZ7nMVib4WPmktLCsAN4kNNCEdgWynK2Du5XUWXA4QZ94", - "totalMinedBlocks": 4065, - "totalRewards": "39641151256865707" - }, - { - "id": "qzmxL7QnoRc7U13dYhYxGHajNCW5rci9QsBhEckVSc7Jm1XU5", - "totalMinedBlocks": 3758, - "totalRewards": "36360491905745898" - }, - { - "id": "qznjwpAM7oy51RwwRMMagrvyy5zHEbpEiDu27akw4STmA1zbN", - "totalMinedBlocks": 3610, - "totalRewards": "30461048668312126" - }, - { - "id": "qzpXyYw3WYhQW4WHY6xUrWWA3K29jd8khQdhAre2AWBkZWyAW", - "totalMinedBlocks": 3578, - "totalRewards": "35014768260886376" - }, - { - "id": "qznNa4SgboRRYChe3rUKJgFu9vyrBsWa62X7KrWwgAnJLVLmN", - "totalMinedBlocks": 3524, - "totalRewards": "34151148782115254" - }, - { - "id": "qzkvYuYFPgJMwve9RSgLeEC3qKpQ81Mn1BiHWuX1k2yaAUzoq", - "totalMinedBlocks": 3509, - "totalRewards": "33203350941365067" - }, - { - "id": "qzmswvR98p3fLfTnCUqXR5rMex9Pn8xVjjyFLSYCLtrs6dUwP", - "totalMinedBlocks": 3508, - "totalRewards": "34810219331356350" - }, - { - "id": "qzp4abhZ4xh7MYXvDu5zFUg9wjG8Lcd1MSzyDYChVFok91xxV", - "totalMinedBlocks": 3491, - "totalRewards": "33985748336814460" - }, - { - "id": "qzpZBpnnQXRgTE6DvpbMHm8etUi1Bxxdy9VmW5GuvqzEEWTMC", - "totalMinedBlocks": 3481, - "totalRewards": "33331615906591776" - }, - { - "id": "qzjfz5bEM1ty3LcXoh1SfHgPWHbpx1n7SZ6ykEDd4e9wfw7XK", - "totalMinedBlocks": 3466, - "totalRewards": "33471315206607005" - }, - { - "id": "qzodyzosc3Kow9GV3oqXivj8Q8LQ7dM9T5wnDp5sFcjXQR1HX", - "totalMinedBlocks": 3434, - "totalRewards": "33920458931731537" - }, - { - "id": "qzpKJ8wBLVE2JkCwqQpWARN9GbQ4yptDTinfMkdE9QzqK7BRz", - "totalMinedBlocks": 3372, - "totalRewards": "32840953652037090" - }, - { - "id": "qzkRCmmoQbVqvGEQshYi6FUYdwxVJ5ES65Dss7NJWuR65YrJV", - "totalMinedBlocks": 3315, - "totalRewards": "31899693537583341" - }, - { - "id": "qzp3TrxcsLLK3r7ymgwDcuksk8KkZXjYw7mUphv7kL67ZqYDN", - "totalMinedBlocks": 3239, - "totalRewards": "31680772057168921" - }, - { - "id": "qzmyMvHGK9xvbWTaFaV7nCuHAaKXEWWx2oy2qPpK8t35G2GmV", - "totalMinedBlocks": 3147, - "totalRewards": "30321415152458077" - }, - { - "id": "qzqBeoPMd275pnrNAeQhmhyu5QJ8SeYNAVA5xZTEdGFTWBAxR", - "totalMinedBlocks": 3039, - "totalRewards": "29800381117463770" - }, - { - "id": "qznbC2n3nbKidee6ekWJQjfoUWbDvmrK81rWf8djuuJfQABd6", - "totalMinedBlocks": 2972, - "totalRewards": "28081807666663952" - }, - { - "id": "qznWLG11DULsQFjo1bvH57RpambhRcS6tFG6DspX4GkvWH8y2", - "totalMinedBlocks": 2860, - "totalRewards": "26932188132458687" - }, - { - "id": "qzoAgZv6Qa9LK6sRA2FL6gdRdgcnkSW1MjFq4sUrmQssmrCvn", - "totalMinedBlocks": 2848, - "totalRewards": "27051978135348738" - }, - { - "id": "qznBrw539t2GXwuoN6hqniNy378Uhoxod1dn247B1bu3qSMwL", - "totalMinedBlocks": 2841, - "totalRewards": "26792113520655213" - }, - { - "id": "qzjjSUA6ByxufGRHm5hKSoaz9gGdhBZkVB6QF57nHvQhXfQVS", - "totalMinedBlocks": 2759, - "totalRewards": "26641615061893618" - }, - { - "id": "qzjd3JcWomUPHNLZ3wev3u1qdaJWFngpq8q1uz7tYv2Rvk2MJ", - "totalMinedBlocks": 2723, - "totalRewards": "26051277636445066" - }, - { - "id": "qzpBwzzKoLUJLC4DRN3yFu3yChg973ZXzG3BGdD8RQUhgXqiU", - "totalMinedBlocks": 2699, - "totalRewards": "25964089620364110" - }, - { - "id": "qzkHXEf1jYEceSyQYEcx5AsPpeySzME791wW6w3SXhwDFooSZ", - "totalMinedBlocks": 2696, - "totalRewards": "26051289713026166" - }, - { - "id": "qzkXpTw5oLkeBwn9SzUbu3jug6VJ5oD4QeRaoqet98eJxPxVi", - "totalMinedBlocks": 2616, - "totalRewards": "25510514787518838" - }, - { - "id": "qzocUbGTXDMWojfxpXiqcxG1JHqBkbKaSAXAzfUAdKaHuCHK6", - "totalMinedBlocks": 2598, - "totalRewards": "25550358639023290" - }, - { - "id": "qzk8e8TrLh4pZHYBad76BWcZxDmhDgvH8EHsFskCiE56HGQ4n", - "totalMinedBlocks": 2505, - "totalRewards": "23991178176824570" - }, - { - "id": "qzobD8H4SacS1ueFA5h1TvupB9MbArDjk4q1ENtWn34y1s5rm", - "totalMinedBlocks": 2467, - "totalRewards": "23721047578651324" - }, - { - "id": "qznXCP2sU8gcKa74XTD1UsiqW8UWBoAH18KH8p5w1sUPD57G4", - "totalMinedBlocks": 2388, - "totalRewards": "22861110680133561" - }, - { - "id": "qzoUyJBrQvAEL3yFpmDEJA4jLyYpMGrNjX28qmZ7Cvmt9TqjT", - "totalMinedBlocks": 2231, - "totalRewards": "21750592114703536" - }, - { - "id": "qzknsLGszSZUa5NnkLzymGm6u4iQ9DDQbBUYuVgLixEwzB9g5", - "totalMinedBlocks": 2180, - "totalRewards": "21190694717415536" - }, - { - "id": "qzmZ7Gpbtu2z4J55AnjqKZCfNfbuoWUmGCANsrRPZgFgb21Ya", - "totalMinedBlocks": 2150, - "totalRewards": "20401167527513357" - }, - { - "id": "qznGci976xEyHroGWm6fCAEm9AUEq38AMDu8zYohcxmU2QUsS", - "totalMinedBlocks": 2109, - "totalRewards": "20360812057168958" - }, - { - "id": "qzo7pUCHfcEhxUuxuafcixv7WU8WvBAMRjR1uCfhym4DHFe6Q", - "totalMinedBlocks": 1981, - "totalRewards": "18950975342282526" - }, - { - "id": "qzkFwmNAVrRKsZ7277jVfUDH4SLy8c88u4pmggc7Wwn2jdsRw", - "totalMinedBlocks": 1793, - "totalRewards": "17400579870994061" - }, - { - "id": "qzk3KyUyvVHXv4K26j2ePTZhr8qgRYqpL5RasN5xELYutRrys", - "totalMinedBlocks": 1790, - "totalRewards": "17050909150659816" - }, - { - "id": "qzjeLJHgufAz3UvAAkN7mtqgesUdwSP4sBdBJYfuFa2wu4Y9j", - "totalMinedBlocks": 1573, - "totalRewards": "15220664050165143" - }, - { - "id": "qzp46dLKdevu6Cn9jzvBSPivSZqTECwFohfx9xr2u86Rhbf9A", - "totalMinedBlocks": 1445, - "totalRewards": "14250186048941502" - }, - { - "id": "qzp7UiUh343dPDjsqQ4ki6gYyRtJw2gL7mLYkkmqsXsDkwZSv", - "totalMinedBlocks": 1354, - "totalRewards": "12970725373021012" - }, - { - "id": "qzpHJECPLDqWc1NHL6ySN7Ts8q7kJXiJGXGRYTHX7egTVkxX6", - "totalMinedBlocks": 1347, - "totalRewards": "12551258735536490" - }, - { - "id": "qzkMW1vkVWCKPaGoNoG1xi4zk9y8mE1rftV5wr4ajoegf6w92", - "totalMinedBlocks": 1342, - "totalRewards": "13130229976413970" - }, - { - "id": "qzjVVTwX6xi8Z7x5qRAidYkxisfjiLuZZUo5VvzUsSYzCH5Pj", - "totalMinedBlocks": 1330, - "totalRewards": "12680846837210378" - }, - { - "id": "qznDuX3cD7T4fj2rFfZCXRpGna29B1ytuHfbWSkLzBGr9tujG", - "totalMinedBlocks": 1309, - "totalRewards": "12580756370507099" - }, - { - "id": "qzpAMvnKaMBrUQMpdkX4JsQFS1peJkSnASvY1yw7CY1uyZod4", - "totalMinedBlocks": 1295, - "totalRewards": "12680240719693657" - }, - { - "id": "qzqBsCm7DVSVW7NUqtk2yJT2SoC2jmroFQ9keHTvRrpQHLRaZ", - "totalMinedBlocks": 1285, - "totalRewards": "12510460080908668" - }, - { - "id": "qzmJbB2PtT6Yn2csnweiHgP5AuYWqHpjSWqYuw5hjvu6V6Ewu", - "totalMinedBlocks": 1283, - "totalRewards": "12360620332405246" - }, - { - "id": "qzq43L99r93FuamJkKTqqg1pWu1xmRB41GSW3ZEhGo6aCMLxP", - "totalMinedBlocks": 1265, - "totalRewards": "12330374699906136" - }, - { - "id": "qznuEeDCfp9wWScQtho9Lp5UVnRoNC1WCCJugtBxBrvJbDSUQ", - "totalMinedBlocks": 1259, - "totalRewards": "12130504041410449" - }, - { - "id": "qzp2vMweB9mkQKypjQ2MCYgEzSSSnkxFb6ZLmpGBAqpwfraCd", - "totalMinedBlocks": 1224, - "totalRewards": "11590876727920967" - }, - { - "id": "qzocZMwjQXbpJPybqwM3RByokpAqXcAzzxe7xzAUwniuG3KkR", - "totalMinedBlocks": 1212, - "totalRewards": "11790333377050282" - }, - { - "id": "qzjuFfppNevtjh1DNNFUSuLqVQZodrjdh5qiG3tkVKvmCcMZz", - "totalMinedBlocks": 1211, - "totalRewards": "11690526722056423" - }, - { - "id": "qzkz9HDb8wNfUMQ6izsrTLuqPssY6vMG37SosmhHtKuvevD47", - "totalMinedBlocks": 1207, - "totalRewards": "11520757374772077" - }, - { - "id": "qzjXFVBj7WmamW8Bi7to2uqfrGh7nmebafYVrZat4Bn2Ufohe", - "totalMinedBlocks": 1184, - "totalRewards": "11320867406816051" - }, - { - "id": "qzkhabqtpLwnzRdwzYaoiVxg1AYDDPz2ZXkvAJqoN6n5yPd8g", - "totalMinedBlocks": 1181, - "totalRewards": "11300535935813541" - }, - { - "id": "qzpHnzRvAyPdFqBi8Raay2EjvS532gmBJDseDB7SHA5eS7sb1", - "totalMinedBlocks": 1181, - "totalRewards": "11385614257171691" - }, - { - "id": "qzpG1aBJwUsxB5LszistDabf8odyDRA6zDWSjYwnDiLY7eEMN", - "totalMinedBlocks": 1175, - "totalRewards": "11400450037908599" - }, - { - "id": "qzov49ugwidabvCnTwfyB9cWV57UH7gCiTrkt74Y9S2ha9bYA", - "totalMinedBlocks": 1172, - "totalRewards": "11240558735452031" - }, - { - "id": "qznJML73SHRr9oH24Ehzv8Xxkg7S8P2yxH5neSYbovNw9Sf6y", - "totalMinedBlocks": 1168, - "totalRewards": "11340375652898443" - }, - { - "id": "qzq8kWW618bHYQigjf4F6qmzHPvANpQCvnM4CAuVj76xcfAPu", - "totalMinedBlocks": 1161, - "totalRewards": "11260418703407977" - }, - { - "id": "qzpFKP7ufpBZmep9Ai24RfFvuBv4katPEQNyehSw2fMFVUNSZ", - "totalMinedBlocks": 1142, - "totalRewards": "10836549691687108" - }, - { - "id": "qznz772YbXNeDBqG9du3rUrW96atu4aM3iMUqhZTCrhSNzxyc", - "totalMinedBlocks": 1131, - "totalRewards": "10780802057168879" - }, - { - "id": "qznhYxqMiqioJosXE6d1Su9eczAuemC8kdDuo6UeMQEbPr9U4", - "totalMinedBlocks": 1118, - "totalRewards": "10810385313406799" - }, - { - "id": "qzjjzBaeGGo2NTvRpZjeCarzCJs8SwRbehPamKZaj43fgCr4b", - "totalMinedBlocks": 1113, - "totalRewards": "10660512028014924" - }, - { - "id": "qzooVku3dWWzSVCgu4Deifvk5WYWSDnZm7cuK6HKG4VZ9iMqE", - "totalMinedBlocks": 1080, - "totalRewards": "10430474240341224" - }, - { - "id": "qzkCku4BbZ2tNtFWyswpWDS1nP4ZkVbAFSQJE4woiYRPwPuQZ", - "totalMinedBlocks": 1063, - "totalRewards": "10260491494911585" - }, - { - "id": "qzp4XnbWTHwNaDoPb6GYmDxRH4yjG5JhAkRRCJFrPKmwChWC5", - "totalMinedBlocks": 1061, - "totalRewards": "10000652715664586" - }, - { - "id": "qzm4SNCVRQMpLrns1RZiBw6kNjzdSXQxMYQ9pcm6Gw1U5jPfF", - "totalMinedBlocks": 1032, - "totalRewards": "9930437357262669" - }, - { - "id": "qzocBuPC4htQ7TusY6RPfDuvxZW76tMciJ4rNGXMFsMgcxra8", - "totalMinedBlocks": 1027, - "totalRewards": "10130160676004931" - }, - { - "id": "qznpLBTmpowK3uH1zajqrCP9Ms81w4MvhLEgQ1wc7jGMHqyiZ", - "totalMinedBlocks": 1009, - "totalRewards": "9550630713913585" - }, - { - "id": "qzp2NKfUKpUGiPmEzXL1SrxyXPHrfWaN7k8szT3Jyv2QNiDzN", - "totalMinedBlocks": 973, - "totalRewards": "9510224679506834" - }, - { - "id": "qzmMnY6RURWJBhJ9dZE7nJ6NxmiG3soA8ksKPyEc44MLRdNdD", - "totalMinedBlocks": 940, - "totalRewards": "9150289361903664" - }, - { - "id": "qzpxfsNZJvqiKJJeaahCMFNc25EbtcAH1v8VsPuV9wPETBGZt", - "totalMinedBlocks": 888, - "totalRewards": "8780106672503110" - }, - { - "id": "qzjnhnm6B843ctXR9okYY5yr2wLCJ9bwTLBVp9whkDB2mM6bc", - "totalMinedBlocks": 880, - "totalRewards": "8410418703407978" - }, - { - "id": "qzpyutoZE1DTpP9MdLa6JHxAUL63munZByRLAaZuLDWqKSczH", - "totalMinedBlocks": 842, - "totalRewards": "8090407666294119" - }, - { - "id": "qzmVGCXECMw743XNz2exq7HWGQF5MyxyDjZh3StUK5u9qeD98", - "totalMinedBlocks": 842, - "totalRewards": "8380042669001244" - }, - { - "id": "qzo49YQ3Du3rCNMg1c4jvq74V2nCSYkvtg2FgcPgSJjGWby5D", - "totalMinedBlocks": 814, - "totalRewards": "7810363709505702" - }, - { - "id": "qzorzTjTDfgF2aWSjpb7X8j2cFLMfnbPokP74vnVzKiDQwJ4n", - "totalMinedBlocks": 806, - "totalRewards": "7970117339753398" - }, - { - "id": "qzm4mRPSvbCBP3365Mm4aAFTK8pzVeNeCvWKEFzupxG5AnE6R", - "totalMinedBlocks": 800, - "totalRewards": "7910096005252799" - }, - { - "id": "qzn5St24cMsjE4JKYdXLBctusWj5zom67dnrW22SweAahLGeG", - "totalMinedBlocks": 781, - "totalRewards": "7410524041410534" - }, - { - "id": "qzo8Nynu1zHsL4Kv3KauzGyKsNnbHraMFULAdixG27UqtEzbJ", - "totalMinedBlocks": 779, - "totalRewards": "7670108728898688" - }, - { - "id": "qzkueR6bJTaPhme2AQJVM79J3tCHV5Y3MxUyG1WkMZEwUWZn1", - "totalMinedBlocks": 775, - "totalRewards": "7370406443749883" - }, - { - "id": "qzmvqBvd3G3Zhko8C3NmP1hRHnYR8xH6z5eXDqTsPj4ujnVHz", - "totalMinedBlocks": 764, - "totalRewards": "7360320859484857" - }, - { - "id": "qznjf2vHZsqLsRom9rJYYphFmUCH238dPAbvwRfb1Rb55gx4Y", - "totalMinedBlocks": 753, - "totalRewards": "7171525416793742" - }, - { - "id": "qzpYbpDas1mtHqibGvmi1sDv6aqQibiNhXihasMpCSYPraQUJ", - "totalMinedBlocks": 741, - "totalRewards": "7060407122644900" - }, - { - "id": "qznD41x7RFCzuyhCDsaCxriNM8SbPbTbwPqH8Hfn3ipojLyoK", - "totalMinedBlocks": 716, - "totalRewards": "7050150008754641" - }, - { - "id": "qzkzyez9vMXtG9zti4Y8yzH2z1hU2UQhYAVnyt7mGJHUi4yhp", - "totalMinedBlocks": 686, - "totalRewards": "6650195994008111" - }, - { - "id": "qzqET4W49fXJyJYM7NSxqmYDQbZ8BuUXtn57CxRogtskJyDuG", - "totalMinedBlocks": 685, - "totalRewards": "6540314723195543" - }, - { - "id": "qznGVokAH4LBTiTveYCFkD2o1w13JNsckYmtWiLR6sPGdwrWu", - "totalMinedBlocks": 670, - "totalRewards": "6550150730649617" - }, - { - "id": "qznQL9mXJ9EogqAyHcx6HdLbqrxbyBKLxiiXVKbqB3vcqrbye", - "totalMinedBlocks": 659, - "totalRewards": "6480128007003699" - }, - { - "id": "qzpDySz2ADQ8oTRWzHrG34F6L8SzVzoci82V2vYxBj2p7GdJL", - "totalMinedBlocks": 640, - "totalRewards": "6170218892668002" - }, - { - "id": "qzmHs95q5JRHEqFgVEAK9QNkRUQP1rQiApJFtvkTHgMC6Ch7S", - "totalMinedBlocks": 635, - "totalRewards": "6010352926478723" - }, - { - "id": "qzm1Z8QJFoJxnmAapezaqjqkV4CH41RGBv4EbNSuENaeozCX9", - "totalMinedBlocks": 611, - "totalRewards": "5960114356221999" - }, - { - "id": "qznx1bjDQgdXkz8YLNx5MyvpeiQFZPbHHn71fq4WsJ5MAjxTV", - "totalMinedBlocks": 609, - "totalRewards": "5930090591620912" - }, - { - "id": "qzpWDnCsmeyKwrTqZra75CcS7YByaXXtmoLrZJNcsMKJbQhSh", - "totalMinedBlocks": 578, - "totalRewards": "5540268365208823" - }, - { - "id": "qzo8YG85VgfweuQMEsJrSLNCo55ogqTKX1MtcyYJ2gi5cXQxf", - "totalMinedBlocks": 565, - "totalRewards": "5446101667173494" - }, - { - "id": "qzpyuWuMJ7c4qcFBedQj3kXvuPbbPJ6qSNognPrnrr1vFs8WT", - "totalMinedBlocks": 549, - "totalRewards": "5180373978906189" - }, - { - "id": "qzoSb6NaAZBT89u6RGN4uzm5y5hJqo6ggc3wbWrF2zQCafjhc", - "totalMinedBlocks": 542, - "totalRewards": "5210214012256519" - }, - { - "id": "qzq2aKiqcBm2YRKhdVfeNu44j7TV2mbWw5NudAE971WJGEmqg", - "totalMinedBlocks": 539, - "totalRewards": "5160245346757153" - }, - { - "id": "qzomoDa4mmawDUuGt89iV9M1m1Tsma4aVEfQ1FQ3zLNFsxrLS", - "totalMinedBlocks": 534, - "totalRewards": "5110236014007447" - }, - { - "id": "qzpDo3FRVpdK1chHgLBpHceinmeNySRfHxRzbNnRfBmxcop8v", - "totalMinedBlocks": 525, - "totalRewards": "5020237048647289" - }, - { - "id": "qzo5UDS3o2XuQiV1KoGJhYteanMAtLS1mkh2KXSdpyncumazL", - "totalMinedBlocks": 521, - "totalRewards": "5000224637401835" - }, - { - "id": "qzoxZo4xxcn9FjbL57Ha9tk5PQd6PheLM66gUABa2eb8Pp8qD", - "totalMinedBlocks": 520, - "totalRewards": "4930289601871882" - }, - { - "id": "qzkdbhMKkVm3uKXwW8Z4mnKggDddhXvVCt2XueV5BytqhA2QV", - "totalMinedBlocks": 512, - "totalRewards": "5080042669001236" - }, - { - "id": "qzmrbWbyEuFi7oc8P7ZQby4imxdn5rpQr2256tD352yDM5Sim", - "totalMinedBlocks": 500, - "totalRewards": "4860181999957574" - }, - { - "id": "qznJY8Hx5mXMweeBxZaLs5HouwViviVDg2iBxecDzfosuy6LC", - "totalMinedBlocks": 495, - "totalRewards": "4790181583223449" - }, - { - "id": "qzosgeCQ3gjB9WLmKRSx4Pj9NF6YZw68c2fhzYU898ro2pMkW", - "totalMinedBlocks": 482, - "totalRewards": "4600235304652146" - }, - { - "id": "qzkBpmFyDU3MkAv1bTg9VB5M9mg33Vw8MDGAeZdrjWvWvt6BV", - "totalMinedBlocks": 476, - "totalRewards": "4510288922976862" - }, - { - "id": "qzkgu6putwW9RkVGAxaTmjGez721MpPdnn9dXHvVHYhrnAy6a", - "totalMinedBlocks": 453, - "totalRewards": "4470054137648917" - }, - { - "id": "qzpGY5qfaS1tFfbUxmybS9ecSikR6rPhn81dgYUfheocjXwJi", - "totalMinedBlocks": 436, - "totalRewards": "4140245346757151" - }, - { - "id": "qzkxen9n28qjMjujX2SEEVphQ2UdPzrHqYjDkHeVvdtPLjEgP", - "totalMinedBlocks": 435, - "totalRewards": "4280044639108153" - }, - { - "id": "qzmheMGf8A1F37WudRkuqDZBQ4q5XWaaqS9jzucJh8LczuqAp", - "totalMinedBlocks": 409, - "totalRewards": "3820279536628932" - }, - { - "id": "qzo2VufVhC6bJwdg2XYkUD2DQqjzQew2m3giz7jJMtxv5SYwS", - "totalMinedBlocks": 392, - "totalRewards": "3760160633899969" - }, - { - "id": "qzpoTFEvSgUwYpR3ePQz5K4rDwmTE46XoCDd1xefSfakZhMYd", - "totalMinedBlocks": 390, - "totalRewards": "3900000000000000" - }, - { - "id": "qzkXTv6v185mvpW7ioXpy3jkJqLS8AMqNdJxqkJid7MGATz3U", - "totalMinedBlocks": 384, - "totalRewards": "3820021334500622" - }, - { - "id": "qznE4LfStdckoGKE3E94kmzNAjkLy28SuNJCfGchy3oo8ZM2X", - "totalMinedBlocks": 366, - "totalRewards": "3460215112139305" - }, - { - "id": "qzqE22pZ2GSJpMmAr7uezPcb62ZmKd8RR846q6S3jaFQcM6V2", - "totalMinedBlocks": 366, - "totalRewards": "3490192677755881" - }, - { - "id": "qzmdKHkqRpus2WMDx8KraGwQpKdEHcjBxnKhFptzLdCSFMWao", - "totalMinedBlocks": 363, - "totalRewards": "3550076005252768" - }, - { - "id": "qzk7gD4DatoxhdPeU82EvAEmeyv92Kfna4oxaKxp3q57iN1nZ", - "totalMinedBlocks": 358, - "totalRewards": "3560021334500622" - }, - { - "id": "qzmwniuZJ8paNbQ2XTGAnCHv7KqqZaBZ81CRqKembsYdqQsBn", - "totalMinedBlocks": 351, - "totalRewards": "3282400798568676" - }, - { - "id": "qzov7trcVu8mAF32dJmdWBhCg1JjFiSSCQUbsYRkjRq6pXiPc", - "totalMinedBlocks": 344, - "totalRewards": "3330098697543423" - }, - { - "id": "qzpFoSokNpM2BKDanS636PWDu9WJBsTsuK7yctpKiZ9m8C58y", - "totalMinedBlocks": 344, - "totalRewards": "3370076016897484" - }, - { - "id": "qzkWs5u6F1Qgf885UB8sREoobtsqq9rzCocnNGDBnTQRSDx7Z", - "totalMinedBlocks": 336, - "totalRewards": "3260046619763062" - }, - { - "id": "qznrDYi7T1BmnW5jPA6VqQhLEDwLZrhv1vssfdPA4tNKS29Lr", - "totalMinedBlocks": 321, - "totalRewards": "3000224433244292" - }, - { - "id": "qzmzEaD7TBYhrQFgMNdqBgkm588p6fkb1deXUGhqy6r1BjRFe", - "totalMinedBlocks": 320, - "totalRewards": "3090117339753421" - }, - { - "id": "qzoippr2KfV49MzLJgp9ea4GaPdXdNtshWhyhStm7ptTKDWwe", - "totalMinedBlocks": 318, - "totalRewards": "3030160676004963" - }, - { - "id": "qzowjRbNQaU1QfhGWsworhQNdi6zEuxFiNQrDfp9ufanXLPfL", - "totalMinedBlocks": 312, - "totalRewards": "2920225358401852" - }, - { - "id": "qznLHZWWGsHqPCKWWi8jTKM7HxkEaxfmKqJVdTC4mkrQZxZGs", - "totalMinedBlocks": 306, - "totalRewards": "3000064003501854" - }, - { - "id": "qzo3MQuQtoueVnz57EHMyujwaSM2LB1PfSUos1w9pX2LUH76o", - "totalMinedBlocks": 305, - "totalRewards": "3050000000000000" - }, - { - "id": "qzmDVGDm9sXz6VnWP4GvhbPdAyWBGKPxSG5DigEwM8Q4KwZbJ", - "totalMinedBlocks": 305, - "totalRewards": "3000043336251546" - }, - { - "id": "qzkE8fGYRYYLqt2bgHW3gUsdLc4GtanMHtEsEskQNEyaBcqpf", - "totalMinedBlocks": 299, - "totalRewards": "2880117339753421" - }, - { - "id": "qzn87mjedhZrBtpadPXZGLfhBkhjda1AhqqjzVKsx4bUG93Ym", - "totalMinedBlocks": 297, - "totalRewards": "2820192431493361" - }, - { - "id": "qznHMb8wtT7r5HBTjgCECLCgJPYsnCB9t7BxKivZEgFq6Swkt", - "totalMinedBlocks": 295, - "totalRewards": "2860106672503094" - }, - { - "id": "qzmB4keFoJeiGHkYFPFbrT2PgcuXnEmaduFCE74n68aDJi1pT", - "totalMinedBlocks": 293, - "totalRewards": "2830117339753413" - }, - { - "id": "qzpwK6747tjFAzNSCS3gekJLBctADVnGRKSYBmfHL5bXshGzu", - "totalMinedBlocks": 292, - "totalRewards": "2810117339753418" - }, - { - "id": "qzpaqTLSqGL61txYjzWUhVDt8LpSK7NRsEPmZVk31o1uCGg7G", - "totalMinedBlocks": 290, - "totalRewards": "2790149341504335" - }, - { - "id": "qzmqR5qhpTDjKDaiRHdNUxpn2Kc1R8MN79Ri4CbBsPR2JU79C", - "totalMinedBlocks": 273, - "totalRewards": "2640096005252797" - }, - { - "id": "qzjvmw9761GuqAoRhsZ2eCBqq7VmNzAfNQGGkGmEjozynMyDF", - "totalMinedBlocks": 269, - "totalRewards": "2570118728898714" - }, - { - "id": "qzkG9ekjCtkPDvLmLGn1cgwWBr1udzRFHGahM4sCFmPdtCneD", - "totalMinedBlocks": 261, - "totalRewards": "2560085338002472" - }, - { - "id": "qzmXZcX3B75WgYWqfaGukQbaDQvEunZTz6A6ERP8Cj24xdHPo", - "totalMinedBlocks": 249, - "totalRewards": "2450032658453234" - }, - { - "id": "qzoEBqbG3ASAmzJakhUoCxdKr2zdAJd4ECnFnuPM6StdD6KyW", - "totalMinedBlocks": 248, - "totalRewards": "2380128007003708" - }, - { - "id": "qzpFWsgxV7BswcVwUkV3YidNRydFYRu3FtkSgGZptswHitwU2", - "totalMinedBlocks": 223, - "totalRewards": "2120128007003728" - }, - { - "id": "qzo2Qo4jhPcKsjZsdr9oUY5YHpRX5sMefDX94atW1VYpyg77o", - "totalMinedBlocks": 223, - "totalRewards": "2150085338002472" - }, - { - "id": "qzkCqZuRiJPqsWnNsWTFHxmvA8K4ZzwobtRGVLMCgyCRfSyDD", - "totalMinedBlocks": 214, - "totalRewards": "2060085338002488" - }, - { - "id": "qzkzo32jERRgWUA2ydbw7F2VbSuyej194K5SyfHgwoEWWoAaM", - "totalMinedBlocks": 209, - "totalRewards": "2020074670752174" - }, - { - "id": "qzpBGycREoVSFyopdsH6n2XAaVyKvZTBkSf3c9AGnJhwQ5YUb", - "totalMinedBlocks": 202, - "totalRewards": "1990032001750933" - }, - { - "id": "qzkT1GgB6dVEnLUJ2jzc7MhJRNMytU2wdEyq4cqBhwb4zcZHD", - "totalMinedBlocks": 198, - "totalRewards": "1880107093490875" - }, - { - "id": "qzkojMR4K6fG3yQF8fz2Cyg3es9nzcnDdDpG6J2G3yfUH9o8J", - "totalMinedBlocks": 195, - "totalRewards": "1910023347896246" - }, - { - "id": "qzngust61yHYBxg8rKq3bkhdYf1BZ2wn1w52X5Gxhk3u5LCmC", - "totalMinedBlocks": 186, - "totalRewards": "1820054003501851" - }, - { - "id": "qzkFJ6MbLjp6iwdpBV5ZfwZqcqeMnTgmryNK1SbH6Vw5ZWLpt", - "totalMinedBlocks": 184, - "totalRewards": "1710170676004944" - }, - { - "id": "qzmR5ZnswAqvhHNiL8edJUG2pvLJx4MUSgEvXjQfw1kAF1kap", - "totalMinedBlocks": 169, - "totalRewards": "1570139516229569" - }, - { - "id": "qzpApMk71Pfz994b9Tvm5VJSYDUwkRDwtHCywxueTyJg8u5Bb", - "totalMinedBlocks": 164, - "totalRewards": "1540118431469832" - }, - { - "id": "qzpW9S2WAKdFRXGg4Ys54fYAXZft5ovpbd7abtGcyFiZTpuUZ", - "totalMinedBlocks": 161, - "totalRewards": "1590013359540951" - }, - { - "id": "qzkhppk9Xuukgpxx6RCp7ngLNhgEHQcGGwPmXpqVbPnjnDmUQ", - "totalMinedBlocks": 156, - "totalRewards": "1550010667250309" - }, - { - "id": "qzkmEmPP5Tr8cmMWKjyQhF9dG5VodFWRHTw1DTAnZz2omPdyq", - "totalMinedBlocks": 153, - "totalRewards": "1440106672503110" - }, - { - "id": "qzkAtFu6LVY7bUGtQFH8LSgwuZmH94SCCuvwpj28ZwoxHwyS5", - "totalMinedBlocks": 143, - "totalRewards": "1340107339753392" - }, - { - "id": "qzkb6MjWY1CKUdBTKm9RKBXTovQA3PuqnHNEUoPutzau8DNo8", - "totalMinedBlocks": 140, - "totalRewards": "1270138674254043" - }, - { - "id": "qzmLaBxdg8ZPk7eMKf2Ez5US9g1JTabZ3LaYm9sKMiac8Br8y", - "totalMinedBlocks": 137, - "totalRewards": "1340032001750933" - }, - { - "id": "qzmHx4Wt4Ymxj6B5q2yEtbK9d1DCogczgcB4JaR3RVwEubPvg", - "totalMinedBlocks": 135, - "totalRewards": "1330011334500607" - }, - { - "id": "qzmUtcJHBbdUBMVxMTbqZDytEX4URhueMZGnyVRQB6u8NQ7V3", - "totalMinedBlocks": 134, - "totalRewards": "1260085338002488" - }, - { - "id": "qzpoMEn6uLMybVqoEdPaxV76GtCmLGqWXMvSUP2HpJ4LazLBs", - "totalMinedBlocks": 134, - "totalRewards": "1280075331708103" - }, - { - "id": "qzobguJdkmujDe2HxMYQL59chMDKitWFjRyd4HyaWD3dbD35F", - "totalMinedBlocks": 126, - "totalRewards": "1230032001750927" - }, - { - "id": "qzpGU15cAxXgi2WsjL71yxz4Ack6jcbZMAUiHTAcwigmkmrBw", - "totalMinedBlocks": 125, - "totalRewards": "1200053336251555" - }, - { - "id": "qzja9XWqYYwaeJeeCxcs36yq7WjU1Lw197MhSjX6ntWMtQFRN", - "totalMinedBlocks": 125, - "totalRewards": "1120150183479880" - }, - { - "id": "qzpwZJt8wSyVLTXJe28rRuFBYBar2iAcU2J6CCa58PdVed9WT", - "totalMinedBlocks": 124, - "totalRewards": "1170074670752177" - }, - { - "id": "qzpK6YNygfbbh5d4xsjiBMS6k9mVSGkLgx25LiGARnzuHw2ha", - "totalMinedBlocks": 123, - "totalRewards": "1150085338002488" - }, - { - "id": "qznxEXFjN3wDxU1S247F9nc58y8c4QWyXRQBQjbysFBtmqjKx", - "totalMinedBlocks": 121, - "totalRewards": "1130085758990252" - }, - { - "id": "qznti68ayntXygP8CRPJCvt2ppKZ4QzidC6PevvAh4RQPaReM", - "totalMinedBlocks": 121, - "totalRewards": "1150064003501866" - }, - { - "id": "qzmmjMGJnzJC3Ey485mR6uLf8Suw7bMP2ps1xyTpoU3CgTTrW", - "totalMinedBlocks": 120, - "totalRewards": "1120085338002488" - }, - { - "id": "qzpyhv3GBQyH2XabDcbEDzYrxMzgn5f9NJGKdv6XcfnmYZX6M", - "totalMinedBlocks": 120, - "totalRewards": "1180021334500618" - }, - { - "id": "qzpQkQpofEkv7Kh5WYgsaxG5o2Sx6yvf44FM5kgbRCF5ftTpQ", - "totalMinedBlocks": 119, - "totalRewards": "1180010667250309" - }, - { - "id": "qzmXTTfAzhw8rCUbfrdq6ZLCqBCbsXBNyuF92mrtC1umBNNgD", - "totalMinedBlocks": 119, - "totalRewards": "1160032001750933" - }, - { - "id": "qzjgQZYb2ASjtZ4oEojNNHSybmBqJDDgTXvRGyMDAaroaPqVf", - "totalMinedBlocks": 118, - "totalRewards": "1130053336251555" - }, - { - "id": "qzpS2fu7AqMP49gEfCp8s8GKexvkFuvzQ9CAngrGVywtSrkb7", - "totalMinedBlocks": 115, - "totalRewards": "1140010667250309" - }, - { - "id": "qzk7PyK5kpKbWaVWPSe3QkGyUzwZww2u2SWxmNbWyGdXeMWM8", - "totalMinedBlocks": 114, - "totalRewards": "1080064003501866" - }, - { - "id": "qzjmWF8mSSATTxbub5TUgf3eALsasejKLBbwyb1xVoDGdbmmW", - "totalMinedBlocks": 113, - "totalRewards": "1090042669001244" - }, - { - "id": "qzpZ2BmcWWwdSKEj8puvoyyMzaPSpEtGY51Cva9j9TE7qS8EW", - "totalMinedBlocks": 110, - "totalRewards": "1040064003501866" - }, - { - "id": "qzoBKTFdcWpoDstmQ6K4fLcuAj3vEKLkiZY9jXw4yXUrKm6zS", - "totalMinedBlocks": 108, - "totalRewards": "1020064003501866" - }, - { - "id": "qzpwecobeg67qddtpgwXftC2WEHYigUrYjG8YQyeXJFdG1nz5", - "totalMinedBlocks": 107, - "totalRewards": "1020053336251555" - }, - { - "id": "qzmMkBGg5Pkfc8dYPFMUq5WFQge2K9tCDw7FzYjxNHP9LPz8d", - "totalMinedBlocks": 107, - "totalRewards": "1030042908969407" - }, - { - "id": "qzq48k3inPqJbwXQVx5QUh2psZSHCg69BnedL3YZ2B6nUSzGz", - "totalMinedBlocks": 107, - "totalRewards": "1010074670752177" - }, - { - "id": "qzknghmWM1bSU2eBJQSCX8WiiPKXQtQw5xWzoxYEYYtPS4Mk9", - "totalMinedBlocks": 107, - "totalRewards": "1020053336251555" - }, - { - "id": "qzmL6e9Xsc6XMPYkDBcjEYhCwMSriCP4ZZM8oAwZai4BsRhTE", - "totalMinedBlocks": 106, - "totalRewards": "1010053336251555" - }, - { - "id": "qzm7CMTimgiasJbGUehtJtpnEVedyouadMukATzNkEL4QEdLh", - "totalMinedBlocks": 105, - "totalRewards": "1020042669001244" - }, - { - "id": "qzktDXj9r4XrUVQy57F4SSBbUXMTc9FXu9s2SgTXhgcUAHjpB", - "totalMinedBlocks": 104, - "totalRewards": "990074910720340" - }, - { - "id": "qzkSpKUTsBWpgarLRfiih3VMqZaKG1Bs5R64MmwsuTPuDaezo", - "totalMinedBlocks": 103, - "totalRewards": "990042669001244" - }, - { - "id": "qzoLfnGmBXg2ZhG9nRu3DXyA6c2oTLE24PEP3tmZSUe6itWSw", - "totalMinedBlocks": 97, - "totalRewards": "970000000000000" - }, - { - "id": "qzpr8TAhvjwVpNQybE55areXndS8cLMGxMgTphQrc7oMHf7GK", - "totalMinedBlocks": 95, - "totalRewards": "910064003501866" - }, - { - "id": "qzjVnNEPrLBjq3TXTnsRApspbUnWV519Fwb1jg7mAtdrT61Kn", - "totalMinedBlocks": 94, - "totalRewards": "930010667250309" - }, - { - "id": "qzjmaa96nMaqg4T8NMUkzbCAdyoEHALftf6m5piB6La8AEyyu", - "totalMinedBlocks": 93, - "totalRewards": "920010667250309" - }, - { - "id": "qznm1xFMwnNmUZEyBYRnizaZ8HqMjHZ7EnZnbZvtD1ZZFB7Jm", - "totalMinedBlocks": 90, - "totalRewards": "900000000000000" - }, - { - "id": "qznVgCeyGJvcoGRkXUgxsq7hJpEbcFNQuZrc5Mrtg56hpBNDT", - "totalMinedBlocks": 88, - "totalRewards": "830053336251555" - }, - { - "id": "qzpisbjK5jJvxmcmPkoYUw1FWdpwFyy1WagoUi7WLQP29hLn8", - "totalMinedBlocks": 82, - "totalRewards": "750075091739938" - }, - { - "id": "qzjvv48q9jHVn9syMCmUtNXeZPsuWFkbbYme2gP3poiySG2YV", - "totalMinedBlocks": 78, - "totalRewards": "750022647905228" - }, - { - "id": "qzpqnnSG2ajZKJXYu2DgXY8xgadHgr2mTgiBZSfhfTrbdZ5ho", - "totalMinedBlocks": 76, - "totalRewards": "750010667250311" - }, - { - "id": "qzmhM5v7P3GiAs6psjSbvu2UcQ1R8Hn85wAzE4Lxtbew53oSV", - "totalMinedBlocks": 75, - "totalRewards": "730021334500622" - }, - { - "id": "qzkM4mC554a2g82fUEp9DtmAHwf46YCv4dA7u6nMg6wA4BKgf", - "totalMinedBlocks": 74, - "totalRewards": "720021334500622" - }, - { - "id": "qzpH8xWWXYfVxVt3a28PuzgrPXxKH8hCgoPm23i5eoemcJrjE", - "totalMinedBlocks": 73, - "totalRewards": "730000000000000" - }, - { - "id": "qzjkCBdLdjxtkMh2rHhqQcY9k9v8TygcnfQcJAeczYvfhoa1m", - "totalMinedBlocks": 73, - "totalRewards": "720010667250311" - }, - { - "id": "qzmX4NAJ7vuQa9Z54yfyXrzPceUNQarLbJaCsQBdreUcPmer3", - "totalMinedBlocks": 71, - "totalRewards": "680032603758298" - }, - { - "id": "qzoSomx616M6SNWx4ws3pZka7WQ1Q7FfSg8JYqFz2oPH8ifoG", - "totalMinedBlocks": 71, - "totalRewards": "650074670752163" - }, - { - "id": "qzky2MecccS6zrpxEAgtUTsvaS9StH8Nj7QhCq2P7vuydtYCP", - "totalMinedBlocks": 70, - "totalRewards": "650053336251555" - }, - { - "id": "qzmYBoaaz26weRiYfYPm3qjaemVxq9rJrcpdZQV8rmpUV55AZ", - "totalMinedBlocks": 68, - "totalRewards": "650042669001244" - }, - { - "id": "qznvi1ZszyuAifiTVz91L5LsDAsCnBQ9XUcpUpqAQ3PV7Fjs1", - "totalMinedBlocks": 66, - "totalRewards": "650010667250311" - }, - { - "id": "qzooDs3ZYEVkhNw7hRp7gYDPBpzX46yqdo2xposqd2v6xD6yF", - "totalMinedBlocks": 65, - "totalRewards": "640010667250311" - }, - { - "id": "qzm3SnHXf693tmr6rUzmNJgV25yygczRpVfm1BvTYZebVCAJd", - "totalMinedBlocks": 64, - "totalRewards": "610032603758301" - }, - { - "id": "qzmHZ8cndFnX53jMM3dxoeFWvf17MeU8Fg8u4aQUxM4Dka689", - "totalMinedBlocks": 59, - "totalRewards": "580010667250311" - }, - { - "id": "qzmzjVzAe7foBDmDS26LR3vyxP8pzHKogRxYZwyUN61ZWZkcM", - "totalMinedBlocks": 59, - "totalRewards": "590000000000000" - }, - { - "id": "qzkso2u15i1SvK3JqKw7QmFsRh5JCQZjoBjSvgsiDVgM5QNzY", - "totalMinedBlocks": 58, - "totalRewards": "570010667250311" - }, - { - "id": "qznUKnV1ujA9NMXdHMnQ8Hpo4hz7t9o4a2NyHsLnYX1tPCazy", - "totalMinedBlocks": 57, - "totalRewards": "560010667250311" - }, - { - "id": "qzmNaLjPU7hcvkjHpGmrVDPD9y12vdAFimCSrP1GkhVFJMaUq", - "totalMinedBlocks": 56, - "totalRewards": "530032001750927" - }, - { - "id": "qznViVkAhoBgCfyov5CjvV6nBgTZq4v6fEPkAcXZbKeFgU5y5", - "totalMinedBlocks": 54, - "totalRewards": "520021334500622" - }, - { - "id": "qzkeXrD69KNvKjQoi8fDyvdZgD89K3mGH5jJPSmxtVMAZesy5", - "totalMinedBlocks": 53, - "totalRewards": "510021334500622" - }, - { - "id": "qzkCLxcwHK5nwTA75gT6PUk4iZnBBrvMyihDGGNdeZSd6Dt3M", - "totalMinedBlocks": 53, - "totalRewards": "510032001750933" - }, - { - "id": "qznMDpa9dcRH1FQT5xQPGsVus9eGDUTK15ZBnLHjnJw7T2UUQ", - "totalMinedBlocks": 51, - "totalRewards": "480032001750933" - }, - { - "id": "qzq8TiLA49RXi1JpoUbqmGsCR3P2yuyg2AXgLaybFdqP7vSXG", - "totalMinedBlocks": 47, - "totalRewards": "460010667250311" - }, - { - "id": "qznSj35WSE658tqovSS3ogDfyM2sZtRcJQUndj7y5m7dqEJCj", - "totalMinedBlocks": 46, - "totalRewards": "430032001750933" - }, - { - "id": "qzkHaFAwxn5oCbTEHXyJXgKQubPFJk4r3S52mSkATeXBtE8xY", - "totalMinedBlocks": 45, - "totalRewards": "430021334500618" - }, - { - "id": "qzo7F79w2iUmJ3TJu1A5SVMshSa83gLzxVBPnCS5Jc8m2J5QU", - "totalMinedBlocks": 45, - "totalRewards": "440010667250311" - }, - { - "id": "qzn1a6f3ttc3NzkXLgKUJ1DD1GmVQ7W7C4bpjLaqDG8cVG3Jb", - "totalMinedBlocks": 43, - "totalRewards": "430000000000000" - }, - { - "id": "qzp9xuqd7i2QqFSwQQziL65eJyvjWYyZMwCEes4uuRNkRVBKi", - "totalMinedBlocks": 43, - "totalRewards": "420010667250311" - }, - { - "id": "qzqEXTmaSg3PayZZFBCFfqrkPCTu4SM9B2gBmsUVpWWGZc8To", - "totalMinedBlocks": 41, - "totalRewards": "400010667250309" - }, - { - "id": "qzka7HSgtxVAKvmVG4keyW9iEzbX6hKtAAuY4ZCVMMBKXTgQq", - "totalMinedBlocks": 41, - "totalRewards": "400010667250311" - }, - { - "id": "qzmHMJjbzJFMKRZJ4TXmT8tpUmouybFhKmAdMg6hR6pp9JtLx", - "totalMinedBlocks": 39, - "totalRewards": "360032001750933" - }, - { - "id": "qzjhjRKqFpPW6vGtBboTcby1kDYqPF29jXotNxhyxJ4dMMWsr", - "totalMinedBlocks": 39, - "totalRewards": "360032001750933" - }, - { - "id": "qzozKd2Y8Lpk5hDTFXHjDJaaQvAt6vpXHdephiNaNSA6yfR9T", - "totalMinedBlocks": 36, - "totalRewards": "350010667250309" - }, - { - "id": "qzmj1Fm3gPdyYTYYki4Th3LTKJTdYf8JyknpPGf6HNtxm5wS5", - "totalMinedBlocks": 35, - "totalRewards": "330021334500622" - }, - { - "id": "qzownhvXskhkRG37Ssyy3SjW18G5DZB4bwqdqMCQLfuqt7M4z", - "totalMinedBlocks": 34, - "totalRewards": "340000000000000" - }, - { - "id": "qzkn3j8b5SgdAQ6U7ES5fakASghRhbaM2XJeE425fPa4kxUFT", - "totalMinedBlocks": 34, - "totalRewards": "320021334500622" - }, - { - "id": "qzo86VbsZSBEuhV7Aaxh9rnZSEPkRmVPNG48vyiFRAeHZufeE", - "totalMinedBlocks": 34, - "totalRewards": "330021574468785" - }, - { - "id": "qzn1Xp6wRpfzbAEocdrDKTSdofmYVp49ZMfj4aQPjTiSu3Fih", - "totalMinedBlocks": 33, - "totalRewards": "300032001750933" - }, - { - "id": "qzkqB3NhqNMesamnhFfdxr3kHyUY5tPPGcs6fT8m36ChoBxHj", - "totalMinedBlocks": 32, - "totalRewards": "290042669001244" - }, - { - "id": "qzq3YbsdtfKthY7wX3MYTBEu425Z9KkMaXNFEERYRYAvD4hqq", - "totalMinedBlocks": 31, - "totalRewards": "300010667250311" - }, - { - "id": "qzkAQ3V6M3MfCU9WAZUTfQCMMPaFpMiAdGFbo5RK6BpWP4zxT", - "totalMinedBlocks": 30, - "totalRewards": "300000000000000" - }, - { - "id": "qzjdDe4BCKLm81xRyKcQT61z3hxLbZDhZ2e1NozerGtqMfYLy", - "totalMinedBlocks": 30, - "totalRewards": "290010667250311" - }, - { - "id": "qzpby3fUMgTu2ZLXtHEEfC9xk9xg8fd6xmrzTiYh2o6tiAbJW", - "totalMinedBlocks": 30, - "totalRewards": "280053336251545" - }, - { - "id": "qzjhL9ujfiVEAFaSU3JkLnMGKCJpK1G4dNmKMC11kjq36NiTQ", - "totalMinedBlocks": 29, - "totalRewards": "270021334500622" - }, - { - "id": "qzjpNgMDzQLeEtw1wA51LHCik8UsxmrEMERSCqc7pRVWTkmjM", - "totalMinedBlocks": 28, - "totalRewards": "260011292395615" - }, - { - "id": "qzovQrRgRg29FYyh6aCjK1cHgjxiSQMwED6RjyLcHXnoHC45E", - "totalMinedBlocks": 28, - "totalRewards": "270010667250309" - }, - { - "id": "qzjnSoYj7zroEVXam3H3n88mCSNWpkb1CPcnXRzXoNgcqeMn4", - "totalMinedBlocks": 28, - "totalRewards": "260021334500622" - }, - { - "id": "qzn2CBXmMorWepWR6DVtpjXE1KwH21nuuoyHUExJEBptQ3k5A", - "totalMinedBlocks": 27, - "totalRewards": "270000000000000" - }, - { - "id": "qzo3AqSb1WhQZiibbofB3PEhwyMQYMEki5N6zUgrsZCNp4kx7", - "totalMinedBlocks": 26, - "totalRewards": "260000000000000" - }, - { - "id": "qzmUxhfxGVy3eJVn4du2KYXHpwNg34dUcYnGvLVSC4nixKJJh", - "totalMinedBlocks": 26, - "totalRewards": "250010667250311" - }, - { - "id": "qzpqK52fRQZWxPcwKjaVMwtLc9zh5H5fMhTk81ygfVSPKjHN3", - "totalMinedBlocks": 26, - "totalRewards": "240021334500622" - }, - { - "id": "qzndHPkVwAPYj3QdWFjmmTdTbeojXyLwN62TW3ddWtMH5kpHj", - "totalMinedBlocks": 24, - "totalRewards": "220021334500622" - }, - { - "id": "qzo9Z1Eq7AXjy8nCNSBwwt7jCh2Dwx3YAMkeJ118BF4aq6wqo", - "totalMinedBlocks": 24, - "totalRewards": "220032001750927" - }, - { - "id": "qzoXJVd83djk2qVMUM9FEwatEcauk2gy7fibcLouGmKnxJMph", - "totalMinedBlocks": 23, - "totalRewards": "220010667250311" - }, - { - "id": "qzpyUUR4izgVZnJuQvF36yKfbSN4No25FLrukSAfxAUYGE95c", - "totalMinedBlocks": 23, - "totalRewards": "200032001750927" - }, - { - "id": "qznKqLKZJrenFFr2xyg12fPwUSwNPgiLfnAoZUHoQwtE7YqHS", - "totalMinedBlocks": 23, - "totalRewards": "200042669001244" - }, - { - "id": "qzkhjMrw8SUw4GPMC3o4NXsb9SiCwjQMnFyCLtBTwn8o9FV6g", - "totalMinedBlocks": 22, - "totalRewards": "220000000000000" - }, - { - "id": "qzpeEaDx2bLz49jgzpfYxWwYwVpGLWmWP767AfHmx3xU8ZqWy", - "totalMinedBlocks": 22, - "totalRewards": "190022680645943" - }, - { - "id": "qznDdf8grx5u7B85oreVY1BTu3BD2Cm6J4d412eZsEUNZD98J", - "totalMinedBlocks": 21, - "totalRewards": "210000000000000" - }, - { - "id": "qzpBbBTvpYRRQndz76mwQS1RrPU9qFTzGotZ5nyhzMhyh3wgh", - "totalMinedBlocks": 21, - "totalRewards": "200010667250309" - }, - { - "id": "qzmkx9JNgxkDm2oB4GBEvWH9Kh9RhCv6cri6URXgxKrwG9z5E", - "totalMinedBlocks": 21, - "totalRewards": "210000000000000" - }, - { - "id": "qzpHxBPuCB5xvCwx1ZMb6RsynTPMBSezhhdYvESBU69Fxit1c", - "totalMinedBlocks": 20, - "totalRewards": "190010667250309" - }, - { - "id": "qznRiMp9942wpgRTqKWpVifye4R1s4uzXdWcTb5Yh4uBbC8kx", - "totalMinedBlocks": 20, - "totalRewards": "200000000000000" - }, - { - "id": "qzk6n5Y8mfzTTX62YK2iXW6XKJ56b6Vm4eJEPUK5v94vyrzSV", - "totalMinedBlocks": 20, - "totalRewards": "180021334500622" - }, - { - "id": "qzoFduCWiF3t8MCiqb2N16NRUEvBxvQw3HMbPzgWaRQPvVZsD", - "totalMinedBlocks": 20, - "totalRewards": "200000000000000" - }, - { - "id": "qzoj91JQYor112B1YyteJumyXzLViw5HriZyqitHhGN5oKETN", - "totalMinedBlocks": 19, - "totalRewards": "190000000000000" - }, - { - "id": "qzkanfY4hsTWqR42JS7R5e3WPHrsRQxZhtrarpGUUGugZBd3x", - "totalMinedBlocks": 19, - "totalRewards": "180010667250311" - }, - { - "id": "qzje8ZWLZD6wvpQWE9rW6ggXbbvL5zn3mT2eyEFJkWPQTwnvu", - "totalMinedBlocks": 19, - "totalRewards": "190000000000000" - }, - { - "id": "qzjkhmhCQgp3R7ykyx24TzJThSE7C6nzDASBK7KQ5ZnZNXHyG", - "totalMinedBlocks": 19, - "totalRewards": "190000000000000" - }, - { - "id": "qzkjUKnQAjfwQBcnKrZjA3gKdiPnm6pT75JQ2DCybKhNFNcbk", - "totalMinedBlocks": 18, - "totalRewards": "180000000000000" - }, - { - "id": "qzokF9ygQhxm39rrZXW633shdMyVu6ctBnEpnuBpPThMCR19C", - "totalMinedBlocks": 18, - "totalRewards": "180000000000000" - }, - { - "id": "qzo5QiGmaMUUYY5oTL3MXEmRLrXjhstkYRVtqz7PDVy2EoCfq", - "totalMinedBlocks": 17, - "totalRewards": "170000000000000" - }, - { - "id": "qzmag6LzBdDuMQx1TCYPFVb4ibif8VgbgHcj5m7HsnAybyzbH", - "totalMinedBlocks": 17, - "totalRewards": "150021334500622" - }, - { - "id": "qznwNY4H4nDnjL3ASfd4PMf4ZgPc4Cnz4uhzxpePbUncti3Bx", - "totalMinedBlocks": 16, - "totalRewards": "150010667250311" - }, - { - "id": "qzpmE8dUhPKcmAns6jH1CDNQauoRd8qMJpcVZYcSvbmeAG45W", - "totalMinedBlocks": 15, - "totalRewards": "150000000000000" - }, - { - "id": "qzmFGkXnQLedPFMFE8Cc6sMoqx5c67dv1pcdWxvHR7bbrmRun", - "totalMinedBlocks": 15, - "totalRewards": "150000000000000" - }, - { - "id": "qzkvVQpXZE4MvfEcHwDp7Z83UdjNpELZShqC65aSpiCoMFkad", - "totalMinedBlocks": 14, - "totalRewards": "140000000000000" - }, - { - "id": "qzkQ1rmDvcjizT2SnLnB24VTRy6NoMWWyFunUEffmKfXbBggV", - "totalMinedBlocks": 14, - "totalRewards": "130010667250311" - }, - { - "id": "qznest3Dw31BVx1K454YnZz2MXmzX3tDXXxwNiimNrHwpB1ed", - "totalMinedBlocks": 14, - "totalRewards": "140000000000000" - }, - { - "id": "qzpf41SAD6EpYRAPrdPp6xAnAJjmwyNUQjN3WwKS3RbkDVZA7", - "totalMinedBlocks": 14, - "totalRewards": "130010667250311" - }, - { - "id": "qzqELNV36vRkd6WLKEk29tJgzt9PWYs8suv97dkqqeQGugBoG", - "totalMinedBlocks": 13, - "totalRewards": "130000000000000" - }, - { - "id": "qzjdEb8W8bSvK2pdy44rYvht2oyMvwvnpdRLS25RUtCSiD4DT", - "totalMinedBlocks": 13, - "totalRewards": "130000000000000" - }, - { - "id": "qznxZnexEwtD8ocY6v2qNAB98fPMWrJ6eTDgEYQVQP7BPCWH2", - "totalMinedBlocks": 12, - "totalRewards": "120000000000000" - }, - { - "id": "qzpPiJ1fDZDAdPmQq2xR54o51wbm9HFzFnpAeyDSBM1QVJ6Vp", - "totalMinedBlocks": 12, - "totalRewards": "110010667250311" - }, - { - "id": "qzjXxp5pqxgRPtokWhq3Q3CaNpFvtRAQvgNuqp3DxsF2UmgWM", - "totalMinedBlocks": 12, - "totalRewards": "120000000000000" - }, - { - "id": "qzpVZUR8b5xtywQR23MBuKPBDRsfRn4whFJNNomw9n4SN3czj", - "totalMinedBlocks": 12, - "totalRewards": "110010667250311" - }, - { - "id": "qzkQomVDZF9EvRreXjaTfAwsoH5x3HnJZEzrZ5a8uVAPNGjnk", - "totalMinedBlocks": 10, - "totalRewards": "100000000000000" - }, - { - "id": "qzpr6axnjhKZq2A3mLUofxn5bYGEkE8T2jnxUDj2mLgJjuF5N", - "totalMinedBlocks": 9, - "totalRewards": "90000000000000" - }, - { - "id": "qzkFRmUrg2MFdhTsSr2isTezePYbuvv8YZSTep1i9mzVRiGVY", - "totalMinedBlocks": 9, - "totalRewards": "90000000000000" - }, - { - "id": "qzkfhFDtDJNLhPSrKNZbqL2TWNPVhx9iM3mwJSztaz9oNSNLT", - "totalMinedBlocks": 9, - "totalRewards": "90000000000000" - }, - { - "id": "qzk4v3k6Zo6t6wrHFgUrPP4JFyPwsTazMBMAvymT8pu8BDieW", - "totalMinedBlocks": 9, - "totalRewards": "90000000000000" - }, - { - "id": "qzjgNjPfg931rE6LNuhj6ChN3yeGRQMfNVxr9hDz1s3ZkfStW", - "totalMinedBlocks": 8, - "totalRewards": "80000000000000" - }, - { - "id": "qzptJkW8LycLN2swTWYZcpSZEJhm7HxkwSoJ2V7wZ9aY8JeBx", - "totalMinedBlocks": 8, - "totalRewards": "80000000000000" - }, - { - "id": "qzojBmWZ7LripgpyTiENmGuX39xKdTCNYgmzhQiHhJFc5qnca", - "totalMinedBlocks": 8, - "totalRewards": "80000000000000" - }, - { - "id": "qzpehC9ZjRUggL9pPpe25bbTTKLiL2LjcnGZznhU41E5a878c", - "totalMinedBlocks": 8, - "totalRewards": "80000000000000" - }, - { - "id": "qzoipFcQwRGzyxj9ydCSvdY2nYVwiwuVoLujie2o2U7J1wPBF", - "totalMinedBlocks": 8, - "totalRewards": "80000000000000" - }, - { - "id": "qzkayX8KN8CEjkUDqTAArut7bH6UgDHLAsXaiMWJEhjcoWGoZ", - "totalMinedBlocks": 8, - "totalRewards": "80000000000000" - }, - { - "id": "qzku31NauuNcF9cX7tZLbVRfBhtXWzmyi9CwZoWSH1bujhpKP", - "totalMinedBlocks": 8, - "totalRewards": "80000000000000" - }, - { - "id": "qzn847Y6b4h1Dff2xbjATrL9aho1bCDWpxrzSbtgyGaAPusET", - "totalMinedBlocks": 8, - "totalRewards": "80000000000000" - }, - { - "id": "qzkPoj8w2qX6MGGBdttAwmsVwRQDrrkSfQcvATLsDW6k2ZkyM", - "totalMinedBlocks": 7, - "totalRewards": "60021334500618" - }, - { - "id": "qzmXmBDQma6WUmr4Sh87s7T59X59L77ciYpfv7K1CidHFxk2H", - "totalMinedBlocks": 7, - "totalRewards": "60010667250311" - }, - { - "id": "qzorXSPdUzCKtP9NMjuZRFvn6eCS1aphgFgf9LeSC6YB2hxBs", - "totalMinedBlocks": 7, - "totalRewards": "70000000000000" - }, - { - "id": "qznAxZLmxAeHNgT5M6RZWjd1vWp299onStfNN5pPXh82TrWRH", - "totalMinedBlocks": 7, - "totalRewards": "70000000000000" - }, - { - "id": "qzmWatjuJuHjNyJ4m8VPJScNf4cyv1hXiWghxq2oydomDg7oR", - "totalMinedBlocks": 7, - "totalRewards": "70000000000000" - }, - { - "id": "qzkdFU7QNXq4rMMHZaT5xD2mUMjZSxuBPi2qDhEY7XoYq5rua", - "totalMinedBlocks": 6, - "totalRewards": "60000000000000" - }, - { - "id": "qzktLW8fzzVLUfmFkvDWFKPH86Eejf6AuKyNmpVxZR91VZjr7", - "totalMinedBlocks": 6, - "totalRewards": "60000000000000" - }, - { - "id": "qznjAswMVC9ASZYuA3KBhU6bRmRMYgF66JrcfbtNYyJRgiTfg", - "totalMinedBlocks": 5, - "totalRewards": "40010667250311" - }, - { - "id": "qzjX4rhtDvic2ZSERVb4bEu1BDSjcz5ucDd4nyfiwzU1qVRd4", - "totalMinedBlocks": 5, - "totalRewards": "40010667250311" - }, - { - "id": "qzoVjftG5NLdEYzCGsa4XTMoYTzaRBmoPq2vBTwf76sQkd82y", - "totalMinedBlocks": 5, - "totalRewards": "40010667250311" - }, - { - "id": "qzkPkfY6vQfmwXto2W8dKqg7RpCLSL5DNT7NjfJNz7iQjwuPg", - "totalMinedBlocks": 5, - "totalRewards": "50000000000000" - }, - { - "id": "qzp8afZ5GXNfbCJeVztWzF568ZMmVKT6G9HPtSD5gVZFdktSV", - "totalMinedBlocks": 4, - "totalRewards": "40000000000000" - }, - { - "id": "qzp5FNZDaGTHgw8RA2k1R1z2Qyccw6hz8z1FnRhEAdf6tPFnv", - "totalMinedBlocks": 4, - "totalRewards": "40000000000000" - }, - { - "id": "qzppUuknLM2VExkeQummtLHpeCsbSvws3Fo4z1LK1BA5m6QmB", - "totalMinedBlocks": 4, - "totalRewards": "30010667250311" - }, - { - "id": "qzpdJRGxfNnby7ue3PcSJZZTiwZVdyXJTRfXgAyv8U3gCRoE9", - "totalMinedBlocks": 3, - "totalRewards": "30000000000000" - }, - { - "id": "qznK378TgU5v6oRai7UgutqV81hGaFoAoXn4DPBfiPcZnM2rz", - "totalMinedBlocks": 3, - "totalRewards": "30000000000000" - }, - { - "id": "qzmXZVNPEkyNoNrj1rewRCPamWQP6YsVM6xt5pzueEEx1SEDC", - "totalMinedBlocks": 3, - "totalRewards": "30000000000000" - }, - { - "id": "qzp378k33doZHi6gvvFCUN4auTEmQgWqrLDZEZrTPXs4opcRo", - "totalMinedBlocks": 3, - "totalRewards": "30000000000000" - }, - { - "id": "qzp4nS2szKTcb31pk7nZeFwL6Qr3GTRcwdMAAZAvbkh1cdVc5", - "totalMinedBlocks": 3, - "totalRewards": "30000000000000" - }, - { - "id": "qzoHKouoRDeno1E1UftGKSVhmjoxZAfJBAUKRHQjnpp7wdBdt", - "totalMinedBlocks": 3, - "totalRewards": "30000000000000" - }, - { - "id": "qzpfmVckguhytKse6VwYXFdpdM4BDgjmp9jB6q3ovbGmUNmVT", - "totalMinedBlocks": 3, - "totalRewards": "30000000000000" - }, - { - "id": "qznQ5i181r87Lw1C8wcp9qRFBEC8CEjhBqYspdSFv12R917NS", - "totalMinedBlocks": 2, - "totalRewards": "20000000000000" - }, - { - "id": "qzkrJDzQuyZ92KTdwG718tQzZUCL4hd1m6ibmHaKox6tnJEXL", - "totalMinedBlocks": 2, - "totalRewards": "20000000000000" - }, - { - "id": "qznscbHurRmmJLVBKn3SgUD3KwXNKZiLFjSsvrjtk1m73c35T", - "totalMinedBlocks": 2, - "totalRewards": "20000000000000" - }, - { - "id": "qzo5LUKwEHe7C66WLen9tcTdRx6rqNkSVyrRWXZGzVNGuQzYH", - "totalMinedBlocks": 2, - "totalRewards": "20000000000000" - }, - { - "id": "qznpoqYmpSrypih4epbyMRp1cFuv7T2pzwqhrc4NccZr8WZB5", - "totalMinedBlocks": 2, - "totalRewards": "20000000000000" - }, - { - "id": "qzq8dkx6Zqg3a6ZN1Pbmrhd1jwow2fNNh6SbWrqYD8M1CMuNB", - "totalMinedBlocks": 2, - "totalRewards": "20000000000000" - }, - { - "id": "qznySY3GQ9dhK5GXjt1jJQN13NRZRLnwW8rgifDNpMsbKESKn", - "totalMinedBlocks": 2, - "totalRewards": "20000000000000" - }, - { - "id": "qzmmSu8dfJ6ww6Rp8iHrN7Q2xBPKS4E6kxM2kdGfozSk4EL8c", - "totalMinedBlocks": 2, - "totalRewards": "20000000000000" - }, - { - "id": "qzpZ457u4UcKAtCHNskiNRyFgX2L3pngm2fL72WqgjWLz8N8z", - "totalMinedBlocks": 2, - "totalRewards": "20000000000000" - }, - { - "id": "qzjUZ9wS8wwLqdiRQW3cn31ZiZRxdD2uqTLrFoCoi3j5FwXom", - "totalMinedBlocks": 2, - "totalRewards": "20000000000000" - }, - { - "id": "qzktZGjX3ALJrEzFiSS8MvZNF3rgFyURLh9eGgFLgNNCWqwYU", - "totalMinedBlocks": 2, - "totalRewards": "20000000000000" - }, - { - "id": "qzkRf4gKmSMXZ1a7K467Fya5XkSv5uhfdt5BoebZSnuRNVFmM", - "totalMinedBlocks": 2, - "totalRewards": "20000000000000" - }, - { - "id": "qzkcPZa7MJHzCHPi8xUbsoM2aDmwmMmzQ7yHRvSoUhw4GoKaK", - "totalMinedBlocks": 2, - "totalRewards": "20000000000000" - }, - { - "id": "qznTeZW9c9ted7HZikNuiM28czaRFJ1QXpSHg2ekxomqKqYwJ", - "totalMinedBlocks": 1, - "totalRewards": "10000000000000" - }, - { - "id": "qzkSEr8NkswvTC8vdpKtdfquH9a1AGnLZhk1v7aK8rnwuhX1o", - "totalMinedBlocks": 1, - "totalRewards": "10000000000000" - }, - { - "id": "qznv5u1NrSWddzWFbFoVFpyBkSwpY3XiQxQHUcT46ueGcdvSN", - "totalMinedBlocks": 1, - "totalRewards": "10000000000000" - }, - { - "id": "qzneQjBF9FuaFEp54xzcasEx65koWny85Ks1HEEhwbwN9SPdu", - "totalMinedBlocks": 1, - "totalRewards": "10000000000000" - }, - { - "id": "qznu5dm3S2yVJHTRxWpezNLa3jnDgYLLESrnxfZnAF7hi1DTm", - "totalMinedBlocks": 1, - "totalRewards": "10000000000000" - }, - { - "id": "qzk366wmwXDNz1XJmcmgfWYd3ZsaTSWShzZdwZC7Er7LgmWih", - "totalMinedBlocks": 1, - "totalRewards": "10000000000000" - }, - { - "id": "qzoayTaF13Y5aC61z5gugijhgiDpz65KxTzvLuyJcMeV5ySM4", - "totalMinedBlocks": 1, - "totalRewards": "10000000000000" - }, - { - "id": "qzomkQ1h8eVx3kKSKnytHSWcavLaBmXKFZVxJAfQiCXXs29PU", - "totalMinedBlocks": 1, - "totalRewards": "10000000000000" - }, - { - "id": "qzp6aPeDG9tMTMWAtougwZVcqd8k8ZDbVgzQegWE9Srzxqyqd", - "totalMinedBlocks": 1, - "totalRewards": "10000000000000" - }, - { - "id": "qzkAV2SXEMJe9um2zH2E4KCf2oqiZjrBZHEpGBKMJ5QAqVboP", - "totalMinedBlocks": 1, - "totalRewards": "10000000000000" - } - ] - } -} \ No newline at end of file diff --git a/mobile-app/assets/testnet_data/resonance_network_miners.json b/mobile-app/assets/testnet_data/resonance_network_miners.json deleted file mode 100644 index c5fadaffc..000000000 --- a/mobile-app/assets/testnet_data/resonance_network_miners.json +++ /dev/null @@ -1,501 +0,0 @@ -{ - "data": { - "minerStats": [ - { - "id": "qzmu2UxAm4JH9CjMLx3yVnPJGrwbq6iJmKq6ZominTXjmSDEN", - "totalMinedBlocks": 53704, - "totalRewards": "535020324406211696" - }, - { - "id": "qzmc7PZeKBhFPF2eUfcGgSqSLafPCjVXW8qTjMpNu7gQB3iTj", - "totalMinedBlocks": 31889, - "totalRewards": "318700028523679757" - }, - { - "id": "qzoJT7eJRBG44EWRuL8MsZGvXKALkzykkfy7AmYYPbGWWNpe6", - "totalMinedBlocks": 27659, - "totalRewards": "276480016513807334" - }, - { - "id": "qznBscoqfgchvYUUYCY6o8f8HsJWNvU9SC3DKMvoLu438Hrmu", - "totalMinedBlocks": 27337, - "totalRewards": "273260016362702351" - }, - { - "id": "qznrsyqc3HEkGy93HxZttNbBtia1nd9UDy5D8HrgfTTqRRnHv", - "totalMinedBlocks": 26204, - "totalRewards": "261450088392576320" - }, - { - "id": "qzq13T533TiW4esoLg7ZRrsiVeGS9k8pKHERiJTAAuj4uJdvJ", - "totalMinedBlocks": 23319, - "totalRewards": "232220164950415339" - }, - { - "id": "qzmre1UiD9iAzP1fHZq3DyXprfDupoVNFDAJRF6edPDmwjEbR", - "totalMinedBlocks": 15792, - "totalRewards": "156040437168817719" - }, - { - "id": "qzoRp2XbwmtP6cb1khkd8gSzp88XLWQnwUHcghX8PtRALSB5U", - "totalMinedBlocks": 15159, - "totalRewards": "150220204958806576" - }, - { - "id": "qzo84V7Ssq7Z3GS1oLVwQ59cByk6bCGvK15EjWrt85AMNqYNt", - "totalMinedBlocks": 11863, - "totalRewards": "118320056585049819" - }, - { - "id": "qzoye6zPEeJz8mxCar3Q1oiN2xpjFf47jvCVoMjjzuFTw9Dsp", - "totalMinedBlocks": 10404, - "totalRewards": "102850177224240185" - }, - { - "id": "qzoQf4od1mzuTa5AT9R8cbEUxQkyEiqGJyKfq8ZWcK9fTLCmh", - "totalMinedBlocks": 9408, - "totalRewards": "92970207760354631" - }, - { - "id": "qznhpVShWsRqhp4g4KVnD82AGdwQo5EVZp9fqdmUAJBLSyCeW", - "totalMinedBlocks": 8412, - "totalRewards": "83861031865799530" - }, - { - "id": "qzn6C9EKUpuNPm5qbe5jSq7uiCHV7ruixuzNM4DQQQGZxe2du", - "totalMinedBlocks": 8035, - "totalRewards": "79460614984327815" - }, - { - "id": "qzprTjCZMGh9P76yJBKWFv5QeAdJ1buqmMtsmCS3uTXpoopWS", - "totalMinedBlocks": 6312, - "totalRewards": "62420183781914608" - }, - { - "id": "qzo8VqpTojHapSvjwdLqze3GebmfAGS4Nq6rTnRsjR2S8u9mA", - "totalMinedBlocks": 6296, - "totalRewards": "62130144250658395" - }, - { - "id": "qzmNCqKCCLk51kY5dHkuMHrK89pdTLWspnxodoVX3ETMGaXUQ", - "totalMinedBlocks": 5751, - "totalRewards": "57180051379054734" - }, - { - "id": "qzjxCJfheVUxLEZto56T6v4cKT5i4PTqHWYssDE6Sfyy6xwcS", - "totalMinedBlocks": 4275, - "totalRewards": "42050764752161292" - }, - { - "id": "qznJ6gdt21ezvLxdYw3iAUjqHEW1FAWMT3U8PtDtnhgPG21nB", - "totalMinedBlocks": 4124, - "totalRewards": "41080023609919877" - }, - { - "id": "qzpJfFKE5ByDBqjcRgYBieKedPrhivsmH13VRUCrdXtsPLdFK", - "totalMinedBlocks": 3527, - "totalRewards": "35140039466249259" - }, - { - "id": "qzpxE7g7vRq8hGxbD7rESVGGvM5Q7CJypTwzvoEoeDEGB2azL", - "totalMinedBlocks": 3503, - "totalRewards": "34850037501164836" - }, - { - "id": "qzotCmuA9SRD5aqWzYnFAeSE9N8FhhhgnKQG4mu4h66kc1YYm", - "totalMinedBlocks": 3122, - "totalRewards": "31120015942525379" - }, - { - "id": "qzkKG6BoZdXG4MYTVEmjAumrSoja2MZ6gFNm9C9e354wmtEsh", - "totalMinedBlocks": 1902, - "totalRewards": "18940011794152407" - }, - { - "id": "qzngmjcJaBFwWQQEHCZP5VUudKGmSS89tXX5WGHK9cGoTZYhA", - "totalMinedBlocks": 1574, - "totalRewards": "15680018705659860" - }, - { - "id": "qzjcfckdrUxQPjR91qEwYgEzu7rYDcfQTXy8gyddAoPnQqs4g", - "totalMinedBlocks": 1545, - "totalRewards": "15380010508724121" - }, - { - "id": "qzm99pG17NMSEF81Vmbt7dohv4rECY2cuNA6sJYhgQ24V6pGk", - "totalMinedBlocks": 1399, - "totalRewards": "13970003002590605" - }, - { - "id": "qzkiLYEGwcobt6ogFEzi3stiSGy16KUPEwTbRLxL4AWr4oZvN", - "totalMinedBlocks": 1310, - "totalRewards": "13040008835864836" - }, - { - "id": "qzomTLVbRsnPgvBiNvLuTHtgQyhEkeePfVjo2zhhBZUKwbUxb", - "totalMinedBlocks": 1031, - "totalRewards": "10280004503738909" - }, - { - "id": "qzjn7bCMVceeJu3hp6cVwKZEJUoPWa3MMCsP53NL1hkYiwSLh", - "totalMinedBlocks": 1024, - "totalRewards": "10110020758728254" - }, - { - "id": "qzpu6CPrW4Mr6zYaeHj35DPybCr2d5DBN5eeq5e5RJ9KPkJWG", - "totalMinedBlocks": 932, - "totalRewards": "9300002851485624" - }, - { - "id": "qzm8CnymCRcPffm1kANqHEJh957AHh35pZUGziy1so2CF6oPy", - "totalMinedBlocks": 922, - "totalRewards": "9140011901963417" - }, - { - "id": "qzpQzhLXucwBenDb28t8mo6eh2CtFuhJTHw41oVWUwq5TNzp8", - "totalMinedBlocks": 781, - "totalRewards": "7790003002492606" - }, - { - "id": "qzozH5LezSn1XQ9k7Ezci6p4xK9J8GcjjeXPG8DwvFRzqKzaf", - "totalMinedBlocks": 756, - "totalRewards": "7550001501246302" - }, - { - "id": "qzoyznFQSeNY5BGvVfUYjwB2s2QSYEDsQjxTWvNHCCq6x98Mt", - "totalMinedBlocks": 678, - "totalRewards": "6750004352731927" - }, - { - "id": "qzmZjkjgi34cba1892dthL38R3JGn9aVEJsDwnbSSwuEs338v", - "totalMinedBlocks": 670, - "totalRewards": "6680003002688607" - }, - { - "id": "qznJvq82Ggw3WrF9jMvc7FJoefUGgYuf6isztg9o6dZSFHd6Y", - "totalMinedBlocks": 631, - "totalRewards": "6300001393239295" - }, - { - "id": "qzpVVgF1MDnTpK1h1QrcwKD4raFrVuC3GKApKqkr5TWQFBVaW", - "totalMinedBlocks": 621, - "totalRewards": "6080019516201939" - }, - { - "id": "qzqCwUhocvzFG2jPFGvnAve5o5CHmsRfTaxszuxqvyhapQrCE", - "totalMinedBlocks": 570, - "totalRewards": "5690001501246303" - }, - { - "id": "qznd1YWbgQrviV76psu5n8d24mHSuHtAc9JmJLB42gTELksvQ", - "totalMinedBlocks": 511, - "totalRewards": "5110000000000000" - }, - { - "id": "qzmjyKbuq7vzwSAu23BhSwTzLe3DDfjWK56Q4EfnFmeuV6rw2", - "totalMinedBlocks": 426, - "totalRewards": "4220005552062266" - }, - { - "id": "qzmzbkwmcPzNnGrTxUdWw2wqKK6AwEdwTfeEXbsLd64NApyDR", - "totalMinedBlocks": 424, - "totalRewards": "4230001501344302" - }, - { - "id": "qzokF78eWPn8WbzmkMpWRYr9ZYxtsDKvLtxQMyDrjcqjjMqng", - "totalMinedBlocks": 386, - "totalRewards": "3820006004985212" - }, - { - "id": "qzkM2uGCcpMBu9WsFRbTJWAv2RoJr1BNoUEzNauhFTQD57iYg", - "totalMinedBlocks": 378, - "totalRewards": "3780000000000000" - }, - { - "id": "qznxQ9gQ7ryCusigg8ymTo6PW6SM99NF9zZVhCXEabgWCb5ze", - "totalMinedBlocks": 360, - "totalRewards": "3600000000000000" - }, - { - "id": "qzoAS8uWhKUMbJG7mJfwBScAXhkG8SUpcBGE4nRP634w6d5cY", - "totalMinedBlocks": 332, - "totalRewards": "3290004503836908" - }, - { - "id": "qzp84JYcwf5vhAK6nKAQ1AEqXAb71i1qm7QYBLd4xzwuaoKkN", - "totalMinedBlocks": 332, - "totalRewards": "3310001501246303" - }, - { - "id": "qzmb7zkTNRg3koUdcAuJxaUREm7T6dQwxm6oBqX2kZjPzfLsE", - "totalMinedBlocks": 276, - "totalRewards": "2740002851583624" - }, - { - "id": "qzmiN6K6FoYzuroPKzfr8bLTbZBpHfmnsGm3EXNEAdPD57qon", - "totalMinedBlocks": 230, - "totalRewards": "2280002851485624" - }, - { - "id": "qzmYASZdMiitqjY4DzeaTg4pKh4GCg63VF7oUthGMEEq1BejD", - "totalMinedBlocks": 209, - "totalRewards": "2090000000000000" - }, - { - "id": "qzmHLozQpHFMxuqv7wJFvzres4LfNSJQ6xKU3vRFAttFw7EkY", - "totalMinedBlocks": 169, - "totalRewards": "1650005703069248" - }, - { - "id": "qzjxpUHFK2uS9VMwQduyAVMAduWQviJ2ACbeLHKc8yhQFZYTD", - "totalMinedBlocks": 167, - "totalRewards": "1660001501246303" - }, - { - "id": "qzjYjaTQZSz41QYRyf58LtR1RSqktkwwQdvDbb5d2kDG7HNMv", - "totalMinedBlocks": 153, - "totalRewards": "1530000000000000" - }, - { - "id": "qzn4BajdypPBSoGYCKJvMV1w8ggUgQvhekay1hkiTpiYWjtD1", - "totalMinedBlocks": 132, - "totalRewards": "1320000000000000" - }, - { - "id": "qzqAEqE34ZYg7sC5b7Y5kKaRkcYhYDSGfgBtBjTJeizuy3wBH", - "totalMinedBlocks": 106, - "totalRewards": "1040003002590606" - }, - { - "id": "qzmZkaiQKnfSAPkn8dekCFWisHtCCuZTYWaZmUD87t126ehLW", - "totalMinedBlocks": 97, - "totalRewards": "920007204315551" - }, - { - "id": "qzjfz14hQTAcv4ErDgKuhYbUdXGqvN5gYCARwvA5hKQzM4nJZ", - "totalMinedBlocks": 95, - "totalRewards": "930003002492606" - }, - { - "id": "qzmB2SKjunKRcQzYzecf4xbFjRHWpc6euWBj6qVCqMi1quEPm", - "totalMinedBlocks": 80, - "totalRewards": "790001501344304" - }, - { - "id": "qzkgaH2uFc9n1ZMHGZ2NxELBeJxp62tktsnX2s6LDZxjtSiSC", - "totalMinedBlocks": 75, - "totalRewards": "750000000000000" - }, - { - "id": "qzpsMcVZLViR2Zr3RywU6wPPQrz5CU2R92buHNatuaqyMRran", - "totalMinedBlocks": 65, - "totalRewards": "650000000000000" - }, - { - "id": "qzpkgwF83WrNb2rM7WXUTG6V5SZLGUzN4QLVXi2dDcuuBsxYU", - "totalMinedBlocks": 61, - "totalRewards": "600001501344303" - }, - { - "id": "qzmRxHJSv5v8U9AjhcKQw6snjmLY2ZaJ5Bxr72iYYXWi1xT9e", - "totalMinedBlocks": 51, - "totalRewards": "510000000000000" - }, - { - "id": "qzjvS5v1YYZ18f3SfeMrjMBUJgRTn48fzcLdPst94J4Q2J7hz", - "totalMinedBlocks": 49, - "totalRewards": "490000000000000" - }, - { - "id": "qzjWn2JcqBs4ttYWrtmZFS4jpkXgfa876TRrA1uscyU6YeLwT", - "totalMinedBlocks": 46, - "totalRewards": "450001501344302" - }, - { - "id": "qzmimGidKhhszaVeVhnNufcKQQU1QTieya1AzL8xSMHNAY7G4", - "totalMinedBlocks": 38, - "totalRewards": "380000000000000" - }, - { - "id": "qzmrdGsRW6WpEr8iHgdhxcYo7FvPrU9mH9WJf8qZ2VUZHDYHJ", - "totalMinedBlocks": 37, - "totalRewards": "370000000000000" - }, - { - "id": "qzniXds4ZVN66LfKrrqQmrNLe2S8UraqiwiF1F5x7CykpeLFy", - "totalMinedBlocks": 32, - "totalRewards": "320000000000000" - }, - { - "id": "qzo7xakq5DVCNdUTudPgAR71W1onYSjQuStoNJnKev6FwxT1L", - "totalMinedBlocks": 32, - "totalRewards": "320000000000000" - }, - { - "id": "qzka5PoygCZ6U8XH71hnGi3sUeqZya3BvJUALbAT3zSKoykiz", - "totalMinedBlocks": 32, - "totalRewards": "320000000000000" - }, - { - "id": "qznhaQE7tJZtrMhAKWXbzrRnskfakNBNYgVoMVYc8mNkHCSMk", - "totalMinedBlocks": 28, - "totalRewards": "280000000000000" - }, - { - "id": "qzpAK583igoJbAFGjVKMF2GW8piywbkQDSDYXbZ2mnmjj1WPs", - "totalMinedBlocks": 21, - "totalRewards": "210000000000000" - }, - { - "id": "qzn6iLjTGrSwHsafAK7pRdcLnz3FR9nivW6sz2D9BscxMnpok", - "totalMinedBlocks": 19, - "totalRewards": "190000000000000" - }, - { - "id": "qzkZytUkY41CCJ38epkZ8ekAvbqrqibLTWxWy7FHAMvJTB9Ei", - "totalMinedBlocks": 19, - "totalRewards": "190000000000000" - }, - { - "id": "qzoBUt2hhkYTv4q5kwWApstheRqrDxfuzZpgte9W6L4h4GpmL", - "totalMinedBlocks": 16, - "totalRewards": "160000000000000" - }, - { - "id": "qzoz5SYKo5BJbeQSNqWHEiNJ23XTs5qNqxVyC8Ua4tx56F7Rn", - "totalMinedBlocks": 12, - "totalRewards": "110001501246303" - }, - { - "id": "qznQ7MPYioe2ohtPN6EWpKa89UAt8bZ8SHwjyAMe51SCXZK4n", - "totalMinedBlocks": 12, - "totalRewards": "120000000000000" - }, - { - "id": "qzoXUT2nF4kRShWquywxay57SQwMr9n78aUn2j8Eg1NeW3rDe", - "totalMinedBlocks": 11, - "totalRewards": "70008748659826" - }, - { - "id": "qzmqfu2zk1ef6GeFnS8aNHvYuFhi62YXXt86nunYNxj433pv1", - "totalMinedBlocks": 10, - "totalRewards": "90001350239321" - }, - { - "id": "qznwx81CwCNomZy8jC8M95kfEtxCv9oosUhmD2BbyLKMCspN5", - "totalMinedBlocks": 9, - "totalRewards": "90000000000000" - }, - { - "id": "qzo6KH3v6tujBYcmZ39PfEzA8Q2qX1j9r4u5b5L7yse7k7pJs", - "totalMinedBlocks": 9, - "totalRewards": "90000000000000" - }, - { - "id": "qzmBAHgFaqHMCrSEPtJg7PcnvAjjyZmcCzPfTuQJqTKSsgETp", - "totalMinedBlocks": 8, - "totalRewards": "70001501246303" - }, - { - "id": "qzng3GPNkC9aq7qQhBKNEAnjdtq6QWQDVpRmXvbkU6eRyhT3Z", - "totalMinedBlocks": 7, - "totalRewards": "50004244724919" - }, - { - "id": "qzk4s4DdWzQMjaYg2p1cAtjPJxQQW1cWKFXjo1rae4xWR2pfd", - "totalMinedBlocks": 7, - "totalRewards": "70000000000000" - }, - { - "id": "qzjjifC2KMrYMFVjUoAwjvURRNoudZFMD9XCbfRvGeyK5dDiH", - "totalMinedBlocks": 5, - "totalRewards": "50000000000000" - }, - { - "id": "qzp5d9h4Cjv6kBFQ5HXnHXVqSCVceMpXkp1E1qvRayo344Yi4", - "totalMinedBlocks": 5, - "totalRewards": "50000000000000" - }, - { - "id": "qzjh6EaaA3PEQZF6HRmLrxQRDA4FDJweP5wYEYTQjWx7rWN5v", - "totalMinedBlocks": 5, - "totalRewards": "50000000000000" - }, - { - "id": "qzpzyGesbhY7G5n1pXq1mhSc1qs5K23oBLzHST4U85zPw9P7N", - "totalMinedBlocks": 5, - "totalRewards": "50000000000000" - }, - { - "id": "qzmBB9bheoaW5AkruaEhVSPRAgxnu9Wfq4FwFGnJnTLHt9a7b", - "totalMinedBlocks": 5, - "totalRewards": "50000000000000" - }, - { - "id": "qzo7wGwtVYVeZ9LEgiA94gH3JbWfgr1ZqDQdEuoR9iN9k3vm7", - "totalMinedBlocks": 5, - "totalRewards": "50000000000000" - }, - { - "id": "qznKMfamaZcnqumUWYyRaNSCWkryPk1GMC5NtTrzwqwAwa7cZ", - "totalMinedBlocks": 4, - "totalRewards": "40000000000000" - }, - { - "id": "qzm21sP93FbrYCtTp1LrQDfs6quxBebPg495AZKa1ZRRfnszh", - "totalMinedBlocks": 4, - "totalRewards": "40000000000000" - }, - { - "id": "qzoEVkv7YwtA1FfqgTVi3shAeXwF4bCZm6jxYMkvjjDjhsG2y", - "totalMinedBlocks": 3, - "totalRewards": "30000000000000" - }, - { - "id": "qzmrw5DXoBE6LBvXGRRTdAHobGBTXGjitLw6wCXEuQE8a3fgi", - "totalMinedBlocks": 3, - "totalRewards": "30000000000000" - }, - { - "id": "qzpAb6dsFo2UXREMEvFL3ZLFmi5WoeJEq28m3HDVwsfBrzviR", - "totalMinedBlocks": 3, - "totalRewards": "30000000000000" - }, - { - "id": "qzpj4w2G2jmVGTb5UuiZ6TVvJtaQpJsbQ14o4AEUHt6vjJrQo", - "totalMinedBlocks": 2, - "totalRewards": "20000000000000" - }, - { - "id": "qzm84pWDU2AtnVTHpVDC7mD2qTRxC3Q1zYuYDEEHTyW1sjjSF", - "totalMinedBlocks": 2, - "totalRewards": "20000000000000" - }, - { - "id": "qzq3VLu6DHcWDtWRAqWXtTkDMPqz4BdJNvy3e7SYaqhZX49PQ", - "totalMinedBlocks": 2, - "totalRewards": "20000000000000" - }, - { - "id": "qzmGYbF9Qm3yykU1MR3nXQDyhHaS8SnXwk8Fd2y8UkM2aWTbG", - "totalMinedBlocks": 1, - "totalRewards": "10000000000000" - }, - { - "id": "qzjWXQK7tV1zVxW3XaKNsK5HdJUteLXZuGC2o3i2X3kVJ2zqJ", - "totalMinedBlocks": 1, - "totalRewards": "10000000000000" - }, - { - "id": "qzpXEJTTtSvWMqKVLhfwVXW9qkXEftZBdzz92hf5QnxVvtqV1", - "totalMinedBlocks": 1, - "totalRewards": "10000000000000" - }, - { - "id": "qzpjuAv3XbYjiW8Lw6RjxCFwkZzypr5TaMLvkQfuNVEigTAns", - "totalMinedBlocks": 1, - "totalRewards": "10000000000000" - } - ] - } -} \ No newline at end of file diff --git a/mobile-app/assets/testnet_data/schrodinger_miners.json b/mobile-app/assets/testnet_data/schrodinger_miners.json deleted file mode 100644 index 34b472d93..000000000 --- a/mobile-app/assets/testnet_data/schrodinger_miners.json +++ /dev/null @@ -1,416 +0,0 @@ -{ - "data": { - "minerStats": [ - { - "id": "qzpShigRwEfoqWefYkCxCyHyyNMFV2rZtq7zgLQoN5JbwbWhE", - "totalMinedBlocks": 32585, - "totalRewards": "324711129504010233" - }, - { - "id": "qzn1a6f3ttc3NzkXLgKUJ1DD1GmVQ7W7C4bpjLaqDG8cVG3Jb", - "totalMinedBlocks": 23273, - "totalRewards": "231481326765829563" - }, - { - "id": "qzot2brxs5s468XbYh9D3NoYxpmnp8xkmWzibsqBPrFmqC6FJ", - "totalMinedBlocks": 21309, - "totalRewards": "212690416690012353" - }, - { - "id": "qzjuqy6MyJRV9fW87fb34VYXYhyEMqyaEtiWNnf3g9yoZdfmP", - "totalMinedBlocks": 18043, - "totalRewards": "178771571041042176" - }, - { - "id": "qznFyQNkVEqWM13sEq9E4qjT75s6Rfazi9Jq1pNaPpciJvyde", - "totalMinedBlocks": 17291, - "totalRewards": "172360540124597307" - }, - { - "id": "qznbFq2GaFvRC45d67FcU2e86SRu32ks6bqBfo4ofwjJUNR2d", - "totalMinedBlocks": 14616, - "totalRewards": "145580589379412919" - }, - { - "id": "qzoCszTr12UqAQUPmJU4UuVf98cpkWKEbiDdpZTG3WyYr73JS", - "totalMinedBlocks": 11507, - "totalRewards": "114420675427665667" - }, - { - "id": "qzmRRr2ZYMRJhP4BJtR5LeGt4atYGUjdq6pJ2YdwjLboTmhFn", - "totalMinedBlocks": 6929, - "totalRewards": "68760567420661953" - }, - { - "id": "qzjfz5bEM1ty3LcXoh1SfHgPWHbpx1n7SZ6ykEDd4e9wfw7XK", - "totalMinedBlocks": 5621, - "totalRewards": "55990224679506792" - }, - { - "id": "qznJ4dHSXqWw112iqxU3F8ZLV7kvTB7myut9r2DKnYfnrggEA", - "totalMinedBlocks": 5266, - "totalRewards": "52300374021011116" - }, - { - "id": "qzpffwT52TxEg2G8hmZ4NcJ1vjw788kyXX329VJzSSNuREkVU", - "totalMinedBlocks": 4984, - "totalRewards": "49680152076794937" - }, - { - "id": "qzp3cP18hQatXezUdyfgzL8JrogmJZzd8zi3HufzCXfXZe8a1", - "totalMinedBlocks": 3782, - "totalRewards": "37550278683008636" - }, - { - "id": "qzq6AsTmxoaTANy3mETvEKDHhFGjxLN9SPgi2rmcxttQfJenn", - "totalMinedBlocks": 3554, - "totalRewards": "35050514708660739" - }, - { - "id": "qzoqBe31EaycT5bp7V9nx686Fr255GvP2xa8XWjDRANFFsmeX", - "totalMinedBlocks": 3496, - "totalRewards": "34870096005252781" - }, - { - "id": "qzjgWhh2jhRFgD8fQnxp9HJXE34dx5XbPCXFUs6VnXFu8xHBj", - "totalMinedBlocks": 3337, - "totalRewards": "33260088018648396" - }, - { - "id": "qzn2fVfnQsb5TX1uQhfK1c1d6E6bR3BoHuynXADGi6KeGCkiM", - "totalMinedBlocks": 2758, - "totalRewards": "27530044015146557" - }, - { - "id": "qzocKmN2SvGyw1pmCwtnTSncfBkevxFfghqPsn9k6gzzQtVCV", - "totalMinedBlocks": 2527, - "totalRewards": "25180086727147767" - }, - { - "id": "qzn2CBXmMorWepWR6DVtpjXE1KwH21nuuoyHUExJEBptQ3k5A", - "totalMinedBlocks": 2407, - "totalRewards": "23970106672503090" - }, - { - "id": "qzpLQViE39bzEfnhuYdx5zUvgY4RJEmcdzTTrVmaovNQA9KrX", - "totalMinedBlocks": 2296, - "totalRewards": "22900064003501854" - }, - { - "id": "qzoAgZv6Qa9LK6sRA2FL6gdRdgcnkSW1MjFq4sUrmQssmrCvn", - "totalMinedBlocks": 2045, - "totalRewards": "20360096005252779" - }, - { - "id": "qzotCmuA9SRD5aqWzYnFAeSE9N8FhhhgnKQG4mu4h66kc1YYm", - "totalMinedBlocks": 1780, - "totalRewards": "17710096005252781" - }, - { - "id": "qzpgbvDxercT56sd9UyBwiGwEqH2igJniJGC8Y9J3K9kBkta9", - "totalMinedBlocks": 1726, - "totalRewards": "17200064003501854" - }, - { - "id": "qzkDSKxYbjpY51FjEkAFMbzpsm8THhnCXLubiKTdx3RfNYcGW", - "totalMinedBlocks": 1647, - "totalRewards": "16200288015758330" - }, - { - "id": "qzovQrRgRg29FYyh6aCjK1cHgjxiSQMwED6RjyLcHXnoHC45E", - "totalMinedBlocks": 1637, - "totalRewards": "16270117339753399" - }, - { - "id": "qzp9MgRYKtW3FEioPbmYb7D4D44R2AdqjQcU5kCCBGyREFfKw", - "totalMinedBlocks": 1623, - "totalRewards": "16210021334500618" - }, - { - "id": "qzjTMPKfiHxHepHUJgHpxxS61fULxTypp6ZruAMrd4jMVhugC", - "totalMinedBlocks": 1617, - "totalRewards": "15930246692902418" - }, - { - "id": "qzpp6Qze6kmfVHTECrcF3uBsdd6yKrTyf9fUe2xKUCxYYngdB", - "totalMinedBlocks": 1611, - "totalRewards": "16030076016897484" - }, - { - "id": "qznkYEdGrwLHWtyPFxSFjgzSBCSDiEwWYA4viEf59u1EcAKyc", - "totalMinedBlocks": 1568, - "totalRewards": "15660021334500618" - }, - { - "id": "qzkr2yeiTZsfbtwMaAx3wyPZtDhLo6TtZhGETynbsMQTP38VY", - "totalMinedBlocks": 1522, - "totalRewards": "15210010667250309" - }, - { - "id": "qzmbGP4bHDVZtPj5v6kYE9EJBxtZZ62edFz7bQKw7ZESxzaqp", - "totalMinedBlocks": 1267, - "totalRewards": "12630042669001236" - }, - { - "id": "qzjbKNCuikFtMYzGzQVcqWdMXtKAD5GmqWLkbSHRngVuddWrG", - "totalMinedBlocks": 1250, - "totalRewards": "12420085338002469" - }, - { - "id": "qzkwZ47Eusy8QsjnetvNXtJoPs8TtviP8Lceg2vErvLacHxQh", - "totalMinedBlocks": 1209, - "totalRewards": "12070021334500616" - }, - { - "id": "qzopaRR8MTRyQHh591rhEPZFZ39Ho6x8CZFji4YfKb6nq3C4i", - "totalMinedBlocks": 998, - "totalRewards": "9950032001750926" - }, - { - "id": "qzpQiSa39BUXmtnwNQYnaRCbx9L11yxXo1vhSZBYDQqmJGDVb", - "totalMinedBlocks": 989, - "totalRewards": "9880010667250309" - }, - { - "id": "qzkS7pAjzsqRW4qmq69PnGMHG89wZoEv5xPv9MxM2CfZCNZRi", - "totalMinedBlocks": 982, - "totalRewards": "9800021334500618" - }, - { - "id": "qzkpRnJwhowJ5dgBeNouESyaybQzcj5fRCBKwX2UP4CqbW7zU", - "totalMinedBlocks": 976, - "totalRewards": "9740021334500618" - }, - { - "id": "qzpqieMvB9uhiNJErsuyZbMozHUEbDBeCuZyQ3sVo8VRVZTvd", - "totalMinedBlocks": 775, - "totalRewards": "7730021334500618" - }, - { - "id": "qzp9qKHA33rLTfE17qSRkZiknDZy6ZJvhGSBHymkcZoGBigq1", - "totalMinedBlocks": 697, - "totalRewards": "6970000000000000" - }, - { - "id": "qzpb4YQqPLRL4Lg9VZWTnbcpBmyhxmqY1MmPSDjpwufkU4wJ6", - "totalMinedBlocks": 692, - "totalRewards": "6910010667250309" - }, - { - "id": "qznpFToYGPVuL7a1eKfuzT1zNa5Df7tXspjT4wrv4HzGb8HKb", - "totalMinedBlocks": 644, - "totalRewards": "6440000000000000" - }, - { - "id": "qzpdk2cFzawAFUd2xsRFDc4f9NPy6r5G7awWgniN1AiQxbAxA", - "totalMinedBlocks": 634, - "totalRewards": "6340000000000000" - }, - { - "id": "qzjXqoS5PAiJc1etGy2J257ncAJkELKW2oLTAUdiJn9VGUgyc", - "totalMinedBlocks": 595, - "totalRewards": "5950000000000000" - }, - { - "id": "qzn2WjBfpWW27vNjQLd759CeGZxLBHdypKHttFJEzB1n81DNG", - "totalMinedBlocks": 594, - "totalRewards": "5930000667250303" - }, - { - "id": "qzokWJfFjAqmaq8dENsP3esBLK64v9yhL5dHJ5AJqQhjq4ruP", - "totalMinedBlocks": 564, - "totalRewards": "5610032001750926" - }, - { - "id": "qzkQMv73GWFfLTus1QofEkbBHrh58e4TJihoEFL1HKvRLKsRy", - "totalMinedBlocks": 484, - "totalRewards": "4830010667250309" - }, - { - "id": "qzpTSJyj6Agt7Pui1L6RyxgLn2NqNw3ZqLYAP7u8nfUrgRnVM", - "totalMinedBlocks": 316, - "totalRewards": "3160000000000000" - }, - { - "id": "qzpoTFEvSgUwYpR3ePQz5K4rDwmTE46XoCDd1xefSfakZhMYd", - "totalMinedBlocks": 313, - "totalRewards": "3120000667250302" - }, - { - "id": "qzkXtGpBppBbjcJHWTfBj1uHLrBzWAo7PpbhC5j5pm483dWpb", - "totalMinedBlocks": 310, - "totalRewards": "3100000000000000" - }, - { - "id": "qzp5BiKYQTjeNudCT35NiJGDkB955yq7cSA4BRdivvYLB7r2m", - "totalMinedBlocks": 306, - "totalRewards": "3050010667250309" - }, - { - "id": "qznStFEBvfuwmVqJwLgEcDVfWg4rqGzmUampuGtvDSUfFAG7e", - "totalMinedBlocks": 272, - "totalRewards": "2710010667250309" - }, - { - "id": "qzopwzx6CtgsmdqBj8w3LMsTAjtWfBdSd1RoiSDrcQ35Weor2", - "totalMinedBlocks": 236, - "totalRewards": "2350010667250309" - }, - { - "id": "qzkhZnQmk1hBwfo3cb61amw294Vy3G5uK6BubpgVgfKALmb2P", - "totalMinedBlocks": 230, - "totalRewards": "2300000000000000" - }, - { - "id": "qzk8taXT3PPJJp87M4PbrMJa8vQG53QqShrFiJuiriJRgcwC3", - "totalMinedBlocks": 224, - "totalRewards": "2240000000000000" - }, - { - "id": "qzpg4T1Y37paXvNCYbvTsEEkkbnyX5KCujFfXo8hbgYT9arP7", - "totalMinedBlocks": 222, - "totalRewards": "2220000000000000" - }, - { - "id": "qzpnWUo1sYJsFV8tUjDAQ3und34XWAEVsgHnEvCXT9ZaQrpZu", - "totalMinedBlocks": 216, - "totalRewards": "2150010667250307" - }, - { - "id": "qzojGPxE7TPsyJztr5iQUA42CD6VrC3uyusj9K9xbH19br15z", - "totalMinedBlocks": 211, - "totalRewards": "2100010667250309" - }, - { - "id": "qzn57cH4DSFgeny98Mwgvt6N2oyniAbEA6nwG5XZj3nPh7rTc", - "totalMinedBlocks": 166, - "totalRewards": "1660000000000000" - }, - { - "id": "qzk1d27R76qRMJk4tHQHqaAsgww52MQapV7qXHMHTGJ8MJ5wy", - "totalMinedBlocks": 164, - "totalRewards": "1640000000000000" - }, - { - "id": "qzmaFUXhmE43YGCAZKLbC885GXmsVnbpRxT1174emvW4ujsha", - "totalMinedBlocks": 149, - "totalRewards": "1490000000000000" - }, - { - "id": "qzoDtmBWVbdyuMJp8ejuR855r5V6CokcfG1TmemAzsyEeZvnP", - "totalMinedBlocks": 142, - "totalRewards": "1380042669001236" - }, - { - "id": "qzpCkSFyjAXdHdRAUbRoPHKeFiG9srSewsceic1KwiBaQQZ9p", - "totalMinedBlocks": 141, - "totalRewards": "1410000000000000" - }, - { - "id": "qznvJC3UaskjTDgb5PxxofDaYmYVJA661tDwuAPQiFGNg1KPw", - "totalMinedBlocks": 131, - "totalRewards": "1310000000000000" - }, - { - "id": "qzpwjY2CW8HSHW2bD2bnoCFTg3Ce1vfNE8SZBnauSD2zVr1jn", - "totalMinedBlocks": 127, - "totalRewards": "1270000000000000" - }, - { - "id": "qzmg1qhH59h5CgCT9JHDbJbDt7XSVoY9kdgkHYJrsYxxjkArk", - "totalMinedBlocks": 127, - "totalRewards": "1250012056395604" - }, - { - "id": "qzjmpXSbCoUBcuEDaEK79tDcB44jghY2NZQTAgSmgEpUWvYzH", - "totalMinedBlocks": 124, - "totalRewards": "1240000000000000" - }, - { - "id": "qzkz8Hs7HoaB5qM2bCjrU17AR9SVn27wAZsAd5dpC3gWy1djx", - "totalMinedBlocks": 115, - "totalRewards": "1150000000000000" - }, - { - "id": "qzozTFK9guP1EUCkYnJCS2C4XHJHSrEVY4WxpoBUTKHBGpzEF", - "totalMinedBlocks": 115, - "totalRewards": "1150000000000000" - }, - { - "id": "qznMAKy1imKixFjJrLYgPAHUqEGzmuwSTDmaVRpnF2usvEwQv", - "totalMinedBlocks": 111, - "totalRewards": "1110000000000000" - }, - { - "id": "qzkgUmAJ2nJYvinnLhyXWsSzDC1yhmzm9sGoQduNdnXhVvvB5", - "totalMinedBlocks": 110, - "totalRewards": "1100000000000000" - }, - { - "id": "qzoSPiicYPizQYKvEJ37Gn2FajWFH1yoMTzccCZVvod1c8isv", - "totalMinedBlocks": 107, - "totalRewards": "1070000000000000" - }, - { - "id": "qzpj3m5o4AseZA9QY5XmNqcuDpUfgkVrETQWs9P93bhQ5zsnS", - "totalMinedBlocks": 104, - "totalRewards": "1040000000000000" - }, - { - "id": "qzjx4SHvfvHvC1CL2om1HFm5f7kFweRKpGCaEcWw4Px4phaSE", - "totalMinedBlocks": 82, - "totalRewards": "820000000000000" - }, - { - "id": "qzmr82yTMQTcVbUtJj9fEfFUi3oCVsBjXc8tuThDV1DdFKaXw", - "totalMinedBlocks": 64, - "totalRewards": "630010667250309" - }, - { - "id": "qznx4px2UTMrKYfg1BHAE6Y9jgh4nuujz7mLMA4V9FzKViZ19", - "totalMinedBlocks": 40, - "totalRewards": "400000000000000" - }, - { - "id": "qzoSiFZtJSBu55p4kFZNsyAntA5o8QZgi8xyZUFmdws9Nhd7t", - "totalMinedBlocks": 33, - "totalRewards": "320010667250309" - }, - { - "id": "qzpL6PmovJaHb6h6vbpQawEjf18o1X6gMVCqMZkBzALgyQvue", - "totalMinedBlocks": 24, - "totalRewards": "240000000000000" - }, - { - "id": "qznd1YWbgQrviV76psu5n8d24mHSuHtAc9JmJLB42gTELksvQ", - "totalMinedBlocks": 13, - "totalRewards": "130000000000000" - }, - { - "id": "qzq5Ui4a7zSeFhNTmixuYBkUDC8oRQAN12fysXXK92LTzksHF", - "totalMinedBlocks": 11, - "totalRewards": "110000000000000" - }, - { - "id": "qzmkhojzgAyXu28fRGCwNvZExbYTTrGAUAsjPYAKaasjQGs9S", - "totalMinedBlocks": 8, - "totalRewards": "80000000000000" - }, - { - "id": "qzpBGycREoVSFyopdsH6n2XAaVyKvZTBkSf3c9AGnJhwQ5YUb", - "totalMinedBlocks": 6, - "totalRewards": "60000000000000" - }, - { - "id": "qzoAovtRCni4t3PwgnjmiBm5hfYEui5jQ5cxWBVB2xnBLiHbt", - "totalMinedBlocks": 4, - "totalRewards": "40000000000000" - }, - { - "id": "qzp7coYcyvNNrt7BiqhyD61ubcxoHBKtsC688zz7Hmotuetuw", - "totalMinedBlocks": 2, - "totalRewards": "20000000000000" - } - ] - } -} \ No newline at end of file diff --git a/mobile-app/assets/v2/axe.svg b/mobile-app/assets/v2/axe.svg deleted file mode 100644 index 8c267333d..000000000 --- a/mobile-app/assets/v2/axe.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/mobile-app/lib/bootstrap/app_bootstrap.dart b/mobile-app/lib/bootstrap/app_bootstrap.dart index 3b2719628..679606617 100644 --- a/mobile-app/lib/bootstrap/app_bootstrap.dart +++ b/mobile-app/lib/bootstrap/app_bootstrap.dart @@ -6,18 +6,16 @@ import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/app.dart'; import 'package:resonance_network_wallet/app_initializer.dart'; import 'package:resonance_network_wallet/app_lifecycle_manager.dart'; -import 'package:resonance_network_wallet/shared/utils/env_utils.dart'; -import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:telemetrydecksdk/telemetrydecksdk.dart'; bool _initialized = false; /// Initializes everything the app needs before [buildApp] can run. /// -/// Safe to call more than once: the heavy, one-shot initializers (Supabase, -/// the Rust SDK, Telemetry) run only on the first invocation. This lets E2E -/// tests reuse the exact production startup path while running several tests in -/// a single app process. +/// Safe to call more than once: the heavy, one-shot initializers (the Rust +/// SDK, Telemetry) run only on the first invocation. This lets E2E tests reuse +/// the exact production startup path while running several tests in a single +/// app process. Future bootstrap() async { WidgetsFlutterBinding.ensureInitialized(); if (_initialized) return; @@ -33,7 +31,6 @@ Future bootstrap() async { await dotenv.load(); - await Supabase.initialize(url: EnvUtils.supabaseUrl, anonKey: EnvUtils.supabaseKey); await QuantusSdk.init(); Telemetrydecksdk.start( diff --git a/mobile-app/lib/l10n/app_en.arb b/mobile-app/lib/l10n/app_en.arb index 4160c486d..3de452f09 100644 --- a/mobile-app/lib/l10n/app_en.arb +++ b/mobile-app/lib/l10n/app_en.arb @@ -157,10 +157,6 @@ "@homeNoActiveAccount": { "description": "Shown when no account is active on the home screen" }, - "homeGetTestnetTokens": "Get Testnet Tokens ↗", - "@homeGetTestnetTokens": { - "description": "Faucet button when balance is zero on the home screen" - }, "homeErrorLoadingBalance": "Error loading balance", "@homeErrorLoadingBalance": { "description": "Error when balance fails to load on the home screen" @@ -1999,23 +1995,6 @@ "@settingsPreferencesSubtitle": { "description": "Preferences row subtitle on settings hub" }, - "settingsMiningRewards": "Mining Rewards", - "@settingsMiningRewards": { - "description": "Mining rewards row title on settings hub" - }, - "settingsMiningRewardsSubtitle": "{count} blocks mined", - "@settingsMiningRewardsSubtitle": { - "description": "Mining rewards row subtitle when data loaded", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "settingsMiningRewardsError": "Error getting mining rewards", - "@settingsMiningRewardsError": { - "description": "Mining rewards row subtitle on error" - }, "settingsAccountTypeTitle": "Add Account", "@settingsAccountTypeTitle": { "description": "Add account row title on settings hub" @@ -2225,130 +2204,7 @@ } }, - "settingsMiningTitle": "Mining Rewards", - "@settingsMiningTitle": { - "description": "App bar on mining rewards screen" - }, - "settingsMiningRedeem": "Redeem", - "@settingsMiningRedeem": { - "description": "Redeem button on mining rewards" - }, - "settingsMiningStatusMining": "Mining", - "@settingsMiningStatusMining": { - "description": "Active mining status label" - }, - "settingsMiningStatusPending": "Pending", - "@settingsMiningStatusPending": { - "description": "Pending mining status label" - }, - "settingsMiningBlocksMined": "BLOCKS MINED", - "@settingsMiningBlocksMined": { - "description": "Blocks mined stat label" - }, - "settingsMiningBlocksAcrossTestnets": "blocks across all testnets", - "@settingsMiningBlocksAcrossTestnets": { - "description": "Subtitle under blocks mined count" - }, - "settingsMiningStatTestnetBlocks": "TESTNET BLOCKS", - "@settingsMiningStatTestnetBlocks": { - "description": "Testnet blocks stat label" - }, - "settingsMiningStatTestnetRewards": "TESTNET REWARDS", - "@settingsMiningStatTestnetRewards": { - "description": "Testnet rewards stat label" - }, - "settingsMiningStatRedeemed": "REDEEMED", - "@settingsMiningStatRedeemed": { - "description": "Redeemed rewards stat label" - }, - "settingsMiningStatRedeemable": "REDEEMABLE", - "@settingsMiningStatRedeemable": { - "description": "Redeemable rewards stat label" - }, - "settingsMiningTokenEarned": "{tokenSymbol} EARNED", - "@settingsMiningTokenEarned": { - "description": "Token earned stat label", - "placeholders": { - "tokenSymbol": { - "type": "String" - } - } - }, - "settingsMiningViewTelemetry": "View Telemetry ↗", - "@settingsMiningViewTelemetry": { - "description": "Link to mining telemetry" - }, - "settingsMiningNoDataTitle": "No mining data yet", - "@settingsMiningNoDataTitle": { - "description": "Empty state title on mining rewards" - }, - "settingsMiningNoDataBody": "Set up a Quantus mining node to start earning rewards.", - "@settingsMiningNoDataBody": { - "description": "Empty state body on mining rewards" - }, - "settingsMiningSetupGuide": "Mining Setup Guide ↗", - "@settingsMiningSetupGuide": { - "description": "Link to mining setup guide" - }, - "settingsMiningLoadError": "Failed to load mining rewards", - "@settingsMiningLoadError": { - "description": "Error title on mining rewards screen" - }, - "settingsMiningCheckConnection": "Please check your connection", - "@settingsMiningCheckConnection": { - "description": "Error subtitle when connection fails" - }, - "settingsMiningTestnetBlocks": "blocks", - "@settingsMiningTestnetBlocks": { - "description": "Blocks label on testnet row" - }, - "settingsMiningDiracSince": "Nov 2025", - "@settingsMiningDiracSince": { - "description": "Dirac testnet active since date" - }, - "settingsMiningSchrodingerSince": "Oct 2025", - "@settingsMiningSchrodingerSince": { - "description": "Schrödinger testnet active since date" - }, - "settingsMiningResonanceSince": "Jul 2025", - "@settingsMiningResonanceSince": { - "description": "Resonance testnet active since date" - }, - "settingsTestnetTitle": "Testnet Rewards", - "@settingsTestnetTitle": { - "description": "App bar on testnet rewards screen" - }, - "settingsTestnetLoadError": "Failed to load testnet rewards", - "@settingsTestnetLoadError": { - "description": "Error title on testnet rewards" - }, - "settingsTestnetTotalBlocks": "{count} blocks", - "@settingsTestnetTotalBlocks": { - "description": "Total blocks headline on testnet rewards", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "settingsTestnetTotalDescription": "Total blocks mined across all testnets", - "@settingsTestnetTotalDescription": { - "description": "Description under total blocks" - }, - "settingsTestnetBreakdown": "Breakdown", - "@settingsTestnetBreakdown": { - "description": "Breakdown section header" - }, - "settingsTestnetRowBlocks": "{count} blocks", - "@settingsTestnetRowBlocks": { - "description": "Blocks count in testnet breakdown row", - "placeholders": { - "count": { - "type": "int" - } - } - }, "settingsHelpScreenTitle": "Help & Support", "@settingsHelpScreenTitle": { @@ -2578,10 +2434,6 @@ } } }, - "swapDepositTestnetBanner": "DEMO ONLY - WE ARE STILL ON TESTNET", - "@swapDepositTestnetBanner": { - "description": "Testnet demo banner on deposit screen" - }, "swapDemoOnly": "Demo Only", "@swapDemoOnly": { "description": "Large banner making clear the swap flow is a demo and nothing is swapped" @@ -2678,134 +2530,14 @@ "description": "Retry label shown when a list or quote fails to load" }, - "redeemToLabel": "Redeem To", - "@redeemToLabel": { - "description": "Section label above the destination address field on redeem" - }, - "redeemAddressHint": "Paste a {symbol} Address", - "@redeemAddressHint": { - "description": "Hint on the redeem destination address field", - "placeholders": { - "symbol": { - "type": "String" - } - } - }, - "redeemAmountCta": "Redeem {amount}", - "@redeemAmountCta": { - "description": "Primary redeem button showing the amount", - "placeholders": { - "amount": { - "type": "String" - } - } - }, - "redeemConfirmTitle": "Confirm Redeem", - "@redeemConfirmTitle": { - "description": "Title of the redeem confirmation sheet" - }, - "redeemConfirmAmount": "Amount", - "@redeemConfirmAmount": { - "description": "Amount row label on redeem confirmation" - }, - "redeemConfirmTo": "To", - "@redeemConfirmTo": { - "description": "Destination row label on redeem confirmation" - }, - "redeemConfirmFee": "Fee", - "@redeemConfirmFee": { - "description": "Fee row label on redeem confirmation" - }, - "redeemFeeValue": "{rate}% volume fee", - "@redeemFeeValue": { - "description": "Volume fee value on redeem confirmation", - "placeholders": { - "rate": { - "type": "String" - } - } - }, - "redeemProgressTitle": "Redeeming...", - "@redeemProgressTitle": { - "description": "App bar title while a redeem is in progress" - }, - "redeemCompleteTitle": "Redeem Complete", - "@redeemCompleteTitle": { - "description": "App bar title when a redeem finished" - }, - "redeemFailedTitle": "Redeem Failed", - "@redeemFailedTitle": { - "description": "App bar title when a redeem failed" - }, - "redeemingLabel": "REDEEMING", - "@redeemingLabel": { - "description": "Status header label on redeem progress" - }, - "redeemStepCircuits": "Preparing circuits", - "@redeemStepCircuits": { - "description": "Redeem progress step: preparing circuits" - }, - "redeemStepTransfers": "Fetching transfers", - "@redeemStepTransfers": { - "description": "Redeem progress step: fetching transfers" - }, - "redeemStepNullifiers": "Computing nullifiers", - "@redeemStepNullifiers": { - "description": "Redeem progress step: computing nullifiers" - }, - "redeemStepCheckNullifiers": "Checking nullifiers", - "@redeemStepCheckNullifiers": { - "description": "Redeem progress step: checking nullifiers" - }, - "redeemStepProofs": "Generating ZK proofs", - "@redeemStepProofs": { - "description": "Redeem progress step: generating ZK proofs" - }, - "redeemStepAggregate": "Aggregating proofs", - "@redeemStepAggregate": { - "description": "Redeem progress step: aggregating ZK proofs" - }, - "redeemStepSubmit": "Submitting to chain", - "@redeemStepSubmit": { - "description": "Redeem progress step: submitting the transaction" - }, - "redeemFetchedCount": "{count} fetched", - "@redeemFetchedCount": { - "description": "Per-step fetched counter on redeem progress", - "placeholders": { - "count": { - "type": "int" - } - } - }, "redeemCancel": "Cancel", "@redeemCancel": { "description": "Cancel button on redeem progress" }, - "redeemRetry": "Retry", - "@redeemRetry": { - "description": "Retry button on redeem progress" - }, "redeemClose": "Close", "@redeemClose": { "description": "Close button on redeem progress" }, - "redeemDone": "Done", - "@redeemDone": { - "description": "Done button on redeem progress" - }, - "redeemSuccessBanner": "{amount} redeemed in {count} batch(es)", - "@redeemSuccessBanner": { - "description": "Success banner on redeem progress", - "placeholders": { - "amount": { - "type": "String" - }, - "count": { - "type": "int" - } - } - }, "encryptedSendFeeLabel": "Privacy fee", "@encryptedSendFeeLabel": { "description": "Fee label for encrypted sends (wormhole volume fee)" diff --git a/mobile-app/lib/l10n/app_id.arb b/mobile-app/lib/l10n/app_id.arb index 89a73456c..d0eb7d2e1 100644 --- a/mobile-app/lib/l10n/app_id.arb +++ b/mobile-app/lib/l10n/app_id.arb @@ -34,7 +34,6 @@ "importWalletValidationError": "Recovery phrase harus 12 atau 24 kata", "homeError": "Gagal: {error}", "homeNoActiveAccount": "Tidak ada akun aktif", - "homeGetTestnetTokens": "Dapatkan Token Testnet ↗", "homeErrorLoadingBalance": "Gagal memuat saldo", "homeBackupReminder": "Cadangkan recovery phrase Anda", "networkStatusOfflineBanner": "Offline · menampilkan saldo terakhir", @@ -385,9 +384,6 @@ "settingsWalletSubtitle": "Frasa Pemulihan, Reset Dompet", "settingsPreferencesTitle": "Preferensi", "settingsPreferencesSubtitle": "Bahasa, mata uang, notifikasi", - "settingsMiningRewards": "Hadiah Mining", - "settingsMiningRewardsSubtitle": "{count} blok ditambang", - "settingsMiningRewardsError": "Gagal memuat hadiah mining", "settingsAccountTypeTitle": "Tambah Akun", "settingsAccountTypeSubtitle": "Kelola akun Anda", "settingsHelpTitle": "Bantuan & Dukungan", @@ -431,33 +427,6 @@ "settingsLanguageSearchHint": "Cari", "settingsLanguageNoMatch": "Tidak ada bahasa yang cocok dengan pencarian Anda", "settingsLanguageError": "Gagal memilih bahasa: {error}", - "settingsMiningTitle": "Hadiah Mining", - "settingsMiningRedeem": "Tukar", - "settingsMiningStatusMining": "Mining", - "settingsMiningStatusPending": "Menunggu", - "settingsMiningBlocksMined": "BLOK DITAMBANG", - "settingsMiningBlocksAcrossTestnets": "blok di semua testnet", - "settingsMiningStatTestnetBlocks": "BLOK TESTNET", - "settingsMiningStatTestnetRewards": "HADIAH TESTNET", - "settingsMiningStatRedeemed": "DITUKAR", - "settingsMiningStatRedeemable": "DAPAT DITUKAR", - "settingsMiningTokenEarned": "{tokenSymbol} DIHASILKAN", - "settingsMiningViewTelemetry": "Lihat Telemetri ↗", - "settingsMiningNoDataTitle": "Belum ada data mining", - "settingsMiningNoDataBody": "Siapkan node mining Quantus untuk mulai mendapatkan hadiah.", - "settingsMiningSetupGuide": "Panduan Setup Mining ↗", - "settingsMiningLoadError": "Gagal memuat hadiah mining", - "settingsMiningCheckConnection": "Periksa koneksi Anda", - "settingsMiningTestnetBlocks": "blok", - "settingsMiningDiracSince": "Nov 2025", - "settingsMiningSchrodingerSince": "Okt 2025", - "settingsMiningResonanceSince": "Jul 2025", - "settingsTestnetTitle": "Hadiah Testnet", - "settingsTestnetLoadError": "Gagal memuat hadiah testnet", - "settingsTestnetTotalBlocks": "{count} blok", - "settingsTestnetTotalDescription": "Total blok ditambang di semua testnet", - "settingsTestnetBreakdown": "Rincian", - "settingsTestnetRowBlocks": "{count} blok", "settingsHelpScreenTitle": "Bantuan & Dukungan", "settingsHelpEmail": "Dukungan Email", "settingsHelpTelegram": "Telegram", @@ -497,7 +466,6 @@ "swapDepositProcessingBody": "Ini mungkin memakan waktu beberapa menit...", "swapDepositCompleteTitle": "Swap Selesai", "swapDepositCompleteBody": "Swap Anda untuk {amount} {tokenSymbol} telah selesai.", - "swapDepositTestnetBanner": "HANYA DEMO - KAMI MASIH DI TESTNET", "swapDemoOnly": "Hanya Demo", "swapDemoOnlyBody": "Tidak ada swap sungguhan yang dilakukan.", "swapDepositSentFunds": "Saya sudah mengirim dana", @@ -519,31 +487,8 @@ "commonContinue": "Lanjutkan", "commonDone": "Selesai", "commonTryAgain": "Coba Lagi", - "redeemToLabel": "Tukar Ke", - "redeemAddressHint": "Tempel Alamat {symbol}", - "redeemAmountCta": "Tukar {amount}", - "redeemConfirmTitle": "Konfirmasi Penukaran", - "redeemConfirmAmount": "Jumlah", - "redeemConfirmTo": "Ke", - "redeemConfirmFee": "Biaya", - "redeemFeeValue": "Biaya volume {rate}%", - "redeemProgressTitle": "Menukar...", - "redeemCompleteTitle": "Penukaran Selesai", - "redeemFailedTitle": "Penukaran Gagal", - "redeemingLabel": "MENUKAR", - "redeemStepCircuits": "Menyiapkan sirkuit", - "redeemStepTransfers": "Mengambil transfer", - "redeemStepNullifiers": "Menghitung nullifier", - "redeemStepCheckNullifiers": "Memeriksa nullifier", - "redeemStepProofs": "Membuat bukti ZK", - "redeemStepAggregate": "Mengagregasi bukti", - "redeemStepSubmit": "Mengirim ke chain", - "redeemFetchedCount": "{count} diambil", "redeemCancel": "Batal", - "redeemRetry": "Coba Lagi", "redeemClose": "Tutup", - "redeemDone": "Selesai", - "redeemSuccessBanner": "{amount} ditukar dalam {count} batch", "encryptedSendFeeLabel": "Biaya privasi", "encryptedSendAmountStep": "Gunakan kelipatan 0,01 {tokenSymbol}", "encryptedSendMinimum": "Pengiriman terenkripsi minimal 0,1 {tokenSymbol}", diff --git a/mobile-app/lib/l10n/app_localizations.dart b/mobile-app/lib/l10n/app_localizations.dart index a4883e519..3e1d10836 100644 --- a/mobile-app/lib/l10n/app_localizations.dart +++ b/mobile-app/lib/l10n/app_localizations.dart @@ -302,12 +302,6 @@ abstract class AppLocalizations { /// **'No active account'** String get homeNoActiveAccount; - /// Faucet button when balance is zero on the home screen - /// - /// In en, this message translates to: - /// **'Get Testnet Tokens ↗'** - String get homeGetTestnetTokens; - /// Error when balance fails to load on the home screen /// /// In en, this message translates to: @@ -2636,24 +2630,6 @@ abstract class AppLocalizations { /// **'Language, currency, notifications'** String get settingsPreferencesSubtitle; - /// Mining rewards row title on settings hub - /// - /// In en, this message translates to: - /// **'Mining Rewards'** - String get settingsMiningRewards; - - /// Mining rewards row subtitle when data loaded - /// - /// In en, this message translates to: - /// **'{count} blocks mined'** - String settingsMiningRewardsSubtitle(int count); - - /// Mining rewards row subtitle on error - /// - /// In en, this message translates to: - /// **'Error getting mining rewards'** - String get settingsMiningRewardsError; - /// Add account row title on settings hub /// /// In en, this message translates to: @@ -2912,168 +2888,6 @@ abstract class AppLocalizations { /// **'Error selecting language: {error}'** String settingsLanguageError(String error); - /// App bar on mining rewards screen - /// - /// In en, this message translates to: - /// **'Mining Rewards'** - String get settingsMiningTitle; - - /// Redeem button on mining rewards - /// - /// In en, this message translates to: - /// **'Redeem'** - String get settingsMiningRedeem; - - /// Active mining status label - /// - /// In en, this message translates to: - /// **'Mining'** - String get settingsMiningStatusMining; - - /// Pending mining status label - /// - /// In en, this message translates to: - /// **'Pending'** - String get settingsMiningStatusPending; - - /// Blocks mined stat label - /// - /// In en, this message translates to: - /// **'BLOCKS MINED'** - String get settingsMiningBlocksMined; - - /// Subtitle under blocks mined count - /// - /// In en, this message translates to: - /// **'blocks across all testnets'** - String get settingsMiningBlocksAcrossTestnets; - - /// Testnet blocks stat label - /// - /// In en, this message translates to: - /// **'TESTNET BLOCKS'** - String get settingsMiningStatTestnetBlocks; - - /// Testnet rewards stat label - /// - /// In en, this message translates to: - /// **'TESTNET REWARDS'** - String get settingsMiningStatTestnetRewards; - - /// Redeemed rewards stat label - /// - /// In en, this message translates to: - /// **'REDEEMED'** - String get settingsMiningStatRedeemed; - - /// Redeemable rewards stat label - /// - /// In en, this message translates to: - /// **'REDEEMABLE'** - String get settingsMiningStatRedeemable; - - /// Token earned stat label - /// - /// In en, this message translates to: - /// **'{tokenSymbol} EARNED'** - String settingsMiningTokenEarned(String tokenSymbol); - - /// Link to mining telemetry - /// - /// In en, this message translates to: - /// **'View Telemetry ↗'** - String get settingsMiningViewTelemetry; - - /// Empty state title on mining rewards - /// - /// In en, this message translates to: - /// **'No mining data yet'** - String get settingsMiningNoDataTitle; - - /// Empty state body on mining rewards - /// - /// In en, this message translates to: - /// **'Set up a Quantus mining node to start earning rewards.'** - String get settingsMiningNoDataBody; - - /// Link to mining setup guide - /// - /// In en, this message translates to: - /// **'Mining Setup Guide ↗'** - String get settingsMiningSetupGuide; - - /// Error title on mining rewards screen - /// - /// In en, this message translates to: - /// **'Failed to load mining rewards'** - String get settingsMiningLoadError; - - /// Error subtitle when connection fails - /// - /// In en, this message translates to: - /// **'Please check your connection'** - String get settingsMiningCheckConnection; - - /// Blocks label on testnet row - /// - /// In en, this message translates to: - /// **'blocks'** - String get settingsMiningTestnetBlocks; - - /// Dirac testnet active since date - /// - /// In en, this message translates to: - /// **'Nov 2025'** - String get settingsMiningDiracSince; - - /// Schrödinger testnet active since date - /// - /// In en, this message translates to: - /// **'Oct 2025'** - String get settingsMiningSchrodingerSince; - - /// Resonance testnet active since date - /// - /// In en, this message translates to: - /// **'Jul 2025'** - String get settingsMiningResonanceSince; - - /// App bar on testnet rewards screen - /// - /// In en, this message translates to: - /// **'Testnet Rewards'** - String get settingsTestnetTitle; - - /// Error title on testnet rewards - /// - /// In en, this message translates to: - /// **'Failed to load testnet rewards'** - String get settingsTestnetLoadError; - - /// Total blocks headline on testnet rewards - /// - /// In en, this message translates to: - /// **'{count} blocks'** - String settingsTestnetTotalBlocks(int count); - - /// Description under total blocks - /// - /// In en, this message translates to: - /// **'Total blocks mined across all testnets'** - String get settingsTestnetTotalDescription; - - /// Breakdown section header - /// - /// In en, this message translates to: - /// **'Breakdown'** - String get settingsTestnetBreakdown; - - /// Blocks count in testnet breakdown row - /// - /// In en, this message translates to: - /// **'{count} blocks'** - String settingsTestnetRowBlocks(int count); - /// App bar on help and support screen /// /// In en, this message translates to: @@ -3308,12 +3122,6 @@ abstract class AppLocalizations { /// **'Your swap for {amount} {tokenSymbol} is complete.'** String swapDepositCompleteBody(String amount, String tokenSymbol); - /// Testnet demo banner on deposit screen - /// - /// In en, this message translates to: - /// **'DEMO ONLY - WE ARE STILL ON TESTNET'** - String get swapDepositTestnetBanner; - /// Large banner making clear the swap flow is a demo and nothing is swapped /// /// In en, this message translates to: @@ -3440,156 +3248,18 @@ abstract class AppLocalizations { /// **'Try Again'** String get commonTryAgain; - /// Section label above the destination address field on redeem - /// - /// In en, this message translates to: - /// **'Redeem To'** - String get redeemToLabel; - - /// Hint on the redeem destination address field - /// - /// In en, this message translates to: - /// **'Paste a {symbol} Address'** - String redeemAddressHint(String symbol); - - /// Primary redeem button showing the amount - /// - /// In en, this message translates to: - /// **'Redeem {amount}'** - String redeemAmountCta(String amount); - - /// Title of the redeem confirmation sheet - /// - /// In en, this message translates to: - /// **'Confirm Redeem'** - String get redeemConfirmTitle; - - /// Amount row label on redeem confirmation - /// - /// In en, this message translates to: - /// **'Amount'** - String get redeemConfirmAmount; - - /// Destination row label on redeem confirmation - /// - /// In en, this message translates to: - /// **'To'** - String get redeemConfirmTo; - - /// Fee row label on redeem confirmation - /// - /// In en, this message translates to: - /// **'Fee'** - String get redeemConfirmFee; - - /// Volume fee value on redeem confirmation - /// - /// In en, this message translates to: - /// **'{rate}% volume fee'** - String redeemFeeValue(String rate); - - /// App bar title while a redeem is in progress - /// - /// In en, this message translates to: - /// **'Redeeming...'** - String get redeemProgressTitle; - - /// App bar title when a redeem finished - /// - /// In en, this message translates to: - /// **'Redeem Complete'** - String get redeemCompleteTitle; - - /// App bar title when a redeem failed - /// - /// In en, this message translates to: - /// **'Redeem Failed'** - String get redeemFailedTitle; - - /// Status header label on redeem progress - /// - /// In en, this message translates to: - /// **'REDEEMING'** - String get redeemingLabel; - - /// Redeem progress step: preparing circuits - /// - /// In en, this message translates to: - /// **'Preparing circuits'** - String get redeemStepCircuits; - - /// Redeem progress step: fetching transfers - /// - /// In en, this message translates to: - /// **'Fetching transfers'** - String get redeemStepTransfers; - - /// Redeem progress step: computing nullifiers - /// - /// In en, this message translates to: - /// **'Computing nullifiers'** - String get redeemStepNullifiers; - - /// Redeem progress step: checking nullifiers - /// - /// In en, this message translates to: - /// **'Checking nullifiers'** - String get redeemStepCheckNullifiers; - - /// Redeem progress step: generating ZK proofs - /// - /// In en, this message translates to: - /// **'Generating ZK proofs'** - String get redeemStepProofs; - - /// Redeem progress step: aggregating ZK proofs - /// - /// In en, this message translates to: - /// **'Aggregating proofs'** - String get redeemStepAggregate; - - /// Redeem progress step: submitting the transaction - /// - /// In en, this message translates to: - /// **'Submitting to chain'** - String get redeemStepSubmit; - - /// Per-step fetched counter on redeem progress - /// - /// In en, this message translates to: - /// **'{count} fetched'** - String redeemFetchedCount(int count); - /// Cancel button on redeem progress /// /// In en, this message translates to: /// **'Cancel'** String get redeemCancel; - /// Retry button on redeem progress - /// - /// In en, this message translates to: - /// **'Retry'** - String get redeemRetry; - /// Close button on redeem progress /// /// In en, this message translates to: /// **'Close'** String get redeemClose; - /// Done button on redeem progress - /// - /// In en, this message translates to: - /// **'Done'** - String get redeemDone; - - /// Success banner on redeem progress - /// - /// In en, this message translates to: - /// **'{amount} redeemed in {count} batch(es)'** - String redeemSuccessBanner(String amount, int count); - /// Fee label for encrypted sends (wormhole volume fee) /// /// In en, this message translates to: diff --git a/mobile-app/lib/l10n/app_localizations_en.dart b/mobile-app/lib/l10n/app_localizations_en.dart index 24631601c..e531bda9c 100644 --- a/mobile-app/lib/l10n/app_localizations_en.dart +++ b/mobile-app/lib/l10n/app_localizations_en.dart @@ -119,9 +119,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get homeNoActiveAccount => 'No active account'; - @override - String get homeGetTestnetTokens => 'Get Testnet Tokens ↗'; - @override String get homeErrorLoadingBalance => 'Error loading balance'; @@ -1392,17 +1389,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get settingsPreferencesSubtitle => 'Language, currency, notifications'; - @override - String get settingsMiningRewards => 'Mining Rewards'; - - @override - String settingsMiningRewardsSubtitle(int count) { - return '$count blocks mined'; - } - - @override - String get settingsMiningRewardsError => 'Error getting mining rewards'; - @override String get settingsAccountTypeTitle => 'Add Account'; @@ -1543,93 +1529,6 @@ class AppLocalizationsEn extends AppLocalizations { return 'Error selecting language: $error'; } - @override - String get settingsMiningTitle => 'Mining Rewards'; - - @override - String get settingsMiningRedeem => 'Redeem'; - - @override - String get settingsMiningStatusMining => 'Mining'; - - @override - String get settingsMiningStatusPending => 'Pending'; - - @override - String get settingsMiningBlocksMined => 'BLOCKS MINED'; - - @override - String get settingsMiningBlocksAcrossTestnets => 'blocks across all testnets'; - - @override - String get settingsMiningStatTestnetBlocks => 'TESTNET BLOCKS'; - - @override - String get settingsMiningStatTestnetRewards => 'TESTNET REWARDS'; - - @override - String get settingsMiningStatRedeemed => 'REDEEMED'; - - @override - String get settingsMiningStatRedeemable => 'REDEEMABLE'; - - @override - String settingsMiningTokenEarned(String tokenSymbol) { - return '$tokenSymbol EARNED'; - } - - @override - String get settingsMiningViewTelemetry => 'View Telemetry ↗'; - - @override - String get settingsMiningNoDataTitle => 'No mining data yet'; - - @override - String get settingsMiningNoDataBody => 'Set up a Quantus mining node to start earning rewards.'; - - @override - String get settingsMiningSetupGuide => 'Mining Setup Guide ↗'; - - @override - String get settingsMiningLoadError => 'Failed to load mining rewards'; - - @override - String get settingsMiningCheckConnection => 'Please check your connection'; - - @override - String get settingsMiningTestnetBlocks => 'blocks'; - - @override - String get settingsMiningDiracSince => 'Nov 2025'; - - @override - String get settingsMiningSchrodingerSince => 'Oct 2025'; - - @override - String get settingsMiningResonanceSince => 'Jul 2025'; - - @override - String get settingsTestnetTitle => 'Testnet Rewards'; - - @override - String get settingsTestnetLoadError => 'Failed to load testnet rewards'; - - @override - String settingsTestnetTotalBlocks(int count) { - return '$count blocks'; - } - - @override - String get settingsTestnetTotalDescription => 'Total blocks mined across all testnets'; - - @override - String get settingsTestnetBreakdown => 'Breakdown'; - - @override - String settingsTestnetRowBlocks(int count) { - return '$count blocks'; - } - @override String get settingsHelpScreenTitle => 'Help & Support'; @@ -1764,9 +1663,6 @@ class AppLocalizationsEn extends AppLocalizations { return 'Your swap for $amount $tokenSymbol is complete.'; } - @override - String get swapDepositTestnetBanner => 'DEMO ONLY - WE ARE STILL ON TESTNET'; - @override String get swapDemoOnly => 'Demo Only'; @@ -1832,91 +1728,12 @@ class AppLocalizationsEn extends AppLocalizations { @override String get commonTryAgain => 'Try Again'; - @override - String get redeemToLabel => 'Redeem To'; - - @override - String redeemAddressHint(String symbol) { - return 'Paste a $symbol Address'; - } - - @override - String redeemAmountCta(String amount) { - return 'Redeem $amount'; - } - - @override - String get redeemConfirmTitle => 'Confirm Redeem'; - - @override - String get redeemConfirmAmount => 'Amount'; - - @override - String get redeemConfirmTo => 'To'; - - @override - String get redeemConfirmFee => 'Fee'; - - @override - String redeemFeeValue(String rate) { - return '$rate% volume fee'; - } - - @override - String get redeemProgressTitle => 'Redeeming...'; - - @override - String get redeemCompleteTitle => 'Redeem Complete'; - - @override - String get redeemFailedTitle => 'Redeem Failed'; - - @override - String get redeemingLabel => 'REDEEMING'; - - @override - String get redeemStepCircuits => 'Preparing circuits'; - - @override - String get redeemStepTransfers => 'Fetching transfers'; - - @override - String get redeemStepNullifiers => 'Computing nullifiers'; - - @override - String get redeemStepCheckNullifiers => 'Checking nullifiers'; - - @override - String get redeemStepProofs => 'Generating ZK proofs'; - - @override - String get redeemStepAggregate => 'Aggregating proofs'; - - @override - String get redeemStepSubmit => 'Submitting to chain'; - - @override - String redeemFetchedCount(int count) { - return '$count fetched'; - } - @override String get redeemCancel => 'Cancel'; - @override - String get redeemRetry => 'Retry'; - @override String get redeemClose => 'Close'; - @override - String get redeemDone => 'Done'; - - @override - String redeemSuccessBanner(String amount, int count) { - return '$amount redeemed in $count batch(es)'; - } - @override String get encryptedSendFeeLabel => 'Privacy fee'; diff --git a/mobile-app/lib/l10n/app_localizations_id.dart b/mobile-app/lib/l10n/app_localizations_id.dart index c1aa50c61..8d2dd634d 100644 --- a/mobile-app/lib/l10n/app_localizations_id.dart +++ b/mobile-app/lib/l10n/app_localizations_id.dart @@ -120,9 +120,6 @@ class AppLocalizationsId extends AppLocalizations { @override String get homeNoActiveAccount => 'Tidak ada akun aktif'; - @override - String get homeGetTestnetTokens => 'Dapatkan Token Testnet ↗'; - @override String get homeErrorLoadingBalance => 'Gagal memuat saldo'; @@ -1395,17 +1392,6 @@ class AppLocalizationsId extends AppLocalizations { @override String get settingsPreferencesSubtitle => 'Bahasa, mata uang, notifikasi'; - @override - String get settingsMiningRewards => 'Hadiah Mining'; - - @override - String settingsMiningRewardsSubtitle(int count) { - return '$count blok ditambang'; - } - - @override - String get settingsMiningRewardsError => 'Gagal memuat hadiah mining'; - @override String get settingsAccountTypeTitle => 'Tambah Akun'; @@ -1546,93 +1532,6 @@ class AppLocalizationsId extends AppLocalizations { return 'Gagal memilih bahasa: $error'; } - @override - String get settingsMiningTitle => 'Hadiah Mining'; - - @override - String get settingsMiningRedeem => 'Tukar'; - - @override - String get settingsMiningStatusMining => 'Mining'; - - @override - String get settingsMiningStatusPending => 'Menunggu'; - - @override - String get settingsMiningBlocksMined => 'BLOK DITAMBANG'; - - @override - String get settingsMiningBlocksAcrossTestnets => 'blok di semua testnet'; - - @override - String get settingsMiningStatTestnetBlocks => 'BLOK TESTNET'; - - @override - String get settingsMiningStatTestnetRewards => 'HADIAH TESTNET'; - - @override - String get settingsMiningStatRedeemed => 'DITUKAR'; - - @override - String get settingsMiningStatRedeemable => 'DAPAT DITUKAR'; - - @override - String settingsMiningTokenEarned(String tokenSymbol) { - return '$tokenSymbol DIHASILKAN'; - } - - @override - String get settingsMiningViewTelemetry => 'Lihat Telemetri ↗'; - - @override - String get settingsMiningNoDataTitle => 'Belum ada data mining'; - - @override - String get settingsMiningNoDataBody => 'Siapkan node mining Quantus untuk mulai mendapatkan hadiah.'; - - @override - String get settingsMiningSetupGuide => 'Panduan Setup Mining ↗'; - - @override - String get settingsMiningLoadError => 'Gagal memuat hadiah mining'; - - @override - String get settingsMiningCheckConnection => 'Periksa koneksi Anda'; - - @override - String get settingsMiningTestnetBlocks => 'blok'; - - @override - String get settingsMiningDiracSince => 'Nov 2025'; - - @override - String get settingsMiningSchrodingerSince => 'Okt 2025'; - - @override - String get settingsMiningResonanceSince => 'Jul 2025'; - - @override - String get settingsTestnetTitle => 'Hadiah Testnet'; - - @override - String get settingsTestnetLoadError => 'Gagal memuat hadiah testnet'; - - @override - String settingsTestnetTotalBlocks(int count) { - return '$count blok'; - } - - @override - String get settingsTestnetTotalDescription => 'Total blok ditambang di semua testnet'; - - @override - String get settingsTestnetBreakdown => 'Rincian'; - - @override - String settingsTestnetRowBlocks(int count) { - return '$count blok'; - } - @override String get settingsHelpScreenTitle => 'Bantuan & Dukungan'; @@ -1767,9 +1666,6 @@ class AppLocalizationsId extends AppLocalizations { return 'Swap Anda untuk $amount $tokenSymbol telah selesai.'; } - @override - String get swapDepositTestnetBanner => 'HANYA DEMO - KAMI MASIH DI TESTNET'; - @override String get swapDemoOnly => 'Hanya Demo'; @@ -1835,91 +1731,12 @@ class AppLocalizationsId extends AppLocalizations { @override String get commonTryAgain => 'Coba Lagi'; - @override - String get redeemToLabel => 'Tukar Ke'; - - @override - String redeemAddressHint(String symbol) { - return 'Tempel Alamat $symbol'; - } - - @override - String redeemAmountCta(String amount) { - return 'Tukar $amount'; - } - - @override - String get redeemConfirmTitle => 'Konfirmasi Penukaran'; - - @override - String get redeemConfirmAmount => 'Jumlah'; - - @override - String get redeemConfirmTo => 'Ke'; - - @override - String get redeemConfirmFee => 'Biaya'; - - @override - String redeemFeeValue(String rate) { - return 'Biaya volume $rate%'; - } - - @override - String get redeemProgressTitle => 'Menukar...'; - - @override - String get redeemCompleteTitle => 'Penukaran Selesai'; - - @override - String get redeemFailedTitle => 'Penukaran Gagal'; - - @override - String get redeemingLabel => 'MENUKAR'; - - @override - String get redeemStepCircuits => 'Menyiapkan sirkuit'; - - @override - String get redeemStepTransfers => 'Mengambil transfer'; - - @override - String get redeemStepNullifiers => 'Menghitung nullifier'; - - @override - String get redeemStepCheckNullifiers => 'Memeriksa nullifier'; - - @override - String get redeemStepProofs => 'Membuat bukti ZK'; - - @override - String get redeemStepAggregate => 'Mengagregasi bukti'; - - @override - String get redeemStepSubmit => 'Mengirim ke chain'; - - @override - String redeemFetchedCount(int count) { - return '$count diambil'; - } - @override String get redeemCancel => 'Batal'; - @override - String get redeemRetry => 'Coba Lagi'; - @override String get redeemClose => 'Tutup'; - @override - String get redeemDone => 'Selesai'; - - @override - String redeemSuccessBanner(String amount, int count) { - return '$amount ditukar dalam $count batch'; - } - @override String get encryptedSendFeeLabel => 'Biaya privasi'; diff --git a/mobile-app/lib/providers/mining_rewards_provider.dart b/mobile-app/lib/providers/mining_rewards_provider.dart deleted file mode 100644 index 8baed724d..000000000 --- a/mobile-app/lib/providers/mining_rewards_provider.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:resonance_network_wallet/providers/account_providers.dart'; -import 'package:resonance_network_wallet/providers/wallet_providers.dart'; -import 'package:resonance_network_wallet/services/mining_rewards_service.dart'; - -final miningRewardsServiceProvider = Provider((ref) => MiningRewardsService()); - -final miningRewardsProvider = FutureProvider((ref) async { - final service = ref.watch(miningRewardsServiceProvider); - final accounts = ref.watch(accountsProvider).value; - - if (accounts == null || accounts.isEmpty) { - 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 QuersiService().getOldMiningAccountId(); - final accountsList = accounts.map((a) => a.accountId).toList(); - accountsList.add(oldMiningAccountId); - - return service.getMiningRewards(ref, keyPair, accountsList); -}); diff --git a/mobile-app/lib/services/logout_service.dart b/mobile-app/lib/services/logout_service.dart index ec301e760..2ae434601 100644 --- a/mobile-app/lib/services/logout_service.dart +++ b/mobile-app/lib/services/logout_service.dart @@ -4,7 +4,6 @@ import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/currency_display_provider.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; -import 'package:resonance_network_wallet/providers/mining_rewards_provider.dart'; import 'package:resonance_network_wallet/providers/multisig_providers.dart'; import 'package:resonance_network_wallet/providers/pending_multisig_approvals_provider.dart'; import 'package:resonance_network_wallet/providers/pending_multisig_cancellations_provider.dart'; @@ -43,8 +42,6 @@ class LogoutService { _ref.read(pendingMultisigApprovalsProvider.notifier).clear(); _ref.read(pendingMultisigExecutionsProvider.notifier).clear(); _ref.read(pendingMultisigCancellationsProvider.notifier).clear(); - _ref.read(miningRewardsServiceProvider).clearCachedRewardsData(); - _ref.invalidate(miningRewardsProvider); _ref.read(accountsProvider.notifier).reset(); _ref.read(activeAccountProvider.notifier).reset(); _ref.invalidate(recoveryPhraseViewedProvider); diff --git a/mobile-app/lib/services/mining_rewards_service.dart b/mobile-app/lib/services/mining_rewards_service.dart deleted file mode 100644 index fb0f330cd..000000000 --- a/mobile-app/lib/services/mining_rewards_service.dart +++ /dev/null @@ -1,134 +0,0 @@ -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/shared/utils/env_utils.dart'; -import 'package:resonance_network_wallet/providers/wallet_providers.dart'; -import 'package:resonance_network_wallet/shared/utils/print.dart'; - -class MiningRewardsData { - final int resonanceBlocks; - 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; -} - -class MiningRewardsService { - static const _assets = { - 'dirac': 'assets/testnet_data/dirac_miners.json', - 'resonance': 'assets/testnet_data/resonance_network_miners.json', - 'schrodinger': 'assets/testnet_data/schrodinger_miners.json', - }; - - Set? _cachedAccountIds; - - void clearCachedRewardsData() { - _cachedAccountIds = null; - } - - Future getMiningRewards(Ref ref, WormholeKeyPair keyPair, List currentAccountIds) async { - quantusPrint('[MiningRewards] Current account IDs: $currentAccountIds'); - final wormholeUtxoService = ref.read(wormholeUtxoServiceProvider); - - final miners = >{}; - for (final entry in _assets.entries) { - final jsonStr = await rootBundle.loadString(entry.value); - miners[entry.key] = _parseMiners(jsonStr); - quantusPrint('[MiningRewards] ${entry.key}: loaded ${miners[entry.key]!.length} miners'); - } - - _cachedAccountIds ??= await _resolveAllAccountIds(currentAccountIds); - final allAccountIds = _cachedAccountIds!; - - final resonance = _countBlocks('resonance', miners['resonance']!, allAccountIds); - final schrodinger = _countBlocks('schrodinger', miners['schrodinger']!, allAccountIds); - final dirac = _countBlocks('dirac', miners['dirac']!, allAccountIds); - final (planckStats, redeemableRewards) = await ( - QuersiService().getMinerStats(), - wormholeUtxoService.getUnspentBalance(wormholeAddress: keyPair.address, secretHex: keyPair.secretHex), - ).wait; - final redeemedRewards = planckStats.totalRewards - redeemableRewards; - - return MiningRewardsData( - resonanceBlocks: resonance, - schrodingerBlocks: schrodinger, - diracBlocks: dirac, - planckBlocks: planckStats.totalMinedBlocks, - planckRewards: planckStats.totalRewards, - redeemedRewards: redeemedRewards, - redeemableRewards: redeemableRewards, - ); - } - - List<_MinerEntry> _parseMiners(String jsonStr) { - final decoded = jsonDecode(jsonStr); - final stats = decoded['data']['minerStats'] as List; - return stats.map((e) => _MinerEntry(e['id'] as String, (e['totalMinedBlocks'] as num).toInt())).toList(); - } - - Future> _resolveAllAccountIds(List currentIds) async { - final allIds = {...currentIds}; - - final mappings = await _fetchAccountMappings(); - - final newToOld = {}; - for (final m in mappings) { - newToOld[m['new_account_id'] as String] = m['old_account_id'] as String; - } - var depth = 0; - var toCheck = currentIds.toList(); - while (toCheck.isNotEmpty) { - depth++; - final next = []; - for (final id in toCheck) { - final oldId = newToOld[id]; - if (oldId != null && allIds.add(oldId)) { - quantusPrint('[MiningRewards] Chain depth $depth: $id -> $oldId'); - next.add(oldId); - } - } - toCheck = next; - } - - quantusPrint('[MiningRewards] Final account ID set (${allIds.length}): $allIds'); - return allIds; - } - - Future>> _fetchAccountMappings() async { - quantusPrint('[MiningRewards] Fetching account_id_mappings from Supabase...'); - final data = await EnvUtils.supabaseClient.from('account_id_mappings').select(); - return List>.from(data); - } - - int _countBlocks(String network, List<_MinerEntry> miners, Set accountIds) { - int total = 0; - for (final miner in miners) { - if (accountIds.contains(miner.id)) { - total += miner.blocks; - } - } - return total; - } -} - -class _MinerEntry { - final String id; - final int blocks; - const _MinerEntry(this.id, this.blocks); -} diff --git a/mobile-app/lib/shared/utils/env_utils.dart b/mobile-app/lib/shared/utils/env_utils.dart index fab684133..1a6f86870 100644 --- a/mobile-app/lib/shared/utils/env_utils.dart +++ b/mobile-app/lib/shared/utils/env_utils.dart @@ -1,26 +1,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:supabase_flutter/supabase_flutter.dart'; /// Utility class for reading environment variables class EnvUtils { - /// Get the Supabase URL from environment variables - static String get supabaseUrl { - final url = dotenv.env['SUPABASE_URL']; - if (url == null || url.isEmpty) { - throw Exception('SUPABASE_URL is not set in .env file'); - } - return url; - } - - /// Get the Supabase key from environment variables - static String get supabaseKey { - final key = dotenv.env['SUPABASE_ANON_KEY']; - if (key == null || key.isEmpty) { - throw Exception('SUPABASE_ANON_KEY is not set in .env file'); - } - return key; - } - static String get iosFirebaseApiKey { final key = dotenv.env['IOS_FIREBASE_API_KEY']; if (key == null || key.isEmpty) { @@ -44,7 +25,4 @@ class EnvUtils { static String getEnvWithFallback(String key, String fallback) { return dotenv.env[key] ?? fallback; } - - /// Get the Supabase client instance - static SupabaseClient get supabaseClient => Supabase.instance.client; } diff --git a/mobile-app/lib/shared/utils/url_utils.dart b/mobile-app/lib/shared/utils/url_utils.dart index d74d3c9da..264f4feb5 100644 --- a/mobile-app/lib/shared/utils/url_utils.dart +++ b/mobile-app/lib/shared/utils/url_utils.dart @@ -1,18 +1,5 @@ import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:url_launcher/url_launcher.dart'; /// Block-explorer URL for an immediate (single-signer) transfer extrinsic. String explorerImmediateTransactionUrl(String extrinsicHash) => '${AppConstants.explorerEndpoint}/immediate-transactions/$extrinsicHash'; - -Future launchXPost(String xUrl) async { - final match = RegExp(r'/status/(\d+)').firstMatch(xUrl); - if (match != null) { - final native = Uri.parse('twitter://status?id=${match.group(1)}'); - if (await canLaunchUrl(native)) { - await launchUrl(native); - return; - } - } - await launchUrl(Uri.parse(xUrl), mode: LaunchMode.externalApplication); -} diff --git a/mobile-app/lib/v2/screens/home/home_screen.dart b/mobile-app/lib/v2/screens/home/home_screen.dart index 3796208ff..a16e415bf 100644 --- a/mobile-app/lib/v2/screens/home/home_screen.dart +++ b/mobile-app/lib/v2/screens/home/home_screen.dart @@ -10,7 +10,6 @@ import 'package:resonance_network_wallet/services/global_history_polling_service import 'package:resonance_network_wallet/services/telemetry_service.dart'; import 'package:resonance_network_wallet/shared/constants/e2e_keys.dart'; import 'package:resonance_network_wallet/shared/utils/print.dart'; -import 'package:resonance_network_wallet/shared/utils/url_utils.dart'; import 'package:resonance_network_wallet/v2/components/amount_display_with_conversion.dart'; import 'package:resonance_network_wallet/v2/components/private_activity_notice.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/open_accounts_management_button.dart'; @@ -275,31 +274,15 @@ class _HomeScreenState extends ConsumerState { } Widget? _buildBottomContent(AppLocalizations l10n) { - final balanceAsync = ref.watch(balanceProvider); final active = ref.watch(activeAccountProvider).value; - - if (isEncryptedAccount(active?.account)) { - return ScaffoldBaseBottomContent( - child: PrivateActivityNotice( - title: l10n.createAccountEncryptedDefaultName, - subtitle: l10n.privateSendSubtitle, - showCard: true, - ), - ); - } - - return balanceAsync - .whenData( - (balance) => balance == BigInt.zero - ? ScaffoldBaseBottomContent( - child: QuantusButton.simple( - label: l10n.homeGetTestnetTokens, - onTap: () => launchXPost(AppConstants.faucetUrl), - ), - ) - : null, - ) - .value; + if (!isEncryptedAccount(active?.account)) return null; + return ScaffoldBaseBottomContent( + child: PrivateActivityNotice( + title: l10n.createAccountEncryptedDefaultName, + subtitle: l10n.privateSendSubtitle, + showCard: true, + ), + ); } Widget _buildTopBar(bool isBalanceHidden) { diff --git a/mobile-app/lib/v2/screens/settings/mining_rewards_screen.dart b/mobile-app/lib/v2/screens/settings/mining_rewards_screen.dart deleted file mode 100644 index 8c7c2afa5..000000000 --- a/mobile-app/lib/v2/screens/settings/mining_rewards_screen.dart +++ /dev/null @@ -1,438 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/l10n/app_localizations.dart'; -import 'package:resonance_network_wallet/providers/l10n_provider.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/split_card.dart'; -import 'package:resonance_network_wallet/v2/screens/settings/redeem_address_screen.dart'; - -class MiningRewardsScreen extends ConsumerWidget { - const MiningRewardsScreen({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final l10n = ref.watch(l10nProvider); - final miningAsync = ref.watch(miningRewardsProvider); - - return ScaffoldBase.refreshable( - appBar: V2AppBar(title: l10n.settingsMiningTitle), - onRefresh: () async => ref.invalidate(miningRewardsProvider), - slivers: [ - miningAsync.when( - data: (data) => data.totalBlocks > 0 ? _WithRewards(data: data) : _NoRewards(l10n: l10n), - loading: () => _NoRewards(l10n: l10n, isLoading: true), - error: (err, _) => _ErrorState(l10n: l10n, onRetry: () => ref.invalidate(miningRewardsProvider)), - ), - ], - bottomContent: miningAsync.when( - data: (data) { - if (data.totalBlocks == 0) return null; - final canRedeem = data.redeemableRewards > BigInt.zero; - return ScaffoldBaseBottomContent( - child: QuantusButton.simple( - label: l10n.settingsMiningRedeem, - isDisabled: !canRedeem, - onTap: canRedeem - ? () => Navigator.of(context).push( - MaterialPageRoute(builder: (_) => RedeemAddressScreen(redeemableRewards: data.redeemableRewards)), - ) - : null, - ), - ); - }, - loading: () => null, - error: (err, _) => null, - ), - ); - } -} - -class _WithRewards extends ConsumerWidget { - final MiningRewardsData data; - - const _WithRewards({required this.data}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final l10n = ref.watch(l10nProvider); - final numberFmt = ref.watch(numberFormattingServiceProvider); - final tokenEarned = numberFmt.formatBalance(data.planckRewards, smartDecimals: 2, addSymbol: true); - final redeemedRewards = numberFmt.formatBalance(data.redeemedRewards, smartDecimals: 2, addSymbol: true); - final redeemableRewards = numberFmt.formatBalance(data.redeemableRewards, smartDecimals: 2, addSymbol: true); - - final colors = context.colorsV3; - - final testnets = [ - _TestnetEntry('Planck', null, data.planckBlocks), - _TestnetEntry('Dirac', l10n.settingsMiningDiracSince, data.diracBlocks), - _TestnetEntry('Schrödinger', l10n.settingsMiningSchrodingerSince, data.schrodingerBlocks), - _TestnetEntry('Resonance', l10n.settingsMiningResonanceSince, data.resonanceBlocks), - ]; - - final miningSummaryPairRows = [ - _StatPairRow( - left: _MiningStatCell( - label: l10n.settingsMiningStatTestnetBlocks, - value: '${data.totalBlocks}', - valueColor: colors.textContent, - ), - right: _MiningStatCell( - label: l10n.settingsMiningStatTestnetRewards, - value: tokenEarned, - valueColor: colors.accentFlare, - ), - ), - _StatPairRow( - left: _MiningStatCell( - label: l10n.settingsMiningStatRedeemed, - value: redeemedRewards, - valueColor: colors.textContent, - ), - right: _MiningStatCell( - label: l10n.settingsMiningStatRedeemable, - value: redeemableRewards, - valueColor: colors.semanticSage, - ), - ), - ]; - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SplitCard( - topChild: _CardTopSection( - l10n: l10n, - totalBlocks: data.totalBlocks, - totalBlocksColor: colors.textContent, - statusLabel: l10n.settingsMiningStatusMining, - statusColor: colors.semanticSage, - ), - 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], blocksLabel: l10n.settingsMiningTestnetBlocks), - if (i < testnets.length - 1) const MenuDivider(), - ], - const SizedBox(height: 48), - Center( - child: _FlareLinkButton( - label: l10n.settingsMiningViewTelemetry, - onTap: () => openUrl(AppConstants.telemetryUrl), - ), - ), - const SizedBox(height: 24), - ], - ); - } -} - -class _NoRewards extends StatelessWidget { - final AppLocalizations l10n; - final bool isLoading; - - const _NoRewards({required this.l10n, this.isLoading = false}); - - @override - Widget build(BuildContext context) { - final colors = context.colorsV3; - final text = context.themeTextV3; - - return Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SplitCard( - topChild: _CardTopSection( - l10n: l10n, - totalBlocks: 0, - totalBlocksColor: colors.textMuted, - statusLabel: l10n.settingsMiningStatusPending, - statusColor: colors.textMuted, - isLoading: isLoading, - ), - bottomChild: _StatColumn( - label: l10n.settingsMiningTokenEarned(AppConstants.tokenSymbol), - value: '0.00', - valueColor: colors.textMuted, - 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) const MenuDivider(), - const SizedBox(height: 24), - ], - ] else ...[ - const SizedBox(height: 64), - Text(l10n.settingsMiningNoDataTitle, style: text.titleScreen.copyWith(color: colors.textMuted)), - const SizedBox(height: 8), - Text( - l10n.settingsMiningNoDataBody, - textAlign: TextAlign.center, - style: text.caption.copyWith(color: colors.textMuted), - ), - const SizedBox(height: 64), - _FlareLinkButton( - label: l10n.settingsMiningSetupGuide, - onTap: () => openUrl(AppConstants.miningSetupGuideUrl), - ), - const SizedBox(height: 24), - ], - ], - ); - } -} - -class _CardTopSection extends StatelessWidget { - final AppLocalizations l10n; - final int totalBlocks; - final Color totalBlocksColor; - final String statusLabel; - final Color statusColor; - final bool isLoading; - - const _CardTopSection({ - required this.l10n, - required this.totalBlocks, - required this.totalBlocksColor, - required this.statusLabel, - required this.statusColor, - this.isLoading = false, - }); - - @override - Widget build(BuildContext context) { - final colors = context.colorsV3; - final text = context.themeTextV3; - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(l10n.settingsMiningBlocksMined, style: text.labelData.copyWith(color: colors.textMuted)), - 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.caption.copyWith(color: statusColor)), - ], - ), - ], - ), - const SizedBox(height: 8), - if (isLoading) - const Skeleton(width: 100, height: 24) - else - Text('$totalBlocks', style: text.displayBalance.copyWith(color: totalBlocksColor)), - const SizedBox(height: 4), - Text(l10n.settingsMiningBlocksAcrossTestnets, style: text.caption.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.colorsV3; - final text = context.themeTextV3; - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(label, style: text.labelData.copyWith(color: colors.textMuted)), - const SizedBox(height: 8), - if (isLoading) - const Skeleton(width: 100, height: 24) - else - FittedBox( - fit: BoxFit.scaleDown, - alignment: Alignment.centerLeft, - child: Text(value, maxLines: 1, softWrap: false, style: text.amountInline.copyWith(color: valueColor)), - ), - ], - ); - } -} - -class _TestnetRow extends StatelessWidget { - final _TestnetEntry entry; - final String blocksLabel; - - const _TestnetRow({required this.entry, required this.blocksLabel}); - - @override - Widget build(BuildContext context) { - final colors = context.colorsV3; - final text = context.themeTextV3; - - return Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(entry.name, style: text.amountRow.copyWith(color: colors.textContent)), - if (entry.subtitle != null) ...[ - const SizedBox(height: 8), - Text(entry.subtitle!, style: text.caption.copyWith(color: colors.textMuted)), - ], - ], - ), - Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text('${entry.blocks}', style: text.amountInline.copyWith(color: colors.textContent)), - const SizedBox(height: 4), - Text(blocksLabel, style: text.caption.copyWith(color: colors.textMuted)), - ], - ), - ], - ), - ); - } -} - -class _FlareLinkButton extends StatelessWidget { - final String label; - final VoidCallback onTap; - - const _FlareLinkButton({required this.label, required this.onTap}); - - @override - Widget build(BuildContext context) { - final colors = context.colorsV3; - final linkColor = colors.accentFlare; - - return GestureDetector( - onTap: onTap, - child: Container( - decoration: BoxDecoration( - border: Border(bottom: BorderSide(color: linkColor, width: 1)), - ), - padding: const EdgeInsets.only(bottom: 3), - child: Text(label, style: context.themeTextV3.body.copyWith(color: linkColor)), - ), - ); - } -} - -class _ErrorState extends StatelessWidget { - final AppLocalizations l10n; - final VoidCallback onRetry; - - const _ErrorState({required this.l10n, required this.onRetry}); - - @override - Widget build(BuildContext context) { - final colors = context.colorsV3; - final text = context.themeTextV3; - - return SizedBox( - height: 200, - child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(l10n.settingsMiningLoadError, style: text.body.copyWith(color: colors.textContent)), - const SizedBox(height: 8), - Text(l10n.settingsMiningCheckConnection, style: text.caption.copyWith(color: colors.textMuted)), - const SizedBox(height: 20), - GestureDetector( - onTap: onRetry, - child: Text(l10n.commonTryAgain, style: text.body.copyWith(color: colors.accentFlare)), - ), - ], - ), - ), - ); - } -} - -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/redeem_address_screen.dart b/mobile-app/lib/v2/screens/settings/redeem_address_screen.dart deleted file mode 100644 index d968bb941..000000000 --- a/mobile-app/lib/v2/screens/settings/redeem_address_screen.dart +++ /dev/null @@ -1,218 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/l10n/app_localizations.dart'; -import 'package:resonance_network_wallet/providers/l10n_provider.dart'; -import 'package:resonance_network_wallet/providers/wallet_providers.dart'; -import 'package:resonance_network_wallet/v2/components/address_input_field.dart'; -import 'package:resonance_network_wallet/v2/screens/settings/redeem_progress_screen.dart'; - -/// Fullscreen flow to pick a destination address for redeeming mining -/// rewards. Defaults the field to the user's primary account (Account 0). -class RedeemAddressScreen extends ConsumerStatefulWidget { - final BigInt redeemableRewards; - - const RedeemAddressScreen({super.key, required this.redeemableRewards}); - - @override - ConsumerState createState() => _RedeemAddressScreenState(); -} - -class _RedeemAddressScreenState extends ConsumerState { - final _recipientController = TextEditingController(); - final _recipientFocus = FocusNode(); - - bool _hasAddressError = true; - String? _recipientChecksum; - - @override - void initState() { - super.initState(); - _recipientController.addListener(_onRecipientChanged); - _prefillPrimaryAccount(); - } - - @override - void dispose() { - _recipientController.removeListener(_onRecipientChanged); - _recipientController.dispose(); - _recipientFocus.dispose(); - super.dispose(); - } - - Future _prefillPrimaryAccount() async { - final settings = ref.read(settingsServiceProvider); - final primary = await settings.getPrimaryAccount(); - if (!mounted || primary == null) return; - _recipientController.text = primary.accountId; - } - - void _onRecipientChanged() { - final text = _recipientController.text.trim(); - if (text.isEmpty) { - setState(() { - _hasAddressError = true; - _recipientChecksum = null; - }); - return; - } - final substrate = ref.read(substrateServiceProvider); - final isValid = substrate.isValidSS58Address(text); - setState(() { - _hasAddressError = !isValid; - _recipientChecksum = null; - }); - if (isValid) { - ref.read(humanReadableChecksumServiceProvider).getHumanReadableName(text).then((checksum) { - if (!mounted || _recipientController.text.trim() != text) return; - setState(() => _recipientChecksum = checksum); - }); - } - } - - bool get _canRedeem => - _recipientController.text.trim().isNotEmpty && !_hasAddressError && widget.redeemableRewards > BigInt.zero; - - Future _redeem() async { - if (!_canRedeem) return; - final destination = _recipientController.text.trim(); - final fmt = ref.read(numberFormattingServiceProvider); - final formatted = fmt.formatBalance(widget.redeemableRewards, maxDecimals: 2, addSymbol: true); - final l10n = ref.read(l10nProvider); - - final confirmed = await BottomSheetContainer.show( - context, - builder: (_) => _RedeemConfirmSheet(formatted: formatted, destination: destination, l10n: l10n), - ); - if (confirmed != true || !mounted) return; - - Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (_) => - RedeemProgressScreen(redeemableRewards: widget.redeemableRewards, destinationAddress: destination), - ), - ); - } - - @override - Widget build(BuildContext context) { - final colors = context.colorsV3; - final text = context.themeTextV3; - final fmt = ref.watch(numberFormattingServiceProvider); - final l10n = ref.watch(l10nProvider); - - final hasValid = _recipientController.text.trim().isNotEmpty && !_hasAddressError; - final amountLabel = fmt.formatBalance(widget.redeemableRewards, maxDecimals: 2, addSymbol: true); - - return ScaffoldBase( - appBar: V2AppBar(title: l10n.settingsMiningRedeem), - mainContent: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _AmountSummary(amountLabel: amountLabel, l10n: l10n), - const SizedBox(height: 28), - Text(l10n.redeemToLabel, style: text.headingRow.copyWith(color: colors.textContent)), - const SizedBox(height: 12), - AddressInputField( - controller: _recipientController, - focusNode: _recipientFocus, - hasValid: hasValid, - recipientChecksum: _recipientChecksum, - hintText: l10n.redeemAddressHint(AppConstants.tokenSymbol), - ), - ], - ), - bottomContent: ScaffoldBaseBottomContent( - child: QuantusButton.simple( - label: _canRedeem ? l10n.redeemAmountCta(amountLabel) : l10n.sendEnterAddress, - variant: ButtonVariant.primary, - isDisabled: !_canRedeem, - onTap: _redeem, - ), - ), - ); - } -} - -class _AmountSummary extends StatelessWidget { - final String amountLabel; - final AppLocalizations l10n; - - const _AmountSummary({required this.amountLabel, required this.l10n}); - - @override - Widget build(BuildContext context) { - final colors = context.colorsV3; - final text = context.themeTextV3; - - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration(color: colors.bgSurface, borderRadius: context.radiusV3.mdBorder), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(l10n.settingsMiningStatRedeemable, style: text.labelData.copyWith(color: colors.textMuted)), - FittedBox( - fit: BoxFit.scaleDown, - alignment: Alignment.centerRight, - child: Text( - amountLabel, - maxLines: 1, - softWrap: false, - style: text.amountInline.copyWith(color: colors.semanticSage), - ), - ), - ], - ), - ); - } -} - -class _RedeemConfirmSheet extends StatelessWidget { - final String formatted; - final String destination; - final AppLocalizations l10n; - - const _RedeemConfirmSheet({required this.formatted, required this.destination, required this.l10n}); - - @override - Widget build(BuildContext context) { - final colors = context.colorsV3; - final text = context.themeTextV3; - - return BottomSheetContainer( - title: l10n.redeemConfirmTitle, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _row(l10n.redeemConfirmAmount, formatted, colors, text), - const Padding(padding: EdgeInsets.symmetric(vertical: 16), child: MenuDivider()), - _row(l10n.redeemConfirmTo, AddressFormattingService.formatAddress(destination), colors, text), - const Padding(padding: EdgeInsets.symmetric(vertical: 16), child: MenuDivider()), - _row(l10n.redeemConfirmFee, l10n.redeemFeeValue(wormholeVolumeFeePercentText()), colors, text), - const SizedBox(height: 32), - QuantusButton.simple(label: l10n.redeemAmountCta(formatted), onTap: () => Navigator.of(context).pop(true)), - ], - ), - ); - } - - Widget _row(String label, String value, AppColorsV3 colors, AppTextThemeV3 text) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(label, style: text.caption.copyWith(color: colors.textMuted)), - Flexible( - child: Text( - value, - style: text.body.copyWith(color: colors.textContent), - textAlign: TextAlign.end, - overflow: TextOverflow.ellipsis, - ), - ), - ], - ); - } -} diff --git a/mobile-app/lib/v2/screens/settings/redeem_progress_screen.dart b/mobile-app/lib/v2/screens/settings/redeem_progress_screen.dart deleted file mode 100644 index fe537e57c..000000000 --- a/mobile-app/lib/v2/screens/settings/redeem_progress_screen.dart +++ /dev/null @@ -1,223 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/l10n/app_localizations.dart'; -import 'package:resonance_network_wallet/providers/l10n_provider.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/shared/utils/print.dart'; -import 'package:resonance_network_wallet/v2/components/wormhole_progress_steps.dart'; - -class RedeemProgressScreen extends ConsumerStatefulWidget { - final BigInt redeemableRewards; - final String destinationAddress; - - const RedeemProgressScreen({super.key, required this.redeemableRewards, required this.destinationAddress}); - - @override - ConsumerState createState() => _RedeemProgressScreenState(); -} - -class _RedeemProgressScreenState extends ConsumerState { - WormholeSendService? _claimService; - bool _running = true; - bool _done = false; - bool _cancelled = false; - String? _errorMessage; - int _currentStep = 0; - final Map _stepProgress = {}; - ClaimResult? _result; - - @override - void initState() { - super.initState(); - _startClaim(); - } - - Future _startClaim() async { - try { - final mnemonic = await ref.read(settingsServiceProvider).getMnemonic(0); - if (mnemonic == null) throw StateError('Mnemonic not found'); - - final keyPair = ref.read(hdWalletServiceProvider).deriveWormholeKeyPair(mnemonic: mnemonic); - if (keyPair.secretHex.isEmpty) throw StateError('Wormhole key pair not available'); - - final circuitDir = await CircuitManager.getCircuitDirectory(); - _claimService = WormholeSendService(); - - final result = await _claimService!.claimRewards( - wormholeAddress: keyPair.address, - secretHex: keyPair.secretHex, - destinationAddress: widget.destinationAddress, - circuitBinsDir: circuitDir, - onProgress: (progress) { - if (!mounted) return; - setState(() { - _currentStep = progress.step; - _stepProgress[progress.step] = progress; - }); - }, - ); - - if (!mounted) return; - setState(() { - // A cancel that landed after some batches were submitted comes back - // as a partial result — show it as cancelled, with the real totals. - _done = !result.cancelled; - _cancelled = result.cancelled; - _running = false; - _result = result; - }); - ref.invalidate(miningRewardsProvider); - } on ClaimCancelled { - if (!mounted) return; - setState(() { - _running = false; - _cancelled = true; - }); - } catch (e) { - quantusPrint('[Redeem] Claim failed: $e'); - if (!mounted) return; - setState(() { - _running = false; - _errorMessage = e.toString(); - }); - } - } - - void _cancel() { - _claimService?.cancel(); - } - - void _retry() { - setState(() { - _running = true; - _done = false; - _cancelled = false; - _errorMessage = null; - _currentStep = 0; - _stepProgress.clear(); - _result = null; - }); - _startClaim(); - } - - @override - Widget build(BuildContext context) { - final colors = context.colorsV3; - final text = context.themeTextV3; - final l10n = ref.watch(l10nProvider); - - return PopScope( - canPop: !_running, - child: ScaffoldBase( - appBar: V2AppBar( - title: _done - ? l10n.redeemCompleteTitle - : _errorMessage != null - ? l10n.redeemFailedTitle - : l10n.redeemProgressTitle, - showBackButton: !_running, - ), - mainContent: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const SizedBox(height: 24), - _buildStatusHeader(colors, text, l10n), - const SizedBox(height: 32), - _buildSteps(l10n), - if (_errorMessage != null) ...[ - const SizedBox(height: 24), - QuantusBanner(tone: BannerTone.ember, message: _errorMessage!), - ], - if (_done && _result != null) ...[const SizedBox(height: 24), _buildSuccessBanner(l10n)], - ], - ), - ), - bottomContent: _buildBottomContent(l10n), - ), - ); - } - - Widget _buildStatusHeader(AppColorsV3 colors, AppTextThemeV3 text, AppLocalizations l10n) { - final fmt = ref.watch(numberFormattingServiceProvider); - final amountLabel = fmt.formatBalance(widget.redeemableRewards, maxDecimals: 2, addSymbol: true); - - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration(color: colors.bgSurface, borderRadius: context.radiusV3.mdBorder), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(l10n.redeemingLabel, style: text.labelData.copyWith(color: colors.textMuted)), - Text(amountLabel, style: text.amountInline.copyWith(color: colors.semanticSage)), - ], - ), - ); - } - - Widget _buildSteps(AppLocalizations l10n) { - return WormholeProgressSteps( - steps: [ - (1, l10n.redeemStepCircuits), - (2, l10n.redeemStepTransfers), - (3, l10n.redeemStepNullifiers), - (4, l10n.redeemStepProofs), - (5, l10n.redeemStepAggregate), - (6, l10n.redeemStepSubmit), - ], - stepProgress: _stepProgress, - currentStep: _currentStep, - done: _done, - cancelled: _cancelled, - hasError: _errorMessage != null, - progressLabelOverride: (step, progress) => step == 2 ? l10n.redeemFetchedCount(progress.completed) : null, - ); - } - - Widget _buildSuccessBanner(AppLocalizations l10n) { - final fmt = ref.watch(numberFormattingServiceProvider); - final withdrawn = fmt.formatBalance(_result!.totalWithdrawn, maxDecimals: 4, addSymbol: true); - return QuantusBanner( - tone: BannerTone.sage, - message: l10n.redeemSuccessBanner(withdrawn, _result!.batchesSubmitted), - ); - } - - Widget? _buildBottomContent(AppLocalizations l10n) { - if (_running) { - return ScaffoldBaseBottomContent( - child: QuantusButton.simple(label: l10n.redeemCancel, variant: ButtonVariant.staged, onTap: _cancel), - ); - } - - if (_errorMessage != null) { - return ScaffoldBaseBottomContent( - child: Row( - children: [ - Expanded( - child: QuantusButton.simple(label: l10n.redeemRetry, onTap: _retry), - ), - const SizedBox(width: 12), - Expanded( - child: QuantusButton.simple( - label: l10n.redeemClose, - variant: ButtonVariant.staged, - onTap: () => Navigator.of(context).pop(), - ), - ), - ], - ), - ); - } - - return ScaffoldBaseBottomContent( - child: QuantusButton.simple( - label: l10n.redeemDone, - onTap: () => Navigator.of(context).popUntil((route) => route.isFirst), - ), - ); - } -} diff --git a/mobile-app/lib/v2/screens/settings/settings_screen.dart b/mobile-app/lib/v2/screens/settings/settings_screen.dart index 68352300e..04e180d0e 100644 --- a/mobile-app/lib/v2/screens/settings/settings_screen.dart +++ b/mobile-app/lib/v2/screens/settings/settings_screen.dart @@ -6,16 +6,13 @@ import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/generated/version.g.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; -import 'package:resonance_network_wallet/providers/mining_rewards_provider.dart'; import 'package:resonance_network_wallet/shared/constants/e2e_keys.dart'; -import 'package:resonance_network_wallet/shared/utils/print.dart'; import 'package:resonance_network_wallet/v2/screens/settings/about_quantus_screen.dart'; import 'package:resonance_network_wallet/v2/screens/settings/add_account_menu_screen.dart'; import 'package:resonance_network_wallet/v2/screens/settings/help_and_support_screen.dart'; 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'; class SettingsScreenV2 extends ConsumerStatefulWidget { @@ -29,8 +26,6 @@ class _SettingsScreenV2State extends ConsumerState { @override Widget build(BuildContext context) { final l10n = ref.watch(l10nProvider); - final miningAsync = ref.watch(miningRewardsProvider); - final trailing = SettingsTappableRowUtils.chevron(); final entries = _settingsHubItems(context, l10n); @@ -40,38 +35,20 @@ class _SettingsScreenV2State extends ConsumerState { mainContent: ListView( children: [ for (final e in entries.asMap().entries) ...[ - if (e.value.isMiningRewards) - miningAsync.when( - data: (data) => _buildTappableRow( - e.value, - subtitle: l10n.settingsMiningRewardsSubtitle(data.totalBlocks), - trailing: trailing, - ), - loading: () => _buildTappableRow(e.value, subtitle: l10n.commonLoading, trailing: trailing), - error: (err, st) { - quantusPrint('Error getting mining rewards: ${err.toString()}'); - quantusPrint('Stack trace: ${st.toString()}'); - - return _buildTappableRow(e.value, subtitle: l10n.settingsMiningRewardsError, trailing: trailing); - }, - ) - else - _buildTappableRow(e.value, trailing: trailing), + SettingsTappableRow( + key: e.value.rowKey, + 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.key < entries.length - 1) const SettingsDivider(), ], ], ), ); } - - Widget _buildTappableRow(_SettingsHubItem item, {required Widget trailing, String? subtitle}) => SettingsTappableRow( - key: item.rowKey, - leading: item.leading, - title: item.title, - subtitle: subtitle ?? item.subtitle, - onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => item.page)), - trailing: trailing, - ); } class _SettingsHubItem { @@ -80,7 +57,6 @@ class _SettingsHubItem { required this.title, required this.subtitle, required this.page, - this.isMiningRewards = false, this.rowKey, }); @@ -88,7 +64,6 @@ class _SettingsHubItem { final String title; final String subtitle; final Widget page; - final bool isMiningRewards; final Key? rowKey; } @@ -108,21 +83,6 @@ List<_SettingsHubItem> _settingsHubItems(BuildContext context, AppLocalizations subtitle: l10n.settingsPreferencesSubtitle, page: const PreferencesSettingsScreenV2(), ), - _SettingsHubItem( - leading: _settingsHubIcon( - colors, - svg: SvgPicture.asset( - 'assets/v2/axe.svg', - width: 18, - height: 18, - colorFilter: ColorFilter.mode(colors.accentFlare, BlendMode.srcIn), - ), - ), - title: l10n.settingsMiningRewards, - subtitle: l10n.commonLoading, - page: const MiningRewardsScreen(), - isMiningRewards: true, - ), _SettingsHubItem( leading: _settingsHubIcon(colors, icon: Icons.person_add_outlined), title: l10n.settingsAccountTypeTitle, diff --git a/mobile-app/lib/v2/screens/settings/testnet_rewards_screen.dart b/mobile-app/lib/v2/screens/settings/testnet_rewards_screen.dart deleted file mode 100644 index 3fb7303ee..000000000 --- a/mobile-app/lib/v2/screens/settings/testnet_rewards_screen.dart +++ /dev/null @@ -1,113 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/providers/l10n_provider.dart'; -import 'package:resonance_network_wallet/providers/mining_rewards_provider.dart'; -import 'package:resonance_network_wallet/services/mining_rewards_service.dart'; -import 'package:resonance_network_wallet/l10n/app_localizations.dart'; - -class TestnetRewardsScreen extends ConsumerWidget { - const TestnetRewardsScreen({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final l10n = ref.watch(l10nProvider); - final colors = context.colorsV3; - final text = context.themeTextV3; - final miningAsync = ref.watch(miningRewardsProvider); - - return ScaffoldBase( - appBar: V2AppBar(title: l10n.settingsTestnetTitle), - mainContent: miningAsync.when( - skipLoadingOnRefresh: false, - data: (data) => RefreshIndicator( - onRefresh: () async => ref.invalidate(miningRewardsProvider), - child: _buildContent(context, l10n, data, colors, text), - ), - loading: () => const Center(child: Loader()), - error: (_, _) => Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(l10n.settingsTestnetLoadError, style: text.body.copyWith(color: colors.textContent)), - const SizedBox(height: 8), - Text(l10n.settingsMiningCheckConnection, style: text.caption.copyWith(color: colors.textMuted)), - const SizedBox(height: 20), - GestureDetector( - onTap: () => ref.invalidate(miningRewardsProvider), - child: Text(l10n.commonTryAgain, style: text.body.copyWith(color: colors.accentFlare)), - ), - ], - ), - ), - ), - ); - } - - Widget _buildContent( - BuildContext context, - AppLocalizations l10n, - MiningRewardsData data, - AppColorsV3 colors, - AppTextThemeV3 text, - ) { - final testnets = [ - ('Planck', data.planckBlocks), - ('Dirac', data.diracBlocks), - ('Schrödinger', data.schrodingerBlocks), - ('Resonance', data.resonanceBlocks), - ]; - - return ListView( - physics: const AlwaysScrollableScrollPhysics(), - padding: EdgeInsets.zero, - children: [ - Container( - padding: const EdgeInsets.symmetric(vertical: 32, horizontal: 20), - decoration: BoxDecoration(color: colors.bgSurface, borderRadius: context.radiusV3.mdBorder), - child: Column( - children: [ - const Text('💰', style: TextStyle(fontSize: 40)), - const SizedBox(height: 12), - Text( - l10n.settingsTestnetTotalBlocks(data.totalBlocks), - style: text.displayBalance.copyWith(color: colors.semanticSage), - ), - const SizedBox(height: 8), - Text(l10n.settingsTestnetTotalDescription, style: text.caption.copyWith(color: colors.textMuted)), - ], - ), - ), - const SizedBox(height: 24), - Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Text(l10n.settingsTestnetBreakdown, style: text.headingRow.copyWith(color: colors.textContent)), - ), - Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration(color: colors.bgSurface, borderRadius: context.radiusV3.mdBorder), - child: Column( - children: [ - for (var i = 0; i < testnets.length; i++) ...[ - if (i > 0) const Padding(padding: EdgeInsets.only(top: 16, bottom: 24), child: MenuDivider()), - Row( - children: [ - Expanded( - child: Text(testnets[i].$1, style: text.body.copyWith(color: colors.textContent)), - ), - const Text('💰 ', style: TextStyle(fontSize: 14)), - Text( - l10n.settingsTestnetRowBlocks(testnets[i].$2), - style: text.bodyEmphasis.copyWith(color: colors.semanticSage), - ), - ], - ), - ], - ], - ), - ), - ], - ); - } -} diff --git a/mobile-app/patrol_test/support/app_launcher.dart b/mobile-app/patrol_test/support/app_launcher.dart index fb5cae399..532ac6003 100644 --- a/mobile-app/patrol_test/support/app_launcher.dart +++ b/mobile-app/patrol_test/support/app_launcher.dart @@ -7,7 +7,7 @@ import 'selectors.dart'; /// Launches the real app in a guaranteed-clean state for an E2E test. /// -/// This runs the production [bootstrap] (dotenv, Supabase, SDK, telemetry), +/// This runs the production [bootstrap] (dotenv, SDK, telemetry), /// then wipes all persisted wallet state via [AppState.reset] so the app starts /// at onboarding, and finally pumps the real widget tree. /// diff --git a/mobile-app/pubspec.lock b/mobile-app/pubspec.lock index 407c7ec6b..5810fcf4f 100644 --- a/mobile-app/pubspec.lock +++ b/mobile-app/pubspec.lock @@ -313,14 +313,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" - dart_jsonwebtoken: - dependency: transitive - description: - name: dart_jsonwebtoken - sha256: ad84e60181696513d04d5f2078e0bbc20365b911f46f647797317414bdc88fbe - url: "https://pub.dev" - source: hosted - version: "3.4.1" dart_style: dependency: transitive description: @@ -711,14 +703,6 @@ packages: description: flutter source: sdk version: "0.0.0" - functions_client: - dependency: transitive - description: - name: functions_client - sha256: "94074d62167ae634127ef6095f536835063a7dc80f2b1aa306d2346ff9023996" - url: "https://pub.dev" - source: hosted - version: "2.5.0" glob: dependency: transitive description: @@ -727,14 +711,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" - gotrue: - dependency: transitive - description: - name: gotrue - sha256: "7a4172601553e61716f5c3dd243aa3297e13308e07eb85b7853c941ba585dcf5" - url: "https://pub.dev" - source: hosted - version: "2.20.0" graphs: dependency: transitive description: @@ -933,14 +909,6 @@ packages: url: "https://pub.dev" source: hosted version: "5.2.2" - jwt_decode: - dependency: transitive - description: - name: jwt_decode - sha256: d2e9f68c052b2225130977429d30f187aa1981d789c76ad104a32243cfdebfbb - url: "https://pub.dev" - source: hosted - version: "0.3.1" leak_tracker: dependency: transitive description: @@ -1288,14 +1256,6 @@ packages: url: "https://pub.dev" source: hosted version: "6.5.0" - postgrest: - dependency: transitive - description: - name: postgrest - sha256: "9d61b3d4a88fcf9424d400127c54d49ed1b56ec30838fc0a33a64f31d4e694cc" - url: "https://pub.dev" - source: hosted - version: "2.7.0" process: dependency: transitive description: @@ -1359,14 +1319,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.3" - realtime_client: - dependency: transitive - description: - name: realtime_client - sha256: "7dfccf372d2f55aacfeefb6186f65a06f3ffae383fe042dbeef9d85d33487576" - url: "https://pub.dev" - source: hosted - version: "2.7.3" record_use: dependency: transitive description: @@ -1375,14 +1327,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.0" - retry: - dependency: transitive - description: - name: retry - sha256: "822e118d5b3aafed083109c72d5f484c6dc66707885e07c0fbcb8b986bba7efc" - url: "https://pub.dev" - source: hosted - version: "3.1.2" rfc_6901: dependency: transitive description: @@ -1438,14 +1382,6 @@ packages: relative: true source: path version: "0.0.1" - rxdart: - dependency: transitive - description: - name: rxdart - sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" - url: "https://pub.dev" - source: hosted - version: "0.28.0" secp256k1_ecdsa: dependency: transitive description: @@ -1630,14 +1566,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - storage_client: - dependency: transitive - description: - name: storage_client - sha256: "4801e8ca219a35e51cbb30589aba5306667ae8935b792504595a45273cef0b18" - url: "https://pub.dev" - source: hosted - version: "2.5.2" stream_channel: dependency: transitive description: @@ -1688,22 +1616,6 @@ packages: url: "https://github.com/Quantus-Network/polkadart.git" source: git version: "1.6.0" - supabase: - dependency: transitive - description: - name: supabase - sha256: "40e5a8833c8834e140ef53b60a6181849667eba9ca125acb7f8e24c6a769d418" - url: "https://pub.dev" - source: hosted - version: "2.10.6" - supabase_flutter: - dependency: "direct main" - description: - name: supabase_flutter - sha256: c02ce58abcaf86cb8055ad40bfd98bbf5b93fed3b5b56b8220d88ed03842818b - url: "https://pub.dev" - source: hosted - version: "2.12.4" sync_http: dependency: transitive description: @@ -2000,14 +1912,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.3" - yet_another_json_isolate: - dependency: transitive - description: - name: yet_another_json_isolate - sha256: fe45897501fa156ccefbfb9359c9462ce5dec092f05e8a56109db30be864f01e - url: "https://pub.dev" - source: hosted - version: "2.1.0" sdks: dart: ">=3.10.3 <4.0.0" flutter: ">=3.41.0" diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index b6eb61f8c..c2053f3c8 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -43,7 +43,6 @@ dependencies: # Config + backend flutter_dotenv: ^6.0.1 - supabase_flutter: ^2.12.4 play_install_referrer: ^0.5.0 # Notifications @@ -82,7 +81,6 @@ flutter: assets: - .env - assets/ - - assets/testnet_data/ - assets/text/human_checkphrase_final_wordlist.txt - assets/notification/ - assets/notification/notification_top_icon.png @@ -93,7 +91,6 @@ flutter: - assets/v2/action_swap.svg - assets/v2/uppercase_q.svg - assets/v2/uppercase_q.png - - assets/v2/axe.svg - assets/v2/swap_arrows_down_up.svg - assets/v2/swap_clock_counter_clockwise.svg - assets/v2/swap_qr_code.svg diff --git a/mobile-app/test/unit/mining_rewards_empty_state_test.dart b/mobile-app/test/unit/mining_rewards_empty_state_test.dart deleted file mode 100644 index 587358cfc..000000000 --- a/mobile-app/test/unit/mining_rewards_empty_state_test.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:resonance_network_wallet/providers/mining_rewards_provider.dart'; -import 'package:resonance_network_wallet/services/mining_rewards_service.dart'; -import 'package:resonance_network_wallet/v2/screens/settings/mining_rewards_screen.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - const colors = AppColorsV3.dark(); - - final noRewards = MiningRewardsData( - resonanceBlocks: 0, - schrodingerBlocks: 0, - diracBlocks: 0, - planckBlocks: 0, - planckRewards: BigInt.zero, - redeemedRewards: BigInt.zero, - redeemableRewards: BigInt.zero, - ); - - setUp(() async { - SharedPreferences.setMockInitialValues({'selected_app_locale': 'en'}); - await SettingsService().initialize(); - }); - - Color colorOf(WidgetTester tester, String text) => tester.widget(find.text(text)).style!.color!; - - testWidgets('the zero-blocks card keeps its placeholders on the muted tier', (tester) async { - await tester.pumpWidget( - ProviderScope( - overrides: [miningRewardsProvider.overrideWith((ref) async => noRewards)], - child: MediaQuery( - data: const MediaQueryData(size: Size(375, 667)), - child: Builder( - builder: (context) => MaterialApp(theme: AppTheme.darkTheme(context), home: const MiningRewardsScreen()), - ), - ), - ), - ); - await tester.pumpAndSettle(); - - expect(colorOf(tester, '0'), colors.textMuted); - expect(colorOf(tester, '0.00'), colors.textMuted); - }); -} diff --git a/mobile-app/test/unit/recipient_checksum_race_test.dart b/mobile-app/test/unit/recipient_checksum_race_test.dart index 896a95694..2780c81ec 100644 --- a/mobile-app/test/unit/recipient_checksum_race_test.dart +++ b/mobile-app/test/unit/recipient_checksum_race_test.dart @@ -11,7 +11,6 @@ import 'package:resonance_network_wallet/shared/constants/e2e_keys.dart'; import 'package:resonance_network_wallet/v2/components/address_input_field.dart'; import 'package:resonance_network_wallet/v2/screens/send/select_recipient_screen.dart'; import 'package:resonance_network_wallet/v2/screens/send/send_strategy.dart'; -import 'package:resonance_network_wallet/v2/screens/settings/redeem_address_screen.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../fakes.dart'; @@ -118,33 +117,4 @@ void main() { expect(input.recipientChecksum, _phraseB); }); }); - - group('RedeemAddressScreen check-phrase race', () { - testWidgets('late checksum for a previous address does not overwrite the current phrase', (tester) async { - final checksum = _DelayedAChecksumService(); - - await pumpScoped( - tester, - home: RedeemAddressScreen(redeemableRewards: BigInt.one), - overrides: [ - humanReadableChecksumServiceProvider.overrideWithValue(checksum), - substrateServiceProvider.overrideWithValue(FakeSubstrateService()), - ], - ); - - final field = find.descendant(of: find.byType(AddressInputField), matching: find.byType(TextField)); - await tester.enterText(field, _addressA); - await tester.pump(); - await tester.enterText(field, _addressB); - await tester.pump(); - - checksum.releaseA(); - await tester.pump(); - drainLayoutOverflow(tester); - - final input = tester.widget(find.byType(AddressInputField)); - expect(input.controller.text, _addressB); - expect(input.recipientChecksum, _phraseB); - }); - }); } diff --git a/mobile-app/test/unit/regular_send_strategy_test.dart b/mobile-app/test/unit/regular_send_strategy_test.dart index 0116ca1e2..5f4dbd0cd 100644 --- a/mobile-app/test/unit/regular_send_strategy_test.dart +++ b/mobile-app/test/unit/regular_send_strategy_test.dart @@ -1,5 +1,4 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_riverpod/misc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:quantus_sdk/generated/planck/pallets/balances.dart' as balances; import 'package:quantus_sdk/quantus_sdk.dart'; diff --git a/quantus_sdk/lib/quantus_sdk.dart b/quantus_sdk/lib/quantus_sdk.dart index 6167f4f53..6659a8219 100644 --- a/quantus_sdk/lib/quantus_sdk.dart +++ b/quantus_sdk/lib/quantus_sdk.dart @@ -38,7 +38,6 @@ export 'src/models/miner_reward_event.dart'; export 'src/models/multisig_creation_event.dart'; export 'src/models/multisig_created_event.dart'; export 'src/models/pending_multisig_creation_event.dart'; -export 'src/models/miner_stats.dart'; export 'src/models/opted_in_position.dart'; export 'src/models/pending_transfer_event.dart'; export 'src/models/reversible_transfer_status.dart'; diff --git a/quantus_sdk/lib/src/constants/app_constants.dart b/quantus_sdk/lib/src/constants/app_constants.dart index 441912dd4..a974a8eda 100644 --- a/quantus_sdk/lib/src/constants/app_constants.dart +++ b/quantus_sdk/lib/src/constants/app_constants.dart @@ -11,13 +11,11 @@ class AppConstants { // static const List rpcEndpoints = ['http://127.0.0.1:9944']; // local testing // static const List graphQlEndpoints = ['http://127.0.0.1:4350/v1/graphql']; // local testing - static const stillOnTestnet = true; - static const List rpcEndpoints = [ - 'https://a1-planck.quantus.cat', - 'https://a2-planck.quantus.cat', - 'https://matcha-latte.quantus.com', + static const List rpcEndpoints = ['https://rpc1-mainnet.quantus.com', 'https://rpc2-mainnet.quantus.com']; + static const List graphQlEndpoints = [ + 'https://subsquid-mainnet-app-1.quantus.com/v1/graphql', + 'https://subsquid-mainnet-app-2.quantus.com/v1/graphql', ]; - static const List graphQlEndpoints = ['https://sub2.quantus.com/v1/graphql']; static const String quersiEndpoint = 'https://qrc-1.quantus.com/api'; @@ -34,9 +32,6 @@ class AppConstants { static const String shillQuestsPageUrl = 'https://www.quantus.com/quests/shill'; 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/models/miner_stats.dart b/quantus_sdk/lib/src/models/miner_stats.dart deleted file mode 100644 index 54d8e1472..000000000 --- a/quantus_sdk/lib/src/models/miner_stats.dart +++ /dev/null @@ -1,6 +0,0 @@ -class MinerStats { - final int totalMinedBlocks; - final BigInt totalRewards; - - MinerStats({required this.totalMinedBlocks, required this.totalRewards}); -} diff --git a/quantus_sdk/lib/src/quantus_payload_parser.dart b/quantus_sdk/lib/src/quantus_payload_parser.dart index 7cd2e0265..7c0296eba 100644 --- a/quantus_sdk/lib/src/quantus_payload_parser.dart +++ b/quantus_sdk/lib/src/quantus_payload_parser.dart @@ -35,6 +35,7 @@ const int maxPayloadBytes = maxCallBytes; /// Purely informational: the signer signs for any genesis hash and shows the /// raw hash alongside the name when one is known. const Map knownNetworks = { + 'fb5487c0be6ae4ade2d41d16e50465129861636c2b8d61fa94d7a19631626fba': 'Mainnet', '4901bf5c57fd3f9e726af399c763de6670dbdb115a91c0237e173f16eef65e72': 'Planck', 'a5aa9e5c84d4a3722c152295e7973c9af522f2fb1ef7db5afaa3d5f4dc8d3b4f': 'Heisenberg', }; diff --git a/quantus_sdk/lib/src/services/network/redundant_endpoint.dart b/quantus_sdk/lib/src/services/network/redundant_endpoint.dart index 15afdb76c..7f05d2c2d 100644 --- a/quantus_sdk/lib/src/services/network/redundant_endpoint.dart +++ b/quantus_sdk/lib/src/services/network/redundant_endpoint.dart @@ -21,6 +21,18 @@ class Endpoint { Endpoint({required this.url, this.latency, this.lastSuccess, this.lastFailure}); } +/// An HTTP 5xx from an endpoint. Raised inside the failover loop so the next +/// endpoint is tried; a 4xx is the request's problem and is returned as is. +class EndpointServerError implements Exception { + final String url; + final int statusCode; + + EndpointServerError(this.url, this.statusCode); + + @override + String toString() => 'EndpointServerError: HTTP $statusCode from $url'; +} + class GraphQlEndpointService extends RedundantEndpointService { static final GraphQlEndpointService _instance = GraphQlEndpointService._internal(); @@ -29,6 +41,10 @@ class GraphQlEndpointService extends RedundantEndpointService { GraphQlEndpointService._internal() : super(endpoints: AppConstants.graphQlEndpoints.map((e) => Endpoint(url: e)).toList()); + /// A non-shared instance for a specific indexer, e.g. the chain a miner selected. + GraphQlEndpointService.forUrls(List urls, {super.client}) + : super(endpoints: urls.map((e) => Endpoint(url: e)).toList()); + static const int _transientRetries = 3; static const Duration _retryBackoff = Duration(milliseconds: 400); @@ -87,6 +103,10 @@ class RpcEndpointService extends RedundantEndpointService { RpcEndpointService._internal() : super(endpoints: AppConstants.rpcEndpoints.map((e) => Endpoint(url: e)).toList()); + /// A non-shared instance for a specific node, e.g. the chain a miner selected. + RpcEndpointService.forUrls(List urls, {super.client}) + : super(endpoints: urls.map((e) => Endpoint(url: e)).toList()); + String get bestEndpointUrl => endpoints.first.url; final Map _providers = {}; @@ -113,9 +133,9 @@ class RedundantEndpointService { final List endpoints; /// Shared client so plain HTTP calls reuse keep-alive connections too. - final http.Client _httpClient = http.Client(); + final http.Client _httpClient; - RedundantEndpointService({required this.endpoints}); + RedundantEndpointService({required this.endpoints, http.Client? client}) : _httpClient = client ?? http.Client(); Map _mergedHeaders(Map? headers) { return {'Content-Type': 'application/json', ...?headers}; @@ -178,13 +198,24 @@ class RedundantEndpointService { } } + static http.Response _failOverOnServerError(String url, http.Response response) { + if (response.statusCode >= 500) throw EndpointServerError(url, response.statusCode); + return response; + } + Future get(String path, {Map? headers}) async { - return _executeTask((url) => _httpClient.get(Uri.parse('$url$path'), headers: _mergedHeaders(headers))); + return _executeTask( + (url) async => + _failOverOnServerError(url, await _httpClient.get(Uri.parse('$url$path'), headers: _mergedHeaders(headers))), + ); } Future post({String? path, Map? headers, String? body}) async { return _executeTask( - (url) => _httpClient.post(Uri.parse('$url${(path ?? '')}'), body: body, headers: _mergedHeaders(headers)), + (url) async => _failOverOnServerError( + url, + await _httpClient.post(Uri.parse('$url${(path ?? '')}'), body: body, headers: _mergedHeaders(headers)), + ), ); } } diff --git a/quantus_sdk/lib/src/services/quersi_service.dart b/quantus_sdk/lib/src/services/quersi_service.dart index facec5473..8d577a7c7 100644 --- a/quantus_sdk/lib/src/services/quersi_service.dart +++ b/quantus_sdk/lib/src/services/quersi_service.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:quantus_sdk/src/models/exchange_rates_result.dart'; -import 'package:quantus_sdk/src/utils/print.dart'; // Quersi service singleton class QuersiService { @@ -14,28 +13,6 @@ class QuersiService { factory QuersiService() => _instance; QuersiService._internal(); - final SettingsService _settingsService = SettingsService(); - final HdWalletService _hd = HdWalletService(); - - Future getMiningAccountId() async { - final mnemonic = await _settingsService.getMnemonic(0); - if (mnemonic == null) { - throw Exception('Mnemonic not found.'); - } - final address = _hd.deriveWormholeKeyPair(mnemonic: mnemonic).address; - return address; - } - - // In the past in the beginnings some people mined with a non-derived account - Future getOldMiningAccountId() async { - final mnemonic = await _settingsService.getMnemonic(0); - if (mnemonic == null) { - throw Exception('Mnemonic not found.'); - } - final rawKeyPair = SubstrateService().nonHDdilithiumKeypairFromMnemonic(mnemonic); - return rawKeyPair.ss58Address; - } - Future getRemoteConfig() async { final http.Response response = await http.get( _remoteConfigsEndpoint, @@ -73,59 +50,4 @@ class QuersiService { return ExchangeRatesResult.fromJson(data); } - - Future getMinerStats() async { - final String minerStatsQuery = r''' - query MinerStats($ids: [String!]!) { - minerStats: account_stats(where: {id: {_in: $ids}}) { - totalMinedBlocks: total_mined_blocks - totalRewards: total_rewards - id - } - } - '''; - - final miningAccountId = await getMiningAccountId(); - final List accountIds = [miningAccountId]; - - final Map requestBody = { - 'query': minerStatsQuery, - 'variables': {'ids': accountIds}, - }; - - try { - final http.Response response = await GraphQlEndpointService().post(body: jsonEncode(requestBody)); - - if (response.statusCode != 200) { - throw Exception('GraphQL request failed with status: ${response.statusCode}. Body: ${response.body}'); - } - - final Map responseBody = jsonDecode(response.body); - if (responseBody['errors'] != null) { - throw Exception('GraphQL errors: ${responseBody['errors']}'); - } - - final Map data = responseBody['data']; - - final List? minerStatsList = data['minerStats']; - if (minerStatsList == null || minerStatsList.isEmpty) { - return MinerStats(totalMinedBlocks: 0, totalRewards: BigInt.zero); - } - - // Aggregate stats across all accounts - int totalMinedBlocks = 0; - BigInt totalRewards = BigInt.zero; - - for (final stats in minerStatsList) { - totalMinedBlocks += int.parse(stats['totalMinedBlocks'].toString()); - totalRewards += BigInt.parse(stats['totalRewards'].toString()); - } - - return MinerStats(totalMinedBlocks: totalMinedBlocks, totalRewards: totalRewards); - } catch (e, stackTrace) { - quantusPrint('Error fetching miner stats: $e'); - quantusPrint('$stackTrace'); - rethrow; - } - } } diff --git a/quantus_sdk/lib/src/services/substrate_service.dart b/quantus_sdk/lib/src/services/substrate_service.dart index 63287a371..b1a5ae8b9 100644 --- a/quantus_sdk/lib/src/services/substrate_service.dart +++ b/quantus_sdk/lib/src/services/substrate_service.dart @@ -128,9 +128,6 @@ class SubstrateService { // Legacy method - supports CLI addresses and Miner App // The mobile app should use @HdWalletService for everything. - crypto.Keypair nonHDdilithiumKeypairFromMnemonic(String senderSeed) { - return crypto.generateKeypair(mnemonicStr: senderSeed); - } Future getFeeForCall(Account account, RuntimeCall call) async { // We use a dummy signature for fee estimation to avoid prompting for password/device. diff --git a/quantus_sdk/lib/src/services/wormhole_utxo_service.dart b/quantus_sdk/lib/src/services/wormhole_utxo_service.dart index 3d7d98b1e..b2bdb1d12 100644 --- a/quantus_sdk/lib/src/services/wormhole_utxo_service.dart +++ b/quantus_sdk/lib/src/services/wormhole_utxo_service.dart @@ -128,10 +128,11 @@ class WormholeUtxoService { @visibleForTesting static const int transferPageSize = 300; - /// v2 drops v1 files that may have omitted a same-height sibling and then - /// advanced `cachedUpToBlock` past that height. + /// Generation of both on-disk caches; bump on any format change. v3 keys the + /// files by network as well as address, so Planck-era files are dropped + /// instead of being read against mainnet. @visibleForTesting - static const int transferCacheVersion = 2; + static const int cacheVersion = 3; static const int _nullifierBatchSize = 300; static const int _reorgDepth = 180; @@ -157,8 +158,13 @@ query TransfersToAddresses($tos: [String!]!, $limit: Int!, $offset: Int!, $after } }'''; - final GraphQlEndpointService _graphQlEndpoint = GraphQlEndpointService(); - final RpcEndpointService _rpcEndpoint = RpcEndpointService(); + final GraphQlEndpointService _graphQlEndpoint; + final RpcEndpointService _rpcEndpoint; + + /// Defaults to the app-wide endpoints; pass both to discover on another chain. + WormholeUtxoService({GraphQlEndpointService? graphQl, RpcEndpointService? rpc}) + : _graphQlEndpoint = graphQl ?? GraphQlEndpointService(), + _rpcEndpoint = rpc ?? RpcEndpointService(); static void _log(String msg) => quantusPrint('[WormholeUtxo] $msg'); @@ -172,51 +178,68 @@ query TransfersToAddresses($tos: [String!]!, $limit: Int!, $offset: Int!, $after // --- Cache --- - static String _cachePrefix(String addressHash) => addressHash.substring(0, 16); + static const int _idLength = 16; - static Future _transferCacheFile(String addressHash) async { - final dir = await getApplicationSupportDirectory(); - return File('${dir.path}/wormhole_cache_v${transferCacheVersion}_${_cachePrefix(addressHash)}.json'); - } + String? _networkId; - /// Bumped to v2 to drop any pre-finalization-filter caches that may contain - /// nullifiers from reorged-out blocks. Old `wormhole_nullifiers_.json` - /// files are best-effort deleted on first read. - static Future _nullifierCacheFile(String addressHash) async { - final dir = await getApplicationSupportDirectory(); - return File('${dir.path}/wormhole_nullifiers_v2_${_cachePrefix(addressHash)}.json'); + /// Namespace of this chain's caches: the first 16 hex chars of its genesis + /// hash, so a chain switch never reads another chain's transfers, scan + /// height, or spent nullifiers. Fetched once per instance. + @visibleForTesting + Future networkId() async { + if (_networkId != null) return _networkId!; + final result = await _rpc('chain_getBlockHash', [0]); + final hash = result is String ? result.replaceFirst('0x', '') : ''; + if (hash.length < _idLength) throw Exception('chain_getBlockHash(0) returned no genesis hash: $result'); + return _networkId = hash.substring(0, _idLength); } - static Future _deleteLegacyNullifierCache(String addressHash) async { - try { - final dir = await getApplicationSupportDirectory(); - final legacy = File('${dir.path}/wormhole_nullifiers_${_cachePrefix(addressHash)}.json'); - if (await legacy.exists()) { - await legacy.delete(); - _log('Deleted legacy nullifier cache: ${legacy.path}'); - } - } catch (e) { - _log('Legacy nullifier cache delete failed (non-fatal): $e'); - } + static String _cachePrefix(String addressHash) => addressHash.substring(0, _idLength); + + static String _cacheName(String kind, String networkId, String prefix) => + 'wormhole_${kind}_v${cacheVersion}_${networkId}_$prefix.json'; + + static bool _isCacheFileFor(String name, String prefix) => + (name.startsWith('wormhole_cache_') || name.startsWith('wormhole_nullifiers')) && name.endsWith('_$prefix.json'); + + static final RegExp _currentGeneration = RegExp( + '^wormhole_(cache|nullifiers)_v${cacheVersion}_[0-9a-fA-F]{$_idLength}_[0-9a-fA-F]{$_idLength}\\.json\$', + ); + + static String _fileName(FileSystemEntity entity) => + entity.uri.pathSegments.isEmpty ? entity.path : entity.uri.pathSegments.last; + + Future _cacheFile(String kind, String addressHash) async { + final dir = await getApplicationSupportDirectory(); + return File('${dir.path}/${_cacheName(kind, await networkId(), _cachePrefix(addressHash))}'); } - static Future _deleteLegacyTransferCache(String addressHash) async { + /// Deletes every cache file of [addressHash] from an earlier generation. + /// Current-generation files of other networks stay, so switching chains + /// back and forth does not rescan. + @visibleForTesting + static Future deleteStaleCaches(String addressHash) async { try { + final prefix = _cachePrefix(addressHash); final dir = await getApplicationSupportDirectory(); - final legacy = File('${dir.path}/wormhole_cache_${_cachePrefix(addressHash)}.json'); - if (await legacy.exists()) { - await legacy.delete(); - _log('Deleted legacy transfer cache: ${legacy.path}'); + if (!await dir.exists()) return; + await for (final entity in dir.list()) { + if (entity is! File) continue; + final name = _fileName(entity); + if (_isCacheFileFor(name, prefix) && !_currentGeneration.hasMatch(name)) { + await entity.delete(); + _log('Deleted stale cache: $name'); + } } } catch (e) { - _log('Legacy transfer cache delete failed (non-fatal): $e'); + _log('Stale cache delete failed (non-fatal): $e'); } } - static Future<_TransferCache> _loadTransferCache(String addressHash) async { - await _deleteLegacyTransferCache(addressHash); + Future<_TransferCache> _loadTransferCache(String addressHash) async { + await deleteStaleCaches(addressHash); try { - final file = await _transferCacheFile(addressHash); + final file = await _cacheFile('cache', addressHash); if (!await file.exists()) return _TransferCache.empty(); final json = jsonDecode(await file.readAsString()) as Map; return _TransferCache.fromJson(json); @@ -226,9 +249,12 @@ query TransfersToAddresses($tos: [String!]!, $limit: Int!, $offset: Int!, $after } } - static Future _saveTransferCache(String addressHash, _TransferCache cache) async { + @visibleForTesting + Future cachedTransferHeight(String addressHash) async => (await _loadTransferCache(addressHash)).cachedUpToBlock; + + Future _saveTransferCache(String addressHash, _TransferCache cache) async { try { - final file = await _transferCacheFile(addressHash); + final file = await _cacheFile('cache', addressHash); await file.writeAsString(jsonEncode(cache.toJson())); _log('Transfer cache saved: ${cache.transfers.length} transfers up to block ${cache.cachedUpToBlock}'); } catch (e) { @@ -236,10 +262,11 @@ query TransfersToAddresses($tos: [String!]!, $limit: Int!, $offset: Int!, $after } } - static Future> _loadSpentNullifiers(String addressHash) async { - await _deleteLegacyNullifierCache(addressHash); + @visibleForTesting + Future> loadSpentNullifiers(String addressHash) async { + await deleteStaleCaches(addressHash); try { - final file = await _nullifierCacheFile(addressHash); + final file = await _cacheFile('nullifiers', addressHash); if (!await file.exists()) return {}; final list = jsonDecode(await file.readAsString()) as List; return list.cast().toSet(); @@ -249,9 +276,10 @@ query TransfersToAddresses($tos: [String!]!, $limit: Int!, $offset: Int!, $after } } - static Future _saveSpentNullifiers(String addressHash, Set spent) async { + @visibleForTesting + Future saveSpentNullifiers(String addressHash, Set spent) async { try { - final file = await _nullifierCacheFile(addressHash); + final file = await _cacheFile('nullifiers', addressHash); await file.writeAsString(jsonEncode(spent.toList())); _log('Nullifier cache saved: ${spent.length} spent nullifiers'); } catch (e) { @@ -268,14 +296,8 @@ query TransfersToAddresses($tos: [String!]!, $limit: Int!, $offset: Int!, $after var deleted = 0; await for (final entity in dir.list()) { if (entity is! File) continue; - final name = entity.uri.pathSegments.isEmpty ? entity.path : entity.uri.pathSegments.last; - final matchesPrefix = prefixes.any( - (p) => - name == 'wormhole_cache_$p.json' || - name == 'wormhole_cache_v${transferCacheVersion}_$p.json' || - name == 'wormhole_nullifiers_v2_$p.json', - ); - if (matchesPrefix) { + final name = _fileName(entity); + if (prefixes.any((p) => _isCacheFileFor(name, p))) { await entity.delete(); deleted++; } @@ -295,7 +317,7 @@ query TransfersToAddresses($tos: [String!]!, $limit: Int!, $offset: Int!, $after var deleted = 0; await for (final entity in dir.list()) { if (entity is! File) continue; - final name = entity.uri.pathSegments.isEmpty ? entity.path : entity.uri.pathSegments.last; + final name = _fileName(entity); if (name.startsWith('wormhole_cache_') || name.startsWith('wormhole_nullifiers')) { await entity.delete(); deleted++; @@ -307,23 +329,28 @@ query TransfersToAddresses($tos: [String!]!, $limit: Int!, $offset: Int!, $after } } - // --- Block height --- + // --- RPC --- - /// Current chain head (best block) height. Throws if RPC fails — callers must - /// not advance the cache from a fabricated value. - Future _getChainHeight() async { - final body = jsonEncode({'jsonrpc': '2.0', 'id': 1, 'method': 'chain_getHeader', 'params': []}); + Future _rpc(String method, List params) async { + final body = jsonEncode({'jsonrpc': '2.0', 'id': 1, 'method': method, 'params': params}); final response = await _rpcEndpoint.post(body: body); if (response.statusCode != 200) { - throw Exception('chain_getHeader HTTP ${response.statusCode}: ${response.body}'); + throw Exception('$method HTTP ${response.statusCode}: ${response.body}'); } final parsed = jsonDecode(response.body) as Map; if (parsed['error'] != null) { - throw Exception('chain_getHeader RPC error: ${parsed['error']}'); + throw Exception('$method RPC error: ${parsed['error']}'); } - final numberHex = parsed['result']?['number'] as String?; + return parsed['result']; + } + + /// Current chain head (best block) height. Throws if RPC fails — callers must + /// not advance the cache from a fabricated value. + Future _getChainHeight() async { + final result = await _rpc('chain_getHeader', []); + final numberHex = result is Map ? result['number'] as String? : null; if (numberHex == null) { - throw Exception('chain_getHeader returned no number: ${response.body}'); + throw Exception('chain_getHeader returned no number: $result'); } final height = int.parse(numberHex.replaceFirst('0x', ''), radix: 16); _log('Chain height from RPC: $height'); @@ -647,7 +674,7 @@ query SpentNullifiers($hashes: [String!]!) { for (final owner in addresses) { final ownerHash = _addressHashOf(owner.address); - final cachedSpent = await _loadSpentNullifiers(ownerHash); + final cachedSpent = await loadSpentNullifiers(ownerHash); cachedSpentByOwner[ownerHash] = cachedSpent; allSpent.addAll(cachedSpent); @@ -705,7 +732,7 @@ query SpentNullifiers($hashes: [String!]!) { } for (final entry in toPersistByOwner.entries) { if (entry.value.length != cachedSpentByOwner[entry.key]!.length) { - await _saveSpentNullifiers(entry.key, entry.value); + await saveSpentNullifiers(entry.key, entry.value); } } _log('Nullifier persistence: skipped $unfinalizedCount above cutoff $safeCutoff'); diff --git a/quantus_sdk/pubspec.lock b/quantus_sdk/pubspec.lock index 64fff7b20..112539b80 100644 --- a/quantus_sdk/pubspec.lock +++ b/quantus_sdk/pubspec.lock @@ -609,7 +609,7 @@ packages: source: hosted version: "2.2.1" path_provider_platform_interface: - dependency: transitive + dependency: "direct dev" description: name: path_provider_platform_interface sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" @@ -641,7 +641,7 @@ packages: source: hosted version: "3.1.6" plugin_platform_interface: - dependency: transitive + dependency: "direct dev" description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" diff --git a/quantus_sdk/pubspec.yaml b/quantus_sdk/pubspec.yaml index 65a8dfdd8..45a9b10e4 100644 --- a/quantus_sdk/pubspec.yaml +++ b/quantus_sdk/pubspec.yaml @@ -65,6 +65,8 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + path_provider_platform_interface: ^2.1.0 + plugin_platform_interface: ^2.1.0 integration_test: sdk: flutter flutter_lints: ^6.0.0 diff --git a/quantus_sdk/test/services/redundant_endpoint_failover_test.dart b/quantus_sdk/test/services/redundant_endpoint_failover_test.dart new file mode 100644 index 000000000..2c57deb59 --- /dev/null +++ b/quantus_sdk/test/services/redundant_endpoint_failover_test.dart @@ -0,0 +1,62 @@ +import 'dart:convert'; + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:quantus_sdk/src/services/network/redundant_endpoint.dart'; + +const _first = 'https://app-1.test'; +const _second = 'https://app-2.test'; + +/// Answers each host with its configured status; the body names the host. +GraphQlEndpointService _service(Map statusByUrl) => GraphQlEndpointService.forUrls( + statusByUrl.keys.toList(), + client: MockClient((request) async { + final url = '${request.url.scheme}://${request.url.host}'; + return http.Response( + jsonEncode({ + 'data': {'host': url}, + }), + statusByUrl[url]!, + ); + }), +); + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + // The failure logger consults connectivity_plus; give it a wifi answer and a silent status stream. + final messenger = TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger; + messenger.setMockMethodCallHandler( + const MethodChannel('dev.fluttercommunity.plus/connectivity'), + (_) async => ['wifi'], + ); + messenger.setMockMethodCallHandler( + const MethodChannel('dev.fluttercommunity.plus/connectivity_status'), + (_) async => null, + ); + }); + + test('a 502 from the first endpoint fails over to the second', () async { + final response = await _service({_first: 502, _second: 200}).post(body: '{}'); + expect(response.statusCode, 200); + expect(jsonDecode(response.body)['data']['host'], _second); + }); + + test('query() sees the healthy endpoint instead of the 502', () async { + final data = await _service({_first: 502, _second: 200}).query(document: '{ x }'); + expect(data['host'], _second); + }); + + test('a client error is the request\'s problem and is not retried elsewhere', () async { + final response = await _service({_first: 400, _second: 200}).post(body: '{}'); + expect(response.statusCode, 400); + expect(jsonDecode(response.body)['data']['host'], _first); + }); + + test('every endpoint failing surfaces the server error', () { + expect(_service({_first: 502, _second: 503}).post(body: '{}'), throwsA(isA())); + }); +} diff --git a/quantus_sdk/test/services/wormhole_cache_generation_test.dart b/quantus_sdk/test/services/wormhole_cache_generation_test.dart new file mode 100644 index 000000000..13617be06 --- /dev/null +++ b/quantus_sdk/test/services/wormhole_cache_generation_test.dart @@ -0,0 +1,109 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +import 'package:quantus_sdk/src/services/network/redundant_endpoint.dart'; +import 'package:quantus_sdk/src/services/wormhole_utxo_service.dart'; + +class _FakePathProvider extends Fake with MockPlatformInterfaceMixin implements PathProviderPlatform { + _FakePathProvider(this.path); + final String path; + + @override + Future getApplicationSupportPath() async => path; +} + +const _hash = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; +const _prefix = '0123456789abcdef'; +const _planck = 'aaaaaaaaaaaaaaaa'; +const _mainnet = 'bbbbbbbbbbbbbbbb'; +final _v = WormholeUtxoService.cacheVersion; + +/// Discovery bound to a chain whose genesis hash starts with [networkId]. The +/// indexer is never consulted here. +WormholeUtxoService _serviceOn(String networkId) => WormholeUtxoService( + graphQl: GraphQlEndpointService.forUrls([ + 'https://indexer.test', + ], client: MockClient((_) async => throw StateError('indexer must not be queried'))), + rpc: RpcEndpointService.forUrls( + ['https://rpc.test'], + client: MockClient((request) async { + expect(jsonDecode(request.body)['method'], 'chain_getBlockHash'); + return http.Response(jsonEncode({'jsonrpc': '2.0', 'id': 1, 'result': '0x${networkId * 4}'}), 200); + }), + ), +); + +void main() { + late Directory dir; + + setUp(() async { + dir = await Directory.systemTemp.createTemp('wormhole-cache-'); + PathProviderPlatform.instance = _FakePathProvider(dir.path); + }); + + tearDown(() => dir.delete(recursive: true)); + + File write(String name, String content) => File('${dir.path}/$name')..writeAsStringSync(content); + Set names() => dir.listSync().map((e) => e.uri.pathSegments.last).toSet(); + + test('Planck-era caches are neither read nor kept after the generation bump', () async { + // Everything a released wallet may have written before the mainnet switch. + write('wormhole_nullifiers_v2_$_prefix.json', '["0xspent-on-planck"]'); + write('wormhole_nullifiers_$_prefix.json', '["0xolder"]'); + write('wormhole_cache_v2_$_prefix.json', '{"cachedUpToBlock":123456,"transfers":[]}'); + write('wormhole_cache_$_prefix.json', '{"cachedUpToBlock":1,"transfers":[]}'); + final otherAddress = write('wormhole_nullifiers_v2_ffffffffffffffff.json', '["0x01"]'); + + expect(await _serviceOn(_mainnet).loadSpentNullifiers(_hash), isEmpty); + expect(names(), {otherAddress.uri.pathSegments.last}); + }); + + test('current-generation files of every network survive stale cleanup', () async { + write('wormhole_nullifiers_v${_v}_${_planck}_$_prefix.json', '["0x02"]'); + write('wormhole_cache_v${_v}_${_mainnet}_$_prefix.json', '{"cachedUpToBlock":7,"transfers":[]}'); + + await WormholeUtxoService.deleteStaleCaches(_hash); + + expect(names(), { + 'wormhole_nullifiers_v${_v}_${_planck}_$_prefix.json', + 'wormhole_cache_v${_v}_${_mainnet}_$_prefix.json', + }); + expect(await _serviceOn(_planck).loadSpentNullifiers(_hash), {'0x02'}); + }); + + test( + 'switching chains within one generation reads neither the other chain\'s scan height nor its nullifiers', + () async { + await _serviceOn(_planck).saveSpentNullifiers(_hash, {'0xspent-on-planck'}); + write('wormhole_cache_v${_v}_${_planck}_$_prefix.json', '{"cachedUpToBlock":123456,"transfers":[]}'); + + final mainnet = _serviceOn(_mainnet); + expect(await mainnet.loadSpentNullifiers(_hash), isEmpty); + expect(await mainnet.cachedTransferHeight(_hash), 0); + + // Planck's files were namespaced, not cleared: switching back does not rescan. + final planck = _serviceOn(_planck); + expect(await planck.loadSpentNullifiers(_hash), {'0xspent-on-planck'}); + expect(await planck.cachedTransferHeight(_hash), 123456); + expect(names(), { + 'wormhole_nullifiers_v${_v}_${_planck}_$_prefix.json', + 'wormhole_cache_v${_v}_${_planck}_$_prefix.json', + }); + }, + ); + + test('clearCachesForAddresses removes every generation of that address only', () async { + write('wormhole_cache_$_prefix.json', '{}'); + write('wormhole_cache_v2_$_prefix.json', '{}'); + write('wormhole_nullifiers_v${_v}_${_planck}_$_prefix.json', '[]'); + write('wormhole_nullifiers_v2_ffffffffffffffff.json', '[]'); + + await WormholeUtxoService.clearCachesForAddresses([]); + expect(names(), hasLength(4)); + }); +} diff --git a/quantus_sdk/test/services/wormhole_utxo_pagination_test.dart b/quantus_sdk/test/services/wormhole_utxo_pagination_test.dart index 06efd4782..652599f73 100644 --- a/quantus_sdk/test/services/wormhole_utxo_pagination_test.dart +++ b/quantus_sdk/test/services/wormhole_utxo_pagination_test.dart @@ -88,7 +88,7 @@ void main() { expect(ids.toSet(), hasLength(ids.length), reason: 'pages must not overlap on a tied height'); }); - test('transfer cache is versioned so a prior height-only miss is re-walked', () { - expect(WormholeUtxoService.transferCacheVersion, 2); + test('caches are generation-versioned so a network switch never reads the previous chain', () { + expect(WormholeUtxoService.cacheVersion, 3); }); } diff --git a/test_qr_payload.sh b/test_qr_payload.sh index d72139c2e..64ab16cd7 100755 --- a/test_qr_payload.sh +++ b/test_qr_payload.sh @@ -11,9 +11,9 @@ # Keystone scanner, and scan what this prints. set -euo pipefail -# The live Planck node, so the request carries a genesis hash the wallets know. +# The live mainnet node, so the request carries a genesis hash the wallets know. # A local dev chain has its own genesis and every wallet refuses it. -NODE_URL="${NODE_URL:-wss://a1-planck.quantus.cat}" +NODE_URL="${NODE_URL:-wss://rpc1-mainnet.quantus.com}" # A wallet whose bundled metadata came from a different runtime than the node # runs says so on its review screen. That warning is the wallet working, not a