feat(wallet): Make wallet behave well when device is offline (#21067)

We make the wallet closer to being offline-first, that is, once data is loaded,
going offline won’t cause unnecessary data re-fetches which currently cause all
balances and data to stay loading forever or eventually balances end up zeroed.

Areas that may be impacted: read-only data displayed in the wallet and editing
accounts.

Fixes https://github.com/status-im/status-mobile/issues/21066
This commit is contained in:
Icaro Motta 2024-08-17 00:48:01 -03:00 committed by GitHub
parent 3e0c31af9a
commit 8e3031f106
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 79 additions and 32 deletions

View File

@ -0,0 +1,5 @@
(ns status-im.contexts.network.data-store)
(defn online?
[{:network/keys [status]}]
(= :online status))

View File

@ -2,6 +2,7 @@
(:require [camel-snake-kebab.extras :as cske] (:require [camel-snake-kebab.extras :as cske]
[clojure.string :as string] [clojure.string :as string]
[react-native.platform :as platform] [react-native.platform :as platform]
[status-im.contexts.network.data-store :as network.data-store]
[status-im.contexts.wallet.collectible.utils :as collectible-utils] [status-im.contexts.wallet.collectible.utils :as collectible-utils]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[utils.ethereum.chain :as chain] [utils.ethereum.chain :as chain]
@ -122,14 +123,15 @@
(rf/reg-event-fx (rf/reg-event-fx
:wallet/request-collectibles-for-current-viewing-account :wallet/request-collectibles-for-current-viewing-account
(fn [{:keys [db]} _] (fn [{:keys [db]} _]
(let [current-viewing-account (-> db :wallet :current-viewing-account-address) (when (network.data-store/online? db)
[request-id] (get-unique-collectible-request-id 1)] (let [current-viewing-account (-> db :wallet :current-viewing-account-address)
{:db (assoc-in db [:wallet :ui :collectibles :pending-requests] 1) [request-id] (get-unique-collectible-request-id 1)]
:fx [[:dispatch {:db (assoc-in db [:wallet :ui :collectibles :pending-requests] 1)
[:wallet/request-new-collectibles-for-account :fx [[:dispatch
{:request-id request-id [:wallet/request-new-collectibles-for-account
:account current-viewing-account {:request-id request-id
:amount collectibles-request-batch-size}]]]}))) :account current-viewing-account
:amount collectibles-request-batch-size}]]]}))))
(defn- update-fetched-collectibles-progress (defn- update-fetched-collectibles-progress
[db owner-address collectibles offset has-more?] [db owner-address collectibles offset has-more?]

View File

@ -262,3 +262,7 @@
;; :cost () ;; tbd not used on desktop ;; :cost () ;; tbd not used on desktop
:token-fees token-fees :token-fees token-fees
:gas-amount (:tx-gas-amount new-path)})) :gas-amount (:tx-gas-amount new-path)}))
(defn tokens-never-loaded?
[db]
(nil? (get-in db [:wallet :ui :tokens-loading])))

View File

@ -7,4 +7,7 @@
(def defaults (def defaults
{:ui {:network-filter network-filter-defaults {:ui {:network-filter network-filter-defaults
:tokens-loading {}}}) ;; Note: we set it to nil by default to differentiate when the user logs
;; in and the device is offline, versus re-fetching when offline and
;; tokens already exist in the app-db.
:tokens-loading nil}})

View File

@ -5,6 +5,7 @@
[clojure.string :as string] [clojure.string :as string]
[react-native.platform :as platform] [react-native.platform :as platform]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.contexts.network.data-store :as network.data-store]
[status-im.contexts.settings.wallet.effects] [status-im.contexts.settings.wallet.effects]
[status-im.contexts.settings.wallet.events] [status-im.contexts.settings.wallet.events]
[status-im.contexts.wallet.common.activity-tab.events] [status-im.contexts.wallet.common.activity-tab.events]
@ -107,23 +108,44 @@
[:dispatch [:wallet/request-new-collectibles-for-account-from-signal address]] [:dispatch [:wallet/request-new-collectibles-for-account-from-signal address]]
[:dispatch [:wallet/check-recent-history-for-account address]]]})) [:dispatch [:wallet/check-recent-history-for-account address]]]}))
(rf/reg-event-fx (defn- reconcile-accounts
:wallet/get-accounts-success [db-accounts-by-address new-accounts]
(reduce
(fn [res {:keys [address] :as account}]
;; Because we add extra fields (tokens and collectibles) into the RPC
;; response from accounts_getAccounts, if we are offline we want to keep
;; the old balances in the accounts, thus we merge the up-to-date account
;; from status-go into the cached accounts. We also merge when online
;; because we will re-fetch balances anyway.
;;
;; Refactor improvement: don't augment entities from status-go, store
;; tokens and collectibles in separate keys in the app-db indexed by
;; account address.
(assoc res
address
(-> (get db-accounts-by-address address)
(merge account)
;; These should not be cached, otherwise when going
;; offline->online collectibles won't be fetched.
(dissoc :current-collectible-idx :has-more-collectibles?))))
{}
new-accounts))
(rf/reg-event-fx :wallet/get-accounts-success
(fn [{:keys [db]} [accounts]] (fn [{:keys [db]} [accounts]]
(let [wallet-accounts (data-store/rpc->accounts accounts) (let [wallet-accounts (data-store/rpc->accounts accounts)
wallet-db (get db :wallet) wallet-db (get db :wallet)
new-account? (:new-account? wallet-db) new-account? (:new-account? wallet-db)
navigate-to-account (:navigate-to-account wallet-db)] navigate-to-account (:navigate-to-account wallet-db)]
{:db (assoc-in db {:db (update-in db [:wallet :accounts] reconcile-accounts wallet-accounts)
[:wallet :accounts] :fx (concat (when (or (data-store/tokens-never-loaded? db)
(utils.collection/index-by :address wallet-accounts)) (network.data-store/online? db))
:fx (concat refresh-accounts-fx-dispatches refresh-accounts-fx-dispatches)
[(when new-account? [(when new-account?
[:dispatch [:wallet/navigate-to-new-account navigate-to-account]])])}))) [:dispatch [:wallet/navigate-to-new-account navigate-to-account]])])})))
(rf/reg-event-fx (rf/reg-event-fx :wallet/get-accounts
:wallet/get-accounts (fn []
(fn [_]
{:fx [[:json-rpc/call {:fx [[:json-rpc/call
[{:method "accounts_getAccounts" [{:method "accounts_getAccounts"
:on-success [:wallet/get-accounts-success] :on-success [:wallet/get-accounts-success]
@ -486,7 +508,7 @@
{:test-networks-enabled? test-networks-enabled? {:test-networks-enabled? test-networks-enabled?
:is-goerli-enabled? is-goerli-enabled?}) :is-goerli-enabled? is-goerli-enabled?})
chains-filtered-by-mode (remove #(not (contains? chain-ids-by-mode %)) down-chain-ids) chains-filtered-by-mode (remove #(not (contains? chain-ids-by-mode %)) down-chain-ids)
chains-down? (seq chains-filtered-by-mode) chains-down? (and (network.data-store/online? db) (seq chains-filtered-by-mode))
chain-names (when chains-down? chain-names (when chains-down?
(->> (map #(-> (network-utils/id->network %) (->> (map #(-> (network-utils/id->network %)
name name

View File

@ -43,9 +43,15 @@
{:fx [[:dispatch [:wallet/reload]]]} {:fx [[:dispatch [:wallet/reload]]]}
"wallet-blockchain-status-changed" "wallet-blockchain-status-changed"
{:fx [[:dispatch {:fx [[:dispatch-later
[:wallet/blockchain-status-changed ;; Don't dispatch immediately because the signal may arrive as
(transforms/js->clj event-js)]]]} ;; soon as the device goes offline. We need to give some time for
;; RN to dispatch the network status update, otherwise when going
;; offline the user will immediately see a toast saying "provider
;; X is down".
[{:ms 500
:dispatch [:wallet/blockchain-status-changed
(transforms/js->clj event-js)]}]]]}
"wallet-activity-filtering-done" "wallet-activity-filtering-done"
{:fx {:fx

View File

@ -152,8 +152,12 @@
(fn [toasts [_ toast-id & cursor]] (fn [toasts [_ toast-id & cursor]]
(get-in toasts (into [:toasts toast-id] cursor)))) (get-in toasts (into [:toasts toast-id] cursor))))
(re-frame/reg-sub (re-frame/reg-sub :network/offline?
:network/offline?
:<- [:network/status] :<- [:network/status]
(fn [status] (fn [status]
(= status :offline))) (= status :offline)))
(re-frame/reg-sub :network/online?
:<- [:network/status]
(fn [status]
(= status :online)))

View File

@ -48,11 +48,10 @@
:wallet/home-tokens-loading? :wallet/home-tokens-loading?
:<- [:wallet/tokens-loading] :<- [:wallet/tokens-loading]
(fn [tokens-loading] (fn [tokens-loading]
(or (empty? tokens-loading) (->> tokens-loading
(->> tokens-loading vals
vals (some true?)
(some true?) boolean)))
boolean))))
(rf/reg-sub (rf/reg-sub
:wallet/current-viewing-account-tokens-loading? :wallet/current-viewing-account-tokens-loading?
@ -351,9 +350,10 @@
:<- [:wallet/accounts] :<- [:wallet/accounts]
:<- [:wallet/balances-in-selected-networks] :<- [:wallet/balances-in-selected-networks]
:<- [:wallet/tokens-loading] :<- [:wallet/tokens-loading]
:<- [:network/online?]
:<- [:profile/currency-symbol] :<- [:profile/currency-symbol]
:<- [:wallet/keypairs] :<- [:wallet/keypairs]
(fn [[accounts balances tokens-loading currency-symbol keypairs]] (fn [[accounts balances tokens-loading online? currency-symbol keypairs]]
(mapv (fn [{:keys [color address watch-only? key-uid operable] :as account}] (mapv (fn [{:keys [color address watch-only? key-uid operable] :as account}]
(let [account-type (cond (let [account-type (cond
(= operable :no) :missing-keypair (= operable :no) :missing-keypair
@ -373,8 +373,9 @@
account account
keypair)}])) keypair)}]))
#(rf/dispatch [:wallet/navigate-to-account address])) #(rf/dispatch [:wallet/navigate-to-account address]))
:loading? (or (get tokens-loading address) :loading? (and online?
(not (contains? tokens-loading address))) (or (get tokens-loading address)
(not (contains? tokens-loading address))))
:balance (utils/prettify-balance currency-symbol :balance (utils/prettify-balance currency-symbol
(get balances address))))) (get balances address)))))
accounts))) accounts)))