From 02b8f82f24d1787f66df3339f886227c3394a78e Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 9 Sep 2026 17:22:18 +0800 Subject: [PATCH 1/6] Switch RPC and Subsquid endpoints to mainnet rpc1/rpc2-mainnet.quantus.com and subsquid-mainnet-app-1/2.quantus.com replace the Planck nodes and sub2. Drops the unused stillOnTestnet flag, points polkadart codegen and test_qr_payload.sh at the mainnet node, and adds the mainnet genesis hash to knownNetworks so signers name it. --- quantus_sdk/lib/src/constants/app_constants.dart | 10 ++++------ quantus_sdk/lib/src/quantus_payload_parser.dart | 1 + quantus_sdk/pubspec.yaml | 2 +- test_qr_payload.sh | 4 ++-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/quantus_sdk/lib/src/constants/app_constants.dart b/quantus_sdk/lib/src/constants/app_constants.dart index 441912dd..99ffb08b 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'; diff --git a/quantus_sdk/lib/src/quantus_payload_parser.dart b/quantus_sdk/lib/src/quantus_payload_parser.dart index 7cd2e026..7c0296eb 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/pubspec.yaml b/quantus_sdk/pubspec.yaml index 65a8dfdd..fc2e0b9e 100644 --- a/quantus_sdk/pubspec.yaml +++ b/quantus_sdk/pubspec.yaml @@ -89,7 +89,7 @@ dev_dependencies: polkadart: output_dir: lib/generated chains: - planck: wss://a1-planck.quantus.cat + planck: wss://rpc1-mainnet.quantus.com flutter: assets: diff --git a/test_qr_payload.sh b/test_qr_payload.sh index d72139c2..64ab16cd 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 From da7b1d833924f1bcb4328fb8d659d25f2a7c5a06 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 9 Sep 2026 18:26:59 +0800 Subject: [PATCH 2/6] Remove faucet prompt, Mining Rewards, and testnet strings The home screen no longer offers testnet tokens on a zero balance; there is no faucet. The Mining Rewards settings item goes away with its screens, redeem flow, provider, service, bundled testnet miner lists, and l10n. The only Supabase consumer was that service, so its init and dependency go too, along with the SDK miner-stats helpers nothing else called. --- .../assets/testnet_data/dirac_miners.json | 2001 ----------------- .../resonance_network_miners.json | 501 ----- .../testnet_data/schrodinger_miners.json | 416 ---- mobile-app/assets/v2/axe.svg | 3 - mobile-app/lib/bootstrap/app_bootstrap.dart | 11 +- mobile-app/lib/l10n/app_en.arb | 268 --- mobile-app/lib/l10n/app_id.arb | 55 - mobile-app/lib/l10n/app_localizations.dart | 330 --- mobile-app/lib/l10n/app_localizations_en.dart | 183 -- mobile-app/lib/l10n/app_localizations_id.dart | 183 -- .../providers/mining_rewards_provider.dart | 36 - mobile-app/lib/services/logout_service.dart | 3 - .../lib/services/mining_rewards_service.dart | 134 -- mobile-app/lib/shared/utils/env_utils.dart | 22 - mobile-app/lib/shared/utils/url_utils.dart | 13 - .../lib/v2/screens/home/home_screen.dart | 33 +- .../settings/mining_rewards_screen.dart | 438 ---- .../settings/redeem_address_screen.dart | 218 -- .../settings/redeem_progress_screen.dart | 223 -- .../v2/screens/settings/settings_screen.dart | 56 +- .../settings/testnet_rewards_screen.dart | 113 - .../patrol_test/support/app_launcher.dart | 2 +- mobile-app/pubspec.lock | 96 - mobile-app/pubspec.yaml | 3 - .../unit/mining_rewards_empty_state_test.dart | 49 - .../unit/recipient_checksum_race_test.dart | 30 - quantus_sdk/lib/quantus_sdk.dart | 1 - .../lib/src/constants/app_constants.dart | 3 - quantus_sdk/lib/src/models/miner_stats.dart | 6 - .../lib/src/services/quersi_service.dart | 78 - .../lib/src/services/substrate_service.dart | 3 - 31 files changed, 21 insertions(+), 5490 deletions(-) delete mode 100644 mobile-app/assets/testnet_data/dirac_miners.json delete mode 100644 mobile-app/assets/testnet_data/resonance_network_miners.json delete mode 100644 mobile-app/assets/testnet_data/schrodinger_miners.json delete mode 100644 mobile-app/assets/v2/axe.svg delete mode 100644 mobile-app/lib/providers/mining_rewards_provider.dart delete mode 100644 mobile-app/lib/services/mining_rewards_service.dart delete mode 100644 mobile-app/lib/v2/screens/settings/mining_rewards_screen.dart delete mode 100644 mobile-app/lib/v2/screens/settings/redeem_address_screen.dart delete mode 100644 mobile-app/lib/v2/screens/settings/redeem_progress_screen.dart delete mode 100644 mobile-app/lib/v2/screens/settings/testnet_rewards_screen.dart delete mode 100644 mobile-app/test/unit/mining_rewards_empty_state_test.dart delete mode 100644 quantus_sdk/lib/src/models/miner_stats.dart 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 61395f47..00000000 --- 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 c5fadaff..00000000 --- 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 34b472d9..00000000 --- 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 8c267333..00000000 --- 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 3b271962..67960661 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 4160c486..3de452f0 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 89a73456..d0eb7d2e 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 a4883e51..3e1d1083 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 24631601..e531bda9 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 c1aa50c6..8d2dd634 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 8baed724..00000000 --- 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 ec301e76..2ae43460 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 fb0f330c..00000000 --- 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 fab68413..1a6f8687 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 d74d3c9d..264f4feb 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 3796208f..a16e415b 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 8c7c2afa..00000000 --- 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 d968bb94..00000000 --- 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 fe537e57..00000000 --- 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 68352300..04e180d0 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 3fb7303e..00000000 --- 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 fb5cae39..532ac600 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 407c7ec6..5810fcf4 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 b6eb61f8..c2053f3c 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 587358cf..00000000 --- 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 896a9569..2780c81e 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/quantus_sdk/lib/quantus_sdk.dart b/quantus_sdk/lib/quantus_sdk.dart index 6167f4f5..6659a821 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 99ffb08b..a974a8ed 100644 --- a/quantus_sdk/lib/src/constants/app_constants.dart +++ b/quantus_sdk/lib/src/constants/app_constants.dart @@ -32,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 54d8e147..00000000 --- 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/services/quersi_service.dart b/quantus_sdk/lib/src/services/quersi_service.dart index facec547..8d577a7c 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 63287a37..b1a5ae8b 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. From f0edaf046056872a2defec6e76fdbb7d8a83726b Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 9 Sep 2026 18:27:30 +0800 Subject: [PATCH 3/6] Keep polkadart codegen source on Planck until the metadata follow-up --- quantus_sdk/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantus_sdk/pubspec.yaml b/quantus_sdk/pubspec.yaml index fc2e0b9e..65a8dfdd 100644 --- a/quantus_sdk/pubspec.yaml +++ b/quantus_sdk/pubspec.yaml @@ -89,7 +89,7 @@ dev_dependencies: polkadart: output_dir: lib/generated chains: - planck: wss://rpc1-mainnet.quantus.com + planck: wss://a1-planck.quantus.cat flutter: assets: From a533c4a3b02b8010811f1a1cfe842605e4d9f4ab Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 9 Sep 2026 20:11:51 +0800 Subject: [PATCH 4/6] Address review: cache generation, per-chain miner discovery, 5xx failover - Wormhole caches: one generation constant for both the transfer and the nullifier file, bumped to v3 for the mainnet switch. The files are keyed by address only, so Planck-era transfers and nullifiers would otherwise be read against mainnet and a transfer count spent on Planck would count as spent here. Earlier generations of an address are deleted on first read. - Miner app: balance, withdrawal and claim discovery use the selected chain's RPC and indexer through MinerSettingsService.utxoService(), replacing the stub that never configured anything. Mainnet is added as the default chain and Planck points at its real indexer. - Endpoint failover: an HTTP 5xx is an endpoint failure inside RedundantEndpointService, so the next endpoint is tried; a 4xx is returned unchanged. The HTTP client is injectable for tests. --- .../features/miner/miner_balance_card.dart | 6 +- .../withdrawal/claim_rewards_dialog.dart | 8 +- .../withdrawal/withdrawal_screen.dart | 7 +- miner-app/lib/src/config/miner_config.dart | 14 ++- .../src/services/miner_settings_service.dart | 37 +++----- .../services/network/redundant_endpoint.dart | 38 +++++++- .../src/services/wormhole_utxo_service.dart | 87 ++++++++++--------- quantus_sdk/pubspec.lock | 4 +- quantus_sdk/pubspec.yaml | 2 + .../redundant_endpoint_failover_test.dart | 62 +++++++++++++ .../wormhole_cache_generation_test.dart | 64 ++++++++++++++ .../wormhole_utxo_pagination_test.dart | 4 +- 12 files changed, 245 insertions(+), 88 deletions(-) create mode 100644 quantus_sdk/test/services/redundant_endpoint_failover_test.dart create mode 100644 quantus_sdk/test/services/wormhole_cache_generation_test.dart diff --git a/miner-app/lib/features/miner/miner_balance_card.dart b/miner-app/lib/features/miner/miner_balance_card.dart index 7d17db73..7f0a0f87 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 cac1eceb..cc825da9 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 155ea00b..5a6c331b 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 60684c26..a1bb7d41 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 be6f2629..a73ace1c 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/quantus_sdk/lib/src/services/network/redundant_endpoint.dart b/quantus_sdk/lib/src/services/network/redundant_endpoint.dart index 15afdb76..a3679093 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,9 @@ 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(endpoints: urls.map((e) => Endpoint(url: e)).toList()); + String get bestEndpointUrl => endpoints.first.url; final Map _providers = {}; @@ -113,9 +132,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 +197,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/wormhole_utxo_service.dart b/quantus_sdk/lib/src/services/wormhole_utxo_service.dart index 3d7d98b1..3f4a42ef 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. They are keyed by address only, not by + /// network, so a network switch must bump this: v3 is the mainnet switch, so + /// Planck-era transfers and nullifiers are never 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'); @@ -174,47 +180,49 @@ query TransfersToAddresses($tos: [String!]!, $limit: Int!, $offset: Int!, $after static String _cachePrefix(String addressHash) => addressHash.substring(0, 16); + static String _transferCacheName(String prefix) => 'wormhole_cache_v${cacheVersion}_$prefix.json'; + + static String _nullifierCacheName(String prefix) => 'wormhole_nullifiers_v${cacheVersion}_$prefix.json'; + + static bool _isCacheFileFor(String name, String prefix) => + (name.startsWith('wormhole_cache_') || name.startsWith('wormhole_nullifiers')) && name.endsWith('_$prefix.json'); + + static String _fileName(FileSystemEntity entity) => + entity.uri.pathSegments.isEmpty ? entity.path : entity.uri.pathSegments.last; + static Future _transferCacheFile(String addressHash) async { final dir = await getApplicationSupportDirectory(); - return File('${dir.path}/wormhole_cache_v${transferCacheVersion}_${_cachePrefix(addressHash)}.json'); + return File('${dir.path}/${_transferCacheName(_cachePrefix(addressHash))}'); } - /// 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'); - } - - 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'); - } + return File('${dir.path}/${_nullifierCacheName(_cachePrefix(addressHash))}'); } - static Future _deleteLegacyTransferCache(String addressHash) async { + /// Deletes every cache file of [addressHash] from an earlier generation. + @visibleForTesting + static Future deleteStaleCaches(String addressHash) async { try { + final prefix = _cachePrefix(addressHash); + final current = {_transferCacheName(prefix), _nullifierCacheName(prefix)}; 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) && !current.contains(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); + await deleteStaleCaches(addressHash); try { final file = await _transferCacheFile(addressHash); if (!await file.exists()) return _TransferCache.empty(); @@ -236,8 +244,9 @@ query TransfersToAddresses($tos: [String!]!, $limit: Int!, $offset: Int!, $after } } - static Future> _loadSpentNullifiers(String addressHash) async { - await _deleteLegacyNullifierCache(addressHash); + @visibleForTesting + static Future> loadSpentNullifiers(String addressHash) async { + await deleteStaleCaches(addressHash); try { final file = await _nullifierCacheFile(addressHash); if (!await file.exists()) return {}; @@ -268,14 +277,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 +298,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++; @@ -647,7 +650,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); diff --git a/quantus_sdk/pubspec.lock b/quantus_sdk/pubspec.lock index 64fff7b2..112539b8 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 65a8dfdd..45a9b10e 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 00000000..2c57deb5 --- /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 00000000..197ecaf8 --- /dev/null +++ b/quantus_sdk/test/services/wormhole_cache_generation_test.dart @@ -0,0 +1,64 @@ +import 'dart:io'; + +import 'package:flutter_test/flutter_test.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/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'; + +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 WormholeUtxoService.loadSpentNullifiers(_hash), isEmpty); + expect(names(), {otherAddress.uri.pathSegments.last}); + }); + + test('current-generation files survive stale cleanup', () async { + final v = WormholeUtxoService.cacheVersion; + write('wormhole_nullifiers_v${v}_$_prefix.json', '["0x02"]'); + write('wormhole_cache_v${v}_$_prefix.json', '{"cachedUpToBlock":7,"transfers":[]}'); + + await WormholeUtxoService.deleteStaleCaches(_hash); + + expect(names(), {'wormhole_nullifiers_v${v}_$_prefix.json', 'wormhole_cache_v${v}_$_prefix.json'}); + expect(await WormholeUtxoService.loadSpentNullifiers(_hash), {'0x02'}); + }); + + 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${WormholeUtxoService.cacheVersion}_$_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 06efd478..652599f7 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); }); } From 2ad6f0183c30c15e63ecf0025cd8f4f17361849c Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 9 Sep 2026 20:22:13 +0800 Subject: [PATCH 5/6] test: drop unused riverpod misc import --- mobile-app/test/unit/regular_send_strategy_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/mobile-app/test/unit/regular_send_strategy_test.dart b/mobile-app/test/unit/regular_send_strategy_test.dart index 0116ca1e..5f4dbd0c 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'; From 43d7cee01568ef6eab50981279b841b9cf6d9817 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 9 Sep 2026 20:44:21 +0800 Subject: [PATCH 6/6] Namespace wormhole caches by genesis hash Both cache files were keyed by generation and address only, so a miner that scanned on Planck and then selected Mainnet read Planck's scan height and spent nullifiers as if they were mainnet's. The files now carry the first 16 hex chars of the chain's genesis hash, fetched once per service instance over the same RPC discovery already uses. Stale cleanup keeps current-generation files of every network, so switching back does not rescan. RpcEndpointService.forUrls takes a client for tests. --- .../services/network/redundant_endpoint.dart | 3 +- .../src/services/wormhole_utxo_service.dart | 90 ++++++++++++------- .../wormhole_cache_generation_test.dart | 61 +++++++++++-- 3 files changed, 112 insertions(+), 42 deletions(-) diff --git a/quantus_sdk/lib/src/services/network/redundant_endpoint.dart b/quantus_sdk/lib/src/services/network/redundant_endpoint.dart index a3679093..7f05d2c2 100644 --- a/quantus_sdk/lib/src/services/network/redundant_endpoint.dart +++ b/quantus_sdk/lib/src/services/network/redundant_endpoint.dart @@ -104,7 +104,8 @@ 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(endpoints: urls.map((e) => Endpoint(url: e)).toList()); + RpcEndpointService.forUrls(List urls, {super.client}) + : super(endpoints: urls.map((e) => Endpoint(url: e)).toList()); String get bestEndpointUrl => endpoints.first.url; diff --git a/quantus_sdk/lib/src/services/wormhole_utxo_service.dart b/quantus_sdk/lib/src/services/wormhole_utxo_service.dart index 3f4a42ef..b2bdb1d1 100644 --- a/quantus_sdk/lib/src/services/wormhole_utxo_service.dart +++ b/quantus_sdk/lib/src/services/wormhole_utxo_service.dart @@ -128,9 +128,9 @@ class WormholeUtxoService { @visibleForTesting static const int transferPageSize = 300; - /// Generation of both on-disk caches. They are keyed by address only, not by - /// network, so a network switch must bump this: v3 is the mainnet switch, so - /// Planck-era transfers and nullifiers are never read against mainnet. + /// 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 cacheVersion = 3; static const int _nullifierBatchSize = 300; @@ -178,40 +178,55 @@ 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 String _transferCacheName(String prefix) => 'wormhole_cache_v${cacheVersion}_$prefix.json'; + String? _networkId; - static String _nullifierCacheName(String prefix) => 'wormhole_nullifiers_v${cacheVersion}_$prefix.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 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; - static Future _transferCacheFile(String addressHash) async { + Future _cacheFile(String kind, String addressHash) async { final dir = await getApplicationSupportDirectory(); - return File('${dir.path}/${_transferCacheName(_cachePrefix(addressHash))}'); - } - - static Future _nullifierCacheFile(String addressHash) async { - final dir = await getApplicationSupportDirectory(); - return File('${dir.path}/${_nullifierCacheName(_cachePrefix(addressHash))}'); + return File('${dir.path}/${_cacheName(kind, await networkId(), _cachePrefix(addressHash))}'); } /// 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 current = {_transferCacheName(prefix), _nullifierCacheName(prefix)}; final dir = await getApplicationSupportDirectory(); 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) && !current.contains(name)) { + if (_isCacheFileFor(name, prefix) && !_currentGeneration.hasMatch(name)) { await entity.delete(); _log('Deleted stale cache: $name'); } @@ -221,10 +236,10 @@ query TransfersToAddresses($tos: [String!]!, $limit: Int!, $offset: Int!, $after } } - static Future<_TransferCache> _loadTransferCache(String addressHash) async { + 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); @@ -234,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) { @@ -245,10 +263,10 @@ query TransfersToAddresses($tos: [String!]!, $limit: Int!, $offset: Int!, $after } @visibleForTesting - static Future> loadSpentNullifiers(String addressHash) async { + 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(); @@ -258,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) { @@ -310,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'); @@ -708,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/test/services/wormhole_cache_generation_test.dart b/quantus_sdk/test/services/wormhole_cache_generation_test.dart index 197ecaf8..13617be0 100644 --- a/quantus_sdk/test/services/wormhole_cache_generation_test.dart +++ b/quantus_sdk/test/services/wormhole_cache_generation_test.dart @@ -1,8 +1,12 @@ +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 { @@ -15,6 +19,24 @@ class _FakePathProvider extends Fake with MockPlatformInterfaceMixin implements 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; @@ -37,25 +59,48 @@ void main() { write('wormhole_cache_$_prefix.json', '{"cachedUpToBlock":1,"transfers":[]}'); final otherAddress = write('wormhole_nullifiers_v2_ffffffffffffffff.json', '["0x01"]'); - expect(await WormholeUtxoService.loadSpentNullifiers(_hash), isEmpty); + expect(await _serviceOn(_mainnet).loadSpentNullifiers(_hash), isEmpty); expect(names(), {otherAddress.uri.pathSegments.last}); }); - test('current-generation files survive stale cleanup', () async { - final v = WormholeUtxoService.cacheVersion; - write('wormhole_nullifiers_v${v}_$_prefix.json', '["0x02"]'); - write('wormhole_cache_v${v}_$_prefix.json', '{"cachedUpToBlock":7,"transfers":[]}'); + 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}_$_prefix.json', 'wormhole_cache_v${v}_$_prefix.json'}); - expect(await WormholeUtxoService.loadSpentNullifiers(_hash), {'0x02'}); + 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${WormholeUtxoService.cacheVersion}_$_prefix.json', '[]'); + write('wormhole_nullifiers_v${_v}_${_planck}_$_prefix.json', '[]'); write('wormhole_nullifiers_v2_ffffffffffffffff.json', '[]'); await WormholeUtxoService.clearCachesForAddresses([]);