From b4866ff72885cab4d0e7f1f5ca162ad4804947c4 Mon Sep 17 00:00:00 2001 From: Ajay Sivan Date: Fri, 5 Jul 2024 15:14:38 +0530 Subject: [PATCH 01/79] Swap: Edit slippage drawer (#20554) --- .../images/icons2/20x20/percentage@2x.png | Bin 0 -> 781 bytes .../images/icons2/20x20/percentage@3x.png | Bin 0 -> 1284 bytes src/status_im/constants.cljs | 5 + .../sheets/slippage_settings/style.cljs | 13 ++ .../wallet/sheets/slippage_settings/view.cljs | 118 ++++++++++++++++++ .../contexts/wallet/swap/events.cljs | 26 ++-- .../wallet/swap/swap_proposal/style.cljs | 4 + .../wallet/swap/swap_proposal/view.cljs | 15 +++ src/status_im/navigation/screens.cljs | 5 + src/status_im/subs/wallet/swap.cljs | 4 + src/utils/number.cljs | 10 ++ src/utils/number_test.cljs | 14 +++ translations/en.json | 8 ++ 13 files changed, 213 insertions(+), 9 deletions(-) create mode 100644 resources/images/icons2/20x20/percentage@2x.png create mode 100644 resources/images/icons2/20x20/percentage@3x.png create mode 100644 src/status_im/contexts/wallet/sheets/slippage_settings/style.cljs create mode 100644 src/status_im/contexts/wallet/sheets/slippage_settings/view.cljs create mode 100644 src/status_im/contexts/wallet/swap/swap_proposal/style.cljs create mode 100644 src/status_im/contexts/wallet/swap/swap_proposal/view.cljs diff --git a/resources/images/icons2/20x20/percentage@2x.png b/resources/images/icons2/20x20/percentage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..020f6d65661fac1bd1b7aa3e89c57a20c8c511a5 GIT binary patch literal 781 zcmV+o1M>WdP)RwGhqDr)7_m1Vdey;E)}&tz8#=&w9V9y;VPzNQemTqN&xkKH)oe5R(? zCsCheM0_3HnD!z8)R#dS(TZgvM z$76%Nxi5n^?5X(f(Uz}H-EPs6ogpefZn3ZpHncaUXO<7GgI9=sVDpa2sVYP3S;gsK zA)p@$D5BkB61usv@~m`-tqdF60pk<5W55m0oPLEGOVmT+e9G`rU`+v@~jeqX+ofkJWICEi7X*d zRGw8r&{QV|=8kuZ>0ci|MdjHxOP{{&D|Q&Uq@Q&Uq@4Ga$8Jm6mtwSZ_( zHSf9w;Judc`XdpBcp#J(c!O`91M)b36ubB=Zcgw3C?)V?zIiQyor1#$pT+P1OVH#& zz%aR{1)XUPJh1jBanpk(4Ol|Lo|3SCCwm#Gv3$?KLTdbG+!8!${=&YEJ`PZjwdRvo z^!2CK)x8zN7bt<%4#z7${x8`pPu8*46CxC|o(=vxsd{v-4HE0{xAieu+%0Hl-owIm zGMs|+ z!!t-txAmP(h?xk}xmR1!sc(Kv#`XV-r^}zBII>TP6!CijxAs45j7`jHg0O;ESwqu&TtM2u=kq zLRzLpiNUU`Sa}f+BCX}9GBGH^N(HVuF(|^yR62HJ<7>UobW^Lq#|nt6n#B`?pZL=q zviv+AC&ef6&Y8-uWQS8KoA(NO%9u4UIik?Bbw8LjnaO)a)DE#DWUwb{@@f| z9@36q7KqwZ0Mj;o`Z821@LiepN;gzynKiv2m@l_$WJJ|->VQbzA9##egLpMKL^ z!vU705u4478^gvi^m_}V-{S>lK%fpdZ5 zXewD4pwos<+E|%fln@Jv^xgjz7C04Hsj^vS9i#EgT^2MwvnqvCfy*TZtU%=?zldK1 zX9C+K2IQvxpSNU?RtTICpEt2!)}{x43k#eGYved&Ra7-=YHDg~YHDg~Z21ly?+{JsS_RGk0000 (count (second (string/split slippage "."))) + constants/max-slippage-decimal-places) + {:message (i18n/label :t/max-2-decimals) + :type :error} + (> slippage-value constants/max-recommended-slippage) + {:message (i18n/label :t/slippage-may-be-higher-than-necessary) + :type :warning}))) + +(defn- on-cancel + [] + (rf/dispatch [:hide-bottom-sheet])) + +(defn- update-string-on-keypress + [k s] + (if (= :i/backspace k) + (subs s 0 (dec (count s))) + (str s k))) + +(defn view + [] + (let [current-slippage (rf/sub [:wallet/swap-max-slippage]) + account-color (rf/sub [:wallet/current-viewing-account-color]) + [max-slippage set-max-slippage] (rn/use-state (str current-slippage)) + [error set-error] (rn/use-state nil) + [custom? set-custom?] (rn/use-state (not-any? #{current-slippage} + constants/slippages)) + handle-slippage-change (rn/use-callback + (fn [value] + (let [new-slippage (update-string-on-keypress value + max-slippage)] + (set-max-slippage new-slippage) + (set-error (validate-slippage new-slippage)))) + [max-slippage set-max-slippage set-error]) + on-select-slippage (rn/use-callback (fn [slippage] + (set-max-slippage (str slippage)) + (set-custom? (not slippage))) + [set-max-slippage set-custom?]) + save-disabled? (rn/use-memo (fn [] + (or (= max-slippage (str current-slippage)) + (= (:type error) :error) + (and custom? + (empty? max-slippage)))) + [max-slippage current-slippage error custom?]) + on-save (rn/use-callback (fn [] + (rf/dispatch [:wallet.swap/set-max-slippage + max-slippage]) + (rf/dispatch [:hide-bottom-sheet])) + [max-slippage])] + [:<> + [quo/drawer-top + {:title (i18n/label :t/slippage-settings) + :description (i18n/label :t/slippage-settings-description)}] + [rn/view {:style style/slippages} + (map (fn [slippage] + ^{:key slippage} + [quo/drawer-action + (cond-> {:title (str slippage "%") + :on-press #(on-select-slippage slippage)} + (= (str slippage) max-slippage) (assoc :state :selected))]) + constants/slippages) + [quo/drawer-action + (cond-> {:title (i18n/label :t/custom) + :action :input + :on-press #(on-select-slippage nil) + :input-props {:auto-focus true + :customization-color account-color + :placeholder (i18n/label :t/type-slippage) + :right-icon {:icon-name :i/percentage + :on-press identity + :style-fn style/percentage-icon} + :value max-slippage}} + custom? (assoc :state :selected))]] + (when (and custom? error) + [quo/info-message + {:status (:type error) + :size :default + :container-style style/info-message + :icon :i/alert} + (:message error)]) + [quo/bottom-actions + {:actions :two-actions + :button-one-label (i18n/label :t/save-changes) + :button-one-props {:disabled? save-disabled? + :customization-color account-color + :on-press on-save} + :button-two-label (i18n/label :t/cancel) + :button-two-props {:on-press on-cancel + :customization-color account-color + :type :grey} + :description :top + :context-tag-props {:size 24 + :type :token + :token "USDT" + :amount "99.97"} ;; will be replaced with real data later + :description-top-text (i18n/label :t/receive-at-least)}] + (when custom? + [quo/numbered-keyboard + {:left-action :dot + :delete-key? true + :on-press handle-slippage-change + :on-delete handle-slippage-change}])])) diff --git a/src/status_im/contexts/wallet/swap/events.cljs b/src/status_im/contexts/wallet/swap/events.cljs index c495b90c81..ff16116a4f 100644 --- a/src/status_im/contexts/wallet/swap/events.cljs +++ b/src/status_im/contexts/wallet/swap/events.cljs @@ -1,6 +1,8 @@ (ns status-im.contexts.wallet.swap.events (:require [re-frame.core :as rf] - [status-im.contexts.wallet.sheets.network-selection.view :as network-selection])) + [status-im.constants :as constants] + [status-im.contexts.wallet.sheets.network-selection.view :as network-selection] + [utils.number])) (rf/reg-event-fx :wallet.swap/start (fn [{:keys [_db]}] @@ -11,13 +13,10 @@ {:db (-> db (assoc-in [:wallet :ui :swap :asset-to-pay] token) (assoc-in [:wallet :ui :swap :network] network)) - :fx [(if network - [:dispatch - [:toasts/upsert - {:id :swap-error - :type :negative - :text "Not implemented yet"}]] - [:dispatch + :fx (if network + [[:dispatch [:navigate-to :screen/wallet.swap-propasal]] + [:dispatch [:wallet.swap/set-default-slippage]]] + [[:dispatch [:show-bottom-sheet {:content (fn [] [network-selection/view @@ -29,8 +28,17 @@ {:token token :network network :stack-id - :screen/wallet.swap-select-asset-to-pay}]))}])}]])]})) + :screen/wallet.swap-select-asset-to-pay}]))}])}]]])})) (rf/reg-event-fx :wallet.swap/clean-asset-to-pay (fn [{:keys [db]}] {:db (update-in db [:wallet :ui :swap] dissoc :asset-to-pay)})) + +(rf/reg-event-fx :wallet.swap/set-default-slippage + (fn [{:keys [db]}] + {:db + (assoc-in db [:wallet :ui :swap :max-slippage] constants/default-slippage)})) + +(rf/reg-event-fx :wallet.swap/set-max-slippage + (fn [{:keys [db]} [max-slippage]] + {:db (assoc-in db [:wallet :ui :swap :max-slippage] (utils.number/parse-float max-slippage))})) diff --git a/src/status_im/contexts/wallet/swap/swap_proposal/style.cljs b/src/status_im/contexts/wallet/swap/swap_proposal/style.cljs new file mode 100644 index 0000000000..47e48e5cd8 --- /dev/null +++ b/src/status_im/contexts/wallet/swap/swap_proposal/style.cljs @@ -0,0 +1,4 @@ +(ns status-im.contexts.wallet.swap.swap-proposal.style) + +(def container + {:flex 1}) diff --git a/src/status_im/contexts/wallet/swap/swap_proposal/view.cljs b/src/status_im/contexts/wallet/swap/swap_proposal/view.cljs new file mode 100644 index 0000000000..e5ffc0940e --- /dev/null +++ b/src/status_im/contexts/wallet/swap/swap_proposal/view.cljs @@ -0,0 +1,15 @@ +(ns status-im.contexts.wallet.swap.swap-proposal.view + (:require [quo.core :as quo] + [react-native.core :as rn] + [status-im.contexts.wallet.sheets.slippage-settings.view :as slippage-settings] + [status-im.contexts.wallet.swap.swap-proposal.style :as style] + [utils.re-frame :as rf])) + +(defn view + [] + (let [max-slippage (rf/sub [:wallet/swap-max-slippage])] + [rn/view {:style style/container} + [quo/button + {:on-press #(rf/dispatch [:show-bottom-sheet + {:content slippage-settings/view}])} + (str "Edit Slippage: " max-slippage "%")]])) diff --git a/src/status_im/navigation/screens.cljs b/src/status_im/navigation/screens.cljs index c31cd7cbee..f6c0dcadf1 100644 --- a/src/status_im/navigation/screens.cljs +++ b/src/status_im/navigation/screens.cljs @@ -116,6 +116,7 @@ [status-im.contexts.wallet.send.transaction-confirmation.view :as wallet-transaction-confirmation] [status-im.contexts.wallet.send.transaction-progress.view :as wallet-transaction-progress] [status-im.contexts.wallet.swap.select-asset-to-pay.view :as wallet-swap-select-asset-to-pay] + [status-im.contexts.wallet.swap.swap-proposal.view :as wallet-swap-propasal] [status-im.contexts.wallet.wallet-connect.modals.send-transaction.view :as wallet-connect-send-transaction] [status-im.contexts.wallet.wallet-connect.modals.sign-message.view :as wallet-connect-sign-message] @@ -519,6 +520,10 @@ :insets {:top? true}} :component wallet-swap-select-asset-to-pay/view} + {:name :screen/wallet.swap-propasal + :options {:insets {:top? true}} + :component wallet-swap-propasal/view} + {:name :scan-profile-qr-code :options (merge options/dark-screen diff --git a/src/status_im/subs/wallet/swap.cljs b/src/status_im/subs/wallet/swap.cljs index 93523c33d1..8e42bb96a1 100644 --- a/src/status_im/subs/wallet/swap.cljs +++ b/src/status_im/subs/wallet/swap.cljs @@ -52,3 +52,7 @@ fiat-value)] {:crypto (str crypto-formatted " " token-symbol) :fiat fiat-formatted}))) +(rf/reg-sub + :wallet/swap-max-slippage + :<- [:wallet/swap] + :-> :max-slippage) diff --git a/src/utils/number.cljs b/src/utils/number.cljs index 392498ca8e..6e5d758530 100644 --- a/src/utils/number.cljs +++ b/src/utils/number.cljs @@ -25,6 +25,16 @@ maybe-int default)))) +(defn parse-float + "Parses `n` as a float. Defaults to zero or `default` instead of NaN." + ([n] + (parse-float n 0)) + ([n default] + (let [maybe-float (js/parseFloat n 10)] + (if (js/Number.isNaN maybe-float) + default + maybe-float)))) + (defn value-in-range "Returns `num` if is in the range [`lower-bound` `upper-bound`] if `num` exceeds a given bound, then returns the bound exceeded." diff --git a/src/utils/number_test.cljs b/src/utils/number_test.cljs index 5cb964ee51..ebfd9a2abe 100644 --- a/src/utils/number_test.cljs +++ b/src/utils/number_test.cljs @@ -16,3 +16,17 @@ (is (= 6 (utils.number/parse-int "6" 0))) (is (= 6 (utils.number/parse-int "6.99" 0))) (is (= -6 (utils.number/parse-int "-6" 0))))) + +(deftest parse-float-test + (testing "defaults to zero" + (is (= 0 (utils.number/parse-float nil)))) + + (testing "accepts any other default value" + (is (= 3 (utils.number/parse-float "" 3))) + (is (= :invalid-float (utils.number/parse-float "" :invalid-float)))) + + (testing "valid numbers" + (is (= -6 (utils.number/parse-float "-6a"))) + (is (= 6 (utils.number/parse-float "6"))) + (is (= 6.99 (utils.number/parse-float "6.99" 0))) + (is (= -6.9 (utils.number/parse-float "-6.9" 0))))) diff --git a/translations/en.json b/translations/en.json index 186b35ba9e..cc12be4486 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1296,6 +1296,7 @@ "reveal-qr-code": "Reveal QR code", "revoke-access": "Revoke access", "save": "Save", + "save-changes": "Save changes", "save-address": "Save address", "save-bio": "Save bio", "save-colour": "Save colour", @@ -1998,6 +1999,13 @@ "status-inactive-subtitle": "Hides your online status", "two-minutes": "two minutes", "swap": "Swap", + "slippage-settings": "Slippage settings", + "slippage-settings-description": "Your transaction will revert if the price changes more than the slippage percentage", + "slippage-may-be-higher-than-necessary": "Slippage may be higher than necessary", + "slippage-should-be-more-than-0": "Slippage should be more than 0", + "max-2-decimals": "Max. 2 decimals", + "type-slippage": "Type slippage", + "receive-at-least": "Receive at least", "select-token-to-swap": "Select token to Swap", "select-token-to-receive": "Select token to receive", "slide-to-request-to-join": "Slide to request to join", From 2cc689a896379f3e1c2f168a0cf7c06f97c8df7b Mon Sep 17 00:00:00 2001 From: Mohsen Date: Fri, 5 Jul 2024 14:33:38 +0330 Subject: [PATCH 02/79] [#20638] fix: Scan missing keypair keep scanning after success attempt (#20640) --- src/status_im/common/scan_qr_code/view.cljs | 6 ++---- src/status_im/contexts/settings/wallet/events.cljs | 8 +++----- .../missing_keypairs/scan_qr/view.cljs | 4 +--- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/status_im/common/scan_qr_code/view.cljs b/src/status_im/common/scan_qr_code/view.cljs index 8b2eb7e7b4..8993454abf 100644 --- a/src/status_im/common/scan_qr_code/view.cljs +++ b/src/status_im/common/scan_qr_code/view.cljs @@ -195,7 +195,7 @@ true) (defn view - [{:keys [title subtitle validate-fn on-success-scan error-message share-button? import-keypair?]}] + [{:keys [title subtitle validate-fn on-success-scan error-message share-button?]}] (let [insets (safe-area/get-insets) qr-code-succeed? (reagent/atom false) qr-view-finder (reagent/atom {}) @@ -233,9 +233,7 @@ :set-qr-code-succeeded (fn [value] (when on-success-scan (on-success-scan value)) - (if import-keypair? - (set-rescan-timeout) - (rf/dispatch [:navigate-back]))) + (rf/dispatch [:navigate-back])) :set-rescan-timeout set-rescan-timeout}]) [rn/view {:style (style/root-container (:top insets))} [header diff --git a/src/status_im/contexts/settings/wallet/events.cljs b/src/status_im/contexts/settings/wallet/events.cljs index 2485fd4a08..4bd0908b2b 100644 --- a/src/status_im/contexts/settings/wallet/events.cljs +++ b/src/status_im/contexts/settings/wallet/events.cljs @@ -92,7 +92,7 @@ (rf/reg-event-fx :wallet/make-keypairs-accounts-fully-operable make-keypairs-accounts-fully-operable) (defn connection-string-for-import-keypair - [{:keys [db]} [{:keys [sha3-pwd keypairs-key-uids connection-string on-success]}]] + [{:keys [db]} [{:keys [sha3-pwd keypairs-key-uids connection-string]}]] (let [key-uid (get-in db [:profile/profile :key-uid])] {:fx [[:effects.syncing/import-keypairs-keystores {:key-uid key-uid @@ -100,7 +100,6 @@ :keypairs-key-uids keypairs-key-uids :connection-string connection-string :on-success (fn [key-uids] - (rf/call-continuation on-success) (rf/dispatch [:wallet/make-keypairs-accounts-fully-operable key-uids])) :on-fail (fn [error] (log/error "failed to import missing key pairs with connection string" @@ -113,7 +112,7 @@ (rf/reg-event-fx :wallet/connection-string-for-import-keypair connection-string-for-import-keypair) (defn success-keypair-qr-scan - [_ [connection-string keypairs-key-uids on-import-success]] + [_ [connection-string keypairs-key-uids]] {:fx [[:dispatch [:standard-auth/authorize-with-password {:blur? true @@ -125,8 +124,7 @@ [:wallet/connection-string-for-import-keypair {:connection-string connection-string :keypairs-key-uids keypairs-key-uids - :sha3-pwd password - :on-success on-import-success}]))}]]]}) + :sha3-pwd password}]))}]]]}) (rf/reg-event-fx :wallet/success-keypair-qr-scan success-keypair-qr-scan) diff --git a/src/status_im/contexts/settings/wallet/keypairs_and_accounts/missing_keypairs/scan_qr/view.cljs b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/missing_keypairs/scan_qr/view.cljs index df20708f05..1567d83de3 100644 --- a/src/status_im/contexts/settings/wallet/keypairs_and_accounts/missing_keypairs/scan_qr/view.cljs +++ b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/missing_keypairs/scan_qr/view.cljs @@ -13,14 +13,12 @@ on-success-scan (rn/use-callback (fn [scanned-text] (rf/dispatch [:wallet/success-keypair-qr-scan scanned-text - keypairs-key-uids - [:navigate-back]]) + keypairs-key-uids]) [keypairs-key-uids]))] [scan-qr-code/view {:title (i18n/label :t/scan-key-pairs-qr-code) :subtitle (i18n/label :t/find-it-in-setting) :share-button? false - :import-keypair? true :validate-fn sync-utils/valid-connection-string? :error-message (i18n/label :t/invalid-key-pair-qr) :on-success-scan on-success-scan}])) From 66639fc3ed3e958298218809845aebe83dcec3d6 Mon Sep 17 00:00:00 2001 From: mmilad75 <55688834+mmilad75@users.noreply.github.com> Date: Fri, 5 Jul 2024 13:33:15 +0100 Subject: [PATCH 03/79] fix(wallet): fix checkbox (#19756) --- .../sheets/network_preferences/view.cljs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/status_im/contexts/wallet/sheets/network_preferences/view.cljs b/src/status_im/contexts/wallet/sheets/network_preferences/view.cljs index f460067123..92772188e0 100644 --- a/src/status_im/contexts/wallet/sheets/network_preferences/view.cljs +++ b/src/status_im/contexts/wallet/sheets/network_preferences/view.cljs @@ -5,7 +5,6 @@ [quo.theme :as quo.theme] [react-native.core :as rn] [reagent.core :as reagent] - [status-im.constants :as constants] [status-im.contexts.wallet.common.utils :as utils] [status-im.contexts.wallet.sheets.network-preferences.style :as style] [utils.i18n :as i18n] @@ -19,20 +18,17 @@ network-preferences-names]} (or account (rf/sub [:wallet/current-viewing-account])) initial-network-preferences-names (or selected-networks network-preferences-names) receiver? (boolean (not-empty receiver-preferred-networks)) - network-preferences-names-state (reagent/atom (if receiver? selected-networks #{})) + network-preferences-names-state (reagent/atom (if receiver? + selected-networks + initial-network-preferences-names)) toggle-network (fn [network-name] (reset! state :changed) (let [contains-network? (contains? @network-preferences-names-state network-name) - update-fn (if contains-network? disj conj) - networks-count (count - @network-preferences-names-state)] - (if (and (= networks-count 1) contains-network?) - (reset! network-preferences-names-state - (set constants/default-network-names)) - (swap! network-preferences-names-state update-fn - network-name)))) + update-fn (if contains-network? disj conj)] + (swap! network-preferences-names-state update-fn + network-name))) get-current-preferences-names (fn [] (if (= @state :default) initial-network-preferences-names @@ -169,7 +165,8 @@ {:actions :one-action :blur? blur? :button-one-label (or button-label (i18n/label :t/confirm)) - :button-one-props {:disabled? (= @state :default) + :button-one-props {:disabled? (or (= @state :default) + (empty? @network-preferences-names-state)) :on-press (fn [] (let [chain-ids (map :chain-id current-networks)] (on-save chain-ids))) From 460aaedbcad9b515e9dbf4e3c55c7a30f7e42bdc Mon Sep 17 00:00:00 2001 From: Yevheniia Berdnyk Date: Thu, 4 Jul 2024 17:07:34 +0300 Subject: [PATCH 04/79] e2e: false failures fixes --- .../critical/chats/test_1_1_public_chats.py | 9 +++++---- .../tests/critical/chats/test_group_chat.py | 18 +++++++----------- test/appium/views/chat_view.py | 4 +++- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/test/appium/tests/critical/chats/test_1_1_public_chats.py b/test/appium/tests/critical/chats/test_1_1_public_chats.py index 880bc17502..c35f5719ba 100644 --- a/test/appium/tests/critical/chats/test_1_1_public_chats.py +++ b/test/appium/tests/critical/chats/test_1_1_public_chats.py @@ -261,11 +261,12 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase): # self.errors.append("Message_4 is not unpinned!") for chat_number, chat in enumerate([self.chat_1, self.chat_2]): - count = chat.pinned_messages_count.text - if count != '2': + try: + chat.pinned_messages_count.wait_for_element_text(text='2', wait_time=20) + except Failed: self.errors.append( - "Pinned messages count is %s but should be 2 after unpinning the last pinned message for user %s" % - (count, chat_number + 1) + "Pinned messages count is not 2 after unpinning the last pinned message for user %s" % ( + chat_number + 1) ) self.errors.verify_no_errors() diff --git a/test/appium/tests/critical/chats/test_group_chat.py b/test/appium/tests/critical/chats/test_group_chat.py index 288579e607..5388e31b2b 100644 --- a/test/appium/tests/critical/chats/test_group_chat.py +++ b/test/appium/tests/critical/chats/test_group_chat.py @@ -435,16 +435,14 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase): self.errors.verify_no_errors() @marks.testrail_id(703495) - @marks.xfail( - reason="Chat is not unmuted after expected time: https://github.com/status-im/status-mobile/issues/19627") def test_group_chat_mute_chat(self): [self.homes[i].navigate_back_to_home_view() for i in range(3)] - self.homes[1].just_fyi("Member 1 mutes the chat for 1 hour") - self.homes[1].mute_chat_long_press(self.chat_name, "mute-for-1-hour") + self.homes[1].just_fyi("Member 1 mutes the chat for 8 hours") + self.homes[1].mute_chat_long_press(self.chat_name, "mute-for-8-hours") device_time = self.homes[1].driver.device_time current_time = datetime.datetime.strptime(device_time, "%Y-%m-%dT%H:%M:%S%z") - expected_times = [current_time + datetime.timedelta(minutes=i) for i in range(59, 62)] + expected_times = [current_time + datetime.timedelta(minutes=i) for i in range(479, 482)] expected_texts = [ "Muted until %s %s" % (exp_time.strftime('%H:%M'), "today" if current_time.hour < 23 else "tomorrow") for exp_time in expected_times] @@ -484,14 +482,12 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase): "Message '%s' is not shown in chat for %s after mute" % (muted_message, self.usernames[1])) self.chats[1].navigate_back_to_home_view() - self.chats[1].just_fyi("Change device time so chat will be unmuted by timer") - unmute_time = current_time + datetime.timedelta(minutes=61) - self.homes[1].driver.execute_script("mobile: shell", - {"command": "su root date %s" % unmute_time.strftime("%m%d%H%M%Y.%S")} - ) + self.chats[1].just_fyi("Member 1 unmutes the chat") + chat.long_press_element() + self.homes[1].mute_chat_button.click() chat.long_press_element() if self.homes[1].element_starts_with_text("Muted until").is_element_displayed(): - self.errors.append("Chat is still muted after timeout") + self.errors.append("Chat is still muted after being unmuted") self.errors.verify_no_errors() if self.homes[1].mute_chat_button.is_element_displayed(): self.homes[1].click_system_back_button() diff --git a/test/appium/views/chat_view.py b/test/appium/views/chat_view.py index 5676fbd71f..572f72159c 100644 --- a/test/appium/views/chat_view.py +++ b/test/appium/views/chat_view.py @@ -1037,7 +1037,9 @@ class ChatView(BaseView): def quote_message(self, message: str): self.driver.info("Quoting '%s' message" % message) - self.chat_element_by_text(message).long_press_until_element_is_shown(self.reply_message_button) + element = self.chat_element_by_text(message) + element.wait_for_sent_state() + element.long_press_until_element_is_shown(self.reply_message_button) self.reply_message_button.click() def set_reaction(self, message: str, emoji: str = 'thumbs-up', emoji_message=False): From 1f333cc93764f6f0cfa56c41b7c27abf6373d160 Mon Sep 17 00:00:00 2001 From: Parvesh Monu Date: Fri, 5 Jul 2024 20:57:20 +0530 Subject: [PATCH 05/79] fix wallet send flow wrong status bar color on select assets screen (#20662) --- src/status_im/navigation/effects.cljs | 4 ++-- src/status_im/navigation/events.cljs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/status_im/navigation/effects.cljs b/src/status_im/navigation/effects.cljs index e28550cc71..181884cbad 100644 --- a/src/status_im/navigation/effects.cljs +++ b/src/status_im/navigation/effects.cljs @@ -104,14 +104,14 @@ ;;;; Navigate to within stack (defn- navigate-to-within-stack - [[component comp-id]] + [[component comp-id theme]] (let [{:keys [options]} (get views/screens component)] (navigation/push (name comp-id) {:component {:id component :name component :options (merge - (options/root-options {:theme (:theme options)}) + (options/root-options {:theme theme}) options)}}) (state/navigation-state-push {:id component :type :stack diff --git a/src/status_im/navigation/events.cljs b/src/status_im/navigation/events.cljs index cf4ac22a7a..aae7cfd20d 100644 --- a/src/status_im/navigation/events.cljs +++ b/src/status_im/navigation/events.cljs @@ -29,7 +29,7 @@ {:events [:navigate-to-within-stack]} [{:keys [db]} comp-id screen-params] {:db (all-screens-params db (first comp-id) screen-params) - :fx [[:navigate-to-within-stack comp-id]]}) + :fx [[:navigate-to-within-stack (conj comp-id (:theme db))]]}) (re-frame/reg-event-fx :open-modal (fn [{:keys [db]} [component screen-params]] From c473b8c9b1fa4c40c20e3c4f30d14f9d5c31e78f Mon Sep 17 00:00:00 2001 From: Icaro Motta Date: Fri, 5 Jul 2024 14:07:01 -0300 Subject: [PATCH 06/79] chore(settings): Add boilerplate for the new Privacy and security screen (#20668) --- .../contexts/profile/settings/list_items.cljs | 9 +++- .../settings/privacy_and_security/style.cljs | 11 +++++ .../settings/privacy_and_security/view.cljs | 42 +++++++++++++++++++ src/status_im/navigation/screens.cljs | 5 +++ 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 src/status_im/contexts/settings/privacy_and_security/style.cljs create mode 100644 src/status_im/contexts/settings/privacy_and_security/view.cljs diff --git a/src/status_im/contexts/profile/settings/list_items.cljs b/src/status_im/contexts/profile/settings/list_items.cljs index 26f84d709d..78e6c0c6a7 100644 --- a/src/status_im/contexts/profile/settings/list_items.cljs +++ b/src/status_im/contexts/profile/settings/list_items.cljs @@ -61,7 +61,14 @@ :image :icon :blur? true :action :arrow})] - [{:title (i18n/label :t/syncing) + [(when config/show-not-implemented-features? + {:title (i18n/label :t/privacy-and-security) + :on-press #(rf/dispatch [:open-modal :screen/settings-privacy-and-security]) + :image-props :i/privacy + :image :icon + :blur? true + :action :arrow}) + {:title (i18n/label :t/syncing) :on-press #(rf/dispatch [:open-modal :settings-syncing]) :image-props :i/syncing :image :icon diff --git a/src/status_im/contexts/settings/privacy_and_security/style.cljs b/src/status_im/contexts/settings/privacy_and_security/style.cljs new file mode 100644 index 0000000000..3786a649e7 --- /dev/null +++ b/src/status_im/contexts/settings/privacy_and_security/style.cljs @@ -0,0 +1,11 @@ +(ns status-im.contexts.settings.privacy-and-security.style) + +(def title-container + {:flex 0 + :padding-horizontal 20 + :margin-vertical 12}) + +(defn page-wrapper + [top-inset] + {:padding-top top-inset + :flex 1}) diff --git a/src/status_im/contexts/settings/privacy_and_security/view.cljs b/src/status_im/contexts/settings/privacy_and_security/view.cljs new file mode 100644 index 0000000000..c7c87d54df --- /dev/null +++ b/src/status_im/contexts/settings/privacy_and_security/view.cljs @@ -0,0 +1,42 @@ +(ns status-im.contexts.settings.privacy-and-security.view + (:require + [quo.core :as quo] + [quo.theme] + [react-native.safe-area :as safe-area] + [status-im.contexts.settings.privacy-and-security.style :as style] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn navigate-back + [] + (rf/dispatch [:navigate-back])) + +(defn view + [] + (let [insets (safe-area/get-insets) + customization-color (rf/sub [:profile/customization-color])] + [quo/overlay + {:type :shell + :container-style (style/page-wrapper (:top insets))} + [quo/page-nav + {:key :header + :background :blur + :icon-name :i/arrow-left + :on-press navigate-back}] + [quo/standard-title + {:title (i18n/label :t/privacy-and-security) + :container-style style/title-container + :accessibility-label :privacy-and-security-header + :customization-color customization-color}] + [quo/category + {:key :category + :data [{:title "Dummy" + :image-props :i/placeholder + :image :icon + :blur? true + :action :selector + :action-props {:on-change identity + :checked? false} + :on-press identity}] + :blur? true + :list-type :settings}]])) diff --git a/src/status_im/navigation/screens.cljs b/src/status_im/navigation/screens.cljs index f6c0dcadf1..812f5f4930 100644 --- a/src/status_im/navigation/screens.cljs +++ b/src/status_im/navigation/screens.cljs @@ -57,6 +57,7 @@ [status-im.contexts.profile.settings.screens.password.change-password.view :as change-password] [status-im.contexts.profile.settings.screens.password.view :as settings-password] [status-im.contexts.profile.settings.view :as settings] + [status-im.contexts.settings.privacy-and-security.view :as settings.privacy-and-security] [status-im.contexts.settings.wallet.keypairs-and-accounts.missing-keypairs.encrypted-qr.view :as encrypted-keypair-qr] [status-im.contexts.settings.wallet.keypairs-and-accounts.missing-keypairs.import-private-key.view @@ -614,6 +615,10 @@ :options options/transparent-modal-screen-options :component settings.blocked-users/view} + {:name :screen/settings-privacy-and-security + :options options/transparent-modal-screen-options + :component settings.privacy-and-security/view} + {:name :screen/change-password :options (assoc options/transparent-modal-screen-options :theme :dark) :component change-password/view} From a13975f3f6905f0daf18f9865402f947a93a75cd Mon Sep 17 00:00:00 2001 From: John Ngei Date: Sat, 6 Jul 2024 00:15:36 +0200 Subject: [PATCH 07/79] fix the warning message when the user enters the wrong password (#20605) --- .../common/standard_authentication/password_input/view.cljs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/status_im/common/standard_authentication/password_input/view.cljs b/src/status_im/common/standard_authentication/password_input/view.cljs index be6501bdd7..3f39d93616 100644 --- a/src/status_im/common/standard_authentication/password_input/view.cljs +++ b/src/status_im/common/standard_authentication/password_input/view.cljs @@ -16,8 +16,7 @@ [error] (if (and (some? error) (or (= error "file is not a database") - (string/starts-with? error "failed to set ") - (string/starts-with? error "Failed"))) + (string/starts-with? (string/lower-case error) "failed"))) (i18n/label :t/oops-wrong-password) error)) From da16d1f69ee312c10cc09e77f52d58f690eee1ab Mon Sep 17 00:00:00 2001 From: Mohsen Date: Mon, 8 Jul 2024 12:27:16 +0330 Subject: [PATCH 08/79] [#20571] fix: cleanup save address on mount (#20650) --- .../saved_addresses/add_address_to_save/view.cljs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/status_im/contexts/settings/wallet/saved_addresses/add_address_to_save/view.cljs b/src/status_im/contexts/settings/wallet/saved_addresses/add_address_to_save/view.cljs index 171b0d669c..9dcd733970 100644 --- a/src/status_im/contexts/settings/wallet/saved_addresses/add_address_to_save/view.cljs +++ b/src/status_im/contexts/settings/wallet/saved_addresses/add_address_to_save/view.cljs @@ -36,8 +36,10 @@ (defn- address-input [{:keys [input-value on-change-text paste-into-input clear-input]}] (let [empty-input? (string/blank? input-value) - on-scan-address (rn/use-callback #(rf/dispatch [:open-modal :screen/wallet.scan-address - {:on-result on-change-text}]))] + on-scan-address (rn/use-callback (fn [] + (rf/dispatch [:wallet/clean-scanned-address]) + (rf/dispatch [:open-modal :screen/wallet.scan-address + {:on-result on-change-text}])))] [rn/view {:style style/input-container} [quo/input {:accessibility-label :add-address-to-save @@ -161,7 +163,9 @@ (rf/dispatch [:open-modal :screen/settings.save-address])) [address ens-name? address-or-ens])] - (rn/use-unmount #(rf/dispatch [:wallet/clear-address-to-save])) + (rn/use-mount (fn [] + (rf/dispatch [:wallet/clean-scanned-address]) + (rf/dispatch [:wallet/clear-address-to-save]))) [quo/overlay {:type :shell} [floating-button-page/view {:footer-container-padding 0 From de6d9a6c3b93bec02ae8e8ee139b8d664738d5af Mon Sep 17 00:00:00 2001 From: Parvesh Monu Date: Mon, 8 Jul 2024 14:51:27 +0530 Subject: [PATCH 09/79] chore: remove hot reload while theme changing (#20663) --- src/legacy/status_im/events.cljs | 3 +-- src/status_im/contexts/profile/login/events.cljs | 3 +-- src/status_im/contexts/profile/settings/effects.cljs | 9 ++------- src/status_im/contexts/profile/settings/events.cljs | 4 ++-- src/status_im/navigation/events.cljs | 10 ---------- 5 files changed, 6 insertions(+), 23 deletions(-) diff --git a/src/legacy/status_im/events.cljs b/src/legacy/status_im/events.cljs index dffe74020a..b49ad3a071 100644 --- a/src/legacy/status_im/events.cljs +++ b/src/legacy/status_im/events.cljs @@ -108,8 +108,7 @@ (when (and (multiaccounts.model/logged-in? db) (= current-theme-type status-im.constants/theme-type-system)) {:profile.settings/switch-theme-fx - [(get-in db [:profile/profile :appearance]) - (:view-id db) true]}))) + [(get-in db [:profile/profile :appearance]) (:view-id db)]}))) (defn- on-biometric-auth-fail [{:keys [code]}] diff --git a/src/status_im/contexts/profile/login/events.cljs b/src/status_im/contexts/profile/login/events.cljs index ac971fd57a..7a04139cb6 100644 --- a/src/status_im/contexts/profile/login/events.cljs +++ b/src/status_im/contexts/profile/login/events.cljs @@ -83,8 +83,7 @@ [[:profile.settings/switch-theme-fx [(or (:appearance settings) constants/theme-type-dark) - :shell-stack - false]] + :shell-stack]] [:dispatch [:init-root :shell-stack]] [:dispatch [:profile/show-testnet-mode-banner-if-enabled]]]))}))) diff --git a/src/status_im/contexts/profile/settings/effects.cljs b/src/status_im/contexts/profile/settings/effects.cljs index 221b7e95af..b31b1e4b66 100644 --- a/src/status_im/contexts/profile/settings/effects.cljs +++ b/src/status_im/contexts/profile/settings/effects.cljs @@ -4,7 +4,6 @@ [react-native.platform :as platform] [status-im.common.theme.core :as theme] [status-im.constants :as constants] - [status-im.setup.hot-reload :as hot-reload] [utils.re-frame :as rf])) (re-frame/reg-fx @@ -20,7 +19,7 @@ (re-frame/reg-fx :profile.settings/switch-theme-fx - (fn [[theme-type view-id reload-ui?]] + (fn [[theme-type view-id]] (let [theme (if (or (= theme-type constants/theme-type-dark) (and (= theme-type constants/theme-type-system) (theme/device-theme-dark?))) @@ -28,8 +27,4 @@ :light)] (theme/set-legacy-theme theme) (rf/dispatch [:theme/switch theme]) - (rf/dispatch [:reload-status-nav-color view-id]) - (when reload-ui? - (re-frame/dispatch [:dismiss-all-overlays]) - (when js/goog.DEBUG - (hot-reload/reload)))))) + (rf/dispatch [:reload-status-nav-color view-id])))) diff --git a/src/status_im/contexts/profile/settings/events.cljs b/src/status_im/contexts/profile/settings/events.cljs index 6126114e72..728041f7b0 100644 --- a/src/status_im/contexts/profile/settings/events.cljs +++ b/src/status_im/contexts/profile/settings/events.cljs @@ -95,14 +95,14 @@ (rf/reg-event-fx :profile.settings/change-appearance (fn [_ [theme]] {:fx [[:dispatch [:profile.settings/profile-update :appearance theme]] - [:profile.settings/switch-theme-fx [theme :appearance true]]]})) + [:profile.settings/switch-theme-fx [theme :appearance]]]})) (rf/reg-event-fx :profile.settings/switch-theme (fn [{:keys [db]} [theme view-id]] (let [theme (or theme (get-in db [:profile/profile :appearance]) constants/theme-type-dark)] - {:fx [[:profile.settings/switch-theme-fx [theme view-id false]]]}))) + {:fx [[:profile.settings/switch-theme-fx [theme view-id]]]}))) (rf/reg-fx :profile.settings/get-profile-picture (fn [key-uid] diff --git a/src/status_im/navigation/events.cljs b/src/status_im/navigation/events.cljs index aae7cfd20d..7ee2049576 100644 --- a/src/status_im/navigation/events.cljs +++ b/src/status_im/navigation/events.cljs @@ -123,16 +123,6 @@ (hide-bottom-sheet new-cofx) {:show-bottom-sheet {:theme theme}})))))) -(rf/defn dismiss-all-overlays - {:events [:dismiss-all-overlays]} - [_] - {:dispatch-n [[:hide-popover] - [:hide-visibility-status-popover] - [:hide-bottom-sheet] - [:bottom-sheet-hidden] - [:bottom-sheet/hide-old-navigation-overlay] - [:toasts/close-all-toasts]]}) - (rf/defn set-view-id {:events [:set-view-id]} [{:keys [db]} view-id] From 4c4a8b65d49049921ea619fe4babb8560ee86f16 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 8 Jul 2024 13:55:32 +0200 Subject: [PATCH 10/79] Add QR scanner to connect Dapps through Connected Dapps screen (#20647) * Add QR scanner to connect Dapps through Connected Dapps screen * Style fix * use-mount --- resources/images/dapps/WalletConnect@2x.png | Bin 0 -> 6731 bytes resources/images/dapps/WalletConnect@3x.png | Bin 0 -> 12290 bytes .../components/tags/context_tag/schema.cljs | 2 +- src/quo/components/tags/context_tag/view.cljs | 173 +++++++++--------- src/quo/foundations/resources.cljs | 13 +- .../wallet/common/account_switcher/view.cljs | 2 +- .../connected_dapps/scan_dapp/style.cljs | 11 ++ .../connected_dapps/scan_dapp/view.cljs | 44 +++++ .../contexts/wallet/connected_dapps/view.cljs | 2 +- .../wallet/wallet_connect/events.cljs | 23 +-- src/status_im/navigation/screens.cljs | 15 +- translations/en.json | 3 + 12 files changed, 179 insertions(+), 109 deletions(-) create mode 100644 resources/images/dapps/WalletConnect@2x.png create mode 100644 resources/images/dapps/WalletConnect@3x.png create mode 100644 src/status_im/contexts/wallet/connected_dapps/scan_dapp/style.cljs create mode 100644 src/status_im/contexts/wallet/connected_dapps/scan_dapp/view.cljs diff --git a/resources/images/dapps/WalletConnect@2x.png b/resources/images/dapps/WalletConnect@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ae713eb62d42093eb835682e711076079587aac GIT binary patch literal 6731 zcmV-R8noq!P)TK1D9ad-xNjK55s*1nd(QQ`{dM=dk9o6Vg&WQ8?0bFw`a6E# z*KY=fKVj)#F1szDS307HaU2G)9trg5n-V=zvT*ewFqG-~5Y?gI;w^v=!p61@SF$1e zi3-DKSUkXJ&|`%}Km8_^XI?6o5qMi^TD{nXBBh9d zC-p+pq|Uz{1~ysC)B{2<2@$$RKl*D-Zaw+J#u9wWvda>vZ5^pAb+9~3&6-JFxs z01yg5v{1}Ew+V(tzt{zpo-}|8JTBxz@FIrrTs|$2bB%oqUe)iz30a|*+ewzDF=g^c zeUk-oQUGf3B!MTx;F&HaM|9Kg>kY`oAlgnaj{LE_{ngEu)Im&5N`;c+xgkwHnC zwiJ`0PBK^*Ng`_z#Fl7`CFOb&`yQ!nM>cgxyB6_`E)7u&lY*y$$5_0*OGTuNys}Fk z4GQiXsY|I(f+8Aneg6{KjhvDT;2F#071a6M?5IMtWPhTCgS6aiPdKr*WJ4em)(D( zbtN)N=Bkw4V~&b2(49RQGjMP}ewhUTHYuKQm*i1p)3MSTvrKi3&$BtuS=Z%|RkY@1 z*~3f~<_@la4_-4H2mcis)U{iz*${4fPn8Kg0sqysDA0uaijOPhq`-^MJ=#X!=yvMP zYGjmD(Fm(H+N+O{7oKmmls;X~)z53y5Wa*ba zcyls~lOY~*Mb=KH)()a}WOF&0(w+rgHc{V|%rLpm^sMRLJAeG(o^mL`=0?HO{Y$nY z%_}DW?7u{=h?3=~jSD6w{RK2HQC0UA(h42X`%Y9@LZn13Ns82ps;1k^@L$XyK32bH zn2Y7{wh>WLjss3K+Q>{4cv_k@;jllY`b?{9gW6g;OTrcXQ3P3UFYP3ycDNix_jG~% zKHr9EUFvR4w{J8G7#U;u!;rv-A5-)p+Tn3XGd)L#bWGb*WkfAB^Wf3zEOb4-hHxF- z-_UfhnHvUzLn}Md!a*<9>IqaL&i-RPH?c=Ll@zf%3iN`R4uRL;90fLlS^EO>4rTb_ z7l2tk(=~856Yk!$xxdYSHHTw}4mID~QN{DGH}Ld}0pkOJ=DY6|&Ve zURY%AiPc06Z}%{?6s}BbjfH>bya~i(-PUgZ}8N8f@Hjy?kT zi~WjJp+WZxx)oR*x-v-^cc&;c#WB6AO<;OTO0gZh@pc`9j{}cAA@R|$O`f^U6&Qhd zq!UmomOAHXF#?csf9SyhJ|n2*mi|i`{UjypkpKmNj7JS={xa%Hb=ao?k$NqHH_%n8 z;MC)Q6aTUU-K2#YQQf_x8!}ojb8Ul+EyfrD$J}F{ni{*O*yD~Nj{6@Q#pZ{K7@jDQ zIj~(LVrgC!P474N$mWixwm@LF%{_~Wz6S;hgX+B<0L2yt?~&q7W$G|NJ}$ajOoPJ7 zHc<@%YQbT3oZoL_uRkk<;4!~hXoF;i2baFlg$5c3(+!5b$UiPVwF}1{Rm19a6WH=x zyKgcq-8yKKZr9bEdvMxTE%6uGCai-;<)2`sGlSuOV$oyKK zl96OV;eKuq|d(@Lm-@nNPA`uk~7ib<20P!sMKYXU=1>h z4&;r+T;H!YQc2#QPaDBMTp+QqZ-%jdq;{NapxY7&QxI}IMrot7@zYw`MtX~fFP#J* zWsWTEgckO7;YXJMdrjY|O$759L!6cw2gex8oL98IeyjLkuX>|Zm=SwU2w`+#6J;!s`*x_L-ORzcKh^fsq#@Z0<4Jg4vByHj}d%4Ji~Vm-ddZbw1Pt} zx6dfT<%fS}&1A~{iK_f0+AxDW8DZi_{zffaM;Fpiq@%)Qi1EM1!U(1>U=3Y&DVebw34(p4D zU%o`T22rxS&XS!+xRRnSWM3v~HlEs6!>ofWe7@TYYbvC!C44e2@!vO%VbfzBs5jcQ z%IjW21c0&l!cs<-#;Met0-k!Aqg)=voI^|KDpG%ZoWLtmv{S>q4+z|}#v*S#O>B!5MP$P3d4i>Qy&43Gh4)X_AYG$GYj)^+QQ(GnWKM>e= zFRsCRe}oJ=snRpA+py_x!J2t|8Cz6noX{_Vo*Rk)u4(v98pIlSHt8Y)Ii5T!$DEDTDaLJ3#s4As)+`A@1 zk9K04&@N#5sJ%62YYr`P%IYk{~wU4=9cDpT-aVb;o9hclIRRIfzei| zpk7lZssr3oj?E77OGodIpk&5AM70>F{@*WXTW{uWc~kLKHN43PU84xAY*VYAY>f@8 zs(*!kRPgoLHC%jN2W(Q6|M}?%wmjcSl)w%2^>~inEX=P|mwMr(irjd~V~!fb zvhygl)xpqKv~c8{65ji81n<0CAdoEGUU^xGkmrFXBbGJ~G&8Da!=xI_Pu0#MZn$9H zF#hpx+tJ=;qd-pQ=s7lCfBR#+{Sj%0<9d4LnGufzB2Bout%HwTu`;+6osd5<$+WQs zuVDV6RV@F02W3{cCa=mq*~IC-`jry?uwx9{2}D8HxtrMZR3S(NM1ysv?5I?2>KeSi z`g#SIo-+;Y?RD3ps!eL(>tCIQ_dg!NTkjBvT=iWrUSGzuEMBA%jH$qLn2LPr=sjwj z=06P`g`y_@jHKD782yO3WQw+rWBU#RzP7EoGDzEH0F@rny~d5iGB8Vu>amAB(rQZpj9ObV$mW?n5fk7 z>TB*f1p-5?cpC{UibPdTIbj?Zp4o{~p{Acq$d7+GN(9>BWOGL+M)2lqmdQy<&tiIo zz#}a<^P~nY_--c!e@3cR_9h}81e`;_KKi7C|GWYCq-yK@p%+J|qT(|R{u0^1m6XaS zDw7IaFll86Rd`gPgPpChz51DAo{Yp@Fg7g)p*!ZM z>%<8xJ*z{<-|A5d?k5tU=giHX`kkE2GC^W-xQ_Wqp20sAPATho+*heJW8lA8q_GP?kai1hQXGULsal2QBb zo;-oYr_CS_C?ezdq?ju8p{Nsi^m7+F zSiGo%q}~d^-^vQu zk$_Zau&R_&dh%IHbnYoJYd@E+L~NIm+HtZ$1$=on`8kRRo_utu?WholSKm@c zz3PzXZPyY5?hT@hI}uZ^_+1g-{uW1D+Zcsdl;S^u)D~4FLOPv{kUFILYtW$ zq|!Kd3@3b}%cUIA!io3_8smGX?tyk|2dsjHMJG(xUR2v?%`pSel{Z(}@J$a_aP4gj z6=E1l@)VO$xx!YCZ$o8}^)8X!CEFxa^B&WPo~xSoii}z$Y3H5cVA1iNFj2bg?q&cx zayNI3@&(*Yr)k&UUZAs)E`RI_ovdAVtBv&!P3X<>jswu5=IF;hbP_>e%`YZ! z!&;!~bm=S-L$J2|2b3bEJh;Im)iw85lpGO! z4q7YbxodTHvF83^tiF@7&7@L7M$cr%$PC>0bB;UzZ>O#s_s&fVg@{#}(VYa|jdvAL z7u|-DO840uubT7{6v;VisgvR7y5*qy>3VYWNAw}=cvvKZ+?Xbw=H>A!<`ii%omfq0 zv3C&=S|Oxy2$IE@)O!>P4C~E1WhL1dgCVux9_^_sA zRkdT1=!gys+->95O`{ZIn#C~!QzZ+uee|X&lqVMy3Dt4!+7S$_rQ~W|rNrFO5)uhj zZSwJrxQXkiY0OOau)J+mW#+ug=hIYXC#5c}O9m;UAj3@>DS#ZxIKNVEpi*hzkT0pH zu&fwcQ6W_g4eH}(_YC8(b?J-y zIT8O!1UDJ#KP9R^@?aZ-?M%a(U)fl_c2q+vL|t<PCXS&092c5R);{Cg31B!A2ryN(ul5bC^lou`B zCUC19*WNyY&5tVZN=BIyUOre%*0`!RksX}eTg4@m$)TJW{i7>nFR#C21e<=XQVAU< z5XqXWZ2n94fsoS#CNltEjtmN(9{dh(#Mh#Jt*uD&?xKxpfrlf8DMnn*tRQ z{1{j@=DeOW*sn2iPtu~@LItfAGmQXkAH3=N9qtF z{;`KL%iQ99TkZ%xGm_AmGFzrhuQIAzI3|&&L46Tv1VpIE_EC~pNZH#mhiCI z5+@@G1n;Q~X&SA1Vr^k%tmkM^Qb^NN-+|DR4O~3@$&*eQ^L#9FFMR>&{X}V2Gaz;a zv~XRw5C0b{|9*`%Gu|?BK&wJKA|k8uLYZKIeDIoEvPkWm`>gf)xq}9FFYyqOHb58$nH!Z( ziLzaqjJ_nc<$pZLSa(ixx-Ap%s-nzw8VNE zc5ZZm%~iD(kD6I8a^3wLCxidBcF0uKl(|G6<<)toZBD^nG+FTU9Pla1g2i?9bs#>) z$$PRt55U%A)fS?MX6ojv-zAu=5mv!y zJJTo9hjp_#)OwIqcImFDZ1zm1pv*tJv4rf>3Yut7VwkDO$K{knpECY@p8`xMsT;r) zl_>?#EA!8;uOvHlJ`#`3nbHb(3_wIUr3C*%u9Qb4QQ5bvgs`vNRa=);b_;;2C{InN z;k_gUDH7=wqf%a$C44nJJ)b7Jh{V5tFwtw^U3MFA(HZXM^0n_Y`sp;bUn;3|OO%qd z=26ofQi3)O5x|4=x{A{8gV@dGGXc;mz5TL#yhu_hL_gtntoNOSUP%KmH(#ecD%mzE zHZsl!9mg8nUDB3TK2reWMFBc$H|BEne|TW*Ksxm9fqvj!t9?Z{002ovPDHLkV1gRy0xkdm literal 0 HcmV?d00001 diff --git a/resources/images/dapps/WalletConnect@3x.png b/resources/images/dapps/WalletConnect@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..c69ecf6f2dbe6573829ad29e0eaecfb0b3617a06 GIT binary patch literal 12290 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 without-watched first - :address) - ) + :address)) :fx [[:dispatch [:open-modal :screen/wallet.wallet-connect-session-proposal]]]} {:fx [[:dispatch @@ -190,27 +189,25 @@ (fn [_ [scanned-text]] (let [parsed-uri (wallet-connect/parse-uri scanned-text) version (:version parsed-uri) + valid-wc-uri? (wc-utils/valid-uri? parsed-uri) expired? (-> parsed-uri :expiryTimestamp wc-utils/timestamp-expired?) version-supported? (wc-utils/version-supported? version)] - (cond - expired? + (if (or (not valid-wc-uri?) expired? (not version-supported?)) {:fx [[:dispatch [:toasts/upsert {:type :negative :theme :dark - :text (i18n/label :t/wallet-connect-qr-expired)}]]]} + :text (cond (not valid-wc-uri?) + (i18n/label :t/wallet-connect-wrong-qr) - (not version-supported?) - {:fx [[:dispatch - [:toasts/upsert - {:type :negative - :theme :dark - :text (i18n/label :t/wallet-connect-version-not-supported - {:version version})}]]]} + expired? + (i18n/label :t/wallet-connect-qr-expired) - :else + (not version-supported?) + (i18n/label :t/wallet-connect-version-not-supported + {:version version}))}]]]} {:fx [[:dispatch [:wallet-connect/pair scanned-text]]]})))) (rf/reg-event-fx diff --git a/src/status_im/navigation/screens.cljs b/src/status_im/navigation/screens.cljs index 812f5f4930..d7b72a78f5 100644 --- a/src/status_im/navigation/screens.cljs +++ b/src/status_im/navigation/screens.cljs @@ -108,6 +108,7 @@ [status-im.contexts.wallet.bridge.select-asset.view :as wallet-bridge-select-asset] [status-im.contexts.wallet.collectible.view :as wallet-collectible] [status-im.contexts.wallet.common.scan-account.view :as wallet-scan-address] + [status-im.contexts.wallet.connected-dapps.scan-dapp.view :as wallet-scan-dapp] [status-im.contexts.wallet.connected-dapps.view :as wallet-connected-dapps] [status-im.contexts.wallet.send.from.view :as wallet-select-from] [status-im.contexts.wallet.send.select-address.view :as wallet-select-address] @@ -411,10 +412,6 @@ :options {:insets {:top? true}} :component wallet-accounts/view} - {:name :screen/wallet.connected-dapps - :options {:insets {:top? true}} - :component wallet-connected-dapps/view} - {:name :screen/wallet.wallet-connect-session-proposal :options {:sheet? true} :component wallet-connect-session-proposal/view} @@ -549,6 +546,16 @@ :options {:sheet? true} :component wallet-connect-send-transaction/view} + {:name :screen/wallet.connected-dapps + :options {:insets {:top? true}} + :component wallet-connected-dapps/view} + + {:name :screen/wallet.scan-dapp + :options (merge + options/dark-screen + {:modalPresentationStyle :overCurrentContext}) + :component wallet-scan-dapp/view} + ;; Settings {:name :screen/settings-password diff --git a/translations/en.json b/translations/en.json index cc12be4486..dea6d1742e 100644 --- a/translations/en.json +++ b/translations/en.json @@ -2733,8 +2733,11 @@ "dapp-will-be-able-to": "{{dapp-name}} will be able to:", "check-your-account-balance-and-activity": "Check your account balance and activity", "request-txns-and-message-signing": "Request transactions and message signing", + "wallet-connect-label": "WalletConnect", + "wallet-connect-via": "via", "wallet-connect-qr-expired": "WalletConnect QR has expired", "wallet-connect-version-not-supported": "WalletConnect version {{version}} is not supported", + "wallet-connect-wrong-qr": "It’s not a WalletConnect QR", "add-network-preferences": "Add network preferences", "saved-address-network-preference-selection-description": "Only change if you know which networks the address owner is happy to to receive funds on", "add-preferences": "Add preferences", From 7774c4eac16fdee950a17bf5d07630c45a980f41 Mon Sep 17 00:00:00 2001 From: Ulises Manuel <90291778+ulisesmac@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:19:01 -0600 Subject: [PATCH 11/79] fix(wallet): Improve collectible UX and fix data displayed (#20657) * Add variant unknown variant for collectibles * Fix blank collectibles listed * Improve `expanded-collectible` animation and add support for different gradient-colors * Make :wallet/collectible-details-owner subscription depend on `wallet/accounts` instead of `:wallet` * Make collectible tabs component more flexible * Remove now unused subscriptions * Improve UX navigation to collectible detail page * Pass the current collectible gradient-color when a collectible is pressed * Fix tests --- .../profile/collectible_list_item/style.cljs | 12 +- .../profile/collectible_list_item/view.cljs | 107 ++++++++------- .../profile/expanded_collectible/view.cljs | 127 ++++++++---------- .../contexts/wallet/account/tabs/view.cljs | 4 +- .../contexts/wallet/collectible/events.cljs | 53 +++++--- .../wallet/collectible/events_test.cljs | 12 -- .../wallet/collectible/tabs/about/view.cljs | 11 +- .../collectible/tabs/overview/view.cljs | 55 ++++---- .../wallet/collectible/tabs/view.cljs | 6 +- .../contexts/wallet/collectible/utils.cljs | 2 +- .../contexts/wallet/collectible/view.cljs | 121 +++++++++-------- .../wallet/common/collectibles_tab/view.cljs | 25 ++-- .../contexts/wallet/home/tabs/view.cljs | 4 +- .../wallet/send/select_asset/view.cljs | 2 +- src/status_im/subs/wallet/collectibles.cljs | 54 ++++---- .../subs/wallet/collectibles_test.cljs | 54 +++----- 16 files changed, 325 insertions(+), 324 deletions(-) diff --git a/src/quo/components/profile/collectible_list_item/style.cljs b/src/quo/components/profile/collectible_list_item/style.cljs index b163c1008b..7d8f1c9dd2 100644 --- a/src/quo/components/profile/collectible_list_item/style.cljs +++ b/src/quo/components/profile/collectible_list_item/style.cljs @@ -11,7 +11,7 @@ (defn fallback [{:keys [theme opacity]}] [{:opacity opacity} - {:opacity default-opacity-for-image + {:opacity 1 :background-color (colors/theme-colors colors/neutral-2_5 colors/neutral-90 theme) :border-style :dashed :border-color (colors/theme-colors colors/neutral-20 colors/neutral-80 theme) @@ -60,8 +60,13 @@ :padding-right 0 :padding-top 4}) -(def card-detail-text - {:flex 1}) +(defn card-detail-text + [empty-name? theme] + {:flex 1 + :margin-right 8 + :color (if empty-name? + (colors/theme-colors colors/neutral-50 colors/neutral-40 theme) + (colors/theme-colors colors/neutral-100 colors/white theme))}) (defn card-loader [opacity] @@ -115,6 +120,7 @@ [{:opacity opacity} {:flex 1 :flex-direction :row + :column-gap 8 :opacity default-opacity-for-image}]) (defn supported-file diff --git a/src/quo/components/profile/collectible_list_item/view.cljs b/src/quo/components/profile/collectible_list_item/view.cljs index 545908859e..e64ff22f1f 100644 --- a/src/quo/components/profile/collectible_list_item/view.cljs +++ b/src/quo/components/profile/collectible_list_item/view.cljs @@ -1,5 +1,6 @@ (ns quo.components.profile.collectible-list-item.view (:require + [clojure.string :as string] [quo.components.avatars.collection-avatar.view :as collection-avatar] [quo.components.counter.collectible-counter.view :as collectible-counter] [quo.components.icon :as icon] @@ -21,7 +22,7 @@ (def cached-load-time 200) (def error-wait-time 800) -(defn on-load-end +(defn animate-loading-image [{:keys [load-time set-state loader-opacity image-opacity]}] (reanimated/animate loader-opacity 0 timing-options-out) (reanimated/animate image-opacity 1 timing-options-in) @@ -60,12 +61,12 @@ (assoc prev-state :avatar-loaded? true))))) (defn- fallback-view - [{:keys [label theme image-opacity]}] - [reanimated/view - {:style (style/fallback {:opacity image-opacity - :theme theme})} + [{:keys [label theme image-opacity on-load-end]}] + (rn/use-mount on-load-end) + [reanimated/view {:style (style/fallback {:opacity image-opacity :theme theme})} [rn/view - [icon/icon :i/sad {:color (colors/theme-colors colors/neutral-40 colors/neutral-50 theme)}]] + [icon/icon :i/sad + {:color (colors/theme-colors colors/neutral-40 colors/neutral-50 theme)}]] [rn/view {:style {:height 4}}] [text/text {:size :paragraph-2 @@ -93,31 +94,43 @@ [{:keys [community? avatar-image-src collectible-name theme state set-state]}] (let [loader-opacity (reanimated/use-shared-value 1) avatar-opacity (reanimated/use-shared-value 0) - [load-time set-load-time] (rn/use-state (datetime/now))] + [load-time set-load-time] (rn/use-state (datetime/now)) + set-avatar-loaded (fn [] + (on-load-avatar {:set-state set-state + :load-time load-time + :loader-opacity loader-opacity + :avatar-opacity avatar-opacity})) + empty-name? (string/blank? collectible-name)] + (rn/use-mount (fn [] + (when (string/blank? avatar-image-src) + (set-avatar-loaded)))) [rn/view {:style style/card-details-container} [reanimated/view {:style (style/avatar-container avatar-opacity)} - (if community? - [preview-list/view - {:type :communities - :size :size-20} + (cond + (string/blank? avatar-image-src) + [loading-square theme] + + community? + [preview-list/view {:type :communities :size :size-20} [avatar-image-src]] + + :else [collection-avatar/view {:size :size-20 :on-start #(set-load-time (fn [start-time] (- (datetime/now) start-time))) - :on-load-end #(on-load-avatar {:set-state set-state - :load-time load-time - :loader-opacity loader-opacity - :avatar-opacity avatar-opacity}) + :on-load-end set-avatar-loaded :image avatar-image-src}]) - [rn/view {:style {:width 8}}] [text/text {:size :paragraph-1 :weight :semi-bold :ellipsize-mode :tail :number-of-lines 1 - :style style/card-detail-text} - collectible-name]] - (when (not (:avatar-loaded? state)) + :style (style/card-detail-text empty-name? theme)} + (if empty-name? + (i18n/label :t/unknown) + collectible-name)]] + + (when-not (:avatar-loaded? state) [reanimated/view {:style (style/card-loader loader-opacity)} [loading-square theme] [loading-message theme]])])) @@ -128,40 +141,44 @@ (let [theme (quo.theme/use-theme) loader-opacity (reanimated/use-shared-value (if supported-file? 1 0)) image-opacity (reanimated/use-shared-value (if supported-file? 0 1)) - [load-time set-load-time] (rn/use-state (datetime/now))] + [load-time set-load-time] (rn/use-state (datetime/now)) + set-image-loaded (fn [] + (animate-loading-image {:load-time load-time + :set-state set-state + :loader-opacity loader-opacity + :image-opacity image-opacity}))] [rn/view {:style (style/card-view-container theme)} [rn/view {:style {:aspect-ratio 1}} (cond - (:image-error? state) - [fallback-view - {:image-opacity image-opacity - :theme theme - :label (i18n/label :t/cant-fetch-info)}] + (:image-error? state) [fallback-view + {:image-opacity image-opacity + :on-load-end set-image-loaded + :theme theme + :label (i18n/label :t/cant-fetch-info)}] + (not supported-file?) [fallback-view + {:image-opacity image-opacity + :on-load-end set-image-loaded + :theme theme + :label (i18n/label :t/unsupported-file)}] + (not (:image-loaded? state)) [loading-image + {:loader-opacity loader-opacity + :theme theme + :gradient-color-index gradient-color-index}]) - (not supported-file?) - [fallback-view - {:image-opacity image-opacity - :theme theme - :label (i18n/label :t/unsupported-file)}] - - (not (:image-loaded? state)) - [loading-image - {:loader-opacity loader-opacity - :theme theme - :gradient-color-index gradient-color-index}]) (when supported-file? [reanimated/view {:style (style/supported-file image-opacity)} [rn/image {:style style/image :on-load #(on-load % set-state) :on-load-start #(set-load-time (fn [start-time] (- (datetime/now) start-time))) - :on-load-end #(on-load-end {:load-time load-time - :set-state set-state - :loader-opacity loader-opacity - :image-opacity image-opacity}) + :on-load-end set-image-loaded :on-error #(on-load-error set-state) :source image-src}]])] - (when (and (:image-loaded? state) (not (:image-error? state)) counter) + + (when (and (or (:image-loaded? state) + (not supported-file?)) + (not (:image-error? state)) + counter) [collectible-counter/view {:container-style style/collectible-counter :size :size-24 @@ -206,10 +223,10 @@ {:style style/image :on-load #(on-load % set-state) :on-load-start #(set-load-time (fn [start-time] (- (datetime/now) start-time))) - :on-load-end #(on-load-end {:load-time load-time - :set-state set-state - :loader-opacity loader-opacity - :image-opacity image-opacity}) + :on-load-end #(animate-loading-image {:load-time load-time + :set-state set-state + :loader-opacity loader-opacity + :image-opacity image-opacity}) :on-error #(on-load-error set-state) :source image-src}]]) (when (and (:image-loaded? state) (not (:image-error? state)) counter) diff --git a/src/quo/components/profile/expanded_collectible/view.cljs b/src/quo/components/profile/expanded_collectible/view.cljs index 17e298a43d..7bcc2fc553 100644 --- a/src/quo/components/profile/expanded_collectible/view.cljs +++ b/src/quo/components/profile/expanded_collectible/view.cljs @@ -11,38 +11,24 @@ [react-native.core :as rn] [react-native.reanimated :as reanimated] [schema.core :as schema] - [utils.datetime :as datetime] [utils.i18n :as i18n])) -(def timing-options-out 650) -(def timing-options-in 1000) -(def first-load-time 500) -(def cached-load-time 200) +(def loader-out 650) +(def image-in 1000) (def error-wait-time 800) (defn on-load-end - [{:keys [load-time set-state loader-opacity image-opacity]}] - (reanimated/animate loader-opacity 0 timing-options-out) - (reanimated/animate image-opacity 1 timing-options-in) - (if (> load-time cached-load-time) - (js/setTimeout - (fn [] - (set-state (fn [prev-state] - (assoc prev-state :image-loaded? true)))) - first-load-time) - (set-state (fn [prev-state] - (assoc prev-state :image-loaded? true))))) + [{:keys [loader-opacity image-opacity]}] + (reanimated/animate loader-opacity 0 loader-out) + (reanimated/animate image-opacity 1 image-in)) (defn on-load-error - [set-state] - (js/setTimeout (fn [] - (set-state (fn [prev-state] (assoc prev-state :image-error? true)))) - error-wait-time)) + [set-error] + (js/setTimeout set-error error-wait-time)) (defn- loading-image [{:keys [theme gradient-color-index loader-opacity aspect-ratio]}] - [reanimated/view - {:style (style/loading-image-with-opacity loader-opacity)} + [reanimated/view {:style (style/loading-image-with-opacity loader-opacity)} [gradients/view {:theme theme :container-style (style/gradient-view aspect-ratio) @@ -67,62 +53,64 @@ :style {:color (colors/theme-colors colors/neutral-40 colors/neutral-50 theme)}} label]]) +(defn- collectible-image + [{:keys [aspect-ratio theme gradient-color-index supported-file? set-error native-ID + square? image-src on-collectible-load counter]}] + (let [loader-opacity (reanimated/use-shared-value (if supported-file? 1 0)) + image-opacity (reanimated/use-shared-value (if supported-file? 0 1))] + [:<> + [loading-image + {:aspect-ratio aspect-ratio + :loader-opacity loader-opacity + :theme theme + :gradient-color-index gradient-color-index}] + + [reanimated/view {:style (style/supported-file image-opacity)} + [rn/image + {:style (style/image square? aspect-ratio theme) + :source image-src + :native-ID native-ID + :on-load-end (fn [] + (on-load-end {:loader-opacity loader-opacity + :image-opacity image-opacity})) + :on-error #(on-load-error set-error) + :on-load on-collectible-load}] + (when counter + [counter-view counter]) + [rn/view {:style (style/collectible-border theme)}]]])) + (defn view-internal [{:keys [container-style square? on-press counter image-src native-ID supported-file? - on-collectible-load aspect-ratio]}] - (let [theme (quo.theme/use-theme) - loader-opacity (reanimated/use-shared-value - (if supported-file? 1 0)) - image-opacity (reanimated/use-shared-value - (if supported-file? 0 1)) - [load-time set-load-time] (rn/use-state (datetime/now)) - [state set-state] (rn/use-state {:image-loaded? false - :image-error? (or (nil? image-src) - (string/blank? - image-src))})] + on-collectible-load aspect-ratio gradient-color-index] + :or {gradient-color-index :gradient-1 + on-collectible-load (fn [])}}] + (let [theme (quo.theme/use-theme) + [error? set-error] (rn/use-state (or (nil? image-src) + (string/blank? image-src)))] [rn/pressable - {:on-press (when (and (not (:image-error? state)) supported-file?) on-press) + {:style (merge container-style (style/container aspect-ratio)) :accessibility-label :expanded-collectible - :style (merge container-style - (style/container aspect-ratio))} - (cond - (not supported-file?) + :on-press (when (and (not error?) supported-file?) + on-press)} + (if (or (not supported-file?) error?) [fallback-view - {:aspect-ratio aspect-ratio - :label (i18n/label :t/unsupported-file) - :counter counter - :theme theme - :on-mount on-collectible-load}] - - (:image-error? state) - [fallback-view - {:label (i18n/label :t/cant-fetch-info) + {:label (i18n/label (if-not supported-file? + :t/unsupported-file + :t/cant-fetch-info)) :counter counter :theme theme :on-mount on-collectible-load}] - - (not (:image-loaded? state)) - [loading-image + [collectible-image {:aspect-ratio aspect-ratio - :loader-opacity loader-opacity :theme theme - :gradient-color-index :gradient-5}]) - (when supported-file? - [reanimated/view {:style (style/supported-file image-opacity)} - [rn/image - {:style (style/image square? aspect-ratio theme) - :source image-src - :native-ID native-ID - :on-load-start #(set-load-time (fn [start-time] (- (datetime/now) start-time))) - :on-load-end #(on-load-end {:load-time load-time - :set-state set-state - :loader-opacity loader-opacity - :image-opacity image-opacity}) - :on-error #(on-load-error set-state) - :on-load on-collectible-load}] - (when counter - [counter-view counter]) - [rn/view {:style (style/collectible-border theme)}]])])) + :supported-file? supported-file? + :set-error set-error + :native-ID native-ID + :square? square? + :image-src image-src + :on-collectible-load on-collectible-load + :counter counter + :gradient-color-index gradient-color-index}])])) (def ?schema [:=> @@ -137,7 +125,8 @@ [:square? {:optional true} [:maybe boolean?]] [:counter {:optional true} [:maybe string?]] [:on-press {:optional true} [:maybe fn?]] - [:on-collectible-load {:optional true} [:maybe fn?]]]]] + [:on-collectible-load {:optional true} [:maybe fn?]] + [:gradient-color-index {:optional true} [:maybe keyword?]]]]] :any]) (def view (schema/instrument #'view-internal ?schema)) diff --git a/src/status_im/contexts/wallet/account/tabs/view.cljs b/src/status_im/contexts/wallet/account/tabs/view.cljs index 9eee1e388a..45d62bd530 100644 --- a/src/status_im/contexts/wallet/account/tabs/view.cljs +++ b/src/status_im/contexts/wallet/account/tabs/view.cljs @@ -11,9 +11,7 @@ [utils.i18n :as i18n] [utils.re-frame :as rf])) -(defn- on-collectible-press - [{:keys [id]} aspect-ratio] - (rf/dispatch [:wallet/get-collectible-details id aspect-ratio])) +(def on-collectible-press #(rf/dispatch [:wallet/navigate-to-collectible-details %])) (defn- on-collectible-long-press [{:keys [preview-url collectible-details id]}] diff --git a/src/status_im/contexts/wallet/collectible/events.cljs b/src/status_im/contexts/wallet/collectible/events.cljs index f41540edc7..a95b231537 100644 --- a/src/status_im/contexts/wallet/collectible/events.cljs +++ b/src/status_im/contexts/wallet/collectible/events.cljs @@ -1,5 +1,6 @@ (ns status-im.contexts.wallet.collectible.events (:require [camel-snake-kebab.extras :as cske] + [clojure.string :as string] [react-native.platform :as platform] [status-im.contexts.wallet.collectible.utils :as collectible-utils] [taoensso.timbre :as log] @@ -44,13 +45,6 @@ (rf/reg-event-fx :wallet/clear-stored-collectibles clear-stored-collectibles) -(defn store-last-collectible-details - [{:keys [db]} [collectible]] - {:db (assoc-in db [:wallet :last-collectible-details] collectible) - :dispatch [:navigate-to :screen/wallet.collectible]}) - -(rf/reg-event-fx :wallet/store-last-collectible-details store-last-collectible-details) - (rf/reg-event-fx :wallet/request-new-collectibles-for-account (fn [{:keys [db]} [{:keys [request-id account amount]}]] @@ -166,39 +160,58 @@ [:dispatch [:wallet/flush-collectibles-fetched]])]}))) (rf/reg-event-fx - :wallet/get-collectible-details - (fn [{:keys [db]} [collectible-id aspect-ratio]] + :wallet/navigate-to-collectible-details + (fn [{:keys [db]} + [{{collectible-id :id :as collectible} :collectible + aspect-ratio :aspect-ratio + gradient-color :gradient-color}]] (let [request-id 0 collectible-id-converted (cske/transform-keys transforms/->PascalCaseKeyword collectible-id) data-type (collectible-data-types :details) request-params [request-id [collectible-id-converted] data-type]] - {:db (assoc-in db [:wallet :last-collectible-aspect-ratio] aspect-ratio) + {:db (assoc-in db + [:wallet :ui :collectible] + {:details collectible + :aspect-ratio aspect-ratio + :gradient-color gradient-color}) :fx [[:json-rpc/call [{:method "wallet_getCollectiblesByUniqueIDAsync" :params request-params :on-error (fn [error] (log/error "failed to request collectible" - {:event :wallet/get-collectible-details + {:event :wallet/navigate-to-collectible-details :error error - :params request-params}))}]]]}))) + :params request-params}))}]] + ;; We delay the navigation because we need re-frame to update the DB on time. + ;; By doing it, we skip a blink while visiting the collectible detail page. + [:dispatch-later + {:ms 1 + :dispatch [:navigate-to :screen/wallet.collectible]}]]}))) + +(defn- keep-not-empty-value + [old-value new-value] + (if (or (string/blank? new-value) (nil? new-value)) + old-value + new-value)) + +(def merge-skipping-empty-values (partial merge-with keep-not-empty-value)) (rf/reg-event-fx :wallet/get-collectible-details-done - (fn [_ [{:keys [message]}]] - (let [response (cske/transform-keys transforms/->kebab-case-keyword - (transforms/json->clj message)) - {:keys [collectibles]} response - collectible (first collectibles)] + (fn [{db :db} [{:keys [message]}]] + (let [response (cske/transform-keys transforms/->kebab-case-keyword + (transforms/json->clj message)) + {[collectible] :collectibles} response] (if collectible - {:fx [[:dispatch [:wallet/store-last-collectible-details collectible]]]} + {:db (update-in db [:wallet :ui :collectible :details] merge-skipping-empty-values collectible)} (log/error "failed to get collectible details" {:event :wallet/get-collectible-details-done :response response}))))) (rf/reg-event-fx - :wallet/clear-last-collectible-details + :wallet/clear-collectible-details (fn [{:keys [db]}] - {:db (update-in db [:wallet] dissoc :last-collectible-details :last-collectible-aspect-ratio)})) + {:db (update-in db [:wallet :ui] dissoc :collectible)})) (rf/reg-event-fx :wallet/trigger-share-collectible (fn [_ [{:keys [title uri]}]] diff --git a/src/status_im/contexts/wallet/collectible/events_test.cljs b/src/status_im/contexts/wallet/collectible/events_test.cljs index adb0910768..ee2fa59580 100644 --- a/src/status_im/contexts/wallet/collectible/events_test.cljs +++ b/src/status_im/contexts/wallet/collectible/events_test.cljs @@ -43,18 +43,6 @@ (is (match? result-db expected-db)))))) -(deftest store-last-collectible-details-test - (testing "store-last-collectible-details" - (let [db {:wallet {}} - last-collectible {:description "Pandaria" - :image-url "https://..."} - expected-db {:wallet {:last-collectible-details {:description "Pandaria" - :image-url "https://..."}}} - effects (events/store-last-collectible-details {:db db} - [last-collectible]) - result-db (:db effects)] - (is (match? result-db expected-db))))) - (deftest request-new-collectibles-for-account-from-signal-test (testing "request new collectibles for account from signal" (let [db {:wallet {}} diff --git a/src/status_im/contexts/wallet/collectible/tabs/about/view.cljs b/src/status_im/contexts/wallet/collectible/tabs/about/view.cljs index 12a214c46d..03ec8a631a 100644 --- a/src/status_im/contexts/wallet/collectible/tabs/about/view.cljs +++ b/src/status_im/contexts/wallet/collectible/tabs/about/view.cljs @@ -8,12 +8,11 @@ (def ^:private link-card-space 28) (defn view - [] - (let [window-width (rf/sub [:dimensions/window-width]) - item-width (- (/ window-width 2) link-card-space) - {:keys [collectible-data]} (rf/sub [:wallet/last-collectible-details]) - link-card-container-style (style/link-card item-width) - collectible-about {:cards []}] + [{:keys [collectible-data]}] + (let [window-width (rf/sub [:dimensions/window-width]) + item-width (- (/ window-width 2) link-card-space) + link-card-container-style (style/link-card item-width) + collectible-about {:cards []}] [:<> [rn/view {:style style/title} [quo/text diff --git a/src/status_im/contexts/wallet/collectible/tabs/overview/view.cljs b/src/status_im/contexts/wallet/collectible/tabs/overview/view.cljs index 635b7d64c4..7c2f9d5711 100644 --- a/src/status_im/contexts/wallet/collectible/tabs/overview/view.cljs +++ b/src/status_im/contexts/wallet/collectible/tabs/overview/view.cljs @@ -21,27 +21,25 @@ :container-style style/traits-item}]) (defn- traits-section - [] - (let [traits (rf/sub [:wallet/last-collectible-details-traits])] - (when (pos? (count traits)) - [rn/view - [quo/section-label - {:section (i18n/label :t/traits) - :container-style style/traits-title-container}] - [rn/flat-list - {:render-fn trait-item - :data traits - :key :collectibles-list - :key-fn :id - :num-columns 2 - :content-container-style style/traits-container}]]))) + [traits] + [rn/view + [quo/section-label + {:section (i18n/label :t/traits) + :container-style style/traits-title-container}] + [rn/flat-list + {:render-fn trait-item + :data traits + :key :collectibles-list + :key-fn :id + :num-columns 2 + :content-container-style style/traits-container}]]) (defn- info - [] - (let [chain-id (rf/sub [:wallet/last-collectible-details-chain-id]) - {:keys [network-name]} (rf/sub [:wallet/network-details-by-chain-id chain-id]) - subtitle (string/capitalize (name (or network-name ""))) - {:keys [name emoji color]} (rf/sub [:wallet/last-collectible-details-owner])] + [{:keys [chain-id account]}] + (let [{:keys [network-name]} (rf/sub [:wallet/network-details-by-chain-id chain-id]) + subtitle (some-> network-name + name + string/capitalize)] [rn/view {:style style/info-container} [rn/view {:style style/account} [quo/data-item @@ -49,10 +47,10 @@ :status :default :size :default :title (i18n/label :t/account-title) - :subtitle name - :emoji emoji :subtitle-type :account - :customization-color color}]] + :subtitle (:name account) + :emoji (:emoji account) + :customization-color (:color account)}]] [rn/view {:style style/network} [quo/data-item {:subtitle-type :network @@ -64,7 +62,12 @@ :subtitle subtitle}]]])) (defn view - [] - [:<> - [info] - [traits-section]]) + [collectible] + (let [owner-account (rf/sub [:wallet/collectible-details-owner collectible]) + traits (-> collectible :collectible-data :traits)] + [:<> + [info + {:chain-id (-> collectible :id :contract-id :chain-id) + :account owner-account}] + (when (pos? (count traits)) + [traits-section traits])])) diff --git a/src/status_im/contexts/wallet/collectible/tabs/view.cljs b/src/status_im/contexts/wallet/collectible/tabs/view.cljs index d695985a27..d649f0966a 100644 --- a/src/status_im/contexts/wallet/collectible/tabs/view.cljs +++ b/src/status_im/contexts/wallet/collectible/tabs/view.cljs @@ -5,9 +5,9 @@ [status-im.contexts.wallet.collectible.tabs.overview.view :as overview])) (defn view - [{:keys [selected-tab]}] + [{:keys [selected-tab collectible]}] (case selected-tab - :overview [overview/view] - :about [about/view] + :overview [overview/view collectible] + :about [about/view collectible] :activity [activity/view] nil)) diff --git a/src/status_im/contexts/wallet/collectible/utils.cljs b/src/status_im/contexts/wallet/collectible/utils.cljs index 2688393b9f..b48ccc6a89 100644 --- a/src/status_im/contexts/wallet/collectible/utils.cljs +++ b/src/status_im/contexts/wallet/collectible/utils.cljs @@ -12,7 +12,7 @@ :balance js/parseInt)) -(def ^:const supported-collectible-types +(def supported-collectible-types #{"image/jpeg" "image/gif" "image/bmp" diff --git a/src/status_im/contexts/wallet/collectible/view.cljs b/src/status_im/contexts/wallet/collectible/view.cljs index 7682e2d7d9..00e4d06fde 100644 --- a/src/status_im/contexts/wallet/collectible/view.cljs +++ b/src/status_im/contexts/wallet/collectible/view.cljs @@ -76,11 +76,10 @@ (defn navigate-back-and-clear-collectible [] (rf/dispatch [:navigate-back]) - (js/setTimeout #(rf/dispatch [:wallet/clear-last-collectible-details]) 700)) + (rf/dispatch [:wallet/clear-collectible-details])) (defn animated-header - [{:keys [scroll-amount title-opacity page-nav-type picture title description theme - id]}] + [{:keys [scroll-amount title-opacity page-nav-type picture title description theme id]}] (let [blur-amount (header-animations/use-blur-amount scroll-amount) layer-opacity (header-animations/use-layer-opacity scroll-amount @@ -149,8 +148,9 @@ (let [title-ref (rn/use-ref-atom nil) set-title-ref (rn/use-callback #(reset! title-ref %)) animation-shared-element-id (rf/sub [:animation-shared-element-id]) - collectible-owner (rf/sub [:wallet/last-collectible-details-owner]) - aspect-ratio (rf/sub [:wallet/last-collectible-aspect-ratio]) + collectible-owner (rf/sub [:wallet/collectible-details-owner collectible]) + aspect-ratio (rf/sub [:wallet/collectible-aspect-ratio]) + gradient-color (rf/sub [:wallet/collectible-gradient-color]) {:keys [id preview-url collection-data @@ -181,35 +181,36 @@ [rn/view [gradient-layer preview-uri] [quo/expanded-collectible - {:aspect-ratio aspect-ratio - :image-src preview-uri - :container-style (style/preview-container) - :counter (utils/collectible-owned-counter total-owned) - :native-ID (when (= animation-shared-element-id token-id) :shared-element) - :supported-file? (utils/supported-file? (:animation-media-type collectible-data)) - :on-press (fn [] - (if svg? - (js/alert "Can't visualize SVG images in lightbox") - (rf/dispatch - [:lightbox/navigate-to-lightbox - token-id - {:images [collectible-image] - :index 0 - :on-options-press #(rf/dispatch [:show-bottom-sheet - {:content - (fn [] - [options-drawer/view - {:name collectible-name - :image - preview-uri - :id id}])}])}]))) - :on-collectible-load (fn [] - ;; We need to delay the measurement because the - ;; navigation has an animation - (js/setTimeout - #(some-> @title-ref - (oops/ocall "measureInWindow" set-title-bottom)) - 300))}]] + {:aspect-ratio aspect-ratio + :image-src preview-uri + :container-style (style/preview-container) + :gradient-color-index gradient-color + :counter (utils/collectible-owned-counter total-owned) + :native-ID (when (= animation-shared-element-id token-id) :shared-element) + :supported-file? (utils/supported-file? (:animation-media-type collectible-data)) + :on-press (fn [] + (if svg? + (js/alert "Can't visualize SVG images in lightbox") + (rf/dispatch + [:lightbox/navigate-to-lightbox + token-id + {:images [collectible-image] + :index 0 + :on-options-press #(rf/dispatch [:show-bottom-sheet + {:content + (fn [] + [options-drawer/view + {:name collectible-name + :image + preview-uri + :id id}])}])}]))) + :on-collectible-load (fn [] + ;; We need to delay the measurement because the + ;; navigation has an animation + (js/setTimeout + #(some-> @title-ref + (oops/ocall "measureInWindow" set-title-bottom)) + 300))}]] [rn/view {:style (style/background-color theme)} [header collectible-name collection-name collection-image set-title-ref] [cta-buttons @@ -225,39 +226,43 @@ :default-active @selected-tab :on-change on-tab-change :data tabs-data}] - [tabs/view {:selected-tab @selected-tab}]]])))) + [tabs/view + {:selected-tab @selected-tab + :collectible collectible}]]])))) + +(defn- get-title-bottom-y-position + [y-element-position element-height] + (let [{:keys [top]} (safe-area/get-insets) + title-height -56] + (+ y-element-position + element-height + title-height + (when platform/ios? (* top -2))))) (defn view [_] - (let [{:keys [top]} (safe-area/get-insets) - theme (quo.theme/use-theme) - title-bottom-coord (rn/use-ref-atom 0) - set-title-bottom (rn/use-callback - (fn [_ y _ height] - (reset! title-bottom-coord - (+ y - height - -56 - (when platform/ios? - (* top -2)))))) - scroll-amount (reanimated/use-shared-value 0) - title-opacity (reanimated/use-shared-value 0) - collectible (rf/sub [:wallet/last-collectible-details]) - {:keys [preview-url - collection-data - collectible-data]} collectible - {preview-uri :uri} preview-url - {collectible-name :name} collectible-data - {collection-name :name} collection-data] + (let [{:keys [top]} (safe-area/get-insets) + theme (quo.theme/use-theme) + title-bottom-coord (rn/use-ref-atom 0) + set-title-bottom (rn/use-callback + (fn [_ y _ height] + (reset! title-bottom-coord + (get-title-bottom-y-position y height)))) + scroll-amount (reanimated/use-shared-value 0) + title-opacity (reanimated/use-shared-value 0) + {:keys [collection-data + collectible-data + preview-url] + :as collectible} (rf/sub [:wallet/collectible-details])] [rn/view {:style (style/background-color theme)} [animated-header {:id (:id collectible) :scroll-amount scroll-amount :title-opacity title-opacity :page-nav-type :title-description - :picture preview-uri - :title collectible-name - :description collection-name + :picture (:uri preview-url) + :title (:name collectible-data) + :description (:name collection-data) :theme theme}] [reanimated/scroll-view {:style (style/scroll-view top) diff --git a/src/status_im/contexts/wallet/common/collectibles_tab/view.cljs b/src/status_im/contexts/wallet/common/collectibles_tab/view.cljs index e0693ffd1c..81c793232e 100644 --- a/src/status_im/contexts/wallet/common/collectibles_tab/view.cljs +++ b/src/status_im/contexts/wallet/common/collectibles_tab/view.cljs @@ -13,25 +13,34 @@ [{:keys [preview-url collection-data collectible-data total-owned on-press on-long-press] :as collectible} index] - (let [on-press-fn (rn/use-callback #(when on-press - (on-press collectible %))) - on-long-press-fn (rn/use-callback #(when on-long-press - (on-long-press collectible)))] + (let [gradient-color (keyword (str "gradient-" (inc (mod index 5)))) + on-press-fn (rn/use-callback + (fn [aspect-ratio] + (when (fn? on-press) + (on-press {:collectible (dissoc collectible :on-press :on-long-press) + :aspect-ratio aspect-ratio + :gradient-color gradient-color})))) + on-long-press-fn (rn/use-callback + (fn [aspect-ratio] + (when on-long-press + (on-long-press + (dissoc collectible :on-press :on-long-press) + aspect-ratio))))] [quo/collectible-list-item {:type :card :image-src (:uri preview-url) :avatar-image-src (:image-url collection-data) - :collectible-name (:name collection-data) + :collectible-name (:name collectible-data) :supported-file? (utils/supported-file? (:animation-media-type collectible-data)) - :gradient-color-index (keyword (str "gradient-" (inc (mod index 5)))) + :gradient-color-index gradient-color :counter (utils/collectible-owned-counter total-owned) :container-style style/collectible-container :on-press on-press-fn :on-long-press on-long-press-fn}])) (defn view - [{:keys [collectibles filtered? on-end-reached - on-collectible-press current-account-address on-collectible-long-press]}] + [{:keys [collectibles filtered? on-end-reached on-collectible-press + current-account-address on-collectible-long-press]}] (let [theme (quo.theme/use-theme) no-results-match-query? (and filtered? (empty? collectibles))] (cond diff --git a/src/status_im/contexts/wallet/home/tabs/view.cljs b/src/status_im/contexts/wallet/home/tabs/view.cljs index 4de089ae3a..4011ab6225 100644 --- a/src/status_im/contexts/wallet/home/tabs/view.cljs +++ b/src/status_im/contexts/wallet/home/tabs/view.cljs @@ -17,9 +17,7 @@ :name (:name collectible-data) :image (:uri preview-url)}])}])) -(defn- on-collectible-press - [{:keys [id]} aspect-ratio] - (rf/dispatch [:wallet/get-collectible-details id aspect-ratio])) +(def on-collectible-press #(rf/dispatch [:wallet/navigate-to-collectible-details %])) (defn view [{:keys [selected-tab]}] diff --git a/src/status_im/contexts/wallet/send/select_asset/view.cljs b/src/status_im/contexts/wallet/send/select_asset/view.cljs index fc17f3e536..15ef6b0d18 100644 --- a/src/status_im/contexts/wallet/send/select_asset/view.cljs +++ b/src/status_im/contexts/wallet/send/select_asset/view.cljs @@ -32,7 +32,7 @@ {:collectibles collectibles :filtered? search-performed? :on-end-reached #(rf/dispatch [:wallet/request-collectibles-for-current-viewing-account]) - :on-collectible-press (fn [collectible] + :on-collectible-press (fn [{:keys [collectible]}] (rf/dispatch [:wallet/set-collectible-to-send {:collectible collectible :current-screen :screen/wallet.select-asset}]))}])) diff --git a/src/status_im/subs/wallet/collectibles.cljs b/src/status_im/subs/wallet/collectibles.cljs index fdcf56b7fc..d1f6fb7b35 100644 --- a/src/status_im/subs/wallet/collectibles.cljs +++ b/src/status_im/subs/wallet/collectibles.cljs @@ -20,8 +20,7 @@ (defn- preview-url [{{collectible-image-url :image-url animation-url :animation-url - animation-media-type :animation-media-type} :collectible-data - {collection-image-url :image-url} :collection-data}] + animation-media-type :animation-media-type} :collectible-data}] (cond (svg-animation? animation-url animation-media-type) {:uri animation-url @@ -34,7 +33,7 @@ {:uri collectible-image-url} :else - {:uri collection-image-url})) + {:uri nil})) (defn add-collectibles-preview-url [collectibles] @@ -91,35 +90,36 @@ current-account-collectibles)))) (re-frame/reg-sub - :wallet/last-collectible-details - :<- [:wallet] - (fn [wallet] - (let [last-collectible (:last-collectible-details wallet)] - (assoc last-collectible :preview-url (preview-url last-collectible))))) + :wallet/collectible + :<- [:wallet/ui] + :-> :collectible) (re-frame/reg-sub - :wallet/last-collectible-aspect-ratio - :<- [:wallet] - (fn [wallet] - (:last-collectible-aspect-ratio wallet))) - -(re-frame/reg-sub - :wallet/last-collectible-details-chain-id - :<- [:wallet/last-collectible-details] + :wallet/collectible-details + :<- [:wallet/collectible] (fn [collectible] - (get-in collectible [:id :contract-id :chain-id]))) + (as-> collectible $ + (:details $) + (assoc $ :preview-url (preview-url $))))) (re-frame/reg-sub - :wallet/last-collectible-details-traits - :<- [:wallet/last-collectible-details] + :wallet/collectible-aspect-ratio + :<- [:wallet/collectible] (fn [collectible] - (get-in collectible [:collectible-data :traits]))) + (:aspect-ratio collectible 1))) (re-frame/reg-sub - :wallet/last-collectible-details-owner - :<- [:wallet/last-collectible-details] - :<- [:wallet] - (fn [[collectible wallet]] - (let [address (:address (first (:ownership collectible))) - account (get-in wallet [:accounts address])] - account))) + :wallet/collectible-gradient-color + :<- [:wallet/collectible] + (fn [collectible] + (:gradient-color collectible :gradient-1))) + +(re-frame/reg-sub + :wallet/collectible-details-owner + :<- [:wallet/accounts] + (fn [accounts [_ collectible]] + (let [collectible-address (-> collectible :ownership first :address)] + (some #(when (= (:address %) collectible-address) + %) + accounts)))) + diff --git a/src/status_im/subs/wallet/collectibles_test.cljs b/src/status_im/subs/wallet/collectibles_test.cljs index 561cdd24d6..5654414d4e 100644 --- a/src/status_im/subs/wallet/collectibles_test.cljs +++ b/src/status_im/subs/wallet/collectibles_test.cljs @@ -2,49 +2,25 @@ (:require [cljs.test :refer [is testing]] [re-frame.db :as rf-db] - status-im.subs.root - status-im.subs.wallet.collectibles + [status-im.subs.root] + [status-im.subs.wallet.collectibles] [test-helpers.unit :as h] [utils.re-frame :as rf])) -(def ^:private traits - [{:trait-type "Background" - :value "Gradient 5" - :display-type "" - :max-value ""} - {:trait-type "Skin" - :value "Pale" - :display-type "" - :max-value ""} - {:trait-type "Clothes" - :value "Naked" - :display-type "" - :max-value ""}]) +(def collectible-owner-wallet + {:ui {:collectible {:details {:ownership [{:address "0x1"}]}}} + :accounts {"0x1" {:address "0x1" + :name "account 1" + :color "army"}}}) -(def ^:private collectible-owner-wallet - {:last-collectible-details {:ownership [{:address "0x1"}]} - :accounts {"0x1" {:name "account 1" - :color "army"}}}) - -(h/deftest-sub :wallet/last-collectible-details-chain-id - [sub-name] - (testing "correct chain-id of the last collectible should be returned" - (swap! rf-db/app-db assoc-in [:wallet :last-collectible-details :id :contract-id :chain-id] "1") - (let [result (rf/sub [sub-name])] - (is (= "1" result))))) - -(h/deftest-sub :wallet/last-collectible-details-traits - [sub-name] - (testing "correct traits of the last collectible should be returned" - (swap! rf-db/app-db assoc-in [:wallet :last-collectible-details :collectible-data :traits] traits) - (let [result (rf/sub [sub-name])] - (is (= traits result))))) - -(h/deftest-sub :wallet/last-collectible-details-owner +(h/deftest-sub :wallet/collectible-details-owner [sub-name] (testing "correct owner of the last collectible should be returned" - (swap! rf-db/app-db assoc-in [:wallet] collectible-owner-wallet) - (let [result (rf/sub [sub-name])] - (is (= {:name "account 1" - :color "army"} + (swap! rf-db/app-db assoc :wallet collectible-owner-wallet) + (let [collectible (-> collectible-owner-wallet :ui :collectible :details) + result (rf/sub [sub-name collectible])] + (is (= {:name "account 1" + :color "army" + :address "0x1" + :network-preferences-names #{}} result))))) From a2178951d7ae0f7310d11d0d7fa700515844177a Mon Sep 17 00:00:00 2001 From: Nikolay Date: Tue, 9 Jul 2024 13:49:31 +0300 Subject: [PATCH 12/79] chore(wallet): import private key - backend integration Co-authored-by: Jamie Caprani --- .../add_account/create_account/events.cljs | 71 ++++++- .../create_account/events_test.cljs | 4 +- .../import_private_key/view.cljs | 14 +- .../create_account/key_pair_name/view.cljs | 7 +- .../add_account/create_account/utils.cljs | 67 ++++-- .../add_account/create_account/view.cljs | 200 ++++++++++-------- src/status_im/contexts/wallet/effects.cljs | 4 +- 7 files changed, 236 insertions(+), 131 deletions(-) diff --git a/src/status_im/contexts/wallet/add_account/create_account/events.cljs b/src/status_im/contexts/wallet/add_account/create_account/events.cljs index dd7f56ca78..2c32ec04d6 100644 --- a/src/status_im/contexts/wallet/add_account/create_account/events.cljs +++ b/src/status_im/contexts/wallet/add_account/create_account/events.cljs @@ -71,7 +71,7 @@ (rf/reg-event-fx :wallet/seed-phrase-entered seed-phrase-entered) -(defn store-account-generated +(defn store-account-generated-with-mnemonic [{:keys [db]} [{:keys [new-account-data keypair-name]}]] (let [new-account (update new-account-data :mnemonic security/mask-data)] {:db (-> db @@ -85,20 +85,21 @@ :random-phrase)) :fx [[:dispatch [:navigate-back-to :screen/wallet.create-account]]]})) -(rf/reg-event-fx :wallet/store-account-generated store-account-generated) +(rf/reg-event-fx :wallet/store-account-generated-with-mnemonic store-account-generated-with-mnemonic) -(defn generate-account-for-keypair +(defn generate-account-for-keypair-with-mnemonic [{:keys [db]} [{:keys [keypair-name]}]] (let [seed-phrase (-> db :wallet :ui :create-account :new-keypair :seed-phrase)] {:fx [[:effects.wallet/create-account-from-mnemonic {:mnemonic-phrase (security/safe-unmask-data seed-phrase) :paths [constants/path-default-wallet] :on-success (fn [new-account-data] - (rf/dispatch [:wallet/store-account-generated + (rf/dispatch [:wallet/store-account-generated-with-mnemonic {:new-account-data new-account-data :keypair-name keypair-name}]))}]]})) -(rf/reg-event-fx :wallet/generate-account-for-keypair generate-account-for-keypair) +(rf/reg-event-fx :wallet/generate-account-for-keypair-with-mnemonic + generate-account-for-keypair-with-mnemonic) (defn clear-create-account-data [{:keys [db]}] @@ -159,14 +160,19 @@ (rf/reg-event-fx :wallet/create-keypair-with-account (fn [{db :db} [password account-preferences]] - (let [{:keys [keypair-name + (let [{:keys [workflow + keypair-name new-account-data]} (-> db :wallet :ui :create-account :new-keypair) + keypair-type (if (= workflow :workflow/new-keypair.import-private-key) + :key + :seed) keypair-with-account (create-account.utils/prepare-new-account {:keypair-name keypair-name + :keypair-type keypair-type :account-data new-account-data :account-preferences account-preferences}) new-address (some-> new-account-data - (create-account.utils/first-derived-account) + (create-account.utils/first-derived-account keypair-type) (:address) (string/lower-case)) unmasked-password (security/safe-unmask-data password)] @@ -176,7 +182,7 @@ :on-success [:wallet/add-account-success new-address] :on-error #(log/error "Failed to add Keypair and create account" %)}]]]}))) -(defn import-and-create-keypair-with-account +(defn import-mnemonic-and-create-keypair-with-account [{db :db} [{:keys [password account-preferences]}]] (let [account-data (-> db :wallet :ui :create-account :new-keypair :new-account-data) unmasked-mnemonic (security/safe-unmask-data (:mnemonic account-data)) @@ -187,7 +193,8 @@ :on-success #(rf/dispatch [:wallet/create-keypair-with-account password account-preferences])}]]]})) -(rf/reg-event-fx :wallet/import-and-create-keypair-with-account import-and-create-keypair-with-account) +(rf/reg-event-fx :wallet/import-mnemonic-and-create-keypair-with-account + import-mnemonic-and-create-keypair-with-account) (rf/reg-event-fx :wallet/derive-address-and-add-account @@ -206,3 +213,49 @@ :on-error [:wallet/log-rpc-error {:event :wallet/derive-address-and-add-account :params [derived-from-address [derivation-path]]}]}]]]})) + +(defn import-private-key-and-create-keypair-with-account + [{:keys [db]} [{:keys [password account-preferences]}]] + (let [private-key (get-in db + [:wallet :ui :create-account :new-keypair + :new-account-data :private-key]) + unmasked-private-key (security/safe-unmask-data private-key) + unmasked-password (security/safe-unmask-data password)] + {:json-rpc/call + [{:method "accounts_importPrivateKey" + :params [unmasked-private-key unmasked-password] + :on-success [:wallet/create-keypair-with-account password account-preferences] + :on-error #(log/error "Failed to import private key" %)}]})) + +(rf/reg-event-fx + :wallet/import-private-key-and-create-keypair-with-account + import-private-key-and-create-keypair-with-account) + +(defn store-account-generated-with-private-key + [{:keys [db]} [{:keys [new-account-data keypair-name]}]] + {:db (-> db + (update-in [:wallet :ui :create-account :new-keypair] + assoc + :new-account-data new-account-data + :keypair-name keypair-name) + (update-in [:wallet :ui :create-account :new-keypair] + dissoc + :private-key)) + :fx [[:dispatch [:navigate-back-to :screen/wallet.create-account]]]}) + +(rf/reg-event-fx :wallet/store-account-generated-with-private-key + store-account-generated-with-private-key) + +(rf/reg-event-fx + :wallet/import-private-key-and-generate-account-for-keypair + (fn [{:keys [db]} [{:keys [keypair-name]}]] + (let [private-key (get-in db [:wallet :ui :create-account :private-key])] + {:db (assoc-in db + [:wallet :ui :create-account :new-keypair :workflow] + :workflow/new-keypair.import-private-key) + :fx [[:effects.wallet/create-account-from-private-key + {:private-key private-key + :on-success (fn [new-account-data] + (rf/dispatch [:wallet/store-account-generated-with-private-key + {:keypair-name keypair-name + :new-account-data new-account-data}]))}]]}))) diff --git a/src/status_im/contexts/wallet/add_account/create_account/events_test.cljs b/src/status_im/contexts/wallet/add_account/create_account/events_test.cljs index dcc30d0a98..efdf6b3969 100644 --- a/src/status_im/contexts/wallet/add_account/create_account/events_test.cljs +++ b/src/status_im/contexts/wallet/add_account/create_account/events_test.cljs @@ -37,7 +37,7 @@ {:new-account-data {"test" "data" :mnemonic masked-mnemonic} :keypair-name "new-keypair-name"}}}}} - effects (events/store-account-generated {:db db} props) + effects (events/store-account-generated-with-mnemonic {:db db} props) result-db (:db effects) remove-mnemonic #(update-in % [:wallet :ui :create-account :new-keypair :new-account-data] @@ -61,7 +61,7 @@ expected-effects [[:effects.wallet/create-account-from-mnemonic {:mnemonic-phrase "test-secret" :paths [constants/path-default-wallet]}]] - effects (events/generate-account-for-keypair {:db db} props)] + effects (events/generate-account-for-keypair-with-mnemonic {:db db} props)] (is (match? (update-in effects [:fx 0 1] dissoc :on-success) {:fx expected-effects})) diff --git a/src/status_im/contexts/wallet/add_account/create_account/import_private_key/view.cljs b/src/status_im/contexts/wallet/add_account/create_account/import_private_key/view.cljs index e7f14a2e5f..0b7d205d75 100644 --- a/src/status_im/contexts/wallet/add_account/create_account/import_private_key/view.cljs +++ b/src/status_im/contexts/wallet/add_account/create_account/import_private_key/view.cljs @@ -63,31 +63,31 @@ (let [{:keys [message] :as props} (case state :scanning - {:type :info + {:status :default :icon :i/scanning :message :t/scanning-for-activity} :inactive-address - {:type :warning + {:status :warning :icon :i/info :message :t/this-account-has-no-activity} :active-address - {:type :success + {:status :success :icon :i/done :message :t/this-address-has-activity} :invalid-private-key - {:type :error + {:status :error :icon :i/info :message :t/invalid-private-key} nil)] (when props [quo/info-message - (assoc props - :size :default - :style style/indicator) + (-> props + (assoc :size :default :container-style style/indicator) + (dissoc :message)) (i18n/label message)]))) (defn on-unmount diff --git a/src/status_im/contexts/wallet/add_account/create_account/key_pair_name/view.cljs b/src/status_im/contexts/wallet/add_account/create_account/key_pair_name/view.cljs index 1d0b9acbb5..dd57cef619 100644 --- a/src/status_im/contexts/wallet/add_account/create_account/key_pair_name/view.cljs +++ b/src/status_im/contexts/wallet/add_account/create_account/key_pair_name/view.cljs @@ -4,7 +4,6 @@ [quo.core :as quo] [react-native.core :as rn] [status-im.common.floating-button-page.view :as floating-button-page] - [status-im.common.not-implemented :as not-implemented] [status-im.common.validation.general :as validators] [status-im.constants :as constants] [status-im.contexts.wallet.add-account.create-account.key-pair-name.style :as style] @@ -25,12 +24,12 @@ (defn- next-workflow-step [workflow key-pair-name] (case workflow - ;; TODO issue #19759. Implement creation account from private key :import-private-key - (not-implemented/alert) + (rf/dispatch [:wallet/import-private-key-and-generate-account-for-keypair + {:keypair-name key-pair-name}]) (:new-keypair :recovery-phrase) - (rf/dispatch [:wallet/generate-account-for-keypair + (rf/dispatch [:wallet/generate-account-for-keypair-with-mnemonic {:keypair-name key-pair-name}]) (do diff --git a/src/status_im/contexts/wallet/add_account/create_account/utils.cljs b/src/status_im/contexts/wallet/add_account/create_account/utils.cljs index 121cbc34a6..ad77728ae0 100644 --- a/src/status_im/contexts/wallet/add_account/create_account/utils.cljs +++ b/src/status_im/contexts/wallet/add_account/create_account/utils.cljs @@ -2,28 +2,57 @@ (:require [status-im.constants :as constants])) (defn first-derived-account - [account-data] - (-> account-data :derived first val)) + [account-data keypair-type] + (if (= keypair-type :seed) + (some-> account-data + :derived + first + val) + account-data)) + +(defn get-account-details + [keypair-type account account-to-create] + (if (= keypair-type :key) + {:key-uid (:key-uid account) + :public-key (:public-key account) + :account-address (:address account)} + {:key-uid (:keyUid account) + :public-key (:publicKey account-to-create) + :account-address (:address account-to-create)})) + +(defn get-account-config + [{:keys [account-address key-uid keypair-type public-key account-name emoji color]}] + {:address account-address + :key-uid key-uid + :wallet false + :chat false + :type keypair-type + :path constants/path-default-wallet + :public-key public-key + :name account-name + :emoji emoji + :colorID color + :hidden false}) (defn prepare-new-account - [{keypair-name :keypair-name - {:keys [keyUid address] :as account} :account-data - {:keys [account-name color emoji]} :account-preferences}] - (let [account-to-create (first-derived-account account) - account-config {:address (:address account-to-create) - :key-uid keyUid - :wallet false - :chat false - :type :seed - :path constants/path-default-wallet - :public-key (:publicKey account-to-create) - :name account-name - :emoji emoji - :colorID color - :hidden false}] - {:key-uid keyUid + [{:keys [keypair-name keypair-type] + {:keys [address] :as account} :account-data + {:keys [account-name color emoji]} :account-preferences}] + (let [account-to-create (first-derived-account account keypair-type) + {:keys [key-uid public-key account-address]} (get-account-details keypair-type + account + account-to-create) + account-config (get-account-config {:account-address + account-address + :key-uid key-uid + :keypair-type keypair-type + :public-key public-key + :account-name account-name + :emoji emoji + :color color})] + {:key-uid key-uid :name keypair-name - :type :seed + :type keypair-type :derived-from address :last-used-derivation-index 0 :accounts [account-config]})) diff --git a/src/status_im/contexts/wallet/add_account/create_account/view.cljs b/src/status_im/contexts/wallet/add_account/create_account/view.cljs index 79c95563a7..c0af91a51f 100644 --- a/src/status_im/contexts/wallet/add_account/create_account/view.cljs +++ b/src/status_im/contexts/wallet/add_account/create_account/view.cljs @@ -22,37 +22,38 @@ (defn- get-keypair-data [{:keys [title primary-keypair? new-keypair? derivation-path customization-color]}] - (let [formatted-path (string/replace derivation-path #"/" " / ") - on-auth-success (fn [password] - (rf/dispatch [:navigate-to - :screen/wallet.edit-derivation-path - {:password password - :current-derivation-path formatted-path}]))] - [{:title title - :image (if primary-keypair? :avatar :icon) - :image-props (if primary-keypair? - {:full-name (utils.string/get-initials title 1) - :size :xxs - :customization-color customization-color} - :i/seed) - :action (when-not new-keypair? :button) - :action-props {:on-press #(rf/dispatch [:navigate-to :screen/wallet.select-keypair]) - :button-text (i18n/label :t/edit) - :alignment :flex-start} - :description :text - :description-props {:text (i18n/label :t/on-device)}} - {:title (i18n/label :t/derivation-path) - :image :icon - :image-props :i/derivated-path - :action (if (ff/enabled? ::ff/wallet.edit-derivation-path) :button :none) - :action-props {:on-press #(rf/dispatch [:standard-auth/authorize - {:on-auth-success on-auth-success - :auth-button-label (i18n/label :t/continue)}]) - :button-text (i18n/label :t/edit) - :icon-left :i/face-id - :alignment :flex-start} - :description :text - :description-props {:text formatted-path}}])) + [{:title title + :image (if primary-keypair? :avatar :icon) + :image-props (if primary-keypair? + {:full-name (utils.string/get-initials title 1) + :size :xxs + :customization-color customization-color} + :i/seed) + :action (when-not new-keypair? :button) + :action-props {:on-press #(rf/dispatch [:navigate-to :screen/wallet.select-keypair]) + :button-text (i18n/label :t/edit) + :alignment :flex-start} + :description :text + :description-props {:text (i18n/label :t/on-device)}} + (when-not (string/blank? derivation-path) + (let [formatted-path (string/replace derivation-path #"/" " / ") + on-auth-success (fn [password] + (rf/dispatch [:navigate-to + :screen/wallet.edit-derivation-path + {:password password + :current-derivation-path formatted-path}]))] + {:title (i18n/label :t/derivation-path) + :image :icon + :image-props :i/derivated-path + :action (if (ff/enabled? ::ff/wallet.edit-derivation-path) :button :none) + :action-props {:on-press #(rf/dispatch [:standard-auth/authorize + {:on-auth-success on-auth-success + :auth-button-label (i18n/label :t/continue)}]) + :button-text (i18n/label :t/edit) + :icon-left :i/face-id + :alignment :flex-start} + :description :text + :description-props {:text formatted-path}}))]) (defn- avatar [{:keys [account-color emoji on-select-emoji]}] @@ -167,38 +168,59 @@ :container-style (style/slide-button-container bottom))]}] children)))) +(defn- on-auth-success-mnemonic + [password account-preferences] + (rf/dispatch + [:wallet/import-mnemonic-and-create-keypair-with-account + {:password password + :account-preferences account-preferences}])) + +(defn- on-auth-success-import-private-key + [password account-preferences] + (rf/dispatch + [:wallet/import-private-key-and-create-keypair-with-account + {:password password + :account-preferences account-preferences}])) + (defn add-new-keypair-variant [{{:keys [account-name account-color emoji]} :state}] - (let [on-auth-success (fn [password] - (rf/dispatch - [:wallet/import-and-create-keypair-with-account - {:password password - :account-preferences {:account-name @account-name - :color @account-color - :emoji @emoji}}]))] - (fn [{:keys [on-change-text set-account-color set-emoji customization-color keypair-name error]}] - (let [{:keys [new-account-data]} (rf/sub [:wallet/create-account-new-keypair])] - [floating-button - {:account-color @account-color - :slide-button-props {:on-auth-success on-auth-success - :disabled? (empty? @account-name) - :dependencies [new-account-data]}} - [avatar - {:account-color @account-color - :emoji @emoji - :on-select-emoji set-emoji}] - [input - {:account-color @account-color - :account-name @account-name - :on-change-text on-change-text - :error error}] - [color-picker - {:account-color @account-color - :set-account-color set-account-color}] - [new-account-origin - {:derivation-path constants/path-default-wallet - :customization-color customization-color - :keypair-title keypair-name}]])))) + (fn [{:keys [on-change-text set-account-color set-emoji customization-color keypair-name error]}] + (let [{:keys [new-account-data workflow]} (rf/sub [:wallet/create-account-new-keypair]) + derivation-path (when (not= workflow + :workflow/new-keypair.import-private-key) + constants/path-default-wallet)] + [floating-button + {:account-color @account-color + :slide-button-props {:on-auth-success (fn on-auth-success [password] + (let [account-preferences {:account-name @account-name + :color @account-color + :emoji @emoji}] + (if (= workflow + :workflow/new-keypair.import-private-key) + (on-auth-success-import-private-key + password + account-preferences) + (on-auth-success-mnemonic + password + account-preferences)))) + :disabled? (empty? @account-name) + :dependencies [new-account-data]}} + [avatar + {:account-color @account-color + :emoji @emoji + :on-select-emoji set-emoji}] + [input + {:account-color @account-color + :account-name @account-name + :on-change-text on-change-text + :error error}] + [color-picker + {:account-color @account-color + :set-account-color set-account-color}] + [new-account-origin + {:derivation-path derivation-path + :customization-color customization-color + :keypair-title keypair-name}]]))) (defn derive-account-variant [{{:keys [account-name account-color emoji]} :state}] @@ -262,33 +284,33 @@ :account-name-error account-name-error :emoji-and-color-error? emoji-and-color-error?}] (fn [] - (let [customization-color (rf/sub [:profile/customization-color]) + (let [customization-color (rf/sub [:profile/customization-color]) ;; Having a keypair means the user is importing it or creating it. - {:keys [keypair-name]} (rf/sub [:wallet/create-account-new-keypair]) - accounts-names (rf/sub [:wallet/accounts-names]) - accounts-emojis-and-colors (rf/sub [:wallet/accounts-emojis-and-colors]) - on-change-text (rn/use-callback - (fn [new-text] - (reset! account-name new-text) - (reset! account-name-error - (common.utils/get-account-name-error new-text - accounts-names))) - [accounts-names accounts-emojis-and-colors]) - check-emoji-and-color-error (fn [emoji color] - (let [repeated? (accounts-emojis-and-colors [emoji color])] - (reset! emoji-and-color-error? - (when repeated? :emoji-and-color)))) - set-account-color (rn/use-callback - (fn [new-color] - (reset! account-color new-color) - (check-emoji-and-color-error @emoji new-color)) - [accounts-emojis-and-colors @emoji]) - set-emoji (rn/use-callback - (fn [new-emoji] - (reset! emoji new-emoji) - (check-emoji-and-color-error new-emoji @account-color)) - [accounts-emojis-and-colors @account-color]) - error (or @account-name-error @emoji-and-color-error?)] + {:keys [keypair-name workflow]} (rf/sub [:wallet/create-account-new-keypair]) + accounts-names (rf/sub [:wallet/accounts-names]) + accounts-emojis-and-colors (rf/sub [:wallet/accounts-emojis-and-colors]) + on-change-text (rn/use-callback + (fn [new-text] + (reset! account-name new-text) + (reset! account-name-error + (common.utils/get-account-name-error new-text + accounts-names))) + [accounts-names accounts-emojis-and-colors]) + check-emoji-and-color-error (fn [emoji color] + (let [repeated? (accounts-emojis-and-colors [emoji color])] + (reset! emoji-and-color-error? + (when repeated? :emoji-and-color)))) + set-account-color (rn/use-callback + (fn [new-color] + (reset! account-color new-color) + (check-emoji-and-color-error @emoji new-color)) + [accounts-emojis-and-colors @emoji]) + set-emoji (rn/use-callback + (fn [new-emoji] + (reset! emoji new-emoji) + (check-emoji-and-color-error new-emoji @account-color)) + [accounts-emojis-and-colors @account-color]) + error (or @account-name-error @emoji-and-color-error?)] (rn/use-mount #(check-emoji-and-color-error @emoji @account-color)) (rn/use-unmount #(rf/dispatch [:wallet/clear-create-account])) @@ -300,8 +322,10 @@ :on-change-text on-change-text :set-account-color set-account-color :set-emoji set-emoji + :keypair-name keypair-name - :error error}] + :error error + :workflow workflow}] [derive-account-variant {:state state :customization-color customization-color diff --git a/src/status_im/contexts/wallet/effects.cljs b/src/status_im/contexts/wallet/effects.cljs index e2fe3c4147..c300b665a5 100644 --- a/src/status_im/contexts/wallet/effects.cljs +++ b/src/status_im/contexts/wallet/effects.cljs @@ -58,11 +58,11 @@ :emoji-hash emojiHash :key-uid keyUid :public-key publicKey - :private-key privateKey}))))) + :private-key (security/mask-data privateKey)}))))) (rf/reg-fx :effects.wallet/create-account-from-private-key - (fn [[private-key on-success on-error]] + (fn [{:keys [private-key on-success on-error]}] (-> (create-account-from-private-key private-key) (promesa/then (partial rf/call-continuation on-success)) (promesa/catch (partial rf/call-continuation on-error))))) From f70a743a8c71c1410f6b3899a9f2b0016fcab57a Mon Sep 17 00:00:00 2001 From: Omar Basem Date: Tue, 9 Jul 2024 15:59:48 +0400 Subject: [PATCH 13/79] Fix(wallet): address validation (#20674) * fix(wallet): address validation (#20674) --- src/status_im/contexts/wallet/events.cljs | 16 ++++++++++++++-- .../wallet/send/select_address/view.cljs | 6 ++++-- src/status_im/subs/wallet/wallet.cljs | 5 +++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/status_im/contexts/wallet/events.cljs b/src/status_im/contexts/wallet/events.cljs index e00cc1a2ec..bd3d1d9607 100644 --- a/src/status_im/contexts/wallet/events.cljs +++ b/src/status_im/contexts/wallet/events.cljs @@ -362,11 +362,19 @@ (rf/reg-event-fx :wallet/address-validation-success (fn [{:keys [db]}] - {:db (assoc-in db [:wallet :ui :search-address :valid-ens-or-address?] true)})) + {:db (update-in db + [:wallet :ui :search-address] + assoc + :valid-ens-or-address? true + :loading? false)})) (rf/reg-event-fx :wallet/address-validation-failed (fn [{:keys [db]}] - {:db (assoc-in db [:wallet :ui :search-address :valid-ens-or-address?] false)})) + {:db (update-in db + [:wallet :ui :search-address] + assoc + :valid-ens-or-address? false + :loading? false)})) (rf/reg-event-fx :wallet/clean-local-suggestions (fn [{:keys [db]}] @@ -374,6 +382,10 @@ (assoc-in [:wallet :ui :search-address :local-suggestions] []) (assoc-in [:wallet :ui :search-address :valid-ens-or-address?] false))})) +(rf/reg-event-fx :wallet/searching-address + (fn [{:keys [db]}] + {:db (assoc-in db [:wallet :ui :search-address :loading?] true)})) + (rf/reg-event-fx :wallet/navigate-to-chain-explorer-from-bottom-sheet (fn [_ [explorer-link address]] diff --git a/src/status_im/contexts/wallet/send/select_address/view.cljs b/src/status_im/contexts/wallet/send/select_address/view.cljs index f936422114..a92e372d97 100644 --- a/src/status_im/contexts/wallet/send/select_address/view.cljs +++ b/src/status_im/contexts/wallet/send/select_address/view.cljs @@ -76,6 +76,7 @@ 300))) :on-change-text (fn [text] (rf/dispatch [:wallet/clean-local-suggestions]) + (rf/dispatch [:wallet/searching-address]) (validate-address text) (reset! input-value text)) :valid-ens-or-address? valid-ens-or-address?}]))) @@ -182,7 +183,8 @@ input-focused? (reagent/atom false)] (fn [] (let [selected-tab (or (rf/sub [:wallet/send-tab]) (:id (first tabs-data))) - valid-ens-or-address? (boolean (rf/sub [:wallet/valid-ens-or-address?]))] + valid-ens-or-address? (boolean (rf/sub [:wallet/valid-ens-or-address?])) + searching-address? (rf/sub [:wallet/searching-address?])] [floating-button-page/view {:content-container-style {:flex 1} :footer-container-padding 0 @@ -198,7 +200,7 @@ :title-accessibility-label :title-label}] [address-input input-value input-focused?] [quo/divider-line] - (when (and (not valid-ens-or-address?) (> (count @input-value) 0)) + (when (and (not valid-ens-or-address?) (> (count @input-value) 0) (not searching-address?)) [rn/view {:style {:padding 20}} [quo/info-message {:status :error diff --git a/src/status_im/subs/wallet/wallet.cljs b/src/status_im/subs/wallet/wallet.cljs index e481c1c7fb..103fae5f03 100644 --- a/src/status_im/subs/wallet/wallet.cljs +++ b/src/status_im/subs/wallet/wallet.cljs @@ -595,6 +595,11 @@ :<- [:wallet/search-address] :-> :valid-ens-or-address?) +(rf/reg-sub + :wallet/searching-address? + :<- [:wallet/search-address] + :-> :loading?) + (rf/reg-sub :wallet/aggregated-fiat-balance-per-chain :<- [:wallet/aggregated-tokens] From 3521421098c735e45b88ccc4b66d8290dc39c06a Mon Sep 17 00:00:00 2001 From: Mohsen Date: Tue, 9 Jul 2024 21:12:18 +0330 Subject: [PATCH 14/79] [#20543] fix: incorrect black background behind floating button (#20585) --- .../floating_button_page/floating_container/view.cljs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/status_im/common/floating_button_page/floating_container/view.cljs b/src/status_im/common/floating_button_page/floating_container/view.cljs index b2d1a752ba..a01505a8a2 100644 --- a/src/status_im/common/floating_button_page/floating_container/view.cljs +++ b/src/status_im/common/floating_button_page/floating_container/view.cljs @@ -1,6 +1,7 @@ (ns status-im.common.floating-button-page.floating-container.view (:require [quo.core :as quo] + [quo.foundations.colors :as colors] [quo.theme :as quo.theme] [react-native.core :as rn] [status-im.common.floating-button-page.floating-container.style :as style])) @@ -9,9 +10,10 @@ [child] (let [theme (quo.theme/use-theme)] [quo/blur - {:blur-amount 12 - :blur-radius 12 - :blur-type theme} + {:blur-amount 52 + :blur-radius 20 + :blur-type :transparent + :blur-overlay-color (colors/theme-colors colors/white-70-blur colors/neutral-95-opa-70-blur theme)} [rn/view {:style style/blur-inner-container} child]])) From 6c352397abe04dbe6135558b1ab72f2abec47ea1 Mon Sep 17 00:00:00 2001 From: Omar Basem Date: Wed, 10 Jul 2024 14:49:24 +0400 Subject: [PATCH 15/79] fix(wallet): asset sorting (#20659) * fix(wallet): asset sorting (#20659) --- .../contexts/wallet/common/utils.cljs | 28 ++++++++++--- .../contexts/wallet/common/utils_test.cljs | 36 ++++++++++++++++ src/status_im/subs/wallet/wallet.cljs | 42 +++++++++---------- 3 files changed, 79 insertions(+), 27 deletions(-) diff --git a/src/status_im/contexts/wallet/common/utils.cljs b/src/status_im/contexts/wallet/common/utils.cljs index 3e2e9660e5..5232d1a4b9 100644 --- a/src/status_im/contexts/wallet/common/utils.cljs +++ b/src/status_im/contexts/wallet/common/utils.cljs @@ -177,7 +177,7 @@ "This function returns token values in the props of token-value (quo) component" [{:keys [token color currency currency-symbol]}] (let [balance (calculate-total-token-balance token) - fiat-value (calculate-token-fiat-value + fiat-unformatted-value (calculate-token-fiat-value {:currency currency :balance balance :token token}) @@ -191,7 +191,7 @@ crypto-value (get-standard-crypto-format token balance) fiat-value (get-standard-fiat-format crypto-value currency-symbol - fiat-value)] + fiat-unformatted-value)] {:token (:symbol token) :token-name (:name token) :state :default @@ -201,10 +201,11 @@ (neg? change-pct-24hour) :negative :else :empty) :customization-color color - :values {:crypto-value crypto-value - :fiat-value fiat-value - :fiat-change formatted-token-price - :percentage-change percentage-change}})) + :values {:crypto-value crypto-value + :fiat-value fiat-value + :fiat-unformatted-value fiat-unformatted-value + :fiat-change formatted-token-price + :percentage-change percentage-change}})) (defn get-multichain-address [networks address] @@ -298,3 +299,18 @@ (utils.string/contains-emoji? s) :emoji (existing-account-names s) :existing-name (utils.string/contains-special-character? s) :special-character)) + +(defn calculate-and-sort-tokens + [{:keys [tokens color currency currency-symbol]}] + (let [calculate-token (fn [token] + (calculate-token-value {:token token + :color color + :currency currency + :currency-symbol currency-symbol})) + calculated-tokens (map calculate-token tokens) + token-priority {"SNT" 1 "STT" 1 "ETH" 2 "DAI" 3}] + (sort-by (fn [token] + (let [fiat-value (get-in token [:values :fiat-unformatted-value]) + priority (get token-priority (:token token) 999)] + [(- fiat-value) priority])) + calculated-tokens))) diff --git a/src/status_im/contexts/wallet/common/utils_test.cljs b/src/status_im/contexts/wallet/common/utils_test.cljs index bce1cf4390..281f8f06fd 100644 --- a/src/status_im/contexts/wallet/common/utils_test.cljs +++ b/src/status_im/contexts/wallet/common/utils_test.cljs @@ -115,3 +115,39 @@ (is (= (utils/prettify-percentage-change 1.113454) "1.11")) (is (= (utils/prettify-percentage-change -0.35) "0.35")) (is (= (utils/prettify-percentage-change -0.78234) "0.78")))) + +(deftest calculate-and-sort-tokens-test + (testing "calculate-and-sort-tokens function" + (let [mock-color "blue" + mock-currency "USD" + mock-currency-symbol "$"] + + (with-redefs [utils/calculate-token-value + (fn [{:keys [token]}] + (case (:symbol token) + "ETH" {:token "ETH" :values {:fiat-unformatted-value 5}} + "DAI" {:token "DAI" :values {:fiat-unformatted-value 10}} + "SNT" {:token "SNT" :values {:fiat-unformatted-value 1}}))] + (testing "Standard case with different fiat-unformatted-values" + (let [mock-tokens [{:symbol "ETH" + :name "Ethereum" + :balances-per-chain {:mock-chain 5} + :decimals 18} + {:symbol "DAI" + :name "Dai" + :balances-per-chain {:mock-chain 10} + :decimals 18} + {:symbol "SNT" + :name "Status Network Token" + :balances-per-chain {:mock-chain 1} + :decimals 18}] + mock-input {:tokens mock-tokens + :color mock-color + :currency mock-currency + :currency-symbol mock-currency-symbol} + sorted-tokens (map :token (utils/calculate-and-sort-tokens mock-input)) + expected-order ["DAI" "ETH" "SNT"]] + (is (= expected-order sorted-tokens)))))))) + + + diff --git a/src/status_im/subs/wallet/wallet.cljs b/src/status_im/subs/wallet/wallet.cljs index 103fae5f03..e1b69acf50 100644 --- a/src/status_im/subs/wallet/wallet.cljs +++ b/src/status_im/subs/wallet/wallet.cljs @@ -489,19 +489,6 @@ (fn [ui] (get-in ui [:account-page :active-tab]))) -(rf/reg-sub - :wallet/current-viewing-account-token-values - :<- [:wallet/current-viewing-account] - :<- [:wallet/current-viewing-account-tokens-in-selected-networks] - :<- [:profile/currency] - :<- [:profile/currency-symbol] - (fn [[{:keys [color]} tokens currency currency-symbol]] - (mapv #(utils/calculate-token-value {:token % - :color color - :currency currency - :currency-symbol currency-symbol}) - tokens))) - (rf/reg-sub :wallet/aggregated-tokens :<- [:wallet/accounts-without-watched-accounts] @@ -515,6 +502,18 @@ (fn [[aggregated-tokens chain-ids]] (utils/filter-tokens-in-chains aggregated-tokens chain-ids))) +(rf/reg-sub + :wallet/current-viewing-account-token-values + :<- [:wallet/current-viewing-account] + :<- [:wallet/current-viewing-account-tokens-in-selected-networks] + :<- [:profile/currency] + :<- [:profile/currency-symbol] + (fn [[{:keys [color]} tokens currency currency-symbol]] + (utils/calculate-and-sort-tokens {:tokens tokens + :color color + :currency currency + :currency-symbol currency-symbol}))) + (rf/reg-sub :wallet/aggregated-token-values-and-balance :<- [:wallet/aggregated-tokens-in-selected-networks] @@ -522,16 +521,17 @@ :<- [:profile/currency] :<- [:profile/currency-symbol] (fn [[aggregated-tokens color currency currency-symbol]] - (let [balance (utils/calculate-balance-from-tokens {:currency currency - :tokens aggregated-tokens}) - formatted-balance (utils/prettify-balance currency-symbol balance)] + (let [balance (utils/calculate-balance-from-tokens {:currency currency + :tokens aggregated-tokens}) + formatted-balance (utils/prettify-balance currency-symbol balance) + sorted-token-values (utils/calculate-and-sort-tokens {:tokens aggregated-tokens + :color color + :currency currency + :currency-symbol currency-symbol})] {:balance balance :formatted-balance formatted-balance - :tokens (mapv #(utils/calculate-token-value {:token % - :color color - :currency currency - :currency-symbol currency-symbol}) - aggregated-tokens)}))) + :tokens sorted-token-values}))) + (rf/reg-sub :wallet/network-preference-details From 7ff7e78691d850b3b9cb99fb756988a7cd7b2123 Mon Sep 17 00:00:00 2001 From: Parvesh Monu Date: Wed, 10 Jul 2024 17:10:44 +0530 Subject: [PATCH 16/79] fix user lands on the Home screen after closing a community opened from Discover Communities --- src/status_im/contexts/communities/events.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/status_im/contexts/communities/events.cljs b/src/status_im/contexts/communities/events.cljs index 1b750a4f01..0612243aa8 100644 --- a/src/status_im/contexts/communities/events.cljs +++ b/src/status_im/contexts/communities/events.cljs @@ -343,7 +343,7 @@ (when (get-in db [:communities community-id :joined]) [:dispatch [:activity-center.notifications/dismiss-community-overview community-id]])]} - (when-not (or (= current-view-id :shell) (= current-view-id :communities-stack)) + (when-not (#{:shell :communities-stack :discover-communities} current-view-id) (navigation/pop-to-root :shell-stack)))))) (rf/reg-event-fx :communities/navigate-to-community-overview navigate-to-community-overview) From d179cbf59b15d4a8503a75a76c367122504a16b3 Mon Sep 17 00:00:00 2001 From: Yevheniia Berdnyk Date: Wed, 10 Jul 2024 16:28:02 +0300 Subject: [PATCH 17/79] e2e: updated discover communities test --- test/appium/tests/critical/chats/test_public_chat_browsing.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/appium/tests/critical/chats/test_public_chat_browsing.py b/test/appium/tests/critical/chats/test_public_chat_browsing.py index 42a0e6facb..0e00666f2b 100644 --- a/test/appium/tests/critical/chats/test_public_chat_browsing.py +++ b/test/appium/tests/critical/chats/test_public_chat_browsing.py @@ -291,7 +291,6 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase): self.errors.append("Status community logo is different from expected template.") self.community_view.close_community_view_button.click() - self.home.discover_communities_button.click() self.home.swipe_up() except TimeoutException: From f89b66f5622e5d2a819567dbd1ad3d4e19dee35f Mon Sep 17 00:00:00 2001 From: Yevheniia Berdnyk Date: Wed, 10 Jul 2024 15:59:26 +0300 Subject: [PATCH 18/79] e2e: adding android.gpu.mode capability to fix emulators crash --- test/appium/tests/base_test_case.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/appium/tests/base_test_case.py b/test/appium/tests/base_test_case.py index 5d55126251..6af9c477ee 100644 --- a/test/appium/tests/base_test_case.py +++ b/test/appium/tests/base_test_case.py @@ -88,6 +88,7 @@ def get_capabilities_sauce_lab(): caps['sauce:options']['name'] = test_suite_data.current_test.name caps['sauce:options']['maxDuration'] = 3600 caps['sauce:options']['idleTimeout'] = 1000 + caps['sauce:options']['android.gpu.mode'] = 'hardware' options = AppiumOptions() options.load_capabilities(caps) From 311fde9409666565fc122fa9c837dd9f28a890e3 Mon Sep 17 00:00:00 2001 From: John Ngei Date: Thu, 11 Jul 2024 02:09:03 +0200 Subject: [PATCH 19/79] chore: remove identifiers screens (#20503) * chore: remove identifiers screens so users are navigated straight to enable notifications during onboarding * removed navigated back button from enable notification screen * fix top margin * e2e: updated sign in flow * make lint-fix --------- Co-authored-by: Yevheniia Berdnyk --- .../onboarding/enable_notifications/view.cljs | 5 +---- src/status_im/contexts/onboarding/events.cljs | 2 +- src/status_im/navigation/screens.cljs | 11 +++++------ test/appium/views/sign_in_view.py | 12 ++++++------ 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/status_im/contexts/onboarding/enable_notifications/view.cljs b/src/status_im/contexts/onboarding/enable_notifications/view.cljs index 1706597eff..0c043b8081 100644 --- a/src/status_im/contexts/onboarding/enable_notifications/view.cljs +++ b/src/status_im/contexts/onboarding/enable_notifications/view.cljs @@ -72,10 +72,7 @@ (let [insets (safe-area/get-insets)] [rn/view {:style (style/page-container insets)} [rn/view {:style style/page-heading} - [quo/page-nav - {:background :blur - :icon-name :i/arrow-left - :on-press #(rf/dispatch [:navigate-back])}] + [quo/page-nav {:type :no-title :background :blur}] [page-title]] [enable-notifications-simple] [enable-notification-buttons {:insets insets}]])) diff --git a/src/status_im/contexts/onboarding/events.cljs b/src/status_im/contexts/onboarding/events.cljs index 865d0ba09c..b5a5a75a7f 100644 --- a/src/status_im/contexts/onboarding/events.cljs +++ b/src/status_im/contexts/onboarding/events.cljs @@ -73,7 +73,7 @@ :onboarding/navigated-to-enter-seed-phrase-from-screen :screen/onboarding.new-to-status)]] :dispatch-later [{:ms constants/onboarding-generating-keys-animation-duration-ms - :dispatch [:onboarding/navigate-to-identifiers]}] + :dispatch [:init-root :screen/onboarding.enable-notifications]}] :db (-> db (dissoc :profile/login) (dissoc :auth-method) diff --git a/src/status_im/navigation/screens.cljs b/src/status_im/navigation/screens.cljs index d7b72a78f5..f8e8345452 100644 --- a/src/status_im/navigation/screens.cljs +++ b/src/status_im/navigation/screens.cljs @@ -346,12 +346,11 @@ :component enter-seed-phrase/view} {:name :screen/onboarding.enable-notifications - :options {:theme :dark - :layout options/onboarding-transparent-layout - :animations (merge - transitions/new-to-status-modal-animations - transitions/push-animations-for-transparent-background) - :modalPresentationStyle :overCurrentContext} + :options {:theme :dark + :layout options/onboarding-transparent-layout + :animations (merge + transitions/new-to-status-modal-animations + transitions/push-animations-for-transparent-background)} :component enable-notifications/view} {:name :screen/onboarding.identifiers diff --git a/test/appium/views/sign_in_view.py b/test/appium/views/sign_in_view.py index a88f169048..6de36d7027 100644 --- a/test/appium/views/sign_in_view.py +++ b/test/appium/views/sign_in_view.py @@ -244,8 +244,8 @@ class SignInView(BaseView): self.generate_keys_button.click_until_presence_of_element(self.profile_title_input) self.set_profile(username) self.set_password(password) - if self.enable_biometric_maybe_later_button.is_element_displayed(10): - self.enable_biometric_maybe_later_button.click() + # if self.enable_biometric_maybe_later_button.is_element_displayed(10): + # self.enable_biometric_maybe_later_button.click() # self.next_button.click_until_absense_of_element(self.element_by_translation_id("intro-wizard-title2")) # if keycard: # keycard_flow = self.keycard_storage_button.click() @@ -256,7 +256,7 @@ class SignInView(BaseView): # self.create_password_input.send_keys(password) # self.confirm_your_password_input.send_keys(password) # self.next_button.click() - self.identifiers_button.wait_and_click(30) + # self.identifiers_button.wait_and_click(30) if enable_notifications: self.enable_notifications_button.click_until_presence_of_element(self.start_button) if self.allow_button.is_element_displayed(): @@ -283,9 +283,9 @@ class SignInView(BaseView): self.continue_button.click_until_presence_of_element(self.profile_title_input) self.set_profile(username, set_image) self.set_password(password) - if self.enable_biometric_maybe_later_button.is_element_displayed(10): - self.enable_biometric_maybe_later_button.click() - self.identifiers_button.wait_and_click(30) + # if self.enable_biometric_maybe_later_button.is_element_displayed(10): + # self.enable_biometric_maybe_later_button.click() + # self.identifiers_button.wait_and_click(30) if enable_notifications: self.enable_notifications_button.click_until_presence_of_element(self.start_button) else: From 7a8db689f4fd32b6a89b67a54254bb46a4bbc17c Mon Sep 17 00:00:00 2001 From: Andrea Maria Piana Date: Wed, 10 Jul 2024 08:24:30 +0100 Subject: [PATCH 20/79] enable light client by default --- src/status_im/contexts/profile/config.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/status_im/contexts/profile/config.cljs b/src/status_im/contexts/profile/config.cljs index a19a5728f7..c4b8c16c17 100644 --- a/src/status_im/contexts/profile/config.cljs +++ b/src/status_im/contexts/profile/config.cljs @@ -40,7 +40,7 @@ :verifyENSURL config/verify-ens-url :verifyENSContractAddress config/verify-ens-contract-address :verifyTransactionChainID config/verify-transaction-chain-id - :wakuV2LightClient false + :wakuV2LightClient true :previewPrivacy config/blank-preview? :testNetworksEnabled config/test-networks-enabled?}))) From 0a0591f67a8edcc45bfa8cbae998c2afa7331c3c Mon Sep 17 00:00:00 2001 From: Andrea Maria Piana Date: Thu, 4 Jul 2024 16:45:00 +0100 Subject: [PATCH 21/79] Add centralized metrics https://github.com/status-im/status-go/compare/6e056348...1ef2434b This commit adds support for pushing centralized metrics to mixpanel. It uses an interceptor and only picks a few selected events to push through. In order to test: 1) Create an account in the app 2) Go to Settings->Privacy 3) Enable metrics You should now see the events on the mixpanel dashboard (login with your status-im account for access). Only some example events are tracked, they are just for testing --- Makefile | 6 + .../status/ethereum/module/AccountManager.kt | 8 ++ .../im/status/ethereum/module/StatusModule.kt | 10 ++ .../ios/RCTStatus/AccountManager.m | 9 ++ .../ios/RCTStatus/RCTStatus.m | 18 +++ modules/react-native-status/nodejs/status.cpp | 33 ++++- scripts/build-android.sh | 2 + shadow-cljs.edn | 6 + src/legacy/status_im/utils/test.cljs | 130 ------------------ src/native_module/core.cljs | 18 ++- src/status_im/config.cljs | 5 + .../contexts/centralized_metrics/effects.cljs | 9 ++ .../contexts/centralized_metrics/events.cljs | 33 +++++ .../centralized_metrics/events_test.cljs | 37 +++++ .../centralized_metrics/tracking.cljs | 40 ++++++ .../centralized_metrics/tracking_test.cljs | 35 +++++ .../actions/community_options/view.cljs | 1 + src/status_im/contexts/profile/config.cljs | 29 ++-- src/status_im/contexts/profile/events.cljs | 44 +++--- .../contexts/profile/settings/list_items.cljs | 13 +- .../contexts/profile/settings/view.cljs | 2 +- .../settings/privacy_and_security/view.cljs | 15 +- src/status_im/setup/interceptors.cljs | 2 + src/status_im/subs/root.cljs | 4 + src/tests/test_utils.cljs | 9 +- status-go-version.json | 6 +- translations/en.json | 1 + 27 files changed, 337 insertions(+), 188 deletions(-) delete mode 100644 src/legacy/status_im/utils/test.cljs create mode 100644 src/status_im/contexts/centralized_metrics/effects.cljs create mode 100644 src/status_im/contexts/centralized_metrics/events.cljs create mode 100644 src/status_im/contexts/centralized_metrics/events_test.cljs create mode 100644 src/status_im/contexts/centralized_metrics/tracking.cljs create mode 100644 src/status_im/contexts/centralized_metrics/tracking_test.cljs diff --git a/Makefile b/Makefile index f6a70f9e2c..256f164bb7 100644 --- a/Makefile +++ b/Makefile @@ -457,6 +457,12 @@ android-tail-geth: export VERSION ?= debug android-tail-geth: adb shell 'while true; do cat; sleep 1; done < /storage/emulated/0/Android/data/im.status.ethereum$$( [ "$(VERSION)" = "release" ] || echo ".$(VERSION)" )/files/Download/geth.log' +android-clean-geth: export TARGET := android-sdk +android-clean-geth: export VERSION ?= debug +android-clean-geth: + adb shell 'rm /storage/emulated/0/Android/data/im.status.ethereum$$( [ "$(VERSION)" = "release" ] || echo ".$(VERSION)" )/files/Download/geth.log' + + android-logcat: export TARGET := android-sdk android-logcat: ##@other Read status-mobile logs from Android phone using adb adb logcat | grep -e RNBootstrap -e ReactNativeJS -e ReactNative -e StatusModule -e StatusNativeLogs -e 'F DEBUG :' -e 'Go :' -e 'GoLog :' -e 'libc :' diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/AccountManager.kt b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/AccountManager.kt index 51b8fea480..a3a64d5ef4 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/AccountManager.kt +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/AccountManager.kt @@ -246,6 +246,14 @@ class AccountManager(private val reactContext: ReactApplicationContext) : ReactC utils.executeRunnableStatusGoMethod({ Statusgo.openAccounts(rootDir) }, callback) } + @ReactMethod + private fun initializeApplication(request: String, callback: Callback) { + Log.d(TAG, "initializeApplication") + Log.d(TAG, "[Initializing application $request") + utils.executeRunnableStatusGoMethod({ Statusgo.initializeApplication(request) }, callback) + } + + @ReactMethod fun logout() { Log.d(TAG, "logout") diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.kt b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.kt index af80b40d00..7cbf56f30f 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.kt +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.kt @@ -76,6 +76,16 @@ class StatusModule(private val reactContext: ReactApplicationContext, private va utils.executeRunnableStatusGoMethod({ Statusgo.getNodeConfig() }, callback) } + @ReactMethod + fun addCentralizedMetric(request: String, callback: Callback) { + utils.executeRunnableStatusGoMethod({ Statusgo.addCentralizedMetric(request) }, callback) + } + + @ReactMethod + fun toggleCentralizedMetrics(request: String, callback: Callback) { + utils.executeRunnableStatusGoMethod({ Statusgo.toggleCentralizedMetrics(request) }, callback) + } + @ReactMethod fun deleteImportedKey(keyUID: String, address: String, password: String, callback: Callback) { val keyStoreDir = utils.getKeyStorePath(keyUID) diff --git a/modules/react-native-status/ios/RCTStatus/AccountManager.m b/modules/react-native-status/ios/RCTStatus/AccountManager.m index 3caafde74c..d8dd8aa556 100644 --- a/modules/react-native-status/ios/RCTStatus/AccountManager.m +++ b/modules/react-native-status/ios/RCTStatus/AccountManager.m @@ -195,6 +195,15 @@ RCT_EXPORT_METHOD(verifyDatabasePassword:(NSString *)keyUID callback(@[result]); } +RCT_EXPORT_METHOD(initializeApplication:(NSString *)request + callback:(RCTResponseSenderBlock)callback) { +#if DEBUG + NSLog(@"initializeApplication() method called"); +#endif + NSString *result = StatusgoInitializeApplication(request); + callback(@[result]); +} + RCT_EXPORT_METHOD(openAccounts:(RCTResponseSenderBlock)callback) { #if DEBUG NSLog(@"OpenAccounts() method called"); diff --git a/modules/react-native-status/ios/RCTStatus/RCTStatus.m b/modules/react-native-status/ios/RCTStatus/RCTStatus.m index 9a9bbf95ba..7149440534 100644 --- a/modules/react-native-status/ios/RCTStatus/RCTStatus.m +++ b/modules/react-native-status/ios/RCTStatus/RCTStatus.m @@ -176,6 +176,24 @@ RCT_EXPORT_METHOD(appStateChange:(NSString *)type) { StatusgoAppStateChange(type); } +RCT_EXPORT_METHOD(addCentralizedMetric:(NSString *)request + callback:(RCTResponseSenderBlock)callback) { +#if DEBUG + NSLog(@"addCentralizedMetric() method called"); +#endif + NSString *result = StatusgoAddCentralizedMetric(request); + callback(@[result]); +} + +RCT_EXPORT_METHOD(toggleCentralizedMetrics:(NSString *)request + callback:(RCTResponseSenderBlock)callback) { +#if DEBUG + NSLog(@"toggleCentralizedMetrics() method called"); +#endif + NSString *result = StatusgoToggleCentralizedMetrics(request); + callback(@[result]); +} + RCT_EXPORT_METHOD(startLocalNotifications) { #if DEBUG NSLog(@"StartLocalNotifications() method called"); diff --git a/modules/react-native-status/nodejs/status.cpp b/modules/react-native-status/nodejs/status.cpp index 620d77032c..126aece4e0 100644 --- a/modules/react-native-status/nodejs/status.cpp +++ b/modules/react-native-status/nodejs/status.cpp @@ -302,6 +302,36 @@ void _MultiAccountStoreAccount(const FunctionCallbackInfo& args) { delete c; } +void _InitializeApplication(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + if (args.Length() != 1) { + // Throw an Error that is passed back to JavaScript + isolate->ThrowException(Exception::TypeError( + String::NewFromUtf8Literal(isolate, "Wrong number of arguments for InitializeApplication"))); + return; + } + + // Check the argument types + + if (!args[0]->IsString()) { + isolate->ThrowException(Exception::TypeError( + String::NewFromUtf8Literal(isolate, "Wrong argument type for request"))); + return; + } + + + String::Utf8Value arg0Obj(isolate, args[0]->ToString(context).ToLocalChecked()); + char *arg0 = *arg0Obj; + + // Call exported Go function, which returns a C string + char *c = InitializeApplication(arg0); + + Local ret = String::NewFromUtf8(isolate, c).ToLocalChecked(); + args.GetReturnValue().Set(ret); + delete c; +} void _InitKeystore(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); @@ -1935,6 +1965,7 @@ void init(Local exports) { NODE_SET_METHOD(exports, "multiAccountStoreDerivedAccounts", _MultiAccountStoreDerivedAccounts); NODE_SET_METHOD(exports, "multiAccountStoreAccount", _MultiAccountStoreAccount); NODE_SET_METHOD(exports, "initKeystore", _InitKeystore); + NODE_SET_METHOD(exports, "initializeApplication", _InitializeApplication); NODE_SET_METHOD(exports, "fleets", _Fleets); NODE_SET_METHOD(exports, "stopCPUProfiling", _StopCPUProfiling); NODE_SET_METHOD(exports, "encodeTransfer", _EncodeTransfer); @@ -1972,7 +2003,7 @@ void init(Local exports) { NODE_SET_METHOD(exports, "signTypedData", _SignTypedData); NODE_SET_METHOD(exports, "sendTransaction", _SendTransaction); NODE_SET_METHOD(exports, "appStateChange", _AppStateChange); - NODE_SET_METHOD(exports, "setSignalEventCallback", _SetSignalEventCallback); + NODE_SET_METHOD(exports, "setSignalEventCallback", _SetSignalEventCallback); NODE_SET_METHOD(exports, "validateNodeConfig", _ValidateNodeConfig); NODE_SET_METHOD(exports, "hashTypedData", _HashTypedData); NODE_SET_METHOD(exports, "recover", _Recover); diff --git a/scripts/build-android.sh b/scripts/build-android.sh index eeb3a8e573..64470e7e22 100755 --- a/scripts/build-android.sh +++ b/scripts/build-android.sh @@ -45,6 +45,8 @@ SECRETS_ENV_VARS=( 'INFURA_TOKEN' 'INFURA_TOKEN_SECRET' 'OPENSEA_API_KEY' + 'MIXPANEL_APP_ID' + 'MIXPANEL_TOKEN' 'POKT_TOKEN' ) diff --git a/shadow-cljs.edn b/shadow-cljs.edn index d9f226b1b8..ad71280492 100644 --- a/shadow-cljs.edn +++ b/shadow-cljs.edn @@ -67,6 +67,8 @@ :closure-defines {status-im.config/POKT_TOKEN #shadow/env "POKT_TOKEN" status-im.config/INFURA_TOKEN #shadow/env "INFURA_TOKEN" + status-im.config/MIXPANEL_APP_ID #shadow/env "MIXPANEL_APP_ID" + status-im.config/MIXPANEL_TOKEN #shadow/env "MIXPANEL_TOKEN" status-im.config/OPENSEA_API_KEY #shadow/env "OPENSEA_API_KEY" status-im.config/RARIBLE_MAINNET_API_KEY #shadow/env "RARIBLE_MAINNET_API_KEY" status-im.config/RARIBLE_TESTNET_API_KEY #shadow/env "RARIBLE_TESTNET_API_KEY" @@ -102,6 +104,8 @@ {status-im.config/POKT_TOKEN #shadow/env "POKT_TOKEN" status-im.config/INFURA_TOKEN #shadow/env "INFURA_TOKEN" status-im.config/OPENSEA_API_KEY #shadow/env "OPENSEA_API_KEY" + status-im.config/MIXPANEL_APP_ID #shadow/env "MIXPANEL_APP_ID" + status-im.config/MIXPANEL_TOKEN #shadow/env "MIXPANEL_TOKEN" status-im.config/RARIBLE_MAINNET_API_KEY #shadow/env "RARIBLE_MAINNET_API_KEY" status-im.config/RARIBLE_TESTNET_API_KEY #shadow/env "RARIBLE_TESTNET_API_KEY" status-im.config/ALCHEMY_ETHEREUM_MAINNET_TOKEN #shadow/env "ALCHEMY_ETHEREUM_MAINNET_TOKEN" @@ -143,6 +147,8 @@ status-im.config/POKT_TOKEN #shadow/env "POKT_TOKEN" status-im.config/INFURA_TOKEN #shadow/env "INFURA_TOKEN" status-im.config/OPENSEA_API_KEY #shadow/env "OPENSEA_API_KEY" + status-im.config/MIXPANEL_APP_ID #shadow/env "MIXPANEL_APP_ID" + status-im.config/MIXPANEL_TOKEN #shadow/env "MIXPANEL_TOKEN" status-im.config/RARIBLE_MAINNET_API_KEY #shadow/env "RARIBLE_MAINNET_API_KEY" status-im.config/RARIBLE_TESTNET_API_KEY #shadow/env "RARIBLE_TESTNET_API_KEY" status-im.config/ALCHEMY_ETHEREUM_MAINNET_TOKEN #shadow/env "ALCHEMY_ETHEREUM_MAINNET_TOKEN" diff --git a/src/legacy/status_im/utils/test.cljs b/src/legacy/status_im/utils/test.cljs deleted file mode 100644 index 4bfb83d7b1..0000000000 --- a/src/legacy/status_im/utils/test.cljs +++ /dev/null @@ -1,130 +0,0 @@ -(ns legacy.status-im.utils.test - (:require - [legacy.status-im.utils.deprecated-types :as types] - [re-frame.core :as re-frame])) - -(def native-status (js/require "../../modules/react-native-status/nodejs/bindings")) - -(def fs (js/require "fs")) -(def path (js/require "path")) -(def os (js/require "os")) - -(def tmpdir (.tmpdir os)) - -(def test-dir-prefix (.join path tmpdir "status-mobile-tests")) - -(def test-dir (.mkdtempSync fs test-dir-prefix)) - -(def initialized? (atom false)) - -(defn signal-received-callback - [a] - (re-frame/dispatch [:signals/signal-received a])) - -;; We poll for signals, could not get callback working -(defn init! - [] - (when-not @initialized? - (.setSignalEventCallback native-status) - (reset! initialized? true) - (js/setInterval (fn [] - (.pollSignal native-status signal-received-callback) - 100)))) - -(def ui-helper - (clj->js - {:clearCookies identity - :clearStorageAPIs identity})) - -(def encryption-utils - (clj->js - {:sha3 - (fn [s] (.sha3 native-status s)) - :setBlankPreviewFlag - identity - :encodeTransfer - (fn [to-norm amount-hex] - (.encodeTransfer native-status to-norm amount-hex)) - :hexToNumber - (fn [hex] (.hexToNumber native-status hex)) - :decodeParameters - (fn [decode-param-json] - (.decodeParameters native-status decode-param-json)) - :numberToHex - (fn [num-str] (.numberToHex native-status num-str)) - :initKeystore - (fn [key-uid callback] - (callback (.initKeystore native-status - (str test-dir "/keystore/" key-uid)))) - :multiformatDeserializePublicKey - (fn [public-key deserialization-key callback] - (callback (.multiformatDeserializePublicKey - native-status - public-key - deserialization-key)))})) - -(def account-manager - (clj->js - {:openAccounts - (fn [callback] - (callback (.openAccounts native-status test-dir))) - :createAccountAndLogin - (fn [request] (.createAccountAndLogin native-status request)) - :logout - (fn [] (.logout native-status)) - :multiAccountImportMnemonic - (fn [json callback] - (callback (.multiAccountImportMnemonic native-status json))) - :multiAccountLoadAccount - (fn [json callback] - (callback (.multiAccountLoadAccount native-status json))) - :multiAccountDeriveAddresses - (fn [json callback] - (callback (.multiAccountDeriveAddresses native-status json))) - :multiAccountGenerateAndDeriveAddresses - (fn [json callback] - (callback (.multiAccountGenerateAndDeriveAddresses native-status json))) - :multiAccountStoreDerived - (fn [json callback] - (callback (.multiAccountStoreDerivedAccounts native-status json)))})) - -(def utils - (clj->js - {:backupDisabledDataDir - (fn [] (str test-dir "/backup")) - :keystoreDir (fn [] "") - :toChecksumAddress - (fn [address] (.toChecksumAddress native-status address)) - :checkAddressChecksum - (fn [address] (.checkAddressChecksum native-status address)) - :validateMnemonic - (fn [json callback] (callback (.validateMnemonic native-status json))) - :isAddress - (fn [address] (.isAddress native-status address))})) - -(def log-manager - (clj->js - {:logFileDirectory - (fn [] (str test-dir "/log")) - :initLogging - (fn [enabled mobile-system log-level callback] - (callback (.initLogging native-status - (types/clj->json {:Enabled enabled - :MobileSystem mobile-system - :Level log-level - :File (str test-dir "/geth.log")}))))})) - -(def network - (clj->js - {:callPrivateRPC - (fn [payload callback] - (callback (.callPrivateRPC native-status payload)))})) - -(def status - (clj->js - {:getNodeConfig - (fn [] (types/clj->json {:WakuV2Config ""})) - :fleets - (fn [] (.fleets native-status)) - :startLocalNotifications - identity})) diff --git a/src/native_module/core.cljs b/src/native_module/core.cljs index 9195bf0f94..44f3f84d24 100644 --- a/src/native_module/core.cljs +++ b/src/native_module/core.cljs @@ -72,10 +72,12 @@ (log/debug "[native-module] init-keystore" key-uid) (.initKeystore ^js (encryption) key-uid callback)) -(defn open-accounts - [callback] - (log/debug "[native-module] open-accounts") - (.openAccounts ^js (account-manager) #(callback (types/json->clj %)))) +(defn initialize-application + [request callback] + (log/debug "[native-module] initialize-application") + (.initializeApplication ^js (account-manager) + (types/clj->json request) + #(callback (types/json->clj %)))) (defn prepare-dir-and-update-config [key-uid config callback] @@ -513,6 +515,14 @@ (let [result (.checkAddressChecksum ^js (utils) address)] (types/json->clj result))) +(defn toggle-centralized-metrics + [enabled callback] + (.toggleCentralizedMetrics ^js (status) (types/clj->json {:enabled enabled}) callback)) + +(defn add-centralized-metric + [metric] + (.addCentralizedMetric ^js (status) (types/clj->json metric) #(log/debug "pushed metric" % metric))) + (defn address? [address] (log/debug "[native-module] address?") diff --git a/src/status_im/config.cljs b/src/status_im/config.cljs index 89e0022297..1213040f6f 100644 --- a/src/status_im/config.cljs +++ b/src/status_im/config.cljs @@ -25,6 +25,8 @@ (goog-define ALCHEMY_OPTIMISM_GOERLI_TOKEN "") (goog-define ALCHEMY_OPTIMISM_SEPOLIA_TOKEN "") (goog-define WALLET_CONNECT_PROJECT_ID "87815d72a81d739d2a7ce15c2cfdefb3") +(goog-define MIXPANEL_APP_ID "3350627") +(goog-define MIXPANEL_TOKEN "5c73bda2d36a9f688a5ee45641fb6775") (def mainnet-rpc-url (str "https://eth-archival.rpc.grove.city/v1/" POKT_TOKEN)) (def goerli-rpc-url (str "https://goerli-archival.gateway.pokt.network/v1/lb/" POKT_TOKEN)) @@ -39,6 +41,9 @@ (def opensea-link "https://opensea.io") (def opensea-tesnet-link "https://testnets.opensea.io") +(def mixpanel-app-id MIXPANEL_APP_ID) +(def mixpanel-token MIXPANEL_TOKEN) + (def opensea-api-key OPENSEA_API_KEY) (def bootnodes-settings-enabled? (enabled? (get-config :BOOTNODES_SETTINGS_ENABLED "1"))) (def mailserver-confirmations-enabled? (enabled? (get-config :MAILSERVER_CONFIRMATIONS_ENABLED))) diff --git a/src/status_im/contexts/centralized_metrics/effects.cljs b/src/status_im/contexts/centralized_metrics/effects.cljs new file mode 100644 index 0000000000..dfa2db377d --- /dev/null +++ b/src/status_im/contexts/centralized_metrics/effects.cljs @@ -0,0 +1,9 @@ +(ns status-im.contexts.centralized-metrics.effects + (:require + [native-module.core :as native-module] + [taoensso.timbre :as log] + [utils.re-frame :as rf])) + +(rf/reg-fx :effects.centralized-metrics/toggle-metrics + (fn [enabled?] + (native-module/toggle-centralized-metrics enabled? #(log/debug "toggled-metrics" % enabled?)))) diff --git a/src/status_im/contexts/centralized_metrics/events.cljs b/src/status_im/contexts/centralized_metrics/events.cljs new file mode 100644 index 0000000000..a9e303802d --- /dev/null +++ b/src/status_im/contexts/centralized_metrics/events.cljs @@ -0,0 +1,33 @@ +(ns status-im.contexts.centralized-metrics.events + (:require + [native-module.core :as native-module] + [re-frame.interceptor :as interceptor] + status-im.contexts.centralized-metrics.effects + [status-im.contexts.centralized-metrics.tracking :as tracking] + [taoensso.timbre :as log] + [utils.re-frame :as rf])) + +(defn push-event? + [db] + (or (not (:centralized-metrics/user-confirmed? db)) + (:centralized-metrics/enabled? db))) + +(defn centralized-metrics-interceptor + [context] + (when-let [event (tracking/tracked-event (interceptor/get-coeffect context :event))] + (log/debug "tracking event" event) + (when (push-event? (interceptor/get-coeffect context :db)) + (native-module/add-centralized-metric event))) + context) + +(def interceptor + (interceptor/->interceptor + :id :centralized-metrics + :after centralized-metrics-interceptor)) + +(rf/reg-event-fx :centralized-metrics/toggle-centralized-metrics + (fn [{:keys [db]} [enabled?]] + {:fx [[:effects.centralized-metrics/toggle-metrics enabled?]] + :db (assoc db + :centralized-metrics/user-confirmed? true + :centralized-metrics/enabled? enabled?)})) diff --git a/src/status_im/contexts/centralized_metrics/events_test.cljs b/src/status_im/contexts/centralized_metrics/events_test.cljs new file mode 100644 index 0000000000..816609694f --- /dev/null +++ b/src/status_im/contexts/centralized_metrics/events_test.cljs @@ -0,0 +1,37 @@ +(ns status-im.contexts.centralized-metrics.events-test + (:require + [cljs.test :refer-macros [deftest is testing]] + matcher-combinators.test + [status-im.contexts.centralized-metrics.events :as events] + [status-im.contexts.centralized-metrics.tracking :as tracking] + [test-helpers.unit :as h])) + +(deftest push-event-test + (testing "returns correct boolean value" + (is (true? (events/push-event? {:centralized-metrics/user-confirmed? false}))) + (is (true? (events/push-event? {:centralized-metrics/enabled? true}))) + (is (false? (events/push-event? {:centralized-metrics/user-confirmed? true + :centralized-metrics/enabled? false}))))) + +(deftest centralized-metrics-interceptor-test + (testing "processes context correctly" + (with-redefs [tracking/tracked-event (fn [_] {:metric "mocked-event"}) + events/push-event? (fn [_] true)] + (let [context {:coeffects {:event [:some-event] + :db {:centralized-metrics/enabled? true}}}] + (is (= context (events/centralized-metrics-interceptor context))))))) + +(h/deftest-event :centralized-metrics/toggle-centralized-metrics + [event-id dispatch] + (testing "toggling value to true" + (let [enabled? true + expected-fxs {:db {:centralized-metrics/user-confirmed? true + :centralized-metrics/enabled? enabled?} + :fx [[:effects.centralized-metrics/toggle-metrics enabled?]]}] + (is (match? expected-fxs (dispatch [event-id enabled?]))))) + (testing "toggling value to false" + (let [enabled? false + expected-fxs {:db {:centralized-metrics/user-confirmed? true + :centralized-metrics/enabled? enabled?} + :fx [[:effects.centralized-metrics/toggle-metrics enabled?]]}] + (is (match? expected-fxs (dispatch [event-id enabled?])))))) diff --git a/src/status_im/contexts/centralized_metrics/tracking.cljs b/src/status_im/contexts/centralized_metrics/tracking.cljs new file mode 100644 index 0000000000..df1de0f9b1 --- /dev/null +++ b/src/status_im/contexts/centralized_metrics/tracking.cljs @@ -0,0 +1,40 @@ +(ns status-im.contexts.centralized-metrics.tracking + (:require + [legacy.status-im.utils.build :as build] + [react-native.platform :as platform])) + +(defn user-journey-event + [action] + {:metric + {:eventName "user-journey" + :platform platform/os + :appVersion build/app-short-version + :eventValue {:action action}}}) + +(def ^:const app-started-event "app-started") +(def ^:const navigate-to-create-profile-event "navigate-to-create-profile") +(def ^:const communities-tab-clicked "communities-tab-clicked") +(def ^:const wallet-tab-clicked "wallet-tab-clicked") +(def ^:const chats-tab-clicked "chats-tab-clicked") + +(defn track-view-id-event + [view-id] + (case view-id + :communities-stack (user-journey-event communities-tab-clicked) + :chats-stack (user-journey-event chats-tab-clicked) + :wallet-stack (user-journey-event wallet-tab-clicked) + nil)) + +(defn tracked-event + [[event-name second-parameter]] + (case event-name + :onboarding/navigate-to-create-profile + (user-journey-event navigate-to-create-profile-event) + + :profile/get-profiles-overview-success + (user-journey-event app-started-event) + + :set-view-id + (track-view-id-event second-parameter) + + nil)) diff --git a/src/status_im/contexts/centralized_metrics/tracking_test.cljs b/src/status_im/contexts/centralized_metrics/tracking_test.cljs new file mode 100644 index 0000000000..bf38e42bfb --- /dev/null +++ b/src/status_im/contexts/centralized_metrics/tracking_test.cljs @@ -0,0 +1,35 @@ +(ns status-im.contexts.centralized-metrics.tracking-test + (:require + [cljs.test :refer-macros [deftest is testing]] + [legacy.status-im.utils.build :as build] + [react-native.platform :as platform] + [status-im.contexts.centralized-metrics.tracking :as tracking])) + +(deftest user-journey-event-test + (testing "creates correct metric event" + (let [action "some-action" + expected {:metric {:eventName "user-journey" + :platform platform/os + :appVersion build/app-short-version + :eventValue {:action action}}}] + (is (= expected (tracking/user-journey-event action)))))) + +(deftest track-view-id-event-test + (testing "returns correct event for view-id" + (is (= (tracking/user-journey-event tracking/communities-tab-clicked) + (tracking/track-view-id-event :communities-stack))) + (is (= (tracking/user-journey-event tracking/chats-tab-clicked) + (tracking/track-view-id-event :chats-stack))) + (is (= (tracking/user-journey-event tracking/wallet-tab-clicked) + (tracking/track-view-id-event :wallet-stack))) + (is (nil? (tracking/track-view-id-event :unknown-stack))))) + +(deftest tracked-event-test + (testing "returns correct event for given inputs" + (is (= (tracking/user-journey-event tracking/navigate-to-create-profile-event) + (tracking/tracked-event [:onboarding/navigate-to-create-profile]))) + (is (= (tracking/user-journey-event tracking/app-started-event) + (tracking/tracked-event [:profile/get-profiles-overview-success]))) + (is (= (tracking/track-view-id-event :wallet-stack) + (tracking/tracked-event [:set-view-id :wallet-stack]))) + (is (nil? (tracking/tracked-event [:unknown-event]))))) diff --git a/src/status_im/contexts/communities/actions/community_options/view.cljs b/src/status_im/contexts/communities/actions/community_options/view.cljs index 674de0ba47..88b653b508 100644 --- a/src/status_im/contexts/communities/actions/community_options/view.cljs +++ b/src/status_im/contexts/communities/actions/community_options/view.cljs @@ -139,6 +139,7 @@ [id token-gated? intro-message] [[(when-not token-gated? (view-members id)) (when-not token-gated? (view-rules id intro-message)) + (mark-as-read id) (invite-contacts id) (when token-gated? (view-token-gating id)) (show-qr id) diff --git a/src/status_im/contexts/profile/config.cljs b/src/status_im/contexts/profile/config.cljs index c4b8c16c17..f9e0c3409c 100644 --- a/src/status_im/contexts/profile/config.cljs +++ b/src/status_im/contexts/profile/config.cljs @@ -28,21 +28,20 @@ (defn create [] (let [log-enabled? (boolean (not-empty config/log-level))] - (merge (login) - {:deviceName (native-module/get-installation-name) - :rootDataDir (native-module/backup-disabled-data-dir) - :rootKeystoreDir (native-module/keystore-dir) - - :logLevel (when log-enabled? config/log-level) - :logEnabled log-enabled? - :logFilePath (native-module/log-file-directory) - :verifyTransactionURL config/verify-transaction-url - :verifyENSURL config/verify-ens-url - :verifyENSContractAddress config/verify-ens-contract-address - :verifyTransactionChainID config/verify-transaction-chain-id - :wakuV2LightClient true - :previewPrivacy config/blank-preview? - :testNetworksEnabled config/test-networks-enabled?}))) + (assoc (login) + :deviceName (native-module/get-installation-name) + :rootDataDir (native-module/backup-disabled-data-dir) + :rootKeystoreDir (native-module/keystore-dir) + :logLevel (when log-enabled? config/log-level) + :logEnabled log-enabled? + :logFilePath (native-module/log-file-directory) + :verifyTransactionURL config/verify-transaction-url + :verifyENSURL config/verify-ens-url + :verifyENSContractAddress config/verify-ens-contract-address + :verifyTransactionChainID config/verify-transaction-chain-id + :wakuV2LightClient true + :previewPrivacy config/blank-preview? + :testNetworksEnabled config/test-networks-enabled?))) (defn strip-file-prefix [path] diff --git a/src/status_im/contexts/profile/events.cljs b/src/status_im/contexts/profile/events.cljs index 337ecbb5de..cb394aa08d 100644 --- a/src/status_im/contexts/profile/events.cljs +++ b/src/status_im/contexts/profile/events.cljs @@ -2,6 +2,7 @@ (:require [legacy.status-im.data-store.settings :as data-store.settings] [native-module.core :as native-module] + [status-im.config :as config] [status-im.contexts.profile.edit.accent-colour.events] [status-im.contexts.profile.edit.bio.events] [status-im.contexts.profile.edit.header.events] @@ -27,7 +28,10 @@ (rf/reg-fx :profile/get-profiles-overview (fn [callback] - (native-module/open-accounts callback))) + (native-module/initialize-application {:dataDir (native-module/backup-disabled-data-dir) + :mixpanelAppId config/mixpanel-app-id + :mixpanelToken config/mixpanel-token} + callback))) (rf/reg-event-fx :profile/profile-selected @@ -36,23 +40,27 @@ (rf/reg-event-fx :profile/get-profiles-overview-success - (fn [{:keys [db]} [profiles-overview]] - (if (seq profiles-overview) - (let [profiles (reduce-profiles profiles-overview) - {:keys [key-uid]} (first (sort-by :timestamp > (vals profiles)))] - {:db (if key-uid - (-> db - (assoc :profile/profiles-overview profiles) - (update :profile/login #(select-profile % key-uid))) - db) - :fx [[:dispatch [:init-root :screen/profile.profiles]] - (when key-uid - [:effects.biometric/check-if-available - {:key-uid key-uid - :on-success (fn [auth-method] - (rf/dispatch [:profile.login/check-biometric-success key-uid - auth-method]))}])]}) - {:fx [[:dispatch [:init-root :screen/onboarding.intro]]]}))) + (fn [{:keys [db]} [{:keys [accounts] {:keys [userConfirmed enabled]} :centralizedMetricsInfo}]] + (let [db-with-settings (assoc db + :centralized-metrics/user-confirmed? userConfirmed + :centralized-metrics/enabled? enabled)] + (if (seq accounts) + (let [profiles (reduce-profiles accounts) + {:keys [key-uid]} (first (sort-by :timestamp > (vals profiles)))] + {:db (if key-uid + (-> db-with-settings + (assoc :profile/profiles-overview profiles) + (update :profile/login #(select-profile % key-uid))) + db-with-settings) + :fx [[:dispatch [:init-root :screen/profile.profiles]] + (when key-uid + [:effects.biometric/check-if-available + {:key-uid key-uid + :on-success (fn [auth-method] + (rf/dispatch [:profile.login/check-biometric-success key-uid + auth-method]))}])]}) + {:db db-with-settings + :fx [[:dispatch [:init-root :screen/onboarding.intro]]]})))) (rf/reg-event-fx :profile/update-setting-from-backup diff --git a/src/status_im/contexts/profile/settings/list_items.cljs b/src/status_im/contexts/profile/settings/list_items.cljs index 78e6c0c6a7..c0a70da6a0 100644 --- a/src/status_im/contexts/profile/settings/list_items.cljs +++ b/src/status_im/contexts/profile/settings/list_items.cljs @@ -61,13 +61,12 @@ :image :icon :blur? true :action :arrow})] - [(when config/show-not-implemented-features? - {:title (i18n/label :t/privacy-and-security) - :on-press #(rf/dispatch [:open-modal :screen/settings-privacy-and-security]) - :image-props :i/privacy - :image :icon - :blur? true - :action :arrow}) + [{:title (i18n/label :t/privacy-and-security) + :on-press #(rf/dispatch [:open-modal :screen/settings-privacy-and-security]) + :image-props :i/privacy + :image :icon + :blur? true + :action :arrow} {:title (i18n/label :t/syncing) :on-press #(rf/dispatch [:open-modal :settings-syncing]) :image-props :i/syncing diff --git a/src/status_im/contexts/profile/settings/view.cljs b/src/status_im/contexts/profile/settings/view.cljs index 59f173ac9d..bbef55bfd8 100644 --- a/src/status_im/contexts/profile/settings/view.cljs +++ b/src/status_im/contexts/profile/settings/view.cljs @@ -79,7 +79,7 @@ profile)}}])}]}]] [rn/flat-list {:header [settings.header/view {:scroll-y scroll-y}] - :data (settings.items/items (boolean (:mnemonic profile))) + :data (settings.items/items (boolean (seq (:mnemonic profile)))) :shows-vertical-scroll-indicator false :render-fn settings-category-view :get-item-layout get-item-layout diff --git a/src/status_im/contexts/settings/privacy_and_security/view.cljs b/src/status_im/contexts/settings/privacy_and_security/view.cljs index c7c87d54df..1ce7641bb8 100644 --- a/src/status_im/contexts/settings/privacy_and_security/view.cljs +++ b/src/status_im/contexts/settings/privacy_and_security/view.cljs @@ -13,8 +13,9 @@ (defn view [] - (let [insets (safe-area/get-insets) - customization-color (rf/sub [:profile/customization-color])] + (let [insets (safe-area/get-insets) + centralized-metrics-enabled? (rf/sub [:centralized-metrics/enabled?]) + customization-color (rf/sub [:profile/customization-color])] [quo/overlay {:type :shell :container-style (style/page-wrapper (:top insets))} @@ -30,13 +31,15 @@ :customization-color customization-color}] [quo/category {:key :category - :data [{:title "Dummy" + :data [{:title (i18n/label :t/share-usage-data) :image-props :i/placeholder - :image :icon :blur? true :action :selector - :action-props {:on-change identity - :checked? false} + :action-props {:on-change #(rf/dispatch + [:centralized-metrics/toggle-centralized-metrics + (not centralized-metrics-enabled?)]) + :customization-color customization-color + :checked? centralized-metrics-enabled?} :on-press identity}] :blur? true :list-type :settings}]])) diff --git a/src/status_im/setup/interceptors.cljs b/src/status_im/setup/interceptors.cljs index 8310f413cd..7c76c24d11 100644 --- a/src/status_im/setup/interceptors.cljs +++ b/src/status_im/setup/interceptors.cljs @@ -2,11 +2,13 @@ (:require [re-frame.core :as re-frame] [re-frame.std-interceptors :as std-interceptors] + [status-im.contexts.centralized-metrics.events :as centralized-metrics] [utils.re-frame :as rf])) (defn register-global-interceptors [] (re-frame/reg-global-interceptor rf/debug-handlers-names) + (re-frame/reg-global-interceptor centralized-metrics/interceptor) (re-frame/reg-global-interceptor (re-frame/inject-cofx :now)) ;; Interceptor `trim-v` removes the first element of the event vector. diff --git a/src/status_im/subs/root.cljs b/src/status_im/subs/root.cljs index 44ca7a67bf..c84fdd9dfb 100644 --- a/src/status_im/subs/root.cljs +++ b/src/status_im/subs/root.cljs @@ -185,3 +185,7 @@ ;;theme (reg-root-key-sub :theme :theme) + +;; centralized-metrics +(reg-root-key-sub :centralized-metrics/enabled? :centralized-metrics/enabled?) +(reg-root-key-sub :centralized-metrics/user-confirmed? :centralized-metrics/user-confirmed?) diff --git a/src/tests/test_utils.cljs b/src/tests/test_utils.cljs index 85b2dcd742..29b11cb8bb 100644 --- a/src/tests/test_utils.cljs +++ b/src/tests/test_utils.cljs @@ -65,9 +65,9 @@ (def account-manager (clj->js - {:openAccounts - (fn [callback] - (callback (.openAccounts native-status test-dir))) + {:initializeApplication + (fn [request callback] + (callback (.initializeApplication native-status request))) :createAccountAndLogin (fn [request] (.createAccountAndLogin native-status request)) :restoreAccountAndLogin @@ -130,6 +130,9 @@ (clj->js {:getNodeConfig (fn [] (types/clj->json {:WakuV2Config ""})) + :addCentralizedMetric + (fn [_ callback] + (callback)) :fleets (fn [] (.fleets native-status)) :startLocalNotifications diff --git a/status-go-version.json b/status-go-version.json index 62115adb5c..24f27cd75a 100644 --- a/status-go-version.json +++ b/status-go-version.json @@ -3,7 +3,7 @@ "_comment": "Instead use: scripts/update-status-go.sh ", "owner": "status-im", "repo": "status-go", - "version": "v0.181.31", - "commit-sha1": "6e056348e6d28f962167118612826f1ef0e47b22", - "src-sha256": "1qvfwk28sg93basjzy8r55qz8pk9xg0h7kxv5ykxkbfh4q17a1ns" + "version": "v0.181.35", + "commit-sha1": "1ef2434b0644581e3c0404d767550cbfee8ad829", + "src-sha256": "1dfw4cff1blg063xxrh78dgivcar92k1fspcr84ra1fxw674kmfk" } diff --git a/translations/en.json b/translations/en.json index dea6d1742e..a4452c46ce 100644 --- a/translations/en.json +++ b/translations/en.json @@ -2749,5 +2749,6 @@ "import-keypair-to-use-account": "Import key pair to use this account", "import-keypair-steps": "{{account-name}} was derived from your {{keypair-name}} key pair, which has not yet been imported to this device. To transact using this account, you will need to import the {{keypair-name}} key pair first.", "not-now": "Not now", + "share-usage-data": "Share usage data with Status", "value-higher-than-send-amount": "This value is higher than entered amount to send" } From d74edff9b9e2fcfc95ca40ca58d4f8764746f8ca Mon Sep 17 00:00:00 2001 From: Shivek Khurana Date: Thu, 11 Jul 2024 15:20:42 +0530 Subject: [PATCH 22/79] =?UTF-8?q?2=EF=B8=8F=E2=83=A3=20Use=20v2=20api=20fo?= =?UTF-8?q?r=20persisting=20sessions=20(#20648)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 2️⃣ Use v2 api for persisting sessions * 🗑️ Remove traces of REPL and v2 suffix --------- Co-authored-by: Lungu Cristian --- .../wallet/wallet_connect/events.cljs | 42 ++++++++----------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/src/status_im/contexts/wallet/wallet_connect/events.cljs b/src/status_im/contexts/wallet/wallet_connect/events.cljs index e0697c97bc..250776568b 100644 --- a/src/status_im/contexts/wallet/wallet_connect/events.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/events.cljs @@ -166,16 +166,10 @@ {:web3-wallet web3-wallet :proposal current-proposal :supported-namespaces supported-namespaces - :on-success (fn [] + :on-success (fn [approved-session] (log/info "Wallet Connect session approved") - (let [metadata (wallet-connect-core/get-session-dapp-metadata - current-proposal)] - (rf/dispatch [:wallet-connect/reset-current-session-proposal]) - (rf/dispatch [:wallet-connect/persist-session - {:id (:id current-proposal) - :dapp-name (:name metadata) - :dapp-url (:url metadata) - :session-info current-proposal}]))) + (rf/dispatch [:wallet-connect/reset-current-session-proposal]) + (rf/dispatch [:wallet-connect/persist-session approved-session])) :on-fail (fn [error] (log/error "Wallet Connect session approval failed" {:error error @@ -210,20 +204,6 @@ {:version version}))}]]]} {:fx [[:dispatch [:wallet-connect/pair scanned-text]]]})))) -(rf/reg-event-fx - :wallet-connect/persist-session - (fn [_ [{:keys [id dapp-name dapp-url session-info]}]] - {:fx [[:json-rpc/call - [{:method "wakuext_addWalletConnectSession" - :params [{:id (str id) - :dappName dapp-name - :dappUrl dapp-url - :info (-> session-info - clj->js - js/JSON.stringify)}] - :on-success #(log/info "Wallet Connect session persisted") - :on-error #(log/info "Wallet Connect session persistence failed" %)}]]]})) - (rf/reg-event-fx :wallet-connect/fetch-persisted-sessions-success (fn [{:keys [db]} [sessions]] @@ -233,6 +213,18 @@ :wallet-connect/fetch-persisted-sessions (fn [_ _] {:fx [[:json-rpc/call - [{:method "wakuext_getWalletConnectSession" + [{:method "wallet_getWalletConnectActiveSessions" + ;; This is the activeSince timestamp to avoid expired sessions + ;; 0 means, return everything + :params [0] :on-success [:wallet-connect/fetch-persisted-sessions-success] - :on-error #(log/info "Wallet Connect fetch persisted sessions failed")}]]]})) + :on-error #(log/info "Wallet Connect fetch persisted sessions failed" %)}]]]})) + +(rf/reg-event-fx + :wallet-connect/persist-session + (fn [_ [session-info]] + {:fx [[:json-rpc/call + [{:method "wallet_addWalletConnectSession" + :params [(js/JSON.stringify session-info)] + :on-success #(log/info "Wallet Connect session persisted") + :on-error #(log/info "Wallet Connect session persistence failed" %)}]]]})) From 072370b0d6700530b43136f05a12cc5d404642d9 Mon Sep 17 00:00:00 2001 From: Shivek Khurana Date: Thu, 11 Jul 2024 15:33:14 +0530 Subject: [PATCH 23/79] =?UTF-8?q?=F0=9F=99=85=20Gracefully=20reject=20prop?= =?UTF-8?q?osals=20and=20requests=20(#20649)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🙅 Gracefully reject proposals and requests * 🔑 Remove redudant fx, send correct error * ➡️ Docstring to comment --------- Co-authored-by: Lungu Cristian --- src/react_native/wallet_connect.cljs | 2 + src/status_im/constants.cljs | 1 + .../wallet/wallet_connect/effects.cljs | 23 +++++++++-- .../modals/common/page_nav/view.cljs | 12 ++++++ .../modals/send_transaction/view.cljs | 8 ++-- .../modals/sign_message/view.cljs | 8 ++-- .../modals/sign_transaction/view.cljs | 8 ++-- .../wallet_connect/responding_events.cljs | 41 +++++++++++++++---- .../wallet_connect/session_proposal/view.cljs | 2 +- 9 files changed, 77 insertions(+), 28 deletions(-) create mode 100644 src/status_im/contexts/wallet/wallet_connect/modals/common/page_nav/view.cljs diff --git a/src/react_native/wallet_connect.cljs b/src/react_native/wallet_connect.cljs index e7809efb2b..b43e272bdd 100644 --- a/src/react_native/wallet_connect.cljs +++ b/src/react_native/wallet_connect.cljs @@ -22,6 +22,8 @@ (clj->js {:proposal proposal :supportedNamespaces supported-namespaces}))) +;; Get an error from this list: +;; https://github.com/WalletConnect/walletconnect-monorepo/blob/c6e9529418a0c81d4efcc6ac4e61f242a50b56c5/packages/utils/src/errors.ts (defn get-sdk-error [error-key] (getSdkError error-key)) diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index 530d2824d1..e1d32923e6 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -281,6 +281,7 @@ (def ^:const wallet-connect-supported-events #{"accountsChanged" "chainChanged"}) (def ^:const wallet-connect-session-proposal-event "session_proposal") (def ^:const wallet-connect-session-request-event "session_request") +(def ^:const wallet-connect-user-rejected-error-key "USER_REJECTED") (def ^:const transaction-pending-type-wallet-connect-transfer "WalletConnectTransfer") diff --git a/src/status_im/contexts/wallet/wallet_connect/effects.cljs b/src/status_im/contexts/wallet/wallet_connect/effects.cljs index 1258487225..35a4bb3622 100644 --- a/src/status_im/contexts/wallet/wallet_connect/effects.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/effects.cljs @@ -118,12 +118,27 @@ (rf/reg-fx :effects.wallet-connect/respond-session-request - (fn [{:keys [web3-wallet topic id result on-success on-error]}] + (fn [{:keys [web3-wallet topic id result error on-success on-error]}] (-> (.respondSessionRequest web3-wallet (clj->js {:topic topic - :response {:id id - :jsonrpc "2.0" - :result result}})) + :response (merge {:id id + :jsonrpc "2.0"} + (when result + {:result result}) + (when error + {:error error}))})) (promesa/then on-success) (promesa/catch on-error)))) + +(rf/reg-fx + :effects.wallet-connect/reject-session-proposal + (fn [{:keys [web3-wallet proposal on-success on-error]}] + (let [{:keys [id]} proposal + reason (wallet-connect/get-sdk-error + constants/wallet-connect-user-rejected-error-key)] + (-> (.rejectSession web3-wallet + (clj->js {:id id + :reason reason})) + (promesa/then on-success) + (promesa/catch on-error))))) diff --git a/src/status_im/contexts/wallet/wallet_connect/modals/common/page_nav/view.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/common/page_nav/view.cljs new file mode 100644 index 0000000000..b16a526223 --- /dev/null +++ b/src/status_im/contexts/wallet/wallet_connect/modals/common/page_nav/view.cljs @@ -0,0 +1,12 @@ +(ns status-im.contexts.wallet.wallet-connect.modals.common.page-nav.view + (:require [quo.core :as quo] + [utils.re-frame :as rf])) + +(defn view + [{:keys [accessibility-label]}] + [quo/page-nav + {:icon-name :i/close + :background :blur + :on-press #(do (rf/dispatch [:navigate-back]) + (rf/dispatch [:wallet-connect/reject-session-request])) + :accessibility-label accessibility-label}]) diff --git a/src/status_im/contexts/wallet/wallet_connect/modals/send_transaction/view.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/send_transaction/view.cljs index 6f74cb6676..3f4724972f 100644 --- a/src/status_im/contexts/wallet/wallet_connect/modals/send_transaction/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/modals/send_transaction/view.cljs @@ -5,6 +5,7 @@ [status-im.contexts.wallet.wallet-connect.modals.common.data-block.view :as data-block] [status-im.contexts.wallet.wallet-connect.modals.common.footer.view :as footer] [status-im.contexts.wallet.wallet-connect.modals.common.header.view :as header] + [status-im.contexts.wallet.wallet-connect.modals.common.page-nav.view :as page-nav] [status-im.contexts.wallet.wallet-connect.modals.common.style :as style] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -20,11 +21,8 @@ error-state]} (rf/sub [:wallet-connect/current-request-transaction-information])] [rn/view {:style (style/container bottom)} [quo/gradient-cover {:customization-color customization-color}] - [quo/page-nav - {:icon-name :i/close - :background :blur - :on-press #(rf/dispatch [:navigate-back]) - :accessibility-label :wallet-connect-sign-message-close}] + [page-nav/view + {:accessibility-label :wallet-connect-sign-message-close}] [rn/view {:flex 1} [rn/view {:style style/data-content-container} [header/view diff --git a/src/status_im/contexts/wallet/wallet_connect/modals/sign_message/view.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/sign_message/view.cljs index ac631677ef..7ba821434d 100644 --- a/src/status_im/contexts/wallet/wallet_connect/modals/sign_message/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/modals/sign_message/view.cljs @@ -5,6 +5,7 @@ [status-im.contexts.wallet.wallet-connect.modals.common.data-block.view :as data-block] [status-im.contexts.wallet.wallet-connect.modals.common.footer.view :as footer] [status-im.contexts.wallet.wallet-connect.modals.common.header.view :as header] + [status-im.contexts.wallet.wallet-connect.modals.common.page-nav.view :as page-nav] [status-im.contexts.wallet.wallet-connect.modals.common.style :as style] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -17,11 +18,8 @@ dapp (rf/sub [:wallet-connect/current-request-dapp])] [rn/view {:style (style/container bottom)} [quo/gradient-cover {:customization-color customization-color}] - [quo/page-nav - {:icon-name :i/close - :background :blur - :on-press #(rf/dispatch [:navigate-back]) - :accessibility-label :wallet-connect-sign-message-close}] + [page-nav/view + {:accessibility-label :wallet-connect-sign-message-close}] [rn/view {:flex 1} [rn/view {:style style/sign-message-content-container} [header/view diff --git a/src/status_im/contexts/wallet/wallet_connect/modals/sign_transaction/view.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/sign_transaction/view.cljs index 15f54467b2..6504f21118 100644 --- a/src/status_im/contexts/wallet/wallet_connect/modals/sign_transaction/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/modals/sign_transaction/view.cljs @@ -5,6 +5,7 @@ [status-im.contexts.wallet.wallet-connect.modals.common.data-block.view :as data-block] [status-im.contexts.wallet.wallet-connect.modals.common.footer.view :as footer] [status-im.contexts.wallet.wallet-connect.modals.common.header.view :as header] + [status-im.contexts.wallet.wallet-connect.modals.common.page-nav.view :as page-nav] [status-im.contexts.wallet.wallet-connect.modals.common.style :as style] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -20,11 +21,8 @@ error-state]} (rf/sub [:wallet-connect/current-request-transaction-information])] [rn/view {:style (style/container bottom)} [quo/gradient-cover {:customization-color customization-color}] - [quo/page-nav - {:icon-name :i/close - :background :blur - :on-press #(rf/dispatch [:navigate-back]) - :accessibility-label :wallet-connect-sign-message-close}] + [page-nav/view + {:accessibility-label :wallet-connect-sign-message-close}] [rn/view {:flex 1} [rn/view {:style style/data-content-container} [header/view diff --git a/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs b/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs index 6a6422fba5..6f0a5fed0f 100644 --- a/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs @@ -1,5 +1,6 @@ (ns status-im.contexts.wallet.wallet-connect.responding-events (:require [re-frame.core :as rf] + [react-native.wallet-connect :as wallet-connect] [status-im.constants :as constants] [status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core] [taoensso.timbre :as log])) @@ -37,8 +38,7 @@ :address address :data raw-data :on-error #(rf/dispatch [:wallet-connect/on-sign-error %]) - :on-success #(rf/dispatch [:wallet-connect/send-response %])}]]}))) - + :on-success #(rf/dispatch [:wallet-connect/send-response {:result %}])}]]}))) (rf/reg-event-fx :wallet-connect/respond-personal-sign @@ -49,7 +49,7 @@ :address address :data raw-data :on-error #(rf/dispatch [:wallet-connect/on-sign-error %]) - :on-success #(rf/dispatch [:wallet-connect/send-response %])}]]}))) + :on-success #(rf/dispatch [:wallet-connect/send-response {:result %}])}]]}))) (rf/reg-event-fx :wallet-connect/respond-sign-typed-data @@ -61,7 +61,7 @@ :data raw-data :version typed-data-version :on-error #(rf/dispatch [:wallet-connect/on-sign-error %]) - :on-success #(rf/dispatch [:wallet-connect/send-response %])}]]}))) + :on-success #(rf/dispatch [:wallet-connect/send-response {:result %}])}]]}))) (rf/reg-event-fx :wallet-connect/respond-send-transaction-data @@ -73,7 +73,7 @@ :chain-id chain-id :tx raw-data :on-error #(rf/dispatch [:wallet-connect/on-sign-error %]) - :on-success #(rf/dispatch [:wallet-connect/send-response %])}]]}))) + :on-success #(rf/dispatch [:wallet-connect/send-response {:result %}])}]]}))) (rf/reg-event-fx :wallet-connect/respond-sign-transaction-data @@ -85,7 +85,7 @@ :chain-id chain-id :tx raw-data :on-error #(rf/dispatch [:wallet-connect/on-sign-error %]) - :on-success #(rf/dispatch [:wallet-connect/send-response %])}]]}))) + :on-success #(rf/dispatch [:wallet-connect/send-response {:result %}])}]]}))) (rf/reg-event-fx :wallet-connect/on-sign-error @@ -104,10 +104,9 @@ {:fx [[:dispatch [:dismiss-modal screen]] [:dispatch [:wallet-connect/reset-current-request]]]}))) - (rf/reg-event-fx :wallet-connect/send-response - (fn [{:keys [db]} [result]] + (fn [{:keys [db]} [{:keys [result error]}]] (let [{:keys [id topic] :as event} (get-in db [:wallet-connect/current-request :event]) method (wallet-connect-core/get-request-method event) screen (wallet-connect-core/method-to-screen method) @@ -117,6 +116,7 @@ :topic topic :id id :result result + :error error :on-error (fn [error] (log/error "Failed to send Wallet Connect response" {:error error @@ -129,3 +129,28 @@ (log/info "Successfully sent Wallet Connect response to dApp") (rf/dispatch [:dismiss-modal screen]) (rf/dispatch [:wallet-connect/reset-current-request]))}]]}))) + +(rf/reg-event-fx + :wallet-connect/reject-session-proposal + (fn [{:keys [db]} _] + (let [web3-wallet (get db :wallet-connect/web3-wallet) + current-proposal (get-in db [:wallet-connect/current-proposal :request])] + {:fx [[:effects.wallet-connect/reject-session-proposal + {:web3-wallet web3-wallet + :proposal current-proposal + :on-success #(log/info "Wallet Connect session proposal rejected") + :on-error #(log/error "Wallet Connect unable to reject session proposal")}] + [:dispatch [:wallet-connect/reset-current-session]]]}))) + +;; NOTE: Currently we only reject a session if the user rejected it +;; But this needs to be solidified to ensure other cases: +;; - Unsupported WC version +;; - Invalid params from dapps +;; - Unsupported method +(rf/reg-event-fx + :wallet-connect/reject-session-request + (fn [_ _] + {:fx [[:dispatch + [:wallet-connect/send-response + {:error (wallet-connect/get-sdk-error + constants/wallet-connect-user-rejected-error-key)}]]]})) diff --git a/src/status_im/contexts/wallet/wallet_connect/session_proposal/view.cljs b/src/status_im/contexts/wallet/wallet_connect/session_proposal/view.cljs index 1062d154e6..3c953d686b 100644 --- a/src/status_im/contexts/wallet/wallet_connect/session_proposal/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/session_proposal/view.cljs @@ -142,7 +142,7 @@ :accessibility-label :wc-deny-connection :on-press #(do (rf/dispatch [:navigate-back]) (rf/dispatch - [:wallet-connect/reset-current-session]))} + [:wallet-connect/reject-session-proposal]))} :button-one-label (i18n/label :t/connect) :button-one-props {:customization-color customization-color :type :primary From 866b854ee0d05b926f7b88d02b3d4334eb7af8ff Mon Sep 17 00:00:00 2001 From: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com> Date: Thu, 11 Jul 2024 15:55:00 +0530 Subject: [PATCH 24/79] fix(key-pairs)_: error message on scanning different key pair QR (#20612) This commit: - update the connection string validation method to use the method from status-go - updates the error message if the user tries to scan a different key pair QR for importing a missing key pair - updates the text for exporting an individual key pair Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com> --- .../java/im/status/ethereum/module/Utils.kt | 5 ++ .../react-native-status/ios/RCTStatus/Utils.m | 4 ++ src/native_module/core.cljs | 8 +++- src/status_im/common/pairing/events.cljs | 6 ++- src/status_im/constants.cljs | 6 ++- .../contexts/settings/wallet/events.cljs | 46 +++++++++++++++---- .../contexts/settings/wallet/events_test.cljs | 10 ++-- .../keypairs_and_accounts/actions/view.cljs | 2 +- src/status_im/contexts/syncing/utils.cljs | 14 ++++-- translations/en.json | 5 +- 10 files changed, 80 insertions(+), 26 deletions(-) diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/Utils.kt b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/Utils.kt index 564aea425a..0401424312 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/Utils.kt +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/Utils.kt @@ -153,4 +153,9 @@ class Utils(private val reactContext: ReactApplicationContext) : ReactContextBas return strArray } + + @ReactMethod(isBlockingSynchronousMethod = true) + fun validateConnectionString(connectionString: String): String { + return Statusgo.validateConnectionString(connectionString) + } } diff --git a/modules/react-native-status/ios/RCTStatus/Utils.m b/modules/react-native-status/ios/RCTStatus/Utils.m index e9b0149fc3..6f0f35e1fa 100644 --- a/modules/react-native-status/ios/RCTStatus/Utils.m +++ b/modules/react-native-status/ios/RCTStatus/Utils.m @@ -128,4 +128,8 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(toChecksumAddress:(NSString *)address) { return StatusgoToChecksumAddress(address); } +RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(validateConnectionString:(NSString *)cs) { + return StatusgoValidateConnectionString(cs); +} + @end diff --git a/src/native_module/core.cljs b/src/native_module/core.cljs index 44f3f84d24..85b11a7f5e 100644 --- a/src/native_module/core.cljs +++ b/src/native_module/core.cljs @@ -543,6 +543,13 @@ (log/debug "[native-module] validate-mnemonic") (.validateMnemonic ^js (utils) mnemonic callback))) +(defn validate-connection-string + [connection-string] + (log/debug "[native-module] validate-connection-string") + (->> connection-string + (.validateConnectionString ^js (utils)) + types/json->clj)) + (defn delete-multiaccount "Delete multiaccount from database, deletes multiaccount's database and key files." @@ -653,7 +660,6 @@ (native-utils/promisify-native-module-call create-account-from-private-key private-key)) ([private-key callback] (log/debug "[native-module] create-account-from-private-key") - (.createAccountFromPrivateKey ^js (account-manager) (types/clj->json {:privateKey private-key}) callback))) diff --git a/src/status_im/common/pairing/events.cljs b/src/status_im/common/pairing/events.cljs index 7beef829e4..a7791b99d1 100644 --- a/src/status_im/common/pairing/events.cljs +++ b/src/status_im/common/pairing/events.cljs @@ -35,7 +35,9 @@ user-in-syncing-devices-screen? (or (= (:view-id db) :screen/onboarding.syncing-progress) (= (:view-id db) :screen/profile.profiles) (= (:view-id db) :screen/onboarding.syncing-progress-intro)) - user-in-sign-in-intro-screen? (= (:view-id db) :screen/onboarding.sign-in-intro)] + user-in-sign-in-intro-screen? (= (:view-id db) :screen/onboarding.sign-in-intro) + keystore-files-transfer-action? (= action + constants/local-pairing-action-keystore-files-transfer)] (merge {:db (cond-> db connection-success? (assoc-in [:syncing :pairing-status] :connected) @@ -61,7 +63,7 @@ (and completed-pairing? receiver?) {:dispatch [:profile.login/local-paired-user]} - (and error-on-pairing? (some? error)) + (and error-on-pairing? (some? error) (not keystore-files-transfer-action?)) {:dispatch [:toasts/upsert {:type :negative :text error}]})))) diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index e1d32923e6..2b39d36ce9 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -370,16 +370,18 @@ (def ^:const local-pairing-role-receiver "receiver") ;; sender and receiver events +(def ^:const local-pairing-event-peer-discovered "peer-discovered") (def ^:const local-pairing-event-connection-success "connection-success") (def ^:const local-pairing-event-connection-error "connection-error") (def ^:const local-pairing-event-transfer-success "transfer-success") (def ^:const local-pairing-event-transfer-error "transfer-error") +(def ^:const local-pairing-event-received-installation "received-installation") ;; receiver events (def ^:const local-pairing-event-received-account "received-account") (def ^:const local-pairing-event-process-success "process-success") (def ^:const local-pairing-event-process-error "process-error") -(def ^:const local-pairing-event-received-installation "received-installation") +(def ^:const local-pairing-event-received-keystore-files "received-keystore-files") (def ^:const local-pairing-event-errors #{local-pairing-event-connection-error @@ -390,6 +392,8 @@ (def ^:const local-pairing-action-pairing-account 2) (def ^:const local-pairing-action-sync-device 3) (def ^:const local-pairing-action-pairing-installation 4) +(def ^:const local-pairing-action-peer-discovery 5) +(def ^:const local-pairing-action-keystore-files-transfer 6) (def ^:const serialization-key "We pass this serialization key as a parameter to MultiformatSerializePublicKey diff --git a/src/status_im/contexts/settings/wallet/events.cljs b/src/status_im/contexts/settings/wallet/events.cljs index 4bd0908b2b..7c559638f1 100644 --- a/src/status_im/contexts/settings/wallet/events.cljs +++ b/src/status_im/contexts/settings/wallet/events.cljs @@ -1,5 +1,6 @@ (ns status-im.contexts.settings.wallet.events (:require + [clojure.string :as string] [native-module.core :as native-module] [status-im.contexts.settings.wallet.data-store :as data-store] [taoensso.timbre :as log] @@ -91,7 +92,37 @@ (rf/reg-event-fx :wallet/make-keypairs-accounts-fully-operable make-keypairs-accounts-fully-operable) -(defn connection-string-for-import-keypair + +(rf/reg-event-fx :wallet/connection-string-for-import-keypairs-failed + (fn [{:keys [db]} [keypairs-key-uids error]] + (let [error-message (-> error ex-data :error) + incorrect-keypair? (string/includes? + error-message + "one or more expected keystore files are not found among the sent files") + single-keypair-to-update? (= (count keypairs-key-uids) 1) + keypair-name (when single-keypair-to-update? + (let [key-uid (first keypairs-key-uids)] + (get-in db [:wallet :keypairs key-uid :name]))) + toast-message (cond + (and single-keypair-to-update? incorrect-keypair?) + (i18n/label + :t/this-qr-does-not-contain-key-pair + {:name keypair-name}) + + (and (not single-keypair-to-update?) incorrect-keypair?) + (i18n/label + :t/this-qr-does-not-contain-any-missing-key-pair) + + :else + error-message)] + (log/error "failed to import missing key pairs with connection string" + {:error error-message}) + (rf/dispatch [:toasts/upsert + {:type :negative + :theme :dark + :text toast-message}])))) + +(defn connection-string-for-import-keypairs [{:keys [db]} [{:keys [sha3-pwd keypairs-key-uids connection-string]}]] (let [key-uid (get-in db [:profile/profile :key-uid])] {:fx [[:effects.syncing/import-keypairs-keystores @@ -102,14 +133,11 @@ :on-success (fn [key-uids] (rf/dispatch [:wallet/make-keypairs-accounts-fully-operable key-uids])) :on-fail (fn [error] - (log/error "failed to import missing key pairs with connection string" - {:error error}) - (rf/dispatch [:toasts/upsert - {:type :negative - :theme :dark - :text (i18n/label :t/incorrect-qr-code)}]))}]]})) + (rf/dispatch [:wallet/connection-string-for-import-keypairs-failed + keypairs-key-uids + error]))}]]})) -(rf/reg-event-fx :wallet/connection-string-for-import-keypair connection-string-for-import-keypair) +(rf/reg-event-fx :wallet/connection-string-for-import-keypairs connection-string-for-import-keypairs) (defn success-keypair-qr-scan [_ [connection-string keypairs-key-uids]] @@ -121,7 +149,7 @@ :on-auth-success (fn [password] (rf/dispatch [:hide-bottom-sheet]) (rf/dispatch - [:wallet/connection-string-for-import-keypair + [:wallet/connection-string-for-import-keypairs {:connection-string connection-string :keypairs-key-uids keypairs-key-uids :sha3-pwd password}]))}]]]}) diff --git a/src/status_im/contexts/settings/wallet/events_test.cljs b/src/status_im/contexts/settings/wallet/events_test.cljs index dcf0b9c916..7c7f5f27ad 100644 --- a/src/status_im/contexts/settings/wallet/events_test.cljs +++ b/src/status_im/contexts/settings/wallet/events_test.cljs @@ -83,11 +83,11 @@ :on-success fn? :on-fail fn?}]]}] (is (match? expected - (sut/connection-string-for-import-keypair cofx - [{:sha3-pwd sha3-pwd - :keypairs-key-uids [test-keypair-key-uid] - :connection-string - connection-string}]))))) + (sut/connection-string-for-import-keypairs cofx + [{:sha3-pwd sha3-pwd + :keypairs-key-uids [test-keypair-key-uid] + :connection-string + connection-string}]))))) (deftest success-keypair-qr-scan-test (let [connection-string "valid-connection-string" diff --git a/src/status_im/contexts/settings/wallet/keypairs_and_accounts/actions/view.cljs b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/actions/view.cljs index 6eed006d40..970fa191bd 100644 --- a/src/status_im/contexts/settings/wallet/keypairs_and_accounts/actions/view.cljs +++ b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/actions/view.cljs @@ -44,7 +44,7 @@ [{:blur? true :icon :i/qr-code :accessibility-label :show-key-pr-qr - :label (i18n/label :t/show-encrypted-qr-of-key-pairs) + :label (i18n/label :t/show-encrypted-qr-of-key-pair) :on-press on-show-qr}] [{:blur? true :icon :i/scan diff --git a/src/status_im/contexts/syncing/utils.cljs b/src/status_im/contexts/syncing/utils.cljs index f906ee02d9..11910541e0 100644 --- a/src/status_im/contexts/syncing/utils.cljs +++ b/src/status_im/contexts/syncing/utils.cljs @@ -1,15 +1,19 @@ (ns status-im.contexts.syncing.utils (:require [clojure.string :as string] - [status-im.constants :as constants] + [native-module.core :as native-module] [utils.transforms :as transforms])) +(defn validate-connection-string + [connection-string] + (native-module/validate-connection-string + connection-string)) + (defn valid-connection-string? [connection-string] - (when connection-string - (string/starts-with? - connection-string - constants/local-pairing-connection-string-identifier))) + (some-> connection-string + validate-connection-string + string/blank?)) (defn extract-error [json-str] diff --git a/translations/en.json b/translations/en.json index a4452c46ce..f35b1e7b19 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1311,7 +1311,8 @@ "scan-key-pairs-qr-code": "Scan key pairs QR code", "invalid-qr": "Oops! This QR doesn’t work with Status", "invalid-key-pair-qr": "This does not look like a key pair QR code", - "incorrect-qr-code": "This is not the QR code you are looking for", + "this-qr-does-not-contain-key-pair": "This QR does not contain {{name}} key pair", + "this-qr-does-not-contain-any-missing-key-pair": "This QR does not contain any missing key pairs", "search": "Search", "search-discover-communities": "Search communities or categories", "secret-keys-confirmation-text": "You will need them to continue to use your Keycard in case you ever lose your phone.", @@ -1363,7 +1364,7 @@ "show-more": "Show more", "show-qr": "Show QR code", "show-transaction-data": "Show transaction data", - "show-encrypted-qr-of-key-pairs": "Show encrypted QR of key pairs on device", + "show-encrypted-qr-of-key-pair": "Show encrypted QR of key pair", "sign-and-send": "Sign and send", "sign-in": "Sign in", "sign-message": "Sign Message", From 180065129fa22d271f7367b73834c5da10822f92 Mon Sep 17 00:00:00 2001 From: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com> Date: Thu, 11 Jul 2024 20:01:55 +0530 Subject: [PATCH 25/79] fix(saved-addresses): clean QR scanned address (#20699) This commit cleans the scanned QR address/result on the unmount of the add new saved address flow. Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com> --- .../wallet/saved_addresses/add_address_to_save/view.cljs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/status_im/contexts/settings/wallet/saved_addresses/add_address_to_save/view.cljs b/src/status_im/contexts/settings/wallet/saved_addresses/add_address_to_save/view.cljs index 9dcd733970..24e2ca4325 100644 --- a/src/status_im/contexts/settings/wallet/saved_addresses/add_address_to_save/view.cljs +++ b/src/status_im/contexts/settings/wallet/saved_addresses/add_address_to_save/view.cljs @@ -163,9 +163,8 @@ (rf/dispatch [:open-modal :screen/settings.save-address])) [address ens-name? address-or-ens])] - (rn/use-mount (fn [] - (rf/dispatch [:wallet/clean-scanned-address]) - (rf/dispatch [:wallet/clear-address-to-save]))) + (rn/use-unmount #(rf/dispatch [:wallet/clean-scanned-address])) + (rn/use-mount #(rf/dispatch [:wallet/clear-address-to-save])) [quo/overlay {:type :shell} [floating-button-page/view {:footer-container-padding 0 From 0b5dac0ad0c3609f6c3e155fcdea626711f7e25f Mon Sep 17 00:00:00 2001 From: Icaro Motta Date: Thu, 11 Jul 2024 22:23:17 -0300 Subject: [PATCH 26/79] feat(settings): Revamp legacy Privacy and security settings (#20671) Revamps the legacy "Privacy and security" screen to meet the new designs. Fixes https://github.com/status-im/status-mobile/issues/20618 - Removed setting "Set dApp access permissions" (won't be used anymore). - Removed setting "Display collectibles" (won't be used anymore). - Removed setting "Chat link previews setting" (they already had no effect). - Removed "Reset password setting". This setting is already supported in Profile > Password > Change password. - Removed "Delete my profile". Profile deletion is already supported in the login screen. - Moved setting "Show your profile picture" to Profile > Privacy and security. - Moved setting "Accept new chats from " to "Allow new contact requests" in Profile > Messages. Now it's just a toggle. - Moved Block screenshots (Android) or Hide Preview (iOS) which are both accessible in Profile > Privacy and security. - Created function utils.navigation/navigate-back, but I only used this in 2 namespaces to keep the PR under scope. Areas that may be impacted: old legacy settings that are still relevant. --- .env | 1 - .env.release | 1 - src/legacy/status_im/events.cljs | 1 - .../status_im/multiaccounts/update/core.cljs | 9 - .../ui/screens/dapps_permissions/styles.cljs | 11 -- .../ui/screens/dapps_permissions/views.cljs | 64 ------- .../link_previews_settings/styles.cljs | 13 -- .../screens/link_previews_settings/views.cljs | 67 ------- .../delete_profile.cljs | 81 -------- .../privacy_and_security_settings/events.cljs | 73 -------- .../messages_from_contacts_only.cljs | 34 ---- .../privacy_and_security_settings/views.cljs | 175 ------------------ .../ui/screens/profile/user/views.cljs | 6 - src/legacy/status_im/ui/screens/screens.cljs | 39 ---- .../settings/settings_item/style.cljs | 2 +- src/status_im/config.cljs | 1 - src/status_im/contexts/profile/events.cljs | 9 + .../settings/screens/messages/view.cljs | 51 ++--- .../profile_picture/view.cljs | 118 ++++++++++++ .../settings/privacy_and_security/view.cljs | 72 ++++++- src/status_im/feature_flags.cljs | 6 + src/status_im/subs/profile.cljs | 14 ++ src/status_im/subs/root.cljs | 3 - src/utils/navigation.cljs | 8 + translations/en.json | 2 + 25 files changed, 250 insertions(+), 611 deletions(-) delete mode 100644 src/legacy/status_im/ui/screens/dapps_permissions/styles.cljs delete mode 100644 src/legacy/status_im/ui/screens/dapps_permissions/views.cljs delete mode 100644 src/legacy/status_im/ui/screens/link_previews_settings/styles.cljs delete mode 100644 src/legacy/status_im/ui/screens/link_previews_settings/views.cljs delete mode 100644 src/legacy/status_im/ui/screens/privacy_and_security_settings/delete_profile.cljs delete mode 100644 src/legacy/status_im/ui/screens/privacy_and_security_settings/events.cljs delete mode 100644 src/legacy/status_im/ui/screens/privacy_and_security_settings/messages_from_contacts_only.cljs delete mode 100644 src/legacy/status_im/ui/screens/privacy_and_security_settings/views.cljs create mode 100644 src/status_im/contexts/settings/privacy_and_security/profile_picture/view.cljs create mode 100644 src/utils/navigation.cljs diff --git a/.env b/.env index 9567ef53f9..d7182d43f9 100644 --- a/.env +++ b/.env @@ -26,7 +26,6 @@ APN_TOPIC=im.status.ethereum.pr COMMUNITIES_ENABLED=1 DATABASE_MANAGEMENT_ENABLED=1 DELETE_MESSAGE_ENABLED=1 -COLLECTIBLES_ENABLED=1 COMMANDS_ENABLED=1 TWO_MINUTES_SYNCING=1 SWAP_ENABLED=1 diff --git a/.env.release b/.env.release index 1d21278c44..c90b1b69ab 100644 --- a/.env.release +++ b/.env.release @@ -18,6 +18,5 @@ PARTITIONED_TOPIC=0 ENABLE_ROOT_ALERT=1 MAX_IMAGES_BATCH=1 DELETE_MESSAGE_ENABLED=1 -COLLECTIBLES_ENABLED=1 FAST_CREATE_COMMUNITY_ENABLED=0 TEST_NETWORKS_ENABLED=0 diff --git a/src/legacy/status_im/events.cljs b/src/legacy/status_im/events.cljs index b49ad3a071..7346f8a15e 100644 --- a/src/legacy/status_im/events.cljs +++ b/src/legacy/status_im/events.cljs @@ -26,7 +26,6 @@ legacy.status-im.ui.components.invite.events [legacy.status-im.ui.components.react :as react] legacy.status-im.ui.screens.notifications-settings.events - legacy.status-im.ui.screens.privacy-and-security-settings.events [legacy.status-im.utils.dimensions :as dimensions] legacy.status-im.utils.logging.core [legacy.status-im.utils.utils :as utils] diff --git a/src/legacy/status_im/multiaccounts/update/core.cljs b/src/legacy/status_im/multiaccounts/update/core.cljs index aba6e98035..35fdafc035 100644 --- a/src/legacy/status_im/multiaccounts/update/core.cljs +++ b/src/legacy/status_im/multiaccounts/update/core.cljs @@ -120,12 +120,3 @@ {:events [:multiaccounts.ui/switch-backup-enabled]} [cofx enabled?] (multiaccount-update cofx :backup-enabled? enabled? {})) - -(rf/defn toggle-opensea-nfts-visibility - {:events [::toggle-opensea-nfts-visiblity]} - [cofx visible?] - (rf/merge cofx - {:db (assoc-in (:db cofx) [:profile/profile :opensea-enabled?] visible?) - ;; need to add fully qualified namespace to counter circular deps - :dispatch [:legacy.status-im.wallet.core/fetch-collectibles-collection]} - (multiaccount-update :opensea-enabled? visible? {}))) diff --git a/src/legacy/status_im/ui/screens/dapps_permissions/styles.cljs b/src/legacy/status_im/ui/screens/dapps_permissions/styles.cljs deleted file mode 100644 index 4ba6fb54ed..0000000000 --- a/src/legacy/status_im/ui/screens/dapps_permissions/styles.cljs +++ /dev/null @@ -1,11 +0,0 @@ -(ns legacy.status-im.ui.screens.dapps-permissions.styles - (:require - [legacy.status-im.ui.components.colors :as colors])) - -(def icon-container - {:width 40 - :height 40 - :border-radius 20 - :background-color colors/gray-lighter - :align-items :center - :justify-content :center}) diff --git a/src/legacy/status_im/ui/screens/dapps_permissions/views.cljs b/src/legacy/status_im/ui/screens/dapps_permissions/views.cljs deleted file mode 100644 index 25d0d27d61..0000000000 --- a/src/legacy/status_im/ui/screens/dapps_permissions/views.cljs +++ /dev/null @@ -1,64 +0,0 @@ -(ns legacy.status-im.ui.screens.dapps-permissions.views - (:require-macros [legacy.status-im.utils.views :as views]) - (:require - [legacy.status-im.ui.components.colors :as colors] - [legacy.status-im.ui.components.core :as quo] - [legacy.status-im.ui.components.icons.icons :as icons] - [legacy.status-im.ui.components.list.item :as list.item] - [legacy.status-im.ui.components.list.views :as list] - [legacy.status-im.ui.components.react :as react] - [legacy.status-im.ui.components.topbar :as topbar] - [legacy.status-im.ui.screens.dapps-permissions.styles :as styles] - [re-frame.core :as re-frame] - [status-im.constants :as constants] - [utils.i18n :as i18n])) - -(defn d-icon - [] - [react/view styles/icon-container - [icons/icon :main-icons/dapp {:color colors/gray}]]) - -(defn prepare-items - [{:keys [dapp permissions]}] - {:title dapp - :chevron true - :on-press #(re-frame/dispatch [:navigate-to :manage-dapps-permissions - {:dapp dapp :permissions permissions}]) - :icon [d-icon]}) - -(defn prepare-items-manage - [name] - (fn [permission] - {:title (cond - (= permission constants/dapp-permission-web3) - name - (= permission constants/dapp-permission-contact-code) - (i18n/label :t/contact-code)) - :size :small - :accessory [icons/icon :main-icons/check {}]})) - -(views/defview dapps-permissions - [] - (views/letsubs [permissions [:dapps/permissions]] - [list/flat-list - {:data (vec (map prepare-items (vals permissions))) - :key-fn (fn [_ i] (str i)) - :render-fn list.item/list-item}])) - -(views/defview manage - [] - (views/letsubs [{:keys [dapp permissions]} [:get-screen-params] - {:keys [name]} [:dapps-account]] - [:<> - [topbar/topbar {:title dapp}] - [list/flat-list - {:data (vec (map (prepare-items-manage name) permissions)) - :key-fn (fn [_ i] (str i)) - :render-fn list.item/list-item}] - [react/view - {:padding-vertical 16 - :padding-horizontal 16} - [quo/button - {:theme :negative - :on-press #(re-frame/dispatch [:dapps/revoke-access dapp])} - (i18n/label :t/revoke-access)]]])) diff --git a/src/legacy/status_im/ui/screens/link_previews_settings/styles.cljs b/src/legacy/status_im/ui/screens/link_previews_settings/styles.cljs deleted file mode 100644 index 18a7ddbd0f..0000000000 --- a/src/legacy/status_im/ui/screens/link_previews_settings/styles.cljs +++ /dev/null @@ -1,13 +0,0 @@ -(ns legacy.status-im.ui.screens.link-previews-settings.styles) - -(def link-preview-settings-image - {:height 100 - :align-self :center - :margin-top 16}) - -(def whitelist-container - {:flex-direction :row - :justify-content :space-between}) - -(def enable-all - {:align-self :flex-end}) diff --git a/src/legacy/status_im/ui/screens/link_previews_settings/views.cljs b/src/legacy/status_im/ui/screens/link_previews_settings/views.cljs deleted file mode 100644 index beb647a796..0000000000 --- a/src/legacy/status_im/ui/screens/link_previews_settings/views.cljs +++ /dev/null @@ -1,67 +0,0 @@ -(ns legacy.status-im.ui.screens.link-previews-settings.views - (:require-macros [legacy.status-im.utils.views :as views]) - (:require - [legacy.status-im.react-native.resources :as resources] - [legacy.status-im.ui.components.core :as components] - [legacy.status-im.ui.components.list.item :as list.item] - [legacy.status-im.ui.components.list.views :as list] - [legacy.status-im.ui.components.react :as react] - [legacy.status-im.ui.screens.link-previews-settings.styles :as styles] - [quo.core :as quo] - [re-frame.core :as re-frame] - [status-im.contexts.chat.messenger.messages.link-preview.events] - [utils.i18n :as i18n] - [utils.re-frame :as rf])) - -(defn prepare-urls-items-data - [link-previews-enabled-sites] - (fn [{:keys [title address]}] - (let [enabled? (contains? link-previews-enabled-sites title)] - {:title title - :subtitle address - :size :small - :accessory :switch - :active (contains? link-previews-enabled-sites title) - :on-press #(re-frame/dispatch - [:chat.ui/enable-link-previews title ((complement boolean) enabled?)])}))) - -(views/defview link-previews-settings - [] - (views/letsubs [link-previews-whitelist [:link-previews-whitelist] - link-previews-enabled-sites [:link-preview/enabled-sites]] - (let [all-enabled (= (count link-previews-whitelist) (count link-previews-enabled-sites))] - [:<> - [quo/page-nav - {:type :title - :title (i18n/label :t/chat-link-previews) - :background :blur - :icon-name :i/close - :on-press #(rf/dispatch [:navigate-back])}] - [react/image - {:source (resources/get-theme-image :unfurl) - :style styles/link-preview-settings-image}] - [components/text {:style {:margin 16}} - (i18n/label :t/you-can-choose-preview-websites)] - [components/separator {:style {:margin-vertical 8}}] - - [react/view styles/whitelist-container - [components/list-header (i18n/label :t/websites)] - - (when (> (count link-previews-whitelist) 1) - [components/button - {:on-press #(re-frame/dispatch [:chat.ui/enable-all-link-previews - link-previews-whitelist - (not all-enabled)]) - :type :secondary - :style styles/enable-all} - (if all-enabled - (i18n/label :t/disable-all) - (i18n/label :t/enable-all))])] - [list/flat-list - {:data (vec (map (prepare-urls-items-data link-previews-enabled-sites) - link-previews-whitelist)) - :key-fn (fn [_ i] (str i)) - :render-fn list.item/list-item - :footer [quo/text - {:color :secondary - :style {:margin 16}} (i18n/label :t/previewing-may-share-metadata)]}]]))) diff --git a/src/legacy/status_im/ui/screens/privacy_and_security_settings/delete_profile.cljs b/src/legacy/status_im/ui/screens/privacy_and_security_settings/delete_profile.cljs deleted file mode 100644 index 953aea10aa..0000000000 --- a/src/legacy/status_im/ui/screens/privacy_and_security_settings/delete_profile.cljs +++ /dev/null @@ -1,81 +0,0 @@ -(ns legacy.status-im.ui.screens.privacy-and-security-settings.delete-profile - (:require - [legacy.status-im.ui.components.chat-icon.screen :as chat-icon.screen] - [legacy.status-im.ui.components.core :as quo] - [legacy.status-im.ui.components.list.item :as list.item] - [legacy.status-im.ui.components.react :as react] - [legacy.status-im.ui.screens.privacy-and-security-settings.events :as delete-profile] - [re-frame.core :as re-frame] - [reagent.core :as reagent] - [status-im.contexts.profile.utils :as profile.utils] - [utils.i18n :as i18n] - [utils.security.core :as security])) - -(defn valid-password? - [password] - (>= (count password) 6)) - -(defn on-delete-profile - [password] - #(do - (re-frame/dispatch - [::delete-profile/delete-profile @password]) - (reset! password nil))) - -(defn delete-profile - [] - (let [password (reagent/atom nil) - text-input-ref (atom nil)] - (fn [] - (let [profile @(re-frame/subscribe [:profile/profile]) - error @(re-frame/subscribe [:delete-profile/error]) - keep-keys-on-keycard? @(re-frame/subscribe - [:delete-profile/keep-keys-on-keycard?])] - (when (and @text-input-ref error (not @password)) - (.clear ^js @text-input-ref)) - [react/keyboard-avoiding-view {:style {:flex 1}} - [react/scroll-view {:style {:flex 1}} - [react/view {:style {:align-items :center}} - [quo/text - {:weight :bold - :size :x-large} - (i18n/label :t/delete-profile)]] - [list.item/list-item - {:title (profile.utils/displayed-name profile) - :icon [chat-icon.screen/contact-icon-contacts-tab profile]}] - [quo/text - {:style {:margin-horizontal 24} - :align :center - :color :negative} - (i18n/label :t/delete-profile-warning)] - [quo/text-input - {:style {:margin-horizontal 36 - :margin-top 36} - :show-cancel false - :secure-text-entry true - :return-key-type :next - :on-submit-editing nil - :auto-focus true - :on-change-text #(reset! password (security/mask-data %)) - :bottom-value 36 - :get-ref #(reset! text-input-ref %) - :error (when (and error (not @password)) - (if (= :wrong-password error) - (i18n/label :t/wrong-password) - (str error)))}]] - [react/view {:style {:align-items :center}} - [quo/separator] - (when (not keep-keys-on-keycard?) - [quo/text - {:style {:margin-horizontal 24 :margin-bottom 16} - :align :center - :color :negative} - (i18n/label :t/delete-profile-warning)]) - [react/view - {:style {:margin-vertical 8}} - [quo/button - {:on-press (on-delete-profile password) - :theme :negative - :accessibility-label :delete-profile-confirm - :disabled ((complement valid-password?) @password)} - (i18n/label :t/delete-profile)]]]])))) diff --git a/src/legacy/status_im/ui/screens/privacy_and_security_settings/events.cljs b/src/legacy/status_im/ui/screens/privacy_and_security_settings/events.cljs deleted file mode 100644 index e166a01329..0000000000 --- a/src/legacy/status_im/ui/screens/privacy_and_security_settings/events.cljs +++ /dev/null @@ -1,73 +0,0 @@ -(ns legacy.status-im.ui.screens.privacy-and-security-settings.events - (:require - [clojure.string :as string] - [legacy.status-im.utils.deprecated-types :as types] - [native-module.core :as native-module] - [re-frame.core :as re-frame] - [taoensso.timbre :as log] - [utils.i18n :as i18n] - [utils.re-frame :as rf] - [utils.security.core :as security])) - -(defn safe-blank? - [s] - (or (not s) - (string/blank? s))) - -(re-frame/reg-fx - ::delete-profile - (fn [{:keys [address key-uid callback masked-password]}] - (let [hashed-password - (-> masked-password - security/safe-unmask-data - native-module/sha3)] - (native-module/verify - address - hashed-password - (fn [result] - (let [{:keys [error]} (types/json->clj result)] - (log/info "[delete-profile] verify-password" result error) - (if-not (safe-blank? error) - (callback :wrong-password nil) - (native-module/delete-multiaccount - key-uid - (fn [result] - (let [{:keys [error]} (types/json->clj result)] - (callback error nil))))))))))) - -(rf/defn delete-profile - {:events [::delete-profile]} - [{:keys [db] :as cofx} masked-password] - (log/info "[delete-profile] delete") - (let [{:keys [key-uid wallet-root-address]} (:profile/profile db)] - {:db (dissoc db :delete-profile/error) - ::delete-profile - {:masked-password masked-password - :key-uid key-uid - :address wallet-root-address - :callback - (fn [error result] - (log/info "[delete-profile] callback" error) - (if (safe-blank? error) - (re-frame/dispatch [::on-delete-profile-success result]) - (re-frame/dispatch [::on-delete-profile-failure error])))}})) - -(rf/defn on-delete-profile-success - {:events [::on-delete-profile-success]} - [cofx] - (log/info "[delete-profile] on-success") - {:effects.utils/show-popup - {:title (i18n/label :t/profile-deleted-title) - :content (i18n/label :t/profile-deleted-content) - :on-dismiss #(re-frame/dispatch [:logout])}}) - -(rf/defn on-delete-profile-failure - {:events [::on-delete-profile-failure]} - [{:keys [db]} error] - (log/info "[delete-profile] on-failure" error) - {:db (assoc db :delete-profile/error error)}) - -(rf/defn keep-keys-on-keycard - {:events [::keep-keys-on-keycard]} - [{:keys [db] :as cofx} checked?] - {:db (assoc-in db [:delete-profile/keep-keys-on-keycard?] checked?)}) diff --git a/src/legacy/status_im/ui/screens/privacy_and_security_settings/messages_from_contacts_only.cljs b/src/legacy/status_im/ui/screens/privacy_and_security_settings/messages_from_contacts_only.cljs deleted file mode 100644 index 59533e8ddf..0000000000 --- a/src/legacy/status_im/ui/screens/privacy_and_security_settings/messages_from_contacts_only.cljs +++ /dev/null @@ -1,34 +0,0 @@ -(ns legacy.status-im.ui.screens.privacy-and-security-settings.messages-from-contacts-only - (:require-macros [legacy.status-im.utils.views :as views]) - (:require - [legacy.status-im.multiaccounts.update.core :as multiaccounts.update] - [legacy.status-im.ui.components.list.item :as list.item] - [legacy.status-im.ui.components.react :as react] - [re-frame.core :as re-frame] - [utils.i18n :as i18n] - [utils.re-frame :as rf])) - -(rf/defn handle-messages-from-contacts-only-switched - {:events [::messages-from-contacts-only-switched]} - [cofx value] - (multiaccounts.update/multiaccount-update cofx - :messages-from-contacts-only - value - {})) - -(views/defview messages-from-contacts-only-view - [] - (views/letsubs [{:keys [messages-from-contacts-only]} [:profile/profile]] - [react/view {:margin-top 8} - [list.item/list-item - {:active (not messages-from-contacts-only) - :accessory :radio - :title (i18n/label :t/anyone) - :on-press #(re-frame/dispatch [::messages-from-contacts-only-switched false])}] - [list.item/list-item - {:active messages-from-contacts-only - :accessory :radio - :title (i18n/label :t/contacts) - :subtitle (i18n/label :t/messages-from-contacts-only-subtitle) - :subtitle-max-lines 4 - :on-press #(re-frame/dispatch [::messages-from-contacts-only-switched true])}]])) diff --git a/src/legacy/status_im/ui/screens/privacy_and_security_settings/views.cljs b/src/legacy/status_im/ui/screens/privacy_and_security_settings/views.cljs deleted file mode 100644 index c5b0ac155d..0000000000 --- a/src/legacy/status_im/ui/screens/privacy_and_security_settings/views.cljs +++ /dev/null @@ -1,175 +0,0 @@ -(ns legacy.status-im.ui.screens.privacy-and-security-settings.views - (:require - [legacy.status-im.multiaccounts.reset-password.core :as reset-password] - [legacy.status-im.multiaccounts.update.core :as multiaccounts.update] - [legacy.status-im.ui.components.core :as components] - [legacy.status-im.ui.components.list.item :as list.item] - [legacy.status-im.ui.components.react :as react] - [quo.core :as quo] - [re-frame.core :as re-frame] - [react-native.platform :as platform] - [status-im.config :as config] - [status-im.constants :as constants] - [utils.i18n :as i18n] - [utils.re-frame :as rf]) - (:require-macros [legacy.status-im.utils.views :as views])) - -(defn separator - [] - [components/separator {:style {:margin-vertical 8}}]) - -(def titles - {constants/profile-pictures-visibility-contacts-only (i18n/label :t/recent-recipients) - constants/profile-pictures-visibility-everyone (i18n/label :t/everyone) - constants/profile-pictures-visibility-none (i18n/label :t/none) - constants/profile-pictures-show-to-contacts-only (i18n/label :t/recent-recipients) - constants/profile-pictures-show-to-everyone (i18n/label :t/everyone) - constants/profile-pictures-show-to-none (i18n/label :t/none)}) - -(views/defview privacy-and-security - [] - (views/letsubs [{:keys [preview-privacy? - messages-from-contacts-only - webview-allow-permission-requests? - opensea-enabled? - profile-pictures-visibility]} - [:profile/profile] - has-picture [:profile/has-picture] - profile-pictures-show-to [:multiaccount/profile-pictures-show-to]] - [:<> - [quo/page-nav - {:type :no-title - :background :blur - :icon-name :i/close - :on-press #(rf/dispatch [:navigate-back])}] - [react/scroll-view {:padding-vertical 8} - [components/list-header (i18n/label :t/privacy)] - [list.item/list-item - {:size :small - :title (i18n/label :t/set-dapp-access-permissions) - :on-press #(re-frame/dispatch [:navigate-to :dapps-permissions]) - :accessibility-label :dapps-permissions-button - :chevron true}] - [list.item/list-item - {:size :small - :title (if platform/android? - (i18n/label :t/hide-content-when-switching-apps) - (i18n/label :t/hide-content-when-switching-apps-ios)) - :container-margin-bottom 8 - :active preview-privacy? - :accessory :switch - :on-press #(re-frame/dispatch - [:profile.settings/change-preview-privacy - ((complement boolean) preview-privacy?)])}] - (when config/collectibles-enabled? - [list.item/list-item - {:size :small - :title (i18n/label :t/display-collectibles) - :container-margin-bottom 8 - :active opensea-enabled? - :accessory :switch - :on-press #(re-frame/dispatch - [::multiaccounts.update/toggle-opensea-nfts-visiblity - (not opensea-enabled?)])}]) - [list.item/list-item - {:size :small - :title (i18n/label :t/chat-link-previews) - :chevron true - :on-press #(re-frame/dispatch [:open-modal :legacy-link-previews-settings]) - :accessibility-label :chat-link-previews}] - [list.item/list-item - {:size :small - :title (i18n/label :t/accept-new-chats-from) - :chevron true - :accessory :text - :accessory-text (i18n/label (if messages-from-contacts-only - :t/contacts - :t/anyone)) - :on-press #(re-frame/dispatch [:navigate-to :messages-from-contacts-only]) - :accessibility-label :accept-new-chats-from}] - [list.item/list-item - {:size :small - :title (i18n/label :t/reset-password) - :chevron true - :accessory :text - :on-press #(do - (re-frame/dispatch [::reset-password/clear-form-vals]) - (re-frame/dispatch [:navigate-to :reset-password])) - :accessibility-label :reset-password}] - (when platform/android? - [list.item/list-item - {:size :small - :title (i18n/label :t/webview-camera-permission-requests) - :active webview-allow-permission-requests? - :accessory :switch - :subtitle (i18n/label :t/webview-camera-permission-requests-subtitle) - :subtitle-max-lines 2 - :on-press #(re-frame/dispatch - [:profile.settings/profile-update - :webview-allow-permission-requests? - ((complement boolean) webview-allow-permission-requests?)])}]) - [separator] - [components/list-header (i18n/label :t/privacy-photos)] - [list.item/list-item - {:size :small - :title (i18n/label :t/show-profile-pictures) - :accessibility-label :show-profile-pictures - :accessory :text - :accessory-text (get titles profile-pictures-visibility) - :on-press #(re-frame/dispatch [:navigate-to :privacy-and-security-profile-pic]) - :chevron true}] - [list.item/list-item - {:size :small - :title (i18n/label :t/show-profile-pictures-to) - :disabled (not has-picture) - :accessibility-label :show-profile-pictures-to - :accessory :text - :accessory-text (get titles profile-pictures-show-to) - :on-press #(re-frame/dispatch [:navigate-to - :privacy-and-security-profile-pic-show-to]) - :chevron true}] - - [separator] - [list.item/list-item - {:size :small - :theme :negative - :title (i18n/label :t/delete-my-profile) - :on-press #(re-frame/dispatch [:navigate-to :delete-profile]) - :accessibility-label :dapps-permissions-button - :chevron true}]]])) - -(defn ppst-radio-item - [id value] - [list.item/list-item - {:active (= value id) - :accessory :radio - :title (get titles id) - :on-press #(re-frame/dispatch [:profile.settings/change-profile-pictures-show-to id])}]) - -(views/defview profile-pic-show-to - [] - (views/letsubs [{:keys [profile-pictures-show-to]} [:profile/profile]] - [react/view {:margin-top 8} - [ppst-radio-item constants/profile-pictures-show-to-everyone profile-pictures-show-to] - [ppst-radio-item constants/profile-pictures-show-to-contacts-only profile-pictures-show-to] - [ppst-radio-item constants/profile-pictures-show-to-none profile-pictures-show-to] - - [react/view {:style {:margin-horizontal 16}} - [components/text {:color :secondary} - (i18n/label :t/privacy-show-to-warning)]]])) - -(defn ppvf-radio-item - [id value] - [list.item/list-item - {:active (= value id) - :accessory :radio - :title (get titles id) - :on-press #(re-frame/dispatch [:profile.settings/profile-update :profile-pictures-visibility id])}]) - -(views/defview profile-pic - [] - (views/letsubs [{:keys [profile-pictures-visibility]} [:profile/profile]] - [react/view {:margin-top 8} - [ppvf-radio-item constants/profile-pictures-visibility-everyone profile-pictures-visibility] - [ppvf-radio-item constants/profile-pictures-visibility-contacts-only profile-pictures-visibility] - [ppvf-radio-item constants/profile-pictures-visibility-none profile-pictures-visibility]])) diff --git a/src/legacy/status_im/ui/screens/profile/user/views.cljs b/src/legacy/status_im/ui/screens/profile/user/views.cljs index c48e02a0e2..e2fc829ca9 100644 --- a/src/legacy/status_im/ui/screens/profile/user/views.cljs +++ b/src/legacy/status_im/ui/screens/profile/user/views.cljs @@ -13,12 +13,6 @@ [] [rn/scroll-view {:flex 1} [components/list-header "Legacy settings"] - [list.item/list-item - {:icon :main-icons/security - :title (i18n/label :t/privacy-and-security) - :accessibility-label :privacy-and-security-settings-button - :chevron true - :on-press #(re-frame/dispatch [:open-modal :legacy-privacy-and-security])}] [list.item/list-item {:icon :main-icons/mobile :title (i18n/label :t/sync-settings) diff --git a/src/legacy/status_im/ui/screens/screens.cljs b/src/legacy/status_im/ui/screens/screens.cljs index a95785c65b..80b8b8d026 100644 --- a/src/legacy/status_im/ui/screens/screens.cljs +++ b/src/legacy/status_im/ui/screens/screens.cljs @@ -8,12 +8,10 @@ [legacy.status-im.ui.screens.bug-report :as bug-report] [legacy.status-im.ui.screens.communities.invite :as communities.invite] [legacy.status-im.ui.screens.communities.members :as members] - [legacy.status-im.ui.screens.dapps-permissions.views :as dapps-permissions] [legacy.status-im.ui.screens.default-sync-period-settings.view :as default-sync-period-settings] [legacy.status-im.ui.screens.fleet-settings.views :as fleet-settings] [legacy.status-im.ui.screens.glossary.view :as glossary] [legacy.status-im.ui.screens.help-center.views :as help-center] - [legacy.status-im.ui.screens.link-previews-settings.views :as link-previews-settings] [legacy.status-im.ui.screens.log-level-settings.views :as log-level-settings] [legacy.status-im.ui.screens.mobile-network-settings.view :as mobile-network-settings] [legacy.status-im.ui.screens.notifications-settings.views :as notifications-settings] @@ -21,10 +19,6 @@ [legacy.status-im.ui.screens.offline-messaging-settings.views :as offline-messaging-settings] [legacy.status-im.ui.screens.pairing.views :as pairing] [legacy.status-im.ui.screens.peers-stats :as peers-stats] - [legacy.status-im.ui.screens.privacy-and-security-settings.delete-profile :as delete-profile] - [legacy.status-im.ui.screens.privacy-and-security-settings.messages-from-contacts-only :as - messages-from-contacts-only] - [legacy.status-im.ui.screens.privacy-and-security-settings.views :as privacy-and-security] [legacy.status-im.ui.screens.profile.seed.views :as profile.seed] [legacy.status-im.ui.screens.profile.user.views :as profile.user] [legacy.status-im.ui.screens.progress.views :as progress] @@ -94,17 +88,6 @@ :insets {:top? platform/android?}} :component profile.user/legacy-settings} - ;; PRIVACY AND SECURITY - {:name :legacy-privacy-and-security - :options {:topBar {:visible false} - :insets {:top? platform/android?}} - :component privacy-and-security/privacy-and-security} - - {:name :legacy-link-previews-settings - :options {:topBar {:visible false} - :insets {:top? platform/android?}} - :component link-previews-settings/link-previews-settings} - ;; SYNC {:name :legacy-sync-settings :options {:topBar {:visible false} @@ -150,25 +133,6 @@ {:name :edit-mailserver :options {:insets {:top? true}} :component edit-mailserver/edit-mailserver} - {:name :dapps-permissions - :options {:topBar (topbar-options :t/dapps-permissions) - :insets {:top? true}} - :component dapps-permissions/dapps-permissions} - {:name :messages-from-contacts-only - :options {:topBar (topbar-options :t/accept-new-chats-from) - :insets {:top? true}} - :component messages-from-contacts-only/messages-from-contacts-only-view} - {:name :privacy-and-security-profile-pic-show-to - :options {:topbar (topbar-options :t/show-profile-pictures-to) - :insets {:top? true}} - :component privacy-and-security/profile-pic-show-to} - {:name :privacy-and-security-profile-pic - :options {:topBar (topbar-options :t/show-profile-pictures) - :insets {:top? true}} - :component privacy-and-security/profile-pic} - {:name :manage-dapps-permissions - :options {:insets {:top? true}} - :component dapps-permissions/manage} {:name :rpc-usage-info :options {:topBar (topbar-options :t/rpc-usage-info) :insets {:top? true}} @@ -196,9 +160,6 @@ :options {:topBar (topbar-options :t/reset-password) :insets {:top? true}} :component reset-password/reset-password} - {:name :delete-profile - :insets {:bottom? true} - :component delete-profile/delete-profile} {:name :default-sync-period-settings :options {:topBar (topbar-options :t/default-sync-period) :insets {:top? true}} diff --git a/src/quo/components/settings/settings_item/style.cljs b/src/quo/components/settings/settings_item/style.cljs index e4ba8c679b..0dc93cdf15 100644 --- a/src/quo/components/settings/settings_item/style.cljs +++ b/src/quo/components/settings/settings_item/style.cljs @@ -51,7 +51,7 @@ (defn color [blur? theme] {:color (if blur? - colors/white-opa-70 + colors/white-opa-40 (colors/theme-colors colors/neutral-50 colors/neutral-40 theme))}) (defn label-dot diff --git a/src/status_im/config.cljs b/src/status_im/config.cljs index 1213040f6f..b85044c90a 100644 --- a/src/status_im/config.cljs +++ b/src/status_im/config.cljs @@ -63,7 +63,6 @@ (def quo-preview-enabled? (enabled? (get-config :ENABLE_QUO_PREVIEW "0"))) (def database-management-enabled? (enabled? (get-config :DATABASE_MANAGEMENT_ENABLED "0"))) (def debug-webview? (enabled? (get-config :DEBUG_WEBVIEW "0"))) -(def collectibles-enabled? (enabled? (get-config :COLLECTIBLES_ENABLED "1"))) (def test-stateofus? (enabled? (get-config :TEST_STATEOFUS "0"))) (def two-minutes-syncing? (enabled? (get-config :TWO_MINUTES_SYNCING "0"))) (def swap-enabled? (enabled? (get-config :SWAP_ENABLED "0"))) diff --git a/src/status_im/contexts/profile/events.cljs b/src/status_im/contexts/profile/events.cljs index cb394aa08d..fa74a525fd 100644 --- a/src/status_im/contexts/profile/events.cljs +++ b/src/status_im/contexts/profile/events.cljs @@ -1,6 +1,7 @@ (ns status-im.contexts.profile.events (:require [legacy.status-im.data-store.settings :as data-store.settings] + [legacy.status-im.multiaccounts.update.core :as multiaccounts.update] [native-module.core :as native-module] [status-im.config :as config] [status-im.contexts.profile.edit.accent-colour.events] @@ -73,3 +74,11 @@ :profile/update-profile-from-backup (fn [_ [{{:keys [ensUsernameDetails]} :backedUpProfile}]] {:fx [[:dispatch [:ens/update-usernames ensUsernameDetails]]]})) + +(rf/reg-event-fx :profile/update-messages-from-contacts-only + (fn [{:keys [db] :as cofx}] + (multiaccounts.update/multiaccount-update + cofx + :messages-from-contacts-only + (not (get-in db [:profile/profile :messages-from-contacts-only])) + {}))) diff --git a/src/status_im/contexts/profile/settings/screens/messages/view.cljs b/src/status_im/contexts/profile/settings/screens/messages/view.cljs index 3adbeb24e0..1425ede052 100644 --- a/src/status_im/contexts/profile/settings/screens/messages/view.cljs +++ b/src/status_im/contexts/profile/settings/screens/messages/view.cljs @@ -1,11 +1,10 @@ (ns status-im.contexts.profile.settings.screens.messages.view - (:require [quo.core :as quo] - [utils.i18n :as i18n] - [utils.re-frame :as rf])) - -(defn- navigate-back - [] - (rf/dispatch [:navigate-back])) + (:require + [quo.core :as quo] + [react-native.core :as rn] + [utils.i18n :as i18n] + [utils.navigation :as navigation] + [utils.re-frame :as rf])) (defn- open-blocked-users [] @@ -13,17 +12,27 @@ (defn view [] - [quo/overlay {:type :shell :top-inset? true} - [quo/page-nav - {:background :blur - :icon-name :i/arrow-left - :on-press navigate-back}] - [quo/page-top {:title (i18n/label :t/messages)}] - [quo/category - {:label (i18n/label :t/contacts) - :data [{:title (i18n/label :t/blocked-users) - :on-press open-blocked-users - :blur? true - :action :arrow}] - :blur? true - :list-type :settings}]]) + (let [allow-new-contact-requests? (rf/sub [:profile/allow-new-contact-requests?]) + toggle-allow-new-contact-requests (rn/use-callback + (fn [] + (rf/dispatch [:profile/update-messages-from-contacts-only])) + [allow-new-contact-requests?])] + [quo/overlay {:type :shell :top-inset? true} + [quo/page-nav + {:background :blur + :icon-name :i/arrow-left + :on-press navigation/navigate-back}] + [quo/page-top {:title (i18n/label :t/messages)}] + [quo/category + {:label (i18n/label :t/contacts) + :data [{:title (i18n/label :t/allow-new-contact-requests) + :blur? true + :action :selector + :action-props {:on-change toggle-allow-new-contact-requests + :checked? allow-new-contact-requests?}} + {:title (i18n/label :t/blocked-users) + :on-press open-blocked-users + :blur? true + :action :arrow}] + :blur? true + :list-type :settings}]])) diff --git a/src/status_im/contexts/settings/privacy_and_security/profile_picture/view.cljs b/src/status_im/contexts/settings/privacy_and_security/profile_picture/view.cljs new file mode 100644 index 0000000000..857ec5e29a --- /dev/null +++ b/src/status_im/contexts/settings/privacy_and_security/profile_picture/view.cljs @@ -0,0 +1,118 @@ +(ns status-im.contexts.settings.privacy-and-security.profile-picture.view + (:require + [quo.core :as quo] + [quo.theme] + [status-im.constants :as constants] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(def pictures-visibility->label + {constants/profile-pictures-visibility-everyone (i18n/label :t/everyone) + constants/profile-pictures-visibility-contacts-only (i18n/label :t/contacts) + constants/profile-pictures-visibility-none (i18n/label :t/no-one)}) + +(def pictures-show-to->label + {constants/profile-pictures-show-to-everyone (i18n/label :t/everyone) + constants/profile-pictures-show-to-contacts-only (i18n/label :t/contacts) + constants/profile-pictures-show-to-none (i18n/label :t/no-one)}) + +(defn- update-profile + [k v] + (rf/dispatch [:profile.settings/profile-update k v]) + (rf/dispatch [:hide-bottom-sheet])) + +(defn- update-pictures-visibility-everyone + [] + (update-profile :profile-pictures-visibility constants/profile-pictures-visibility-everyone)) + +(defn- update-pictures-visibility-contacts-only + [] + (update-profile :profile-pictures-visibility constants/profile-pictures-visibility-contacts-only)) + +(defn- update-pictures-visibility-nobody + [] + (update-profile :profile-pictures-visibility constants/profile-pictures-visibility-none)) + +(defn- update-pictures-show-to-everyone + [] + (update-profile :profile-pictures-show-to constants/profile-pictures-show-to-everyone)) + +(defn- update-pictures-show-to-contacts-only + [] + (update-profile :profile-pictures-show-to constants/profile-pictures-show-to-contacts-only)) + +(defn- update-pictures-show-to-nobody + [] + (update-profile :profile-pictures-show-to constants/profile-pictures-show-to-none)) + +(defn options-for-profile-pictures-visibility + [setting] + (let [customization-color (rf/sub [:profile/customization-color])] + [:<> + [quo/drawer-top {:title (i18n/label :t/show-profile-pictures)}] + [quo/action-drawer + [[{:icon :i/globe + :accessibility-label :see-profile-pictures-from-everyone + :label (i18n/label :t/everyone) + :on-press update-pictures-visibility-everyone + :state (when (= constants/profile-pictures-visibility-everyone setting) + :selected)} + {:icon :i/contact + :icon-color customization-color + :accessibility-label :see-profile-pictures-from-contacts + :label (i18n/label :t/contacts) + :on-press update-pictures-visibility-contacts-only + :state (when (= constants/profile-pictures-visibility-contacts-only setting) + :selected)} + {:icon :i/hide + :accessibility-label :see-profile-pictures-from-nobody + :label (i18n/label :t/no-one) + :add-divider? true + :on-press update-pictures-visibility-nobody + :state (when (= constants/profile-pictures-visibility-none setting) + :selected)}]]]])) + +(defn options-for-profile-pictures-show-to + [setting] + (let [customization-color (rf/sub [:profile/customization-color])] + [:<> + [quo/drawer-top {:title (i18n/label :t/show-profile-pictures-to)}] + [quo/action-drawer + [[{:icon :i/globe + :accessibility-label :show-profile-pictures-to-everyone + :label (i18n/label :t/everyone) + :on-press update-pictures-show-to-everyone + :state (when (= constants/profile-pictures-show-to-everyone setting) + :selected)} + {:icon :i/contact + :icon-color customization-color + :accessibility-label :show-profile-pictures-to-contacts + :label (i18n/label :t/contacts) + :on-press update-pictures-show-to-contacts-only + :state (when (= constants/profile-pictures-show-to-contacts-only setting) + :selected)} + {:icon :i/hide + :accessibility-label :show-profile-pictures-to-nobody + :label (i18n/label :t/no-one) + :on-press update-pictures-show-to-nobody + :state (when (= constants/profile-pictures-show-to-none setting) + :selected) + :add-divider? true}]]]])) + +(defn setting-profile-pictures-visibility + [pictures-visibility on-press] + {:title (i18n/label :t/show-profile-pictures) + :description :text + :description-props {:text (pictures-visibility->label pictures-visibility)} + :blur? true + :action :arrow + :on-press on-press}) + +(defn setting-profile-pictures-show-to + [pictures-show-to on-press] + {:title (i18n/label :t/show-profile-pictures-to) + :description :text + :description-props {:text (pictures-show-to->label pictures-show-to)} + :blur? true + :action :arrow + :on-press on-press}) diff --git a/src/status_im/contexts/settings/privacy_and_security/view.cljs b/src/status_im/contexts/settings/privacy_and_security/view.cljs index 1ce7641bb8..909b84ab6a 100644 --- a/src/status_im/contexts/settings/privacy_and_security/view.cljs +++ b/src/status_im/contexts/settings/privacy_and_security/view.cljs @@ -2,20 +2,65 @@ (:require [quo.core :as quo] [quo.theme] + [react-native.core :as rn] + [react-native.platform :as platform] [react-native.safe-area :as safe-area] + [status-im.contexts.settings.privacy-and-security.profile-picture.view :as profile-picture.view] [status-im.contexts.settings.privacy-and-security.style :as style] + [status-im.feature-flags :as ff] [utils.i18n :as i18n] + [utils.navigation :as navigation] [utils.re-frame :as rf])) -(defn navigate-back - [] - (rf/dispatch [:navigate-back])) +(defn- setting-preview-privacy + [preview-privacy? customization-color on-change] + {:title (i18n/label (if platform/android? + :t/hide-content-when-switching-apps + :t/hide-content-when-switching-apps-ios)) + :blur? true + :action :selector + :action-props {:on-change on-change + :checked? preview-privacy? + :id :preview-privacy + :customization-color customization-color}}) (defn view [] - (let [insets (safe-area/get-insets) + (let [insets (safe-area/get-insets) centralized-metrics-enabled? (rf/sub [:centralized-metrics/enabled?]) - customization-color (rf/sub [:profile/customization-color])] + customization-color (rf/sub [:profile/customization-color]) + + preview-privacy? (rf/sub [:profile/preview-privacy?]) + see-profile-pictures-from (rf/sub [:profile/pictures-visibility]) + show-profile-pictures-to (rf/sub [:multiaccount/profile-pictures-show-to]) + + toggle-preview-privacy + (rn/use-callback (fn [] + (rf/dispatch [:profile.settings/change-preview-privacy + (not preview-privacy?)])) + [preview-privacy?]) + + open-see-profile-pictures-from-options + (rn/use-callback (fn [] + (rf/dispatch + [:show-bottom-sheet + {:theme :dark + :blur? true + :content (fn [] + [profile-picture.view/options-for-profile-pictures-visibility + see-profile-pictures-from])}])) + [see-profile-pictures-from]) + + open-show-profile-pictures-to-options + (rn/use-callback (fn [] + (rf/dispatch + [:show-bottom-sheet + {:theme :dark + :blur? true + :content (fn [] + [profile-picture.view/options-for-profile-pictures-show-to + show-profile-pictures-to])}])) + [show-profile-pictures-to])] [quo/overlay {:type :shell :container-style (style/page-wrapper (:top insets))} @@ -23,7 +68,7 @@ {:key :header :background :blur :icon-name :i/arrow-left - :on-press navigate-back}] + :on-press navigation/navigate-back}] [quo/standard-title {:title (i18n/label :t/privacy-and-security) :container-style style/title-container @@ -31,15 +76,22 @@ :customization-color customization-color}] [quo/category {:key :category - :data [{:title (i18n/label :t/share-usage-data) - :image-props :i/placeholder + :data [(when (ff/enabled? ::ff/profile-pictures-visibility) + (profile-picture.view/setting-profile-pictures-visibility + see-profile-pictures-from + open-see-profile-pictures-from-options)) + (when (ff/enabled? ::ff/profile-pictures-visibility) + (profile-picture.view/setting-profile-pictures-show-to + show-profile-pictures-to + open-show-profile-pictures-to-options)) + (setting-preview-privacy preview-privacy? customization-color toggle-preview-privacy) + {:title (i18n/label :t/share-usage-data) :blur? true :action :selector :action-props {:on-change #(rf/dispatch [:centralized-metrics/toggle-centralized-metrics (not centralized-metrics-enabled?)]) :customization-color customization-color - :checked? centralized-metrics-enabled?} - :on-press identity}] + :checked? centralized-metrics-enabled?}}] :blur? true :list-type :settings}]])) diff --git a/src/status_im/feature_flags.cljs b/src/status_im/feature_flags.cljs index f730cc302d..9185031933 100644 --- a/src/status_im/feature_flags.cljs +++ b/src/status_im/feature_flags.cljs @@ -11,6 +11,12 @@ (def ^:private initial-flags {::community.edit-account-selection (enabled-in-env? :FLAG_EDIT_ACCOUNT_SELECTION_ENABLED) + + ;; Feature toggled (off by default) because the desktop app disabled this + ;; feature and we want both clients in sync. We keep the code because it + ;; works and we may re-enable it by default. + ::profile-pictures-visibility (enabled-in-env? :FLAG_PROFILE_PICTURES_VISIBILITY_ENABLED) + ::settings.import-all-keypairs (enabled-in-env? :FLAG_WALLET_SETTINGS_IMPORT_ALL_KEYPAIRS) ::shell.jump-to (enabled-in-env? :ENABLE_JUMP_TO) diff --git a/src/status_im/subs/profile.cljs b/src/status_im/subs/profile.cljs index d87a720f5e..609a1641d5 100644 --- a/src/status_im/subs/profile.cljs +++ b/src/status_im/subs/profile.cljs @@ -231,6 +231,20 @@ (fn [multiaccount] (pos? (count (get multiaccount :images))))) +(re-frame/reg-sub :profile/pictures-visibility + :<- [:profile/profile] + :-> :profile-pictures-visibility) + +(re-frame/reg-sub :profile/allow-new-contact-requests? + :<- [:profile/profile] + (fn [{:keys [messages-from-contacts-only]}] + (not messages-from-contacts-only))) + +(re-frame/reg-sub :profile/preview-privacy? + :<- [:profile/profile] + (fn [{:keys [preview-privacy?]}] + (boolean preview-privacy?))) + (defn- replace-multiaccount-image-uri [profile ens-names port font-file avatar-opts theme] (let [{:keys [key-uid ens-name? images diff --git a/src/status_im/subs/root.cljs b/src/status_im/subs/root.cljs index c84fdd9dfb..029eb3207d 100644 --- a/src/status_im/subs/root.cljs +++ b/src/status_im/subs/root.cljs @@ -91,9 +91,6 @@ (reg-root-key-sub :multiaccount/reset-password-form-vals :multiaccount/reset-password-form-vals) (reg-root-key-sub :multiaccount/reset-password-errors :multiaccount/reset-password-errors) (reg-root-key-sub :multiaccount/resetting-password? :multiaccount/resetting-password?) -;;delete profile -(reg-root-key-sub :delete-profile/error :delete-profile/error) -(reg-root-key-sub :delete-profile/keep-keys-on-keycard? :delete-profile/keep-keys-on-keycard?) ;;chat (reg-root-key-sub :chats/cooldown-enabled? :chat/cooldown-enabled?) diff --git a/src/utils/navigation.cljs b/src/utils/navigation.cljs new file mode 100644 index 0000000000..0a2d92651e --- /dev/null +++ b/src/utils/navigation.cljs @@ -0,0 +1,8 @@ +(ns utils.navigation + "Prefer to declare in this namespace only reusable navigation functions that + aren't better suited in a particular bounded context." + (:require [utils.re-frame :as rf])) + +(defn navigate-back + [] + (rf/dispatch [:navigate-back])) diff --git a/translations/en.json b/translations/en.json index f35b1e7b19..3e70c031a1 100644 --- a/translations/en.json +++ b/translations/en.json @@ -16,6 +16,7 @@ "added-you-to": "added you to", "address-name": "Address name", "anyone": "Anyone", + "allow-new-contact-requests": "Allow new contact requests", "messages-from-contacts-only-subtitle": "Only people you added as contacts can start a new chat with you or invite you to a group", "messages-gap-warning": "Some messages might be missing", "accept-new-chats-from": "Accept new chats from", @@ -1093,6 +1094,7 @@ "node-version": "Node version", "nonce": "Nonce", "none": "None", + "no-one": "No one", "not-a-chatkey": "This is not a chatkey", "not-applicable": "Not applicable for unsigned transactions", "not-keycard-text": "The card you used is not a Keycard. You need to purchase a Keycard to use it", From 54e4b597edc6b7dd785cfa26b586085f287c2f1d Mon Sep 17 00:00:00 2001 From: Icaro Motta Date: Thu, 11 Jul 2024 22:38:18 -0300 Subject: [PATCH 27/79] chore(communities): Hide non-implemented tabs in Discover communities (#20684) Tabs "Open" and "Gated" in Discover communities screen were never implemented in the code, they are dummies. We now hide them under the flag config/show-not-implemented-features?, which is disabled for users. Fixes: https://github.com/status-im/status-mobile/issues/20682 To avoid regressions and be quicker, I opted to not remove the "All" tab even though is the only one shown now, as that would require me to check non-trivial code due to the fact that the tabs can be sticky as the user scrolls. --- .../contexts/communities/discover/view.cljs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/status_im/contexts/communities/discover/view.cljs b/src/status_im/contexts/communities/discover/view.cljs index 6b92ac815b..a491ab820a 100644 --- a/src/status_im/contexts/communities/discover/view.cljs +++ b/src/status_im/contexts/communities/discover/view.cljs @@ -8,6 +8,7 @@ [react-native.safe-area :as safe-area] [reagent.core :as reagent] [status-im.common.scroll-page.view :as scroll-page] + [status-im.config :as config] [status-im.contexts.communities.actions.community-options.view :as options] [status-im.contexts.communities.discover.style :as style] [status-im.feature-flags :as ff] @@ -76,12 +77,14 @@ :data [{:id :all :label (i18n/label :t/all) :accessibility-label :all-communities-tab} - {:id :open - :label (i18n/label :t/open) - :accessibility-label :open-communities-tab} - {:id :gated - :label (i18n/label :t/gated) - :accessibility-label :gated-communities-tab}]}]]) + (when config/show-not-implemented-features? + {:id :open + :label (i18n/label :t/open) + :accessibility-label :open-communities-tab}) + (when config/show-not-implemented-features? + {:id :gated + :label (i18n/label :t/gated) + :accessibility-label :gated-communities-tab})]}]]) (defn loading-community-item [_ _ _ {:keys [width]}] From 23c25d771529793a6861c2c7a3014d01f2a743eb Mon Sep 17 00:00:00 2001 From: Parvesh Monu Date: Fri, 12 Jul 2024 11:12:49 +0530 Subject: [PATCH 28/79] Fix missing avatar and name in members of the community channels and group chats (#20619) --- .../ethereum/module/EncryptionUtils.java | 5 ++ .../ios/RCTStatus/EncryptionUtils.m | 4 ++ modules/react-native-status/nodejs/status.cpp | 33 +++++++++ src/native_module/core.cljs | 5 ++ src/status_im/subs/chats.cljs | 18 ++++- src/status_im/subs/contact.cljs | 67 +++++-------------- src/status_im/subs/contact/utils.cljs | 54 +++++++++++++++ src/tests/test_utils.cljs | 2 + 8 files changed, 136 insertions(+), 52 deletions(-) create mode 100644 src/status_im/subs/contact/utils.cljs diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/EncryptionUtils.java b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/EncryptionUtils.java index bed7b8ac08..d7ca639593 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/EncryptionUtils.java +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/EncryptionUtils.java @@ -92,6 +92,11 @@ public class EncryptionUtils extends ReactContextBaseJavaModule { return Statusgo.hexToUtf8(str); } + @ReactMethod(isBlockingSynchronousMethod = true) + public String serializeLegacyKey(final String publicKey) { + return Statusgo.serializeLegacyKey(publicKey); + } + @ReactMethod public void setBlankPreviewFlag(final Boolean blankPreview) { final SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this.reactContext); diff --git a/modules/react-native-status/ios/RCTStatus/EncryptionUtils.m b/modules/react-native-status/ios/RCTStatus/EncryptionUtils.m index 99dbf91036..d580dea59f 100644 --- a/modules/react-native-status/ios/RCTStatus/EncryptionUtils.m +++ b/modules/react-native-status/ios/RCTStatus/EncryptionUtils.m @@ -94,6 +94,10 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(hexToUtf8:(NSString *)str) { return StatusgoHexToUtf8(str); } +RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(serializeLegacyKey:(NSString *)str) { + return StatusgoSerializeLegacyKey(str); +} + RCT_EXPORT_METHOD(setBlankPreviewFlag:(BOOL *)newValue) { NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; diff --git a/modules/react-native-status/nodejs/status.cpp b/modules/react-native-status/nodejs/status.cpp index 126aece4e0..d236e5edda 100644 --- a/modules/react-native-status/nodejs/status.cpp +++ b/modules/react-native-status/nodejs/status.cpp @@ -672,6 +672,38 @@ void _Sha3(const FunctionCallbackInfo& args) { } +void _SerializeLegacyKey(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + if (args.Length() != 1) { + // Throw an Error that is passed back to JavaScript + isolate->ThrowException(Exception::TypeError( + String::NewFromUtf8Literal(isolate, "Wrong number of arguments for SerializeLegacyKey"))); + return; + } + + // Check the argument types + + if (!args[0]->IsString()) { + isolate->ThrowException(Exception::TypeError( + String::NewFromUtf8Literal(isolate, "Wrong argument type for 'str'"))); + return; + } + + + String::Utf8Value arg0Obj(isolate, args[0]->ToString(context).ToLocalChecked()); + char *arg0 = *arg0Obj; + + // Call exported Go function, which returns a C string + char *c = SerializeLegacyKey(arg0); + + Local ret = String::NewFromUtf8(isolate, c).ToLocalChecked(); + args.GetReturnValue().Set(ret); + delete c; + +} + void _ToChecksumAddress(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); Local context = isolate->GetCurrentContext(); @@ -1976,6 +2008,7 @@ void init(Local exports) { NODE_SET_METHOD(exports, "checkAddressChecksum", _CheckAddressChecksum); NODE_SET_METHOD(exports, "isAddress", _IsAddress); NODE_SET_METHOD(exports, "sha3", _Sha3); + NODE_SET_METHOD(exports, "serializeLegacyKey", _SerializeLegacyKey); NODE_SET_METHOD(exports, "toChecksumAddress", _ToChecksumAddress); NODE_SET_METHOD(exports, "logout", _Logout); NODE_SET_METHOD(exports, "hashMessage", _HashMessage); diff --git a/src/native_module/core.cljs b/src/native_module/core.cljs index 85b11a7f5e..4f5edc4893 100644 --- a/src/native_module/core.cljs +++ b/src/native_module/core.cljs @@ -333,6 +333,11 @@ :key input-key}) (.deserializeAndCompressKey ^js (encryption) input-key callback)) +(defn serialize-legacy-key + "Compresses an old format public key (0x04...) to the new one zQ..." + [public-key] + (.serializeLegacyKey ^js (encryption) public-key)) + (defn compressed-key->public-key "Provides compressed key to status-go and gets back the uncompressed public key via deserialization" [public-key deserialization-key callback] diff --git a/src/status_im/subs/chats.cljs b/src/status_im/subs/chats.cljs index b804fb4977..4d0d0c9545 100644 --- a/src/status_im/subs/chats.cljs +++ b/src/status_im/subs/chats.cljs @@ -5,7 +5,8 @@ [re-frame.core :as re-frame] [status-im.constants :as constants] [status-im.contexts.chat.events :as chat.events] - [status-im.contexts.profile.utils :as profile.utils])) + [status-im.contexts.profile.utils :as profile.utils] + [status-im.subs.contact.utils :as contact.utils])) (def memo-chats-stack-items (atom nil)) @@ -259,8 +260,19 @@ :chats/photo-path :<- [:contacts/contacts] :<- [:profile/profile-with-image] - (fn [[contacts {:keys [public-key] :as multiaccount}] [_ id]] - (let [contact (or (when (= id public-key) multiaccount) (get contacts id))] + :<- [:mediaserver/port] + :<- [:initials-avatar-font-file] + :<- [:theme] + (fn [[contacts {:keys [public-key] :as multiaccount} port font-file theme] [_ id]] + (let [contact (or (when (= id public-key) multiaccount) + (get contacts id) + (contact.utils/replace-contact-image-uri + {:contact {:public-key id + :customization-color constants/profile-default-color} + :port port + :public-key id + :font-file font-file + :theme theme}))] (profile.utils/photo contact)))) (re-frame/reg-sub diff --git a/src/status_im/subs/contact.cljs b/src/status_im/subs/contact.cljs index 1a866e1b0f..2b5ff555a0 100644 --- a/src/status_im/subs/contact.cljs +++ b/src/status_im/subs/contact.cljs @@ -5,10 +5,10 @@ [legacy.status-im.ui.screens.profile.visibility-status.utils :as visibility-status-utils] [quo.theme] [re-frame.core :as re-frame] - [status-im.common.pixel-ratio :as pixel-ratio] [status-im.constants :as constants] [status-im.contexts.profile.utils :as profile.utils] [status-im.subs.chat.utils :as chat.utils] + [status-im.subs.contact.utils :as contact.utils] [utils.address :as address] [utils.collection] [utils.i18n :as i18n])) @@ -37,50 +37,6 @@ (fn [multiaccount] (get multiaccount :profile-pictures-visibility))) -(defn- replace-contact-image-uri - [contact port public-key font-file theme] - (let [{:keys [images ens-name customization-color]} contact - images - (reduce (fn [acc image] - (let [image-name (:type image) - clock (:clock image) - options {:port port - :ratio pixel-ratio/ratio - :public-key - public-key - :image-name - image-name - ; We pass the clock so that we reload the - ; image if the image is updated - :clock - clock - :theme - theme - :override-ring? - (when ens-name false)}] - (assoc-in acc - [(keyword image-name) :config] - {:type :contact - :options options}))) - images - (vals images)) - - images (if (seq images) - images - {:thumbnail - {:config {:type :initials - :options {:port port - :ratio pixel-ratio/ratio - :public-key public-key - :override-ring? (when ens-name false) - :uppercase-ratio (:uppercase-ratio - constants/initials-avatar-font-conf) - :customization-color customization-color - :theme theme - :font-file font-file}}}})] - - (assoc contact :images images))) - (defn- enrich-contact ([contact] (enrich-contact contact nil nil)) ([{:keys [public-key] :as contact} setting own-public-key] @@ -103,7 +59,12 @@ (defn- reduce-contacts-image-uri [contacts port font-file theme] (reduce-kv (fn [acc public-key contact] - (let [contact (replace-contact-image-uri contact port public-key font-file theme)] + (let [contact (contact.utils/replace-contact-image-uri + {:contact contact + :port port + :public-key public-key + :font-file font-file + :theme theme})] (assoc acc public-key contact))) {} contacts)) @@ -230,7 +191,12 @@ [_ contact-identity ens-name port font-file theme] (let [contact (enrich-contact (public-key-and-ens-name->new-contact contact-identity ens-name))] - (replace-contact-image-uri contact port contact-identity font-file theme))) + (contact.utils/replace-contact-image-uri + {:contact contact + :port port + :public-key contact-identity + :font-file font-file + :theme theme}))) (re-frame/reg-sub :contacts/current-contact @@ -250,7 +216,10 @@ :contacts/contact-by-identity :<- [:contacts/contacts] (fn [contacts [_ contact-identity]] - (get contacts contact-identity {:public-key contact-identity}))) + (get + contacts + contact-identity + (contact.utils/build-contact-from-public-key contact-identity)))) (re-frame/reg-sub :contacts/contact-two-names-by-identity @@ -288,7 +257,7 @@ (assoc public-key current-contact))] (->> members (map #(or (get all-contacts %) - {:public-key %})) + (contact.utils/build-contact-from-public-key %))) (sort-by (comp string/lower-case (fn [{:keys [primary-name name alias public-key]}] (or primary-name diff --git a/src/status_im/subs/contact/utils.cljs b/src/status_im/subs/contact/utils.cljs new file mode 100644 index 0000000000..37862bea26 --- /dev/null +++ b/src/status_im/subs/contact/utils.cljs @@ -0,0 +1,54 @@ +(ns status-im.subs.contact.utils + (:require + [native-module.core :as native-module] + [status-im.common.pixel-ratio :as pixel-ratio] + [status-im.constants :as constants] + [utils.address :as address])) + +(defn replace-contact-image-uri + [{:keys [contact port public-key font-file theme]}] + (let [{:keys [images ens-name customization-color]} contact + images + (reduce (fn [acc image] + (let [image-name (:type image) + clock (:clock image) + options {:port port + :ratio pixel-ratio/ratio + :public-key public-key + :image-name image-name + ; We pass the clock so that we reload the + ; image if the image is updated + :clock clock + :theme theme + :override-ring? (when ens-name false)}] + (assoc-in acc + [(keyword image-name) :config] + {:type :contact + :options options}))) + images + (vals images)) + + images (if (seq images) + images + {:thumbnail + {:config {:type :initials + :options {:port port + :ratio pixel-ratio/ratio + :public-key public-key + :override-ring? (when ens-name false) + :uppercase-ratio (:uppercase-ratio + constants/initials-avatar-font-conf) + :customization-color customization-color + :theme theme + :font-file font-file}}}})] + + (assoc contact :images images))) + + +(defn build-contact-from-public-key + [public-key] + (when public-key + (let [compressed-key (native-module/serialize-legacy-key public-key)] + {:public-key public-key + :compressed-key compressed-key + :primary-name (address/get-shortened-compressed-key (or compressed-key public-key))}))) diff --git a/src/tests/test_utils.cljs b/src/tests/test_utils.cljs index 29b11cb8bb..2ab9f00c80 100644 --- a/src/tests/test_utils.cljs +++ b/src/tests/test_utils.cljs @@ -40,6 +40,8 @@ (clj->js {:sha3 (fn [s] (.sha3 native-status s)) + :serializeLegacyKey + (fn [s] (.serializeLegacyKey native-status s)) :setBlankPreviewFlag identity :encodeTransfer From 721aa51d8d051018cc98a02a9026dbf5622e32ac Mon Sep 17 00:00:00 2001 From: Shivek Khurana Date: Fri, 12 Jul 2024 14:47:25 +0530 Subject: [PATCH 29/79] =?UTF-8?q?=F0=9F=8E=A3=20Fix=20wallet=20connect=20i?= =?UTF-8?q?nit=20issue=20in=20release=20bundles=20(#20665)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 👀 Added alerts so I peek into build * 🆙 Update logs * 🅰️ Annotate * 📤 Don't refer * 🖊️ Use default import * added ^js * ➕ Added ocall * 🎣 Isolate WC js calls to rn ns * 🧯Fix on-scan-connection issue --------- Co-authored-by: Lungu Cristian --- src/react_native/wallet_connect.cljs | 86 +++++++++++++++---- .../wallet/wallet_connect/effects.cljs | 54 ++++++------ .../wallet/wallet_connect/events.cljs | 2 +- 3 files changed, 96 insertions(+), 46 deletions(-) diff --git a/src/react_native/wallet_connect.cljs b/src/react_native/wallet_connect.cljs index b43e272bdd..cd2a1c2625 100644 --- a/src/react_native/wallet_connect.cljs +++ b/src/react_native/wallet_connect.cljs @@ -1,35 +1,91 @@ (ns react-native.wallet-connect (:require - ["@walletconnect/core" :refer [Core]] - ["@walletconnect/utils" :refer - [buildApprovedNamespaces getSdkError parseUri]] - ["@walletconnect/web3wallet" :refer [Web3Wallet]])) + ["@walletconnect/core" :as wc-core] + ["@walletconnect/utils" :as wc-utils] + ["@walletconnect/web3wallet$default" :as Web3Wallet] + [cljs-bean.core :as bean] + [oops.core :as oops])) (defn- wallet-connect-core [project-id] - (Core. #js {:projectId project-id})) + (new ^js wc-core/Core (clj->js {:projectId project-id}))) (defn init [project-id metadata] (let [core (wallet-connect-core project-id)] - (Web3Wallet.init - (clj->js {:core core - :metadata metadata})))) + (oops/ocall Web3Wallet + "init" + (bean/->js {:core core + :metadata metadata})))) (defn build-approved-namespaces [proposal supported-namespaces] - (buildApprovedNamespaces - (clj->js {:proposal proposal - :supportedNamespaces supported-namespaces}))) + (oops/ocall wc-utils + "buildApprovedNamespaces" + (bean/->js {:proposal proposal + :supportedNamespaces supported-namespaces}))) ;; Get an error from this list: ;; https://github.com/WalletConnect/walletconnect-monorepo/blob/c6e9529418a0c81d4efcc6ac4e61f242a50b56c5/packages/utils/src/errors.ts (defn get-sdk-error [error-key] - (getSdkError error-key)) + (oops/ocall wc-utils "getSdkError" error-key)) (defn parse-uri [uri] - (-> uri - parseUri - (js->clj :keywordize-keys true))) + (-> (oops/ocall wc-utils "parseUri" uri) + (bean/->clj))) + +(defn respond-session-request + [{:keys [web3-wallet topic id result error]}] + (oops/ocall web3-wallet + "respondSessionRequest" + (bean/->js {:topic topic + :response + (merge {:id id + :jsonrpc "2.0"} + (when result + {:result result}) + (when error + {:error error}))}))) + +(defn reject-session + [{:keys [web3-wallet id reason]}] + (.rejectSession web3-wallet + (clj->js {:id id + :reason reason}))) + +(defn approve-session + [{:keys [web3-wallet id approved-namespaces]}] + (oops/ocall web3-wallet + "approveSession" + (bean/->js {:id id + :namespaces approved-namespaces}))) + +(defn get-active-sessions + [web3-wallet] + (oops/ocall web3-wallet "getActiveSessions")) + +(defn core-pairing-disconnnect + [web3-wallet topic] + (oops/ocall web3-wallet + "core.pairing.disconnect" + (bean/->js {:topic topic}))) + +(defn core-pairing-pair + [web3-wallet url] + (oops/ocall web3-wallet + "core.pairing.pair" + (bean/->js {:uri url}))) + +(defn get-pairings + [web3-wallet] + (oops/ocall web3-wallet "core.pairing.getPairings")) + +(defn register-handler + [{:keys [web3-wallet event handler]}] + (oops/ocall web3-wallet + "on" + event + #(-> (bean/->clj %) + handler))) diff --git a/src/status_im/contexts/wallet/wallet_connect/effects.cljs b/src/status_im/contexts/wallet/wallet_connect/effects.cljs index 35a4bb3622..cb9364e7be 100644 --- a/src/status_im/contexts/wallet/wallet_connect/effects.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/effects.cljs @@ -29,18 +29,15 @@ (rf/reg-fx :effects.wallet-connect/register-event-listener (fn [[web3-wallet wc-event handler]] - (.on web3-wallet - wc-event - (fn [js-proposal] - (-> js-proposal - (js->clj :keywordize-keys true) - handler))))) + (wallet-connect/register-handler + {:web3-wallet web3-wallet + :event wc-event + :handler handler}))) (rf/reg-fx :effects.wallet-connect/fetch-pairings (fn [{:keys [web3-wallet on-success on-fail]}] - (-> (.. web3-wallet -core -pairing) - (.getPairings) + (-> (wallet-connect/get-pairings web3-wallet) (promesa/then on-success) (promesa/catch on-fail)))) @@ -48,23 +45,21 @@ :effects.wallet-connect/pair (fn [{:keys [web3-wallet url on-success on-fail]}] (when web3-wallet - (-> (.. web3-wallet -core -pairing) - (.pair (clj->js {:uri url})) + (-> (wallet-connect/core-pairing-pair web3-wallet url) (promesa/then on-success) (promesa/catch on-fail))))) (rf/reg-fx :effects.wallet-connect/disconnect (fn [{:keys [web3-wallet topic on-success on-fail]}] - (-> (.. web3-wallet -core -pairing) - (.disconnect (clj->js {:topic topic})) + (-> (wallet-connect/core-pairing-disconnnect web3-wallet topic) (promesa/then on-success) (promesa/catch on-fail)))) (rf/reg-fx :effects.wallet-connect/fetch-active-sessions (fn [{:keys [web3-wallet on-success on-fail]}] - (-> (.getActiveSessions web3-wallet) + (-> (wallet-connect/get-active-sessions web3-wallet) (promesa/then on-success) (promesa/catch on-fail)))) @@ -75,9 +70,10 @@ approved-namespaces (wallet-connect/build-approved-namespaces params supported-namespaces)] - (-> (.approveSession web3-wallet - (clj->js {:id id - :namespaces approved-namespaces})) + (-> (wallet-connect/approve-session + {:web3-wallet web3-wallet + :id id + :approved-namespaces approved-namespaces}) (promesa/then on-success) (promesa/catch on-fail))))) @@ -119,17 +115,14 @@ (rf/reg-fx :effects.wallet-connect/respond-session-request (fn [{:keys [web3-wallet topic id result error on-success on-error]}] - (-> - (.respondSessionRequest web3-wallet - (clj->js {:topic topic - :response (merge {:id id - :jsonrpc "2.0"} - (when result - {:result result}) - (when error - {:error error}))})) - (promesa/then on-success) - (promesa/catch on-error)))) + (-> (wallet-connect/respond-session-request + {:web3-wallet web3-wallet + :topic topic + :id id + :result result + :error error}) + (promesa/then on-success) + (promesa/catch on-error)))) (rf/reg-fx :effects.wallet-connect/reject-session-proposal @@ -137,8 +130,9 @@ (let [{:keys [id]} proposal reason (wallet-connect/get-sdk-error constants/wallet-connect-user-rejected-error-key)] - (-> (.rejectSession web3-wallet - (clj->js {:id id - :reason reason})) + (-> (wallet-connect/reject-session + {:web3-wallet web3-wallet + :id id + :reason reason}) (promesa/then on-success) (promesa/catch on-error))))) diff --git a/src/status_im/contexts/wallet/wallet_connect/events.cljs b/src/status_im/contexts/wallet/wallet_connect/events.cljs index 250776568b..51856c97da 100644 --- a/src/status_im/contexts/wallet/wallet_connect/events.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/events.cljs @@ -183,7 +183,7 @@ (fn [_ [scanned-text]] (let [parsed-uri (wallet-connect/parse-uri scanned-text) version (:version parsed-uri) - valid-wc-uri? (wc-utils/valid-uri? parsed-uri) + valid-wc-uri? (wc-utils/valid-wc-uri? parsed-uri) expired? (-> parsed-uri :expiryTimestamp wc-utils/timestamp-expired?) From d2e975f6d9336f12678903b24267e1f357f656fa Mon Sep 17 00:00:00 2001 From: flexsurfer Date: Fri, 12 Jul 2024 12:02:17 +0200 Subject: [PATCH 30/79] [#20434] Allow users from v1 to login with keycard (#20558) --- resources/images/ui2/nfc-prompt@2x.png | Bin 0 -> 6107 bytes resources/images/ui2/nfc-prompt@3x.png | Bin 0 -> 9053 bytes resources/images/ui2/nfc-success@2x.png | Bin 0 -> 6264 bytes resources/images/ui2/nfc-success@3x.png | Bin 0 -> 9378 bytes src/keycard/keycard.cljs | 313 +++++++++++++-- src/keycard/real_keycard.cljs | 365 ------------------ src/legacy/status_im/utils/keychain/core.cljs | 58 --- src/mocks/js_dependencies.cljs | 7 +- src/native_module/core.cljs | 95 ----- src/quo/components/pin_input/pin/view.cljs | 43 +++ src/quo/components/pin_input/view.cljs | 27 ++ src/quo/core.cljs | 4 + src/status_im/common/bottom_sheet/view.cljs | 8 +- src/status_im/common/keychain/events.cljs | 53 ++- src/status_im/common/resources.cljs | 4 +- .../standard_authentication/events.cljs | 14 +- src/status_im/constants.cljs | 1 + src/status_im/contexts/keycard/effects.cljs | 111 ++++++ src/status_im/contexts/keycard/events.cljs | 57 +++ .../contexts/keycard/login/events.cljs | 87 +++++ .../contexts/keycard/pin/events.cljs | 21 + src/status_im/contexts/keycard/pin/view.cljs | 26 ++ .../contexts/keycard/sheet/events.cljs | 30 ++ .../contexts/keycard/sheet/view.cljs | 36 ++ src/status_im/contexts/keycard/utils.cljs | 45 +++ src/status_im/contexts/preview/quo/main.cljs | 3 + .../preview/quo/pin_input/pin_input.cljs | 27 ++ .../contexts/profile/login/events.cljs | 10 +- .../contexts/profile/profiles/view.cljs | 36 +- src/status_im/db.cljs | 7 +- src/status_im/events.cljs | 5 + src/status_im/navigation/core.cljs | 16 +- src/status_im/navigation/effects.cljs | 63 +-- src/status_im/navigation/events.cljs | 5 - src/status_im/navigation/view.cljs | 12 + src/status_im/subs/keycard.cljs | 38 ++ src/status_im/subs/root.cljs | 4 + src/utils/re_frame.cljs | 2 + 38 files changed, 964 insertions(+), 669 deletions(-) create mode 100644 resources/images/ui2/nfc-prompt@2x.png create mode 100644 resources/images/ui2/nfc-prompt@3x.png create mode 100644 resources/images/ui2/nfc-success@2x.png create mode 100644 resources/images/ui2/nfc-success@3x.png delete mode 100644 src/keycard/real_keycard.cljs delete mode 100644 src/legacy/status_im/utils/keychain/core.cljs create mode 100644 src/quo/components/pin_input/pin/view.cljs create mode 100644 src/quo/components/pin_input/view.cljs create mode 100644 src/status_im/contexts/keycard/effects.cljs create mode 100644 src/status_im/contexts/keycard/events.cljs create mode 100644 src/status_im/contexts/keycard/login/events.cljs create mode 100644 src/status_im/contexts/keycard/pin/events.cljs create mode 100644 src/status_im/contexts/keycard/pin/view.cljs create mode 100644 src/status_im/contexts/keycard/sheet/events.cljs create mode 100644 src/status_im/contexts/keycard/sheet/view.cljs create mode 100644 src/status_im/contexts/keycard/utils.cljs create mode 100644 src/status_im/contexts/preview/quo/pin_input/pin_input.cljs create mode 100644 src/status_im/subs/keycard.cljs diff --git a/resources/images/ui2/nfc-prompt@2x.png b/resources/images/ui2/nfc-prompt@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c2b224cc7fb6775cb64fb0ff1204fdf175ab5005 GIT binary patch literal 6107 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 X|E>N^Tw>jZ0dAX^-@spYdiH++N!Q4> literal 0 HcmV?d00001 diff --git a/resources/images/ui2/nfc-success@2x.png b/resources/images/ui2/nfc-success@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8a24eae441e7c08e0c925d1353933b915b658212 GIT binary patch literal 6264 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%0DCKC9qKIwnzZAzD zxViwT^%DJ%jcX@O-Spfi5!i9s`W4NKpJ-o^*L2kpi?X;`JpZO`!vGEKvX31MdL=w! zpDz8@wp=R7gW59k?D7gG_{-mwSTL@==@K{PuY49Js{2psNh-~D`Q;>idg{77?n;m& zd7h?0Uon{%F=g(z>ZT%qLs=8YdysbXUKE}9g7wI`8EJ2m&mXDsAp$2lRWM7mjFv_Y!J+7X*y9QyCh^#S(I1O1W1 zwfP2D`}q>=!<}+ss?vV<)-nt3=`o`o8Rt%CU|doQ&qp@_)~yx;x$8_7lE1!N<40y} zj4gp*bERc#v?xK=JG}Xwy;uo|J|M=BO>ELlwd6NrUeqXO)-ssEKlk7D8{^ku@=0+; zDEu-54hbwTN#7Z#6}HP4XI$+VUsL=u1aM4!Tub8DL5tROmB7SfN#u{gf-im})dVQ_ z$_o1bzJm=v-)|JEombC7vjJJ}H?ImBJltnym@yNtF(TsGs$vAsHGuon3!tT7a5bUm zOq2d?IJ5l6m_rQtm^(SB%ILB&;^&U;noHV}a6d7Lt3Sv}e_9ay&<*%?wvJyxW*fip zMnm=Q@^?EaoRQr?avX(RU-y}9&IRwj`YdUG z{qe_-c=)fyz)PoKX^Mk<+8fL|l8?t11w%M7i0Jvjn<+hDqeJo!G_iIae;|*GaIkto zV2jKuCoKs|E>WT)NPb-)I`UvJG^BJ;43MC4IngXi&0<}yE|RzxL|Fm=DlZ|D$Ovz4 zp@QoFQ~8qsNF7i_{%(*xfNk*W8hsEmyLYe;NmFj>f1m^esM#!)Ml1|Q2Nxzy%T)z& z4pnA}R!l5LnTKnh#dw5v>VN%)xjDuM2+Pt<{EGT6A-i4W0>Q_!62Jz9ncn`N%OHTwp-(O(!|q~dd|@ab(*whS zfB^2`DQN3h24-E1kE6Esl2FbPswi2TDvVj*p8PWQ0#bkprS*=e12FG#9-#Q+@{Egc z_9n5c_C65dyFpt(=06uG$y~TMmke4J0Mzs~B}^c^NBmgmcT2Zy7M+OvuyP3A!6Ums zRK+h;28X+-hsZ=rQ(lYwx@!{*!7qr13CH*}@ox)0zzcJ7k1Q{s0@5GyG0cy<*I@N@VJ zw-Dr(pMV_%cg8?#4g?4Tu6&4fgJ6%>C6g76uPszE5IfST(NvNK28DG^^jOHmHTkIt^3`$wdyidTw1oTsXJa1VJw^H=AL#)Y$8W|WOL80O1{I$F=hNy8#iXT z*K;yPyldsdNU8MnrBpRdOJ?)yJ!F5FR?C!Lcj`Y-B?!P2-sGKK7L)nAzB z<=Q#qDN#tnic!|ywjt(#YT&89k*Qn%+&CPTYmc}$st_OnB_{`#BCrSO&zmOIiY;TY zY1MAd%VPAi9s~3GX}lM1O4pP${4I-aN+5=cE^6%5H5tS4S7%5!cB5JVuxE!0e*pY&Q`J{8)P`+0={7HRl?_O;pBW%8I4v|%udR6GrPnG@jUG2zIDv1s)e{byE`lrzazC z_+ZGq)U%#%8%uc|UGg|PIk7vf<@EL_wT}5Drs}kU#=pldD0L15Ayqe+rc=80VEyWL zkc9tN3LA-yz_3IZ>A;#`XRX|pQ3MsNAXjr@t}gRl5*;&oIhDx@f)bMvDe43jrnyF+ zL`~3CRBurG!njwuHOuG_2nbdu6zX*(*b%-X@}a0Z(4;6N-7)M#?K5DF#>lb4c%v+C zyyQYO)Od87He<-`a5rs~?B0VQPMt{ytJlkRwf(S%gpYO3%NusaOvQdKo}%1q(Kd*r8{$HMPn zbmeod5%^*1u<>E57w-^lzn@`YzBs?(M_)E2pZ&hQwvVOpxzBao;dfU(I}$JK=Im- zJq3jORdVmJO(z?6qQU*n9xkl2>B*)yEB?ArDwAp*YtTO$DcGv`;eD3n%m=DfTPYY9 z%Oq?C32RU@Fb0RSQ5La&EF+zgGiLUHt%FjyF)xIH44x7RdUc{M?dD3 z%sOO@U&`MUP-?0$l(XdT@CClkl`h5;YSnjn4HG&}Kley^fL7oyP@;Yn}CxK!Rc^Qo6yLmdTNn!aX^6 zfQR-$XxK5cl%vh=+zTlx5ehxwU8rNFSzpfFTE{K1D!4%cM@h2il1rr0(kgBWL4RoRR{0sBP*f#wH@D>MM%eRjdK_8dzs z{BkDxyj5=1+0eOKE~8-dd#}CcI((b{sbS^id0ECbjeY7l(weZbpH(b%2^g)}z5|$W z|5Im#3hWkKY)gs3l9nF=ONND^C~j2T!N>V;_OgFIKb9iCE5BuBVDM~j__d~!)uoQ1 z(Ct*uzMVi~g3_f2|JrsZ5xUzzY;N43D!lcyhwxa3%LW{~A9r4+e&qdk0bKjWM7`tv z80S^~!wUC>(8l#2z3(F3ySK%+VnKea7OjK(&8xP_zMjN0rB_==iEe74V zMm`Q>z9WQycJq^Pk0Ms`VaE^)UR3ENpRp+7Mdq%3JaGSfo!ir)RMCG`dIxM|uQa!!|p`DBc> zPuAU3>{*sb%(aJVbZ-;5>GO$kqeI_jro^){Q+=t@5ZyzdB|kTWhC!R;x6NbQ@f&;l zHKcF^HGjy_Jq2DaB-lKuU0QZexiy!(ElapcrW)<{>teyDjm|O`wo4KE>o>!ouX zVVDZsE6E`E^4)((&S^;bY0HkFHR26S#UVa<$d_jO%+Ui=aap#i@Jl?%FMjB-O6&|-|cV^iwHGMsXbB~o&3 z{1=)_zCi9;s#Fvixd{wel!|f};Z5F=4z{ll(j4J&NSWvP($?PIE_6z;}Dv4`4#>QQrw~_z0&a``gp4p4dFtWnxU~A>tZIbaIm-A=B47;y(h( z{s|xO6sTHYPydcO58JX|#3_zSf6xapKw0Xclpuo4v~t=rhrzfIo$kAk-$#DN`nSNE}4<6Dt|y>rb`B=viLUAH33|%cOT~`60*u177Y> zL7)lkEnL$vC~NT5k#G4b2;$3nah`vb^_!Cyw)(6GJBB=i=$%ZdSJiwN<%30`|H0xK zVWHu>(H$c13@k$=O*7acLn6wN$0JawqJ~(UHjT$!)3OT3gy>BNy3(87A8Luy|Jzc9 zljxT-=#yJ~7-qGBFaxt<(eW(%R?&2ZI^sLgOG_G)vCy+AUbBq@Cec5>5{9`LBgFeK ze%_>u79D z9N9JODmVOFrqHJC5rVSb^2ZEyy-%^A5vo*jT;@U{LjhFJTuF_o7J-R=Y345!h6WV- zDr#Pkb?QmV8BeVGo2GOJmSVrGE|3{E#f?viX>80BUn}v#N#VT3yx*z7b?yWEGA7P& zW#o(YqS!muraI)!l=+9-xF+Sz!d>Lr;Z70|5j3J z;ShbL$Ys6ul;^NqkCT3dQCLCX+*5Onx0+YockDy%{3%cLoLmRZgK=^hQg!YEuEncn zQ&*s)?(rD&Jx|^afdacfC!JhQD#6R*=F{fvmZi+g#w7noO^dGl`lPob;?P#wz*3se z0k5y`>6}c88LwrhxefWsO!?#=*Jvu-m%S< z?7Ad}xB=N)3rRNN>pLb>qk5GxCaZoo!TNU)3ytOL6MTfi?oLY*_NkBfH|Oe?{r=Gk z0nsTdE5jQm(dT?a%p! z%TRHs`__M`54B6M}?!b*B0u5Yo^|YI0so0qmZUJ$G=e#77l{- z#O}~~o2y4aQ066kNMBIN7GL7I(l|g(zX9|kCfNZeiwi=140|-0Jx>hPLo`e!a;-+s9fiRTdZCnS|Y@Jv)DFNepn-Z!w$HMx1D_Xcw$H(8TKSiM4 z1+VVH5?RTsyXR*+6^h$5J*FjaF%jrASr#q@D8H4Ecs|Oe%UBz*; zm%TX!x9r>23;x0Y72fUZb_%d-x9YI)f~0jhpKM?%sjeSYJN?#v2PrDwiFFKRM=Bgh z?bj0zY4A8RJpMi$l36(_mSSAHL<~d91xxPq#%vfX-N&+pX72IT zU+YP$WV}ye7CtM$yf1rJ{mdA!1`QA&+i;C>{$2OeMn?u82}W_C0?)ThVH<7y9I3Lz zod&w&@9@!TG7ByJ+AVxwQQ=;_V5;JSVX^ad=g$(Zj3dF^-109Ceiyij=NQ1*#{^@_ zwppIyVM0oC*#jk<4$1G#n1Gq-hYLUDv=nwHZ)fKJ%)}%>S{@*Qbz$8B_b~a0BXTyx#_+x-A zlo~r^68r*jZA9#?o)oT9(xkxLaNh#8-sr80K#4qp%{i5$ zutDJ$714d>Q~L6#2KlsO&MpsM4XG=f~Um01Bnt2s~35Azay zZT1xYxeUZa5lqEI``m2Z*FIf1EgYrz=uzDJ^i<%`eTAB0DeVV`ZRQN0beqtdk2dNm zWjsDrXz_K|!u7q*Ei?QF@sU?P`=EK@)2{I_UW#uS0Fc&@u-Qe>fHjBXR-gB@M}phQ zmE3a@;hryErUMsW2|?T`y*2$tcm)&dLUr3N(<~sGYZJO3Jd1+kfZDa+DZ^ zR)z$3hxMlrAGA8I548u~B326opyqFM+}?o+s6LK_Q?_sHU82V~-L1I)-DmVnX3yDd zz}xWHSNa?J_AW~YV6WT@f~N-_?V9>bZWxt82tIrS_@WD+uIft5VO#rpT{!sj5rVD{ z_wW4(6y@NV5sP#P5EBG-LzIbiXigD7=&va)Hx2?|KYA`X9IW|jI-?1K(7yK)BWI)2 z)W610mt5Y{Cg(NZPF?)aTI~D1F=go|94I{s9g0o(#3JI)y02Z{?n$Xu?6qvK_~^jD z27Z8r=oU?{PTXMQ;RSmr&mz2PtpKEU0F^8Pf^NTl_VYq8r?6*Z13ok7N$HHd>h%Ei zhHwroi{axHCt~|{a6%(vO_DgZ7|Q&kg39H6(Iv@cj!zbaoJucz7=A>ewA5?zNTKCV zWwKcbpj0Z%pVNBuZ29Fk4mPpcaPWL8&18`xxX2440qzx{skZT_`kaPl$7@%^?ggLP z&|okbIMjx5q&6oiKPH{DZe_WKr@UdZ@{T4>O-U?K;@rbNp|C z#I~@#bT%AJ3*yyuW)leLJ!hPB>CY3so`I(W2eYjesV6Z-pDe-Q63D$Hv!9+e?x8St z5vufasFoFMy3}u}V~qsszn->n{GV^7%oGs!>Vd!KJpY^=4aAkKNx6j^osZt(fAzAP zi0_p#+eXlcnb_UsmiHt5y)hnAAa~uSh;2zMzg1NxAKV4IWT)%!#yB=16A89i^5=fq zyixqJ;p;s}2MhrELIUv5$mV(;!oPYWM7N6qxl6M<+Exk{gDqr&L93ARE!CYFtGf`H zBOv+-Nu1%V1^w>jjomNu_6&jBDrWBS89}r7aM?hTxZ4qsa<)-)J39Y* zRi#|?9)hAKME4zX1iJcZWN5TGgVC&}f=a#YcSw)~E>3vIE2dIC(@pT>OXcQi&*s@r z8&EAgS9%O!FUc!L4ywF;_(m7Ra34_phng4U`o#^+OaL~O;R``4&h&FlsJ?<7p~O}- zW}sL3ybrfl``@%qH(h7$!XM30$* zb?vY8HPdH0RN8TDqw~}dA9UNpWd&5&FBtSo7;q?mE zd!}^fal;<`a(2uN^oBT>;x0k5 zclI*MQ`#-3KO2E zJ}YbT>k(pZD?3u_Hdkx;Ov%=A_`e{-H$P|qX{AE$dgX=Fch}^V%C^Hrhbp75&%P@k zfL^Y* zdCx}giske>+H7nldSFKq{pc9HWMmf@Ijd$tyw1Cv&@`!Krj`-JyO}U9@g8Oq(AcuU z&V_0?Pp2WjfEm>4s)=a3A*WPp#*G3^Dp7Rptc7xduzTVrm^nfTZxwXx5r;h>qlN&It@5zUNXC1+_(^OEe^ z0PzA5;;<0CZBRkbYa^iK^{)nINbVOmu77ezsWZonyfGTWhzd|5tWt6u1VEi1`{O5; zRe5(&3_CZB)tJTT-fy4EhfCzOgleaC)j_4u9Bz%bC{X>y&HX+4{KoVD8KQt~EaasE46c ndi0MJt?}qTX@9J)a>YO9X9zIWeSLUWiU79O4puk|MEd^$YigBG literal 0 HcmV?d00001 diff --git a/src/keycard/keycard.cljs b/src/keycard/keycard.cljs index 037e721e63..7ea3692fe5 100644 --- a/src/keycard/keycard.cljs +++ b/src/keycard/keycard.cljs @@ -1,38 +1,277 @@ -(ns keycard.keycard) +(ns keycard.keycard + (:require + ["react-native" :as rn] + ["react-native-status-keycard" :default status-keycard] + [react-native.platform :as platform] + [taoensso.timbre :as log] + [utils.address :as address])) -(defprotocol Keycard - (start-nfc [this args]) - (stop-nfc [this args]) - (set-nfc-message [this args]) - (check-nfc-support [this args]) - (check-nfc-enabled [this args]) - (open-nfc-settings [this]) - (register-card-events [this args]) - (set-pairings [this args]) - (on-card-disconnected [this callback]) - (on-card-connected [this callback]) - (remove-event-listener [this event]) - (remove-event-listeners [this]) - (get-application-info [this args]) - (factory-reset [this args]) - (install-applet [this args]) - (install-cash-applet [this args]) - (init-card [this args]) - (install-applet-and-init-card [this args]) - (pair [this args]) - (generate-and-load-key [this args]) - (unblock-pin [this args]) - (verify-pin [this args]) - (change-pin [this args]) - (change-puk [this args]) - (change-pairing [this args]) - (unpair [this args]) - (delete [this args]) - (remove-key [this args]) - (remove-key-with-unpair [this args]) - (export-key [this args]) - (unpair-and-delete [this args]) - (import-keys [this args]) - (get-keys [this args]) - (sign [this args]) - (sign-typed-data [this args])) +(defonce event-emitter + (if platform/ios? + (new (.-NativeEventEmitter rn) status-keycard) + (.-DeviceEventEmitter rn))) + +(defn start-nfc + [{:keys [on-success on-failure prompt-message]}] + (log/debug "start-nfc") + (.. status-keycard + (startNFC (str prompt-message)) + (then on-success) + (catch on-failure))) + +(defn stop-nfc + [{:keys [on-success on-failure error-message]}] + (log/debug "stop-nfc") + (.. status-keycard + (stopNFC (str error-message)) + (then on-success) + (catch on-failure))) + +(defn set-nfc-message + [{:keys [on-success on-failure status-message]}] + (log/debug "set-nfc-message") + (.. status-keycard + (setNFCMessage (str status-message)) + (then on-success) + (catch on-failure))) + +(defn check-nfc-support + [{:keys [on-success]}] + (.. status-keycard + nfcIsSupported + (then on-success))) + +(defn check-nfc-enabled + [{:keys [on-success]}] + (.. status-keycard + nfcIsEnabled + (then on-success))) + +(defn open-nfc-settings + [] + (.openNfcSettings status-keycard)) + +(defn remove-event-listeners + [] + (doseq [event ["keyCardOnConnected" "keyCardOnDisconnected" "keyCardOnNFCUserCancelled" + "keyCardOnNFCTimeout"]] + (.removeAllListeners ^js event-emitter event))) + +(defn remove-event-listener + [^js event] + (when event + (.remove event))) + +(defn on-card-connected + [callback] + (.addListener ^js event-emitter "keyCardOnConnected" callback)) + +(defn on-card-disconnected + [callback] + (.addListener ^js event-emitter "keyCardOnDisconnected" callback)) + +(defn on-nfc-user-cancelled + [callback] + (.addListener ^js event-emitter "keyCardOnNFCUserCancelled" callback)) + +(defn on-nfc-timeout + [callback] + (.addListener ^js event-emitter "keyCardOnNFCTimeout" callback)) + +(defn on-nfc-enabled + [callback] + (.addListener ^js event-emitter "keyCardOnNFCEnabled" callback)) + +(defn on-nfc-disabled + [callback] + (.addListener ^js event-emitter "keyCardOnNFCDisabled" callback)) + +(defn set-pairings + [pairings] + (.. status-keycard (setPairings (clj->js (or pairings {}))))) + +(defn get-application-info + [{:keys [on-success on-failure]}] + (.. status-keycard + (getApplicationInfo) + (then (fn [response] + (let [info (-> response + (js->clj :keywordize-keys true) + (update :key-uid address/normalized-hex))] + (on-success info)))) + (catch on-failure))) + +(defn factory-reset + [{:keys [on-success on-failure]}] + (.. status-keycard + (factoryReset) + (then (fn [response] + (let [info (-> response + (js->clj :keywordize-keys true) + (update :key-uid address/normalized-hex))] + (on-success info)))) + (catch on-failure))) + +(defn install-applet + [{:keys [on-success on-failure]}] + (.. status-keycard + installApplet + (then on-success) + (catch on-failure))) + +(defn install-cash-applet + [{:keys [on-success on-failure]}] + (.. status-keycard + installCashApplet + (then on-success) + (catch on-failure))) + +(defn init-card + [{:keys [pin on-success on-failure]}] + (.. status-keycard + (init pin) + (then on-success) + (catch on-failure))) + +(defn install-applet-and-init-card + [{:keys [pin on-success on-failure]}] + (.. status-keycard + (installAppletAndInitCard pin) + (then on-success) + (catch on-failure))) + +(defn pair + [{:keys [password on-success on-failure]}] + (when password + (.. status-keycard + (pair password) + (then on-success) + (catch on-failure)))) + +(defn generate-and-load-key + [{:keys [mnemonic pin on-success on-failure]}] + (.. status-keycard + (generateAndLoadKey mnemonic pin) + (then on-success) + (catch on-failure))) + +(defn unblock-pin + [{:keys [puk new-pin on-success on-failure]}] + (when (and new-pin puk) + (.. status-keycard + (unblockPin puk new-pin) + (then on-success) + (catch on-failure)))) + +(defn verify-pin + [{:keys [pin on-success on-failure]}] + (when (not-empty pin) + (.. status-keycard + (verifyPin pin) + (then on-success) + (catch on-failure)))) + +(defn change-pin + [{:keys [current-pin new-pin on-success on-failure]}] + (when (and current-pin new-pin) + (.. status-keycard + (changePin current-pin new-pin) + (then on-success) + (catch on-failure)))) + +(defn change-puk + [{:keys [pin puk on-success on-failure]}] + (when (and pin puk) + (.. status-keycard + (changePUK pin puk) + (then on-success) + (catch on-failure)))) + +(defn change-pairing + [{:keys [pin pairing on-success on-failure]}] + (when (and pin pairing) + (.. status-keycard + (changePairingPassword pin pairing) + (then on-success) + (catch on-failure)))) + +(defn unpair + [{:keys [pin on-success on-failure]}] + (when pin + (.. status-keycard + (unpair pin) + (then on-success) + (catch on-failure)))) + +(defn delete + [{:keys [on-success on-failure]}] + (.. status-keycard + (delete) + (then on-success) + (catch on-failure))) + +(defn remove-key + [{:keys [pin on-success on-failure]}] + (.. status-keycard + (removeKey pin) + (then on-success) + (catch on-failure))) + +(defn remove-key-with-unpair + [{:keys [pin on-success on-failure]}] + (.. status-keycard + (removeKeyWithUnpair pin) + (then on-success) + (catch on-failure))) + +(defn export-key + [{:keys [pin path on-success on-failure]}] + (.. status-keycard + (exportKeyWithPath pin path) + (then on-success) + (catch on-failure))) + +(defn unpair-and-delete + [{:keys [pin on-success on-failure]}] + (when (not-empty pin) + (.. status-keycard + (unpairAndDelete pin) + (then on-success) + (catch on-failure)))) + +(defn import-keys + [{:keys [pin on-success on-failure]}] + (when (not-empty pin) + (.. status-keycard + (importKeys pin) + (then on-success) + (catch on-failure)))) + +(defn get-keys + [{:keys [pin on-success on-failure]}] + (when (not-empty pin) + (.. status-keycard + (getKeys pin) + (then on-success) + (catch on-failure)))) + +(defn sign + [{pin :pin path :path card-hash :hash on-success :on-success on-failure :on-failure}] + (when (and pin card-hash) + (if path + (.. status-keycard + (signWithPath pin path card-hash) + (then on-success) + (catch on-failure)) + (.. status-keycard + (sign pin card-hash) + (then on-success) + (catch on-failure))))) + +(defn sign-typed-data + [{card-hash :hash on-success :on-success on-failure :on-failure}] + (when card-hash + (.. status-keycard + (signPinless card-hash) + (then on-success) + (catch on-failure)))) diff --git a/src/keycard/real_keycard.cljs b/src/keycard/real_keycard.cljs deleted file mode 100644 index 7250479e69..0000000000 --- a/src/keycard/real_keycard.cljs +++ /dev/null @@ -1,365 +0,0 @@ -(ns keycard.real-keycard - (:require - ["react-native" :as rn] - ["react-native-status-keycard" :default status-keycard] - [keycard.keycard :as keycard] - [react-native.platform :as platform] - [taoensso.timbre :as log] - [utils.address :as address])) - -(defonce event-emitter - (if platform/ios? - (new (.-NativeEventEmitter rn) status-keycard) - (.-DeviceEventEmitter rn))) - -(defonce active-listeners (atom [])) - -(defn start-nfc - [{:keys [on-success on-failure prompt-message]}] - (log/debug "start-nfc") - (.. status-keycard - (startNFC (str prompt-message)) - (then on-success) - (catch on-failure))) - -(defn stop-nfc - [{:keys [on-success on-failure error-message]}] - (log/debug "stop-nfc") - (.. status-keycard - (stopNFC (str error-message)) - (then on-success) - (catch on-failure))) - -(defn set-nfc-message - [{:keys [on-success on-failure status-message]}] - (log/debug "set-nfc-message") - (.. status-keycard - (setNFCMessage (str status-message)) - (then on-success) - (catch on-failure))) - -(defn check-nfc-support - [{:keys [on-success]}] - (.. status-keycard - nfcIsSupported - (then on-success))) - -(defn check-nfc-enabled - [{:keys [on-success]}] - (.. status-keycard - nfcIsEnabled - (then on-success))) - -(defn open-nfc-settings - [] - (.openNfcSettings status-keycard)) - -(defn remove-event-listeners - [] - (doseq [event ["keyCardOnConnected" "keyCardOnDisconnected" "keyCardOnNFCUserCancelled" - "keyCardOnNFCTimeout"]] - (.removeAllListeners ^js event-emitter event))) - -(defn remove-event-listener - [^js event] - (.remove event)) - -(defn on-card-connected - [callback] - (.addListener ^js event-emitter "keyCardOnConnected" callback)) - -(defn on-card-disconnected - [callback] - (.addListener ^js event-emitter "keyCardOnDisconnected" callback)) - -(defn on-nfc-user-cancelled - [callback] - (.addListener ^js event-emitter "keyCardOnNFCUserCancelled" callback)) - -(defn on-nfc-timeout - [callback] - (.addListener ^js event-emitter "keyCardOnNFCTimeout" callback)) - -(defn on-nfc-enabled - [callback] - (.addListener ^js event-emitter "keyCardOnNFCEnabled" callback)) - -(defn on-nfc-disabled - [callback] - (.addListener ^js event-emitter "keyCardOnNFCDisabled" callback)) - -(defn set-pairings - [{:keys [pairings]}] - (.. status-keycard (setPairings (clj->js (or pairings {}))))) - -(defn register-card-events - [args] - (doseq [listener @active-listeners] - (remove-event-listener listener)) - (reset! active-listeners - [(on-card-connected (:on-card-connected args)) - (on-card-disconnected (:on-card-disconnected args)) - (on-nfc-user-cancelled (:on-nfc-user-cancelled args)) - (on-nfc-timeout (:on-nfc-timeout args)) - (on-nfc-enabled (:on-nfc-enabled args)) - (on-nfc-disabled (:on-nfc-disabled args))])) - -(defn get-application-info - [{:keys [on-success on-failure]}] - - (.. status-keycard - (getApplicationInfo) - (then (fn [response] - (let [info (-> response - (js->clj :keywordize-keys true) - (update :key-uid address/normalized-hex))] - (on-success info)))) - (catch on-failure))) - -(defn factory-reset - [{:keys [on-success on-failure]}] - (.. status-keycard - (factoryReset) - (then (fn [response] - (let [info (-> response - (js->clj :keywordize-keys true) - (update :key-uid address/normalized-hex))] - (on-success info)))) - (catch on-failure))) - -(defn install-applet - [{:keys [on-success on-failure]}] - (.. status-keycard - installApplet - (then on-success) - (catch on-failure))) - -(defn install-cash-applet - [{:keys [on-success on-failure]}] - (.. status-keycard - installCashApplet - (then on-success) - (catch on-failure))) - -(defn init-card - [{:keys [pin on-success on-failure]}] - (.. status-keycard - (init pin) - (then on-success) - (catch on-failure))) - -(defn install-applet-and-init-card - [{:keys [pin on-success on-failure]}] - (.. status-keycard - (installAppletAndInitCard pin) - (then on-success) - (catch on-failure))) - -(defn pair - [{:keys [password on-success on-failure]}] - (when password - (.. status-keycard - (pair password) - (then on-success) - (catch on-failure)))) - -(defn generate-and-load-key - [{:keys [mnemonic pin on-success on-failure]}] - (.. status-keycard - (generateAndLoadKey mnemonic pin) - (then on-success) - (catch on-failure))) - -(defn unblock-pin - [{:keys [puk new-pin on-success on-failure]}] - (when (and new-pin puk) - (.. status-keycard - (unblockPin puk new-pin) - (then on-success) - (catch on-failure)))) - -(defn verify-pin - [{:keys [pin on-success on-failure]}] - (when (not-empty pin) - (.. status-keycard - (verifyPin pin) - (then on-success) - (catch on-failure)))) - -(defn change-pin - [{:keys [current-pin new-pin on-success on-failure]}] - (when (and current-pin new-pin) - (.. status-keycard - (changePin current-pin new-pin) - (then on-success) - (catch on-failure)))) - -(defn change-puk - [{:keys [pin puk on-success on-failure]}] - (when (and pin puk) - (.. status-keycard - (changePUK pin puk) - (then on-success) - (catch on-failure)))) - -(defn change-pairing - [{:keys [pin pairing on-success on-failure]}] - (when (and pin pairing) - (.. status-keycard - (changePairingPassword pin pairing) - (then on-success) - (catch on-failure)))) - -(defn unpair - [{:keys [pin on-success on-failure]}] - (when pin - (.. status-keycard - (unpair pin) - (then on-success) - (catch on-failure)))) - -(defn delete - [{:keys [on-success on-failure]}] - (.. status-keycard - (delete) - (then on-success) - (catch on-failure))) - -(defn remove-key - [{:keys [pin on-success on-failure]}] - (.. status-keycard - (removeKey pin) - (then on-success) - (catch on-failure))) - -(defn remove-key-with-unpair - [{:keys [pin on-success on-failure]}] - (.. status-keycard - (removeKeyWithUnpair pin) - (then on-success) - (catch on-failure))) - -(defn export-key - [{:keys [pin path on-success on-failure]}] - (.. status-keycard - (exportKeyWithPath pin path) - (then on-success) - (catch on-failure))) - -(defn unpair-and-delete - [{:keys [pin on-success on-failure]}] - (when (not-empty pin) - (.. status-keycard - (unpairAndDelete pin) - (then on-success) - (catch on-failure)))) - -(defn import-keys - [{:keys [pin on-success on-failure]}] - (when (not-empty pin) - (.. status-keycard - (importKeys pin) - (then on-success) - (catch on-failure)))) - -(defn get-keys - [{:keys [pin on-success on-failure]}] - (when (not-empty pin) - (.. status-keycard - (getKeys pin) - (then on-success) - (catch on-failure)))) - -(defn sign - [{pin :pin path :path card-hash :hash on-success :on-success on-failure :on-failure}] - (when (and pin card-hash) - (if path - (.. status-keycard - (signWithPath pin path card-hash) - (then on-success) - (catch on-failure)) - (.. status-keycard - (sign pin card-hash) - (then on-success) - (catch on-failure))))) - -(defn sign-typed-data - [{card-hash :hash on-success :on-success on-failure :on-failure}] - (when card-hash - (.. status-keycard - (signPinless card-hash) - (then on-success) - (catch on-failure)))) - -(defrecord RealKeycard [] - keycard/Keycard - (keycard/start-nfc [_this args] - (start-nfc args)) - (keycard/stop-nfc [_this args] - (stop-nfc args)) - (keycard/set-nfc-message [_this args] - (set-nfc-message args)) - (keycard/check-nfc-support [_this args] - (check-nfc-support args)) - (keycard/check-nfc-enabled [_this args] - (check-nfc-enabled args)) - (keycard/open-nfc-settings [_this] - (open-nfc-settings)) - (keycard/register-card-events [_this args] - (register-card-events args)) - (keycard/on-card-connected [_this callback] - (on-card-connected callback)) - (keycard/on-card-disconnected [_this callback] - (on-card-disconnected callback)) - (keycard/remove-event-listener [_this event] - (remove-event-listener event)) - (keycard/remove-event-listeners [_this] - (remove-event-listeners)) - (keycard/set-pairings [_this args] - (set-pairings args)) - (keycard/get-application-info [_this args] - (get-application-info args)) - (keycard/factory-reset [_this args] - (factory-reset args)) - (keycard/install-applet [_this args] - (install-applet args)) - (keycard/install-cash-applet [_this args] - (install-cash-applet args)) - (keycard/init-card [_this args] - (init-card args)) - (keycard/install-applet-and-init-card [_this args] - (install-applet-and-init-card args)) - (keycard/pair [_this args] - (pair args)) - (keycard/generate-and-load-key [_this args] - (generate-and-load-key args)) - (keycard/unblock-pin [_this args] - (unblock-pin args)) - (keycard/verify-pin [_this args] - (verify-pin args)) - (keycard/change-pin [_this args] - (change-pin args)) - (keycard/change-puk [_this args] - (change-puk args)) - (keycard/change-pairing [_this args] - (change-pairing args)) - (keycard/unpair [_this args] - (unpair args)) - (keycard/delete [_this args] - (delete args)) - (keycard/remove-key [_this args] - (remove-key args)) - (keycard/remove-key-with-unpair [_this args] - (remove-key-with-unpair args)) - (keycard/export-key [_this args] - (export-key args)) - (keycard/unpair-and-delete [_this args] - (unpair-and-delete args)) - (keycard/import-keys [_this args] - (import-keys args)) - (keycard/get-keys [_this args] - (get-keys args)) - (keycard/sign [_this args] - (sign args)) - (keycard/sign-typed-data [_this args] - (sign-typed-data args))) diff --git a/src/legacy/status_im/utils/keychain/core.cljs b/src/legacy/status_im/utils/keychain/core.cljs deleted file mode 100644 index 5d95de3c27..0000000000 --- a/src/legacy/status_im/utils/keychain/core.cljs +++ /dev/null @@ -1,58 +0,0 @@ -(ns legacy.status-im.utils.keychain.core - (:require - [oops.core :as oops] - [re-frame.core :as re-frame] - [react-native.keychain :as keychain] - [taoensso.timbre :as log] - [utils.re-frame :as rf])) - -(defn- whisper-key-name - [address] - (str address "-whisper")) - -(re-frame/reg-fx - :keychain/get-keycard-keys - (fn [[key-uid callback]] - (keychain/get-credentials - key-uid - (fn [encryption-key-data] - (if encryption-key-data - (keychain/get-credentials - (whisper-key-name key-uid) - (fn [whisper-key-data] - (if whisper-key-data - (callback [(oops/oget encryption-key-data "password") - (oops/oget whisper-key-data "password")]) - (callback nil)))) - (callback nil)))))) - -(re-frame/reg-fx - :keychain/save-keycard-keys - (fn [[key-uid encryption-public-key whisper-private-key]] - (keychain/save-credentials - key-uid - key-uid - encryption-public-key - #(when-not % - (log/error - (str "Error while saving encryption-public-key")))) - (keychain/save-credentials - (whisper-key-name key-uid) - key-uid - whisper-private-key - #(when-not % - (log/error - (str "Error while saving whisper-private-key")))))) - -(rf/defn get-keycard-keys - [_ key-uid] - {:keychain/get-keycard-keys - [key-uid - #(re-frame/dispatch - [:multiaccounts.login.callback/get-keycard-keys-success key-uid %])]}) - -(rf/defn save-keycard-keys - [_ key-uid encryption-public-key whisper-private-key] - {:keychain/save-keycard-keys [key-uid - encryption-public-key - whisper-private-key]}) diff --git a/src/mocks/js_dependencies.cljs b/src/mocks/js_dependencies.cljs index fb0a2ddf9a..a1b8aee2c0 100644 --- a/src/mocks/js_dependencies.cljs +++ b/src/mocks/js_dependencies.cljs @@ -110,8 +110,11 @@ (def status-keycard #js {:default #js - {:nfcIsSupported (fn [] #js {:then identity}) - :nfcIsEnabled (fn [] #js {:then identity})}}) + {:nfcIsSupported (fn [] #js {:then identity}) + :nfcIsEnabled (fn [] #js {:then identity}) + :getApplicationInfo (fn [] #js {:then identity}) + :getKeys (fn [] #js {:then identity}) + :setPairings (fn [] #js {:then identity})}}) (def snoopy #js {:default #js {}}) (def snoopy-filter #js {:default #js {}}) diff --git a/src/native_module/core.cljs b/src/native_module/core.cljs index 4f5edc4893..6587d2efc9 100644 --- a/src/native_module/core.cljs +++ b/src/native_module/core.cljs @@ -87,31 +87,6 @@ config #(callback (types/json->clj %)))) -(defn save-multiaccount-and-login-with-keycard - "NOTE: chat-key is a whisper private key sent from keycard" - [key-uid multiaccount-data password settings config accounts-data chat-key] - (log/debug "[native-module] save-account-and-login-with-keycard") - (init-keystore - key-uid - #(.saveAccountAndLoginWithKeycard - ^js (account-manager) - multiaccount-data - password - settings - config - accounts-data - chat-key))) - -(defn login-with-config - "NOTE: beware, the password has to be sha3 hashed" - [key-uid account-data hashed-password config] - (log/debug "[native-module] loginWithConfig") - (clear-web-data) - (let [config (if config (types/clj->json config) "")] - (init-keystore - key-uid - #(.loginWithConfig ^js (account-manager) account-data hashed-password config)))) - (defn login-account "NOTE: beware, the password has to be sha3 hashed" [{:keys [keyUid] :as request}] @@ -153,19 +128,6 @@ (clear-web-data) (.logout ^js (account-manager))) -(defn multiaccount-load-account - "NOTE: beware, the password has to be sha3 hashed - - this function is used after storing an account when you still want to - derive accounts from it, because saving an account flushes the loaded keys - from memory" - [address hashed-password callback] - (log/debug "[native-module] multiaccount-load-account") - (.multiAccountLoadAccount ^js (account-manager) - (types/clj->json {:address address - :password hashed-password}) - callback)) - (defn multiaccount-derive-addresses "NOTE: this should be named derive-accounts this only derive addresses, they still need to be stored @@ -179,38 +141,6 @@ :paths paths}) callback))) -(defn multiaccount-store-account - "NOTE: beware, the password has to be sha3 hashed - - this stores the account and flush keys in memory so - in order to also store derived accounts like initial wallet - and chat accounts, you need to load the account again with - `multiaccount-load-account` before using `multiaccount-store-derived` - and the id of the account stored will have changed" - [account-id key-uid hashed-password callback] - (log/debug "[native-module] multiaccount-store-account") - (when (status) - (init-keystore - key-uid - #(.multiAccountStoreAccount ^js (account-manager) - (types/clj->json {:accountID account-id - :password hashed-password}) - callback)))) - -(defn multiaccount-store-derived - "NOTE: beware, the password has to be sha3 hashed" - [account-id key-uid paths hashed-password callback] - (log/debug "[native-module] multiaccount-store-derived" - "account-id" - account-id) - (init-keystore - key-uid - #(.multiAccountStoreDerived ^js (account-manager) - (types/clj->json {:accountID account-id - :paths paths - :password hashed-password}) - callback))) - (defn multiaccount-generate-and-derive-addresses "used to generate multiple multiaccounts for onboarding NOTE: nothing is saved so you will need to use @@ -234,37 +164,12 @@ :Bip39Passphrase password}) callback)) -(defn multiaccount-import-private-key - [private-key callback] - (log/debug "[native-module] multiaccount-import-private-key") - (.multiAccountImportPrivateKey ^js (account-manager) - (types/clj->json {:privateKey private-key}) - callback)) - (defn verify "NOTE: beware, the password has to be sha3 hashed" [address hashed-password callback] (log/debug "[native-module] verify") (.verify ^js (account-manager) address hashed-password callback)) -(defn verify-database-password - "NOTE: beware, the password has to be sha3 hashed" - [key-uid hashed-password callback] - (log/debug "[native-module] verify-database-password") - (.verifyDatabasePassword ^js (account-manager) key-uid hashed-password callback)) - -(defn login-with-keycard - [{:keys [key-uid multiaccount-data password chat-key node-config]}] - (log/debug "[native-module] login-with-keycard") - (clear-web-data) - (init-keystore - key-uid - #(.loginWithKeycard ^js (account-manager) - multiaccount-data - password - chat-key - (types/clj->json node-config)))) - (defn set-soft-input-mode [mode] (log/debug "[native-module] set-soft-input-mode") diff --git a/src/quo/components/pin_input/pin/view.cljs b/src/quo/components/pin_input/pin/view.cljs new file mode 100644 index 0000000000..47c342a207 --- /dev/null +++ b/src/quo/components/pin_input/pin/view.cljs @@ -0,0 +1,43 @@ +(ns quo.components.pin-input.pin.view + (:require [quo.foundations.colors :as colors] + quo.theme + [react-native.core :as rn])) + +(defn view + [{:keys [theme state blur?]}] + (let [app-theme (quo.theme/use-theme) + theme (or theme app-theme)] + [rn/view {:style {:width 36 :height 36 :align-items :center :justify-content :center}} + (case state + :active + [rn/view + {:style {:width 16 + :height 16 + :border-radius 8 + :background-color (if blur? + colors/white-opa-20 + (colors/theme-colors colors/neutral-40 colors/neutral-50 theme))}}] + :filled + [rn/view + {:style {:width 16 + :height 16 + :border-radius 8 + :background-color (if blur? + colors/white + (colors/theme-colors colors/neutral-100 colors/white theme))}}] + :error + [rn/view + {:style {:width 16 + :height 16 + :border-radius 8 + :background-color (if blur? + colors/danger-60 + (colors/theme-colors colors/danger-50 colors/danger-60 theme))}}] + [rn/view + {:style + {:width 12 + :height 12 + :border-radius 6 + :background-color (if blur? + colors/white-opa-20 + (colors/theme-colors colors/neutral-40 colors/neutral-50 theme))}}])])) diff --git a/src/quo/components/pin_input/view.cljs b/src/quo/components/pin_input/view.cljs new file mode 100644 index 0000000000..0ae264007f --- /dev/null +++ b/src/quo/components/pin_input/view.cljs @@ -0,0 +1,27 @@ +(ns quo.components.pin-input.view + (:require [quo.components.markdown.text :as text] + [quo.components.pin-input.pin.view :as pin] + [quo.foundations.colors :as colors] + quo.theme + [react-native.core :as rn])) + +(defn view + [{:keys [number-of-pins number-of-filled-pins error? info] + :or {number-of-pins 6 number-of-filled-pins 0}}] + (let [theme (quo.theme/use-theme)] + [rn/view {:style {:align-items :center}} + [rn/view {:style {:flex-direction :row}} + (for [i (range 1 (inc number-of-pins))] + ^{:key i} + [pin/view + {:state (cond + error? :error + (<= i number-of-filled-pins) :filled + (= i (inc number-of-filled-pins)) :active)}])] + (when info + [text/text + {:style {:color (if error? + (colors/theme-colors colors/danger-50 colors/danger-60 theme) + (colors/theme-colors colors/neutral-50 colors/neutral-40 theme))} + :size :paragraph-2} + info])])) diff --git a/src/quo/core.cljs b/src/quo/core.cljs index e04a5cca98..9ea0c9a71b 100644 --- a/src/quo/core.cljs +++ b/src/quo/core.cljs @@ -120,6 +120,7 @@ quo.components.overlay.view quo.components.password.password-tips.view quo.components.password.tips.view + quo.components.pin-input.view quo.components.profile.collectible-list-item.view quo.components.profile.collectible.view quo.components.profile.expanded-collectible.view @@ -320,6 +321,9 @@ (def keyboard-key quo.components.numbered-keyboard.keyboard-key.view/view) (def numbered-keyboard quo.components.numbered-keyboard.numbered-keyboard.view/view) +;;;; PIN input +(def pin-input quo.components.pin-input.view/view) + ;;;; Links (def internal-link-card quo.components.links.internal-link-card.view/view) (def link-preview quo.components.links.link-preview.view/view) diff --git a/src/status_im/common/bottom_sheet/view.cljs b/src/status_im/common/bottom_sheet/view.cljs index a941d9bad0..c4d1b1abee 100644 --- a/src/status_im/common/bottom_sheet/view.cljs +++ b/src/status_im/common/bottom_sheet/view.cljs @@ -63,8 +63,10 @@ (defn view [{:keys [hide? insets]} {:keys [content selected-item padding-bottom-override border-radius on-close shell? - gradient-cover? customization-color hide-handle? blur-radius] - :or {border-radius 12}}] + gradient-cover? customization-color hide-handle? blur-radius + hide-on-background-press?] + :or {border-radius 12 + hide-on-background-press? true}}] (let [theme (quo.theme/use-theme) {window-height :height} (rn/get-window) [sheet-height set-sheet-height] (rn/use-state 0) @@ -119,7 +121,7 @@ :on-layout handle-layout-height} ;; backdrop [rn/pressable - {:on-press #(rf/dispatch [:hide-bottom-sheet]) + {:on-press #(when hide-on-background-press? (rf/dispatch [:hide-bottom-sheet])) :style {:flex 1}} [reanimated/view {:style (reanimated/apply-animations-to-style diff --git a/src/status_im/common/keychain/events.cljs b/src/status_im/common/keychain/events.cljs index e441f71bbc..3686b96a90 100644 --- a/src/status_im/common/keychain/events.cljs +++ b/src/status_im/common/keychain/events.cljs @@ -144,6 +144,10 @@ (fn [_ [opts]] {:keychain/save-password-and-auth-method opts})) +(defn- whisper-key-name + [address] + (str address "-whisper")) + ;; NOTE: migrating the plaintext password in the keychain ;; with the hashed one. Added due to the sync onboarding ;; flow, where the password arrives already hashed. @@ -151,17 +155,38 @@ :keychain/password-hash-migration (fn [{:keys [key-uid callback] :or {callback identity}}] - (-> (get-password-migration! key-uid identity) - (.then (fn [migrated?] - (if migrated? - (callback) - (-> (get-user-password! key-uid identity) - (.then security/hash-masked-password) - (.then #(save-user-password! key-uid %)) - (.then #(save-password-migration! key-uid)) - (.then callback))))) - (.catch (fn [err] - (log/error "Failed to migrate the keychain password" - {:error err - :key-uid key-uid - :event :keychain/password-hash-migration})))))) + (keychain/get-credentials + (whisper-key-name key-uid) + (fn [whisper-key-data] + (if whisper-key-data + (callback) ;; we don't need to migrate keycard password + (-> (get-password-migration! key-uid identity) + (.then (fn [migrated?] + (if migrated? + (callback) + (-> (get-user-password! key-uid identity) + (.then security/hash-masked-password) + (.then #(save-user-password! key-uid %)) + (.then #(save-password-migration! key-uid)) + (.then callback))))) + (.catch (fn [err] + (log/error "Failed to migrate the keychain password" + {:error err + :key-uid key-uid + :event :keychain/password-hash-migration}))))))))) + +(re-frame/reg-fx + :keychain/get-keycard-keys + (fn [[key-uid callback]] + (keychain/get-credentials + key-uid + (fn [encryption-key-data] + (if encryption-key-data + (keychain/get-credentials + (whisper-key-name key-uid) + (fn [whisper-key-data] + (if whisper-key-data + (callback [(oops/oget encryption-key-data "password") + (oops/oget whisper-key-data "password")]) + (callback nil)))) + (callback nil)))))) diff --git a/src/status_im/common/resources.cljs b/src/status_im/common/resources.cljs index 6d6013f8e6..14eacd856d 100644 --- a/src/status_im/common/resources.cljs +++ b/src/status_im/common/resources.cljs @@ -25,7 +25,9 @@ :invite-friends (js/require "../resources/images/ui2/invite-friends.png") :transaction-progress (js/require "../resources/images/ui2/transaction-progress.png") :welcome-illustration (js/require "../resources/images/ui2/welcome_illustration.png") - :notifications (js/require "../resources/images/ui2/notifications.png")}) + :notifications (js/require "../resources/images/ui2/notifications.png") + :nfc-prompt (js/require "../resources/images/ui2/nfc-prompt.png") + :nfc-success (js/require "../resources/images/ui2/nfc-success.png")}) (def ui-themed {:angry-man diff --git a/src/status_im/common/standard_authentication/events.cljs b/src/status_im/common/standard_authentication/events.cljs index 7c2580a931..f29d139893 100644 --- a/src/status_im/common/standard_authentication/events.cljs +++ b/src/status_im/common/standard_authentication/events.cljs @@ -10,11 +10,14 @@ (defn authorize [{:keys [db]} [args]] - (let [key-uid (get-in db [:profile/profile :key-uid])] + (let [key-uid (get-in db [:profile/profile :key-uid]) + keycard? (get-in db [:profile/profile :keycard-pairing])] {:fx [[:effects.biometric/check-if-available {:key-uid key-uid :on-success #(rf/dispatch [:standard-auth/authorize-with-biometric args]) - :on-fail #(rf/dispatch [:standard-auth/authorize-with-password args])}]]})) + :on-fail (if keycard? + #(rf/dispatch [:standard-auth/authorize-with-keycard args]) + #(rf/dispatch [:standard-auth/authorize-with-password args]))}]]})) (schema/=> authorize events-schema/?authorize) (rf/reg-event-fx :standard-auth/authorize authorize) @@ -45,8 +48,11 @@ (defn on-biometric-success [{:keys [db]} [on-auth-success]] - (let [key-uid (get-in db [:profile/profile :key-uid])] - {:fx [[:keychain/get-user-password [key-uid on-auth-success]] + (let [key-uid (get-in db [:profile/profile :key-uid]) + keycard? (get-in db [:profile/profile :keycard-pairing])] + {:fx [(if keycard? + [:keychain/get-keycard-keys [key-uid on-auth-success]] + [:keychain/get-user-password [key-uid on-auth-success]]) [:dispatch [:standard-auth/set-success true]] [:dispatch [:standard-auth/reset-login-password]]]})) diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index 2b39d36ce9..e31b233f56 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -126,6 +126,7 @@ (def ^:const profile-pictures-visibility-none 3) (def ^:const min-password-length 6) +(def ^:const pincode-length 6) (def ^:const new-password-min-length 10) (def ^:const max-group-chat-participants 20) (def ^:const max-group-chat-name-length 24) diff --git a/src/status_im/contexts/keycard/effects.cljs b/src/status_im/contexts/keycard/effects.cljs new file mode 100644 index 0000000000..c831984ecc --- /dev/null +++ b/src/status_im/contexts/keycard/effects.cljs @@ -0,0 +1,111 @@ +(ns status-im.contexts.keycard.effects + (:require [keycard.keycard :as keycard] + [native-module.core :as native-module] + [react-native.async-storage :as async-storage] + [react-native.platform :as platform] + [status-im.contexts.profile.config :as profile.config] + [taoensso.timbre :as log] + [utils.re-frame :as rf] + [utils.transforms :as transforms])) + +(defonce ^:private active-listeners (atom [])) + +(defn register-card-events + [] + (doseq [listener @active-listeners] + (keycard/remove-event-listener listener)) + (reset! active-listeners + [(keycard/on-card-connected #(rf/dispatch [:keycard/on-card-connected])) + (keycard/on-card-disconnected #(rf/dispatch [:keycard/on-card-disconnected])) + (when platform/ios? + (keycard/on-nfc-user-cancelled #(rf/dispatch [:keycard.ios/on-nfc-user-cancelled]))) + (when platform/ios? + (keycard/on-nfc-timeout #(rf/dispatch [:keycard.ios/on-nfc-timeout]))) + (keycard/on-nfc-enabled #(rf/dispatch [:keycard/on-check-nfc-enabled-success true])) + (keycard/on-nfc-disabled #(rf/dispatch [:keycard/on-check-nfc-enabled-success false]))])) +(rf/reg-fx :effects.keycard/register-card-events register-card-events) + +(defn check-nfc-enabled + [] + (log/debug "[keycard] check-nfc-enabled") + (keycard/check-nfc-enabled + {:on-success + (fn [response] + (log/debug "[keycard response] check-nfc-enabled") + (rf/dispatch [:keycard/on-check-nfc-enabled-success response]))})) +(rf/reg-fx :effects.keycard/check-nfc-enabled check-nfc-enabled) + +(rf/reg-fx + :effects.keycard.ios/start-nfc + (fn [args] + (log/debug "fx start-nfc") + (keycard/start-nfc args))) + +(rf/reg-fx + :effects.keycard.ios/stop-nfc + (fn [args] + (log/debug "fx stop-nfc") + (keycard/stop-nfc args))) + +(defn- error-object->map + [^js object] + {:code (.-code object) + :error (.-message object)}) + +(defn get-application-info + [{:keys [on-success on-failure] :as args}] + (log/debug "[keycard] get-application-info") + (keycard/get-application-info + (assoc + args + :on-success + (fn [response] + (log/debug "[keycard response succ] get-application-info") + (when on-success + (on-success response))) + :on-failure + (fn [response] + (log/error "[keycard response fail] get-application-info") + (when on-failure + (on-failure (error-object->map response))))))) +(rf/reg-fx :effects.keycard/get-application-info get-application-info) + +(defn get-keys + [{:keys [on-success on-failure] :as args}] + (log/debug "[keycard] get-keys") + (keycard/get-keys + (assoc + args + :on-success + (fn [response] + (log/debug "[keycard response succ] get-keys") + (when on-success + (on-success (transforms/js->clj response)))) + :on-failure + (fn [response] + (log/warn "[keycard response fail] get-keys" + (error-object->map response)) + (when on-failure + (on-failure (error-object->map response))))))) +(rf/reg-fx :effects.keycard/get-keys get-keys) + +(defn login + [{:keys [key-uid password whisper-private-key]}] + (native-module/login-account + (assoc (profile.config/login) + :keyUid key-uid + :password password + :keycardWhisperPrivateKey whisper-private-key))) +(rf/reg-fx :effects.keycard/login-with-keycard login) + +(defn retrieve-pairings + [] + (async-storage/get-item + "status-keycard-pairings" + #(rf/dispatch [:keycard/on-retrieve-pairings-success %]))) +(rf/reg-fx :effects.keycard/retrieve-pairings retrieve-pairings) + +(defn set-pairing-to-keycard + [pairings] + (keycard/set-pairings pairings)) +(rf/reg-fx :effects.keycard/set-pairing-to-keycard set-pairing-to-keycard) diff --git a/src/status_im/contexts/keycard/events.cljs b/src/status_im/contexts/keycard/events.cljs new file mode 100644 index 0000000000..bad13fbf65 --- /dev/null +++ b/src/status_im/contexts/keycard/events.cljs @@ -0,0 +1,57 @@ +(ns status-im.contexts.keycard.events + (:require [re-frame.core :as rf] + status-im.contexts.keycard.login.events + status-im.contexts.keycard.pin.events + status-im.contexts.keycard.sheet.events + [taoensso.timbre :as log])) + +(rf/reg-event-fx :keycard/on-check-nfc-enabled-success + (fn [{:keys [db]} [nfc-enabled?]] + {:db (assoc-in db [:keycard :nfc-enabled?] nfc-enabled?)})) + +(rf/reg-event-fx :keycard.ios/on-nfc-user-cancelled + (fn [{:keys [db]}] + (log/debug "[keycard] nfc user cancelled") + {:db (assoc-in db [:keycard :pin :status] nil) + :fx [(when-let [on-nfc-cancelled-event-vector (get-in db [:keycard :on-nfc-cancelled-event-vector])] + [:dispatch on-nfc-cancelled-event-vector])]})) + +(rf/reg-event-fx :keycard/on-card-connected + (fn [{:keys [db]} _] + (log/debug "[keycard] card globally connected") + {:db (assoc-in db [:keycard :card-connected?] true) + :fx [(when-let [event (get-in db [:keycard :on-card-connected-event-vector])] + [:dispatch event])]})) + +(rf/reg-event-fx :keycard/on-card-disconnected + (fn [{:keys [db]} _] + (log/debug "[keycard] card disconnected") + {:db (assoc-in db [:keycard :card-connected?] false) + :fx [(when-let [event (get-in db [:keycard :on-card-disconnected-event-vector])] + [:dispatch event])]})) + +(rf/reg-event-fx :keycard.ios/start-nfc + (fn [_] + {:effects.keycard.ios/start-nfc nil})) + +(rf/reg-event-fx :keycard.ios/on-nfc-timeout + (fn [{:keys [db]} _] + (log/debug "[keycard] nfc timeout") + {:db (assoc-in db [:keycard :card-connected?] false) + :fx [[:dispatch-later [{:ms 500 :dispatch [:keycard.ios/start-nfc]}]]]})) + +(rf/reg-event-fx :keycard/get-application-info + (fn [_ [{:keys [on-success on-failure]}]] + (log/debug "[keycard] get-application-info") + {:effects.keycard/get-application-info {:on-success on-success + :on-failure on-failure}})) + +(rf/reg-event-fx :keycard/on-retrieve-pairings-success + (fn [{:keys [db]} [pairings]] + {:db (assoc-in db [:keycard :pairings] pairings) + :fx [[:effects.keycard/set-pairing-to-keycard pairings]]})) + +(rf/reg-event-fx :keycard.ios/on-start-nfc-success + (fn [{:keys [db]} [{:keys [on-cancel-event-vector]}]] + (log/debug "[keycard] nfc started success") + {:db (assoc-in db [:keycard :on-nfc-cancelled-event-vector] on-cancel-event-vector)})) diff --git a/src/status_im/contexts/keycard/login/events.cljs b/src/status_im/contexts/keycard/login/events.cljs new file mode 100644 index 0000000000..b241a25d5e --- /dev/null +++ b/src/status_im/contexts/keycard/login/events.cljs @@ -0,0 +1,87 @@ +(ns status-im.contexts.keycard.login.events + (:require [status-im.contexts.keycard.utils :as keycard.utils] + [taoensso.timbre :as log] + [utils.re-frame :as rf])) + +(rf/reg-event-fx :keycard.login/on-get-keys-error + (fn [{:keys [db]} [error]] + (log/debug "[keycard] get keys error: " error) + (let [tag-was-lost? (keycard.utils/tag-lost? (:error error)) + pin-retries-count (keycard.utils/pin-retries (:error error))] + (if tag-was-lost? + {:db (assoc-in db [:keycard :pin :status] nil)} + (if (nil? pin-retries-count) + {:effects.utils/show-popup {:title "wrong-keycard"}} + {:db (-> db + (assoc-in [:keycard :application-info :pin-retry-counter] pin-retries-count) + (update-in [:keycard :pin] assoc :status :error)) + :fx [[:dispatch [:keycard/hide-connection-sheet]] + (when (zero? pin-retries-count) + [:effects.utils/show-popup {:title "frozen-keycard"}])]}))))) + +(rf/reg-event-fx :keycard.login/on-get-keys-success + (fn [{:keys [db]} [data]] + (let [{:keys [key-uid encryption-public-key + whisper-private-key]} data + key-uid (str "0x" key-uid) + profile (get-in db [:profile/profiles-overview key-uid])] + {:db + (-> db + (dissoc :keycard) + (update :profile/login assoc + :password encryption-public-key + :key-uid key-uid + :name (:name profile))) + :fx [[:dispatch [:keycard/hide-connection-sheet]] + [:effects.keycard/login-with-keycard + {:password encryption-public-key + :whisper-private-key whisper-private-key + :key-uid key-uid}]]}))) + +(rf/reg-event-fx :keycard.login/on-get-keys-from-keychain-success + (fn [{:keys [db]} [key-uid [encryption-public-key whisper-private-key]]] + (when (and encryption-public-key whisper-private-key) + (let [profile (get-in db [:profile/profiles-overview key-uid])] + {:db + (-> db + (dissoc :keycard) + (update :profile/login assoc + :password encryption-public-key + :key-uid key-uid + :name (:name profile))) + :fx [[:dispatch [:keycard/hide-connection-sheet]] + [:effects.keycard/login-with-keycard + {:password encryption-public-key + :whisper-private-key whisper-private-key + :key-uid key-uid}]]})))) + +(rf/reg-event-fx :keycard.login/on-get-application-info-success + (fn [{:keys [db]} [application-info]] + (let [profile (get-in db [:profile/profiles-overview (get-in db [:profile/login :key-uid])]) + pin (get-in db [:keycard :pin :text]) + error (keycard.utils/validate-application-info profile application-info)] + (if error + {:effects.utils/show-popup {:title (str error)}} + {:db (-> db + (assoc-in [:keycard :application-info] application-info) + (assoc-in [:keycard :pin :status] :verifying)) + :effects.keycard/get-keys {:pin pin + :on-success #(rf/dispatch [:keycard.login/on-get-keys-success %]) + :on-failure #(rf/dispatch [:keycard.login/on-get-keys-error %])}})))) + +(rf/reg-event-fx :keycard.login/cancel-reading-card + (fn [{:keys [db]}] + {:db (assoc-in db [:keycard :on-card-connected-event-vector] nil)})) + +(rf/reg-event-fx :keycard/read-card-and-login + (fn [{:keys [db]}] + (let [connected? (get-in db [:keycard :card-connected?]) + event-vector [:keycard/get-application-info + {:on-success #(rf/dispatch [:keycard.login/on-get-application-info-success %])}]] + (log/debug "[keycard] proceed-to-login") + {:db (assoc-in db [:keycard :on-card-connected-event-vector] event-vector) + :fx [[:dispatch + [:keycard/show-connection-sheet + {:on-cancel-event-vector [:keycard.login/cancel-reading-card]}]] + (when connected? + [:dispatch event-vector])]}))) diff --git a/src/status_im/contexts/keycard/pin/events.cljs b/src/status_im/contexts/keycard/pin/events.cljs new file mode 100644 index 0000000000..eeba5ae4cf --- /dev/null +++ b/src/status_im/contexts/keycard/pin/events.cljs @@ -0,0 +1,21 @@ +(ns status-im.contexts.keycard.pin.events + (:require [utils.re-frame :as rf])) + +(rf/reg-event-fx :keycard.pin/delete-pressed + (fn [{:keys [db]}] + (let [pin (get-in db [:keycard :pin :text])] + (when (and pin (pos? (count pin))) + {:db (-> db + (assoc-in [:keycard :pin :text] (.slice pin 0 -1)) + (assoc-in [:keycard :pin :status] nil))})))) + +(rf/reg-event-fx :keycard.pin/number-pressed + (fn [{:keys [db]} [number max-numbers on-complete-event]] + (let [pin (get-in db [:keycard :pin :text]) + new-pin (str pin number)] + (when (<= (count new-pin) max-numbers) + {:db (-> db + (assoc-in [:keycard :pin :text] new-pin) + (assoc-in [:keycard :pin :status] nil)) + :fx [(when (= (dec max-numbers) (count pin)) + [:dispatch [on-complete-event]])]})))) diff --git a/src/status_im/contexts/keycard/pin/view.cljs b/src/status_im/contexts/keycard/pin/view.cljs new file mode 100644 index 0000000000..debc48cc66 --- /dev/null +++ b/src/status_im/contexts/keycard/pin/view.cljs @@ -0,0 +1,26 @@ +(ns status-im.contexts.keycard.pin.view + (:require [quo.core :as quo] + [react-native.core :as rn] + [status-im.constants :as constants] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn auth + [callback-event-key] + (let [{:keys [text status]} (rf/sub [:keycard/pin]) + pin-retry-counter (rf/sub [:keycard/pin-retry-counter]) + error? (= status :error)] + [rn/view {:padding-bottom 12 :flex 1} + [rn/view {:flex 1 :justify-content :center :align-items :center :padding 34} + [quo/pin-input + {:blur? false + :number-of-pins constants/pincode-length + :number-of-filled-pins (count text) + :error? error? + :info (when error? + (i18n/label :t/pin-retries-left {:number pin-retry-counter}))}]] + [quo/numbered-keyboard + {:delete-key? true + :on-delete #(rf/dispatch [:keycard.pin/delete-pressed]) + :on-press #(rf/dispatch [:keycard.pin/number-pressed % constants/pincode-length + callback-event-key])}]])) diff --git a/src/status_im/contexts/keycard/sheet/events.cljs b/src/status_im/contexts/keycard/sheet/events.cljs new file mode 100644 index 0000000000..01f7419d0a --- /dev/null +++ b/src/status_im/contexts/keycard/sheet/events.cljs @@ -0,0 +1,30 @@ +(ns status-im.contexts.keycard.sheet.events + (:require [re-frame.core :as rf] + [react-native.platform :as platform] + [taoensso.timbre :as log])) + +(rf/reg-event-fx :keycard/show-connection-sheet-component + (fn [{:keys [db]} [{:keys [on-cancel-event-vector]}]] + {:db (assoc-in db [:keycard :connection-sheet-opts] {:on-close #(rf/dispatch on-cancel-event-vector)}) + :fx [[:dismiss-keyboard true] + [:show-nfc-sheet nil]]})) + +(rf/reg-event-fx :keycard/show-connection-sheet + (fn [_ [args]] + (if platform/android? + {:dispatch [:keycard/show-connection-sheet-component args]} + {:effects.keycard.ios/start-nfc + {:on-success + (fn [] + (log/debug "nfc started successfully. next: show-connection-sheet") + (rf/dispatch [:keycard.ios/on-start-nfc-success args])) + :on-failure + (fn [] + (log/debug "nfc failed star starting. not calling show-connection-sheet"))}}))) + +(rf/reg-event-fx :keycard/hide-connection-sheet + (fn [{:keys [db]}] + (if platform/android? + {:db (assoc-in db [:keycard :connection-sheet-opts] nil) + :fx [[:hide-nfc-sheet nil]]} + {:effects.keycard.ios/stop-nfc nil}))) diff --git a/src/status_im/contexts/keycard/sheet/view.cljs b/src/status_im/contexts/keycard/sheet/view.cljs new file mode 100644 index 0000000000..4422ed3852 --- /dev/null +++ b/src/status_im/contexts/keycard/sheet/view.cljs @@ -0,0 +1,36 @@ +(ns status-im.contexts.keycard.sheet.view + (:require [quo.foundations.colors :as colors] + quo.theme + [react-native.core :as rn] + [status-im.common.resources :as resources] + [utils.re-frame :as rf])) + +(defn connect-keycard + [] + (let [connected? (rf/sub [:keycard/connected?]) + {:keys [on-close]} (rf/sub [:keycard/connection-sheet-opts]) + theme (quo.theme/use-theme)] + [rn/view {:flex 1} + [rn/view {:flex 1}] + [rn/view + {:style {:align-items :center + :padding-horizontal 36 + :padding-vertical 30 + :background-color (colors/theme-colors colors/white colors/neutral-95 theme)}} + [rn/text {:style {:font-size 26 :color "#9F9FA5" :margin-bottom 36}} + "Ready to Scan"] + [rn/image + {:source (resources/get-image :nfc-prompt)}] + [rn/text {:style {:font-size 16 :color :white :margin-vertical 36}} + (if connected? + "Connected. Don’t move your card." + "Hold your phone near a Status Keycard")] + [rn/pressable + {:on-press (fn [] + (when on-close (on-close)) + (rf/dispatch [:keycard/hide-connection-sheet])) + :style {:flex-direction :row}} + [rn/view + {:style {:background-color "#8E8E93" :flex 1 :align-items :center :padding 18 :border-radius 10}} + [rn/text {:style {:color :white :font-size 16}} + "Cancel"]]]]])) diff --git a/src/status_im/contexts/keycard/utils.cljs b/src/status_im/contexts/keycard/utils.cljs new file mode 100644 index 0000000000..968c2e8807 --- /dev/null +++ b/src/status_im/contexts/keycard/utils.cljs @@ -0,0 +1,45 @@ +(ns status-im.contexts.keycard.utils + (:require [taoensso.timbre :as log])) + +(def pin-mismatch-error #"Unexpected error SW, 0x63C(\d+)|wrongPIN\(retryCounter: (\d+)\)") + +(defn pin-retries + [error] + (when-let [matched-error (re-matches pin-mismatch-error error)] + (js/parseInt (second (filter some? matched-error))))) + +(defn tag-lost? + [error] + (or + (= error "Tag was lost.") + (= error "NFCError:100") + (re-matches #".*NFCError:100.*" error))) + +(defn validate-application-info + [profile {:keys [key-uid paired? pin-retry-counter puk-retry-counter] :as application-info}] + (let [profile-mismatch? (or (nil? profile) (not= (:key-uid profile) key-uid))] + (log/debug "[keycard] login-with-keycard" + "empty application info" (empty? application-info) + "no key-uid" (empty? key-uid) + "profile-mismatch?" profile-mismatch? + "no pairing" paired?) + (cond + (empty? application-info) + :not-keycard + + (empty? (:key-uid application-info)) + :keycard-blank + + profile-mismatch? + :keycard-wrong + + (not paired?) + :keycard-unpaired + + (and (zero? pin-retry-counter) + (or (nil? puk-retry-counter) + (pos? puk-retry-counter))) + nil + + :else + nil))) diff --git a/src/status_im/contexts/preview/quo/main.cljs b/src/status_im/contexts/preview/quo/main.cljs index b8db3dc7e5..1875c47d8e 100644 --- a/src/status_im/contexts/preview/quo/main.cljs +++ b/src/status_im/contexts/preview/quo/main.cljs @@ -144,6 +144,7 @@ small-option-card] [status-im.contexts.preview.quo.password.password-tips :as password-tips] [status-im.contexts.preview.quo.password.tips :as tips] + [status-im.contexts.preview.quo.pin-input.pin-input :as pin-input] [status-im.contexts.preview.quo.profile.collectible :as collectible] [status-im.contexts.preview.quo.profile.collectible-list-item :as collectible-list-item] [status-im.contexts.preview.quo.profile.expanded-collectible :as expanded-collectible] @@ -369,6 +370,8 @@ :component keyboard-key/view} {:name :numbered-keyboard :component numbered-keyboard/view}] + :pin-input [{:name :pin-input + :component pin-input/view}] :links [{:name :internal-link-card :options {:insets {:top true}} :component internal-link-card/view} diff --git a/src/status_im/contexts/preview/quo/pin_input/pin_input.cljs b/src/status_im/contexts/preview/quo/pin_input/pin_input.cljs new file mode 100644 index 0000000000..fca2d6773c --- /dev/null +++ b/src/status_im/contexts/preview/quo/pin_input/pin_input.cljs @@ -0,0 +1,27 @@ +(ns status-im.contexts.preview.quo.pin-input.pin-input + (:require [quo.core :as quo] + [react-native.core :as rn] + [status-im.contexts.preview.quo.preview :as preview])) + +(def descriptor + [{:key :blur? :type :boolean} + {:type :number + :key :number-of-pins} + {:type :number + :key :number-of-filled-pins} + {:type :boolean + :key :error?} + {:type :text + :key :info}]) + +(defn view + [] + (let [[state set-state] (rn/use-state {:blur? false + :number-of-pins 6 + :number-of-filled-pins 0})] + [preview/preview-container + {:state state + :set-state set-state + :descriptor descriptor} + [rn/view {:style {:padding-vertical 40 :align-items :center :justify-content :center}} + [quo/pin-input state]]])) diff --git a/src/status_im/contexts/profile/login/events.cljs b/src/status_im/contexts/profile/login/events.cljs index 7a04139cb6..477596b3a0 100644 --- a/src/status_im/contexts/profile/login/events.cljs +++ b/src/status_im/contexts/profile/login/events.cljs @@ -186,9 +186,13 @@ (rf/reg-event-fx :profile.login/biometric-success (fn [{:keys [db]}] - (let [key-uid (get-in db [:profile/login :key-uid])] - {:keychain/get-user-password [key-uid - #(rf/dispatch [:profile.login/get-user-password-success %])]}))) + (let [key-uid (get-in db [:profile/login :key-uid]) + keycard? (get-in db [:profile/profiles-overview key-uid :keycard-pairing])] + (if keycard? + {:keychain/get-keycard-keys + [key-uid #(rf/dispatch [:keycard.login/on-get-keys-from-keychain-success key-uid %])]} + {:keychain/get-user-password + [key-uid #(rf/dispatch [:profile.login/get-user-password-success %])]})))) (rf/reg-event-fx :profile.login/biometric-auth-fail diff --git a/src/status_im/contexts/profile/profiles/view.cljs b/src/status_im/contexts/profile/profiles/view.cljs index 748907164b..ff4a9d2546 100644 --- a/src/status_im/contexts/profile/profiles/view.cljs +++ b/src/status_im/contexts/profile/profiles/view.cljs @@ -10,6 +10,7 @@ [status-im.common.standard-authentication.core :as standard-authentication] [status-im.config :as config] [status-im.constants :as constants] + [status-im.contexts.keycard.pin.view :as keycard.pin] [status-im.contexts.onboarding.common.background.view :as background] [status-im.contexts.profile.profiles.style :as style] [taoensso.timbre :as log] @@ -140,7 +141,7 @@ [:profile/profile-selected key-uid]) (rf/dispatch [:profile.login/login-with-biometric-if-available key-uid]) - (when-not keycard-pairing (set-hide-profiles)))}])) + (set-hide-profiles))}])) (defn- profiles-section [{:keys [hide-profiles]}] @@ -189,8 +190,8 @@ [:profile.login/biometric-success]) :on-fail #(rf/dispatch [:profile.login/biometric-auth-fail - %])}])) - ] + %])}]))] + [standard-authentication/password-input {:shell? true :blur? true @@ -199,7 +200,7 @@ (defn login-section [{:keys [show-profiles]}] (let [processing (rf/sub [:profile/login-processing]) - {:keys [key-uid name + {:keys [key-uid name keycard-pairing customization-color]} (rf/sub [:profile/login-profile]) sign-in-enabled? (rf/sub [:sign-in-enabled?]) profile-picture (rf/sub [:profile/login-profiles-picture key-uid]) @@ -228,7 +229,7 @@ :disabled? processing :accessibility-label :show-profiles} :i/multi-profile]] - [rn/scroll-view + [(if keycard-pairing rn/view rn/scroll-view) {:keyboard-should-persist-taps :always :style {:flex 1}} [quo/profile-card @@ -236,17 +237,20 @@ :customization-color (or customization-color :primary) :profile-picture profile-picture :card-style style/login-profile-card}] - [password-input]] - [quo/button - {:size 40 - :type :primary - :customization-color (or customization-color :primary) - :accessibility-label :login-button - :icon-left :i/unlocked - :disabled? (or (not sign-in-enabled?) processing) - :on-press login-multiaccount - :container-style {:margin-bottom (+ (safe-area/get-bottom) 12)}} - (i18n/label :t/log-in)]])) + (if keycard-pairing + [keycard.pin/auth :keycard/read-card-and-login] + [password-input])] + (when-not keycard-pairing + [quo/button + {:size 40 + :type :primary + :customization-color (or customization-color :primary) + :accessibility-label :login-button + :icon-left :i/unlocked + :disabled? (or (not sign-in-enabled?) processing) + :on-press login-multiaccount + :container-style {:margin-bottom (+ (safe-area/get-bottom) 12)}} + (i18n/label :t/log-in)])])) (defn view [] diff --git a/src/status_im/db.cljs b/src/status_im/db.cljs index 71259c3a24..2fe4ecd7fb 100644 --- a/src/status_im/db.cljs +++ b/src/status_im/db.cljs @@ -41,10 +41,5 @@ :visibility-status-updates {} :stickers/packs-pending #{} :settings/change-password {} - :keycard {:nfc-enabled? false - :pin {:original [] - :confirmation [] - :current [] - :puk [] - :enter-step :original}} + :keycard {} :theme :light}) diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index a93ca91201..d77cd4055e 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -25,6 +25,8 @@ status-im.contexts.communities.overview.events status-im.contexts.communities.sharing.events status-im.contexts.contact.blocking.events + status-im.contexts.keycard.effects + status-im.contexts.keycard.events status-im.contexts.onboarding.common.overlay.events status-im.contexts.onboarding.events status-im.contexts.profile.events @@ -57,6 +59,9 @@ :theme/init-theme nil :network/listen-to-network-info nil :effects.biometric/get-supported-type nil + :effects.keycard/register-card-events nil + :effects.keycard/check-nfc-enabled nil + :effects.keycard/retrieve-pairings nil ;;app starting flow continues in get-profiles-overview :profile/get-profiles-overview #(rf/dispatch [:profile/get-profiles-overview-success %]) :effects.font/get-font-file-for-initials-avatar diff --git a/src/status_im/navigation/core.cljs b/src/status_im/navigation/core.cljs index 286b82a9b6..5c84909c6b 100644 --- a/src/status_im/navigation/core.cljs +++ b/src/status_im/navigation/core.cljs @@ -81,14 +81,22 @@ (fn [] views/bottom-sheet)) ;;;; Alert Banner + (navigation/register-component "alert-banner" (fn [] (gesture/gesture-handler-root-hoc views/alert-banner #js {:flex 0})) (fn [] views/alert-banner)) - ;;;; LEGACY (should be removed in status 2.0) + ;;;; NFC sheet (navigation/register-component - "popover" - (fn [] (gesture/gesture-handler-root-hoc views/popover-comp)) - (fn [] views/popover-comp))) + "nfc-sheet" + (fn [] (gesture/gesture-handler-root-hoc views/nfc-sheet-comp)) + (fn [] views/nfc-sheet-comp))) + +;;;; LEGACY (should be removed in status 2.0) + +(navigation/register-component + "popover" + (fn [] (gesture/gesture-handler-root-hoc views/popover-comp)) + (fn [] views/popover-comp)) diff --git a/src/status_im/navigation/effects.cljs b/src/status_im/navigation/effects.cljs index 181884cbad..b0b9079e7a 100644 --- a/src/status_im/navigation/effects.cljs +++ b/src/status_im/navigation/effects.cljs @@ -230,6 +230,7 @@ (fn [] (navigation/dissmiss-overlay "bottom-sheet"))) ;;;; Alert Banner + (rf/reg-fx :show-alert-banner (fn [[view-id theme]] (show-overlay "alert-banner" @@ -245,67 +246,17 @@ (reset! state/alert-banner-shown? false) (reload-status-nav-color-fx [view-id theme]))) -;;;; Merge options +;;;; NFC sheet -(rf/reg-fx :merge-options - (fn [{:keys [id options]}] - (navigation/merge-options id options))) +(rf/reg-fx :show-nfc-sheet + (fn [] (show-overlay "nfc-sheet"))) + +(rf/reg-fx :hide-nfc-sheet + (fn [] (navigation/dissmiss-overlay "nfc-sheet"))) ;;;; Legacy (should be removed in status 2.0) - -(defn- get-screen-component - [component] - (let [{:keys [options]} (get views/screens component)] - {:component {:id component - :name component - :options (merge (options/statusbar-and-navbar-options (:theme options) nil nil) - options)}})) - -(rf/reg-fx :set-stack-root-fx - (fn [[stack component]] - ;; We don't have bottom tabs as separate stacks anymore,. So the old way of pushing screens in - ;; specific tabs will not work. Disabled set-stack-root for :shell-stack as it is not working - ;; and currently only being used for browser and some rare keycard flows after login - (when-not (= @state/root-id :shell-stack) - (log/debug :set-stack-root-fx stack component) - (navigation/set-stack-root - (name stack) - (if (vector? component) - (mapv get-screen-component component) - (get-screen-component component)))))) - (rf/reg-fx :show-popover (fn [] (show-overlay "popover"))) (rf/reg-fx :hide-popover (fn [] (navigation/dissmiss-overlay "popover"))) - -(rf/reg-fx :show-visibility-status-popover - (fn [] (show-overlay "visibility-status-popover"))) - -(rf/reg-fx :hide-visibility-status-popover - (fn [] (navigation/dissmiss-overlay "visibility-status-popover"))) - -(rf/reg-fx :show-wallet-connect-sheet - (fn [] (show-overlay "wallet-connect-sheet"))) - -(rf/reg-fx :hide-wallet-connect-sheet - (fn [] (navigation/dissmiss-overlay "wallet-connect-sheet"))) - -(rf/reg-fx :show-wallet-connect-success-sheet - (fn [] (show-overlay "wallet-connect-success-sheet"))) - -(rf/reg-fx :hide-wallet-connect-success-sheet - (fn [] (navigation/dissmiss-overlay "wallet-connect-success-sheet"))) - -(rf/reg-fx :show-wallet-connect-app-management-sheet - (fn [] (show-overlay "wallet-connect-app-management-sheet"))) - -(rf/reg-fx :hide-wallet-connect-app-management-sheet - (fn [] (navigation/dissmiss-overlay "wallet-connect-app-management-sheet"))) - -(rf/reg-fx :show-signing-sheet - (fn [] (show-overlay "signing-sheet"))) - -(rf/reg-fx :hide-signing-sheet - (fn [] (navigation/dissmiss-overlay "signing-sheet"))) diff --git a/src/status_im/navigation/events.cljs b/src/status_im/navigation/events.cljs index 7ee2049576..50ca682e69 100644 --- a/src/status_im/navigation/events.cljs +++ b/src/status_im/navigation/events.cljs @@ -74,11 +74,6 @@ [{:keys [db]} root-id] {:set-root [root-id (:theme db)]}) -(rf/defn set-stack-root - {:events [:set-stack-root]} - [_ stack root] - {:set-stack-root-fx [stack root]}) - (rf/defn change-tab {:events [:navigate-change-tab]} [{:keys [db]} stack-id] diff --git a/src/status_im/navigation/view.cljs b/src/status_im/navigation/view.cljs index a35ecf3487..d920670d1c 100644 --- a/src/status_im/navigation/view.cljs +++ b/src/status_im/navigation/view.cljs @@ -11,6 +11,7 @@ [status-im.common.bottom-sheet-screen.view :as bottom-sheet-screen] [status-im.common.bottom-sheet.view :as bottom-sheet] [status-im.common.toasts.view :as toasts] + [status-im.contexts.keycard.sheet.view :as keycard.sheet] [status-im.navigation.screens :as screens] [status-im.setup.hot-reload :as reloader] [utils.re-frame :as rf])) @@ -121,6 +122,17 @@ [alert-banner/view]]) functional-compiler)) +(def nfc-sheet-comp + (reagent/reactify-component + (fn [] + (let [app-theme (rf/sub [:theme])] + ^{:key (str "nfc-sheet-" @reloader/cnt)} + [quo.theme/provider app-theme + [rn/keyboard-avoiding-view + {:style {:position :relative :flex 1}} + [keycard.sheet/connect-keycard]]])) + functional-compiler)) + ;; LEGACY (should be removed in status 2.0) (def popover-comp diff --git a/src/status_im/subs/keycard.cljs b/src/status_im/subs/keycard.cljs new file mode 100644 index 0000000000..53db0caecd --- /dev/null +++ b/src/status_im/subs/keycard.cljs @@ -0,0 +1,38 @@ +(ns status-im.subs.keycard + (:require [utils.re-frame :as rf])) + +(rf/reg-sub + :keycard/keycard-profile? + (fn [db] + (not (nil? (get-in db [:profile/profile :keycard-pairing]))))) + +(rf/reg-sub + :keycard/nfc-enabled? + :<- [:keycard] + (fn [keycard] + (:nfc-enabled? keycard))) + +(rf/reg-sub + :keycard/connected? + :<- [:keycard] + (fn [keycard] + (:card-connected? keycard))) + +(rf/reg-sub + :keycard/pin + :<- [:keycard] + (fn [keycard] + (:pin keycard))) + +(rf/reg-sub + :keycard/pin-retry-counter + :<- [:keycard] + (fn [keycard] + (get-in keycard [:application-info :pin-retry-counter]))) + +(rf/reg-sub + :keycard/connection-sheet-opts + :<- [:keycard] + (fn [keycard] + (:connection-sheet-opts keycard))) + diff --git a/src/status_im/subs/root.cljs b/src/status_im/subs/root.cljs index 029eb3207d..a364cd1a40 100644 --- a/src/status_im/subs/root.cljs +++ b/src/status_im/subs/root.cljs @@ -9,6 +9,7 @@ status-im.subs.community.account-selection status-im.subs.contact status-im.subs.general + status-im.subs.keycard status-im.subs.messages status-im.subs.onboarding status-im.subs.pairing @@ -186,3 +187,6 @@ ;; centralized-metrics (reg-root-key-sub :centralized-metrics/enabled? :centralized-metrics/enabled?) (reg-root-key-sub :centralized-metrics/user-confirmed? :centralized-metrics/user-confirmed?) + +;;keycard +(reg-root-key-sub :keycard :keycard) diff --git a/src/utils/re_frame.cljs b/src/utils/re_frame.cljs index ded7d3c593..be4d05e604 100644 --- a/src/utils/re_frame.cljs +++ b/src/utils/re_frame.cljs @@ -81,6 +81,8 @@ (def sub (comp deref re-frame/subscribe)) +(def reg-sub re-frame/reg-sub) + (def dispatch re-frame/dispatch) (def reg-fx re-frame/reg-fx) From 1c85b292f841a71fa9be216c9f8f6be531d1e838 Mon Sep 17 00:00:00 2001 From: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com> Date: Fri, 12 Jul 2024 16:29:43 +0530 Subject: [PATCH 31/79] fix(community): remove non operable accounts in the selection of sharing addresses with the community (#20636) This commit removes the non-operable accounts in the "Addresses for permission" and "Airdrop address" selections while joining a community Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com> --- .../actions/accounts_selection/events.cljs | 4 +- .../accounts_selection/events_test.cljs | 24 +- .../actions/accounts_selection/view.cljs | 2 +- .../addresses_for_permissions/events.cljs | 4 +- .../events_test.cljs | 18 +- .../addresses_for_permissions/view.cljs | 2 +- .../contexts/communities/events.cljs | 4 +- .../contexts/communities/overview/events.cljs | 2 +- src/status_im/contexts/communities/utils.cljs | 3 +- src/status_im/contexts/wallet/data_store.cljs | 3 +- .../contexts/wallet/data_store_test.cljs | 15 +- src/status_im/contexts/wallet/events.cljs | 43 ++-- .../contexts/wallet/events_test.cljs | 23 +- .../wallet/sheets/account_options/view.cljs | 5 +- .../wallet/sheets/select_account/view.cljs | 2 +- .../subs/community/account_selection.cljs | 4 +- .../community/account_selection_test.cljs | 12 +- src/status_im/subs/wallet/wallet.cljs | 16 +- src/status_im/subs/wallet/wallet_test.cljs | 214 ++++++++++-------- 19 files changed, 212 insertions(+), 188 deletions(-) diff --git a/src/status_im/contexts/communities/actions/accounts_selection/events.cljs b/src/status_im/contexts/communities/actions/accounts_selection/events.cljs index 0be9019961..3a0847df51 100644 --- a/src/status_im/contexts/communities/actions/accounts_selection/events.cljs +++ b/src/status_im/contexts/communities/actions/accounts_selection/events.cljs @@ -18,7 +18,7 @@ (defn do-init-permission-addresses [{:keys [db]} [community-id revealed-accounts]] - (let [wallet-accounts (utils/sorted-non-watch-only-accounts db) + (let [wallet-accounts (utils/sorted-operable-non-watch-only-accounts db) addresses-to-reveal (if (seq revealed-accounts) (set (keys revealed-accounts)) ;; Reveal all addresses as fallback. @@ -62,7 +62,7 @@ status-go will default to all available." [{:keys [db]} [{:keys [community-id password on-success addresses airdrop-address]}]] (let [pub-key (get-in db [:profile/profile :public-key]) - wallet-accounts (utils/sorted-non-watch-only-accounts db) + wallet-accounts (utils/sorted-operable-non-watch-only-accounts db) addresses-to-reveal (if (seq addresses) (set addresses) (get-in db [:communities/all-addresses-to-reveal community-id])) diff --git a/src/status_im/contexts/communities/actions/accounts_selection/events_test.cljs b/src/status_im/contexts/communities/actions/accounts_selection/events_test.cljs index db933aaa7b..81a6ff06e5 100644 --- a/src/status_im/contexts/communities/actions/accounts_selection/events_test.cljs +++ b/src/status_im/contexts/communities/actions/accounts_selection/events_test.cljs @@ -9,17 +9,25 @@ (def wallet-accounts {"0xA" {:address "0xA" :watch-only? true + :operable? true :position 2 :color :red :emoji "🦇"} - "0xB" {:address "0xB" - :position 0 - :color :blue - :emoji "🐈"} - "0xC" {:address "0xC" - :position 1 - :color :orange - :emoji "🛏️"}}) + "0xB" {:address "0xB" + :operable? true + :position 0 + :color :blue + :emoji "🐈"} + "0xC" {:address "0xC" + :operable? true + :position 1 + :color :orange + :emoji "🛏️"} + "0xD" {:address "0xD" + :operable? false + :position 3 + :color :flamingo + :emoji "🦩"}}) (def permissioned-accounts [{:address "0xB" diff --git a/src/status_im/contexts/communities/actions/accounts_selection/view.cljs b/src/status_im/contexts/communities/actions/accounts_selection/view.cljs index 81e1146ca5..7c4bca5a28 100644 --- a/src/status_im/contexts/communities/actions/accounts_selection/view.cljs +++ b/src/status_im/contexts/communities/actions/accounts_selection/view.cljs @@ -22,7 +22,7 @@ airdrop-account (rf/sub [:communities/airdrop-account id]) revealed-accounts (rf/sub [:communities/accounts-to-reveal id]) revealed-accounts-count (count revealed-accounts) - wallet-accounts-count (count (rf/sub [:wallet/accounts-without-watched-accounts])) + wallet-accounts-count (count (rf/sub [:wallet/operable-accounts-without-watched-accounts])) addresses-shared-text (if (= revealed-accounts-count wallet-accounts-count) (i18n/label :t/all-addresses) (i18n/label-pluralize diff --git a/src/status_im/contexts/communities/actions/addresses_for_permissions/events.cljs b/src/status_im/contexts/communities/actions/addresses_for_permissions/events.cljs index 541a4da397..efa178de31 100644 --- a/src/status_im/contexts/communities/actions/addresses_for_permissions/events.cljs +++ b/src/status_im/contexts/communities/actions/addresses_for_permissions/events.cljs @@ -120,7 +120,7 @@ (defn set-permissioned-accounts [{:keys [db]} [community-id addresses-to-reveal]] (let [addresses-to-reveal (set addresses-to-reveal) - wallet-accounts (utils/sorted-non-watch-only-accounts db) + wallet-accounts (utils/sorted-operable-non-watch-only-accounts db) current-airdrop-address (get-in db [:communities/all-airdrop-addresses community-id]) new-airdrop-address (if (contains? addresses-to-reveal current-airdrop-address) current-airdrop-address @@ -142,7 +142,7 @@ [{:keys [db]} [community-id new-value]] (let [current-addresses (get-in db [:communities/all-addresses-to-reveal community-id]) addresses-to-reveal (if new-value - (->> (utils/sorted-non-watch-only-accounts db) + (->> (utils/sorted-operable-non-watch-only-accounts db) (map :address) set) current-addresses)] diff --git a/src/status_im/contexts/communities/actions/addresses_for_permissions/events_test.cljs b/src/status_im/contexts/communities/actions/addresses_for_permissions/events_test.cljs index 8ce5c107fe..0c4e0ceded 100644 --- a/src/status_im/contexts/communities/actions/addresses_for_permissions/events_test.cljs +++ b/src/status_im/contexts/communities/actions/addresses_for_permissions/events_test.cljs @@ -33,9 +33,15 @@ (let [cofx {:db {:communities/all-addresses-to-reveal {community-id #{"0xA" "0xB" "0xC"}} :communities/all-airdrop-addresses {community-id "0xB"} - :wallet {:accounts {"0xB" {:address "0xB" :position 0} - "0xA" {:address "0xA" :position 1} - "0xC" {:address "0xC" :position 2}}}}} + :wallet {:accounts {"0xB" {:address "0xB" + :operable? true + :position 0} + "0xA" {:address "0xA" + :operable? true + :position 1} + "0xC" {:address "0xC" + :operable? true + :position 2}}}}} addresses-to-reveal ["0xA" "0xC"]] (is (match? {:db {:communities/all-addresses-to-reveal @@ -52,9 +58,9 @@ (testing "sets flag from false -> true will mark all addresses to be revealed" (let [cofx {:db {:wallet - {:accounts {"0xB" {:address "0xB" :position 0} - "0xA" {:address "0xA" :position 1} - "0xC" {:address "0xC" :position 2}}} + {:accounts {"0xB" {:address "0xB" :operable? true :position 0} + "0xA" {:address "0xA" :operable? true :position 1} + "0xC" {:address "0xC" :operable? true :position 2}}} :communities/all-addresses-to-reveal {community-id #{"0xA"}} :communities/selected-share-all-addresses {community-id false}}} addresses-to-reveal #{"0xA" "0xB" "0xC"}] diff --git a/src/status_im/contexts/communities/actions/addresses_for_permissions/view.cljs b/src/status_im/contexts/communities/actions/addresses_for_permissions/view.cljs index 0bd4849c02..b37337966d 100644 --- a/src/status_im/contexts/communities/actions/addresses_for_permissions/view.cljs +++ b/src/status_im/contexts/communities/actions/addresses_for_permissions/view.cljs @@ -260,7 +260,7 @@ can-edit-addresses? (rf/sub [:communities/can-edit-shared-addresses? id]) - wallet-accounts (rf/sub [:wallet/accounts-without-watched-accounts]) + wallet-accounts (rf/sub [:wallet/operable-accounts-without-watched-accounts]) unmodified-addresses-to-reveal (rf/sub [:communities/addresses-to-reveal id]) [addresses-to-reveal set-addresses-to-reveal] (rn/use-state unmodified-addresses-to-reveal) diff --git a/src/status_im/contexts/communities/events.cljs b/src/status_im/contexts/communities/events.cljs index 0612243aa8..139d4debf0 100644 --- a/src/status_im/contexts/communities/events.cljs +++ b/src/status_im/contexts/communities/events.cljs @@ -154,7 +154,7 @@ (defn update-previous-permission-addresses [{:keys [db]} [community-id]] (when community-id - (let [accounts (utils/sorted-non-watch-only-accounts db) + (let [accounts (utils/sorted-operable-non-watch-only-accounts db) selected-permission-addresses (get-in db [:communities community-id :selected-permission-addresses]) @@ -198,7 +198,7 @@ [{:keys [db]} [community-id]] (let [share-all-addresses? (get-in db [:communities community-id :share-all-addresses?]) next-share-all-addresses? (not share-all-addresses?) - accounts (utils/sorted-non-watch-only-accounts db) + accounts (utils/sorted-operable-non-watch-only-accounts db) addresses (set (map :address accounts))] {:db (update-in db [:communities community-id] diff --git a/src/status_im/contexts/communities/overview/events.cljs b/src/status_im/contexts/communities/overview/events.cljs index 6761a8bc6a..d9f69b30f3 100644 --- a/src/status_im/contexts/communities/overview/events.cljs +++ b/src/status_im/contexts/communities/overview/events.cljs @@ -51,7 +51,7 @@ (rf/reg-event-fx :communities/check-permissions-to-join-community-with-all-addresses (fn [{:keys [db]} [community-id]] - (let [accounts (utils/sorted-non-watch-only-accounts db) + (let [accounts (utils/sorted-operable-non-watch-only-accounts db) addresses (set (map :address accounts))] {:db (assoc-in db [:communities/permissions-check community-id :checking?] true) :json-rpc/call [{:method "wakuext_checkPermissionsToJoinCommunity" diff --git a/src/status_im/contexts/communities/utils.cljs b/src/status_im/contexts/communities/utils.cljs index 808dbc9279..86929c8ba4 100644 --- a/src/status_im/contexts/communities/utils.cljs +++ b/src/status_im/contexts/communities/utils.cljs @@ -12,9 +12,10 @@ constants/community-token-permission-become-member :t/member fallback-to))) -(defn sorted-non-watch-only-accounts +(defn sorted-operable-non-watch-only-accounts [db] (->> (get-in db [:wallet :accounts]) (vals) (remove :watch-only?) + (filter :operable?) (sort-by :position))) diff --git a/src/status_im/contexts/wallet/data_store.cljs b/src/status_im/contexts/wallet/data_store.cljs index 98c3291a3f..99ed3b0283 100644 --- a/src/status_im/contexts/wallet/data_store.cljs +++ b/src/status_im/contexts/wallet/data_store.cljs @@ -23,6 +23,7 @@ (defn add-keys-to-account [account] (-> account + (assoc :operable? (not= (:operable account) :no)) (assoc :watch-only? (= (:type account) :watch)) (assoc :default-account? (:wallet account)))) @@ -67,7 +68,7 @@ :color :colorId}) (update :prodPreferredChainIds chain-ids-set->string) (update :testPreferredChainIds chain-ids-set->string) - (dissoc :watch-only? :default-account? :tokens :collectibles))) + (dissoc :watch-only? :default-account? :operable? :tokens :collectibles))) (defn- rpc->balances-per-chain [token] diff --git a/src/status_im/contexts/wallet/data_store_test.cljs b/src/status_im/contexts/wallet/data_store_test.cljs index fd305ab7c5..73ada0ee93 100644 --- a/src/status_im/contexts/wallet/data_store_test.cljs +++ b/src/status_im/contexts/wallet/data_store_test.cljs @@ -32,6 +32,7 @@ :watch-only? false :prod-preferred-chain-ids #{1 42161} :created-at 1716548742000 + :operable? true :operable :fully :removed false}) @@ -171,9 +172,10 @@ {:key-uid "0x123" :address "1x123"}) "1x456" (merge account - {:key-uid "0x456" - :address "1x456" - :operable :no})} + {:key-uid "0x456" + :address "1x456" + :operable? false + :operable :no})} :updated-keypairs-by-id {"0x123" {:key-uid "0x123" :type :seed :lowest-operability :fully @@ -184,9 +186,10 @@ :type :key :lowest-operability :no :accounts [(merge account - {:key-uid "0x456" - :address "1x456" - :operable :no})]}}}) + {:key-uid "0x456" + :address "1x456" + :operable? false + :operable :no})]}}}) (sut/reconcile-keypairs [raw-keypair-seed-phrase raw-keypair-private-key])))) (testing "reconcile-keypairs represents removed key pairs and accounts" diff --git a/src/status_im/contexts/wallet/events.cljs b/src/status_im/contexts/wallet/events.cljs index bd3d1d9607..4cefbcec2f 100644 --- a/src/status_im/contexts/wallet/events.cljs +++ b/src/status_im/contexts/wallet/events.cljs @@ -87,6 +87,13 @@ [:dispatch [:wallet/request-collectibles-for-all-accounts {:new-request? true}]] [:dispatch [:wallet/check-recent-history-for-all-accounts]]]) +(rf/reg-event-fx + :wallet/fetch-assets-for-address + (fn [_ [address]] + {:fx [[:dispatch [:wallet/get-wallet-token-for-account address]] + [:dispatch [:wallet/request-new-collectibles-for-account-from-signal address]] + [:dispatch [:wallet/check-recent-history-for-account address]]]})) + (rf/reg-event-fx :wallet/get-accounts-success (fn [{:keys [db]} [accounts]] @@ -112,9 +119,7 @@ (rf/reg-event-fx :wallet/process-account-from-signal (fn [{:keys [db]} [{:keys [address] :as account}]] {:db (assoc-in db [:wallet :accounts address] (data-store/rpc->account account)) - :fx [[:dispatch [:wallet/get-wallet-token-for-account address]] - [:dispatch [:wallet/request-new-collectibles-for-account-from-signal address]] - [:dispatch [:wallet/check-recent-history-for-account address]]]})) + :fx [[:dispatch [:wallet/fetch-assets-for-address address]]]})) (rf/reg-event-fx :wallet/save-account @@ -575,18 +580,17 @@ updated-account-addresses (set (map :address updated-accounts)) new-account-addresses (clojure.set/difference updated-account-addresses existing-account-addresses)] - {:db (update-in db - [:wallet :accounts] - (fn [existing-accounts] - (merge-with merge - (apply dissoc existing-accounts removed-account-addresses) - (utils.collection/index-by :address updated-accounts)))) - :fx (mapcat (fn [address] - [[:dispatch [:wallet/get-wallet-token-for-account address]] - [:dispatch - [:wallet/request-new-collectibles-for-account-from-signal address]] - [:dispatch [:wallet/check-recent-history-for-account address]]]) - new-account-addresses)})) + (cond-> {:db (update-in db + [:wallet :accounts] + (fn [existing-accounts] + (merge-with merge + (apply dissoc existing-accounts removed-account-addresses) + (utils.collection/index-by :address updated-accounts))))} + + (seq new-account-addresses) + (assoc :fx + (mapv (fn [address] [:dispatch [:wallet/fetch-assets-for-address address]]) + new-account-addresses))))) (rf/reg-event-fx :wallet/reconcile-watch-only-accounts reconcile-watch-only-accounts) @@ -621,13 +625,10 @@ (into removed-account-addresses old-account-addresses)) updated-accounts-by-address)))} + (seq new-account-addresses) (assoc :fx - (mapcat (fn [address] - [[:dispatch [:wallet/get-wallet-token-for-account address]] - [:dispatch - [:wallet/request-new-collectibles-for-account-from-signal address]] - [:dispatch [:wallet/check-recent-history-for-account address]]]) - new-account-addresses))))) + (mapv (fn [address] [:dispatch [:wallet/fetch-assets-for-address address]]) + new-account-addresses))))) (rf/reg-event-fx :wallet/reconcile-keypairs reconcile-keypairs) diff --git a/src/status_im/contexts/wallet/events_test.cljs b/src/status_im/contexts/wallet/events_test.cljs index febd4e017e..166712a588 100644 --- a/src/status_im/contexts/wallet/events_test.cljs +++ b/src/status_im/contexts/wallet/events_test.cljs @@ -40,6 +40,7 @@ :color :purple :wallet true :default-account? true + :operable? true :name "Ethereum account" :type :generated :chat false @@ -145,10 +146,7 @@ (h/deftest-event :wallet/process-account-from-signal [event-id dispatch] (let [expected-effects {:db {:wallet {:accounts {address account}}} - :fx [[:dispatch [:wallet/get-wallet-token-for-account address]] - [:dispatch - [:wallet/request-new-collectibles-for-account-from-signal address]] - [:dispatch [:wallet/check-recent-history-for-account address]]]}] + :fx [[:dispatch [:wallet/fetch-assets-for-address address]]]}] (reset! rf-db/app-db {:wallet {:accounts {}}}) (is (match? expected-effects (dispatch [event-id raw-account]))))) @@ -169,9 +167,7 @@ :type :seed :lowest-operability :fully :accounts [account]}}}} - :fx [[:dispatch [:wallet/get-wallet-token-for-account address]] - [:dispatch [:wallet/request-new-collectibles-for-account-from-signal address]] - [:dispatch [:wallet/check-recent-history-for-account address]]]}) + :fx [[:dispatch [:wallet/fetch-assets-for-address address]]]}) (dispatch [event-id [{:key-uid keypair-key-uid :type "seed" @@ -292,6 +288,7 @@ (assoc raw-account :address "1x001" :chat true)]}]])))))) + (h/deftest-event :wallet/reconcile-watch-only-accounts [event-id dispatch] (testing "event adds new watch-only accounts" @@ -303,10 +300,7 @@ vector? matchers/equals map? matchers/equals] {:db {:wallet {:accounts {(:address account) account}}} - :fx [[:dispatch [:wallet/get-wallet-token-for-account address]] - [:dispatch - [:wallet/request-new-collectibles-for-account-from-signal address]] - [:dispatch [:wallet/check-recent-history-for-account address]]]}) + :fx [[:dispatch [:wallet/fetch-assets-for-address address]]]}) (dispatch [event-id [raw-account]])))) (testing "event removes watch-only accounts that are marked as removed" (reset! rf-db/app-db {:wallet {:accounts {(:address account) account}}}) @@ -316,8 +310,7 @@ [set? matchers/set-equals vector? matchers/equals map? matchers/equals] - {:db {:wallet {:accounts {}}} - :fx []}) + {:db {:wallet {:accounts {}}}}) (dispatch [event-id [(assoc raw-account :removed true)]])))) (testing "event updates existing watch-only accounts" (reset! rf-db/app-db {:wallet @@ -328,10 +321,8 @@ [set? matchers/set-equals vector? matchers/equals map? matchers/equals] - {:db {:wallet {:accounts {address (assoc account :name "Test")}}} - :fx []}) + {:db {:wallet {:accounts {address (assoc account :name "Test")}}}}) (dispatch [event-id [(assoc raw-account :address address :name "Test")]]))))) -(cljs.test/run-tests) diff --git a/src/status_im/contexts/wallet/sheets/account_options/view.cljs b/src/status_im/contexts/wallet/sheets/account_options/view.cljs index 248f180a46..371419e9ff 100644 --- a/src/status_im/contexts/wallet/sheets/account_options/view.cljs +++ b/src/status_im/contexts/wallet/sheets/account_options/view.cljs @@ -108,9 +108,8 @@ [] (let [options-height (reagent/atom 0)] (fn [] - (let [theme (quo.theme/use-theme) - accounts (rf/sub - [:wallet/fully-or-partially-operable-accounts-without-current-viewing-account]) + (let [theme (quo.theme/use-theme) + accounts (rf/sub [:wallet/operable-accounts-without-current-viewing-account]) show-account-selector? (pos? (count accounts))] [:<> (when show-account-selector? diff --git a/src/status_im/contexts/wallet/sheets/select_account/view.cljs b/src/status_im/contexts/wallet/sheets/select_account/view.cljs index 3e79abe671..df468a6222 100644 --- a/src/status_im/contexts/wallet/sheets/select_account/view.cljs +++ b/src/status_im/contexts/wallet/sheets/select_account/view.cljs @@ -20,7 +20,7 @@ (defn view [] (let [selected-account-address (rf/sub [:wallet/current-viewing-account-address]) - accounts (rf/sub [:wallet/fully-or-partially-operable-accounts-without-watched-accounts])] + accounts (rf/sub [:wallet/operable-accounts-without-watched-accounts])] [:<> [quo/drawer-top {:title (i18n/label :t/select-account)}] [gesture/flat-list diff --git a/src/status_im/subs/community/account_selection.cljs b/src/status_im/subs/community/account_selection.cljs index a64039c4bc..a4220067ef 100644 --- a/src/status_im/subs/community/account_selection.cljs +++ b/src/status_im/subs/community/account_selection.cljs @@ -53,7 +53,7 @@ (re-frame/reg-sub :communities/accounts-to-reveal (fn [[_ community-id]] - [(re-frame/subscribe [:wallet/accounts-without-watched-accounts]) + [(re-frame/subscribe [:wallet/operable-accounts-without-watched-accounts]) (re-frame/subscribe [:communities/addresses-to-reveal community-id])]) (fn [[accounts addresses] _] (filter #(contains? addresses (:address %)) @@ -61,7 +61,7 @@ (re-frame/reg-sub :communities/airdrop-account (fn [[_ community-id]] - [(re-frame/subscribe [:wallet/accounts-without-watched-accounts]) + [(re-frame/subscribe [:wallet/operable-accounts-without-watched-accounts]) (re-frame/subscribe [:communities/airdrop-address community-id])]) (fn [[accounts airdrop-address] _] (->> accounts diff --git a/src/status_im/subs/community/account_selection_test.cljs b/src/status_im/subs/community/account_selection_test.cljs index 7fab36df3c..461215f63e 100644 --- a/src/status_im/subs/community/account_selection_test.cljs +++ b/src/status_im/subs/community/account_selection_test.cljs @@ -24,10 +24,10 @@ (h/deftest-sub :communities/airdrop-account [sub-name] - (let [airdrop-account {:address "0xA" :position 1}] + (let [airdrop-account {:address "0xA" :operable? true :position 1}] (reset! rf-db/app-db {:communities/all-airdrop-addresses {community-id "0xA"} - :wallet {:accounts {"0xB" {:address "0xB" :position 0} + :wallet {:accounts {"0xB" {:address "0xB" :operable? true :position 0} "0xA" airdrop-account}}}) (is (match? airdrop-account (rf/sub [sub-name community-id]))))) @@ -36,9 +36,11 @@ [sub-name] (reset! rf-db/app-db {:communities/all-addresses-to-reveal {community-id #{"0xC" "0xB"}} - :wallet {:accounts {"0xB" {:address "0xB" :position 0} - "0xA" {:address "0xA" :position 1} - "0xC" {:address "0xC" :position 2}}}}) + :wallet {:accounts {"0xB" {:address "0xB" :operable? true :position 0} + "0xA" {:address "0xA" :operable? true :position 1} + "0xC" {:address "0xC" + :operable? true + :position 2}}}}) (is (match? [{:address "0xB" :position 0} {:address "0xC" :position 2}] diff --git a/src/status_im/subs/wallet/wallet.cljs b/src/status_im/subs/wallet/wallet.cljs index e1b69acf50..b8b9b8bc75 100644 --- a/src/status_im/subs/wallet/wallet.cljs +++ b/src/status_im/subs/wallet/wallet.cljs @@ -454,25 +454,23 @@ (fn [accounts] (remove :watch-only? accounts))) -(defn- keep-fully-or-partially-operable-accounts +(defn- keep-operable-accounts [accounts] - (filter (fn fully-or-partially-operable? [{:keys [operable]}] - (#{:fully :partially} operable)) - accounts)) + (filter :operable? accounts)) (rf/reg-sub - :wallet/fully-or-partially-operable-accounts-without-current-viewing-account + :wallet/operable-accounts-without-current-viewing-account :<- [:wallet/accounts-without-current-viewing-account] - keep-fully-or-partially-operable-accounts) + keep-operable-accounts) (rf/reg-sub - :wallet/fully-or-partially-operable-accounts-without-watched-accounts + :wallet/operable-accounts-without-watched-accounts :<- [:wallet/accounts-without-watched-accounts] - keep-fully-or-partially-operable-accounts) + keep-operable-accounts) (rf/reg-sub :wallet/accounts-with-current-asset - :<- [:wallet/fully-or-partially-operable-accounts-without-watched-accounts] + :<- [:wallet/operable-accounts-without-watched-accounts] :<- [:wallet/wallet-send-token-symbol] :<- [:wallet/wallet-send-token] (fn [[accounts token-symbol token]] diff --git a/src/status_im/subs/wallet/wallet_test.cljs b/src/status_im/subs/wallet/wallet_test.cljs index 2fe56bda7f..5893fb6b62 100644 --- a/src/status_im/subs/wallet/wallet_test.cljs +++ b/src/status_im/subs/wallet/wallet_test.cljs @@ -16,10 +16,12 @@ {:0x1 {:tokens [{:symbol "ETH"} {:symbol "SNT"}] :network-preferences-names #{} :customization-color nil + :operable? true :operable :fully} :0x2 {:tokens [{:symbol "SNT"}] :network-preferences-names #{} :customization-color nil + :operable? true :operable :partially}}) (def tokens-0x1 @@ -106,6 +108,7 @@ :name "Account One" :type :generated :watch-only? false + :operable? true :chat false :test-preferred-chain-ids #{5 420 421613} :color :blue @@ -127,6 +130,7 @@ :name "Account Two" :type :generated :watch-only? false + :operable? true :chat false :test-preferred-chain-ids #{5 420 421613} :color :purple @@ -148,6 +152,7 @@ :name "Watched Account 1" :type :watch :watch-only? true + :operable? true :chat false :test-preferred-chain-ids #{0} :color :magenta @@ -219,7 +224,7 @@ (assoc-in [:wallet :accounts] accounts) (assoc-in [:wallet :networks] network-data))) (is - (= + (match? (list {:path "m/44'/60'/0'/0/0" :emoji "😃" :key-uid "0x2f5ea39" @@ -228,6 +233,7 @@ :name "Account One" :type :generated :watch-only? false + :operable? true :chat false :test-preferred-chain-ids #{5 420 421613} :color :blue @@ -250,6 +256,7 @@ :name "Account Two" :type :generated :watch-only? false + :operable? true :chat false :test-preferred-chain-ids #{5 420 421613} :color :purple @@ -272,6 +279,7 @@ :name "Watched Account 1" :type :watch :watch-only? true + :operable? true :chat false :test-preferred-chain-ids #{0} :color :magenta @@ -307,29 +315,30 @@ (let [result (rf/sub [sub-name])] (is - (= {:path "m/44'/60'/0'/0/0" - :emoji "😃" - :key-uid "0x2f5ea39" - :address "0x1" - :wallet false - :name "Account One" - :type :generated - :watch-only? false - :chat false - :test-preferred-chain-ids #{5 420 421613} - :color :blue - :hidden false - :prod-preferred-chain-ids #{1 10 42161} - :network-preferences-names #{:mainnet :arbitrum :optimism} - :position 0 - :clock 1698945829328 - :created-at 1698928839000 - :operable :fully - :mixedcase-address "0x7bcDfc75c431" - :public-key "0x04371e2d9d66b82f056bc128064" - :removed false - :tokens tokens-0x1} - (dissoc result :balance :formatted-balance))) + (match? {:path "m/44'/60'/0'/0/0" + :emoji "😃" + :key-uid "0x2f5ea39" + :address "0x1" + :wallet false + :name "Account One" + :type :generated + :watch-only? false + :operable? true + :chat false + :test-preferred-chain-ids #{5 420 421613} + :color :blue + :hidden false + :prod-preferred-chain-ids #{1 10 42161} + :network-preferences-names #{:mainnet :arbitrum :optimism} + :position 0 + :clock 1698945829328 + :created-at 1698928839000 + :operable :fully + :mixedcase-address "0x7bcDfc75c431" + :public-key "0x04371e2d9d66b82f056bc128064" + :removed false + :tokens tokens-0x1} + (dissoc result :balance :formatted-balance))) (is (money/equal-to (:balance result) (money/bignumber 3250))) (is (match? (:formatted-balance result) "$3250.00"))))) @@ -367,62 +376,7 @@ (assoc-in [:wallet :current-viewing-account-address] "0x2") (assoc-in [:wallet :networks] network-data))) (is - (= (list - {:path "m/44'/60'/0'/0/0" - :emoji "😃" - :key-uid "0x2f5ea39" - :address "0x1" - :wallet false - :name "Account One" - :type :generated - :watch-only? false - :chat false - :test-preferred-chain-ids #{5 420 421613} - :color :blue - :hidden false - :prod-preferred-chain-ids #{1 10 42161} - :network-preferences-names #{:mainnet :arbitrum :optimism} - :position 0 - :clock 1698945829328 - :created-at 1698928839000 - :operable :fully - :mixedcase-address "0x7bcDfc75c431" - :public-key "0x04371e2d9d66b82f056bc128064" - :removed false - :tokens tokens-0x1} - {:path "" - :emoji "🎉" - :key-uid "0x2f5ea39" - :address "0x3" - :wallet false - :name "Watched Account 1" - :type :watch - :watch-only? true - :chat false - :test-preferred-chain-ids #{0} - :color :magenta - :hidden false - :prod-preferred-chain-ids #{0} - :network-preferences-names #{} - :position 2 - :clock 1698945829328 - :created-at 1698928839000 - :operable :fully - :mixedcase-address "0x7bcDfc75c431" - :public-key "0x" - :removed false - :tokens tokens-0x3}) - (rf/sub [sub-name]))))) - -(h/deftest-sub :wallet/accounts-without-watched-accounts - [sub-name] - (testing "returns the accounts list without the watched accounts in it" - (swap! rf-db/app-db - #(-> % - (assoc-in [:wallet :accounts] accounts) - (assoc-in [:wallet :networks] network-data))) - (is - (= + (match? (list {:path "m/44'/60'/0'/0/0" :emoji "😃" @@ -432,6 +386,65 @@ :name "Account One" :type :generated :watch-only? false + :operable? true + :chat false + :test-preferred-chain-ids #{5 420 421613} + :color :blue + :hidden false + :prod-preferred-chain-ids #{1 10 42161} + :network-preferences-names #{:mainnet :arbitrum :optimism} + :position 0 + :clock 1698945829328 + :created-at 1698928839000 + :operable :fully + :mixedcase-address "0x7bcDfc75c431" + :public-key "0x04371e2d9d66b82f056bc128064" + :removed false + :tokens tokens-0x1} + {:path "" + :emoji "🎉" + :key-uid "0x2f5ea39" + :address "0x3" + :wallet false + :name "Watched Account 1" + :type :watch + :watch-only? true + :operable? true + :chat false + :test-preferred-chain-ids #{0} + :color :magenta + :hidden false + :prod-preferred-chain-ids #{0} + :network-preferences-names #{} + :position 2 + :clock 1698945829328 + :created-at 1698928839000 + :operable :fully + :mixedcase-address "0x7bcDfc75c431" + :public-key "0x" + :removed false + :tokens tokens-0x3}) + (rf/sub [sub-name]))))) + +(h/deftest-sub :wallet/accounts-without-watched-accounts + [sub-name] + (testing "returns the accounts list without the watched accounts in it" + (swap! rf-db/app-db + #(-> % + (assoc-in [:wallet :accounts] accounts) + (assoc-in [:wallet :networks] network-data))) + (is + (match? + (list + {:path "m/44'/60'/0'/0/0" + :emoji "😃" + :key-uid "0x2f5ea39" + :address "0x1" + :wallet false + :name "Account One" + :type :generated + :watch-only? false + :operable? true :chat false :test-preferred-chain-ids #{5 420 421613} :color :blue @@ -455,6 +468,7 @@ :name "Account Two" :type :generated :watch-only? false + :operable? true :chat false :test-preferred-chain-ids #{5 420 421613} :color :purple @@ -484,6 +498,7 @@ [{:tokens [{:symbol "ETH"} {:symbol "SNT"}] :network-preferences-names #{} :customization-color nil + :operable? true :operable :fully}])))) (testing "returns the accounts list with the current asset using token" @@ -496,6 +511,7 @@ [{:tokens [{:symbol "ETH"} {:symbol "SNT"}] :network-preferences-names #{} :customization-color nil + :operable? true :operable :fully}])))) (testing @@ -576,19 +592,19 @@ (assoc-in [:wallet :accounts] accounts) (assoc-in [:wallet :networks] network-data))) (is - (= [(-> accounts - (get "0x1") - (assoc :customization-color :blue) - (assoc :network-preferences-names #{:mainnet :arbitrum :optimism})) - (-> accounts - (get "0x2") - (assoc :customization-color :purple) - (assoc :network-preferences-names #{:mainnet :arbitrum :optimism})) - (-> accounts - (get "0x3") - (assoc :customization-color :magenta) - (assoc :network-preferences-names #{}))] - (rf/sub [sub-name]))))) + (match? [(-> accounts + (get "0x1") + (assoc :customization-color :blue) + (assoc :network-preferences-names #{:mainnet :arbitrum :optimism})) + (-> accounts + (get "0x2") + (assoc :customization-color :purple) + (assoc :network-preferences-names #{:mainnet :arbitrum :optimism})) + (-> accounts + (get "0x3") + (assoc :customization-color :magenta) + (assoc :network-preferences-names #{}))] + (rf/sub [sub-name]))))) (h/deftest-sub :wallet/watch-only-accounts [sub-name] @@ -598,10 +614,10 @@ (assoc-in [:wallet :accounts] accounts) (assoc-in [:wallet :networks] network-data))) (is - (= [(-> accounts - (get "0x3") - (assoc :network-preferences-names #{}))] - (rf/sub [sub-name]))))) + (match? [(-> accounts + (get "0x3") + (assoc :network-preferences-names #{}))] + (rf/sub [sub-name]))))) (def chat-account {:path "m/43'/60'/1581'/0'/0" @@ -811,9 +827,7 @@ (testing "returns local suggestions:" (swap! rf-db/app-db #(assoc-in % [:wallet :ui :search-address :local-suggestions] local-suggestions)) - (is - (= local-suggestions - (rf/sub [sub-name]))))) + (is (match? local-suggestions (rf/sub [sub-name]))))) (h/deftest-sub :wallet/valid-ens-or-address? [sub-name] From 20b256b0d056aaf11c3d73de9e1cc6fc71180b1d Mon Sep 17 00:00:00 2001 From: Ajay Sivan Date: Fri, 12 Jul 2024 16:45:37 +0530 Subject: [PATCH 32/79] Settings/Data Item - editable variant (#20656) --- .../settings/data_item/component_spec.cljs | 17 +++++++- .../components/settings/data_item/style.cljs | 3 +- .../components/settings/data_item/view.cljs | 43 +++++++++++++++++-- .../preview/quo/settings/data_item.cljs | 3 +- .../wallet/account/tabs/about/view.cljs | 2 +- 5 files changed, 61 insertions(+), 7 deletions(-) diff --git a/src/quo/components/settings/data_item/component_spec.cljs b/src/quo/components/settings/data_item/component_spec.cljs index 600f4f0896..aa0857d619 100644 --- a/src/quo/components/settings/data_item/component_spec.cljs +++ b/src/quo/components/settings/data_item/component_spec.cljs @@ -159,4 +159,19 @@ :icon :i/placeholder :emoji "🎮" :customization-color :yellow}]) - (h/is-truthy (h/query-by-label-text :account-emoji)))) + (h/is-truthy (h/query-by-label-text :account-emoji))) + + (h/test "edit icon is visible when subtitle type is editable" + (h/render [quo/data-item + {:on-press (h/mock-fn) + :blur? false + :card? true + :status :default + :size :default + :title "Label" + :subtitle "Subtitle" + :subtitle-type :editable + :icon :i/placeholder + :emoji "🎮" + :customization-color :yellow}]) + (h/is-truthy (h/query-by-label-text :edit-icon)))) diff --git a/src/quo/components/settings/data_item/style.cljs b/src/quo/components/settings/data_item/style.cljs index dd82e6a0ff..007f1b785d 100644 --- a/src/quo/components/settings/data_item/style.cljs +++ b/src/quo/components/settings/data_item/style.cljs @@ -54,6 +54,7 @@ (def subtitle-container {:flex-direction :row + :align-items :center :margin-bottom 1}) (def right-container @@ -62,7 +63,7 @@ (defn subtitle-icon-container [subtitle-type] - {:margin-right (when (not= :default subtitle-type) 4) + {:margin-right (when-not (contains? #{:editable :default} subtitle-type) 4) :justify-content :center}) (defn title diff --git a/src/quo/components/settings/data_item/view.cljs b/src/quo/components/settings/data_item/view.cljs index e99104498a..712a577d48 100644 --- a/src/quo/components/settings/data_item/view.cljs +++ b/src/quo/components/settings/data_item/view.cljs @@ -7,7 +7,8 @@ [quo.components.settings.data-item.style :as style] [quo.foundations.colors :as colors] [quo.theme :as quo.theme] - [react-native.core :as rn])) + [react-native.core :as rn] + [schema.core :as schema])) (defn- left-loading [{:keys [size blur?]}] @@ -40,7 +41,17 @@ {:weight :medium :size :paragraph-2 :style (style/description blur? theme)} - subtitle]])) + subtitle] + (when (= subtitle-type :editable) + [icons/icon :i/edit + {:accessibility-label :edit-icon + :size 12 + :container-style {:margin-left 2} + :color (if blur? + colors/neutral-40 + (colors/theme-colors colors/neutral-50 + colors/neutral-40 + theme))}])])) (defn- left-title [{:keys [title blur?]}] @@ -100,7 +111,31 @@ :color icon-color :size 20}]])])) -(defn view + +(def ?schema + [:=> + [:catn + [:props + [:map {:closed true} + [:blur? {:optional true} [:maybe :boolean]] + [:card? {:optional true} [:maybe :boolean]] + [:right-icon {:optional true} [:maybe :keyword]] + [:right-content {:optional true} [:maybe :map]] + [:status {:optional true} [:maybe [:enum :default :loading]]] + [:subtitle-type {:optional true} [:maybe [:enum :default :icon :network :account :editable]]] + [:size {:optional true} [:maybe [:enum :default :small :large]]] + [:title :string] + [:subtitle {:optional true} [:maybe :string]] + [:custom-subtitle {:optional true} [:maybe fn?]] + [:icon {:optional true} [:maybe :keyword]] + [:emoji {:optional true} [:maybe :string]] + [:customization-color {:optional true} [:maybe :schema.common/customization-color]] + [:network-image {:optional true} [:maybe :schema.common/image-source]] + [:on-press {:optional true} [:maybe fn?]] + [:container-style {:optional true} [:maybe :map]]]]] + :any]) + +(defn view-internal [{:keys [blur? card? right-icon right-content status size on-press container-style] :as props}] (let [theme (quo.theme/use-theme) @@ -123,3 +158,5 @@ {:right-icon right-icon :right-content right-content :icon-color icon-color}])])) + +(def view (schema/instrument #'view-internal ?schema)) diff --git a/src/status_im/contexts/preview/quo/settings/data_item.cljs b/src/status_im/contexts/preview/quo/settings/data_item.cljs index 36d171dd61..0714391bc3 100644 --- a/src/status_im/contexts/preview/quo/settings/data_item.cljs +++ b/src/status_im/contexts/preview/quo/settings/data_item.cljs @@ -47,7 +47,8 @@ :options [{:key :default} {:key :icon} {:key :network} - {:key :account}]} + {:key :account} + {:key :editable}]} {:type :select :key :status :options [{:key :default} diff --git a/src/status_im/contexts/wallet/account/tabs/about/view.cljs b/src/status_im/contexts/wallet/account/tabs/about/view.cljs index 5c8534de0b..21a3ed124b 100644 --- a/src/status_im/contexts/wallet/account/tabs/about/view.cljs +++ b/src/status_im/contexts/wallet/account/tabs/about/view.cljs @@ -83,7 +83,7 @@ {:style style/about-tab :content-container-style {:padding-bottom (+ constants/floating-shell-button-height 8)}} [quo/data-item - {:description :default + {:subtitle-type :default :right-icon :i/options :card? true :status :default From 6553d5ca447e9ffbf0a04f2b166381214ba5ddac Mon Sep 17 00:00:00 2001 From: Parvesh Monu Date: Fri, 12 Jul 2024 16:59:44 +0530 Subject: [PATCH 33/79] fix peer count invisible in Light mode (#20733) --- src/legacy/status_im/ui/screens/peers_stats.cljs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/legacy/status_im/ui/screens/peers_stats.cljs b/src/legacy/status_im/ui/screens/peers_stats.cljs index cfa20c734d..5ddc6c4127 100644 --- a/src/legacy/status_im/ui/screens/peers_stats.cljs +++ b/src/legacy/status_im/ui/screens/peers_stats.cljs @@ -1,12 +1,14 @@ (ns legacy.status-im.ui.screens.peers-stats (:require + [quo.foundations.colors :as colors] [react-native.core :as rn] [utils.i18n :as i18n] [utils.re-frame :as rf])) (defn peers-stats [] - (let [peers-count (rf/sub [:peer-stats/count])] + (let [peers-count (rf/sub [:peer-stats/count]) + theme (rf/sub [:theme])] (rn/use-mount (fn [] (rf/dispatch [:peer-stats/get-count]))) @@ -18,4 +20,5 @@ {:style {:flex-direction :row :margin-vertical 8 :justify-content :space-between}} - [rn/text (str (i18n/label :t/peers-count) ": " peers-count)]]])) + [rn/text {:style {:color (colors/theme-colors colors/neutral-100 colors/white theme)}} + (str (i18n/label :t/peers-count) ": " peers-count)]]])) From 0de4c8cabb5bb0d96a7e0074f33c881e18173ad5 Mon Sep 17 00:00:00 2001 From: Ajay Sivan Date: Fri, 12 Jul 2024 17:25:36 +0530 Subject: [PATCH 34/79] List-Items/Approval-Info Component (#20317) --- .../avatars/collection_avatar/style.cljs | 1 + .../approval_info/component_spec.cljs | 57 ++++++++ .../list_items/approval_info/style.cljs | 36 +++++ .../list_items/approval_info/view.cljs | 124 ++++++++++++++++++ src/quo/core.cljs | 2 + src/quo/core_spec.cljs | 1 + .../preview/quo/list_items/approval_info.cljs | 80 +++++++++++ src/status_im/contexts/preview/quo/main.cljs | 3 + 8 files changed, 304 insertions(+) create mode 100644 src/quo/components/list_items/approval_info/component_spec.cljs create mode 100644 src/quo/components/list_items/approval_info/style.cljs create mode 100644 src/quo/components/list_items/approval_info/view.cljs create mode 100644 src/status_im/contexts/preview/quo/list_items/approval_info.cljs diff --git a/src/quo/components/avatars/collection_avatar/style.cljs b/src/quo/components/avatars/collection_avatar/style.cljs index aebc90899c..51edd5c185 100644 --- a/src/quo/components/avatars/collection_avatar/style.cljs +++ b/src/quo/components/avatars/collection_avatar/style.cljs @@ -5,6 +5,7 @@ (defn- get-dimensions [size] (case size + :size-32 32 :size-24 24 :size-20 20 nil)) diff --git a/src/quo/components/list_items/approval_info/component_spec.cljs b/src/quo/components/list_items/approval_info/component_spec.cljs new file mode 100644 index 0000000000..bb67767081 --- /dev/null +++ b/src/quo/components/list_items/approval_info/component_spec.cljs @@ -0,0 +1,57 @@ +(ns quo.components.list-items.approval-info.component-spec + (:require [quo.components.list-items.approval-info.view :as approval-info] + [test-helpers.component :as h])) + +(h/describe "List Items: Approval Info" + (h/test "should render correctly with basic props" + (h/render-with-theme-provider + [approval-info/view + {:type :spending-cap + :label "Spending Cap" + :avatar-props {:image "image"}}]) + (h/is-truthy (h/get-by-label-text :approval-info))) + + (h/test "should render correctly with label & description" + (h/render-with-theme-provider + [approval-info/view + {:type :spending-cap + :label "Spending Cap" + :description "Description" + :avatar-props {:image "image"}}]) + (h/is-truthy (h/get-by-text "Spending Cap")) + (h/is-truthy (h/get-by-text "Description"))) + + (h/test "on-button-press event is called when button is pressed" + (let [on-button-press (h/mock-fn)] + (h/render-with-theme-provider + [approval-info/view + {:type :spending-cap + :label "Spending Cap" + :button-label "Edit" + :on-button-press on-button-press + :avatar-props {:image "image"}}]) + (h/fire-event :press (h/get-by-text "Edit")) + (h/was-called on-button-press))) + + (h/test "on-option-press event is called when option icon is pressed" + (let [on-option-press (h/mock-fn)] + (h/render-with-theme-provider + [approval-info/view + {:type :spending-cap + :label "Spending Cap" + :option-icon :i/options + :on-option-press on-option-press + :avatar-props {:image "image"}}]) + (h/fire-event :press (h/get-by-label-text :icon)) + (h/was-called on-option-press))) + + (h/test "on-avatar-press event is called when avatar is pressed" + (let [on-avatar-press (h/mock-fn)] + (h/render-with-theme-provider + [approval-info/view + {:type :spending-cap + :label "Spending Cap" + :on-avatar-press on-avatar-press + :avatar-props {:image "image"}}]) + (h/fire-event :press (h/get-by-label-text :token-avatar)) + (h/was-called on-avatar-press)))) diff --git a/src/quo/components/list_items/approval_info/style.cljs b/src/quo/components/list_items/approval_info/style.cljs new file mode 100644 index 0000000000..38e81d43e1 --- /dev/null +++ b/src/quo/components/list_items/approval_info/style.cljs @@ -0,0 +1,36 @@ +(ns quo.components.list-items.approval-info.style + (:require [quo.foundations.colors :as colors])) + +(defn icon-description-color + [blur? theme] + (if blur? + colors/white-opa-40 + (colors/theme-colors colors/neutral-50 colors/neutral-40 theme))) + +(defn container + [description? blur? theme] + {:flex-direction :row + :padding-horizontal 12 + :padding-vertical (if description? 8 12) + :border-radius 16 + :gap 8 + :align-items :center + :border-width 1 + :border-color (if blur? + colors/white-opa-5 + (colors/theme-colors colors/neutral-10 colors/neutral-80 theme))}) + +(def labels + {:flex 1 + :justify-content :center + :padding-right 4}) + +(defn label + [blur? theme] + {:color (if blur? + colors/white + (colors/theme-colors colors/neutral-100 colors/white theme))}) + +(defn description + [blur? theme] + {:color (icon-description-color blur? theme)}) diff --git a/src/quo/components/list_items/approval_info/view.cljs b/src/quo/components/list_items/approval_info/view.cljs new file mode 100644 index 0000000000..93c912969d --- /dev/null +++ b/src/quo/components/list_items/approval_info/view.cljs @@ -0,0 +1,124 @@ +(ns quo.components.list-items.approval-info.view + (:require [clojure.string :as string] + [quo.components.avatars.account-avatar.view :as account-avatar] + [quo.components.avatars.collection-avatar.view :as collection-avatar] + [quo.components.avatars.community-avatar.view :as community-avatar] + [quo.components.avatars.dapp-avatar.view :as dapp-avatar] + [quo.components.avatars.icon-avatar :as icon-avatar] + [quo.components.avatars.token-avatar.view :as token-avatar] + [quo.components.avatars.wallet-user-avatar.view :as wallet-user-avatar] + [quo.components.buttons.button.view :as button] + [quo.components.icon :as icon] + [quo.components.list-items.approval-info.style :as style] + [quo.components.markdown.text :as text] + [quo.components.tags.tiny-tag.view :as tiny-tag] + [quo.foundations.colors :as colors] + quo.theme + [react-native.core :as rn] + [schema.core :as schema])) + +(def ?schema + [:=> + [:catn + [:props + [:map {:closed true} + [:type + [:enum :spending-cap :token-contract :account :spending-contract :network :date-signed + :collectible :address :community :collectible-contract]] + [:avatar-props :map] + [:label :string] + [:description {:optional true} [:maybe :string]] + [:button-label {:optional true} [:maybe :string]] + [:button-icon {:optional true} [:maybe :keyword]] + [:blur? {:optional true} [:maybe :boolean]] + [:unlimited-icon? {:optional true} [:maybe :boolean]] + [:option-icon {:optional true} [:maybe :keyword]] + [:tag-label {:optional true} [:maybe :string]] + [:on-button-press {:optional true} [:maybe fn?]] + [:on-avatar-press {:optional true} [:maybe fn?]] + [:on-option-press {:optional true} [:maybe fn?]] + [:container-style {:optional true} [:maybe :any]]]]] + :any]) + +(defn- avatar + [{:keys [type avatar-props blur? theme on-press]}] + [rn/pressable {:on-press on-press} + (case type + :account [account-avatar/view (assoc avatar-props :size 32)] + :collectible-contract [collection-avatar/view (assoc avatar-props :size :size-32)] + :spending-contract [dapp-avatar/view avatar-props] + :date-signed [icon-avatar/icon-avatar + (assoc avatar-props + :size :size-32 + :opacity 10 + :color (if blur? + colors/white + (colors/theme-colors colors/neutral-50 + colors/neutral-40 + theme)))] + :address [wallet-user-avatar/wallet-user-avatar + (assoc avatar-props + :size :size-32 + :monospace? true + :lowercase? true + :neutral? true)] + :community [community-avatar/view avatar-props] + [token-avatar/view + (assoc avatar-props + :type + (if (= type :collectible) :collectible :asset))])]) + +(defn- view-internal + [{:keys [type avatar-props label description blur? unlimited-icon? container-style + on-option-press on-avatar-press on-button-press button-label button-icon tag-label + option-icon]}] + (let [theme (quo.theme/use-theme) + description? (not (string/blank? description))] + [rn/view + {:style (merge (style/container description? blur? theme) container-style) + :accessibility-label :approval-info} + [avatar + {:type type + :blur? blur? + :theme theme + :on-press on-avatar-press + :avatar-props avatar-props}] + [rn/view + {:style style/labels} + [rn/view + {:style {:flex-direction :row :align-items :center}} + [text/text + {:size :paragraph-1 + :weight (if (= type :address) :monospace :semi-bold) + :style (style/label blur? theme)} + label] + (when unlimited-icon? + [icon/icon :i/alert + {:container-style {:margin-left 4} + :size 16 + :color (style/icon-description-color blur? theme)}])] + (when description? + [text/text + {:size :paragraph-2 + :weight (if (contains? #{:collectible-contract :spending-contract :account :token-contract} + type) + :monospace + :regular) + :style (style/description blur? theme)} + description])] + (when (= type :account) [tiny-tag/view {:label tag-label}]) + (when (= type :spending-cap) + [button/button + {:type :outline + :size 24 + :blur? blur? + :icon-left button-icon + :on-press on-button-press} + button-label]) + (when option-icon + [rn/pressable {:on-press on-option-press} + [icon/icon option-icon + {:color (style/icon-description-color blur? theme) + :size 20}]])])) + +(def view (schema/instrument #'view-internal ?schema)) diff --git a/src/quo/core.cljs b/src/quo/core.cljs index 9ea0c9a71b..dd01cf0b75 100644 --- a/src/quo/core.cljs +++ b/src/quo/core.cljs @@ -86,6 +86,7 @@ quo.components.list-items.account-list-card.view quo.components.list-items.account.view quo.components.list-items.address.view + quo.components.list-items.approval-info.view quo.components.list-items.channel.view quo.components.list-items.community.view quo.components.list-items.dapp.view @@ -334,6 +335,7 @@ (def account-item quo.components.list-items.account.view/view) (def account-list-card quo.components.list-items.account-list-card.view/view) (def address quo.components.list-items.address.view/view) +(def approval-info quo.components.list-items.approval-info.view/view) (def channel quo.components.list-items.channel.view/view) (def community-list quo.components.list-items.community.view/view) (def dapp quo.components.list-items.dapp.view/view) diff --git a/src/quo/core_spec.cljs b/src/quo/core_spec.cljs index 6ba9fb8e09..aa685d80c5 100644 --- a/src/quo/core_spec.cljs +++ b/src/quo/core_spec.cljs @@ -55,6 +55,7 @@ quo.components.links.url-preview.component-spec quo.components.list-items.account.component-spec quo.components.list-items.address.component-spec + quo.components.list-items.approval-info.component-spec quo.components.list-items.channel.component-spec quo.components.list-items.community.component-spec quo.components.list-items.dapp.component-spec diff --git a/src/status_im/contexts/preview/quo/list_items/approval_info.cljs b/src/status_im/contexts/preview/quo/list_items/approval_info.cljs new file mode 100644 index 0000000000..a716a99388 --- /dev/null +++ b/src/status_im/contexts/preview/quo/list_items/approval_info.cljs @@ -0,0 +1,80 @@ +(ns status-im.contexts.preview.quo.list-items.approval-info + (:require + [quo.core :as quo] + [quo.foundations.resources :as resources] + [react-native.core :as rn] + [status-im.common.resources :as common.resources] + [status-im.contexts.preview.quo.preview :as preview])) + +(def descriptor + [{:type :select + :key :type + :options [{:key :spending-cap} + {:key :token-contract} + {:key :account} + {:key :spending-contract} + {:key :network} + {:key :date-signed} + {:key :collectible} + {:key :collectible-contract} + {:key :address} + {:key :community}]} + {:type :text + :key :label} + {:type :text + :key :description} + {:type :boolean + :key :blur?} + {:type :boolean + :key :unlimited-icon?} + {:type :text + :key :button-label} + {:type :text + :key :tag-label} + {:type :select + :key :option-icon + :options [{:key nil + :value "None"} + {:key :i/options} + {:key :i/chevron-right}]}]) + +(defn- get-avatar-props + [type] + (case type + :collectible {:image (common.resources/get-mock-image :collectible2)} + :account {:customization-color :orange + :emoji "😇"} + :collectible-contract {:image (common.resources/get-mock-image :bored-ape)} + :spending-contract {:network-image (resources/get-network :ethereum) + :image (resources/get-dapp :coingecko)} + :date-signed {:icon :i/signature} + :address {:customization-color :blue + :full-name "0 x"} + :community {:image (common.resources/get-mock-image :status-logo) + :size :size-32} + {:image (common.resources/get-mock-image :status-logo)})) + +(defn view + [] + (let [[state set-state] (rn/use-state {:type :spending-cap + :label "Label" + :description "Description" + :blur? false + :unlimited-icon? false + :button-label "Edit" + :tag-label "31,283.77 EUR" + :option-icon :i/options})] + [preview/preview-container + {:state state + :set-state set-state + :blur? (:blur? state) + :show-blur-background? true + :blur-dark-only? true + :descriptor descriptor} + [quo/approval-info + (assoc state + :button-icon :i/edit + :on-button-press #(js/alert "Button Pressed") + :on-avatar-press #(js/alert "Token Pressed") + :avatar-props (get-avatar-props (:type state)) + :on-option-press #(js/alert "Option Pressed"))]])) diff --git a/src/status_im/contexts/preview/quo/main.cljs b/src/status_im/contexts/preview/quo/main.cljs index 1875c47d8e..2f6ff77299 100644 --- a/src/status_im/contexts/preview/quo/main.cljs +++ b/src/status_im/contexts/preview/quo/main.cljs @@ -105,6 +105,7 @@ [status-im.contexts.preview.quo.list-items.account-list-card :as account-list-card] [status-im.contexts.preview.quo.list-items.address :as address] + [status-im.contexts.preview.quo.list-items.approval-info :as approval-info] [status-im.contexts.preview.quo.list-items.channel :as channel] [status-im.contexts.preview.quo.list-items.dapp :as dapp] [status-im.contexts.preview.quo.list-items.missing-keypair :as missing-keypair] @@ -390,6 +391,8 @@ :component account-list-card/view} {:name :address :component address/view} + {:name :approval-info + :component approval-info/view} {:name :channel :component channel/view} {:name :community-list From 087fcb1010f43a8c997abebeb8ac4cefd8f4b875 Mon Sep 17 00:00:00 2001 From: Omar Basem Date: Fri, 12 Jul 2024 16:07:45 +0400 Subject: [PATCH 35/79] Fix(wallet): navigation after transaction from settings (#20676) * fix(wallet): navigation after transaction from settings (#20676) --- .../sheets/address_options/view.cljs | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/status_im/contexts/settings/wallet/saved_addresses/sheets/address_options/view.cljs b/src/status_im/contexts/settings/wallet/saved_addresses/sheets/address_options/view.cljs index c760394f68..e722d7e61b 100644 --- a/src/status_im/contexts/settings/wallet/saved_addresses/sheets/address_options/view.cljs +++ b/src/status_im/contexts/settings/wallet/saved_addresses/sheets/address_options/view.cljs @@ -15,13 +15,19 @@ [{:keys [name full-address chain-short-names address] :as opts}] (let [[_ splitted-address] (network-utils/split-network-full-address address) open-send-flow (rn/use-callback - #(rf/dispatch [:wallet/select-send-address - {:address full-address - :recipient {:label (utils/get-shortened-address - splitted-address) - :recipient-type :saved-address} - :stack-id :wallet-select-address - :start-flow? true}]) + (fn [] + (rf/dispatch [:hide-bottom-sheet]) + (rf/dispatch [:pop-to-root :shell-stack]) + (js/setTimeout #(rf/dispatch [:wallet/select-send-address + {:address full-address + :recipient + {:label + (utils/get-shortened-address + splitted-address) + :recipient-type :saved-address} + :stack-id :wallet-select-address + :start-flow? true}]) + 400)) [full-address]) open-eth-chain-explorer (rn/use-callback #(rf/dispatch [:wallet/navigate-to-chain-explorer From 0e6d59b31262b74a0991240e83075f638c42b833 Mon Sep 17 00:00:00 2001 From: Omar Basem Date: Fri, 12 Jul 2024 17:09:42 +0400 Subject: [PATCH 36/79] fix: hardware induced back actions (#20183) * fix: hardware induced back actions (#20183) --- src/status_im/contexts/wallet/account/view.cljs | 8 +++++--- .../create_account/import_private_key/view.cljs | 3 ++- .../wallet/add_account/create_account/view.cljs | 3 ++- .../contexts/wallet/bridge/bridge_to/view.cljs | 3 ++- .../contexts/wallet/bridge/input_amount/view.cljs | 6 +++--- src/status_im/contexts/wallet/events.cljs | 12 +++++++----- src/status_im/contexts/wallet/send/events.cljs | 14 +++++++++++--- src/status_im/contexts/wallet/send/from/view.cljs | 8 ++++---- .../contexts/wallet/send/input_amount/view.cljs | 4 +++- .../contexts/wallet/send/select_address/view.cljs | 7 ++++--- .../contexts/wallet/send/select_asset/view.cljs | 7 ++++--- .../contexts/wallet/send/send_amount/view.cljs | 3 +-- .../wallet/swap/select_asset_to_pay/view.cljs | 5 +++-- src/status_im/subs/wallet/send.cljs | 5 +++++ 14 files changed, 56 insertions(+), 32 deletions(-) diff --git a/src/status_im/contexts/wallet/account/view.cljs b/src/status_im/contexts/wallet/account/view.cljs index a466345f8b..f11421d1ef 100644 --- a/src/status_im/contexts/wallet/account/view.cljs +++ b/src/status_im/contexts/wallet/account/view.cljs @@ -7,6 +7,7 @@ [status-im.contexts.wallet.common.account-switcher.view :as account-switcher] [status-im.contexts.wallet.sheets.buy-token.view :as buy-token] [status-im.feature-flags :as ff] + [status-im.setup.hot-reload :as hot-reload] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -28,14 +29,15 @@ {:keys [name color formatted-balance watch-only?]} (rf/sub [:wallet/current-viewing-account]) customization-color (rf/sub [:profile/customization-color])] - (rn/use-unmount #(rf/dispatch [:wallet/clean-send-data])) + (hot-reload/use-safe-unmount (fn [] + (rf/dispatch [:wallet/close-account-page]) + (rf/dispatch [:wallet/clean-current-viewing-account]))) (rn/use-mount #(rf/dispatch [:wallet/fetch-activities-for-current-account])) [rn/view {:style {:flex 1}} [account-switcher/view {:type :wallet-networks - :on-press (fn [] - (rf/dispatch [:wallet/close-account-page]))}] + :on-press #(rf/dispatch [:pop-to-root :shell-stack])}] [quo/account-overview {:container-style style/account-overview :current-value formatted-balance diff --git a/src/status_im/contexts/wallet/add_account/create_account/import_private_key/view.cljs b/src/status_im/contexts/wallet/add_account/create_account/import_private_key/view.cljs index 0b7d205d75..508f8e90d1 100644 --- a/src/status_im/contexts/wallet/add_account/create_account/import_private_key/view.cljs +++ b/src/status_im/contexts/wallet/add_account/create_account/import_private_key/view.cljs @@ -8,6 +8,7 @@ [status-im.common.floating-button-page.view :as floating-button-page] [status-im.contexts.wallet.add-account.create-account.import-private-key.style :as style] [status-im.contexts.wallet.common.validation :as v] + [status-im.setup.hot-reload :as hot-reload] [utils.debounce :as debounce] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -112,7 +113,7 @@ public-address (rf/sub [:wallet/public-address]) [flow-state set-flow-state] (rn/use-state nil) error? (= :invalid-private-key flow-state)] - (rn/use-unmount on-unmount) + (hot-reload/use-safe-unmount on-unmount) [rn/view {:flex 1} [floating-button-page/view {:customization-color customization-color diff --git a/src/status_im/contexts/wallet/add_account/create_account/view.cljs b/src/status_im/contexts/wallet/add_account/create_account/view.cljs index c0af91a51f..4fca1cca93 100644 --- a/src/status_im/contexts/wallet/add_account/create_account/view.cljs +++ b/src/status_im/contexts/wallet/add_account/create_account/view.cljs @@ -15,6 +15,7 @@ [status-im.contexts.wallet.common.utils :as common.utils] [status-im.contexts.wallet.sheets.account-origin.view :as account-origin] [status-im.feature-flags :as ff] + [status-im.setup.hot-reload :as hot-reload] [utils.i18n :as i18n] [utils.re-frame :as rf] [utils.responsiveness :as responsiveness] @@ -313,7 +314,7 @@ error (or @account-name-error @emoji-and-color-error?)] (rn/use-mount #(check-emoji-and-color-error @emoji @account-color)) - (rn/use-unmount #(rf/dispatch [:wallet/clear-create-account])) + (hot-reload/use-safe-unmount #(rf/dispatch [:wallet/clear-create-account])) (if keypair-name [add-new-keypair-variant diff --git a/src/status_im/contexts/wallet/bridge/bridge_to/view.cljs b/src/status_im/contexts/wallet/bridge/bridge_to/view.cljs index 8a8387146f..1aa2d9e339 100644 --- a/src/status_im/contexts/wallet/bridge/bridge_to/view.cljs +++ b/src/status_im/contexts/wallet/bridge/bridge_to/view.cljs @@ -9,6 +9,7 @@ [status-im.contexts.wallet.common.account-switcher.view :as account-switcher] [status-im.contexts.wallet.common.utils :as utils] [status-im.contexts.wallet.common.utils.networks :as network-utils] + [status-im.setup.hot-reload :as hot-reload] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -52,7 +53,7 @@ bridge-to-title (i18n/label :t/bridge-to {:name (string/upper-case (str token-symbol))})] - (rn/use-unmount #(rf/dispatch [:wallet/clean-bridge-to-selection])) + (hot-reload/use-safe-unmount #(rf/dispatch [:wallet/clean-bridge-to-selection])) [rn/view [account-switcher/view diff --git a/src/status_im/contexts/wallet/bridge/input_amount/view.cljs b/src/status_im/contexts/wallet/bridge/input_amount/view.cljs index 6826191cb5..78a1902aff 100644 --- a/src/status_im/contexts/wallet/bridge/input_amount/view.cljs +++ b/src/status_im/contexts/wallet/bridge/input_amount/view.cljs @@ -3,12 +3,13 @@ [react-native.core :as rn] [status-im.contexts.wallet.bridge.input-amount.style :as style] [status-im.contexts.wallet.send.input-amount.view :as input-amount] + [status-im.setup.hot-reload :as hot-reload] [utils.i18n :as i18n] [utils.re-frame :as rf])) (defn view [] - (rn/use-unmount #(rf/dispatch [:wallet/clean-routes-calculation])) + (hot-reload/use-safe-unmount #(rf/dispatch [:wallet/clean-routes-calculation])) [rn/view {:style style/bridge-send-wrapper} [input-amount/view {:current-screen-id :screen/wallet.bridge-input-amount @@ -20,5 +21,4 @@ :stack-id :screen/wallet.bridge-input-amount}])) :on-navigate-back (fn [] (rf/dispatch [:wallet/clean-disabled-from-networks]) - (rf/dispatch [:wallet/clean-send-amount]) - (rf/dispatch [:navigate-back]))}]]) + (rf/dispatch [:wallet/clean-send-amount]))}]]) diff --git a/src/status_im/contexts/wallet/events.cljs b/src/status_im/contexts/wallet/events.cljs index 4cefbcec2f..1bcdc760b3 100644 --- a/src/status_im/contexts/wallet/events.cljs +++ b/src/status_im/contexts/wallet/events.cljs @@ -66,13 +66,15 @@ (rf/reg-event-fx :wallet/clean-current-viewing-account (fn [{:keys [db]}] - {:db (update db :wallet dissoc :current-viewing-account-address)})) + (let [just-completed-transaction? (get-in db [:wallet :ui :send :just-completed-transaction?])] + (when-not just-completed-transaction? + {:db (update db :wallet dissoc :current-viewing-account-address)})))) (rf/reg-event-fx :wallet/close-account-page - (fn [_] - {:fx [[:dispatch [:wallet/clean-current-viewing-account]] - [:dispatch [:wallet/clear-account-tab]] - [:dispatch [:pop-to-root :shell-stack]]]})) + (fn [{:keys [db]}] + (let [just-completed-transaction? (get-in db [:wallet :ui :send :just-completed-transaction?])] + (when-not just-completed-transaction? + {:fx [[:dispatch [:wallet/clear-account-tab]]]})))) (defn log-rpc-error [_ [{:keys [event params]} error]] diff --git a/src/status_im/contexts/wallet/send/events.cljs b/src/status_im/contexts/wallet/send/events.cljs index 0582c8ac8f..43e035a178 100644 --- a/src/status_im/contexts/wallet/send/events.cljs +++ b/src/status_im/contexts/wallet/send/events.cljs @@ -487,10 +487,18 @@ transaction-details (send-utils/map-multitransaction-by-ids transaction-batch-id transaction-hashes)] {:db (-> db + (assoc-in [:wallet :ui :send :just-completed-transaction?] true) (assoc-in [:wallet :transactions] transaction-details) (assoc-in [:wallet :ui :send :transaction-ids] transaction-ids)) :fx [[:dispatch - [:wallet/end-transaction-flow]]]}))) + [:wallet/end-transaction-flow]] + [:dispatch-later + [{:ms 2000 + :dispatch [:wallet/clean-just-completed-transaction]}]]]}))) + +(rf/reg-event-fx :wallet/clean-just-completed-transaction + (fn [{:keys [db]}] + {:db (update-in db [:wallet :ui :send] dissoc :just-completed-transaction?)})) (rf/reg-event-fx :wallet/clean-up-transaction-flow (fn [_] @@ -659,8 +667,8 @@ {:json-rpc/call [{:method "wallet_createMultiTransaction" :params request-params :on-success (fn [result] - (rf/dispatch [:hide-bottom-sheet]) - (rf/dispatch [:wallet/add-authorized-transaction result])) + (rf/dispatch [:wallet/add-authorized-transaction result]) + (rf/dispatch [:hide-bottom-sheet])) :on-error (fn [error] (log/error "failed to send transaction" {:event :wallet/send-transaction diff --git a/src/status_im/contexts/wallet/send/from/view.cljs b/src/status_im/contexts/wallet/send/from/view.cljs index 81fa2652f6..43fc1b78a9 100644 --- a/src/status_im/contexts/wallet/send/from/view.cljs +++ b/src/status_im/contexts/wallet/send/from/view.cljs @@ -6,6 +6,7 @@ [status-im.common.floating-button-page.view :as floating-button-page] [status-im.contexts.wallet.common.account-switcher.view :as account-switcher] [status-im.contexts.wallet.send.from.style :as style] + [status-im.setup.hot-reload :as hot-reload] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -18,9 +19,7 @@ (defn- on-close [] - (rf/dispatch [:wallet/clean-current-viewing-account]) - (rf/dispatch [:wallet/clean-send-data]) - (rf/dispatch [:navigate-back])) + (rf/dispatch [:wallet/clean-current-viewing-account])) (defn- render-fn [item _ _ {:keys [network-details]}] @@ -36,10 +35,11 @@ [] (let [accounts (rf/sub [:wallet/accounts-with-current-asset]) network-details (rf/sub [:wallet/network-details])] + (hot-reload/use-safe-unmount on-close) [floating-button-page/view {:footer-container-padding 0 :header [account-switcher/view - {:on-press on-close + {:on-press #(rf/dispatch [:navigate-back]) :margin-top (safe-area/get-top) :switcher-type :select-account}]} diff --git a/src/status_im/contexts/wallet/send/input_amount/view.cljs b/src/status_im/contexts/wallet/send/input_amount/view.cljs index fef06ff4fd..5bfd9bc244 100644 --- a/src/status_im/contexts/wallet/send/input_amount/view.cljs +++ b/src/status_im/contexts/wallet/send/input_amount/view.cljs @@ -16,6 +16,7 @@ [status-im.contexts.wallet.sheets.buy-token.view :as buy-token] [status-im.contexts.wallet.sheets.unpreferred-networks-alert.view :as unpreferred-networks-alert] [status-im.feature-flags :as ff] + [status-im.setup.hot-reload :as hot-reload] [utils.debounce :as debounce] [utils.i18n :as i18n] [utils.money :as money] @@ -322,6 +323,7 @@ (let [dismiss-keyboard-fn #(when (= % "active") (rn/dismiss-keyboard!)) app-keyboard-listener (.addEventListener rn/app-state "change" dismiss-keyboard-fn)] #(.remove app-keyboard-listener)))) + (hot-reload/use-safe-unmount on-navigate-back) (rn/use-effect (fn [] (set-input-state #(controlled-input/set-upper-limit % current-limit))) @@ -346,7 +348,7 @@ (when (controlled-input/input-error input-state) "-error"))} [account-switcher/view {:icon-name :i/arrow-left - :on-press on-navigate-back + :on-press #(rf/dispatch [:navigate-back]) :switcher-type :select-account}] [quo/token-input {:container-style style/input-container diff --git a/src/status_im/contexts/wallet/send/select_address/view.cljs b/src/status_im/contexts/wallet/send/select_address/view.cljs index a92e372d97..8e7d0ca2ac 100644 --- a/src/status_im/contexts/wallet/send/select_address/view.cljs +++ b/src/status_im/contexts/wallet/send/select_address/view.cljs @@ -17,6 +17,7 @@ [status-im.contexts.wallet.send.select-address.style :as style] [status-im.contexts.wallet.send.select-address.tabs.view :as tabs] [status-im.feature-flags :as ff] + [status-im.setup.hot-reload :as hot-reload] [utils.debounce :as debounce] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -176,8 +177,7 @@ (rf/dispatch [:wallet/clean-selected-collectible]) (rf/dispatch [:wallet/clean-send-address]) (rf/dispatch [:wallet/clean-disabled-from-networks]) - (rf/dispatch [:wallet/select-address-tab nil]) - (rf/dispatch [:navigate-back])) + (rf/dispatch [:wallet/select-address-tab nil])) on-change-tab #(rf/dispatch [:wallet/select-address-tab %]) input-value (reagent/atom "") input-focused? (reagent/atom false)] @@ -185,12 +185,13 @@ (let [selected-tab (or (rf/sub [:wallet/send-tab]) (:id (first tabs-data))) valid-ens-or-address? (boolean (rf/sub [:wallet/valid-ens-or-address?])) searching-address? (rf/sub [:wallet/searching-address?])] + (hot-reload/use-safe-unmount on-close) [floating-button-page/view {:content-container-style {:flex 1} :footer-container-padding 0 :keyboard-should-persist-taps true :header [account-switcher/view - {:on-press on-close + {:on-press #(rf/dispatch [:navigate-back]) :margin-top (safe-area/get-top) :switcher-type :select-account}] :footer (when-not (string/blank? @input-value) diff --git a/src/status_im/contexts/wallet/send/select_asset/view.cljs b/src/status_im/contexts/wallet/send/select_asset/view.cljs index 15ef6b0d18..f12912f44a 100644 --- a/src/status_im/contexts/wallet/send/select_asset/view.cljs +++ b/src/status_im/contexts/wallet/send/select_asset/view.cljs @@ -7,6 +7,7 @@ [status-im.contexts.wallet.common.asset-list.view :as asset-list] [status-im.contexts.wallet.common.collectibles-tab.view :as collectibles-tab] [status-im.contexts.wallet.send.select-asset.style :as style] + [status-im.setup.hot-reload :as hot-reload] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -66,9 +67,9 @@ (rf/dispatch [:wallet/clean-selected-token]) (rf/dispatch [:wallet/clean-selected-collectible]) (rf/dispatch [:navigate-back]))] - (rn/use-unmount (fn [] - (rf/dispatch [:wallet/clean-selected-token]) - (rf/dispatch [:wallet/clean-selected-collectible]))) + (hot-reload/use-safe-unmount (fn [] + (rf/dispatch [:wallet/clean-selected-token]) + (rf/dispatch [:wallet/clean-selected-collectible]))) [rn/safe-area-view {:style style/container} [account-switcher/view {:icon-name :i/arrow-left diff --git a/src/status_im/contexts/wallet/send/send_amount/view.cljs b/src/status_im/contexts/wallet/send/send_amount/view.cljs index 37276f122c..1277caf7d6 100644 --- a/src/status_im/contexts/wallet/send/send_amount/view.cljs +++ b/src/status_im/contexts/wallet/send/send_amount/view.cljs @@ -13,5 +13,4 @@ :on-navigate-back (fn [] (rf/dispatch [:wallet/clean-disabled-from-networks]) (rf/dispatch [:wallet/clean-from-locked-amounts]) - (rf/dispatch [:wallet/clean-send-amount]) - (rf/dispatch [:navigate-back]))}]) + (rf/dispatch [:wallet/clean-send-amount]))}]) diff --git a/src/status_im/contexts/wallet/swap/select_asset_to_pay/view.cljs b/src/status_im/contexts/wallet/swap/select_asset_to_pay/view.cljs index f3c3412311..c24963bcfd 100644 --- a/src/status_im/contexts/wallet/swap/select_asset_to_pay/view.cljs +++ b/src/status_im/contexts/wallet/swap/select_asset_to_pay/view.cljs @@ -5,6 +5,7 @@ [status-im.contexts.wallet.common.account-switcher.view :as account-switcher] [status-im.contexts.wallet.common.asset-list.view :as asset-list] [status-im.contexts.wallet.swap.select-asset-to-pay.style :as style] + [status-im.setup.hot-reload :as hot-reload] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -40,8 +41,8 @@ on-close (fn [] (rf/dispatch [:wallet.swap/clean-asset-to-pay]) (rf/dispatch [:navigate-back]))] - (rn/use-unmount (fn [] - (rf/dispatch [:wallet.swap/clean-asset-to-pay]))) + (hot-reload/use-safe-unmount (fn [] + (rf/dispatch [:wallet.swap/clean-asset-to-pay]))) [rn/safe-area-view {:style style/container} [account-switcher/view {:on-press on-close diff --git a/src/status_im/subs/wallet/send.cljs b/src/status_im/subs/wallet/send.cljs index 31c8eae142..24f98ffda2 100644 --- a/src/status_im/subs/wallet/send.cljs +++ b/src/status_im/subs/wallet/send.cljs @@ -25,6 +25,11 @@ :<- [:wallet/wallet-send] :-> :transaction-ids) +(rf/reg-sub + :wallet/just-completed-transaction + :<- [:wallet/wallet-send] + :-> :just-completed-transaction?) + (rf/reg-sub :wallet/wallet-send-amount :<- [:wallet/wallet-send] From 12b8dcf1e7452e9323fd05fcffb8e1493fb3cfc5 Mon Sep 17 00:00:00 2001 From: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com> Date: Fri, 12 Jul 2024 19:25:39 +0530 Subject: [PATCH 37/79] fix(saved-addresses): grouping and sorting of saved addresses (#20689) This commit fixes the grouping and sorting of saved addresses. Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com> --- src/status_im/subs/wallet/saved_addresses.cljs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/status_im/subs/wallet/saved_addresses.cljs b/src/status_im/subs/wallet/saved_addresses.cljs index 8490adcc99..4b9d8176e1 100644 --- a/src/status_im/subs/wallet/saved_addresses.cljs +++ b/src/status_im/subs/wallet/saved_addresses.cljs @@ -38,11 +38,11 @@ (fn [saved-addresses] (->> saved-addresses vals - (sort-by :name) - (group-by #(string/upper-case (first (:name %)))) + (group-by (comp string/upper-case first :name)) (map (fn [[k v]] {:title k - :data v}))))) + :data (sort-by (comp string/lower-case :name) v)})) + (sort-by :title)))) (rf/reg-sub :wallet/saved-addresses-addresses @@ -62,7 +62,7 @@ (fn [saved-addresses [_ query]] (->> saved-addresses vals - (sort-by :name) + (sort-by (comp string/lower-case :name)) (filter (fn [{:keys [name address ens chain-short-names]}] (let [lowercase-query (string/lower-case (string/trim query))] From 5f085e9cea1889bd71842f38830b22a16e5c2f84 Mon Sep 17 00:00:00 2001 From: Brian Sztamfater Date: Sun, 14 Jul 2024 00:51:02 -0300 Subject: [PATCH 38/79] feat(swap): swap confirmation screen (#20669) Signed-off-by: Brian Sztamfater --- .../wallet/summary_info/schema.cljs | 3 +- .../components/wallet/summary_info/view.cljs | 9 +- src/status_im/constants.cljs | 3 + .../contexts/wallet/swap/events.cljs | 20 ++ .../wallet/swap/select_asset_to_pay/view.cljs | 23 ++- .../wallet/swap/swap_confirmation/style.cljs | 39 ++++ .../wallet/swap/swap_confirmation/view.cljs | 192 ++++++++++++++++++ .../wallet/swap/swap_proposal/view.cljs | 6 +- src/status_im/navigation/screens.cljs | 5 + src/status_im/subs/wallet/swap.cljs | 1 + translations/en.json | 14 +- 11 files changed, 301 insertions(+), 14 deletions(-) create mode 100644 src/status_im/contexts/wallet/swap/swap_confirmation/style.cljs create mode 100644 src/status_im/contexts/wallet/swap/swap_confirmation/view.cljs diff --git a/src/quo/components/wallet/summary_info/schema.cljs b/src/quo/components/wallet/summary_info/schema.cljs index 83f9e912e3..7be808dbdb 100644 --- a/src/quo/components/wallet/summary_info/schema.cljs +++ b/src/quo/components/wallet/summary_info/schema.cljs @@ -5,8 +5,9 @@ [:catn [:props [:map - [:type [:enum :status-account :saved-account :account :user]] + [:type [:enum :status-account :saved-account :account :user :token]] [:account-props {:optional true} [:maybe :map]] + [:token-props {:optional true} [:maybe :map]] [:networks? {:optional true} [:maybe :boolean]] [:values {:optional true} [:maybe :map]]]]] :any]) diff --git a/src/quo/components/wallet/summary_info/view.cljs b/src/quo/components/wallet/summary_info/view.cljs index d1e2c32e1e..b2826dba4a 100644 --- a/src/quo/components/wallet/summary_info/view.cljs +++ b/src/quo/components/wallet/summary_info/view.cljs @@ -4,6 +4,7 @@ [quo.components.avatars.user-avatar.view :as user-avatar] [quo.components.avatars.wallet-user-avatar.view :as wallet-user-avatar] [quo.components.markdown.text :as text] + [quo.components.utilities.token.view :as token] [quo.components.wallet.summary-info.schema :as summary-info-schema] [quo.components.wallet.summary-info.style :as style] [quo.foundations.colors :as colors] @@ -58,13 +59,14 @@ :theme theme}])])) (defn- view-internal - [{:keys [type account-props networks? values]}] + [{:keys [type account-props token-props networks? values]}] (let [theme (quo.theme/use-theme)] [rn/view {:style (style/container networks? theme)} [rn/view {:style style/info-container} (case type + :token [token/view (select-keys token-props #{:token :size})] :status-account [account-avatar/view account-props] :saved-account [wallet-user-avatar/wallet-user-avatar (assoc account-props :size :size-32)] :account [wallet-user-avatar/wallet-user-avatar @@ -73,7 +75,8 @@ :neutral? true)] [user-avatar/user-avatar account-props]) [rn/view {:style {:margin-left 8}} - (when (not= type :account) [text/text {:weight :semi-bold} (:name account-props)]) + (when (not= type :account) + [text/text {:weight :semi-bold} (or (:name account-props) (:label token-props))]) [rn/view {:style {:flex-direction :row :align-items :center}} @@ -91,7 +94,7 @@ :weight (when (= type :account) :semi-bold) :style {:color (when (not= type :account) (colors/theme-colors colors/neutral-50 colors/neutral-40 theme))}} - (:address account-props)]]]] + (or (:address account-props) (:address token-props))]]]] (when networks? [:<> [rn/view diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index e31b233f56..7c92dccf2b 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -566,3 +566,6 @@ (def ^:const default-slippage 0.5) (def ^:const max-recommended-slippage 5) (def ^:const max-slippage-decimal-places 2) +(def ^:const swap-default-provider + {:name "Paraswap" + :terms-and-conditions-url "https://files.paraswap.io/tos_v4.pdf"}) diff --git a/src/status_im/contexts/wallet/swap/events.cljs b/src/status_im/contexts/wallet/swap/events.cljs index ff16116a4f..cef429a259 100644 --- a/src/status_im/contexts/wallet/swap/events.cljs +++ b/src/status_im/contexts/wallet/swap/events.cljs @@ -42,3 +42,23 @@ (rf/reg-event-fx :wallet.swap/set-max-slippage (fn [{:keys [db]} [max-slippage]] {:db (assoc-in db [:wallet :ui :swap :max-slippage] (utils.number/parse-float max-slippage))})) + +(rf/reg-event-fx :wallet.swap/select-asset-to-receive + (fn [{:keys [db]} [{:keys [token]}]] + {:db (assoc-in db [:wallet :ui :swap :asset-to-receive] token)})) + +(rf/reg-event-fx :wallet.swap/set-pay-amount + (fn [{:keys [db]} [amount]] + {:db (assoc-in db [:wallet :ui :swap :pay-amount] amount)})) + +(rf/reg-event-fx :wallet.swap/set-swap-proposal + (fn [{:keys [db]} [swap-proposal]] + {:db (assoc-in db [:wallet :ui :swap :swap-proposal] swap-proposal)})) + +(rf/reg-event-fx :wallet.swap/set-provider + (fn [{:keys [db]}] + {:db (assoc-in db [:wallet :ui :swap :providers] [constants/swap-default-provider])})) + +(rf/reg-event-fx :wallet.swap/recalculate-fees + (fn [{:keys [db]} [loading-fees?]] + {:db (assoc-in db [:wallet :ui :swap :loading-fees?] loading-fees?)})) diff --git a/src/status_im/contexts/wallet/swap/select_asset_to_pay/view.cljs b/src/status_im/contexts/wallet/swap/select_asset_to_pay/view.cljs index c24963bcfd..753aca83ce 100644 --- a/src/status_im/contexts/wallet/swap/select_asset_to_pay/view.cljs +++ b/src/status_im/contexts/wallet/swap/select_asset_to_pay/view.cljs @@ -5,7 +5,6 @@ [status-im.contexts.wallet.common.account-switcher.view :as account-switcher] [status-im.contexts.wallet.common.asset-list.view :as asset-list] [status-im.contexts.wallet.swap.select-asset-to-pay.style :as style] - [status-im.setup.hot-reload :as hot-reload] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -19,6 +18,20 @@ :value search-text :on-change-text on-change-text}]]) +(def dummy-swap-proposal + {:from {:chain-id 1 + :native-currency-symbol "ETH"} + :to {:chain-id 1 + :native-currency-symbol "ETH"} + :gas-amount "23487" + :gas-fees {:base-fee "32.325296406" + :max-priority-fee-per-gas "0.011000001" + :eip1559-enabled true} + :estimated-time 3 + :receive-amount 99.98 + :receive-token {:symbol "SNT" + :address "0x432492384728934239789"}}) + (defn- assets-view [search-text on-change-text] (let [on-token-press (fn [token] @@ -27,7 +40,11 @@ {:token token :network (when (= (count token-networks) 1) (first token-networks)) - :stack-id :screen/wallet.swap-select-asset-to-pay}])))] + :stack-id :screen/wallet.swap-select-asset-to-pay}]) + (rf/dispatch [:wallet.swap/select-asset-to-receive {:token token}]) + (rf/dispatch [:wallet.swap/set-pay-amount 100]) + (rf/dispatch [:wallet.swap/set-swap-proposal dummy-swap-proposal]) + (rf/dispatch [:wallet.swap/set-provider])))] [:<> [search-input search-text on-change-text] [asset-list/view @@ -41,8 +58,6 @@ on-close (fn [] (rf/dispatch [:wallet.swap/clean-asset-to-pay]) (rf/dispatch [:navigate-back]))] - (hot-reload/use-safe-unmount (fn [] - (rf/dispatch [:wallet.swap/clean-asset-to-pay]))) [rn/safe-area-view {:style style/container} [account-switcher/view {:on-press on-close diff --git a/src/status_im/contexts/wallet/swap/swap_confirmation/style.cljs b/src/status_im/contexts/wallet/swap/swap_confirmation/style.cljs new file mode 100644 index 0000000000..dc6c644670 --- /dev/null +++ b/src/status_im/contexts/wallet/swap/swap_confirmation/style.cljs @@ -0,0 +1,39 @@ +(ns status-im.contexts.wallet.swap.swap-confirmation.style + (:require [quo.foundations.colors :as colors])) + +(def detail-item + {:flex 1 + :height 36 + :background-color :transparent}) + +(def content-container + {:padding-top 12 + :padding-horizontal 20 + :padding-bottom 32}) + +(def title-container + {:margin-right 4}) + +(def title-line-with-margin-top + {:flex-direction :row + :margin-top 4}) + +(def details-container + {:flex-direction :row + :justify-content :space-between + :height 52 + :padding-top 7 + :margin-bottom 8}) + +(def summary-section-container + {:padding-horizontal 20 + :padding-bottom 16}) + +(defn section-label + [theme] + {:margin-bottom 8 + :color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}) + +(def providers-container + {:align-items :center + :margin-top 12}) diff --git a/src/status_im/contexts/wallet/swap/swap_confirmation/view.cljs b/src/status_im/contexts/wallet/swap/swap_confirmation/view.cljs new file mode 100644 index 0000000000..7a4acfc5c5 --- /dev/null +++ b/src/status_im/contexts/wallet/swap/swap_confirmation/view.cljs @@ -0,0 +1,192 @@ +(ns status-im.contexts.wallet.swap.swap-confirmation.view + (:require + [quo.core :as quo] + [quo.foundations.colors :as colors] + [quo.theme :as quo.theme] + [react-native.core :as rn] + [react-native.safe-area :as safe-area] + [status-im.common.floating-button-page.view :as floating-button-page] + [status-im.common.standard-authentication.core :as standard-auth] + [status-im.contexts.wallet.swap.swap-confirmation.style :as style] + [utils.address :as address-utils] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn- on-close-action + [] + (rf/dispatch [:navigate-back])) + +(defn- swap-title + [{:keys [pay-token-symbol pay-amount receive-token-symbol receive-amount account]}] + [rn/view {:style style/content-container} + [rn/view {:style {:flex-direction :row}} + [quo/text + {:size :heading-1 + :weight :semi-bold + :style style/title-container + :accessibility-label :title-label} + (i18n/label :t/swap)] + [quo/summary-tag + {:token pay-token-symbol + :label (str pay-amount " " pay-token-symbol) + :type :token}]] + [rn/view {:style style/title-line-with-margin-top} + [quo/text + {:size :heading-1 + :weight :semi-bold + :style style/title-container + :accessibility-label :title-label} + (i18n/label :t/to)] + [quo/summary-tag + {:token receive-token-symbol + :label (str receive-amount " " receive-token-symbol) + :type :token}]] + [rn/view {:style style/title-line-with-margin-top} + [quo/text + {:size :heading-1 + :weight :semi-bold + :style style/title-container + :accessibility-label :send-label} + (i18n/label :t/in)] + [quo/summary-tag + {:label (:name account) + :type :account + :emoji (:emoji account) + :customization-color (:color account)}]]]) + +(defn- summary-section + [{:keys [theme label title-accessibility-label amount token-symbol token-address network]}] + (let [network-values {(if (= network :mainnet) :ethereum network) + {:amount amount :token-symbol token-symbol}}] + [rn/view {:style style/summary-section-container} + [quo/text + {:size :paragraph-2 + :weight :medium + :style (style/section-label theme) + :accessibility-label title-accessibility-label} + label] + [quo/summary-info + {:type :token + :networks? true + :values network-values + :token-props {:token token-symbol + :label (str amount " " token-symbol) + :address (address-utils/get-shortened-compressed-key token-address) + :size 32}}]])) + +(defn- data-item + [{:keys [title subtitle loading?]}] + [quo/data-item + {:container-style style/detail-item + :blur? false + :card? false + :status (if loading? :loading :default) + :size :small + :title title + :subtitle subtitle}]) + +(defn- transaction-details + [{:keys [estimated-time-min max-fees max-slippage loading-fees?]}] + [rn/view {:style style/details-container} + [:<> + [data-item + {:title (i18n/label :t/est-time) + :subtitle (i18n/label :t/time-in-mins {:minutes (str estimated-time-min)})}] + [data-item + {:title (i18n/label :t/max-fees) + :subtitle max-fees + :loading? loading-fees?}] + [data-item + {:title (i18n/label :t/max-slippage) + :subtitle (str max-slippage "%")}]]]) + +(defn footer + [{:keys [estimated-time-min native-currency-symbol max-slippage theme account-color provider + loading-fees?]}] + (let [native-token (when native-currency-symbol + (rf/sub [:wallet/token-by-symbol + native-currency-symbol])) + fee-formatted (rf/sub [:wallet/wallet-send-fee-fiat-formatted + native-token])] + [:<> + [transaction-details + {:estimated-time-min estimated-time-min + :max-fees fee-formatted + :max-slippage max-slippage + :loading-fees? loading-fees? + :theme theme}] + [standard-auth/slide-button + {:size :size-48 + :track-text (i18n/label :t/slide-to-swap) + :container-style {:z-index 2} + :customization-color account-color + :disabled? loading-fees? + :auth-button-label (i18n/label :t/confirm)}] + [rn/view {:style style/providers-container} + [quo/text + {:size :paragraph-2 + :style {:color (colors/theme-colors colors/neutral-80-opa-40 + colors/white-opa-70 + theme)}} + (i18n/label :t/swaps-powered-by {:provider (:name provider)})]]])) + +(defn view + [] + (let [theme (quo.theme/use-theme) + swap-transaction-data (rf/sub [:wallet/swap]) + {:keys [asset-to-pay max-slippage network + pay-amount providers swap-proposal + loading-fees?]} swap-transaction-data + receive-amount (:receive-amount swap-proposal) + receive-token (:receive-token swap-proposal) + receive-token-symbol (:symbol receive-token) + receive-token-address (:address receive-token) + estimated-time-min (:estimated-time swap-proposal) + pay-token-symbol (:symbol asset-to-pay) + pay-token-address (:address asset-to-pay) + native-currency-symbol (get-in swap-proposal [:from :native-currency-symbol]) + account (rf/sub [:wallet/current-viewing-account]) + account-color (:color account) + provider (first providers)] + [rn/view {:style {:flex 1}} + [floating-button-page/view + {:footer-container-padding 0 + :header [quo/page-nav + {:icon-name :i/arrow-left + :on-press on-close-action + :margin-top (safe-area/get-top) + :background :blur + :accessibility-label :top-bar}] + :footer [footer + {:estimated-time-min estimated-time-min + :native-currency-symbol native-currency-symbol + :max-slippage max-slippage + :account-color account-color + :provider provider + :loading-fees? loading-fees? + :theme theme}] + :gradient-cover? true + :customization-color account-color} + [rn/view + [swap-title + {:pay-token-symbol pay-token-symbol + :pay-amount pay-amount + :receive-token-symbol receive-token-symbol + :receive-amount receive-amount + :account account}] + [summary-section + {:title-accessibility-label :summary-section-pay + :label (i18n/label :t/pay) + :token-symbol pay-token-symbol + :amount pay-amount + :token-address pay-token-address + :network (:network-name network) + :theme theme}] + [summary-section + {:title-accessibility-label :summary-section-receive + :label (i18n/label :t/receive) + :token-symbol receive-token-symbol + :amount receive-amount + :token-address receive-token-address + :network (:network-name network) + :theme theme}]]]])) diff --git a/src/status_im/contexts/wallet/swap/swap_proposal/view.cljs b/src/status_im/contexts/wallet/swap/swap_proposal/view.cljs index e5ffc0940e..0354c63709 100644 --- a/src/status_im/contexts/wallet/swap/swap_proposal/view.cljs +++ b/src/status_im/contexts/wallet/swap/swap_proposal/view.cljs @@ -12,4 +12,8 @@ [quo/button {:on-press #(rf/dispatch [:show-bottom-sheet {:content slippage-settings/view}])} - (str "Edit Slippage: " max-slippage "%")]])) + (str "Edit Slippage: " max-slippage "%")] + [quo/button + {:on-press #(rf/dispatch [:navigate-to-within-stack + [:screen/wallet.swap-confirmation :screen/wallet.swap-propasal]])} + "Swap confirmation"]])) diff --git a/src/status_im/navigation/screens.cljs b/src/status_im/navigation/screens.cljs index f8e8345452..c0ab4609a9 100644 --- a/src/status_im/navigation/screens.cljs +++ b/src/status_im/navigation/screens.cljs @@ -118,6 +118,7 @@ [status-im.contexts.wallet.send.transaction-confirmation.view :as wallet-transaction-confirmation] [status-im.contexts.wallet.send.transaction-progress.view :as wallet-transaction-progress] [status-im.contexts.wallet.swap.select-asset-to-pay.view :as wallet-swap-select-asset-to-pay] + [status-im.contexts.wallet.swap.swap-confirmation.view :as wallet-swap-confirmation] [status-im.contexts.wallet.swap.swap-proposal.view :as wallet-swap-propasal] [status-im.contexts.wallet.wallet-connect.modals.send-transaction.view :as wallet-connect-send-transaction] @@ -521,6 +522,10 @@ :options {:insets {:top? true}} :component wallet-swap-propasal/view} + {:name :screen/wallet.swap-confirmation + :options {:modalPresentationStyle :overCurrentContext} + :component wallet-swap-confirmation/view} + {:name :scan-profile-qr-code :options (merge options/dark-screen diff --git a/src/status_im/subs/wallet/swap.cljs b/src/status_im/subs/wallet/swap.cljs index 8e42bb96a1..75aa21fed9 100644 --- a/src/status_im/subs/wallet/swap.cljs +++ b/src/status_im/subs/wallet/swap.cljs @@ -52,6 +52,7 @@ fiat-value)] {:crypto (str crypto-formatted " " token-symbol) :fiat fiat-formatted}))) + (rf/reg-sub :wallet/swap-max-slippage :<- [:wallet/swap] diff --git a/translations/en.json b/translations/en.json index 3e70c031a1..c01a5e1176 100644 --- a/translations/en.json +++ b/translations/en.json @@ -182,7 +182,7 @@ "chat-with-friends": "Chat privately with friends", "chats": "Chats", "check-before-syncing": "Check before syncing", - "check-before-syncing-doc-description" :"To sync your devices successfully, make sure to check and complete these steps:", + "check-before-syncing-doc-description": "To sync your devices successfully, make sure to check and complete these steps:", "check-before-syncing-doc-checkbox-1": "Connect both devices to the same network", "check-before-syncing-doc-checkbox-2": "Make sure you are logged in on the other device", "check-before-syncing-doc-checkbox-3": "Disable the firewall and VPN on your devices", @@ -2007,7 +2007,7 @@ "slippage-may-be-higher-than-necessary": "Slippage may be higher than necessary", "slippage-should-be-more-than-0": "Slippage should be more than 0", "max-2-decimals": "Max. 2 decimals", - "type-slippage": "Type slippage", + "type-slippage": "Type slippage", "receive-at-least": "Receive at least", "select-token-to-swap": "Select token to Swap", "select-token-to-receive": "Select token to receive", @@ -2651,6 +2651,7 @@ "testnet-not-available": "Testnet not available", "bridged-to": "Bridged to {{network}}", "slide-to-bridge": "Slide to bridge", + "slide-to-swap": "Slide to swap", "provider-is-down": "The provider for the following chain(s) is down: {{chains}}", "unknown": "Unknown", "unsupported-file": "Unsupported file", @@ -2746,12 +2747,15 @@ "add-preferences": "Add preferences", "buy-eth": "Buy ETH", "not-enough-assets": "Not enough assets to pay gas fees", - "send-from-network" : "Send from {{network}}", - "define-amount-sent-from-network" : "Define amount sent from {{network}} network", + "send-from-network": "Send from {{network}}", + "define-amount-sent-from-network": "Define amount sent from {{network}} network", "dont-auto-recalculate-network": "Don't auto recalculate {{network}}", "import-keypair-to-use-account": "Import key pair to use this account", "import-keypair-steps": "{{account-name}} was derived from your {{keypair-name}} key pair, which has not yet been imported to this device. To transact using this account, you will need to import the {{keypair-name}} key pair first.", "not-now": "Not now", "share-usage-data": "Share usage data with Status", - "value-higher-than-send-amount": "This value is higher than entered amount to send" + "value-higher-than-send-amount": "This value is higher than entered amount to send", + "swaps-powered-by": "Swaps powered by {{provider}}", + "max-slippage": "Max slippage", + "pay": "Pay" } From ba76b9fd51b348cbf8f7ca95defa5c07f8d3c953 Mon Sep 17 00:00:00 2001 From: Andrea Maria Piana Date: Wed, 10 Jul 2024 22:02:59 +0100 Subject: [PATCH 39/79] Allow setting store confirmations This commit adds the ability to enable store confirmations in the UI, under advanced settings. https://github.com/status-im/status-go/compare/1ef2434b...0d1e5aae --- .../ui/screens/advanced_settings/views.cljs | 42 ++++++++++++------- src/legacy/status_im/waku/core.cljs | 16 +++++++ src/status_im/contexts/profile/config.cljs | 27 ++++++------ src/status_im/subs/profile.cljs | 6 +++ status-go-version.json | 6 +-- translations/en.json | 3 +- 6 files changed, 68 insertions(+), 32 deletions(-) diff --git a/src/legacy/status_im/ui/screens/advanced_settings/views.cljs b/src/legacy/status_im/ui/screens/advanced_settings/views.cljs index 8f44a38b26..93f295653d 100644 --- a/src/legacy/status_im/ui/screens/advanced_settings/views.cljs +++ b/src/legacy/status_im/ui/screens/advanced_settings/views.cljs @@ -18,6 +18,7 @@ [{:keys [current-log-level telemetry-enabled? light-client-enabled? + store-confirmations-enabled? current-fleet test-networks-enabled? is-goerli-enabled? @@ -77,6 +78,15 @@ [:wakuv2.ui/toggle-light-client (not light-client-enabled?)]) :accessory :switch :active light-client-enabled?} + {:size :small + :title (i18n/label :t/store-confirmations) + :accessibility-label :store-confirmations + :container-margin-bottom 8 + :on-press + #(re-frame/dispatch + [:wakuv2.ui/toggle-store-confirmations (not store-confirmations-enabled?)]) + :accessory :switch + :active store-confirmations-enabled?} {:size :small :title "Testnet mode" :accessibility-label :test-networks-enabled @@ -114,13 +124,14 @@ (views/defview advanced-settings [] - (views/letsubs [test-networks-enabled? [:profile/test-networks-enabled?] - is-goerli-enabled? [:profile/is-goerli-enabled?] - light-client-enabled? [:profile/light-client-enabled?] - telemetry-enabled? [:profile/telemetry-enabled?] - current-log-level [:log-level/current-log-level] - current-fleet [:fleets/current-fleet] - peer-syncing-enabled? [:profile/peer-syncing-enabled?]] + (views/letsubs [test-networks-enabled? [:profile/test-networks-enabled?] + is-goerli-enabled? [:profile/is-goerli-enabled?] + light-client-enabled? [:profile/light-client-enabled?] + store-confirmations-enabled? [:profile/store-confirmations-enabled?] + telemetry-enabled? [:profile/telemetry-enabled?] + current-log-level [:log-level/current-log-level] + current-fleet [:fleets/current-fleet] + peer-syncing-enabled? [:profile/peer-syncing-enabled?]] [:<> [quo/page-nav {:type :title @@ -130,13 +141,14 @@ :on-press #(rf/dispatch [:navigate-back])}] [list/flat-list {:data (flat-list-data - {:current-log-level current-log-level - :telemetry-enabled? telemetry-enabled? - :light-client-enabled? light-client-enabled? - :current-fleet current-fleet - :dev-mode? false - :test-networks-enabled? test-networks-enabled? - :is-goerli-enabled? is-goerli-enabled? - :peer-syncing-enabled? peer-syncing-enabled?}) + {:current-log-level current-log-level + :telemetry-enabled? telemetry-enabled? + :light-client-enabled? light-client-enabled? + :store-confirmations-enabled? store-confirmations-enabled? + :current-fleet current-fleet + :dev-mode? false + :test-networks-enabled? test-networks-enabled? + :is-goerli-enabled? is-goerli-enabled? + :peer-syncing-enabled? peer-syncing-enabled?}) :key-fn (fn [_ i] (str i)) :render-fn render-item}]])) diff --git a/src/legacy/status_im/waku/core.cljs b/src/legacy/status_im/waku/core.cljs index c43a5aea6e..abbcff1fae 100644 --- a/src/legacy/status_im/waku/core.cljs +++ b/src/legacy/status_im/waku/core.cljs @@ -137,3 +137,19 @@ :on-error #(log/error "failed to set light client" {:error % :enabled? enabled?})}]}) + +(rf/defn toggle-store-confirmations + {:events [:wakuv2.ui/toggle-store-confirmations]} + [{:keys [db]} enabled?] + {:db (assoc-in db + [:profile/profile :wakuv2-config :EnableStoreConfirmationForMessagesSent] + enabled?) + + :json-rpc/call [{:method "wakuext_setStoreConfirmationForMessagesSent" + :params [{:enabled enabled?}] + :on-success (fn [] + (log/info "store confirmation set successfully" enabled?) + (re-frame/dispatch [:logout])) + :on-error #(log/error "failed to set store confirmation" + {:error % + :enabled? enabled?})}]}) diff --git a/src/status_im/contexts/profile/config.cljs b/src/status_im/contexts/profile/config.cljs index f9e0c3409c..296ee5adfa 100644 --- a/src/status_im/contexts/profile/config.cljs +++ b/src/status_im/contexts/profile/config.cljs @@ -29,19 +29,20 @@ [] (let [log-enabled? (boolean (not-empty config/log-level))] (assoc (login) - :deviceName (native-module/get-installation-name) - :rootDataDir (native-module/backup-disabled-data-dir) - :rootKeystoreDir (native-module/keystore-dir) - :logLevel (when log-enabled? config/log-level) - :logEnabled log-enabled? - :logFilePath (native-module/log-file-directory) - :verifyTransactionURL config/verify-transaction-url - :verifyENSURL config/verify-ens-url - :verifyENSContractAddress config/verify-ens-contract-address - :verifyTransactionChainID config/verify-transaction-chain-id - :wakuV2LightClient true - :previewPrivacy config/blank-preview? - :testNetworksEnabled config/test-networks-enabled?))) + :deviceName (native-module/get-installation-name) + :rootDataDir (native-module/backup-disabled-data-dir) + :rootKeystoreDir (native-module/keystore-dir) + :logLevel (when log-enabled? config/log-level) + :logEnabled log-enabled? + :logFilePath (native-module/log-file-directory) + :verifyTransactionURL config/verify-transaction-url + :verifyENSURL config/verify-ens-url + :verifyENSContractAddress config/verify-ens-contract-address + :verifyTransactionChainID config/verify-transaction-chain-id + :wakuV2LightClient true + :wakuV2EnableStoreConfirmationForMessagesSent false + :previewPrivacy config/blank-preview? + :testNetworksEnabled config/test-networks-enabled?))) (defn strip-file-prefix [path] diff --git a/src/status_im/subs/profile.cljs b/src/status_im/subs/profile.cljs index 609a1641d5..283fb15911 100644 --- a/src/status_im/subs/profile.cljs +++ b/src/status_im/subs/profile.cljs @@ -87,6 +87,12 @@ (fn [profile] (get-in profile [:wakuv2-config :LightClient]))) +(re-frame/reg-sub + :profile/store-confirmations-enabled? + :<- [:profile/profile] + (fn [profile] + (get-in profile [:wakuv2-config :EnableStoreConfirmationForMessagesSent]))) + (re-frame/reg-sub :profile/telemetry-enabled? :<- [:profile/profile] diff --git a/status-go-version.json b/status-go-version.json index 24f27cd75a..fe7c00dd46 100644 --- a/status-go-version.json +++ b/status-go-version.json @@ -3,7 +3,7 @@ "_comment": "Instead use: scripts/update-status-go.sh ", "owner": "status-im", "repo": "status-go", - "version": "v0.181.35", - "commit-sha1": "1ef2434b0644581e3c0404d767550cbfee8ad829", - "src-sha256": "1dfw4cff1blg063xxrh78dgivcar92k1fspcr84ra1fxw674kmfk" + "version": "feat/flag-sent-messages-check-2", + "commit-sha1": "0d1e5aaed9c26f1948cfb1661a76f52f6b7585bb", + "src-sha256": "0965vsnj1xxxwjiz1lnxfcp8kg6nbw2dqvcmcgayxccfgf6a377a" } diff --git a/translations/en.json b/translations/en.json index c01a5e1176..269d8ffdf3 100644 --- a/translations/en.json +++ b/translations/en.json @@ -2757,5 +2757,6 @@ "value-higher-than-send-amount": "This value is higher than entered amount to send", "swaps-powered-by": "Swaps powered by {{provider}}", "max-slippage": "Max slippage", - "pay": "Pay" + "pay": "Pay", + "store-confirmations": "Store confirmations" } From bb8aad3be81ec519ee390c90044f77b211eaffe1 Mon Sep 17 00:00:00 2001 From: Andrea Maria Piana Date: Sat, 13 Jul 2024 08:57:48 +0100 Subject: [PATCH 40/79] set fleet to status.staging https://github.com/status-im/status-go/compare/1ef2434b...484b8aca --- .env | 1 - .env.e2e | 2 +- .env.jenkins | 1 - .env.nightly | 1 - .env.release | 1 - src/status_im/config.cljs | 2 +- src/status_im/contexts/profile/config.cljs | 1 + status-go-version.json | 6 +++--- 8 files changed, 6 insertions(+), 9 deletions(-) diff --git a/.env b/.env index d7182d43f9..cd94c2d3ee 100644 --- a/.env +++ b/.env @@ -4,7 +4,6 @@ DEFAULT_NETWORK=mainnet_rpc DEV_BUILD=1 ETHEREUM_DEV_CLUSTER=1 EXTENSIONS=0 -FLEET=status.prod GROUP_CHATS_ENABLED=1 LOG_LEVEL=info MAILSERVER_CONFIRMATIONS_ENABLED=1 diff --git a/.env.e2e b/.env.e2e index 768448fa2a..cbe3ce216f 100644 --- a/.env.e2e +++ b/.env.e2e @@ -2,7 +2,7 @@ DEBUG_WEBVIEW=1 DEFAULT_NETWORK=goerli_rpc ETHEREUM_DEV_CLUSTER=1 EXTENSIONS=0 -FLEET=status.prod +FLEET=status.staging GROUP_CHATS_ENABLED=1 LOG_LEVEL=debug MAILSERVER_CONFIRMATIONS_ENABLED=0 diff --git a/.env.jenkins b/.env.jenkins index 26060f1f96..b7415f9948 100644 --- a/.env.jenkins +++ b/.env.jenkins @@ -3,7 +3,6 @@ DEBUG_WEBVIEW=1 DEFAULT_NETWORK=goerli_rpc ETHEREUM_DEV_CLUSTER=1 EXTENSIONS=0 -FLEET=status.prod GROUP_CHATS_ENABLED=1 LOG_LEVEL=debug MAILSERVER_CONFIRMATIONS_ENABLED=1 diff --git a/.env.nightly b/.env.nightly index fdeacd4f4d..07aee35b91 100644 --- a/.env.nightly +++ b/.env.nightly @@ -2,7 +2,6 @@ DEBUG_WEBVIEW=1 DEFAULT_NETWORK=mainnet_rpc ETHEREUM_DEV_CLUSTER=1 EXTENSIONS=0 -FLEET=status.prod GROUP_CHATS_ENABLED=1 LOG_LEVEL=info MAILSERVER_CONFIRMATIONS_ENABLED=1 diff --git a/.env.release b/.env.release index c90b1b69ab..41b630e1b3 100644 --- a/.env.release +++ b/.env.release @@ -2,7 +2,6 @@ DEBUG_WEBVIEW=0 DEFAULT_NETWORK=mainnet_rpc ETHEREUM_DEV_CLUSTER=0 EXTENSIONS=0 -FLEET=status.prod GROUP_CHATS_ENABLED=1 LOG_LEVEL= MAILSERVER_CONFIRMATIONS_ENABLED=1 diff --git a/src/status_im/config.cljs b/src/status_im/config.cljs index b85044c90a..5f712732d8 100644 --- a/src/status_im/config.cljs +++ b/src/status_im/config.cljs @@ -72,7 +72,7 @@ ;; CONFIG VALUES (def log-level (string/upper-case (get-config :LOG_LEVEL ""))) -(def fleet (get-config :FLEET "waku.sandbox")) +(def fleet (get-config :FLEET "")) (def apn-topic (get-config :APN_TOPIC "im.status.ethereum")) (def default-network (get-config :DEFAULT_NETWORK "goerli_rpc")) (def max-installations 2) diff --git a/src/status_im/contexts/profile/config.cljs b/src/status_im/contexts/profile/config.cljs index 296ee5adfa..57a0ce4009 100644 --- a/src/status_im/contexts/profile/config.cljs +++ b/src/status_im/contexts/profile/config.cljs @@ -40,6 +40,7 @@ :verifyENSContractAddress config/verify-ens-contract-address :verifyTransactionChainID config/verify-transaction-chain-id :wakuV2LightClient true + :wakuV2Fleet config/fleet :wakuV2EnableStoreConfirmationForMessagesSent false :previewPrivacy config/blank-preview? :testNetworksEnabled config/test-networks-enabled?))) diff --git a/status-go-version.json b/status-go-version.json index fe7c00dd46..1226d81ffe 100644 --- a/status-go-version.json +++ b/status-go-version.json @@ -3,7 +3,7 @@ "_comment": "Instead use: scripts/update-status-go.sh ", "owner": "status-im", "repo": "status-go", - "version": "feat/flag-sent-messages-check-2", - "commit-sha1": "0d1e5aaed9c26f1948cfb1661a76f52f6b7585bb", - "src-sha256": "0965vsnj1xxxwjiz1lnxfcp8kg6nbw2dqvcmcgayxccfgf6a377a" + "version": "v0.182.35", + "commit-sha1": "484b8aca1a12718c9b3940f8a398e60ee4419600", + "src-sha256": "1xnpx86k54lnsw93xw6la0sggd9y0nm7hn70cm0qgmq2qg8pzb3a" } From 942c130d23357b410396135e1d90a6be340cc644 Mon Sep 17 00:00:00 2001 From: Yevheniia Berdnyk Date: Fri, 12 Jul 2024 19:29:44 +0300 Subject: [PATCH 41/79] e2e: separated tests to run in PRs --- ci/tests/Jenkinsfile.e2e-nightly | 2 +- ci/tests/Jenkinsfile.e2e-prs | 4 +- test/appium/pytest.ini | 4 +- test/appium/support/testrail_report.py | 62 ++++++------------ .../activity_center/test_activity_center.py | 6 +- .../critical/chats/test_1_1_public_chats.py | 5 +- .../tests/critical/chats/test_group_chat.py | 2 +- .../chats/test_public_chat_browsing.py | 10 +-- .../critical/test_deep_and_universal_links.py | 2 +- test/appium/tests/critical/test_wallet.py | 6 +- test/appium/tests/marks.py | 4 +- .../status_community_join_button.png | Bin 6802 -> 6555 bytes .../status_community_logo.png | Bin 11060 -> 11030 bytes 13 files changed, 44 insertions(+), 63 deletions(-) diff --git a/ci/tests/Jenkinsfile.e2e-nightly b/ci/tests/Jenkinsfile.e2e-nightly index c550e7853d..15f5d5d03f 100644 --- a/ci/tests/Jenkinsfile.e2e-nightly +++ b/ci/tests/Jenkinsfile.e2e-nightly @@ -99,7 +99,7 @@ pipeline { --rerun_count=2 \ --testrail_report=True \ -m testrail_id \ - -m \"new_ui_critical or new_ui_medium\" \ + -m \"nightly\" \ -k \"${params.KEYWORD_EXPRESSION}\" \ --apk=${params.APK_URL ?: apk_path} """ diff --git a/ci/tests/Jenkinsfile.e2e-prs b/ci/tests/Jenkinsfile.e2e-prs index 8bca86ecf9..832303820a 100644 --- a/ci/tests/Jenkinsfile.e2e-prs +++ b/ci/tests/Jenkinsfile.e2e-prs @@ -35,13 +35,11 @@ pipeline { description: 'OBSOLETE ARGUMENT TO BE REMOVED', defaultValue: 'DUMMY', ) - /* Commented to use TEST_MARKERS values from job params string( name: 'TEST_MARKERS', description: 'Marker expression for matching tests to run.', - defaultValue: 'new_ui_critical', + defaultValue: 'smoke', ) - */ } options { diff --git a/test/appium/pytest.ini b/test/appium/pytest.ini index f3e743c22b..a782c0ebef 100644 --- a/test/appium/pytest.ini +++ b/test/appium/pytest.ini @@ -12,4 +12,6 @@ markers = testrail_case_id: case ID in testrail flaky: flaky tests for re-run if necessary transaction: all cases that are related to transaction completion in Ropsten network - new_ui_critical: PR tests in new UI \ No newline at end of file + smoke: PR tests + nightly: nightly tests + secured: tests where SauceLabs session link is not posted in PR comments \ No newline at end of file diff --git a/test/appium/support/testrail_report.py b/test/appium/support/testrail_report.py index 63e6114c22..f6c553739b 100644 --- a/test/appium/support/testrail_report.py +++ b/test/appium/support/testrail_report.py @@ -108,51 +108,23 @@ class TestrailReport(BaseTestReport): test_cases['pr'] = dict() test_cases['nightly'] = dict() test_cases['upgrade'] = dict() - ## PR e2e old UI - # test_cases['pr']['critical'] = 730 - # test_cases['pr']['contacts'] = 50831 - # test_cases['pr']['public_chat'] = 50654 - # test_cases['pr']['one_to_one_chat'] = 50655 - # test_cases['pr']['group_chat'] = 50656 - # test_cases['pr']['onboarding'] = 50659 - # test_cases['pr']['recovery'] = 50660 - # test_cases['pr']['wallet'] = 50661 - # test_cases['pr']['send_tx'] = 50662 - # test_cases['pr']['keycard_tx'] = 50663 - # test_cases['pr']['1_1_chat_commands'] = 50825 - # test_cases['pr']['ens'] = 50827 - # test_cases['pr']['sync'] = 50834 - # test_cases['pr']['browser'] = 50812 + # PR e2e test_cases['pr']['critical'] = 50955 - test_cases['pr']['one_to_one_chat'] = 50956 - test_cases['pr']['deep_links'] = 51535 - test_cases['pr']['group_chat'] = 50964 - test_cases['pr']['community_single'] = 50983 - test_cases['pr']['community_multiple'] = 50982 - test_cases['pr']['activity_centre_contact_request'] = 50984 - test_cases['pr']['activity_centre_other'] = 51005 + # test_cases['pr']['one_to_one_chat'] = 50956 + # test_cases['pr']['community_single'] = 50983 test_cases['pr']['wallet'] = 59443 - ## Nightly e2e - # test_cases['nightly']['activity_center'] = 736 - # test_cases['nightly']['chat'] = 50811 - # test_cases['nightly']['browser'] = 50826 - # test_cases['nightly']['profile'] = 50828 - # test_cases['nightly']['deep_link'] = 50836 - # test_cases['nightly']['share_profile'] = 50837 - # test_cases['nightly']['chat_2'] = 50838 - # test_cases['nightly']['group_chat'] = 50839 - # test_cases['nightly']['pairing'] = 50840 - # test_cases['nightly']['activity_center'] = 50833 - # test_cases['nightly']['timeline'] = 50842 - # test_cases['nightly']['community'] = 50841 - # test_cases['nightly']['permissions'] = 50843 - # test_cases['nightly']['scan qr'] = 50844 - # test_cases['nightly']['mentions'] = 50845 - # test_cases['nightly']['mutual_contact_requests'] = 50857 - # test_cases['nightly']['keycard'] = 50850 - # test_cases['nightly']['wallet'] = 50851 + # Nightly e2e + test_cases['nightly']['critical'] = 50955 + test_cases['nightly']['one_to_one_chat'] = 50956 + test_cases['nightly']['deep_links'] = 51535 + test_cases['nightly']['group_chat'] = 50964 + test_cases['nightly']['community_single'] = 50983 + test_cases['nightly']['community_multiple'] = 50982 + test_cases['nightly']['activity_centre_contact_request'] = 50984 + test_cases['nightly']['activity_centre_other'] = 51005 + test_cases['nightly']['wallet'] = 59443 ## Upgrade e2e # test_cases['upgrade']['general'] = 881 @@ -163,11 +135,15 @@ class TestrailReport(BaseTestReport): key, value = arg.split('=') case_ids = value.split(',') if len(case_ids) == 0: - # if 'critical' in argv: - if 'new_ui_critical' in argv: + if 'smoke' in argv: for category in test_cases['pr']: for case in self.get_cases([test_cases['pr'][category]]): case_ids.append(case['id']) + case_ids.extend([703133, 702742, 702745]) + elif 'nightly' in argv: + for category in test_cases['nightly']: + for case in self.get_cases([test_cases['nightly'][category]]): + case_ids.append(case['id']) elif 'upgrade' in argv and 'not upgrade' not in argv: for case in self.get_cases([test_cases['upgrade']['general']]): case_ids.append(case['id']) diff --git a/test/appium/tests/activity_center/test_activity_center.py b/test/appium/tests/activity_center/test_activity_center.py index 681755ee09..4b5b6e7de9 100644 --- a/test/appium/tests/activity_center/test_activity_center.py +++ b/test/appium/tests/activity_center/test_activity_center.py @@ -11,7 +11,7 @@ from views.sign_in_view import SignInView @pytest.mark.xdist_group(name="new_two_2") -@marks.new_ui_critical +@marks.nightly class TestActivityCenterContactRequestMultipleDevicePR(MultipleSharedDeviceTestCase): def prepare_devices(self): @@ -238,7 +238,7 @@ class TestActivityCenterContactRequestMultipleDevicePR(MultipleSharedDeviceTestC @pytest.mark.xdist_group(name="new_four_2") -@marks.new_ui_critical +@marks.nightly class TestActivityMultipleDevicePR(MultipleSharedDeviceTestCase): def prepare_devices(self): @@ -378,7 +378,7 @@ class TestActivityMultipleDevicePR(MultipleSharedDeviceTestCase): @pytest.mark.xdist_group(name="new_six_2") -@marks.new_ui_critical +@marks.nightly class TestActivityMultipleDevicePRTwo(MultipleSharedDeviceTestCase): def prepare_devices(self): diff --git a/test/appium/tests/critical/chats/test_1_1_public_chats.py b/test/appium/tests/critical/chats/test_1_1_public_chats.py index c35f5719ba..44f193a98f 100644 --- a/test/appium/tests/critical/chats/test_1_1_public_chats.py +++ b/test/appium/tests/critical/chats/test_1_1_public_chats.py @@ -13,7 +13,7 @@ from views.sign_in_view import SignInView @pytest.mark.xdist_group(name="new_one_2") -@marks.new_ui_critical +@marks.nightly class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase): def prepare_devices(self): @@ -271,6 +271,7 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase): self.errors.verify_no_errors() @marks.testrail_id(702745) + @marks.smoke def test_1_1_chat_non_latin_messages_stack_update_profile_photo(self): self.home_1.navigate_back_to_home_view() self.home_1.profile_button.click() @@ -533,7 +534,7 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase): @pytest.mark.xdist_group(name="new_six_2") -@marks.new_ui_critical +@marks.nightly class TestOneToOneChatMultipleSharedDevicesNewUiTwo(MultipleSharedDeviceTestCase): def prepare_devices(self): diff --git a/test/appium/tests/critical/chats/test_group_chat.py b/test/appium/tests/critical/chats/test_group_chat.py index 5388e31b2b..442a69a865 100644 --- a/test/appium/tests/critical/chats/test_group_chat.py +++ b/test/appium/tests/critical/chats/test_group_chat.py @@ -12,7 +12,7 @@ from views.sign_in_view import SignInView @pytest.mark.xdist_group(name="new_one_3") -@marks.new_ui_critical +@marks.nightly class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase): def prepare_devices(self): diff --git a/test/appium/tests/critical/chats/test_public_chat_browsing.py b/test/appium/tests/critical/chats/test_public_chat_browsing.py index 0e00666f2b..93b691a2b5 100644 --- a/test/appium/tests/critical/chats/test_public_chat_browsing.py +++ b/test/appium/tests/critical/chats/test_public_chat_browsing.py @@ -15,7 +15,7 @@ from views.sign_in_view import SignInView @pytest.mark.xdist_group(name="new_one_1") -@marks.new_ui_critical +@marks.nightly class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase): def prepare_devices(self): @@ -47,6 +47,7 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase): self.drivers[0].fail("Not navigated to channel view after reopening app") @marks.testrail_id(702742) + @marks.smoke def test_community_copy_and_paste_message_in_chat_input(self): message_texts = ['mmmeowesage_text', 'https://status.im'] if not self.channel.chat_message_input.is_element_displayed(): @@ -152,6 +153,7 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase): self.errors.verify_no_errors() @marks.testrail_id(703133) + @marks.smoke def test_restore_multiaccount_with_waku_backup_remove_switch(self): self.home.reopen_app(sign_in=False) self.home.just_fyi("Restore user with predefined communities and contacts") @@ -261,7 +263,7 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase): self.home.community_card_item.wait_for_elements(seconds=120) expected_communities = { - ' 0xUX': ['Design', 'Ethereum', 'Collaboration'], + # ' 0xUX': ['Design', 'Ethereum', 'Collaboration'], 'Status': ['Web3', 'Blockchain', 'Ethereum'], 'Status Inu': ['News', 'Social', 'Web3'], } @@ -301,7 +303,7 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase): @pytest.mark.xdist_group(name="new_three_2") -@marks.new_ui_critical +@marks.nightly class TestCommunityMultipleDeviceMerged(MultipleSharedDeviceTestCase): def prepare_devices(self): @@ -826,7 +828,7 @@ class TestCommunityMultipleDeviceMerged(MultipleSharedDeviceTestCase): @pytest.mark.xdist_group(name="new_five_2") -@marks.new_ui_critical +@marks.nightly class TestCommunityMultipleDeviceMergedTwo(MultipleSharedDeviceTestCase): def prepare_devices(self): diff --git a/test/appium/tests/critical/test_deep_and_universal_links.py b/test/appium/tests/critical/test_deep_and_universal_links.py index 0e870adcd1..1525e5405d 100644 --- a/test/appium/tests/critical/test_deep_and_universal_links.py +++ b/test/appium/tests/critical/test_deep_and_universal_links.py @@ -6,7 +6,7 @@ from views.sign_in_view import SignInView @pytest.mark.xdist_group(name="new_one_1") -@marks.new_ui_critical +@marks.nightly class TestDeepLinksOneDevice(MultipleSharedDeviceTestCase): def prepare_devices(self): diff --git a/test/appium/tests/critical/test_wallet.py b/test/appium/tests/critical/test_wallet.py index d2f7e94a01..6718838308 100644 --- a/test/appium/tests/critical/test_wallet.py +++ b/test/appium/tests/critical/test_wallet.py @@ -13,8 +13,9 @@ from views.sign_in_view import SignInView @pytest.mark.xdist_group(name="new_four_2") -@marks.new_ui_critical +@marks.nightly @marks.secured +@marks.smoke class TestWalletMultipleDevice(MultipleSharedDeviceTestCase): def prepare_devices(self): @@ -174,7 +175,8 @@ class TestWalletMultipleDevice(MultipleSharedDeviceTestCase): @pytest.mark.xdist_group(name="new_one_2") -@marks.new_ui_critical +@marks.nightly +@marks.smoke class TestWalletOneDevice(MultipleSharedDeviceTestCase): def prepare_devices(self): diff --git a/test/appium/tests/marks.py b/test/appium/tests/marks.py index da55e4039f..2addc25340 100644 --- a/test/appium/tests/marks.py +++ b/test/appium/tests/marks.py @@ -5,8 +5,8 @@ testrail_id = pytest.mark.testrail_id # atomic tests critical = pytest.mark.critical medium = pytest.mark.medium # new ui -new_ui_critical = pytest.mark.new_ui_critical -new_ui_medium = pytest.mark.new_ui_medium +nightly = pytest.mark.nightly +smoke = pytest.mark.smoke flaky = pytest.mark.flaky upgrade = pytest.mark.upgrade diff --git a/test/appium/views/elements_templates/status_community_join_button.png b/test/appium/views/elements_templates/status_community_join_button.png index 9622b5e98d5ce13f89c9bbd1c0337e7834ce3732..bc6f5ecb0c5cbff4e755e0735c39cc8b5aea0637 100644 GIT binary patch literal 6555 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 a*<#I(989?tq=7$OK$?$qR7+KCLjDhY*@NT& literal 6802 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 QoHYS-Gz~N=)u2)T2bT0Q<^TWy diff --git a/test/appium/views/elements_templates/status_community_logo.png b/test/appium/views/elements_templates/status_community_logo.png index 3f61a0ff8c75cb3ed82f213ce35a778e017886e1..3e684da43716353bcf6c02976906163173c6203f 100644 GIT binary patch literal 11030 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

@k&IRA8T-6=eeJINRuj}v8 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 Date: Mon, 15 Jul 2024 14:56:50 +0200 Subject: [PATCH 42/79] Cover wallet send events with tests #20411 (#20533) --- .../contexts/wallet/send/events_test.cljs | 308 +++++++++++++++++- 1 file changed, 307 insertions(+), 1 deletion(-) diff --git a/src/status_im/contexts/wallet/send/events_test.cljs b/src/status_im/contexts/wallet/send/events_test.cljs index a8cb0f1ef6..8d2740b7ac 100644 --- a/src/status_im/contexts/wallet/send/events_test.cljs +++ b/src/status_im/contexts/wallet/send/events_test.cljs @@ -2,8 +2,19 @@ (:require [cljs.test :refer-macros [is testing]] [re-frame.db :as rf-db] + [status-im.contexts.wallet.common.utils.networks :as network-utils] status-im.contexts.wallet.send.events - [test-helpers.unit :as h])) + [status-im.contexts.wallet.send.utils :as send-utils] + [test-helpers.unit :as h] + [utils.money :as money])) + +(defn collectible-with-balance + [balance] + {:name "DOG #1" + :description + "dogs are cute and this one is the cutestdogs are cute and this one is the cutest" + :ownership [{:address "0x01" + :balance balance}]}) (h/deftest-event :wallet/update-receiver-networks [event-id dispatch] @@ -163,3 +174,298 @@ result (dispatch [event-id {:amount amount}])] (testing "amount set" (is (match? amount (get-in result [:db :wallet :ui :send :amount])))))) + +(h/deftest-event :wallet/clean-send-data + [event-id dispatch] + (let [token-symbol "ETH" + token {:symbol "ETH" + :name "Ether" + :networks #{{:chain-id 421614} + {:chain-id 11155420} + {:chain-id 11155111}}} + receiver-networks [421614 11155420]] + (reset! rf-db/app-db + {:wallet {:ui {:send {:token-display-name token-symbol + :token token + :receiver-networks receiver-networks} + :other-props :value}}}) + (is (not (contains? (get-in (dispatch [event-id]) [:db :wallet :ui]) :send))))) + +(h/deftest-event :wallet/select-address-tab + [event-id dispatch] + (let [expected-db {:wallet {:ui {:send {:select-address-tab "tab"}}}}] + (reset! rf-db/app-db + {:wallet {:ui {:send nil}}}) + (is (match? expected-db (:db (dispatch [event-id "tab"])))))) + +(h/deftest-event :wallet/clean-send-address + [event-id dispatch] + (reset! rf-db/app-db + {:wallet {:ui {:send {:to-address "0x01" + :recipient {:recipient-type :saved-address + :label "label"} + :other-props :value}}}}) + (is (not (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :to-address))) + (is (not (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :recipient))) + (is (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :other-props))) + +(h/deftest-event :wallet/clean-send-amount + [event-id dispatch] + (reset! rf-db/app-db + {:wallet {:ui {:send {:amount 10 + :other-props :value}}}}) + (is (not (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :amount))) + (is (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :other-props))) + +(h/deftest-event :wallet/clean-disabled-from-networks + [event-id dispatch] + (reset! rf-db/app-db + {:wallet {:ui {:send {:disabled-from-chain-ids [:optimism] + :other-props :value}}}}) + (is (not (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :disabled-from-chain-ids))) + (is (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :other-props))) + +(h/deftest-event :wallet/clean-from-locked-amounts + [event-id dispatch] + (reset! rf-db/app-db + {:wallet {:ui {:send {:from-locked-amounts "value" + :other-props :value}}}}) + (is (not (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :from-locked-amounts))) + (is (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :other-props))) + +(h/deftest-event :wallet/disable-from-networks + [event-id dispatch] + (let [expected-db {:wallet {:ui {:send {:disabled-from-chain-ids [:optimism] + :other-props :value}}}}] + (reset! rf-db/app-db + {:wallet {:ui {:send {:other-props :value}}}}) + (is (match? expected-db (:db (dispatch [event-id [:optimism]])))))) + +(h/deftest-event :wallet/unlock-from-amount + [event-id dispatch] + (let [expected-db {:wallet {:ui {:send {:other-props :value + :from-locked-amounts {}}}}}] + (reset! rf-db/app-db + {:wallet {:ui {:send {:other-props :value + :from-locked-amounts {:chain-id [1 10]}}}}}) + (is (match? expected-db (:db (dispatch [event-id :chain-id])))))) + +(h/deftest-event :wallet/lock-from-amount + [event-id dispatch] + (let [expected-db {:wallet {:ui {:send {:other-props :value + :from-locked-amounts {:amount 10}}}}}] + (reset! rf-db/app-db + {:wallet {:ui {:send {:other-props :value}}}}) + (is (match? expected-db (:db (dispatch [event-id :amount 10])))))) + +(h/deftest-event :wallet/clean-selected-token + [event-id dispatch] + (reset! rf-db/app-db + {:wallet {:ui {:send {:other-props :value + :token "ETH" + :token-display-name "ETH" + :tx-type :tx/collectible-erc-721}}}}) + (is (not (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :token))) + (is (not (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :token-display-name))) + (is (not (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :tx-type))) + (is (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :other-props))) + +(h/deftest-event :wallet/clean-selected-collectible + [event-id dispatch] + (reset! rf-db/app-db + {:wallet {:ui {:send {:other-props :value + :collectible "ETH" + :token-display-name "ETH" + :amount 10}}}}) + (is (not (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :collectible))) + (is (not (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :token-display-name))) + (is (not (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :amount))) + (is (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :other-props))) + +(h/deftest-event :wallet/clean-suggested-routes + [event-id dispatch] + (reset! rf-db/app-db + {:wallet {:ui {:send + {:other-props :value + :suggested-routes ["1" "2"] + :route "1" + :amount 10 + :from-values-by-chain [{:chain-id 1} {:chain-id 10} {:chain-id 42161}] + :to-values-by-chain [{:chain-id 1} {:chain-id 10} {:chain-id 42161}] + :sender-network-values [:eth :arb1] + :receiver-network-values [:eth :arb1] + :network-links [{:from-chain-id 1 + :to-chain-id 10 + :position-diff 1}] + :loading-suggested-routes? false + :suggested-routes-call-timestamp ["1" "2"]}}}}) + (is (not (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :suggested-routes))) + (is (not (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :route))) + (is (not (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :amount))) + (is (not (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :from-values-by-chain))) + (is (not (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :to-values-by-chain))) + (is (not (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :sender-network-values))) + (is (not (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :receiver-network-values))) + (is (not (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :network-links))) + (is (not (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) + :loading-suggested-routes?))) + (is (not (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) + :suggested-routes-call-timestamp))) + (is (contains? (get-in (dispatch [event-id]) [:db :wallet :ui :send]) :other-props))) + +(h/deftest-event :wallet/suggested-routes-error + [event-id dispatch] + (let [sender-network-amounts [{:chain-id 1 :total-amount (money/bignumber "100") :type :loading} + {:chain-id 10 :total-amount (money/bignumber "200") :type :default}] + receiver-network-amounts [{:chain-id 1 :total-amount (money/bignumber "100") :type :loading}] + expected-result {:db {:wallet {:ui {:send + {:sender-network-values + (send-utils/reset-loading-network-amounts-to-zero + sender-network-amounts) + :receiver-network-values + (send-utils/reset-loading-network-amounts-to-zero + receiver-network-amounts) + :loading-suggested-routes? false + :suggested-routes {:best []}}}}} + :fx [[:dispatch + [:toasts/upsert + {:id :send-transaction-error + :type :negative + :text "error"}]]]}] + (reset! rf-db/app-db + {:wallet {:ui {:send {:sender-network-values sender-network-amounts + :receiver-network-values receiver-network-amounts + :route :values + :loading-suggested-routes? true}}}}) + (is (match? expected-result (dispatch [event-id {:message "error"}]))))) + +(h/deftest-event :wallet/reset-network-amounts-to-zero + [event-id dispatch] + (let [sender-network-values [{:chain-id 1 :total-amount (money/bignumber "100") :type :default} + {:chain-id 10 :total-amount (money/bignumber "200") :type :default}] + receiver-network-values [{:chain-id 1 :total-amount (money/bignumber "100") :type :loading}] + disabled-from-chain-ids [:ethereum] + sender-network-zero (send-utils/reset-network-amounts-to-zero + {:network-amounts sender-network-values + :disabled-chain-ids disabled-from-chain-ids}) + receiver-network-zero (send-utils/reset-network-amounts-to-zero + {:network-amounts receiver-network-values + :disabled-chain-ids []})] + (testing "if sender-network-value and receiver-network-value are not empty" + (let [expected-db {:wallet {:ui {:send {:other-props :value + :sender-network-values sender-network-zero + :receiver-network-values receiver-network-zero}}}}] + (reset! rf-db/app-db + {:wallet {:ui {:send {:other-props :value + :sender-network-values sender-network-values + :receiver-network-values receiver-network-values + :network-links [{:from-chain-id 1 + :to-chain-id 10 + :position-diff 1}]}}}}) + (is (match? expected-db (:db (dispatch [event-id])))))) + (testing "if only receiver-network-value is empty" + (let [expected-db {:wallet {:ui {:send {:other-props :value + :sender-network-values sender-network-zero}}}}] + (reset! rf-db/app-db + {:wallet {:ui {:send {:other-props :value + :sender-network-values sender-network-values + :receiver-network-values [] + :network-links [{:from-chain-id 1 + :to-chain-id 10 + :position-diff 1}]}}}}) + (is (match? expected-db (:db (dispatch [event-id])))))) + (testing "if receiver-network-value and sender-network-values are empty" + (let [expected-db {:wallet {:ui {:send {:other-props :value}}}}] + (reset! rf-db/app-db + {:wallet {:ui {:send {:other-props :value + :sender-network-values [] + :receiver-network-values [] + :network-links [{:from-chain-id 1 + :to-chain-id 10 + :position-diff 1}]}}}}) + (is (match? expected-db (:db (dispatch [event-id])))))))) + +(h/deftest-event :wallet/select-send-address + [event-id dispatch] + (let [address "eth:arb1:0x01" + prefix "eth:arb1:" + to-address "0x01" + recipient {:type :saved-address + :label "0x01...23f"} + stack-id :screen/wallet.select-address + start-flow? false + tx-type :tx/collectible-erc-721] + (testing "testing when collectible balance is more than 1" + (let [collectible (collectible-with-balance 2) + testnet-enabled? false + goerli-enabled? false + receiver-networks (network-utils/resolve-receiver-networks + {:prefix prefix + :testnet-enabled? testnet-enabled? + :goerli-enabled? goerli-enabled?}) + expected-result {:db {:wallet {:ui {:send {:other-props :value + :recipient recipient + :to-address to-address + :address-prefix prefix + :receiver-preferred-networks + receiver-networks + :receiver-networks receiver-networks + :tx-type tx-type + :collectible collectible}}} + :profile/profile {:test-networks-enabled? false + :is-goerli-enabled? false}} + :fx [nil + [:dispatch + [:wallet/wizard-navigate-forward + {:current-screen stack-id + :start-flow? start-flow? + :flow-id :wallet-send-flow}]]]}] + (reset! rf-db/app-db + {:wallet {:ui {:send {:other-props :value + :tx-type tx-type + :collectible collectible}}} + :profile/profile {:test-networks-enabled? testnet-enabled? + :is-goerli-enabled? goerli-enabled?}}) + (is (match? expected-result + (dispatch [event-id + {:address address + :recipient recipient + :stack-id stack-id + :start-flow? start-flow?}]))))) + (testing "testing when collectible balance is 1" + (let [collectible (collectible-with-balance 1) + testnet-enabled? false + goerli-enabled? false + receiver-networks (network-utils/resolve-receiver-networks + {:prefix prefix + :testnet-enabled? testnet-enabled? + :goerli-enabled? goerli-enabled?}) + expected-result {:db {:wallet {:ui {:send {:other-props :value + :recipient recipient + :to-address to-address + :address-prefix prefix + :receiver-preferred-networks + receiver-networks + :receiver-networks receiver-networks + :tx-type tx-type + :collectible collectible}}} + :profile/profile {:test-networks-enabled? false + :is-goerli-enabled? false}} + :fx [[:dispatch [:wallet/get-suggested-routes {:amount 1}]] + [:dispatch + [:wallet/wizard-navigate-forward + {:current-screen stack-id + :start-flow? start-flow? + :flow-id :wallet-send-flow}]]]}] + (reset! rf-db/app-db + {:wallet {:ui {:send {:other-props :value + :tx-type tx-type + :collectible collectible}}} + :profile/profile {:test-networks-enabled? testnet-enabled? + :is-goerli-enabled? goerli-enabled?}}) + (is (match? expected-result + (dispatch [event-id + {:address address + :recipient recipient + :stack-id stack-id + :start-flow? start-flow?}]))))))) From 940edc2e53bf1e3a4e671c2dac7ae1cb329e348d Mon Sep 17 00:00:00 2001 From: Ulises Manuel <90291778+ulisesmac@users.noreply.github.com> Date: Mon, 15 Jul 2024 12:34:02 -0600 Subject: [PATCH 43/79] fix(wallet): Some collectibles were displayed as "can iOS can't fetch collectible error (#20714) --- .../profile/expanded_collectible/view.cljs | 10 ++- .../contexts/wallet/collectible/events.cljs | 2 +- .../contexts/wallet/collectible/view.cljs | 74 +++++++++---------- 3 files changed, 46 insertions(+), 40 deletions(-) diff --git a/src/quo/components/profile/expanded_collectible/view.cljs b/src/quo/components/profile/expanded_collectible/view.cljs index 7bcc2fc553..7187573660 100644 --- a/src/quo/components/profile/expanded_collectible/view.cljs +++ b/src/quo/components/profile/expanded_collectible/view.cljs @@ -79,14 +79,20 @@ [counter-view counter]) [rn/view {:style (style/collectible-border theme)}]]])) +(defn invalid-image? + [image-src] + (or (nil? image-src) + (string/blank? image-src))) + (defn view-internal [{:keys [container-style square? on-press counter image-src native-ID supported-file? on-collectible-load aspect-ratio gradient-color-index] :or {gradient-color-index :gradient-1 on-collectible-load (fn [])}}] (let [theme (quo.theme/use-theme) - [error? set-error] (rn/use-state (or (nil? image-src) - (string/blank? image-src)))] + [error? set-error] (rn/use-state (invalid-image? image-src))] + (rn/use-effect #(set-error (invalid-image? image-src)) + [image-src]) [rn/pressable {:style (merge container-style (style/container aspect-ratio)) :accessibility-label :expanded-collectible diff --git a/src/status_im/contexts/wallet/collectible/events.cljs b/src/status_im/contexts/wallet/collectible/events.cljs index a95b231537..f78b06bb9a 100644 --- a/src/status_im/contexts/wallet/collectible/events.cljs +++ b/src/status_im/contexts/wallet/collectible/events.cljs @@ -185,7 +185,7 @@ ;; We delay the navigation because we need re-frame to update the DB on time. ;; By doing it, we skip a blink while visiting the collectible detail page. [:dispatch-later - {:ms 1 + {:ms 17 :dispatch [:navigate-to :screen/wallet.collectible]}]]}))) (defn- keep-not-empty-value diff --git a/src/status_im/contexts/wallet/collectible/view.cljs b/src/status_im/contexts/wallet/collectible/view.cljs index 00e4d06fde..cb86c75c04 100644 --- a/src/status_im/contexts/wallet/collectible/view.cljs +++ b/src/status_im/contexts/wallet/collectible/view.cljs @@ -79,12 +79,20 @@ (rf/dispatch [:wallet/clear-collectible-details])) (defn animated-header - [{:keys [scroll-amount title-opacity page-nav-type picture title description theme id]}] - (let [blur-amount (header-animations/use-blur-amount scroll-amount) - layer-opacity (header-animations/use-layer-opacity - scroll-amount - (colors/theme-colors colors/white-opa-0 colors/neutral-95-opa-0 theme) - (colors/theme-colors colors/white-opa-50 colors/neutral-95-opa-70-blur theme))] + [{:keys [scroll-amount title-opacity page-nav-type theme]}] + (let [blur-amount (header-animations/use-blur-amount scroll-amount) + layer-opacity (header-animations/use-layer-opacity + scroll-amount + (colors/theme-colors colors/white-opa-0 + colors/neutral-95-opa-0 + theme) + (colors/theme-colors colors/white-opa-50 + colors/neutral-95-opa-70-blur + theme)) + {{preview-uri :uri} :preview-url + {title :name} :collectible-data + {description :name} :collection-data + id :id} (rf/sub [:wallet/collectible-details])] [rn/view {:style (style/animated-header)} [reanimated/blur-view {:style {:flex 1 @@ -97,7 +105,7 @@ [reanimated/view {:style layer-opacity} [quo/page-nav {:type page-nav-type - :picture picture + :picture preview-uri :title title :description description :background :blur @@ -110,7 +118,7 @@ {:content (fn [] [options-drawer/view {:name title - :image picture + :image preview-uri :id id}]) :theme theme}])}] :center-opacity title-opacity}]]]])) @@ -144,10 +152,11 @@ [_] (let [selected-tab (reagent/atom :overview) on-tab-change #(reset! selected-tab %)] - (fn [{:keys [collectible set-title-bottom theme]}] + (fn [{:keys [set-title-bottom theme]}] (let [title-ref (rn/use-ref-atom nil) set-title-ref (rn/use-callback #(reset! title-ref %)) animation-shared-element-id (rf/sub [:animation-shared-element-id]) + collectible (rf/sub [:wallet/collectible-details]) collectible-owner (rf/sub [:wallet/collectible-details-owner collectible]) aspect-ratio (rf/sub [:wallet/collectible-aspect-ratio]) gradient-color (rf/sub [:wallet/collectible-gradient-color]) @@ -196,14 +205,14 @@ token-id {:images [collectible-image] :index 0 - :on-options-press #(rf/dispatch [:show-bottom-sheet - {:content - (fn [] - [options-drawer/view - {:name collectible-name - :image - preview-uri - :id id}])}])}]))) + :on-options-press #(rf/dispatch + [:show-bottom-sheet + {:content + (fn [] + [options-drawer/view + {:name collectible-name + :image preview-uri + :id id}])}])}]))) :on-collectible-load (fn [] ;; We need to delay the measurement because the ;; navigation has an animation @@ -241,33 +250,24 @@ (defn view [_] - (let [{:keys [top]} (safe-area/get-insets) - theme (quo.theme/use-theme) - title-bottom-coord (rn/use-ref-atom 0) - set-title-bottom (rn/use-callback - (fn [_ y _ height] - (reset! title-bottom-coord - (get-title-bottom-y-position y height)))) - scroll-amount (reanimated/use-shared-value 0) - title-opacity (reanimated/use-shared-value 0) - {:keys [collection-data - collectible-data - preview-url] - :as collectible} (rf/sub [:wallet/collectible-details])] + (let [{:keys [top]} (safe-area/get-insets) + theme (quo.theme/use-theme) + title-bottom-coord (rn/use-ref-atom 0) + set-title-bottom (rn/use-callback + (fn [_ y _ height] + (reset! title-bottom-coord + (get-title-bottom-y-position y height)))) + scroll-amount (reanimated/use-shared-value 0) + title-opacity (reanimated/use-shared-value 0)] [rn/view {:style (style/background-color theme)} [animated-header - {:id (:id collectible) - :scroll-amount scroll-amount + {:scroll-amount scroll-amount :title-opacity title-opacity :page-nav-type :title-description - :picture (:uri preview-url) - :title (:name collectible-data) - :description (:name collection-data) :theme theme}] [reanimated/scroll-view {:style (style/scroll-view top) :on-scroll #(on-scroll % scroll-amount title-opacity title-bottom-coord)} [collectible-details - {:collectible collectible - :set-title-bottom set-title-bottom + {:set-title-bottom set-title-bottom :theme theme}]]])) From bd9e440839afa97badd015626f9c3a47d3d985e1 Mon Sep 17 00:00:00 2001 From: Nikolay Date: Tue, 16 Jul 2024 13:42:38 +0300 Subject: [PATCH 44/79] [18942] Slideshow: slider-bar component (#20615) --- .../slideshow/slider_bar/component_spec.cljs | 25 +++ .../slideshow/slider_bar/schema.cljs | 14 ++ .../slideshow/slider_bar/style.cljs | 31 ++++ .../components/slideshow/slider_bar/view.cljs | 150 ++++++++++++++++++ src/quo/core.cljs | 4 + src/quo/core_spec.cljs | 1 + src/status_im/contexts/preview/quo/main.cljs | 3 + .../preview/quo/slideshow/slider_bar.cljs | 49 ++++++ .../contexts/shell/share/wallet/style.cljs | 18 --- .../contexts/shell/share/wallet/view.cljs | 16 +- 10 files changed, 281 insertions(+), 30 deletions(-) create mode 100644 src/quo/components/slideshow/slider_bar/component_spec.cljs create mode 100644 src/quo/components/slideshow/slider_bar/schema.cljs create mode 100644 src/quo/components/slideshow/slider_bar/style.cljs create mode 100644 src/quo/components/slideshow/slider_bar/view.cljs create mode 100644 src/status_im/contexts/preview/quo/slideshow/slider_bar.cljs delete mode 100644 src/status_im/contexts/shell/share/wallet/style.cljs diff --git a/src/quo/components/slideshow/slider_bar/component_spec.cljs b/src/quo/components/slideshow/slider_bar/component_spec.cljs new file mode 100644 index 0000000000..bc9cc1e8d0 --- /dev/null +++ b/src/quo/components/slideshow/slider_bar/component_spec.cljs @@ -0,0 +1,25 @@ +(ns quo.components.slideshow.slider-bar.component-spec + (:require + [quo.components.slideshow.slider-bar.view :as slider-bar] + [test-helpers.component :as h])) + +(h/describe "Slideshow: slider-bar" + (h/test "default render" + (h/render-with-theme-provider [slider-bar/view {:accessibility-label :slider-bar}]) + (h/is-truthy (h/query-by-label-text :slider-bar))) + + (h/test "render with total-amount and active-index" + (h/render-with-theme-provider [slider-bar/view + {:total-amount 4 + :active-index 1 + :accessibility-label :slider-bar}]) + (h/is-truthy (h/query-by-label-text :slider-bar)) + (h/is-equal 4 (count (h/query-all-by-label-text :slide-bar-item)))) + + (h/test "render with total-amount active-index and possible scroll" + (h/render-with-theme-provider [slider-bar/view + {:total-amount 10 + :active-index 7 + :accessibility-label :slider-bar}]) + (h/is-truthy (h/query-by-label-text :slider-bar)) + (h/is-equal 10 (count (h/query-all-by-label-text :slide-bar-item))))) diff --git a/src/quo/components/slideshow/slider_bar/schema.cljs b/src/quo/components/slideshow/slider_bar/schema.cljs new file mode 100644 index 0000000000..e479cc950d --- /dev/null +++ b/src/quo/components/slideshow/slider_bar/schema.cljs @@ -0,0 +1,14 @@ +(ns quo.components.slideshow.slider-bar.schema) + +(def ?schema + [:=> + [:cat + [:map {:closed true} + [:total-amount {:optional true} [:maybe :int]] + [:active-index {:optional true} :int] + [:customization-color {:optional true} + [:maybe :schema.common/customization-color]] + [:blur? {:optional true} [:maybe :boolean]] + [:accessibility-label {:optional true} [:maybe :keyword]] + [:container-style {:optional true} [:maybe :map]]]] + :any]) diff --git a/src/quo/components/slideshow/slider_bar/style.cljs b/src/quo/components/slideshow/slider_bar/style.cljs new file mode 100644 index 0000000000..504a1e289d --- /dev/null +++ b/src/quo/components/slideshow/slider_bar/style.cljs @@ -0,0 +1,31 @@ +(ns quo.components.slideshow.slider-bar.style + (:require + [quo.foundations.colors :as colors])) + +(def list-wrapper + {:align-items :center}) + +(defn list-bar + [bar-width] + {:width bar-width}) + +(defn item-wrapper + [{:keys [size spacing]}] + {:width size + :height size + :margin-left spacing + :flex 1 + :align-items :center + :justify-content :center}) + +(defn item + [{:keys [size spacing active? customization-color theme blur?]}] + {:width size + :height size + :border-radius spacing + :background-color + (cond + (and blur? active?) colors/white + (and blur? (not active?)) colors/white-opa-10 + (and (not blur?) active?) (colors/resolve-color customization-color theme) + :else (colors/theme-colors colors/neutral-20 colors/neutral-80 theme))}) diff --git a/src/quo/components/slideshow/slider_bar/view.cljs b/src/quo/components/slideshow/slider_bar/view.cljs new file mode 100644 index 0000000000..f95f60cbd0 --- /dev/null +++ b/src/quo/components/slideshow/slider_bar/view.cljs @@ -0,0 +1,150 @@ +(ns quo.components.slideshow.slider-bar.view + (:require + [quo.components.slideshow.slider-bar.schema :as component-schema] + [quo.components.slideshow.slider-bar.style :as style] + [quo.theme] + [react-native.core :as rn] + [react-native.reanimated :as reanimated] + [schema.core :as schema])) + +(def item-size 8) +(def item-spacing item-size) +(def max-length 6) +(def bar-width (* (+ item-spacing item-size) max-length)) + +(def micro-scale 0.25) +(def small-scale 0.5) +(def medium-scale 0.75) +(def default-scale 1) + +(def items-before-scroll + (/ max-length 2)) + +(defn- calc-relative-index + "Calculates item index in visible area" + [index active-index total-length] + (let [last-index (dec total-length)] + (cond + (< active-index items-before-scroll) index + (> active-index + (- last-index items-before-scroll)) (+ (- index total-length) max-length) + :else (+ (- index active-index) items-before-scroll)))) + +(defn- calc-first-item-scale + "Calculates scale for first item in visible area" + [active-index] + (cond + (= active-index items-before-scroll) medium-scale + (> active-index items-before-scroll) small-scale + :else default-scale)) + +(defn- calc-second-item-scale + "Calculates scale for second item in visible area" + [active-index] + (if (> active-index items-before-scroll) + medium-scale + default-scale)) + +(defn- calc-last-item-scale + "Calculates scale for last item in visible area" + [active-index total-length] + (let [last-index (dec total-length)] + (if (> active-index (- last-index items-before-scroll)) + medium-scale + small-scale))) + +(defn- calc-last-but-one-item-scale + "Calculates scale for before last item in visible area" + [active-index total-length] + (if (> active-index (- (dec total-length) items-before-scroll)) + default-scale + medium-scale)) + +(defn- get-scale + [index active-index total-length] + (let [relative-index (calc-relative-index index active-index total-length)] + (if (or (= index active-index) + (< total-length max-length)) + default-scale + (cond + (< relative-index 0) micro-scale + (= relative-index 0) (calc-first-item-scale active-index) + (= relative-index 1) (calc-second-item-scale active-index) + (= relative-index 4) (calc-last-but-one-item-scale active-index total-length) + (= relative-index 5) (calc-last-item-scale active-index total-length) + (> relative-index 5) micro-scale + :else + default-scale)))) + +(defn- bar-item + [index _ _ {:keys [active-index total-length customization-color theme blur?]}] + (let [active? (= index active-index) + shared-scale (reanimated/use-shared-value (get-scale index active-index total-length))] + (rn/use-effect (fn [] + (let [new-scale-value (get-scale index active-index total-length)] + (reanimated/animate shared-scale new-scale-value))) + [index active-index total-length]) + [rn/view + {:key index + :style (style/item-wrapper {:size item-size + :spacing item-spacing})} + [reanimated/view + {:accessibility-label :slide-bar-item + :style + (reanimated/apply-animations-to-style + {:transform [{:scale shared-scale}]} + (style/item {:size item-size + :spacing item-spacing + :active? active? + :customization-color customization-color + :theme theme + :blur? blur?}))}]])) + +(defn- get-item-layout + [_ index] + (let [length (+ item-size item-spacing)] + #js {:length length + :index index + :offset (* index length)})) + +(defn- view-internal + [{:keys [customization-color blur? accessibility-label container-style] + :as props}] + (let [active-index (or (:active-index props) 0) + total-amount (or (:total-amount props) 1) + theme (quo.theme/use-theme) + flat-list-ref (rn/use-ref-atom nil) + set-flat-list-ref (rn/use-callback #(reset! flat-list-ref %)) + center-position 0.5 + data (range total-amount) + scroll-to-index (rn/use-callback + (fn [] + (some-> ^js @flat-list-ref + (.scrollToIndex #js {:animated true + :index active-index + :viewOffset item-spacing + :viewPosition center-position}))) + [flat-list-ref active-index])] + (rn/use-effect scroll-to-index [active-index]) + [rn/view + {:style (merge style/list-wrapper container-style) + :accessibility-label accessibility-label} + [reanimated/flat-list + {:style (style/list-bar bar-width) + :data data + :scroll-enabled false + :ref set-flat-list-ref + :shows-horizontal-scroll-indicator false + :bounces false + :horizontal true + :extra-data (str active-index) + :render-data {:active-index active-index + :total-length total-amount + :customization-color customization-color + :theme theme + :blur? blur?} + :get-item-layout get-item-layout + :render-fn bar-item + :key-fn identity}]])) + +(def view (schema/instrument #'view-internal component-schema/?schema)) diff --git a/src/quo/core.cljs b/src/quo/core.cljs index dd01cf0b75..a819ee74d7 100644 --- a/src/quo/core.cljs +++ b/src/quo/core.cljs @@ -147,6 +147,7 @@ quo.components.settings.settings-item.view quo.components.share.qr-code.view quo.components.share.share-qr-code.view + quo.components.slideshow.slider-bar.view quo.components.switchers.group-messaging-card.view quo.components.tabs.account-selector quo.components.tabs.segmented-tab @@ -418,6 +419,9 @@ (def qr-code quo.components.share.qr-code.view/view) (def share-qr-code quo.components.share.share-qr-code.view/view) +;;;; Slideshow +(def slider-bar quo.components.slideshow.slider-bar.view/view) + ;;;; SWITCHER (def group-messaging-card quo.components.switchers.group-messaging-card.view/view) diff --git a/src/quo/core_spec.cljs b/src/quo/core_spec.cljs index aa685d80c5..59d76a356e 100644 --- a/src/quo/core_spec.cljs +++ b/src/quo/core_spec.cljs @@ -87,6 +87,7 @@ quo.components.settings.reorder-item.component-spec quo.components.settings.settings-item.component-spec quo.components.share.share-qr-code.component-spec + quo.components.slideshow.slider-bar.component-spec quo.components.switchers.base-card.component-spec quo.components.switchers.group-messaging-card.component-spec quo.components.tags.collectible-tag.component-spec diff --git a/src/status_im/contexts/preview/quo/main.cljs b/src/status_im/contexts/preview/quo/main.cljs index 2f6ff77299..795cb39300 100644 --- a/src/status_im/contexts/preview/quo/main.cljs +++ b/src/status_im/contexts/preview/quo/main.cljs @@ -170,6 +170,7 @@ [status-im.contexts.preview.quo.settings.settings-item :as settings-item] [status-im.contexts.preview.quo.share.qr-code :as qr-code] [status-im.contexts.preview.quo.share.share-qr-code :as share-qr-code] + [status-im.contexts.preview.quo.slideshow.slider-bar :as slider-bar] [status-im.contexts.preview.quo.style :as style] [status-im.contexts.preview.quo.switcher.group-messaging-card :as group-messaging-card] @@ -502,6 +503,8 @@ :component qr-code/view} {:name :share-qr-code :component share-qr-code/view}] + :slideshow [{:name :slider-bar + :component slider-bar/view}] :switchers [{:name :group-messaging-card :component group-messaging-card/view} {:name :switcher-cards diff --git a/src/status_im/contexts/preview/quo/slideshow/slider_bar.cljs b/src/status_im/contexts/preview/quo/slideshow/slider_bar.cljs new file mode 100644 index 0000000000..c2d332305b --- /dev/null +++ b/src/status_im/contexts/preview/quo/slideshow/slider_bar.cljs @@ -0,0 +1,49 @@ +(ns status-im.contexts.preview.quo.slideshow.slider-bar + (:require + [quo.core :as quo] + [react-native.core :as rn] + [status-im.contexts.preview.quo.preview :as preview])) + +(def descriptor + [{:key :total-amount + :type :number} + {:label "Blur (dark only)?" + :key :blur? + :type :boolean} + (preview/customization-color-option)]) + +(defn view + [] + (let [[state set-state] (rn/use-state {:total-amount 10 + :active-index 0 + :blur? false + :customization-color :blue})] + [preview/preview-container + {:state state + :set-state set-state + :descriptor descriptor + :blur? (:blur? state) + :show-blur-background? true + :blur-dark-only? true} + [rn/view + {:style {:flex-direction :row + :justify-content :center + :gap 10 + :padding 10}} + [quo/button + {:type :outline + :style {:disabled true} + :disabled? (zero? (:active-index state)) + :icon-only? true + :on-press (fn [] + (set-state (update state :active-index dec)))} + :i/chevron-left] + [quo/button + {:type :outline + :disabled? (= (:active-index state) + (dec (:total-amount state))) + :icon-only? true + :on-press (fn [] + (set-state (update state :active-index inc)))} + :i/chevron-right]] + [quo/slider-bar state]])) diff --git a/src/status_im/contexts/shell/share/wallet/style.cljs b/src/status_im/contexts/shell/share/wallet/style.cljs deleted file mode 100644 index b5e029c67f..0000000000 --- a/src/status_im/contexts/shell/share/wallet/style.cljs +++ /dev/null @@ -1,18 +0,0 @@ -(ns status-im.contexts.shell.share.wallet.style - (:require - [quo.foundations.colors :as colors])) - -(defn indicator-wrapper-style - [active?] - {:width 8 - :height 8 - :border-radius 4 - :background-color colors/white - :opacity (if active? 1.0 0.5)}) - -(def indicator-list-style - {:display :flex - :flex-direction :row - :align-items :center - :justify-content :center - :gap 8}) diff --git a/src/status_im/contexts/shell/share/wallet/view.cljs b/src/status_im/contexts/shell/share/wallet/view.cljs index 7b379adc93..3962bb3fd6 100644 --- a/src/status_im/contexts/shell/share/wallet/view.cljs +++ b/src/status_im/contexts/shell/share/wallet/view.cljs @@ -6,7 +6,6 @@ [react-native.platform :as platform] [reagent.core :as reagent] [status-im.contexts.shell.share.style :as style] - [status-im.contexts.shell.share.wallet.style :as wallet-style] [status-im.contexts.wallet.common.utils :as utils] [status-im.contexts.wallet.common.utils.networks :as network-utils] [status-im.contexts.wallet.sheets.network-preferences.view :as network-preferences] @@ -85,16 +84,6 @@ :on-legacy-press on-legacy-press :on-settings-press on-settings-press}]]])))) -(defn- indicator - [active?] - [rn/view {:style (wallet-style/indicator-wrapper-style active?)}]) - -(defn- indicator-list - [num-indicators current-index] - [rn/view {:style wallet-style/indicator-list-style} - (for [i (range num-indicators)] - ^{:key i} [indicator (= current-index i)])]) - (defn render-item [{:keys [address] :as account}] [wallet-qr-code-item @@ -136,4 +125,7 @@ :render-fn render-item}] (when (> num-accounts 1) [rn/view {:style {:margin-top 20}} - [indicator-list num-accounts @current-index]])])))) + [quo/slider-bar + {:total-amount num-accounts + :active-index @current-index + :blur? true}]])])))) From 588692e0ebeb5d967c1da22292653bc09c740133 Mon Sep 17 00:00:00 2001 From: Lungu Cristian Date: Tue, 16 Jul 2024 14:36:16 +0300 Subject: [PATCH 45/79] Wallet Connect message signing (#20693) * feat: updated signing endpoints and refactor https://github.com/status-im/status-go/compare/6e056348...e8aec741 * fix: address review comments --- ios/Podfile.lock | 4 +- src/legacy/status_im/utils/hex.cljs | 16 ----- src/status_im/contexts/wallet/send/utils.cljs | 2 +- .../wallet/wallet_connect/effects.cljs | 49 +++++++++------ .../wallet_connect/responding_events.cljs | 28 +++------ .../contexts/wallet/wallet_connect/rpc.cljs | 61 +++++++++++++++++++ .../wallet/wallet_connect/signing.cljs | 32 ++++++++++ .../wallet/wallet_connect/transactions.cljs | 58 +++--------------- src/status_im/subs/wallet/activities.cljs | 2 +- src/status_im/subs/wallet/wallet_connect.cljs | 2 +- src/utils/hex.cljs | 27 ++++++++ 11 files changed, 171 insertions(+), 110 deletions(-) delete mode 100644 src/legacy/status_im/utils/hex.cljs create mode 100644 src/status_im/contexts/wallet/wallet_connect/rpc.cljs create mode 100644 src/status_im/contexts/wallet/wallet_connect/signing.cljs create mode 100644 src/utils/hex.cljs diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b48f3e9ca2..25e7f83d35 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -915,7 +915,7 @@ PODS: - glog - RCT-Folly (= 2022.05.16.00) - React-Core - - react-native-compat (2.12.2): + - react-native-compat (2.11.2): - glog - RCT-Folly (= 2022.05.16.00) - React-Core @@ -1517,7 +1517,7 @@ SPEC CHECKSUMS: react-native-blob-util: 600972b1782380a5a7d5db61a3817ea32349dae9 react-native-blur: 799045500f56146afc46245148080e7b7623cb75 react-native-cameraroll: af8eec1e585d053ff485d98ec837f9a8a11b5745 - react-native-compat: 84e00e8dcff9251278c0d48f2bce81f4502e3925 + react-native-compat: 3af9add14d349701306d3d052638435f6795ac2c react-native-config: 5330c8258265c1e5fdb8c009d2cabd6badd96727 react-native-get-random-values: 21325b2244dfa6b58878f51f9aa42821e7ba3d06 react-native-hole-view: 6935448993bac79f2b5a4ad7e9741094cf810679 diff --git a/src/legacy/status_im/utils/hex.cljs b/src/legacy/status_im/utils/hex.cljs deleted file mode 100644 index c292732b7b..0000000000 --- a/src/legacy/status_im/utils/hex.cljs +++ /dev/null @@ -1,16 +0,0 @@ -(ns legacy.status-im.utils.hex - (:require - [clojure.string :as string])) - -(defn normalize-hex - [hex] - (when hex - (string/lower-case (if (string/starts-with? hex "0x") - (subs hex 2) - hex)))) - -(defn valid-hex? - [hex] - (let [hex (normalize-hex hex)] - (and (re-matches #"^[0-9a-fA-F]+$" hex) - (not= (js/parseInt hex 16) 0)))) diff --git a/src/status_im/contexts/wallet/send/utils.cljs b/src/status_im/contexts/wallet/send/utils.cljs index d297a3981d..479547a6ec 100644 --- a/src/status_im/contexts/wallet/send/utils.cljs +++ b/src/status_im/contexts/wallet/send/utils.cljs @@ -1,8 +1,8 @@ (ns status-im.contexts.wallet.send.utils (:require - [legacy.status-im.utils.hex :as utils.hex] [native-module.core :as native-module] [status-im.contexts.wallet.common.utils.networks :as network-utils] + [utils.hex :as utils.hex] [utils.money :as money])) (defn amount-in-hex diff --git a/src/status_im/contexts/wallet/wallet_connect/effects.cljs b/src/status_im/contexts/wallet/wallet_connect/effects.cljs index cb9364e7be..40c12f6ef4 100644 --- a/src/status_im/contexts/wallet/wallet_connect/effects.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/effects.cljs @@ -1,17 +1,14 @@ (ns status-im.contexts.wallet.wallet-connect.effects (:require - [cljs-bean.core :as bean] - [native-module.core :as native-module] [promesa.core :as promesa] [re-frame.core :as rf] [react-native.wallet-connect :as wallet-connect] [status-im.config :as config] [status-im.constants :as constants] - [status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core] + [status-im.contexts.wallet.wallet-connect.signing :as signing] [status-im.contexts.wallet.wallet-connect.transactions :as transactions] [utils.i18n :as i18n] - [utils.security.core :as security] - [utils.transforms :as transforms])) + [utils.security.core :as security])) (rf/reg-fx :effects.wallet-connect/init @@ -79,36 +76,48 @@ (rf/reg-fx :effects.wallet-connect/sign-message - (fn [{:keys [password address data on-success on-error]}] - (-> {:data data - :account address - :password (security/safe-unmask-data password)} - bean/->js - transforms/clj->json - native-module/sign-message - (promesa/then wallet-connect-core/extract-native-call-signature) - (promesa/then on-success) - (promesa/catch on-error)))) + (fn [{:keys [password address data rpc-method on-success on-error]}] + (let [password (security/safe-unmask-data password)] + (-> (condp = + rpc-method + :personal-sign + (signing/personal-sign password address data) + + :eth-sign + (signing/eth-sign password address data) + + (signing/personal-sign password address data)) + (promesa/then on-success) + (promesa/catch on-error))))) (rf/reg-fx :effects.wallet-connect/sign-transaction (fn [{:keys [password address chain-id tx on-success on-error]}] - (-> (transactions/sign-transaction (security/safe-unmask-data password) address tx chain-id) + (-> (transactions/sign-transaction (security/safe-unmask-data password) + address + tx + chain-id) (promesa/then on-success) (promesa/catch on-error)))) (rf/reg-fx :effects.wallet-connect/send-transaction (fn [{:keys [password address chain-id tx on-success on-error]}] - (-> (transactions/send-transaction (security/safe-unmask-data password) address tx chain-id) + (-> (transactions/send-transaction (security/safe-unmask-data password) + address + tx + chain-id) (promesa/then on-success) (promesa/catch on-error)))) (rf/reg-fx :effects.wallet-connect/sign-typed-data - (fn [{:keys [password address data version on-success on-error]}] - (-> (wallet-connect-core/sign-typed-data version data address (security/safe-unmask-data password)) - (promesa/then wallet-connect-core/extract-native-call-signature) + (fn [{:keys [password address data version chain-id on-success on-error]}] + (-> (signing/eth-sign-typed-data (security/safe-unmask-data password) + address + data + chain-id + version) (promesa/then on-success) (promesa/catch on-error)))) diff --git a/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs b/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs index 6f0a5fed0f..838788576f 100644 --- a/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs @@ -12,14 +12,14 @@ method (wallet-connect-core/get-request-method event)] {:fx [(condp = method constants/wallet-connect-personal-sign-method - [:dispatch [:wallet-connect/respond-personal-sign password]] + [:dispatch [:wallet-connect/respond-sign-message password :personal-sign]] + + constants/wallet-connect-eth-sign-method + [:dispatch [:wallet-connect/respond-sign-message password :eth-sign]] constants/wallet-connect-eth-send-transaction-method [:dispatch [:wallet-connect/respond-send-transaction-data password]] - constants/wallet-connect-eth-sign-method - [:dispatch [:wallet-connect/respond-eth-sign password]] - constants/wallet-connect-eth-sign-transaction-method [:dispatch [:wallet-connect/respond-sign-transaction-data password]] @@ -30,35 +30,27 @@ [:dispatch [:wallet-connect/respond-sign-typed-data password :v4]])]}))) (rf/reg-event-fx - :wallet-connect/respond-eth-sign - (fn [{:keys [db]} [password]] - (let [{:keys [address raw-data]} (get db :wallet-connect/current-request)] - {:fx [[:effects.wallet-connect/sign-message - {:password password - :address address - :data raw-data - :on-error #(rf/dispatch [:wallet-connect/on-sign-error %]) - :on-success #(rf/dispatch [:wallet-connect/send-response {:result %}])}]]}))) - -(rf/reg-event-fx - :wallet-connect/respond-personal-sign - (fn [{:keys [db]} [password]] + :wallet-connect/respond-sign-message + (fn [{:keys [db]} [password rpc-method]] (let [{:keys [address raw-data]} (get db :wallet-connect/current-request)] {:fx [[:effects.wallet-connect/sign-message {:password password :address address :data raw-data + :rpc-method rpc-method :on-error #(rf/dispatch [:wallet-connect/on-sign-error %]) :on-success #(rf/dispatch [:wallet-connect/send-response {:result %}])}]]}))) (rf/reg-event-fx :wallet-connect/respond-sign-typed-data (fn [{:keys [db]} [password typed-data-version]] - (let [{:keys [address raw-data]} (get db :wallet-connect/current-request)] + (let [{:keys [address raw-data event]} (get db :wallet-connect/current-request) + chain-id (get-in event [:params :chainId])] {:fx [[:effects.wallet-connect/sign-typed-data {:password password :address address :data raw-data + :chain-id chain-id :version typed-data-version :on-error #(rf/dispatch [:wallet-connect/on-sign-error %]) :on-success #(rf/dispatch [:wallet-connect/send-response {:result %}])}]]}))) diff --git a/src/status_im/contexts/wallet/wallet_connect/rpc.cljs b/src/status_im/contexts/wallet/wallet_connect/rpc.cljs new file mode 100644 index 0000000000..3b1ce23afe --- /dev/null +++ b/src/status_im/contexts/wallet/wallet_connect/rpc.cljs @@ -0,0 +1,61 @@ +(ns status-im.contexts.wallet.wallet-connect.rpc + (:require [oops.core :as oops] + [promesa.core :as promesa] + [status-im.common.json-rpc.events :as rpc-events] + [status-im.constants :as constants] + [utils.hex :as hex] + [utils.transforms :as transforms])) + +(defn- call-rpc + "Helper to handle RPC calls to status-go as promises" + [method & args] + (promesa/create + (fn [p-resolve p-reject] + (rpc-events/call {:method method + :params args + :on-success p-resolve + :on-error p-reject + :js-response true})))) + +(defn wallet-build-transaction + [chain-id tx] + (promesa/let [res (call-rpc :wallet_buildTransaction chain-id tx)] + {:message-to-sign (oops/oget res :messageToSign) + :tx-args (oops/oget res :txArgs)})) + +(defn wallet-build-raw-transaction + [chain-id tx-args signature] + (-> (call-rpc "wallet_buildRawTransaction" + chain-id + (transforms/js-stringify tx-args 0) + signature) + (promesa/then #(oops/oget % "rawTx")))) + +(defn wallet-send-transaction-with-signature + [chain-id tx-args signature] + (call-rpc "wallet_sendTransactionWithSignature" + chain-id + constants/transaction-pending-type-wallet-connect-transfer + (transforms/js-stringify tx-args 0) + signature)) + +(defn wallet-sign-message + [message address password] + (-> (call-rpc "wallet_signMessage" + message + address + password) + (promesa/then hex/normalize-hex))) + +(defn wallet-hash-message-eip-191 + [message] + (call-rpc "wallet_hashMessageEIP191" message)) + +(defn wallet-safe-sign-typed-data + [data address password chain-id legacy?] + (call-rpc "wallet_safeSignTypedDataForDApps" + data + address + password + chain-id + legacy?)) diff --git a/src/status_im/contexts/wallet/wallet_connect/signing.cljs b/src/status_im/contexts/wallet/wallet_connect/signing.cljs new file mode 100644 index 0000000000..29ecec1021 --- /dev/null +++ b/src/status_im/contexts/wallet/wallet_connect/signing.cljs @@ -0,0 +1,32 @@ +(ns status-im.contexts.wallet.wallet-connect.signing + (:require [native-module.core :as native-module] + [promesa.core :as promesa] + [status-im.contexts.wallet.wallet-connect.core :as core] + [status-im.contexts.wallet.wallet-connect.rpc :as rpc] + [utils.hex :as hex] + [utils.transforms :as transforms])) + +(defn eth-sign + [password address data] + (-> {:data data + :account address + :password password} + transforms/clj->json + native-module/sign-message + (promesa/then core/extract-native-call-signature))) + +(defn personal-sign + [password address data] + (-> (rpc/wallet-hash-message-eip-191 data) + (promesa/then #(rpc/wallet-sign-message % address password)) + (promesa/then hex/prefix-hex))) + +(defn eth-sign-typed-data + [password address data chain-id-eip155 version] + (let [legacy? (= version :v1) + chain-id (core/eip155->chain-id chain-id-eip155)] + (rpc/wallet-safe-sign-typed-data data + address + password + chain-id + legacy?))) diff --git a/src/status_im/contexts/wallet/wallet_connect/transactions.cljs b/src/status_im/contexts/wallet/wallet_connect/transactions.cljs index 5e659c25b5..e23f196ecd 100644 --- a/src/status_im/contexts/wallet/wallet_connect/transactions.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/transactions.cljs @@ -1,23 +1,10 @@ (ns status-im.contexts.wallet.wallet-connect.transactions (:require [cljs-bean.core :as bean] [clojure.string :as string] - [oops.core :as oops] [promesa.core :as promesa] - [status-im.common.json-rpc.events :as rpc-events] - [status-im.constants :as constants] + [status-im.contexts.wallet.wallet-connect.rpc :as rpc] [utils.transforms :as transforms])) -(defn- call-rpc - "Helper to handle RPC calls to status-go as promises" - [method & args] - (promesa/create - (fn [p-resolve p-reject] - (rpc-events/call {:method method - :params (vec args) - :on-success p-resolve - :on-error p-reject - :js-response true})))) - (defn- strip-hex-prefix "Strips the extra 0 in hex value if present" [hex-value] @@ -46,53 +33,22 @@ bean/->js (transforms/js-stringify 0))) -(defn wallet-sign-message-rpc - [password address data] - (-> (call-rpc "wallet_signMessage" - data - address - password) - ;; NOTE: removing `0x`, as status-go expects the signature without it. - (promesa/then #(subs % 2)))) - -(defn- wallet-build-transaction-rpc - [chain-id tx] - (-> (call-rpc "wallet_buildTransaction" chain-id tx) - (promesa/then #(hash-map :message-to-sign (oops/oget % "messageToSign") - :tx-args (oops/oget % "txArgs"))))) - -(defn- wallet-build-raw-transaction-rpc - [chain-id tx-args signature] - (-> (call-rpc "wallet_buildRawTransaction" - chain-id - (transforms/js-stringify tx-args 0) - signature) - (promesa/then #(oops/oget % "rawTx")))) - -(defn- wallet-send-transaction-with-signature-rpc - [chain-id tx-args signature] - (call-rpc "wallet_sendTransactionWithSignature" - chain-id - constants/transaction-pending-type-wallet-connect-transfer - (transforms/js-stringify tx-args 0) - signature)) - (defn sign-transaction [password address tx chain-id] (promesa/let [formatted-tx (prepare-transaction-for-rpc tx) - {:keys [message-to-sign tx-args]} (wallet-build-transaction-rpc chain-id formatted-tx) - signature (wallet-sign-message-rpc password address message-to-sign) - raw-tx (wallet-build-raw-transaction-rpc chain-id tx-args signature)] + {:keys [message-to-sign tx-args]} (rpc/wallet-build-transaction chain-id formatted-tx) + signature (rpc/wallet-sign-message message-to-sign address password) + raw-tx (rpc/wallet-build-raw-transaction chain-id tx-args signature)] raw-tx)) (defn send-transaction [password address tx chain-id] (promesa/let [formatted-tx (prepare-transaction-for-rpc tx) - {:keys [message-to-sign tx-args]} (wallet-build-transaction-rpc chain-id formatted-tx) - signature (wallet-sign-message-rpc password address message-to-sign) - tx (wallet-send-transaction-with-signature-rpc chain-id + {:keys [message-to-sign tx-args]} (rpc/wallet-build-transaction chain-id formatted-tx) + signature (rpc/wallet-sign-message message-to-sign address password) + tx (rpc/wallet-send-transaction-with-signature chain-id tx-args signature)] tx)) diff --git a/src/status_im/subs/wallet/activities.cljs b/src/status_im/subs/wallet/activities.cljs index d636ceb88a..418680b12d 100644 --- a/src/status_im/subs/wallet/activities.cljs +++ b/src/status_im/subs/wallet/activities.cljs @@ -1,12 +1,12 @@ (ns status-im.subs.wallet.activities (:require - [legacy.status-im.utils.hex :as utils.hex] [native-module.core :as native-module] [quo.foundations.resources :as quo.resources] [quo.foundations.resources] [re-frame.core :as rf] [status-im.contexts.wallet.common.activity-tab.constants :as constants] [utils.datetime :as datetime] + [utils.hex :as utils.hex] [utils.money :as money])) (def precision 6) diff --git a/src/status_im/subs/wallet/wallet_connect.cljs b/src/status_im/subs/wallet/wallet_connect.cljs index 6530dca032..763bd8b8c1 100644 --- a/src/status_im/subs/wallet/wallet_connect.cljs +++ b/src/status_im/subs/wallet/wallet_connect.cljs @@ -50,7 +50,7 @@ :<- [:wallet-connect/current-request] (fn [request] (-> request - (get-in [:raw-data :params :chainId]) + (get-in [:event :params :chainId]) (wallet-connect-core/eip155->chain-id) (networks/get-network-details)))) diff --git a/src/utils/hex.cljs b/src/utils/hex.cljs new file mode 100644 index 0000000000..6892f35d28 --- /dev/null +++ b/src/utils/hex.cljs @@ -0,0 +1,27 @@ +(ns utils.hex + (:require + [clojure.string :as string] + [schema.core :as schema])) + +(defn normalize-hex + [hex] + (when hex + (string/lower-case (if (string/starts-with? hex "0x") + (subs hex 2) + hex)))) + +(schema/=> normalize-hex + [:=> + [:cat [:maybe :string]] + [:maybe :string]]) + +(defn prefix-hex + [hex] + (if (string/starts-with? hex "0x") + hex + (str "0x" hex))) + +(schema/=> prefix-hex + [:=> + [:cat :string] + :string]) From 4898d3224334ee8d2b3b24ab8180d79fce57f1ee Mon Sep 17 00:00:00 2001 From: Mohsen Date: Tue, 9 Jul 2024 17:48:35 +0330 Subject: [PATCH 46/79] [#20664] feat: add share usage data confirmation modal --- .../metrics_confirmation_modal/style.cljs | 14 ++++ .../metrics_confirmation_modal/view.cljs | 69 +++++++++++++++++++ .../contexts/onboarding/intro/view.cljs | 6 ++ translations/en.json | 15 +++- 4 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 src/status_im/common/metrics_confirmation_modal/style.cljs create mode 100644 src/status_im/common/metrics_confirmation_modal/view.cljs diff --git a/src/status_im/common/metrics_confirmation_modal/style.cljs b/src/status_im/common/metrics_confirmation_modal/style.cljs new file mode 100644 index 0000000000..e14d0a4159 --- /dev/null +++ b/src/status_im/common/metrics_confirmation_modal/style.cljs @@ -0,0 +1,14 @@ +(ns status-im.common.metrics-confirmation-modal.style) + +(def points-wrapper + {:margin-top 11 + :margin-horizontal 20 + :gap 21 + :margin-bottom 15}) + +(def item-text + {:margin-top 10 + :margin-left -4}) + +(def info-text + {:margin-top -5}) diff --git a/src/status_im/common/metrics_confirmation_modal/view.cljs b/src/status_im/common/metrics_confirmation_modal/view.cljs new file mode 100644 index 0000000000..fedaf4bec0 --- /dev/null +++ b/src/status_im/common/metrics_confirmation_modal/view.cljs @@ -0,0 +1,69 @@ +(ns status-im.common.metrics-confirmation-modal.view + (:require + [quo.core :as quo] + [react-native.core :as rn] + [status-im.common.metrics-confirmation-modal.style :as style] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn- hide-bottom-sheet + [] + (rf/dispatch [:hide-bottom-sheet])) + +(def will-receive-points + [(i18n/label :t/ip-address) + (i18n/label :t/universally-unique-identifiers-of-device) + (i18n/label :t/logs-of-actions-withing-the-app)]) + +(def not-receive-points + [(i18n/label :t/your-profile-information) + (i18n/label :t/your-addresses) + (i18n/label :t/information-you-input-and-send)]) + +(defn- render-points + [{:keys [title points]}] + [rn/view + [quo/text {:weight :semi-bold} + title] + (map-indexed + (fn [idx label] + ^{:key (str idx label)} + [quo/markdown-list + {:description label + :blur? true + :container-style style/item-text}]) + points)]) + +(defn view + [{:keys [settings?]}] + (let [on-cancel hide-bottom-sheet + on-share-usage (rn/use-callback + (fn [] + (hide-bottom-sheet))) + on-do-not-share (rn/use-callback + (fn [] + (hide-bottom-sheet)))] + [rn/view + [quo/drawer-top + {:title (i18n/label :t/help-us-improve-status) + :description (i18n/label :t/collecting-usage-data)}] + [rn/view {:style style/points-wrapper} + [render-points + {:title (i18n/label :t/what-we-will-receive) + :points will-receive-points}] + [render-points + {:title (i18n/label :t/what-we-not-receive) + :points not-receive-points}] + (when-not settings? + [quo/text + {:size :paragraph-2 + :style style/info-text} + (i18n/label :t/sharing-usage-data-can-be-turned-off)])] + [quo/bottom-actions + {:actions :two-actions + :blur? true + :button-one-label (i18n/label :t/share-usage-data) + :button-one-props {:on-press on-share-usage} + :button-two-label (i18n/label (if settings? :t/do-not-share :t/not-now)) + :button-two-props {:type :grey + :on-press (if settings? on-do-not-share on-cancel)}}]])) diff --git a/src/status_im/contexts/onboarding/intro/view.cljs b/src/status_im/contexts/onboarding/intro/view.cljs index 79107d8468..37acf33f85 100644 --- a/src/status_im/contexts/onboarding/intro/view.cljs +++ b/src/status_im/contexts/onboarding/intro/view.cljs @@ -2,6 +2,7 @@ (:require [quo.core :as quo] [react-native.core :as rn] + [status-im.common.metrics-confirmation-modal.view :as metrics-modal] [status-im.contexts.onboarding.common.background.view :as background] [status-im.contexts.onboarding.common.overlay.view :as overlay] [status-im.contexts.onboarding.intro.style :as style] @@ -11,6 +12,11 @@ (defn view [] + (rn/use-mount (fn [] + (rf/dispatch + [:show-bottom-sheet + {:content (fn [] [metrics-modal/view]) + :shell? true}]))) [rn/view {:style style/page-container} [background/view false] [quo/bottom-actions diff --git a/translations/en.json b/translations/en.json index 269d8ffdf3..5b90379d2a 100644 --- a/translations/en.json +++ b/translations/en.json @@ -2758,5 +2758,18 @@ "swaps-powered-by": "Swaps powered by {{provider}}", "max-slippage": "Max slippage", "pay": "Pay", - "store-confirmations": "Store confirmations" + "store-confirmations": "Store confirmations", + "help-us-improve-status": "Help us improve Status", + "collecting-usage-data": "Collecting usage data helps us improve Status.", + "what-we-will-receive":"What we will receive:", + "ip-address":"IP address", + "universally-unique-identifiers-of-device":"Universally Unique Identifiers of device", + "logs-of-actions-withing-the-app":"Logs of actions within the app, including button presses and screen visits", + "what-we-not-receive":"What we not receive:", + "your-profile-information":"Your profile information", + "your-addresses":"Your addresses", + "information-you-input-and-send":"Information you input and send", + "sharing-usage-data-can-be-turned-off":"Sharing usage data can be turned off anytime in Settings / Privacy and Security.", + "share-usage-data":"Share usage data", + "do-not-share":"Do not share" } From 93b5f7a9188dd29aa88c083c27a425cc5c8528e6 Mon Sep 17 00:00:00 2001 From: Andrea Maria Piana Date: Wed, 10 Jul 2024 09:45:08 +0100 Subject: [PATCH 47/79] Add confirmation to centralized metrics This commit add a confirmation for centralized metrics. It is added in 3 places: 1) Onboarding -> Create new account 2) Onboarding -> Sync profile 3) On the accounts view if the user is upgrading To test 1 & 2, you should just be able to do that on a newly installed device. To test 3, you will have to upgrade from a PR without this feature that has at least an account. It should show the confirmation modal until you either click on Not now or Share usage data. The modal should also be added in settings, but I will do that as a separate PR. --- .../metrics_confirmation_modal/view.cljs | 106 ++++++++++-------- .../contexts/centralized_metrics/events.cljs | 26 ++++- .../centralized_metrics/events_test.cljs | 5 + .../create_or_sync_profile/view.cljs | 2 + .../contexts/onboarding/intro/view.cljs | 6 - .../contexts/profile/profiles/view.cljs | 2 + translations/en.json | 2 +- 7 files changed, 90 insertions(+), 59 deletions(-) diff --git a/src/status_im/common/metrics_confirmation_modal/view.cljs b/src/status_im/common/metrics_confirmation_modal/view.cljs index fedaf4bec0..b02c92c3ee 100644 --- a/src/status_im/common/metrics_confirmation_modal/view.cljs +++ b/src/status_im/common/metrics_confirmation_modal/view.cljs @@ -6,64 +6,74 @@ [utils.i18n :as i18n] [utils.re-frame :as rf])) +(defn- dismiss-keyboard + [] + (rf/dispatch [:dismiss-keyboard])) + (defn- hide-bottom-sheet [] (rf/dispatch [:hide-bottom-sheet])) -(def will-receive-points - [(i18n/label :t/ip-address) - (i18n/label :t/universally-unique-identifiers-of-device) - (i18n/label :t/logs-of-actions-withing-the-app)]) +(defn- toggle-metrics + [enabled?] + (rf/dispatch [:centralized-metrics/toggle-centralized-metrics enabled?])) -(def not-receive-points - [(i18n/label :t/your-profile-information) - (i18n/label :t/your-addresses) - (i18n/label :t/information-you-input-and-send)]) +(def ^:private will-receive-points + [:t/ip-address + :t/universally-unique-identifiers-of-device + :t/logs-of-actions-withing-the-app]) -(defn- render-points +(def ^:private not-receive-points + [:t/your-profile-information + :t/your-addresses + :t/information-you-input-and-send]) + +(defn- bullet-points [{:keys [title points]}] [rn/view [quo/text {:weight :semi-bold} title] - (map-indexed - (fn [idx label] - ^{:key (str idx label)} - [quo/markdown-list - {:description label - :blur? true - :container-style style/item-text}]) - points)]) + (for [label points] + ^{:key label} + [quo/markdown-list + {:description (i18n/label label) + :blur? true + :container-style style/item-text}])]) + +(defn- on-share-usage + [] + (toggle-metrics true) + (hide-bottom-sheet)) + +(defn- on-do-not-share + [] + (toggle-metrics false) + (hide-bottom-sheet)) (defn view [{:keys [settings?]}] - (let [on-cancel hide-bottom-sheet - on-share-usage (rn/use-callback - (fn [] - (hide-bottom-sheet))) - on-do-not-share (rn/use-callback - (fn [] - (hide-bottom-sheet)))] - [rn/view - [quo/drawer-top - {:title (i18n/label :t/help-us-improve-status) - :description (i18n/label :t/collecting-usage-data)}] - [rn/view {:style style/points-wrapper} - [render-points - {:title (i18n/label :t/what-we-will-receive) - :points will-receive-points}] - [render-points - {:title (i18n/label :t/what-we-not-receive) - :points not-receive-points}] - (when-not settings? - [quo/text - {:size :paragraph-2 - :style style/info-text} - (i18n/label :t/sharing-usage-data-can-be-turned-off)])] - [quo/bottom-actions - {:actions :two-actions - :blur? true - :button-one-label (i18n/label :t/share-usage-data) - :button-one-props {:on-press on-share-usage} - :button-two-label (i18n/label (if settings? :t/do-not-share :t/not-now)) - :button-two-props {:type :grey - :on-press (if settings? on-do-not-share on-cancel)}}]])) + (rn/use-mount #(dismiss-keyboard)) + [:<> + [quo/drawer-top + {:title (i18n/label :t/help-us-improve-status) + :description (i18n/label :t/collecting-usage-data)}] + [rn/view {:style style/points-wrapper} + [bullet-points + {:title (i18n/label :t/what-we-will-receive) + :points will-receive-points}] + [bullet-points + {:title (i18n/label :t/what-we-wont-receive) + :points not-receive-points}] + (when-not settings? + [quo/text + {:size :paragraph-2 + :style style/info-text} + (i18n/label :t/sharing-usage-data-can-be-turned-off)])] + [quo/bottom-actions + {:actions :two-actions + :blur? true + :button-one-label (i18n/label :t/share-usage-data) + :button-one-props {:on-press on-share-usage} + :button-two-label (i18n/label (if settings? :t/do-not-share :t/not-now)) + :button-two-props {:type :grey + :on-press on-do-not-share}}]]) diff --git a/src/status_im/contexts/centralized_metrics/events.cljs b/src/status_im/contexts/centralized_metrics/events.cljs index a9e303802d..078e5ab3a4 100644 --- a/src/status_im/contexts/centralized_metrics/events.cljs +++ b/src/status_im/contexts/centralized_metrics/events.cljs @@ -7,10 +7,17 @@ [taoensso.timbre :as log] [utils.re-frame :as rf])) +(def ^:const user-confirmed-key :centralized-metrics/user-confirmed?) +(def ^:const enabled-key :centralized-metrics/enabled?) + +(defn show-confirmation-modal? + [db] + (not (user-confirmed-key db))) + (defn push-event? [db] - (or (not (:centralized-metrics/user-confirmed? db)) - (:centralized-metrics/enabled? db))) + (or (not (user-confirmed-key db)) + (enabled-key db))) (defn centralized-metrics-interceptor [context] @@ -29,5 +36,16 @@ (fn [{:keys [db]} [enabled?]] {:fx [[:effects.centralized-metrics/toggle-metrics enabled?]] :db (assoc db - :centralized-metrics/user-confirmed? true - :centralized-metrics/enabled? enabled?)})) + user-confirmed-key + true + enabled-key + enabled?)})) + +(rf/reg-event-fx :centralized-metrics/check-modal + (fn [{:keys [db]} [modal-view]] + (when (show-confirmation-modal? db) + {:fx [[:dispatch + [:show-bottom-sheet + {:content (fn [] [modal-view]) + :shell? true}]]]}))) + diff --git a/src/status_im/contexts/centralized_metrics/events_test.cljs b/src/status_im/contexts/centralized_metrics/events_test.cljs index 816609694f..75a9659f3a 100644 --- a/src/status_im/contexts/centralized_metrics/events_test.cljs +++ b/src/status_im/contexts/centralized_metrics/events_test.cljs @@ -6,6 +6,11 @@ [status-im.contexts.centralized-metrics.tracking :as tracking] [test-helpers.unit :as h])) +(deftest show-confirmation-modal-test + (testing "returns true if the user confirmed" + (is (false? (events/show-confirmation-modal? {events/user-confirmed-key true}))) + (is (true? (events/show-confirmation-modal? {}))))) + (deftest push-event-test (testing "returns correct boolean value" (is (true? (events/push-event? {:centralized-metrics/user-confirmed? false}))) diff --git a/src/status_im/contexts/onboarding/create_or_sync_profile/view.cljs b/src/status_im/contexts/onboarding/create_or_sync_profile/view.cljs index c30dd0719a..987901961a 100644 --- a/src/status_im/contexts/onboarding/create_or_sync_profile/view.cljs +++ b/src/status_im/contexts/onboarding/create_or_sync_profile/view.cljs @@ -5,6 +5,7 @@ [react-native.core :as rn] [react-native.safe-area :as safe-area] [status-im.common.check-before-syncing.view :as check-before-syncing] + [status-im.common.metrics-confirmation-modal.view :as metrics-modal] [status-im.common.not-implemented :as not-implemented] [status-im.common.resources :as resources] [status-im.config :as config] @@ -126,6 +127,7 @@ (defn- internal-view [sign-in-type] + (rn/use-mount #(rf/dispatch [:centralized-metrics/check-modal metrics-modal/view])) (let [{:keys [top]} (safe-area/get-insets)] [rn/view {:style style/content-container} [quo/page-nav diff --git a/src/status_im/contexts/onboarding/intro/view.cljs b/src/status_im/contexts/onboarding/intro/view.cljs index 37acf33f85..79107d8468 100644 --- a/src/status_im/contexts/onboarding/intro/view.cljs +++ b/src/status_im/contexts/onboarding/intro/view.cljs @@ -2,7 +2,6 @@ (:require [quo.core :as quo] [react-native.core :as rn] - [status-im.common.metrics-confirmation-modal.view :as metrics-modal] [status-im.contexts.onboarding.common.background.view :as background] [status-im.contexts.onboarding.common.overlay.view :as overlay] [status-im.contexts.onboarding.intro.style :as style] @@ -12,11 +11,6 @@ (defn view [] - (rn/use-mount (fn [] - (rf/dispatch - [:show-bottom-sheet - {:content (fn [] [metrics-modal/view]) - :shell? true}]))) [rn/view {:style style/page-container} [background/view false] [quo/bottom-actions diff --git a/src/status_im/contexts/profile/profiles/view.cljs b/src/status_im/contexts/profile/profiles/view.cljs index ff4a9d2546..a844af0b52 100644 --- a/src/status_im/contexts/profile/profiles/view.cljs +++ b/src/status_im/contexts/profile/profiles/view.cljs @@ -7,6 +7,7 @@ [react-native.safe-area :as safe-area] [status-im.common.check-before-syncing.view :as check-before-syncing] [status-im.common.confirmation-drawer.view :as confirmation-drawer] + [status-im.common.metrics-confirmation-modal.view :as metrics-modal] [status-im.common.standard-authentication.core :as standard-authentication] [status-im.config :as config] [status-im.constants :as constants] @@ -254,6 +255,7 @@ (defn view [] + (rn/use-mount #(rf/dispatch [:centralized-metrics/check-modal metrics-modal/view])) (let [[show-profiles? set-show-profiles] (rn/use-state false) show-profiles (rn/use-callback #(set-show-profiles true)) hide-profiles (rn/use-callback #(set-show-profiles false))] diff --git a/translations/en.json b/translations/en.json index 5b90379d2a..de6b4c281e 100644 --- a/translations/en.json +++ b/translations/en.json @@ -2765,7 +2765,7 @@ "ip-address":"IP address", "universally-unique-identifiers-of-device":"Universally Unique Identifiers of device", "logs-of-actions-withing-the-app":"Logs of actions within the app, including button presses and screen visits", - "what-we-not-receive":"What we not receive:", + "what-we-wont-receive":"What we won't receive:", "your-profile-information":"Your profile information", "your-addresses":"Your addresses", "information-you-input-and-send":"Information you input and send", From 13232dc7b712cf7e9bc6562a132c6a570634700c Mon Sep 17 00:00:00 2001 From: Yevheniia Berdnyk Date: Fri, 12 Jul 2024 01:39:48 +0300 Subject: [PATCH 48/79] e2e: updated create profile flow --- test/appium/views/sign_in_view.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/appium/views/sign_in_view.py b/test/appium/views/sign_in_view.py index 6de36d7027..a6fb04ef31 100644 --- a/test/appium/views/sign_in_view.py +++ b/test/appium/views/sign_in_view.py @@ -150,6 +150,7 @@ class SignInView(BaseView): self.i_m_new_in_status_button = Button(self.driver, accessibility_id="new-to-status-button") self.create_profile_button = Button(self.driver, accessibility_id='new-to-status-button') + self.not_now_button = Button(self.driver, xpath="//*[@text='Not now']") self.sync_or_recover_profile_button = Button(self.driver, accessibility_id='already-use-status-button') self.migration_password_input = EditBox(self.driver, accessibility_id="enter-password-input") @@ -236,6 +237,7 @@ class SignInView(BaseView): (password, str(keycard), str(enable_notifications)), device=False) if first_user: self.create_profile_button.click_until_presence_of_element(self.generate_keys_button) + self.not_now_button.wait_and_click() else: if self.show_profiles_button.is_element_displayed(20): self.show_profiles_button.click() @@ -274,6 +276,7 @@ class SignInView(BaseView): if not second_user: self.sync_or_recover_profile_button.click_until_presence_of_element(self.generate_keys_button) + self.not_now_button.wait_and_click() else: self.show_profiles_button.wait_and_click(20) self.plus_profiles_button.click() From 43651ef0d0020eeea829bad437a6769427225cfe Mon Sep 17 00:00:00 2001 From: Parvesh Monu Date: Tue, 16 Jul 2024 20:43:19 +0530 Subject: [PATCH 49/79] Adjust on-boarding illustrations position and scale (#20692) --- .../drawers/bottom_actions/style.cljs | 3 -- .../drawers/bottom_actions/view.cljs | 2 - .../onboarding/common/background/style.cljs | 9 +---- .../onboarding/common/background/view.cljs | 11 +++--- .../onboarding/common/carousel/style.cljs | 17 ++++----- .../onboarding/common/carousel/view.cljs | 15 ++++---- .../onboarding/enable_biometrics/style.cljs | 16 ++++---- .../onboarding/generating_keys/style.cljs | 11 ++++-- .../onboarding/generating_keys/view.cljs | 37 +++++++++---------- .../contexts/onboarding/intro/style.cljs | 16 ++++---- .../contexts/onboarding/intro/view.cljs | 3 +- 11 files changed, 64 insertions(+), 76 deletions(-) diff --git a/src/quo/components/drawers/bottom_actions/style.cljs b/src/quo/components/drawers/bottom_actions/style.cljs index 9eacbacf7b..b462a152c3 100644 --- a/src/quo/components/drawers/bottom_actions/style.cljs +++ b/src/quo/components/drawers/bottom_actions/style.cljs @@ -28,9 +28,6 @@ :gap 12 :padding-horizontal 20}) -(def button-container - {:flex 1}) - (def description-top {:flex-direction :row :align-items :center diff --git a/src/quo/components/drawers/bottom_actions/view.cljs b/src/quo/components/drawers/bottom_actions/view.cljs index cbcc3c34bf..68de6073af 100644 --- a/src/quo/components/drawers/bottom_actions/view.cljs +++ b/src/quo/components/drawers/bottom_actions/view.cljs @@ -79,7 +79,6 @@ (merge {:size 40 :background (when (or blur? scroll?) :blur) - :container-style style/button-container :theme theme :accessibility-label :button-two} button-two-props) @@ -87,7 +86,6 @@ [button/button (merge {:size 40 - :container-style style/button-container :background (when (or blur? scroll?) :blur) :theme theme :accessibility-label :button-one} diff --git a/src/status_im/contexts/onboarding/common/background/style.cljs b/src/status_im/contexts/onboarding/common/background/style.cljs index cbde25567f..024a56022c 100644 --- a/src/status_im/contexts/onboarding/common/background/style.cljs +++ b/src/status_im/contexts/onboarding/common/background/style.cljs @@ -5,13 +5,8 @@ (def background-container {:background-color colors/neutral-100 - :flex-direction :row - :position :absolute - :overflow :hidden - :top 0 - :bottom 0 - :left 0 - :right 0}) + :flex 1 + :overflow :hidden}) (def background-blur-overlay {:position :absolute diff --git a/src/status_im/contexts/onboarding/common/background/view.cljs b/src/status_im/contexts/onboarding/common/background/view.cljs index dd88ea842d..9436cf768c 100644 --- a/src/status_im/contexts/onboarding/common/background/view.cljs +++ b/src/status_im/contexts/onboarding/common/background/view.cljs @@ -25,13 +25,12 @@ :sub-text (i18n/label :t/explore-the-decentralized-web)}]) (defn background-image - [content-width] + [image-view-width] [rn/image - {:resize-mode :stretch - :resize-method :scale - :style {:top 138 - :width content-width} - :source (resources/get-image :onboarding-illustration)}]) + {:resize-mode :stretch + :style {:flex 1 + :width image-view-width} + :source (resources/get-image :onboarding-illustration)}]) (defonce progress (atom nil)) (defonce paused? (atom nil)) diff --git a/src/status_im/contexts/onboarding/common/carousel/style.cljs b/src/status_im/contexts/onboarding/common/carousel/style.cljs index 8c35b15bb7..e4f4439cb3 100644 --- a/src/status_im/contexts/onboarding/common/carousel/style.cljs +++ b/src/status_im/contexts/onboarding/common/carousel/style.cljs @@ -5,12 +5,9 @@ (defn header-container [status-bar-height content-width index header-background] - {:position :absolute - :top 0 - :left (* content-width index) + {:margin-left (* content-width index) :padding-top (+ 30 status-bar-height) :width content-width - :height (+ 96 status-bar-height) :flex-direction :row :background-color (when header-background colors/onboarding-header-black)}) @@ -62,9 +59,9 @@ :top (+ 12 status-bar-height)}) (defn carousel-container - [left animate?] - (cond->> {:position :absolute - :top 0 - :flex-direction :row - :left left} - animate? (reanimated/apply-animations-to-style {:left left}))) + [left animate? width] + (cond->> {:flex-direction :row + :flex 1 + :width width + :margin-left left} + animate? (reanimated/apply-animations-to-style {:margin-left left}))) diff --git a/src/status_im/contexts/onboarding/common/carousel/view.cljs b/src/status_im/contexts/onboarding/common/carousel/view.cljs index 8db48ed886..a47d2ace46 100644 --- a/src/status_im/contexts/onboarding/common/carousel/view.cljs +++ b/src/status_im/contexts/onboarding/common/carousel/view.cljs @@ -18,19 +18,18 @@ :size :heading-2} (get-in header-text [index :text])] [quo/text - {:style style/carousel-sub-text - :size :paragraph-1} + {:style style/carousel-sub-text} (get-in header-text [index :sub-text])]]) (defn content-view [{:keys [window-width status-bar-height index header-text header-background]} content] (let [content-width (* 4 window-width)] - [:<> - (when content content) + [rn/view {:style {:flex 1}} [rn/view {:style (style/header-container status-bar-height content-width index header-background)} (for [index (range 4)] ^{:key index} - [header-text-view index window-width header-text])]])) + [header-text-view index window-width header-text])] + (when content content)])) (defn progress-bar [{:keys [static? progress-bar-width]}] @@ -64,10 +63,10 @@ :swipeable (animation/composed-gestures progress paused? is-dragging? drag-amount) :tappable (animation/tap-gesture progress paused?) nil)] - [:<> + [rn/view {:style {:flex 1}} [gesture/gesture-detector {:gesture identified-gesture} - [container-view {:style (style/carousel-container carousel-left animate?)} - (for [index (range 2)] + [container-view {:style (style/carousel-container carousel-left animate? (* 4 window-width))} + (for [index (range 1)] ^{:key index} [content-view {:window-width window-width diff --git a/src/status_im/contexts/onboarding/enable_biometrics/style.cljs b/src/status_im/contexts/onboarding/enable_biometrics/style.cljs index ee48632000..ef8124affa 100644 --- a/src/status_im/contexts/onboarding/enable_biometrics/style.cljs +++ b/src/status_im/contexts/onboarding/enable_biometrics/style.cljs @@ -4,20 +4,20 @@ (def title-container {:margin-horizontal 20 - :padding-vertical 12}) + :padding-bottom 20 + :padding-top 12}) (defn page-container [insets] - {:flex 1 - :justify-content :space-between - :padding-top (+ (:top insets) 56)}) + {:flex 1 + :padding-top (+ (:top insets) 56)}) (defn page-illustration [width] - {:flex 1 - :padding-top 12 - :padding-bottom 10 - :width width}) + {:flex 1 + :margin-top 12 + :margin-bottom 10 + :width width}) (defn buttons [insets] diff --git a/src/status_im/contexts/onboarding/generating_keys/style.cljs b/src/status_im/contexts/onboarding/generating_keys/style.cljs index 9136d09e47..4fd248b28e 100644 --- a/src/status_im/contexts/onboarding/generating_keys/style.cljs +++ b/src/status_im/contexts/onboarding/generating_keys/style.cljs @@ -2,11 +2,16 @@ (defn page-container [insets] - {:flex 1 - :justify-content :space-between - :padding-top (:top insets)}) + {:flex 1 + :padding-top (:top insets)}) (defn page-illustration [width] {:flex 1 :width width}) + +(def title-style + {:position :absolute + :left 0 + :right 0 + :top 0}) diff --git a/src/status_im/contexts/onboarding/generating_keys/view.cljs b/src/status_im/contexts/onboarding/generating_keys/view.cljs index 5a27098ac3..7877b96fa6 100644 --- a/src/status_im/contexts/onboarding/generating_keys/view.cljs +++ b/src/status_im/contexts/onboarding/generating_keys/view.cljs @@ -17,19 +17,22 @@ (defn generate-keys-title [] [quo/text-combinations - {:container-style {:margin-horizontal 20} + {:container-style {:margin-horizontal 20 + :margin-vertical 12} :title (i18n/label :t/generating-keys)}]) (defn saving-keys-title [] [quo/text-combinations - {:container-style {:margin-horizontal 20} + {:container-style {:margin-horizontal 20 + :margin-vertical 12} :title (i18n/label :t/saving-keys-to-device)}]) (defn keys-saved-title [] [quo/text-combinations - {:container-style {:margin-horizontal 20} + {:container-style {:margin-horizontal 20 + :margin-vertical 12} :title (i18n/label :t/keys-saved)}]) (defn sequence-animation @@ -61,46 +64,42 @@ reanimated/easings)))))) (defn title - [insets] - (let [top-insets (+ (if rn/small-screen? 62 112) (:insets insets)) - generate-keys-opacity (reanimated/use-shared-value 1) + [] + (let [generate-keys-opacity (reanimated/use-shared-value 1) saving-keys-opacity (reanimated/use-shared-value 0) keys-saved-opacity (reanimated/use-shared-value 0)] (sequence-animation generate-keys-opacity saving-keys-opacity keys-saved-opacity) [rn/view - {:position :absolute - :top top-insets} + {:style {:margin-top 56 + :height 56}} [reanimated/view {:style (reanimated/apply-animations-to-style {:opacity generate-keys-opacity} - {:position :absolute})} + style/title-style)} [generate-keys-title]] [reanimated/view {:style (reanimated/apply-animations-to-style {:opacity saving-keys-opacity} - {:position :absolute})} + style/title-style)} [saving-keys-title]] [reanimated/view {:style (reanimated/apply-animations-to-style {:opacity keys-saved-opacity} - {:position :absolute})} + style/title-style)} [keys-saved-title]]])) (defn content [] (let [width (:width (rn/get-window))] - [rn/view - {:top 156 - :position :absolute} - [rn/image - {:resize-mode :contain - :style (style/page-illustration width) - :source (resources/get-image :generate-keys1)}]])) + [rn/image + {:resize-mode :contain + :style (style/page-illustration width) + :source (resources/get-image :generate-keys1)}])) (defn view [] (let [insets (safe-area/get-insets)] [rn/view {:style (style/page-container insets)} [:<> - [title insets] + [title] [content]]])) diff --git a/src/status_im/contexts/onboarding/intro/style.cljs b/src/status_im/contexts/onboarding/intro/style.cljs index c5b813dcd6..ce13e2215e 100644 --- a/src/status_im/contexts/onboarding/intro/style.cljs +++ b/src/status_im/contexts/onboarding/intro/style.cljs @@ -7,9 +7,8 @@ :justify-content :flex-end}) (def text-container - {:flex 1 - :text-align :center - :margin-top 16 + {:text-align :center + :margin-top 4 :flex-wrap :wrap}) (def plain-text @@ -19,9 +18,8 @@ {:flex 1 :color colors/white}) -(def bottom-actions-container - {:position :absolute - :background-color colors/onboarding-header-black - :left 0 - :right 0 - :padding-bottom 40}) +(defn bottom-actions-container + [bottom-insets] + {:background-color colors/onboarding-header-black + :flex-shrink 1 + :padding-bottom (+ 20 bottom-insets)}) diff --git a/src/status_im/contexts/onboarding/intro/view.cljs b/src/status_im/contexts/onboarding/intro/view.cljs index 79107d8468..2497d48b24 100644 --- a/src/status_im/contexts/onboarding/intro/view.cljs +++ b/src/status_im/contexts/onboarding/intro/view.cljs @@ -2,6 +2,7 @@ (:require [quo.core :as quo] [react-native.core :as rn] + [react-native.safe-area :as safe-area] [status-im.contexts.onboarding.common.background.view :as background] [status-im.contexts.onboarding.common.overlay.view :as overlay] [status-im.contexts.onboarding.intro.style :as style] @@ -14,7 +15,7 @@ [rn/view {:style style/page-container} [background/view false] [quo/bottom-actions - {:container-style style/bottom-actions-container + {:container-style (style/bottom-actions-container (safe-area/get-bottom)) :actions :two-vertical-actions :description :bottom :description-text [quo/text From 2d0437dd5e261cdb91ac85f38f9051ce4f8b5dc3 Mon Sep 17 00:00:00 2001 From: Ulises Manuel <90291778+ulisesmac@users.noreply.github.com> Date: Tue, 16 Jul 2024 15:07:33 -0600 Subject: [PATCH 50/79] fix(wallet): Improvements on activity tab (#20703) * Fix schema for networks in context-tags * Fix wallet-activity component overflowing the activity tab * Improve robustness of the activity tab fetching mechanism * Handle `wallet-activity-filtering-entries-updated` signal * Improve processing of data received for the activity tab --- .../components/tags/context_tag/schema.cljs | 2 +- .../components/tags/context_tag/style.cljs | 9 +- src/quo/components/tags/context_tag/view.cljs | 21 ++-- .../wallet/wallet_activity/style.cljs | 12 +- .../wallet/wallet_activity/view.cljs | 9 +- .../wallet/common/activity_tab/events.cljs | 48 ++++++-- .../wallet/common/activity_tab/view.cljs | 90 +++++++++++---- src/status_im/contexts/wallet/signals.cljs | 53 ++++++--- src/status_im/subs/wallet/activities.cljs | 105 +++++++++++++----- .../subs/wallet/activities_test.cljs | 40 +++---- src/status_im/subs/wallet/send.cljs | 3 +- src/status_im/subs/wallet/send_test.cljs | 40 +++---- 12 files changed, 291 insertions(+), 141 deletions(-) diff --git a/src/quo/components/tags/context_tag/schema.cljs b/src/quo/components/tags/context_tag/schema.cljs index 4e7345737c..e4fd74ea0d 100644 --- a/src/quo/components/tags/context_tag/schema.cljs +++ b/src/quo/components/tags/context_tag/schema.cljs @@ -53,7 +53,7 @@ (def ^:private ?network [:map [:network-logo {:optional true} [:maybe :schema.common/image-source]] - [:network-name {:optional true} [:maybe :string]]]) + [:network-name {:optional true} [:maybe [:or :string :keyword]]]]) (def ^:private ?multinetwork [:map diff --git a/src/quo/components/tags/context_tag/style.cljs b/src/quo/components/tags/context_tag/style.cljs index 688cf59db1..4caea0f2fc 100644 --- a/src/quo/components/tags/context_tag/style.cljs +++ b/src/quo/components/tags/context_tag/style.cljs @@ -41,7 +41,8 @@ :align-items :center :height size :background-color background-color - :border-radius border-radius} + :border-radius border-radius + :flex-shrink 1} (= state :selected) (assoc :height (+ size 2) :border-color border-color :border-width 1)))) @@ -50,11 +51,13 @@ [size] {:margin-right (if (= size 24) 6 10) :flex-direction :row + :flex-shrink 1 :align-items :center}) (defn tag-spacing - [size] - {:margin-left (if (= size 24) 4 8)}) + [size shrinkable?] + (cond-> {:margin-left (if (= size 24) 4 8)} + shrinkable? (assoc :flex-shrink 1))) (defn text [theme] diff --git a/src/quo/components/tags/context_tag/view.cljs b/src/quo/components/tags/context_tag/view.cljs index 78ad97ae90..3248f1d94a 100644 --- a/src/quo/components/tags/context_tag/view.cljs +++ b/src/quo/components/tags/context_tag/view.cljs @@ -17,17 +17,19 @@ [schema.core :as schema])) (defn- tag-skeleton - [{:keys [size text theme] + [{:keys [size text theme shrinkable?] :or {size 24 theme (quo.theme/use-theme)}} logo-component] [rn/view {:style (style/tag-container size)} logo-component - [rn/view {:style (style/tag-spacing size)} + [rn/view {:style (style/tag-spacing size shrinkable?)} [text/text - {:style (style/text theme) - :weight :medium - :size (if (= size 24) :paragraph-2 :paragraph-1)} + {:style (style/text theme) + :weight :medium + :size (if (= size 24) :paragraph-2 :paragraph-1) + :number-of-lines 1 + :ellipsize-mode :middle} text]]]) (defn- communities-tag @@ -37,7 +39,7 @@ icon-size (if (= size 24) 16 20)] [rn/view {:style (style/tag-container size)} [fast-image/fast-image {:style (style/circle-logo size) :source community-logo}] - [rn/view {:style (style/tag-spacing size)} + [rn/view {:style (style/tag-spacing size false)} [text/text {:style (style/text theme) :weight :medium @@ -148,9 +150,10 @@ :collectible [tag-skeleton - {:theme theme - :size size - :text (str collectible-name " #" collectible-number)} + {:theme theme + :size size + :text (str collectible-name " #" collectible-number) + :shrinkable? true} [rn/image {:style (style/rounded-logo size) :source collectible}]] :account diff --git a/src/quo/components/wallet/wallet_activity/style.cljs b/src/quo/components/wallet/wallet_activity/style.cljs index 76d3c89e20..6d77607237 100644 --- a/src/quo/components/wallet/wallet_activity/style.cljs +++ b/src/quo/components/wallet/wallet_activity/style.cljs @@ -51,8 +51,7 @@ (defn prop-text [theme] - {:margin-right 4 - :color (colors/theme-colors colors/neutral-100 colors/white theme)}) + {:color (colors/theme-colors colors/neutral-100 colors/white theme)}) (def icon-container {:width 32 @@ -61,12 +60,15 @@ (def container {:flex-direction :row + :flex 1 :column-gap 8}) (def content-line - {:flex-direction :row - :margin-top 2 - :align-items :center}) + {:flex-direction :row + :margin-top 2 + :align-items :center + :column-gap 4 + :justify-content :flex-start}) (def icon-hole-view {:width 32 diff --git a/src/quo/components/wallet/wallet_activity/view.cljs b/src/quo/components/wallet/wallet_activity/view.cljs index 5f9bc8aaa3..d6d72f4913 100644 --- a/src/quo/components/wallet/wallet_activity/view.cljs +++ b/src/quo/components/wallet/wallet_activity/view.cljs @@ -98,8 +98,11 @@ (defn prop-tag [props blur?] - [rn/view {:style {:margin-right 4}} - [context-tag/view (merge props {:size 24 :blur? blur?})]]) + [context-tag/view + (assoc props + :size 24 + :blur? blur? + :container-style {:flex-shrink 1})]) (defn- view-internal [{:keys [state blur? first-tag second-tag third-tag fourth-tag on-press @@ -121,7 +124,7 @@ :on-press-out on-press-out} [rn/view {:style style/container} [transaction-icon-view props theme] - [rn/view + [rn/view {:style {:flex 1}} [transaction-header props theme] [rn/view {:style style/content-line} (when first-tag [prop-tag first-tag blur?]) diff --git a/src/status_im/contexts/wallet/common/activity_tab/events.cljs b/src/status_im/contexts/wallet/common/activity_tab/events.cljs index 0ee358e4b6..5d46201ab9 100644 --- a/src/status_im/contexts/wallet/common/activity_tab/events.cljs +++ b/src/status_im/contexts/wallet/common/activity_tab/events.cljs @@ -4,12 +4,19 @@ [utils.re-frame :as rf] [utils.transforms :as transforms])) +(defonce ^:private request-id-atom (atom 0)) + +(defn- get-unique-request-id + [] + (swap! request-id-atom inc) + @request-id-atom) + (rf/reg-event-fx :wallet/fetch-activities-for-current-account (fn [{:keys [db]}] (let [address (-> db :wallet :current-viewing-account-address) chain-ids (chain/chain-ids db) - request-id 0 + request-id (get-unique-request-id) filters {:period {:startTimestamp 0 :endTimestamp 0} :types [] @@ -20,9 +27,10 @@ :filterOutAssets false :filterOutCollectibles false} offset 0 - limit 35 + limit 50 request-params [request-id [address] chain-ids filters offset limit]] - {:fx [[:json-rpc/call + {:db (assoc-in db [:wallet :ui :activity-tab :request request-id] address) + :fx [[:json-rpc/call [{;; This method is deprecated and will be replaced by ;; "wallet_startActivityFilterSession" ;; https://github.com/status-im/status-mobile/issues/19864 @@ -32,13 +40,31 @@ {:event :wallet/fetch-activities-for-current-account :params request-params}]}]]]}))) +(def ^:private activity-transaction-id (comp hash :transaction)) + (rf/reg-event-fx :wallet/activity-filtering-for-current-account-done - (fn [{:keys [db]} [{:keys [message]}]] - (let [address (-> db :wallet :current-viewing-account-address) - activities (->> message - (transforms/json->clj) - (:activities) - (cske/transform-keys transforms/->kebab-case-keyword)) - sorted-activities (sort :timestamp activities)] - {:db (assoc-in db [:wallet :activities address] sorted-activities)}))) + (fn [{:keys [db]} [{:keys [message requestId]}]] + (let [address (get-in db [:wallet :ui :activity-tab :request requestId]) + activities (->> message + (transforms/json->clj) + (:activities) + (cske/transform-keys transforms/->kebab-case-keyword)) + activities-indexed (zipmap (map activity-transaction-id activities) + activities)] + {:db (assoc-in db [:wallet :activities address] activities-indexed)}))) + +(def ^:private nested-merge (partial merge-with merge)) + +(rf/reg-event-fx + :wallet/activities-filtering-entries-updated + (fn [{:keys [db]} [{:keys [message requestId]}]] + (let [address (get-in db [:wallet :ui :activity-tab :request requestId]) + activities (->> message + (transforms/json->clj) + (cske/transform-keys transforms/->kebab-case-keyword)) + activities-indexed (zipmap (map activity-transaction-id activities) + activities)] + {:db (-> db + (update-in [:wallet :ui :activity-tab :request] dissoc requestId) + (update-in [:wallet :activities address] nested-merge activities-indexed))}))) diff --git a/src/status_im/contexts/wallet/common/activity_tab/view.cljs b/src/status_im/contexts/wallet/common/activity_tab/view.cljs index b10f9d0b52..480b9d0b22 100644 --- a/src/status_im/contexts/wallet/common/activity_tab/view.cljs +++ b/src/status_im/contexts/wallet/common/activity_tab/view.cljs @@ -11,31 +11,81 @@ (defn send-and-receive-activity [{:keys [transaction relative-date status sender recipient token amount network-name - network-logo]}] - [quo/wallet-activity - {:transaction transaction - :timestamp relative-date - :status status - :counter 1 - :first-tag {:size 24 - :type :token - :token token - :amount amount} - :second-tag-prefix :t/from - :second-tag {:type :address :address sender} - :third-tag-prefix :t/to - :third-tag {:type :address :address recipient} - :fourth-tag-prefix :t/via - :fourth-tag {:size 24 - :type :network - :network-name network-name - :network-logo network-logo} - :blur? false}]) + network-logo token-id nft-url nft-name]}] + (if token-id + [quo/wallet-activity + {:transaction transaction + :timestamp relative-date + :status status + :counter 1 + :first-tag {:size 24 + :type :collectible + :collectible nft-url + :collectible-name (if (> amount 1) + (str amount " " nft-name) + nft-name) + :collectible-number token-id} + :second-tag-prefix :t/from + :second-tag {:type :address :address sender} + :third-tag-prefix :t/to + :third-tag {:type :address :address recipient} + :fourth-tag-prefix :t/via + :fourth-tag {:size 24 + :type :network + :network-name network-name + :network-logo network-logo} + :blur? false}] + [quo/wallet-activity + {:transaction transaction + :timestamp relative-date + :status status + :counter 1 + :first-tag {:size 24 + :type :token + :token token + :amount amount} + :second-tag-prefix :t/from + :second-tag {:type :address :address sender} + :third-tag-prefix :t/to + :third-tag {:type :address :address recipient} + :fourth-tag-prefix :t/via + :fourth-tag {:size 24 + :type :network + :network-name network-name + :network-logo network-logo} + :blur? false}])) + +;; WIP to add the mint activity. +;(defn mint-activity +; [{:keys [transaction relative-date status recipient network-name +; network-logo nft-name nft-url token-id]}] +; [quo/wallet-activity +; {:transaction transaction +; :timestamp relative-date +; :status status +; :counter 1 +; :first-tag {:size 24 +; :type :collectible +; :collectible nft-url +; :collectible-name nft-name +; :collectible-number token-id} +; :second-tag-prefix :t/at +; :second-tag {:type :address :address recipient} +; :third-tag-prefix :t/to +; :third-tag {:type :address :address recipient} +; :fourth-tag-prefix :t/via +; :fourth-tag {:size 24 +; :type :network +; :network-name network-name +; :network-logo network-logo} +; :blur? false}]) (defn activity-item [{:keys [transaction] :as activity}] (case transaction (:send :receive) [send-and-receive-activity activity] + ;; WIP to add the mint activity. + ;; :mint [mint-activity activity] nil)) (defn view diff --git a/src/status_im/contexts/wallet/signals.cljs b/src/status_im/contexts/wallet/signals.cljs index 3b503d80b9..cb9b37f571 100644 --- a/src/status_im/contexts/wallet/signals.cljs +++ b/src/status_im/contexts/wallet/signals.cljs @@ -23,22 +23,39 @@ :block-number blockNumber :accounts accounts}) (case event-type - "pending-transaction-status-changed" {:fx - [[:dispatch - [:wallet/pending-transaction-status-changed-received - (transforms/js->clj event-js)]]]} - "wallet-owned-collectibles-filtering-done" {:fx [[:dispatch - [:wallet/owned-collectibles-filtering-done - (transforms/js->clj event-js)]]]} - "wallet-get-collectibles-details-done" {:fx [[:dispatch - [:wallet/get-collectible-details-done - (transforms/js->clj event-js)]]]} - "wallet-tick-reload" {:fx [[:dispatch [:wallet/reload]]]} - "wallet-blockchain-status-changed" {:fx [[:dispatch - [:wallet/blockchain-status-changed - (transforms/js->clj event-js)]]]} - "wallet-activity-filtering-done" {:fx - [[:dispatch - [:wallet/activity-filtering-for-current-account-done - (transforms/js->clj event-js)]]]} + "pending-transaction-status-changed" + {:fx + [[:dispatch + [:wallet/pending-transaction-status-changed-received + (transforms/js->clj event-js)]]]} + + "wallet-owned-collectibles-filtering-done" + {:fx [[:dispatch + [:wallet/owned-collectibles-filtering-done + (transforms/js->clj event-js)]]]} + + "wallet-get-collectibles-details-done" + {:fx [[:dispatch + [:wallet/get-collectible-details-done + (transforms/js->clj event-js)]]]} + + "wallet-tick-reload" + {:fx [[:dispatch [:wallet/reload]]]} + + "wallet-blockchain-status-changed" + {:fx [[:dispatch + [:wallet/blockchain-status-changed + (transforms/js->clj event-js)]]]} + + "wallet-activity-filtering-done" + {:fx + [[:dispatch + [:wallet/activity-filtering-for-current-account-done + (transforms/js->clj event-js)]]]} + + "wallet-activity-filtering-entries-updated" + {:fx [[:dispatch + [:wallet/activities-filtering-entries-updated + (transforms/js->clj event-js)]]]} + (log/debug ::unknown-wallet-event :type event-type))))) diff --git a/src/status_im/subs/wallet/activities.cljs b/src/status_im/subs/wallet/activities.cljs index 418680b12d..4deb544dad 100644 --- a/src/status_im/subs/wallet/activities.cljs +++ b/src/status_im/subs/wallet/activities.cljs @@ -1,5 +1,6 @@ (ns status-im.subs.wallet.activities (:require + [clojure.string :as string] [native-module.core :as native-module] [quo.foundations.resources :as quo.resources] [quo.foundations.resources] @@ -16,49 +17,91 @@ :<- [:wallet] :-> :activities) -(defn- activity-amount - [amount] - (-> amount +(defn- hex-wei->amount + [hex-str-amount] + (-> hex-str-amount (utils.hex/normalize-hex) (native-module/hex-to-number) (money/wei->ether) (money/with-precision precision) (str))) +(defn- hex-string->number + [hex-str-amount] + (-> hex-str-amount + (utils.hex/normalize-hex) + (native-module/hex-to-number) + (str))) + +(defn- normalize-nft-name + [token-id nft-name] + (if (and (some? token-id) (string/blank? nft-name)) + "Unknown" + nft-name)) + +(defn- get-token-amount + [token amount] + (let [token-type (:token-type token)] + (if (#{constants/wallet-activity-token-type-erc-721 + constants/wallet-activity-token-type-erc-1155} + token-type) + (hex-string->number amount) + (hex-wei->amount amount)))) + (defn- process-send-activity - [{:keys [symbol-out chain-id-out amount-out]} activity chain-id->network-name] - (let [network-name (chain-id->network-name chain-id-out)] - (assoc activity - :transaction :send - :token symbol-out - :amount (activity-amount amount-out) - :network-name network-name - :network-logo (quo.resources/get-network network-name)))) + [{:keys [symbol-out amount-out token-out] + :as data}] + (assoc data + :transaction :send + :token symbol-out + :amount (get-token-amount token-out amount-out))) (defn- process-receive-activity - [{:keys [symbol-in amount-in chain-id-in]} activity chain-id->network-name] - (let [network-name (chain-id->network-name chain-id-in)] - (assoc activity - :transaction :receive - :token symbol-in - :amount (activity-amount amount-in) - :network-name network-name - :network-logo (quo.resources/get-network network-name)))) + [{:keys [symbol-in amount-in token-in] :as data}] + (assoc data + :transaction :receive + :token symbol-in + :amount (get-token-amount token-in amount-in))) + +;; WIP to add the mint activity. +;(defn- process-mint-activity +; [{:keys [token-in symbol-in amount-in chain-id-in nft-name] :as data} +; chain-id->network-name] +; (-> data +; (merge activity) +; (assoc :transaction :mint +; ;:token symbol-in +; ;:amount (activity-amount amount-in) +; :nft-name (normalize-nft-name token-id nft-name)))) (defn- process-activity-by-type [chain-id->network-name - {:keys [activity-type activity-status timestamp sender recipient] :as data}] - (let [activity {:relative-date (datetime/timestamp->relative (* timestamp 1000)) - :timestamp timestamp - :status (constants/wallet-activity-status->name activity-status) - :sender sender - :recipient recipient}] + {:keys [activity-type activity-status timestamp sender recipient token-in token-out + chain-id-in chain-id-out nft-name] + :as data}] + (let [network-name (chain-id->network-name (or chain-id-in chain-id-out)) + token-id (some-> (or token-in token-out) + :token-id + hex-string->number) + activity (assoc data + :relative-date (datetime/timestamp->relative (* timestamp 1000)) + :sender sender + :recipient recipient + :timestamp timestamp + :network-name network-name + :token-id token-id + :status (constants/wallet-activity-status->name activity-status) + :network-logo (quo.resources/get-network network-name) + :nft-name (normalize-nft-name token-id nft-name))] (condp = activity-type constants/wallet-activity-type-send - (process-send-activity data activity chain-id->network-name) + (process-send-activity activity) constants/wallet-activity-type-receive - (process-receive-activity data activity chain-id->network-name) + (process-receive-activity activity) + + ;; WIP to add the mint activity. Constants/wallet-activity-type-mint + ;; (process-mint-activity activity chain-id->network-name) nil))) @@ -69,9 +112,11 @@ :<- [:wallet/network-details] (fn [[activities current-viewing-account-address network-details]] (let [chain-id->network-name (update-vals (group-by :chain-id network-details) - (comp :network-name first))] - (->> current-viewing-account-address - (get activities) + (comp :network-name first)) + address-activities (->> (get activities current-viewing-account-address) + (vals) + (sort :timestamp))] + (->> address-activities (keep #(process-activity-by-type chain-id->network-name %)) (group-by (fn [{:keys [timestamp]}] (datetime/timestamp->relative-short-date (* timestamp 1000)))) diff --git a/src/status_im/subs/wallet/activities_test.cljs b/src/status_im/subs/wallet/activities_test.cljs index b949598910..b90b2872a9 100644 --- a/src/status_im/subs/wallet/activities_test.cljs +++ b/src/status_im/subs/wallet/activities_test.cljs @@ -24,26 +24,26 @@ (fn [db] (-> db (assoc-in [:wallet :activities] - {"acc1" [{:activity-type constants/wallet-activity-type-send - :amount-out "0x1" - :sender "acc1" - :recipient "acc2" - :timestamp 1588291200} - {:activity-type constants/wallet-activity-type-receive - :amount-in "0x1" - :sender "acc2" - :recipient "acc1" - :timestamp 1588377600} - {:activity-type constants/wallet-activity-type-send - :amount-out "0x1" - :sender "acc1" - :recipient "acc4" - :timestamp 1588464000}] - "acc3" [{:activity-type constants/wallet-activity-type-receive - :amount-in "0x1" - :sender "acc4" - :recipient "acc3" - :timestamp 1588464000}]}) + {"acc1" {1 {:activity-type constants/wallet-activity-type-send + :amount-out "0x1" + :sender "acc1" + :recipient "acc2" + :timestamp 1588291200} + 2 {:activity-type constants/wallet-activity-type-receive + :amount-in "0x1" + :sender "acc2" + :recipient "acc1" + :timestamp 1588377600} + 3 {:activity-type constants/wallet-activity-type-send + :amount-out "0x1" + :sender "acc1" + :recipient "acc4" + :timestamp 1588464000}} + "acc3" {4 {:activity-type constants/wallet-activity-type-receive + :amount-in "0x1" + :sender "acc4" + :recipient "acc3" + :timestamp 1588464000}}}) (assoc-in [:wallet :current-viewing-account-address] "acc1")))) (is (match? [{:title "May 3, 2020" diff --git a/src/status_im/subs/wallet/send.cljs b/src/status_im/subs/wallet/send.cljs index 24f98ffda2..ad1045ba51 100644 --- a/src/status_im/subs/wallet/send.cljs +++ b/src/status_im/subs/wallet/send.cljs @@ -49,8 +49,9 @@ :<- [:wallet/all-activities] :<- [:wallet/current-viewing-account-address] (fn [[all-activities current-viewing-account-address]] - (let [address-activity (get all-activities current-viewing-account-address)] + (let [address-activity (vals (get all-activities current-viewing-account-address))] (->> address-activity + (sort :timestamp) (keep (fn [{:keys [activity-type recipient]}] (when (= constants/wallet-activity-type-send activity-type) recipient))) diff --git a/src/status_im/subs/wallet/send_test.cljs b/src/status_im/subs/wallet/send_test.cljs index 0cb307b41b..16714d4af3 100644 --- a/src/status_im/subs/wallet/send_test.cljs +++ b/src/status_im/subs/wallet/send_test.cljs @@ -62,25 +62,25 @@ (fn [db] (-> db (assoc-in [:wallet :activities] - {"acc1" [{:activity-type constants/wallet-activity-type-send - :amount-out "0x1" - :sender "acc1" - :recipient "acc2" - :timestamp 1588291200} - {:activity-type constants/wallet-activity-type-receive - :amount-in "0x1" - :sender "acc2" - :recipient "acc1" - :timestamp 1588377600} - {:activity-type constants/wallet-activity-type-send - :amount-out "0x1" - :sender "acc1" - :recipient "acc4" - :timestamp 1588464000}] - "acc3" [{:activity-type constants/wallet-activity-type-receive - :amount-in "0x1" - :sender "acc4" - :recipient "acc3" - :timestamp 1588464000}]}) + {"acc1" {1 {:activity-type constants/wallet-activity-type-send + :amount-out "0x1" + :sender "acc1" + :recipient "acc2" + :timestamp 1588291200} + 2 {:activity-type constants/wallet-activity-type-receive + :amount-in "0x1" + :sender "acc2" + :recipient "acc1" + :timestamp 1588377600} + 3 {:activity-type constants/wallet-activity-type-send + :amount-out "0x1" + :sender "acc1" + :recipient "acc4" + :timestamp 1588464000}} + "acc3" {4 {:activity-type constants/wallet-activity-type-receive + :amount-in "0x1" + :sender "acc4" + :recipient "acc3" + :timestamp 1588464000}}}) (assoc-in [:wallet :current-viewing-account-address] "acc1")))) (is (match? ["acc2" "acc4"] (rf/sub [sub-name]))))) From a912125ba948700cce363a33d6e988a7fbb16e6c Mon Sep 17 00:00:00 2001 From: mmilad75 <55688834+mmilad75@users.noreply.github.com> Date: Wed, 17 Jul 2024 10:47:27 +0200 Subject: [PATCH 51/79] Token value shows fiat value on the transaction confirmation page #20734 (#20770) --- src/status_im/contexts/wallet/send/input_amount/view.cljs | 6 ++---- .../contexts/wallet/send/transaction_confirmation/view.cljs | 1 - 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/status_im/contexts/wallet/send/input_amount/view.cljs b/src/status_im/contexts/wallet/send/input_amount/view.cljs index 5bfd9bc244..52373ea80c 100644 --- a/src/status_im/contexts/wallet/send/input_amount/view.cljs +++ b/src/status_im/contexts/wallet/send/input_amount/view.cljs @@ -154,11 +154,9 @@ [input-state set-input-state] (rn/use-state controlled-input/init-state) [just-toggled-mode? set-just-toggled-mode?] (rn/use-state false) clear-input! #(set-input-state controlled-input/delete-all) - handle-on-confirm (fn [] + handle-on-confirm (fn [amount] (rf/dispatch [:wallet/set-token-amount-to-send - {:amount - (controlled-input/input-value - input-state) + {:amount amount :stack-id current-screen-id}])) {fiat-currency :currency} (rf/sub [:profile/profile]) {token-symbol :symbol diff --git a/src/status_im/contexts/wallet/send/transaction_confirmation/view.cljs b/src/status_im/contexts/wallet/send/transaction_confirmation/view.cljs index 7f2fd8263f..c35bd8984b 100644 --- a/src/status_im/contexts/wallet/send/transaction_confirmation/view.cljs +++ b/src/status_im/contexts/wallet/send/transaction_confirmation/view.cljs @@ -157,7 +157,6 @@ [quo/data-item {:container-style style/detail-item :blur? false - :description :default :card? false :status :default :size :small From 7b09402fcb34ee997e61440fc02c1b847ad86e89 Mon Sep 17 00:00:00 2001 From: Mohsen Date: Wed, 17 Jul 2024 16:04:40 +0330 Subject: [PATCH 52/79] [#20742] fix: blue background behind the "Save address" button (#20754) --- .../floating_container/style.cljs | 12 +++++++++--- .../floating_container/view.cljs | 16 +++++++--------- .../common/floating_button_page/view.cljs | 5 +++-- .../add_address_to_save/view.cljs | 14 ++++++++------ .../saved_addresses/save_address/view.cljs | 3 ++- 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/status_im/common/floating_button_page/floating_container/style.cljs b/src/status_im/common/floating_button_page/floating_container/style.cljs index 58c59c8a48..5c0429e5eb 100644 --- a/src/status_im/common/floating_button_page/floating_container/style.cljs +++ b/src/status_im/common/floating_button_page/floating_container/style.cljs @@ -1,5 +1,6 @@ (ns status-im.common.floating-button-page.floating-container.style - (:require [react-native.safe-area :as safe-area])) + (:require [quo.foundations.colors :as colors] + [react-native.safe-area :as safe-area])) (defn content-container [blur? keyboard-shown?] @@ -11,7 +12,12 @@ :padding-horizontal 20} blur? (dissoc :padding-vertical :padding-horizontal)))) -(def blur-inner-container - {:background-color :transparent ; required, otherwise blur-view will shrink +(defn blur-inner-container + [theme shell-overlay?] + {:background-color (colors/theme-colors colors/white-70-blur + (if shell-overlay? + colors/neutral-80-opa-80-blur + colors/neutral-95-opa-70-blur) + theme) :padding-vertical 12 :padding-horizontal 20}) diff --git a/src/status_im/common/floating_button_page/floating_container/view.cljs b/src/status_im/common/floating_button_page/floating_container/view.cljs index a01505a8a2..2ea0207ecc 100644 --- a/src/status_im/common/floating_button_page/floating_container/view.cljs +++ b/src/status_im/common/floating_button_page/floating_container/view.cljs @@ -1,27 +1,25 @@ (ns status-im.common.floating-button-page.floating-container.view (:require [quo.core :as quo] - [quo.foundations.colors :as colors] [quo.theme :as quo.theme] [react-native.core :as rn] [status-im.common.floating-button-page.floating-container.style :as style])) (defn- blur-container - [child] + [child shell-overlay?] (let [theme (quo.theme/use-theme)] [quo/blur - {:blur-amount 52 - :blur-radius 20 - :blur-type :transparent - :blur-overlay-color (colors/theme-colors colors/white-70-blur colors/neutral-95-opa-70-blur theme)} - [rn/view {:style style/blur-inner-container} + {:blur-amount 20 + :blur-type :transparent + :overlay-color :transparent} + [rn/view {:style (style/blur-inner-container theme shell-overlay?)} child]])) (defn view - [{:keys [on-layout keyboard-shown? blur?]} child] + [{:keys [on-layout keyboard-shown? blur? shell-overlay?]} child] [rn/view {:style (style/content-container blur? keyboard-shown?) :on-layout on-layout} (if blur? - [blur-container child] + [blur-container child shell-overlay?] child)]) diff --git a/src/status_im/common/floating_button_page/view.cljs b/src/status_im/common/floating_button_page/view.cljs index f67b5f98a2..5d9cb21188 100644 --- a/src/status_im/common/floating_button_page/view.cljs +++ b/src/status_im/common/floating_button_page/view.cljs @@ -58,7 +58,7 @@ (defn view [{:keys [header footer customization-color footer-container-padding header-container-style - content-container-style gradient-cover? keyboard-should-persist-taps] + content-container-style gradient-cover? keyboard-should-persist-taps shell-overlay?] :or {footer-container-padding (safe-area/get-top)}} & children] (reagent/with-let [scroll-view-ref (atom nil) @@ -121,7 +121,8 @@ [floating-container/view {:on-layout set-footer-container-height :keyboard-shown? keyboard-shown? - :blur? show-background?} + :blur? show-background? + :shell-overlay? shell-overlay?} footer]]]]) (finally (remove-listeners)))) diff --git a/src/status_im/contexts/settings/wallet/saved_addresses/add_address_to_save/view.cljs b/src/status_im/contexts/settings/wallet/saved_addresses/add_address_to_save/view.cljs index 24e2ca4325..8697bb1bce 100644 --- a/src/status_im/contexts/settings/wallet/saved_addresses/add_address_to_save/view.cljs +++ b/src/status_im/contexts/settings/wallet/saved_addresses/add_address_to_save/view.cljs @@ -113,7 +113,8 @@ (defn view [] - (let [profile-color (rf/sub [:profile/customization-color]) + (let [view-id (rf/sub [:view-id]) + profile-color (rf/sub [:profile/customization-color]) accounts-addresses (rf/sub [:wallet/addresses]) saved-addresses-addresses (rf/sub [:wallet/saved-addresses-addresses]) [address-or-ens set-address-or-ens] (rn/use-state "") @@ -175,11 +176,12 @@ :on-press navigate-back :margin-top (safe-area/get-top) :accessibility-label :add-address-to-save-page-nav}] - :footer [quo/button - {:customization-color profile-color - :disabled? button-disabled? - :on-press on-press-continue} - (i18n/label :t/continue)]} + :footer (when (= view-id :screen/settings.add-address-to-save) + [quo/button + {:customization-color profile-color + :disabled? button-disabled? + :on-press on-press-continue} + (i18n/label :t/continue)])} [quo/page-top {:container-style style/header-container :blur? true diff --git a/src/status_im/contexts/settings/wallet/saved_addresses/save_address/view.cljs b/src/status_im/contexts/settings/wallet/saved_addresses/save_address/view.cljs index 57372f7386..34fb023444 100644 --- a/src/status_im/contexts/settings/wallet/saved_addresses/save_address/view.cljs +++ b/src/status_im/contexts/settings/wallet/saved_addresses/save_address/view.cljs @@ -121,7 +121,8 @@ :on-press on-press-save} (i18n/label :t/save-address)] :customization-color address-color - :gradient-cover? true} + :gradient-cover? true + :shell-overlay? true} [quo/wallet-user-avatar {:full-name (if (string/blank? address-label) placeholder From b3e88508ac80b056851a11cb755f6edb03045b42 Mon Sep 17 00:00:00 2001 From: Icaro Motta Date: Wed, 17 Jul 2024 12:16:06 -0300 Subject: [PATCH 53/79] chore(activity-center): Various small refactors (#20690) Potentially a solution to https://github.com/status-im/status-mobile/issues/15706 - [x] Fixes swipe button on Android and iOS. - [x] Performance: we now subscribe only to the minimum from each community. This could be the reason the AC would lag as described in the parent issue. - [x] Performance: was able to use flex and removed swipe button height calculation that was using `onLayout` and was causing a re-render. - [x] Performance: reduced the initial number of items to render in the flatlist from 10 to 7. - [x] Performance: delay rendering the heavy list of notification components. See in the video below how slow it is to open the AC with just 6 notifications and that the opening animation is never displayed. And then check the improved version with the artificial delay provided by `rn/delay-render`. By opening the AC first and animating, this gives the user something to look for, and hopefully a few milliseconds more to think the app is not stuck, which will be preciously used to render notifications. We refactor all views in the AC to: - [x] Follow our newest standards with React hooks. - [x] Removed prop-drilling by creating a separate React context to store the current swipeable item (because we need to call `.close` on a `Gesture Swipeable` instance whenever a new swipeable opens. --- src/status_im/constants.cljs | 5 + .../shell/activity_center/context.cljs | 18 ++ .../shell/activity_center/events.cljs | 5 +- .../notification/admin/view.cljs | 50 +++-- .../notification/common/style.cljs | 1 + .../notification/common/view.cljs | 121 +++++------ .../notification/community_kicked/view.cljs | 34 +-- .../notification/community_request/view.cljs | 57 ++--- .../notification/contact_requests/view.cljs | 69 ++++--- .../contact_verification/view.cljs | 194 +++++++++--------- .../notification/membership/view.cljs | 57 +++-- .../notification/mentions/view.cljs | 26 ++- .../notification/reply/view.cljs | 38 ++-- .../contexts/shell/activity_center/view.cljs | 118 ++++++----- src/status_im/subs/communities.cljs | 21 ++ 15 files changed, 447 insertions(+), 367 deletions(-) create mode 100644 src/status_im/contexts/shell/activity_center/context.cljs diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index 7c92dccf2b..f9aad3c896 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -45,6 +45,11 @@ (def ^:const activity-center-membership-status-accepted 2) (def ^:const activity-center-membership-status-declined 3) +;; Choose the maximum number of notifications that *usually/safely* fit on +;; most screens, so that the UI doesn't have to needlessly render +;; notifications. +(def ^:const notifications-per-page 7) + (def ^:const mute-for-15-mins-type 1) (def ^:const mute-for-1-hour-type 2) (def ^:const mute-for-8-hours-type 3) diff --git a/src/status_im/contexts/shell/activity_center/context.cljs b/src/status_im/contexts/shell/activity_center/context.cljs new file mode 100644 index 0000000000..696708c438 --- /dev/null +++ b/src/status_im/contexts/shell/activity_center/context.cljs @@ -0,0 +1,18 @@ +(ns status-im.contexts.shell.activity-center.context + (:require + ["react" :as react] + [oops.core :as oops] + [react-native.core :as rn])) + +(defonce ^:private context + (react/createContext {})) + +(defn provider + [state & children] + (into [:> (oops/oget context :Provider) {:value state}] + children)) + +(defn use-context + [] + (let [ctx (rn/use-context context)] + {:active-swipeable (oops/oget ctx :activeSwipeable)})) diff --git a/src/status_im/contexts/shell/activity_center/events.cljs b/src/status_im/contexts/shell/activity_center/events.cljs index 34c1cc832f..d68f16bdd4 100644 --- a/src/status_im/contexts/shell/activity_center/events.cljs +++ b/src/status_im/contexts/shell/activity_center/events.cljs @@ -15,10 +15,7 @@ (def defaults {:filter-status :unread :filter-type types/no-type - ;; Choose the maximum number of notifications that *usually/safely* fit on - ;; most screens, so that the UI doesn't have to needlessly render - ;; notifications. - :notifications-per-page 8}) + :notifications-per-page constants/notifications-per-page}) ;;;; Navigation diff --git a/src/status_im/contexts/shell/activity_center/notification/admin/view.cljs b/src/status_im/contexts/shell/activity_center/notification/admin/view.cljs index 7bfe39151e..c06633e7f8 100644 --- a/src/status_im/contexts/shell/activity_center/notification/admin/view.cljs +++ b/src/status_im/contexts/shell/activity_center/notification/admin/view.cljs @@ -1,6 +1,7 @@ (ns status-im.contexts.shell.activity-center.notification.admin.view (:require [quo.core :as quo] + [react-native.core :as rn] [status-im.constants :as constants] [status-im.contexts.shell.activity-center.notification.common.style :as common-style] [status-im.contexts.shell.activity-center.notification.common.view :as common] @@ -23,42 +24,50 @@ :text (i18n/label :t/decline)}]) (defn- swipeable - [{:keys [active-swipeable notification extra-fn]} child] - (let [{:keys [community-id id membership-status]} notification] + [{:keys [notification extra-fn]} child] + (let [{:keys [community-id id + membership-status]} notification + accept (rn/use-callback + (fn [] + (rf/dispatch [:communities/accept-request-to-join-pressed + community-id id])) + [community-id id]) + decline (rn/use-callback + (fn [] + (rf/dispatch [:communities/decline-request-to-join-pressed + community-id id])) + [community-id id])] (cond (#{constants/activity-center-membership-status-accepted constants/activity-center-membership-status-declined} membership-status) [common/swipeable - {:left-button common/swipe-button-read-or-unread - :left-on-press common/swipe-on-press-toggle-read - :right-button common/swipe-button-delete - :right-on-press common/swipe-on-press-delete - :active-swipeable active-swipeable - :extra-fn extra-fn} + {:left-button common/swipe-button-read-or-unread + :left-on-press common/swipe-on-press-toggle-read + :right-button common/swipe-button-delete + :right-on-press common/swipe-on-press-delete + :extra-fn extra-fn} child] (= membership-status constants/activity-center-membership-status-pending) [common/swipeable - {:left-button swipe-button-accept - :left-on-press #(rf/dispatch [:communities/accept-request-to-join-pressed community-id id]) - :right-button swipe-button-decline - :right-on-press #(rf/dispatch [:communities/decline-request-to-join-pressed community-id - id]) - :active-swipeable active-swipeable - :extra-fn extra-fn} + {:left-button swipe-button-accept + :left-on-press accept + :right-button swipe-button-decline + :right-on-press decline + :extra-fn extra-fn} child] :else child))) (defn view - [{:keys [notification set-swipeable-height customization-color] :as props}] + [{:keys [notification] :as props}] (let [{:keys [author community-id id membership-status read timestamp]} notification - community (rf/sub [:communities/community community-id]) - community-name (:name community) - community-image (get-in community [:images :thumbnail :uri])] + community-name (rf/sub [:communities/name community-id]) + community-logo (rf/sub [:communities/logo community-id]) + customization-color (rf/sub [:profile/customization-color])] [swipeable props [quo/activity-log {:title (i18n/label :t/join-request) @@ -66,14 +75,13 @@ :icon :i/add-user :timestamp (datetime/timestamp->relative timestamp) :unread? (not read) - :on-layout set-swipeable-height :context [[common/user-avatar-tag author] (i18n/label :t/wants-to-join) [quo/context-tag {:type :community :size 24 :blur? true - :community-logo community-image + :community-logo community-logo :community-name community-name}]] :items (condp = membership-status constants/activity-center-membership-status-accepted diff --git a/src/status_im/contexts/shell/activity_center/notification/common/style.cljs b/src/status_im/contexts/shell/activity_center/notification/common/style.cljs index 1a54e11691..b1ce7a44da 100644 --- a/src/status_im/contexts/shell/activity_center/notification/common/style.cljs +++ b/src/status_im/contexts/shell/activity_center/notification/common/style.cljs @@ -4,6 +4,7 @@ (def swipe-action-width 80) (def swipe-button-border-radius 16) +(def swipe-button-margin 8) (def user-avatar-tag {:background-color colors/white-opa-10}) diff --git a/src/status_im/contexts/shell/activity_center/notification/common/view.cljs b/src/status_im/contexts/shell/activity_center/notification/common/view.cljs index 245f34344b..8b0508ee8e 100644 --- a/src/status_im/contexts/shell/activity_center/notification/common/view.cljs +++ b/src/status_im/contexts/shell/activity_center/notification/common/view.cljs @@ -1,10 +1,12 @@ (ns status-im.contexts.shell.activity-center.notification.common.view (:require + [oops.core :as oops] [quo.core :as quo] [quo.foundations.colors :as colors] [react-native.core :as rn] [react-native.gesture :as gesture] [status-im.contexts.profile.utils :as profile.utils] + [status-im.contexts.shell.activity-center.context :as ac.context] [status-im.contexts.shell.activity-center.notification.common.style :as style] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -14,7 +16,7 @@ (let [profile (rf/sub [:contacts/contact-by-identity user-id])] [rn/view {:on-start-should-set-responder - (fn [_event] + (fn [] (rf/dispatch [:navigate-back]) (rf/dispatch [:chat.ui/show-profile user-id]) true)} @@ -33,35 +35,27 @@ swipe-button swipeable-ref style]}] - (fn [_ ^js drag-x] - (let [{:keys [height] :as extra} (extra-fn) - opacity (.interpolate drag-x interpolation-opacity) - translate-x (.interpolate drag-x interpolation-translate-x)] + (fn [_ drag-x] + (let [extra (extra-fn) + opacity (oops/ocall drag-x :interpolate interpolation-opacity) + translate-x (oops/ocall drag-x :interpolate interpolation-translate-x)] [gesture/rect-button {:style (merge {:border-radius style/swipe-button-border-radius} style) :accessibility-label :notification-swipe-action-button :on-press (fn [] (when @swipeable-ref - (.close ^js @swipeable-ref) + (oops/ocall @swipeable-ref :close) (reset! active-swipeable nil)) (on-press extra))} [swipe-button {:style {:opacity opacity :transform [{:translateX translate-x}] - :height height + :flex 1 :width style/swipe-action-width}} extra]]))) -(defn- close-active-swipeable - [active-swipeable swipeable] - (fn [_] - (when (and @active-swipeable - (not= @active-swipeable @swipeable)) - (.close ^js @active-swipeable)) - (reset! active-swipeable @swipeable))) - (defn swipe-button-container - [{:keys [style icon text]} _] + [{:keys [style icon text]}] [rn/animated-view {:accessibility-label :notification-swipe :style style} @@ -103,47 +97,54 @@ (rf/dispatch [:activity-center.notifications/delete (:id notification)])) (defn swipeable - [_] - (let [swipeable-ref (atom nil)] - (fn [{:keys [active-swipeable - extra-fn - left-button - left-on-press - right-button - right-on-press]} - & children] - (into - [gesture/swipeable - (merge - {:ref #(reset! swipeable-ref %) - :accessibility-label :notification-swipeable - :friction 2 - :on-swipeable-will-open (close-active-swipeable active-swipeable swipeable-ref) - :children-container-style {:padding-horizontal 20}} - (when left-button - {:overshoot-left false - :left-threshold style/swipe-action-width - :render-left-actions (render-swipe-action - {:active-swipeable active-swipeable - :extra-fn extra-fn - :interpolation-opacity style/left-swipe-opacity-interpolation-js - :interpolation-translate-x - style/left-swipe-translate-x-interpolation-js - :on-press left-on-press - :swipe-button left-button - :swipeable-ref swipeable-ref - :style {:left 8}})}) - (when right-button - {:overshoot-right false - :right-threshold style/swipe-action-width - :render-right-actions (render-swipe-action - {:active-swipeable active-swipeable - :extra-fn extra-fn - :interpolation-opacity style/right-swipe-opacity-interpolation-js - :interpolation-translate-x - style/right-swipe-translate-x-interpolation-js - :on-press right-on-press - :swipe-button right-button - :swipeable-ref swipeable-ref - :style {:right -8}})}))] - children)))) + [{:keys [extra-fn + left-button + left-on-press + right-button + right-on-press]} + child] + (let [{:keys [active-swipeable]} (ac.context/use-context) + this-swipeable (rn/use-ref-atom nil) + set-this-swipeable (rn/use-callback #(reset! this-swipeable %) + [this-swipeable]) + on-swipeable-will-open (rn/use-callback + (fn [] + (when (and @active-swipeable + (not= @active-swipeable @this-swipeable)) + (oops/ocall @active-swipeable :close)) + (reset! active-swipeable @this-swipeable)) + [@active-swipeable @this-swipeable])] + [gesture/swipeable + (cond-> {:ref set-this-swipeable + :accessibility-label :notification-swipeable + :friction 2 + :on-swipeable-will-open on-swipeable-will-open + :children-container-style {:padding-horizontal 20}} + left-button + (assoc :overshoot-left false + :left-threshold style/swipe-action-width + :render-left-actions (render-swipe-action + {:active-swipeable active-swipeable + :extra-fn extra-fn + :interpolation-opacity style/left-swipe-opacity-interpolation-js + :interpolation-translate-x + style/left-swipe-translate-x-interpolation-js + :on-press left-on-press + :swipe-button left-button + :swipeable-ref this-swipeable + :style {:left style/swipe-button-margin}})) + + right-button + (assoc :overshoot-right false + :right-threshold style/swipe-action-width + :render-right-actions (render-swipe-action + {:active-swipeable active-swipeable + :extra-fn extra-fn + :interpolation-opacity style/right-swipe-opacity-interpolation-js + :interpolation-translate-x + style/right-swipe-translate-x-interpolation-js + :on-press right-on-press + :swipe-button right-button + :swipeable-ref this-swipeable + :style {:right (- style/swipe-button-margin)}}))) + child])) diff --git a/src/status_im/contexts/shell/activity_center/notification/community_kicked/view.cljs b/src/status_im/contexts/shell/activity_center/notification/community_kicked/view.cljs index 0fc0021d6a..598d2ba094 100644 --- a/src/status_im/contexts/shell/activity_center/notification/community_kicked/view.cljs +++ b/src/status_im/contexts/shell/activity_center/notification/community_kicked/view.cljs @@ -1,6 +1,7 @@ (ns status-im.contexts.shell.activity-center.notification.community-kicked.view (:require [quo.core :as quo] + [react-native.core :as rn] [react-native.gesture :as gesture] [status-im.contexts.shell.activity-center.notification.common.style :as common-style] [status-im.contexts.shell.activity-center.notification.common.view :as common] @@ -9,33 +10,34 @@ [utils.re-frame :as rf])) (defn- swipeable - [{:keys [active-swipeable extra-fn]} child] + [{:keys [extra-fn]} child] [common/swipeable - {:left-button common/swipe-button-read-or-unread - :left-on-press common/swipe-on-press-toggle-read - :right-button common/swipe-button-delete - :right-on-press common/swipe-on-press-delete - :active-swipeable active-swipeable - :extra-fn extra-fn} + {:left-button common/swipe-button-read-or-unread + :left-on-press common/swipe-on-press-toggle-read + :right-button common/swipe-button-delete + :right-on-press common/swipe-on-press-delete + :extra-fn extra-fn} child]) (defn view - [{:keys [notification set-swipeable-height customization-color] :as props}] - (let [{:keys [community-id read + [{:keys [notification extra-fn]}] + (let [{:keys [id community-id read timestamp]} notification community (rf/sub [:communities/community community-id]) community-name (:name community) - community-image (get-in community [:images :thumbnail :uri])] - [swipeable props - [gesture/touchable-without-feedback - {:on-press (fn [] - (rf/dispatch [:navigate-back]) - (rf/dispatch [:activity-center.notifications/mark-as-read (:id notification)]))} + community-image (get-in community [:images :thumbnail :uri]) + customization-color (rf/sub [:profile/customization-color]) + on-press (rn/use-callback + (fn [] + (rf/dispatch [:navigate-back]) + (rf/dispatch [:activity-center.notifications/mark-as-read id])) + [id])] + [swipeable {:extra-fn extra-fn} + [gesture/touchable-without-feedback {:on-press on-press} [quo/activity-log {:title (i18n/label :t/community-kicked-heading) :customization-color customization-color :icon :i/placeholder - :on-layout set-swipeable-height :timestamp (datetime/timestamp->relative timestamp) :unread? (not read) :context [[quo/text {:style common-style/user-avatar-tag-text} diff --git a/src/status_im/contexts/shell/activity_center/notification/community_request/view.cljs b/src/status_im/contexts/shell/activity_center/notification/community_request/view.cljs index dc2cbe9230..905ab5803e 100644 --- a/src/status_im/contexts/shell/activity_center/notification/community_request/view.cljs +++ b/src/status_im/contexts/shell/activity_center/notification/community_request/view.cljs @@ -1,6 +1,7 @@ (ns status-im.contexts.shell.activity-center.notification.community-request.view (:require [quo.core :as quo] + [react-native.core :as rn] [react-native.gesture :as gesture] [status-im.constants :as constants] [status-im.contexts.shell.activity-center.notification.common.style :as common-style] @@ -10,27 +11,23 @@ [utils.re-frame :as rf])) (defn- swipeable - [{:keys [active-swipeable extra-fn]} child] + [{:keys [extra-fn]} child] [common/swipeable - {:left-button common/swipe-button-read-or-unread - :left-on-press common/swipe-on-press-toggle-read - :right-button common/swipe-button-delete - :right-on-press common/swipe-on-press-delete - :active-swipeable active-swipeable - :extra-fn extra-fn} + {:left-button common/swipe-button-read-or-unread + :left-on-press common/swipe-on-press-toggle-read + :right-button common/swipe-button-delete + :right-on-press common/swipe-on-press-delete + :extra-fn extra-fn} child]) (defn- get-header-text-and-context - [community membership-status] - (let [community-name (:name community) - permissions (:permissions community) - open? (not= 3 (:access permissions)) - community-image (get-in community [:images :thumbnail :uri]) + [community-logo community-name community-permissions membership-status] + (let [open? (not= 3 (:access community-permissions)) community-context-tag [quo/context-tag {:type :community :size 24 :blur? true - :community-logo community-image + :community-logo community-logo :community-name community-name}]] (cond (= membership-status constants/activity-center-membership-status-idle) @@ -56,27 +53,33 @@ :t/joined-community :t/community-request-accepted-body-text) (when open? {:community community-name}))] - community-context-tag]} - - :else nil))) + community-context-tag]}))) (defn view - [{:keys [notification set-swipeable-height customization-color] :as props}] + [{:keys [notification extra-fn]}] (let [{:keys [community-id membership-status read - timestamp]} notification - community (rf/sub [:communities/community community-id]) - {:keys [header-text context]} (get-header-text-and-context community - membership-status)] - [swipeable props - [gesture/touchable-without-feedback - {:on-press (fn [] - (rf/dispatch [:navigate-back]) - (rf/dispatch [:communities/navigate-to-community-overview community-id]))} + timestamp]} notification + community-name (rf/sub [:communities/name community-id]) + community-logo (rf/sub [:communities/logo community-id]) + community-permissions (rf/sub [:communities/permissions community-id]) + customization-color (rf/sub [:profile/customization-color]) + {:keys [header-text + context]} (get-header-text-and-context community-logo + community-name + community-permissions + membership-status) + on-press (rn/use-callback + (fn [] + (rf/dispatch [:navigate-back]) + (rf/dispatch [:communities/navigate-to-community-overview + community-id])) + [community-id])] + [swipeable {:extra-fn extra-fn} + [gesture/touchable-without-feedback {:on-press on-press} [quo/activity-log {:title header-text :customization-color customization-color :icon :i/communities - :on-layout set-swipeable-height :timestamp (datetime/timestamp->relative timestamp) :unread? (not read) :context context}]]])) diff --git a/src/status_im/contexts/shell/activity_center/notification/contact_requests/view.cljs b/src/status_im/contexts/shell/activity_center/notification/contact_requests/view.cljs index ce50ca2c13..37a58f666b 100644 --- a/src/status_im/contexts/shell/activity_center/notification/contact_requests/view.cljs +++ b/src/status_im/contexts/shell/activity_center/notification/contact_requests/view.cljs @@ -1,6 +1,7 @@ (ns status-im.contexts.shell.activity-center.notification.contact-requests.view (:require [quo.core :as quo] + quo.theme [react-native.core :as rn] [status-im.constants :as constants] [status-im.contexts.shell.activity-center.notification.common.style :as common-style] @@ -24,48 +25,52 @@ :text (i18n/label :t/decline)}]) (defn- swipeable - [{:keys [active-swipeable extra-fn notification]} child] + [{:keys [extra-fn notification]} child] (let [{:keys [id author message]} notification {:keys [contact-request-state]} message {:keys [public-key]} (rf/sub [:multiaccount/contact]) - outgoing? (= public-key author)] + outgoing? (= public-key author) + accept (rn/use-callback + #(rf/dispatch [:activity-center.contact-requests/accept id]) + [id]) + decline (rn/use-callback + #(rf/dispatch [:activity-center.contact-requests/decline id]) + [id])] (cond (or (#{constants/contact-request-message-state-accepted constants/contact-request-message-state-declined} contact-request-state) (and outgoing? (= contact-request-state constants/contact-request-message-state-pending))) [common/swipeable - {:left-button common/swipe-button-read-or-unread - :left-on-press common/swipe-on-press-toggle-read - :right-button common/swipe-button-delete - :right-on-press common/swipe-on-press-delete - :active-swipeable active-swipeable - :extra-fn extra-fn} + {:left-button common/swipe-button-read-or-unread + :left-on-press common/swipe-on-press-toggle-read + :right-button common/swipe-button-delete + :right-on-press common/swipe-on-press-delete + :extra-fn extra-fn} child] (and (= contact-request-state constants/contact-request-message-state-pending) (not outgoing?)) [common/swipeable - {:left-button swipe-button-accept - :left-on-press #(rf/dispatch [:activity-center.contact-requests/accept id]) - :right-button swipe-button-decline - :right-on-press #(rf/dispatch [:activity-center.contact-requests/decline id]) - :active-swipeable active-swipeable - :extra-fn extra-fn} + {:left-button swipe-button-accept + :left-on-press accept + :right-button swipe-button-decline + :right-on-press decline + :extra-fn extra-fn} child] :else child))) (defn- outgoing-contact-request-view - [{:keys [notification set-swipeable-height customization-color]} theme] + [{:keys [notification]} theme] (let [{:keys [chat-id message last-message accepted]} notification - {:keys [contact-request-state] :as message} (or message last-message)] + {:keys [contact-request-state] :as message} (or message last-message) + customization-color (rf/sub [:profile/customization-color])] (if accepted [quo/activity-log {:title (i18n/label :t/contact-request-was-accepted) :customization-color customization-color - :on-layout set-swipeable-height :icon :i/add-user :timestamp (datetime/timestamp->relative (:timestamp notification)) :unread? (not (:read notification)) @@ -76,7 +81,6 @@ [quo/activity-log {:title (i18n/label :t/contact-request) :customization-color customization-color - :on-layout set-swipeable-height :icon :i/add-user :timestamp (datetime/timestamp->relative (:timestamp notification)) :unread? (not (:read notification)) @@ -102,13 +106,22 @@ nil)}]))) (defn- incoming-contact-request-view - [{:keys [notification set-swipeable-height customization-color]} theme] - (let [{:keys [id author message last-message]} notification - message (or message last-message)] + [{:keys [notification]} theme] + (let [{:keys [id author message + last-message]} notification + customization-color (rf/sub [:profile/customization-color]) + message (or message last-message) + accept (rn/use-callback + (fn [] + (rf/dispatch [:activity-center.contact-requests/accept id])) + [id]) + decline (rn/use-callback + (fn [] + (rf/dispatch [:activity-center.contact-requests/decline id])) + [id])] [quo/activity-log {:title (i18n/label :t/contact-request) :customization-color customization-color - :on-layout set-swipeable-height :icon :i/add-user :timestamp (datetime/timestamp->relative (:timestamp notification)) :unread? (not (:read notification)) @@ -140,13 +153,13 @@ :key :button-decline :label (i18n/label :t/decline) :accessibility-label :decline-contact-request - :on-press #(rf/dispatch [:activity-center.contact-requests/decline id])} + :on-press decline} {:type :button :subtype :positive :key :button-accept :label (i18n/label :t/accept) :accessibility-label :accept-contact-request - :on-press #(rf/dispatch [:activity-center.contact-requests/accept id])}] + :on-press accept}] nil)}])) @@ -155,16 +168,16 @@ (let [{:keys [author message last-message]} notification {:keys [public-key]} (rf/sub [:multiaccount/contact]) {:keys [contact-request-state]} (or message last-message) - app-theme (rf/sub [:theme])] + theme (quo.theme/use-theme)] [swipeable props (cond (= public-key author) - [outgoing-contact-request-view props app-theme] + [outgoing-contact-request-view props theme] (= contact-request-state constants/contact-request-message-state-accepted) [rn/pressable {:on-press #(rf/dispatch [:chat.ui/start-chat author])} - [incoming-contact-request-view props app-theme]] + [incoming-contact-request-view props theme]] :else - [incoming-contact-request-view props app-theme])])) + [incoming-contact-request-view props theme])])) diff --git a/src/status_im/contexts/shell/activity_center/notification/contact_verification/view.cljs b/src/status_im/contexts/shell/activity_center/notification/contact_verification/view.cljs index cb3a2827e9..9e6ab82d99 100644 --- a/src/status_im/contexts/shell/activity_center/notification/contact_verification/view.cljs +++ b/src/status_im/contexts/shell/activity_center/notification/contact_verification/view.cljs @@ -105,7 +105,7 @@ (rf/dispatch [:activity-center.notifications/mark-as-read id])) (defn- swipeable - [{:keys [active-swipeable extra-fn notification replying?] :as props} child] + [{:keys [extra-fn notification replying?] :as props} child] (let [{:keys [id message contact-verification-status]} notification challenger? (:outgoing message)] @@ -116,23 +116,21 @@ (and (not challenger?) (= contact-verification-status constants/contact-verification-status-pending)) [common/swipeable - {:left-button swipe-button-reply - :left-on-press #(prepare-challenge-reply props) - :right-button swipe-button-decline - :right-on-press #(decline-challenge id) - :active-swipeable active-swipeable - :extra-fn extra-fn} + {:left-button swipe-button-reply + :left-on-press #(prepare-challenge-reply props) + :right-button swipe-button-decline + :right-on-press #(decline-challenge id) + :extra-fn extra-fn} child] (and challenger? (= contact-verification-status constants/contact-verification-status-accepted)) [common/swipeable - {:left-button swipe-button-trust - :left-on-press #(mark-challenge-trusted id) - :right-button swipe-button-untrustworthy - :right-on-press #(mark-challenge-untrustworthy id) - :active-swipeable active-swipeable - :extra-fn extra-fn} + {:left-button swipe-button-trust + :left-on-press #(mark-challenge-trusted id) + :right-button swipe-button-untrustworthy + :right-on-press #(mark-challenge-untrustworthy id) + :extra-fn extra-fn} child] (#{constants/contact-verification-status-accepted @@ -140,12 +138,11 @@ constants/contact-verification-status-trusted} contact-verification-status) [common/swipeable - {:left-button common/swipe-button-read-or-unread - :left-on-press common/swipe-on-press-toggle-read - :right-button common/swipe-button-delete - :right-on-press common/swipe-on-press-delete - :active-swipeable active-swipeable - :extra-fn extra-fn} + {:left-button common/swipe-button-read-or-unread + :left-on-press common/swipe-on-press-toggle-read + :right-button common/swipe-button-delete + :right-on-press common/swipe-on-press-delete + :extra-fn extra-fn} child] :else @@ -154,8 +151,9 @@ (defn view [_] (let [reply (atom "")] - (fn [{:keys [notification set-swipeable-height replying? customization-color] :as props}] - (let [{:keys [id message + (fn [{:keys [notification replying?] :as props}] + (let [customization-color (rf/sub [:profile/customization-color]) + {:keys [id message contact-verification-status]} notification challenger? (:outgoing message)] ;; TODO(@ilmotta): Declined challenges should only be displayed for the challengee, not the @@ -165,86 +163,84 @@ (= contact-verification-status constants/contact-verification-status-declined)) [swipeable props [quo/activity-log - (merge - (when-not replying? - {:on-layout set-swipeable-height}) - {:title (i18n/label :t/identity-verification-request) - :customization-color customization-color - :icon :i/friend - :timestamp (datetime/timestamp->relative (:timestamp notification)) - :unread? (not (:read notification)) - :on-update-reply #(reset! reply %) - :replying? replying? - :max-reply-length max-reply-length - :valid-reply? valid-reply? - :context (context-tags challenger? notification) - :message (activity-message challenger? notification) - :items - (cond-> [] - (and challenger? - (= contact-verification-status constants/contact-verification-status-accepted)) - (concat - [{:type :button - :subtype :danger - :key :button-mark-as-untrustworthy - :label (i18n/label :t/untrustworthy) - :accessibility-label :mark-contact-verification-as-untrustworthy - :on-press #(mark-challenge-untrustworthy id)} - {:type :button - :subtype :positive - :key :button-accept - :label (i18n/label :t/accept) - :accessibility-label :mark-contact-verification-as-trusted - :on-press #(mark-challenge-trusted id)}]) + {:title (i18n/label :t/identity-verification-request) + :customization-color customization-color + :icon :i/friend + :timestamp (datetime/timestamp->relative (:timestamp notification)) + :unread? (not (:read notification)) + :on-update-reply #(reset! reply %) + :replying? replying? + :max-reply-length max-reply-length + :valid-reply? valid-reply? + :context (context-tags challenger? notification) + :message (activity-message challenger? notification) + :items + (cond-> [] + (and challenger? + (= contact-verification-status constants/contact-verification-status-accepted)) + (concat + [{:type :button + :subtype :danger + :key :button-mark-as-untrustworthy + :label (i18n/label :t/untrustworthy) + :accessibility-label :mark-contact-verification-as-untrustworthy + :on-press #(mark-challenge-untrustworthy id)} + {:type :button + :subtype :positive + :key :button-accept + :label (i18n/label :t/accept) + :accessibility-label :mark-contact-verification-as-trusted + :on-press #(mark-challenge-trusted id)}]) - (and challenger? - (= contact-verification-status constants/contact-verification-status-trusted)) - (concat [{:type :status - :subtype :positive - :key :status-trusted - :label (i18n/label :t/status-confirmed)}]) + (and challenger? + (= contact-verification-status constants/contact-verification-status-trusted)) + (concat [{:type :status + :subtype :positive + :key :status-trusted + :label (i18n/label :t/status-confirmed)}]) - (and challenger? - (= contact-verification-status constants/contact-verification-status-untrustworthy)) - (concat [{:type :status - :subtype :negative - :key :status-untrustworthy - :label (i18n/label :t/untrustworthy)}]) + (and challenger? + (= contact-verification-status + constants/contact-verification-status-untrustworthy)) + (concat [{:type :status + :subtype :negative + :key :status-untrustworthy + :label (i18n/label :t/untrustworthy)}]) - (and (not challenger?) - (= contact-verification-status constants/contact-verification-status-accepted)) - (concat [{:type :status - :subtype :positive - :key :status-accepted - :label (i18n/label :t/replied)}]) + (and (not challenger?) + (= contact-verification-status constants/contact-verification-status-accepted)) + (concat [{:type :status + :subtype :positive + :key :status-accepted + :label (i18n/label :t/replied)}]) - (and (not challenger?) - (= contact-verification-status constants/contact-verification-status-declined)) - (concat [{:type :status - :subtype :negative - :key :status-declined - :label (i18n/label :t/declined)}]) + (and (not challenger?) + (= contact-verification-status constants/contact-verification-status-declined)) + (concat [{:type :status + :subtype :negative + :key :status-declined + :label (i18n/label :t/declined)}]) - (and (not challenger?) - (= contact-verification-status constants/contact-verification-status-pending)) - (concat - [{:type :button - :subtype :danger - :key :button-decline - :label (i18n/label :t/decline) - :accessibility-label :decline-contact-verification - :on-press #(decline-challenge id)} - (if replying? - {:type :button - :subtype :primary - :key :button-reply - :label (i18n/label :t/send-reply) - :accessibility-label :reply-to-contact-verification - :disable-when invalid-reply? - :on-press #(send-challenge-reply id @reply)} - {:type :button - :subtype :primary - :key :button-send-reply - :label (i18n/label :t/message-reply) - :accessibility-label :send-reply-to-contact-verification - :on-press #(prepare-challenge-reply props)})]))})]]))))) + (and (not challenger?) + (= contact-verification-status constants/contact-verification-status-pending)) + (concat + [{:type :button + :subtype :danger + :key :button-decline + :label (i18n/label :t/decline) + :accessibility-label :decline-contact-verification + :on-press #(decline-challenge id)} + (if replying? + {:type :button + :subtype :primary + :key :button-reply + :label (i18n/label :t/send-reply) + :accessibility-label :reply-to-contact-verification + :disable-when invalid-reply? + :on-press #(send-challenge-reply id @reply)} + {:type :button + :subtype :primary + :key :button-send-reply + :label (i18n/label :t/message-reply) + :accessibility-label :send-reply-to-contact-verification + :on-press #(prepare-challenge-reply props)})]))}]]))))) diff --git a/src/status_im/contexts/shell/activity_center/notification/membership/view.cljs b/src/status_im/contexts/shell/activity_center/notification/membership/view.cljs index 11cd253bde..811fc02126 100644 --- a/src/status_im/contexts/shell/activity_center/notification/membership/view.cljs +++ b/src/status_im/contexts/shell/activity_center/notification/membership/view.cljs @@ -1,6 +1,7 @@ (ns status-im.contexts.shell.activity-center.notification.membership.view (:require [quo.core :as quo] + [react-native.core :as rn] [react-native.gesture :as gesture] [status-im.contexts.shell.activity-center.notification.common.style :as common-style] [status-im.contexts.shell.activity-center.notification.common.view :as common] @@ -33,35 +34,50 @@ :text (i18n/label :t/decline)}]) (defn- swipeable - [{:keys [active-swipeable notification extra-fn]} child] - (let [{:keys [accepted dismissed id]} notification] + [{:keys [notification extra-fn]} child] + (let [{:keys [accepted dismissed + id]} notification + accept (rn/use-callback + (fn [] (rf/dispatch [:activity-center.notifications/accept id])) + [id]) + dismiss (rn/use-callback + (fn [] (rf/dispatch [:activity-center.notifications/dismiss id])) + [id])] (if (or accepted dismissed) [common/swipeable - {:left-button common/swipe-button-read-or-unread - :left-on-press common/swipe-on-press-toggle-read - :right-button common/swipe-button-delete - :right-on-press common/swipe-on-press-delete - :active-swipeable active-swipeable - :extra-fn extra-fn} + {:left-button common/swipe-button-read-or-unread + :left-on-press common/swipe-on-press-toggle-read + :right-button common/swipe-button-delete + :right-on-press common/swipe-on-press-delete + :extra-fn extra-fn} child] [common/swipeable - {:left-button swipe-button-accept - :left-on-press #(rf/dispatch [:activity-center.notifications/accept id]) - :right-button swipe-button-decline - :right-on-press #(rf/dispatch [:activity-center.notifications/dismiss id]) - :active-swipeable active-swipeable - :extra-fn extra-fn} + {:left-button swipe-button-accept + :left-on-press accept + :right-button swipe-button-decline + :right-on-press dismiss + :extra-fn extra-fn} child]))) (defn view - [{:keys [notification set-swipeable-height customization-color] :as props}] - (let [{:keys [id accepted dismissed author read timestamp chat-name chat-id]} notification] + [{:keys [notification] :as props}] + (let [{:keys [id accepted dismissed author read + timestamp chat-name + chat-id]} notification + customization-color (rf/sub [:profile/customization-color]) + accept (rn/use-callback + (fn [] + (rf/dispatch [:activity-center.notifications/accept id])) + [id]) + dismiss (rn/use-callback + (fn [] + (rf/dispatch [:activity-center.notifications/dismiss id])) + [id])] [swipeable props [pressable {:accepted accepted :chat-id chat-id} [quo/activity-log {:title (i18n/label :t/added-to-group-chat) :customization-color customization-color - :on-layout set-swipeable-height :icon :i/add-user :timestamp (datetime/timestamp->relative timestamp) :unread? (not read) @@ -78,13 +94,10 @@ :key :button-accept :label (i18n/label :t/accept) :accessibility-label :accept-group-chat-invitation - :on-press #(rf/dispatch - [:activity-center.notifications/accept id])} + :on-press accept} {:type :button :subtype :danger :key :button-decline :label (i18n/label :t/decline) :accessibility-label :decline-group-chat-invitation - :on-press #(rf/dispatch - [:activity-center.notifications/dismiss - id])}])}]]])) + :on-press dismiss}])}]]])) diff --git a/src/status_im/contexts/shell/activity_center/notification/mentions/view.cljs b/src/status_im/contexts/shell/activity_center/notification/mentions/view.cljs index fe0300dc6c..0f2e7f89c6 100644 --- a/src/status_im/contexts/shell/activity_center/notification/mentions/view.cljs +++ b/src/status_im/contexts/shell/activity_center/notification/mentions/view.cljs @@ -30,25 +30,24 @@ parsed-text-children)))) (defn- swipeable - [{:keys [active-swipeable extra-fn]} child] + [{:keys [extra-fn]} child] [common/swipeable - {:left-button common/swipe-button-read-or-unread - :left-on-press common/swipe-on-press-toggle-read - :right-button common/swipe-button-delete - :right-on-press common/swipe-on-press-delete - :active-swipeable active-swipeable - :extra-fn extra-fn} + {:left-button common/swipe-button-read-or-unread + :left-on-press common/swipe-on-press-toggle-read + :right-button common/swipe-button-delete + :right-on-press common/swipe-on-press-delete + :extra-fn extra-fn} child]) (defn view - [{:keys [notification set-swipeable-height customization-color] :as props}] + [{:keys [notification extra-fn]}] (let [{:keys [author chat-name community-id chat-id message read timestamp]} notification community-chat? (not (string/blank? community-id)) - community (rf/sub [:communities/community community-id]) - community-name (:name community) - community-image (get-in community [:images :thumbnail :uri])] - [swipeable props + community-name (rf/sub [:communities/name community-id]) + community-logo (rf/sub [:communities/logo community-id]) + customization-color (rf/sub [:profile/customization-color])] + [swipeable {:extra-fn extra-fn} [gesture/touchable-without-feedback {:on-press (fn [] (rf/dispatch [:hide-popover]) @@ -56,7 +55,6 @@ [quo/activity-log {:title (i18n/label :t/mention) :customization-color customization-color - :on-layout set-swipeable-height :icon :i/mention :timestamp (datetime/timestamp->relative timestamp) :unread? (not read) @@ -68,7 +66,7 @@ {:type :channel :blur? true :size 24 - :community-logo community-image + :community-logo community-logo :community-name community-name :channel-name chat-name}] [quo/context-tag diff --git a/src/status_im/contexts/shell/activity_center/notification/reply/view.cljs b/src/status_im/contexts/shell/activity_center/notification/reply/view.cljs index f0bbc201b4..a319cbd564 100644 --- a/src/status_im/contexts/shell/activity_center/notification/reply/view.cljs +++ b/src/status_im/contexts/shell/activity_center/notification/reply/view.cljs @@ -51,26 +51,26 @@ nil)) (defn- swipeable - [{:keys [active-swipeable extra-fn]} child] + [{:keys [extra-fn]} child] [common/swipeable - {:left-button common/swipe-button-read-or-unread - :left-on-press common/swipe-on-press-toggle-read - :right-button common/swipe-button-delete - :right-on-press common/swipe-on-press-delete - :active-swipeable active-swipeable - :extra-fn extra-fn} + {:left-button common/swipe-button-read-or-unread + :left-on-press common/swipe-on-press-toggle-read + :right-button common/swipe-button-delete + :right-on-press common/swipe-on-press-delete + :extra-fn extra-fn} child]) (defn view - [{:keys [notification set-swipeable-height customization-color] :as props}] + [{:keys [notification extra-fn]}] (let [{:keys [author chat-name community-id chat-id - message read timestamp album-messages]} notification - community-chat? (not (string/blank? community-id)) - community (rf/sub [:communities/community community-id]) - community-name (:name community) - community-image (get-in community [:images :thumbnail :uri]) - media-server-port (rf/sub [:mediaserver/port])] - [swipeable props + message read timestamp + album-messages]} notification + community-chat? (not (string/blank? community-id)) + community-name (rf/sub [:communities/name community-id]) + community-logo (rf/sub [:communities/logo community-id]) + customization-color (rf/sub [:profile/customization-color]) + media-server-port (rf/sub [:mediaserver/port])] + [swipeable {:extra-fn extra-fn} [gesture/touchable-without-feedback {:on-press (fn [] (rf/dispatch [:hide-popover]) @@ -78,7 +78,6 @@ [quo/activity-log {:title (i18n/label :t/message-reply) :customization-color customization-color - :on-layout set-swipeable-height :icon :i/reply :timestamp (datetime/timestamp->relative timestamp) :unread? (not read) @@ -89,7 +88,7 @@ {:type :channel :blur? true :size 24 - :community-logo community-image + :community-logo community-logo :community-name community-name :channel-name chat-name}] [quo/context-tag @@ -113,10 +112,7 @@ (= (:content-type message) constants/content-type-gif) - :gif - - :else - nil) + :gif) :body (get-message-content message album-messages media-server-port)}}]]])) diff --git a/src/status_im/contexts/shell/activity_center/view.cljs b/src/status_im/contexts/shell/activity_center/view.cljs index ca7d3f7ef6..25f9ca6e8e 100644 --- a/src/status_im/contexts/shell/activity_center/view.cljs +++ b/src/status_im/contexts/shell/activity_center/view.cljs @@ -1,9 +1,10 @@ (ns status-im.contexts.shell.activity-center.view (:require - [oops.core :as oops] [quo.core :as quo] [react-native.core :as rn] [react-native.navigation :as navigation] + [status-im.constants :as constants] + [status-im.contexts.shell.activity-center.context :as ac.context] [status-im.contexts.shell.activity-center.header.view :as header] [status-im.contexts.shell.activity-center.notification-types :as types] [status-im.contexts.shell.activity-center.notification.admin.view :as admin] @@ -23,61 +24,68 @@ [utils.re-frame :as rf])) (defn notification-component + [{:keys [type] :as notification} index] + (let [extra-fn (rn/use-callback + (fn [] + {:notification notification}) + [notification]) + props {:notification notification + :extra-fn extra-fn}] + ;; Notifications are expensive to render. Without `delay-render` the opening + ;; animation of the Activity Center can be clunky and the time to open the + ;; AC after pressing the bell icon can be high. + [rn/view {:style (style/notification-container index)} + (cond + (= type types/contact-verification) + [contact-verification/view props] + + (= type types/contact-request) + [contact-requests/view props] + + (= type types/mention) + [mentions/view props] + + (= type types/reply) + [reply/view props] + + (= type types/admin) + [admin/view props] + + (some types/membership [type]) + (condp = type + types/private-group-chat [membership/view props] + types/community-request [community-request/view props] + types/community-kicked [community-kicked/view props] + nil))])) + +(defn- fetch-next-page [] - (let [height (atom 0) - set-swipeable-height #(reset! height (oops/oget % "nativeEvent.layout.height"))] - (fn [{:keys [type] :as notification} index _ {:keys [active-swipeable customization-color]}] - (let [props {:height height - :customization-color customization-color - :active-swipeable active-swipeable - :set-swipeable-height set-swipeable-height - :notification notification - :extra-fn (fn [] {:height @height :notification notification})}] - [rn/view {:style (style/notification-container index)} - (cond - (= type types/contact-verification) - [contact-verification/view props] - - (= type types/contact-request) - [contact-requests/view props] - - (= type types/mention) - [mentions/view props] - - (= type types/reply) - [reply/view props] - - (= type types/admin) - [admin/view props] - - (some types/membership [type]) - (condp = type - types/private-group-chat [membership/view props] - types/community-request [community-request/view props] - types/community-kicked [community-kicked/view props] - nil) - - :else - nil)])))) + (rf/dispatch [:activity-center.notifications/fetch-next-page])) (defn view [] - (let [active-swipeable (atom nil)] - (rf/dispatch [:activity-center.notifications/fetch-first-page]) - (fn [] - (let [notifications (rf/sub [:activity-center/notifications]) - customization-color (rf/sub [:profile/customization-color])] - [quo/overlay {:type :shell} - [rn/view {:flex 1 :padding-top (navigation/status-bar-height)} - [header/header] - [rn/flat-list - {:data notifications - :render-data {:active-swipeable active-swipeable - :customization-color customization-color} - :content-container-style {:flex-grow 1} - :empty-component [empty-tab/empty-tab] - :key-fn :id - :on-scroll-to-index-failed identity - :on-end-reached #(rf/dispatch [:activity-center.notifications/fetch-next-page]) - :render-fn notification-component}]] - ])))) + (let [notifications (rf/sub [:activity-center/notifications]) + + ;; We globally control the active swipeable for all notifications + ;; because when a swipe left/right gesture initiates, the previously + ;; active swiped notification (if any) must be removed & closed with + ;; animation. + active-swipeable (rn/use-ref-atom nil)] + (rn/use-mount + (fn [] + (rf/dispatch [:activity-center.notifications/fetch-first-page]))) + + [ac.context/provider {:active-swipeable active-swipeable} + [quo/overlay {:type :shell} + [rn/view {:style {:flex 1 :padding-top (navigation/status-bar-height)}} + [header/header] + (rn/delay-render + [rn/flat-list + {:data notifications + :initial-num-to-render constants/notifications-per-page + :content-container-style {:flex-grow 1} + :empty-component [empty-tab/empty-tab] + :key-fn :id + :on-scroll-to-index-failed identity + :on-end-reached fetch-next-page + :render-fn notification-component}])]]])) diff --git a/src/status_im/subs/communities.cljs b/src/status_im/subs/communities.cljs index 61e5dfb15b..5336cc418b 100644 --- a/src/status_im/subs/communities.cljs +++ b/src/status_im/subs/communities.cljs @@ -16,12 +16,33 @@ (fn [info [_ id]] (get info id))) +;; Do not use this subscription directly in views. There is a significant risk +;; of re-rendering views too frequently because an active community can change +;; for numerous reasons. (re-frame/reg-sub :communities/community :<- [:communities] (fn [communities [_ id]] (get communities id))) +(re-frame/reg-sub :communities/logo + (fn [[_ community-id]] + [(re-frame/subscribe [:communities/community community-id])]) + (fn [[community]] + (get-in community [:images :thumbnail :uri]))) + +(re-frame/reg-sub :communities/name + (fn [[_ community-id]] + [(re-frame/subscribe [:communities/community community-id])]) + (fn [[{:keys [name]}]] + name)) + +(re-frame/reg-sub :communities/permissions + (fn [[_ community-id]] + [(re-frame/subscribe [:communities/community community-id])]) + (fn [[{:keys [permissions]}]] + permissions)) + (re-frame/reg-sub :communities/community-color (fn [[_ community-id]] From 97b19c54401b9fffc003f0b79feff83e21f55851 Mon Sep 17 00:00:00 2001 From: Yevheniia Berdnyk Date: Wed, 17 Jul 2024 19:39:41 +0300 Subject: [PATCH 54/79] e2e: fixes for wallet and deep/universal links tests --- .../chats/test_public_chat_browsing.py | 1 + .../critical/test_deep_and_universal_links.py | 45 +++++++++++-------- test/appium/tests/critical/test_wallet.py | 18 +++++--- test/appium/views/wallet_view.py | 13 ++++++ 4 files changed, 53 insertions(+), 24 deletions(-) diff --git a/test/appium/tests/critical/chats/test_public_chat_browsing.py b/test/appium/tests/critical/chats/test_public_chat_browsing.py index 93b691a2b5..b852d56393 100644 --- a/test/appium/tests/critical/chats/test_public_chat_browsing.py +++ b/test/appium/tests/critical/chats/test_public_chat_browsing.py @@ -219,6 +219,7 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase): self.sign_in.show_profiles_button.wait_and_click() self.sign_in.element_by_text(self.username).click() self.sign_in.sign_in() + self.home.navigate_back_to_home_view() self.home.communities_tab.click() if self.home.element_by_text(waku_user.communities['admin_open']).is_element_displayed(30): self.errors.append("Community of previous user is shown!") diff --git a/test/appium/tests/critical/test_deep_and_universal_links.py b/test/appium/tests/critical/test_deep_and_universal_links.py index 1525e5405d..1f51fd1889 100644 --- a/test/appium/tests/critical/test_deep_and_universal_links.py +++ b/test/appium/tests/critical/test_deep_and_universal_links.py @@ -48,11 +48,11 @@ class TestDeepLinksOneDevice(MultipleSharedDeviceTestCase): self.profile_view.close_button.click() community_urls = { - "https://status.app/c/G0IAAGS9TbI9SrSPbDPplfBUeBBOIWhFRhGIKVlxntAg5CSvsJw4jurb636UKo7Lm2gTUS_M#zQ3shm7cS2MX6K1iinjNhiw88g1WH6fbE8rvnMHBq4RhznkWc": - "token-gated community e2e", - "https://status.app/c/G0UAAMTyNsn2QZDEG0EXftOl8pOEfwEBOOSA_YTfIk85xmADDgINGmxpUHAXzK36bN0fK42Xf4YD2yjPk1z2pbFwFw==#zQ3shZU9PNP6QHwEmqoh5mQqjCMDsfZYnpY4BEC8hom7KoNEz": + "https://status.app/c/ixyACjgKDVNOVCBjb21tdW5pdHkSHHJlcXVpcmUgMTAgU05UIEdvZXJsaSB0byB1c2UYASIHI2VhYjcwMAM=#zQ3shUeGnhM33QW4g9JfYfeLFAH9ZwbDboNYn5exCR7S3ii1y": + "SNT community", + "https://status.app/c/G0UAAMTyNsn2QZDEG0EXftOl8pOEfwEBOOSA_YTfIk85xmADDgINGmxpUHAXzK36bN0fK42Xf4YD2yjPk1z2pbFwFw==#zQ3sheoNX5kiuM393TJ6xDnL57aQoiwFWEuJnazJ6W2eNuh9u": "open community", - "https://status.app/c/G00AAGS9TbI9mSR-ZNmFrhRjNuEeXAAbcAIUaLLJyjMOG3ACJQ12oIHD78QhzO9s_T5bUeU7rnATWJg3mGgTUemrAg==#zQ3shTK1zXmLq2yZWMij65j1LNtmvuG6x71Nmg2fwtKs32wHj": + "https://status.app/c/G00AAGS9TbI9mSR-ZNmFrhRjNuEeXAAbcAIUaLLJyjMOG3ACJQ12oIHD78QhzO9s_T5bUeU7rnATWJg3mGgTUemrAg==#zQ3shp9f5M3uyMpwTi3rFpFrP6WCWmNsW9pgK9cjXVTaf2vgj": "closed community" } for url, text in community_urls.items(): @@ -60,11 +60,14 @@ class TestDeepLinksOneDevice(MultipleSharedDeviceTestCase): self.channel.chat_message_input.clear() self.channel.send_message(url) self.channel.chat_element_by_text(url).click_on_link_inside_message_body() - if not (self.community_view.join_button.is_element_displayed(10) - or self.community_view.join_community_button.is_element_displayed(5)) \ - or self.community_view.community_title.text != text: - self.errors.append("Community '%s' was not requested to join by the url %s" % (text, url)) - if text != "Closed community": # the last one + if text == 'SNT community': + if self.community_view.community_title.text != text: + self.errors.append("Community '%s' was not requested to join by the url %s" % (text, url)) + else: + if not self.community_view.join_button.is_element_displayed( + 10) or self.community_view.community_title.text != text: + self.errors.append("Community '%s' was not requested to join by the url %s" % (text, url)) + if text != "closed community": # the last one self.home.navigate_back_to_home_view() self.home.get_to_community_channel_from_home(self.community_name) @@ -95,22 +98,26 @@ class TestDeepLinksOneDevice(MultipleSharedDeviceTestCase): self.browser_view.click_system_back_button() community_links = { - "status.app://c/G0IAAGS9TbI9SrSPbDPplfBUeBBOIWhFRhGIKVlxntAg5CSvsJw4jurb636UKo7Lm2gTUS_M#zQ3shm7cS2MX6K1iinjNhiw88g1WH6fbE8rvnMHBq4RhznkWc": - "token-gated community e2e", - "status.app://c/G0UAAMTyNsn2QZDEG0EXftOl8pOEfwEBOOSA_YTfIk85xmADDgINGmxpUHAXzK36bN0fK42Xf4YD2yjPk1z2pbFwFw==#zQ3shZU9PNP6QHwEmqoh5mQqjCMDsfZYnpY4BEC8hom7KoNEz": + "status.app://c/ixyACjgKDVNOVCBjb21tdW5pdHkSHHJlcXVpcmUgMTAgU05UIEdvZXJsaSB0byB1c2UYASIHI2VhYjcwMAM=#zQ3shUeGnhM33QW4g9JfYfeLFAH9ZwbDboNYn5exCR7S3ii1y": + "SNT community", + "status.app://c/G0UAAMTyNsn2QZDEG0EXftOl8pOEfwEBOOSA_YTfIk85xmADDgINGmxpUHAXzK36bN0fK42Xf4YD2yjPk1z2pbFwFw==#zQ3sheoNX5kiuM393TJ6xDnL57aQoiwFWEuJnazJ6W2eNuh9u": "open community", - "status.app://c/G00AAGS9TbI9mSR-ZNmFrhRjNuEeXAAbcAIUaLLJyjMOG3ACJQ12oIHD78QhzO9s_T5bUeU7rnATWJg3mGgTUemrAg==#zQ3shTK1zXmLq2yZWMij65j1LNtmvuG6x71Nmg2fwtKs32wHj": + "status.app://c/G00AAGS9TbI9mSR-ZNmFrhRjNuEeXAAbcAIUaLLJyjMOG3ACJQ12oIHD78QhzO9s_T5bUeU7rnATWJg3mGgTUemrAg==#zQ3shp9f5M3uyMpwTi3rFpFrP6WCWmNsW9pgK9cjXVTaf2vgj": "closed community" } for link, text in community_links.items(): self.channel.just_fyi("Opening community '%s' by the link %s" % (text, link)) self.browser_view.open_url(link) - if not (self.community_view.join_button.is_element_displayed(10) - or self.community_view.join_community_button.is_element_displayed(5)) \ - or self.community_view.community_title.text != text: - self.errors.append("Community '%s' was not requested to join by the deep link %s" % (text, link)) - self.home.navigate_back_to_home_view() - self.home.browser_tab.click() + if text == 'SNT community': + if self.community_view.community_title.text != text: + self.errors.append("Community '%s' was not requested to join by the deep link %s" % (text, link)) + else: + if not self.community_view.join_button.is_element_displayed( + 10) or self.community_view.community_title.text != text: + self.errors.append("Community '%s' was not requested to join by the deep link %s" % (text, link)) + if text != "closed community": # the last one + self.home.navigate_back_to_home_view() + self.home.browser_tab.click() self.errors.verify_no_errors() diff --git a/test/appium/tests/critical/test_wallet.py b/test/appium/tests/critical/test_wallet.py index 6718838308..195583f15f 100644 --- a/test/appium/tests/critical/test_wallet.py +++ b/test/appium/tests/critical/test_wallet.py @@ -33,16 +33,16 @@ class TestWalletMultipleDevice(MultipleSharedDeviceTestCase): 'username': self.receiver_username})))) self.home_1, self.home_2 = self.sign_in_1.get_home_view(), self.sign_in_2.get_home_view() self.wallet_1, self.wallet_2 = self.sign_in_1.get_wallet_view(), self.sign_in_2.get_wallet_view() + self.wallet_1.wallet_tab.click() + self.wallet_2.wallet_tab.click() def _get_balances_before_tx(self): sender_balance = self.network_api.get_balance(self.sender['wallet_address']) receiver_balance = self.network_api.get_balance(self.receiver['wallet_address']) self.wallet_1.just_fyi("Getting ETH amount in the wallet of the sender before transaction") - self.wallet_1.wallet_tab.click() self.wallet_1.get_account_element().click() eth_amount_sender = self.wallet_1.get_asset(asset_name='Ether').get_amount() self.wallet_2.just_fyi("Getting ETH amount in the wallet of the receiver before transaction") - self.wallet_2.wallet_tab.click() self.wallet_2.get_account_element().click() eth_amount_receiver = self.wallet_2.get_asset(asset_name='Ether').get_amount() return sender_balance, receiver_balance, eth_amount_sender, eth_amount_receiver @@ -68,7 +68,6 @@ class TestWalletMultipleDevice(MultipleSharedDeviceTestCase): exp_amount = round(initial_eth_amount + amount_to_send, 4) # for _ in range(12): # ToDo: 120 sec wait time, enable when autoupdate feature is ready - wallet_view.wallet_tab.wait_and_click() new_eth_amount = round(wallet_view.get_asset(asset_name='Ether').get_amount(), 4) if user_name == 'sender' and new_eth_amount <= exp_amount: return @@ -87,6 +86,10 @@ class TestWalletMultipleDevice(MultipleSharedDeviceTestCase): self.loop.run_until_complete( run_in_parallel(((self.home_1.reopen_app,), (self.home_2.reopen_app,)))) + self.wallet_1.wallet_tab.wait_and_click() + self.wallet_2.wallet_tab.wait_and_click() + self.wallet_1.select_network(network_name='Arbitrum') + self.wallet_2.select_network(network_name='Arbitrum') self.loop.run_until_complete( run_in_parallel(((wait_for_wallet_balance_to_update, {'wallet_view': self.wallet_1, 'user_name': self.sender_username, @@ -123,6 +126,8 @@ class TestWalletMultipleDevice(MultipleSharedDeviceTestCase): @marks.testrail_id(727229) def test_wallet_send_eth(self): + self.wallet_1.select_network(network_name='Arbitrum') + self.wallet_2.select_network(network_name='Arbitrum') sender_balance, receiver_balance, eth_amount_sender, eth_amount_receiver = self._get_balances_before_tx() self.wallet_2.close_account_button.click() @@ -130,7 +135,9 @@ class TestWalletMultipleDevice(MultipleSharedDeviceTestCase): self.wallet_1.just_fyi("Sending funds from wallet") amount_to_send = 0.0001 - self.wallet_1.send_asset(address=self.receiver['wallet_address'], asset_name='Ether', amount=amount_to_send) + self.wallet_1.send_asset(address='arb1:' + self.receiver['wallet_address'], + asset_name='Ether', + amount=amount_to_send) device_time = self.wallet_1.driver.device_time @@ -156,7 +163,8 @@ class TestWalletMultipleDevice(MultipleSharedDeviceTestCase): self.wallet_1.just_fyi("Sending asset from drawer") amount_to_send = 0.0001 - self.wallet_1.send_asset_from_drawer(address=self.receiver['wallet_address'], asset_name='Ether', + self.wallet_1.send_asset_from_drawer(address='arb1:' + self.receiver['wallet_address'], + asset_name='Ether', amount=amount_to_send) device_time = self.wallet_1.driver.device_time diff --git a/test/appium/views/wallet_view.py b/test/appium/views/wallet_view.py index 8bbb425d26..0865b3c7dd 100644 --- a/test/appium/views/wallet_view.py +++ b/test/appium/views/wallet_view.py @@ -92,9 +92,16 @@ class WalletView(BaseView): # Sending transaction self.address_text_input = EditBox(self.driver, accessibility_id='address-text-input') self.amount_input = EditBox(self.driver, xpath="//android.widget.EditText") + self.from_network_text = Text( + self.driver, xpath="(//*[@content-desc='loading']/following-sibling::android.widget.TextView)[1]") self.confirm_button = Button(self.driver, accessibility_id='button-one') self.done_button = Button(self.driver, accessibility_id='done') + def select_network(self, network_name: str): + self.network_drop_down.click() + Button(self.driver, accessibility_id="%s, label-component" % network_name.capitalize()).click() + self.network_drop_down.click() + def get_account_element(self, account_name: str = 'Account 1'): return Button(self.driver, xpath="//android.view.ViewGroup[contains(@content-desc,'%s')]" % account_name) @@ -120,12 +127,17 @@ class WalletView(BaseView): for i in '{:f}'.format(amount).rstrip('0'): Button(self.driver, accessibility_id='keyboard-key-%s' % i).click() + def disable_mainnet_in_from_network(self): + if self.from_network_text.text == 'Mainnet': + self.from_network_text.click() + def send_asset(self, address: str, asset_name: str, amount: float): self.send_button.click() self.address_text_input.send_keys(address) self.continue_button.click() self.select_asset(asset_name).click() self.set_amount(amount) + self.disable_mainnet_in_from_network() self.confirm_transaction() def send_asset_from_drawer(self, address: str, asset_name: str, amount: float): @@ -135,6 +147,7 @@ class WalletView(BaseView): self.address_text_input.send_keys(address) self.continue_button.click() self.set_amount(amount) + self.disable_mainnet_in_from_network() self.confirm_transaction() def add_regular_account(self, account_name: str): From 2b0847ef76899ef3a3aeb948c2389c6e7c44104d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vit=E2=88=80ly=20Vlasov?= Date: Thu, 18 Jul 2024 15:36:38 +0300 Subject: [PATCH 55/79] fix_: ui part for share-all-future-addresses storage (PR #20549) (#20549) Related status-go PR: https://github.com/status-im/status-go/pull/5354 https://github.com/status-im/status-go/compare/6e056348...8458cafe Signed-off-by: Vitaly Vlasov --- .../actions/accounts_selection/effects.cljs | 23 ++++--- .../actions/accounts_selection/events.cljs | 64 ++++++++++--------- .../accounts_selection/events_test.cljs | 35 +++++----- .../addresses_for_permissions/view.cljs | 7 +- .../contexts/communities/events.cljs | 32 ++++------ .../contexts/communities/events_test.cljs | 4 +- .../contexts/communities/overview/events.cljs | 33 +++++----- .../communities/overview/events_test.cljs | 31 +++++---- status-go-version.json | 6 +- 9 files changed, 126 insertions(+), 109 deletions(-) diff --git a/src/status_im/contexts/communities/actions/accounts_selection/effects.cljs b/src/status_im/contexts/communities/actions/accounts_selection/effects.cljs index 55242b30a7..20e51630ba 100644 --- a/src/status_im/contexts/communities/actions/accounts_selection/effects.cljs +++ b/src/status_im/contexts/communities/actions/accounts_selection/effects.cljs @@ -27,7 +27,7 @@ :on-error #(p-reject (str "failed to sign data\n" %))})))) (defn- edit-shared-addresses-for-community - [community-id signatures addresses-to-reveal airdrop-address] + [community-id signatures addresses-to-reveal airdrop-address _share-future-addresses?] (promesa/create (fn [p-resolve p-reject] (rpc/call @@ -41,15 +41,16 @@ :on-error p-reject})))) (defn- request-to-join - [community-id signatures addresses-to-reveal airdrop-address] + [community-id signatures addresses-to-reveal airdrop-address share-future-addresses?] (promesa/create (fn [p-resolve p-reject] (rpc/call {:method :wakuext_requestToJoinCommunity - :params [{:communityId community-id - :signatures signatures - :addressesToReveal addresses-to-reveal - :airdropAddress airdrop-address}] + :params [{:communityId community-id + :signatures signatures + :addressesToReveal addresses-to-reveal + :airdropAddress airdrop-address + :shareFutureAddresses share-future-addresses?}] :js-response true :on-success p-resolve :on-error p-reject})))) @@ -64,15 +65,18 @@ (defn- sign-and-call-endpoint [{:keys [community-id password pub-key - addresses-to-reveal airdrop-address + addresses-to-reveal airdrop-address share-future-addresses? on-success on-error callback]}] - (-> (promesa/let [sign-params (generate-requests-for-signing pub-key community-id addresses-to-reveal) + (-> (promesa/let [sign-params (generate-requests-for-signing pub-key + community-id + addresses-to-reveal) signatures (sign-data sign-params password) result (callback community-id signatures addresses-to-reveal - airdrop-address)] + airdrop-address + share-future-addresses?)] (run-callback-or-event on-success result)) (promesa/catch #(run-callback-or-event on-error %)))) @@ -87,6 +91,7 @@ [:or [:set string?] [:sequential string?]]] [:airdrop-address string?] + [:share-future-addresses? boolean?] [:on-success [:or fn? :schema.re-frame/event]] [:on-error [:or fn? :schema.re-frame/event]] [:callback fn?]]] diff --git a/src/status_im/contexts/communities/actions/accounts_selection/events.cljs b/src/status_im/contexts/communities/actions/accounts_selection/events.cljs index 3a0847df51..33a26ef04f 100644 --- a/src/status_im/contexts/communities/actions/accounts_selection/events.cljs +++ b/src/status_im/contexts/communities/actions/accounts_selection/events.cljs @@ -17,13 +17,12 @@ (rf/reg-event-fx :communities/initialize-permission-addresses initialize-permission-addresses) (defn do-init-permission-addresses - [{:keys [db]} [community-id revealed-accounts]] + [{:keys [db]} [community-id revealed-accounts share-future-addresses?]] (let [wallet-accounts (utils/sorted-operable-non-watch-only-accounts db) addresses-to-reveal (if (seq revealed-accounts) (set (keys revealed-accounts)) ;; Reveal all addresses as fallback. (set (map :address wallet-accounts))) - ;; When there are no revealed addresses, such as when joining a ;; community, use first address for airdrops. airdrop-address (or (->> revealed-accounts @@ -35,10 +34,7 @@ first :address))] {:db (-> db - ;; Set to false by default while we don't persist the user's choice - ;; in status-go, otherwise whenever the view is mounted, the choice - ;; of selected addresses won't be respected. - (assoc-in [:communities/selected-share-all-addresses community-id] false) + (assoc-in [:communities/selected-share-all-addresses community-id] share-future-addresses?) (assoc-in [:communities/all-addresses-to-reveal community-id] addresses-to-reveal) (assoc-in [:communities/all-airdrop-addresses community-id] airdrop-address)) :fx [[:dispatch @@ -60,36 +56,44 @@ is selecting an airdrop address, we must submit to status-go the current choice of addresses to share, and vice-versa. If we omit addresses to share, status-go will default to all available." - [{:keys [db]} [{:keys [community-id password on-success addresses airdrop-address]}]] - (let [pub-key (get-in db [:profile/profile :public-key]) - wallet-accounts (utils/sorted-operable-non-watch-only-accounts db) - addresses-to-reveal (if (seq addresses) - (set addresses) - (get-in db [:communities/all-addresses-to-reveal community-id])) - new-airdrop-address (if (contains? addresses-to-reveal airdrop-address) - airdrop-address - (->> wallet-accounts - (filter #(contains? addresses-to-reveal (:address %))) - first - :address))] + [{:keys [db]} + [{:keys [community-id password on-success addresses airdrop-address share-future-addresses?]}]] + (let [pub-key (get-in db [:profile/profile :public-key]) + wallet-accounts (utils/sorted-operable-non-watch-only-accounts db) + addresses-to-reveal (if (seq addresses) + (set addresses) + (get-in db [:communities/all-addresses-to-reveal community-id])) + new-airdrop-address (if (contains? addresses-to-reveal airdrop-address) + airdrop-address + (->> wallet-accounts + (filter #(contains? addresses-to-reveal (:address %))) + first + :address)) + share-future-addresses? (if (nil? share-future-addresses?) + (get-in db [:communities/selected-share-all-addresses community-id]) + share-future-addresses?)] {:fx [[:effects.community/edit-shared-addresses - {:community-id community-id - :password password - :pub-key pub-key - :addresses-to-reveal addresses-to-reveal - :airdrop-address new-airdrop-address - :on-success (fn [] - (when (fn? on-success) - (on-success addresses-to-reveal new-airdrop-address)) - (rf/dispatch [:communities/edit-shared-addresses-success - community-id addresses-to-reveal airdrop-address])) - :on-error [:communities/edit-shared-addresses-failure community-id]}]]})) + {:community-id community-id + :password password + :pub-key pub-key + :addresses-to-reveal addresses-to-reveal + :share-future-addresses? share-future-addresses? + :airdrop-address new-airdrop-address + :on-success (fn [] + (when (fn? on-success) + (on-success addresses-to-reveal + new-airdrop-address + share-future-addresses?)) + (rf/dispatch [:communities/edit-shared-addresses-success + community-id addresses-to-reveal airdrop-address])) + :on-error [:communities/edit-shared-addresses-failure community-id]}]]})) (rf/reg-event-fx :communities/edit-shared-addresses edit-shared-addresses) (rf/reg-event-fx :communities/edit-shared-addresses-success - (fn [_ [community-id addresses-to-reveal airdrop-address]] + (fn [_ [community-id addresses-to-reveal airdrop-address share-future-addresses?]] {:fx [[:dispatch [:communities/set-airdrop-address community-id airdrop-address]] + [:dispatch [:communities/set-share-all-addresses community-id share-future-addresses?]] [:dispatch [:communities/set-addresses-to-reveal community-id addresses-to-reveal]]]})) (rf/reg-event-fx :communities/edit-shared-addresses-failure diff --git a/src/status_im/contexts/communities/actions/accounts_selection/events_test.cljs b/src/status_im/contexts/communities/actions/accounts_selection/events_test.cljs index 81a6ff06e5..8174c4930f 100644 --- a/src/status_im/contexts/communities/actions/accounts_selection/events_test.cljs +++ b/src/status_im/contexts/communities/actions/accounts_selection/events_test.cljs @@ -70,7 +70,7 @@ (is (match? {:db (-> (:db cofx) - (assoc-in [:communities/selected-share-all-addresses community-id] false) + (assoc-in [:communities/selected-share-all-addresses community-id] true) (assoc-in [:communities/all-addresses-to-reveal community-id] addresses-to-reveal) (assoc-in [:communities/all-airdrop-addresses community-id] airdrop-address)) :fx [[:dispatch @@ -82,7 +82,7 @@ [:dispatch [:communities/check-permissions-to-join-during-selection community-id addresses-to-reveal]]]} - (sut/do-init-permission-addresses cofx [community-id revealed-accounts]))))) + (sut/do-init-permission-addresses cofx [community-id revealed-accounts true]))))) ;; Expect to mark all addresses to be revealed and first one to receive ;; airdrops when no addresses were previously revealed. @@ -93,7 +93,7 @@ (is (match? {:db (-> (:db cofx) - (assoc-in [:communities/selected-share-all-addresses community-id] false) + (assoc-in [:communities/selected-share-all-addresses community-id] true) (assoc-in [:communities/all-addresses-to-reveal community-id] addresses-to-reveal)) :fx [[:dispatch [:communities/check-permissions-to-join-community @@ -101,7 +101,7 @@ [:dispatch [:communities/check-permissions-to-join-during-selection community-id addresses-to-reveal]]]} - (sut/do-init-permission-addresses cofx [community-id revealed-accounts])))))) + (sut/do-init-permission-addresses cofx [community-id revealed-accounts true])))))) (deftest edit-shared-addresses-test (testing @@ -109,6 +109,7 @@ fallback to all wallet addresses" (let [pub-key "abcdef" revealed-addresses #{"0xB" "0xC"} + share-future-addresses? true cofx {:db {:profile/profile {:public-key pub-key} :communities/all-addresses-to-reveal {community-id revealed-addresses}}} airdrop-address "0xB" @@ -116,22 +117,24 @@ actual (sut/edit-shared-addresses cofx - [{:community-id community-id - :password password - :airdrop-address airdrop-address - :on-success (fn [new-addresses-to-reveal] - (is (match? revealed-addresses new-addresses-to-reveal)))}]) + [{:community-id community-id + :password password + :airdrop-address airdrop-address + :share-future-addresses? share-future-addresses? + :on-success (fn [new-addresses-to-reveal] + (is (match? revealed-addresses new-addresses-to-reveal)))}]) on-success-wrapper (-> actual :fx first second :on-success)] (is (match? {:fx [[:effects.community/edit-shared-addresses - {:community-id community-id - :password password - :pub-key pub-key - :addresses-to-reveal revealed-addresses - :airdrop-address airdrop-address - :on-success fn? - :on-error [:communities/edit-shared-addresses-failure community-id]}]]} + {:community-id community-id + :password password + :pub-key pub-key + :addresses-to-reveal revealed-addresses + :share-future-addresses? share-future-addresses? + :airdrop-address airdrop-address + :on-success fn? + :on-error [:communities/edit-shared-addresses-failure community-id]}]]} actual)) (on-success-wrapper))) diff --git a/src/status_im/contexts/communities/actions/addresses_for_permissions/view.cljs b/src/status_im/contexts/communities/actions/addresses_for_permissions/view.cljs index b37337966d..591c89233c 100644 --- a/src/status_im/contexts/communities/actions/addresses_for_permissions/view.cljs +++ b/src/status_im/contexts/communities/actions/addresses_for_permissions/view.cljs @@ -203,7 +203,9 @@ :addresses-for-permissions]) (rf/dispatch [:hide-bottom-sheet]))}]))}]) (rf/dispatch [:communities/set-share-all-addresses id flag-share-all-addresses])) - (rf/dispatch [:communities/set-addresses-to-reveal id addresses-to-reveal]))) + (do + (rf/dispatch [:communities/set-share-all-addresses id flag-share-all-addresses]) + (rf/dispatch [:communities/set-addresses-to-reveal id addresses-to-reveal])))) highest-role (rf/sub [:communities/highest-role-for-selection id]) [unmodified-role _] (rn/use-state highest-role)] @@ -261,6 +263,7 @@ can-edit-addresses? (rf/sub [:communities/can-edit-shared-addresses? id]) wallet-accounts (rf/sub [:wallet/operable-accounts-without-watched-accounts]) + joined (rf/sub [:communities/community-joined id]) unmodified-addresses-to-reveal (rf/sub [:communities/addresses-to-reveal id]) [addresses-to-reveal set-addresses-to-reveal] (rn/use-state unmodified-addresses-to-reveal) @@ -285,7 +288,6 @@ (set-flag-share-all-addresses new-value) (when new-value (set-addresses-to-reveal (set (map :address wallet-accounts))))))] - (rn/use-mount (fn [] (when-not flag-share-all-addresses @@ -310,6 +312,7 @@ flag-share-all-addresses] :header [quo/page-setting {:checked? flag-share-all-addresses + :disabled? joined :customization-color color :on-change toggle-flag-share-all-addresses :setting-text (i18n/label diff --git a/src/status_im/contexts/communities/events.cljs b/src/status_im/contexts/communities/events.cljs index 139d4debf0..07414ab4b2 100644 --- a/src/status_im/contexts/communities/events.cljs +++ b/src/status_im/contexts/communities/events.cljs @@ -16,6 +16,7 @@ [status-im.navigation.events :as navigation] [status-im.navigation.transitions :as transitions] [taoensso.timbre :as log] + [utils.collection :as collection-utils] [utils.re-frame :as rf])) (defn handle-community @@ -372,15 +373,14 @@ (when (and community joined (not fetching-revealed-accounts)) {:db (assoc-in db [:communities community-id :fetching-revealed-accounts] true) :json-rpc/call - [{:method "wakuext_getRevealedAccounts" - :params [community-id (get-in db [:profile/profile :public-key])] - :js-response true - :on-success [:communities/get-revealed-accounts-success community-id on-success] - :on-error (fn [err] - (log/error {:message "failed to fetch revealed accounts" - :community-id community-id - :err err}) - (rf/dispatch [:communities/get-revealed-accounts-failed community-id]))}]}))) + [{:method "wakuext_latestRequestToJoinForCommunity" + :params [community-id] + :on-success [:communities/get-revealed-accounts-success community-id on-success] + :on-error (fn [err] + (log/error {:message "failed to fetch revealed accounts" + :community-id community-id + :err err}) + (rf/dispatch [:communities/get-revealed-accounts-failed community-id]))}]}))) (rf/reg-event-fx :communities/get-revealed-accounts get-revealed-accounts) @@ -399,22 +399,18 @@ [:json-rpc/call :schema.common/rpc-call]]]]) (rf/reg-event-fx :communities/get-revealed-accounts-success - (fn [{:keys [db]} [community-id on-success revealed-accounts-js]] + (fn [{:keys [db]} [community-id on-success request-to-join]] (when-let [community (get-in db [:communities community-id])] - (let [revealed-accounts - (reduce - (fn [acc {:keys [address] :as revealed-account}] - (assoc acc address revealed-account)) - {} - (data-store.communities/<-revealed-accounts-rpc revealed-accounts-js)) - + (let [revealed-accounts (collection-utils/index-by :address (:revealedAccounts request-to-join)) + share-future-addresses? (:shareFutureAddresses request-to-join) community-with-revealed-accounts (-> community (assoc :revealed-accounts revealed-accounts) + (assoc :share-future-addresses? share-future-addresses?) (dissoc :fetching-revealed-accounts))] {:db (assoc-in db [:communities community-id] community-with-revealed-accounts) :fx [(when (vector? on-success) - [:dispatch (conj on-success revealed-accounts)])]})))) + [:dispatch (conj on-success revealed-accounts share-future-addresses?)])]})))) (rf/reg-event-fx :communities/get-revealed-accounts-failed (fn [{:keys [db]} [community-id]] diff --git a/src/status_im/contexts/communities/events_test.cljs b/src/status_im/contexts/communities/events_test.cljs index 0d2d55ac2e..4fb45e2435 100644 --- a/src/status_im/contexts/communities/events_test.cljs +++ b/src/status_im/contexts/communities/events_test.cljs @@ -146,8 +146,8 @@ effects (events/get-revealed-accounts {:db db} [community-id])] (is (match? (assoc-in db [:communities community-id :fetching-revealed-accounts] true) (:db effects))) - (is (match? {:method "wakuext_getRevealedAccounts" - :params [community-id "profile-public-key"]} + (is (match? {:method "wakuext_latestRequestToJoinForCommunity" + :params [community-id]} (-> effects :json-rpc/call first (select-keys [:method :params])))))))) (deftest handle-community-test diff --git a/src/status_im/contexts/communities/overview/events.cljs b/src/status_im/contexts/communities/overview/events.cljs index d9f69b30f3..37f1a5a174 100644 --- a/src/status_im/contexts/communities/overview/events.cljs +++ b/src/status_im/contexts/communities/overview/events.cljs @@ -117,13 +117,14 @@ {:community community-name})}]]]}))) (defn request-to-join-with-signatures - [_ [community-id addresses-to-reveal signatures]] + [_ [community-id addresses-to-reveal signatures share-future-addresses?]] {:fx [[:json-rpc/call [{:method "wakuext_requestToJoinCommunity" - :params [{:communityId community-id - :signatures signatures - :addressesToReveal addresses-to-reveal - :airdropAddress (first addresses-to-reveal)}] + :params [{:communityId community-id + :signatures signatures + :addressesToReveal addresses-to-reveal + :shareFutureAddresses share-future-addresses? + :airdropAddress (first addresses-to-reveal)}] :js-response true :on-success [:communities/requested-to-join] :on-error [:communities/requested-to-join-error community-id]}]]]}) @@ -153,16 +154,18 @@ (defn request-to-join-with-addresses [{:keys [db]} [{:keys [community-id password]}]] - (let [pub-key (get-in db [:profile/profile :public-key]) - addresses-to-reveal (get-in db [:communities/all-addresses-to-reveal community-id]) - airdrop-address (get-in db [:communities/all-airdrop-addresses community-id])] + (let [pub-key (get-in db [:profile/profile :public-key]) + addresses-to-reveal (get-in db [:communities/all-addresses-to-reveal community-id]) + share-future-addresses? (get-in db [:communities/selected-share-all-addresses community-id]) + airdrop-address (get-in db [:communities/all-airdrop-addresses community-id])] {:fx [[:effects.community/request-to-join - {:community-id community-id - :password password - :pub-key pub-key - :addresses-to-reveal addresses-to-reveal - :airdrop-address airdrop-address - :on-success [:communities/requested-to-join] - :on-error [:communities/requested-to-join-error community-id]}]]})) + {:community-id community-id + :password password + :pub-key pub-key + :addresses-to-reveal addresses-to-reveal + :airdrop-address airdrop-address + :share-future-addresses? share-future-addresses? + :on-success [:communities/requested-to-join] + :on-error [:communities/requested-to-join-error community-id]}]]})) (rf/reg-event-fx :communities/request-to-join-with-addresses request-to-join-with-addresses) diff --git a/src/status_im/contexts/communities/overview/events_test.cljs b/src/status_im/contexts/communities/overview/events_test.cljs index 2be10a1dda..17be7ec3d5 100644 --- a/src/status_im/contexts/communities/overview/events_test.cljs +++ b/src/status_im/contexts/communities/overview/events_test.cljs @@ -37,19 +37,22 @@ (sut/sign-data cofx [community-id password sign-params]))))) (deftest request-to-join-with-signatures-test - (let [cofx {:db {}} - addresses-to-reveal [account-pub-key "0x2"] - signatures ["11111" "222222"] - expected {:fx [[:json-rpc/call - [{:method "wakuext_requestToJoinCommunity" - :params [{:communityId community-id - :signatures signatures - :addressesToReveal addresses-to-reveal - :airdropAddress "0x1"}] - :js-response true - :on-success [:communities/requested-to-join] - :on-error [:communities/requested-to-join-error - community-id]}]]]}] + (let [cofx {:db {}} + addresses-to-reveal [account-pub-key "0x2"] + share-future-addresses? true + signatures ["11111" "222222"] + expected {:fx [[:json-rpc/call + [{:method "wakuext_requestToJoinCommunity" + :params [{:communityId community-id + :signatures signatures + :addressesToReveal addresses-to-reveal + :shareFutureAddresses share-future-addresses? + :airdropAddress "0x1"}] + :js-response true + :on-success [:communities/requested-to-join] + :on-error [:communities/requested-to-join-error + community-id]}]]]}] (is (match? expected (sut/request-to-join-with-signatures cofx - [community-id addresses-to-reveal signatures]))))) + [community-id addresses-to-reveal signatures + share-future-addresses?]))))) diff --git a/status-go-version.json b/status-go-version.json index 1226d81ffe..1c8e4ea8b6 100644 --- a/status-go-version.json +++ b/status-go-version.json @@ -3,7 +3,7 @@ "_comment": "Instead use: scripts/update-status-go.sh ", "owner": "status-im", "repo": "status-go", - "version": "v0.182.35", - "commit-sha1": "484b8aca1a12718c9b3940f8a398e60ee4419600", - "src-sha256": "1xnpx86k54lnsw93xw6la0sggd9y0nm7hn70cm0qgmq2qg8pzb3a" + "version": "v0.182.36", + "commit-sha1": "8458cafef9f876c11a58dc9ede14fc58a5a0f968", + "src-sha256": "0nkb0zpqgpklh8mlrgcglh5v32qlfly9hd7ia3a1icx5kdp2q2wp" } From 9f84a7b912bbdb6823498a2d069bc2ba1e3ed41a Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 18 Jul 2024 14:57:53 +0200 Subject: [PATCH 56/79] A pack of bugfixes for the user story "Connect to dApps" (#20711) --- .../drawers/bottom_actions/style.cljs | 14 +- .../drawers/bottom_actions/view.cljs | 7 +- src/status_im/constants.cljs | 1 + .../contexts/shell/qr_reader/view.cljs | 15 +- .../contexts/wallet/connected_dapps/view.cljs | 53 +++---- .../contexts/wallet/wallet_connect/core.cljs | 28 +++- .../wallet/wallet_connect/effects.cljs | 21 +-- .../wallet/wallet_connect/events.cljs | 140 ++++++++++++------ .../modals/common/header/view.cljs | 4 +- .../wallet_connect/responding_events.cljs | 2 +- .../session_proposal/style.cljs | 22 +-- .../wallet_connect/session_proposal/view.cljs | 64 ++++---- .../contexts/wallet/wallet_connect/utils.cljs | 8 +- src/status_im/subs/root.cljs | 2 +- src/status_im/subs/wallet/wallet_connect.cljs | 23 ++- 15 files changed, 247 insertions(+), 157 deletions(-) diff --git a/src/quo/components/drawers/bottom_actions/style.cljs b/src/quo/components/drawers/bottom_actions/style.cljs index b462a152c3..9e958c69fe 100644 --- a/src/quo/components/drawers/bottom_actions/style.cljs +++ b/src/quo/components/drawers/bottom_actions/style.cljs @@ -21,12 +21,14 @@ (colors/theme-colors colors/white colors/neutral-95 theme))}) (defn buttons-container - [actions] - {:flex-direction (if (= actions :two-vertical-actions) :column :row) - :justify-content :space-around - :padding-vertical 12 - :gap 12 - :padding-horizontal 20}) + [actions container-style] + (merge + {:flex-direction (if (= actions :two-vertical-actions) :column :row) + :justify-content :space-around + :padding-vertical 12 + :gap 12 + :padding-horizontal 20} + container-style)) (def description-top {:flex-direction :row diff --git a/src/quo/components/drawers/bottom_actions/view.cljs b/src/quo/components/drawers/bottom_actions/view.cljs index 68de6073af..e3ef4ce46c 100644 --- a/src/quo/components/drawers/bottom_actions/view.cljs +++ b/src/quo/components/drawers/bottom_actions/view.cljs @@ -30,7 +30,8 @@ [:button-two-props {:optional true} [:maybe :map]] [:scroll? {:optional true} [:maybe :boolean]] [:blur? {:optional true} [:maybe :boolean]] - [:container-style {:optional true} [:maybe :map]]]]] + [:container-style {:optional true} [:maybe :map]] + [:buttons-container-style {:optional true} [:maybe :map]]]]] :any]) (def ^:private role-icon @@ -42,7 +43,7 @@ (defn- view-internal [{:keys [actions description description-text description-top-text error-message role button-one-label button-two-label blur? button-one-props button-two-props scroll? container-style - context-tag-props]}] + buttons-container-style context-tag-props]}] (let [theme (quo.theme/use-theme)] [rn/view {:style (merge (style/container scroll? blur? theme) container-style)} @@ -72,7 +73,7 @@ :context (i18n/label (keyword "t" role))} context-tag-props)]]) - [rn/view {:style (style/buttons-container actions)} + [rn/view {:style (style/buttons-container actions buttons-container-style)} (when (or (= actions :two-actions) (= actions :two-vertical-actions)) [button/button diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index f9aad3c896..755e56d620 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -287,6 +287,7 @@ (def ^:const wallet-connect-supported-events #{"accountsChanged" "chainChanged"}) (def ^:const wallet-connect-session-proposal-event "session_proposal") (def ^:const wallet-connect-session-request-event "session_request") +(def ^:const wallet-connect-session-delete-event "session_delete") (def ^:const wallet-connect-user-rejected-error-key "USER_REJECTED") (def ^:const transaction-pending-type-wallet-connect-transfer "WalletConnectTransfer") diff --git a/src/status_im/contexts/shell/qr_reader/view.cljs b/src/status_im/contexts/shell/qr_reader/view.cljs index 3868e1b5cb..cf967d0b19 100644 --- a/src/status_im/contexts/shell/qr_reader/view.cljs +++ b/src/status_im/contexts/shell/qr_reader/view.cljs @@ -117,13 +117,14 @@ (defn- f-internal-view [] (let [{:keys [keyboard-shown]} (hooks/use-keyboard)] - [:<> - (when keyboard-shown - (rn/dismiss-keyboard!)) - [scan-qr-code/view - {:title (i18n/label :t/scan-qr) - :share-button? true - :on-success-scan on-qr-code-scanned}]])) + (rn/use-mount + (fn [] + (when keyboard-shown + (rn/dismiss-keyboard!)))) + [scan-qr-code/view + {:title (i18n/label :t/scan-qr) + :share-button? true + :on-success-scan on-qr-code-scanned}])) (defn view [] diff --git a/src/status_im/contexts/wallet/connected_dapps/view.cljs b/src/status_im/contexts/wallet/connected_dapps/view.cljs index b8408f95f7..01d7ad5cea 100644 --- a/src/status_im/contexts/wallet/connected_dapps/view.cljs +++ b/src/status_im/contexts/wallet/connected_dapps/view.cljs @@ -13,26 +13,26 @@ [utils.re-frame :as rf])) (defn- on-disconnect - [wallet-account {:keys [name topic]}] + [wallet-account {:keys [name topic pairing-topic]}] (rf/dispatch [:hide-bottom-sheet]) (rf/dispatch [:wallet-connect/disconnect-dapp - {:topic topic - :on-success (fn [] - (rf/dispatch [:wallet-connect/remove-pairing-by-topic topic]) - (rf/dispatch [:toasts/upsert - {:id :dapp-disconnect-success - :type :positive - :text (i18n/label :t/disconnect-dapp-success - {:dapp name - :account (:name wallet-account)})}])) - :on-fail (fn [] - (rf/dispatch [:toasts/upsert - {:id :dapp-disconnect-failure - :type :negative - :text (i18n/label :t/disconnect-dapp-fail - {:dapp name - :account (:name wallet-account)})}]))}])) + {:topic topic + :pairing-topic pairing-topic + :on-success (fn [] + (rf/dispatch [:toasts/upsert + {:id :dapp-disconnect-success + :type :positive + :text (i18n/label :t/disconnect-dapp-success + {:dapp name + :account (:name wallet-account)})}])) + :on-fail (fn [] + (rf/dispatch [:toasts/upsert + {:id :dapp-disconnect-failure + :type :negative + :text (i18n/label :t/disconnect-dapp-fail + {:dapp name + :account (:name wallet-account)})}]))}])) (defn- on-dapp-disconnect-press [wallet-account dapp] @@ -83,7 +83,8 @@ [] (let [{:keys [bottom]} (safe-area/get-insets) {:keys [color] :as wallet-account} (rf/sub [:wallet/current-viewing-account]) - pairings (rf/sub [:wallet-connect/pairings]) + sessions (rf/sub + [:wallet-connect/sessions-for-current-account]) theme (quo.theme/use-theme)] [rn/view {:flex 1} [header @@ -91,7 +92,7 @@ :wallet-account wallet-account :on-close #(rf/dispatch [:navigate-back]) :on-add #(rf/dispatch [:navigate-to :screen/wallet.scan-dapp])}] - (if (empty? pairings) + (if (empty? sessions) [quo/empty-state {:title (i18n/label :t/no-dapps) :description (i18n/label :t/no-dapps-description) @@ -99,16 +100,16 @@ :container-style style/empty-container-style}] [rn/view (style/dapps-container bottom) [rn/flat-list - {:data pairings + {:data sessions :always-bounce-vertical false :content-container-style (style/dapps-list theme) - :render-fn (fn [{:keys [topic] - {:keys [icons name url]} :peerMetadata}] + :render-fn (fn [{:keys [topic pairingTopic name url iconUrl]}] [quo/dapp - {:dapp {:avatar (get icons 0) - :name name - :value url - :topic topic} + {:dapp {:avatar iconUrl + :name name + :value url + :topic topic + :pairing-topic pairingTopic} :accessibility-label (str "dapp-" topic) :state :default :action :icon diff --git a/src/status_im/contexts/wallet/wallet_connect/core.cljs b/src/status_im/contexts/wallet/wallet_connect/core.cljs index 5354f335dd..047ccaa190 100644 --- a/src/status_im/contexts/wallet/wallet_connect/core.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/core.cljs @@ -85,12 +85,34 @@ (defn required-networks-supported? [proposal supported-networks] - (let [required-networks (get-in proposal [:params :requiredNamespaces :eip155 :chains]) - supported-eip155 (set (map chain-id->eip155 supported-networks))] - (every? #(contains? supported-eip155 %) required-networks))) + (let [supported-namespaces #{:eip155} + required-namespaces (get-in proposal [:params :requiredNamespaces])] + (when (every? #(contains? supported-namespaces %) + (keys required-namespaces)) + (let [required-networks (get-in required-namespaces [:eip155 :chains]) + supported-eip155 (set (map chain-id->eip155 supported-networks))] + (every? #(contains? supported-eip155 %) + required-networks))))) (defn get-networks-by-mode [db] (let [test-mode? (get-in db [:profile/profile :test-networks-enabled?]) networks (get-in db [:wallet :networks (if test-mode? :test :prod)])] (mapv #(-> % :chain-id) networks))) + +(defn event-should-be-handled? + [db {:keys [topic]}] + (some #(= topic %) + (map :topic (:wallet-connect/sessions db)))) + +(defn sdk-session->db-session + [{:keys [topic expiry pairingTopic] :as session}] + {:topic topic + :expiry expiry + :sessionJson (transforms/clj->json session) + :pairingTopic pairingTopic + :name (get-in session [:peer :metadata :name]) + :iconUrl (get-in session [:peer :metadata :icons 0]) + :url (get-in session [:peer :metadata :url]) + :accounts (get-in session [:namespaces :eip155 :accounts]) + :disconnected false}) diff --git a/src/status_im/contexts/wallet/wallet_connect/effects.cljs b/src/status_im/contexts/wallet/wallet_connect/effects.cljs index 40c12f6ef4..bbab603f33 100644 --- a/src/status_im/contexts/wallet/wallet_connect/effects.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/effects.cljs @@ -31,13 +31,6 @@ :event wc-event :handler handler}))) -(rf/reg-fx - :effects.wallet-connect/fetch-pairings - (fn [{:keys [web3-wallet on-success on-fail]}] - (-> (wallet-connect/get-pairings web3-wallet) - (promesa/then on-success) - (promesa/catch on-fail)))) - (rf/reg-fx :effects.wallet-connect/pair (fn [{:keys [web3-wallet url on-success on-fail]}] @@ -53,13 +46,6 @@ (promesa/then on-success) (promesa/catch on-fail)))) -(rf/reg-fx - :effects.wallet-connect/fetch-active-sessions - (fn [{:keys [web3-wallet on-success on-fail]}] - (-> (wallet-connect/get-active-sessions web3-wallet) - (promesa/then on-success) - (promesa/catch on-fail)))) - (rf/reg-fx :effects.wallet-connect/approve-session (fn [{:keys [web3-wallet proposal supported-namespaces on-success on-fail]}] @@ -74,6 +60,13 @@ (promesa/then on-success) (promesa/catch on-fail))))) +(rf/reg-fx + :effects.wallet-connect/fetch-active-sessions + (fn [{:keys [web3-wallet on-success on-fail]}] + (-> (wallet-connect/get-active-sessions web3-wallet) + (promesa/then on-success) + (promesa/catch on-fail)))) + (rf/reg-fx :effects.wallet-connect/sign-message (fn [{:keys [password address data rpc-method on-success on-error]}] diff --git a/src/status_im/contexts/wallet/wallet_connect/events.cljs b/src/status_im/contexts/wallet/wallet_connect/events.cljs index 51856c97da..bfc9d9e3ce 100644 --- a/src/status_im/contexts/wallet/wallet_connect/events.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/events.cljs @@ -8,7 +8,8 @@ status-im.contexts.wallet.wallet-connect.responding-events [status-im.contexts.wallet.wallet-connect.utils :as wc-utils] [taoensso.timbre :as log] - [utils.i18n :as i18n])) + [utils.i18n :as i18n] + [utils.transforms :as types])) (rf/reg-event-fx :wallet-connect/init @@ -22,12 +23,7 @@ (fn [{:keys [db]} [web3-wallet]] {:db (assoc db :wallet-connect/web3-wallet web3-wallet) :fx [[:dispatch [:wallet-connect/register-event-listeners]] - [:effects.wallet-connect/fetch-pairings - {:web3-wallet web3-wallet - :on-fail #(log/error "Failed to get dApp pairings" {:error %}) - :on-success (fn [data] - (rf/dispatch [:wallet-connect/set-pairings - (js->clj data :keywordize-keys true)]))}]]})) + [:dispatch [:wallet-connect/fetch-persisted-sessions]]]})) (rf/reg-event-fx :wallet-connect/register-event-listeners @@ -40,7 +36,11 @@ [:effects.wallet-connect/register-event-listener [web3-wallet constants/wallet-connect-session-request-event - #(rf/dispatch [:wallet-connect/on-session-request %])]]]}))) + #(rf/dispatch [:wallet-connect/on-session-request %])]] + [:effects.wallet-connect/register-event-listener + [web3-wallet + constants/wallet-connect-session-delete-event + #(rf/dispatch [:wallet-connect/on-session-delete %])]]]}))) (rf/reg-event-fx :wallet-connect/on-init-fail @@ -54,7 +54,10 @@ (fn [{:keys [db]} [proposal]] (log/info "Received Wallet Connect session proposal: " {:id (:id proposal)}) (let [accounts (get-in db [:wallet :accounts]) - without-watched (remove :watch-only? (vals accounts)) + current-viewing-address (get-in db [:wallet :current-viewing-account-address]) + available-accounts (filter #(and (:operable? %) + (not (:watch-only? %))) + (vals accounts)) networks (wallet-connect-core/get-networks-by-mode db) session-networks (wallet-connect-core/proposal-networks-intersection proposal networks) @@ -65,9 +68,10 @@ :wallet-connect/current-proposal assoc :request proposal :session-networks session-networks - :address (-> without-watched - first - :address)) + :address (or current-viewing-address + (-> available-accounts + first + :address))) :fx [[:dispatch [:open-modal :screen/wallet.wallet-connect-session-proposal]]]} {:fx [[:dispatch @@ -85,9 +89,16 @@ (rf/reg-event-fx :wallet-connect/on-session-request - (fn [_ [event]] - (log/info "Received Wallet Connect session request: " event) - {:fx [[:dispatch [:wallet-connect/process-session-request event]]]})) + (fn [{:keys [db]} [event]] + (when (wallet-connect-core/event-should-be-handled? db event) + {:fx [[:dispatch [:wallet-connect/process-session-request event]]]}))) + +(rf/reg-event-fx + :wallet-connect/on-session-delete + (fn [{:keys [db]} [{:keys [topic] :as event}]] + (when (wallet-connect-core/event-should-be-handled? db event) + (log/info "Received Wallet Connect session delete: " event) + {:fx [[:dispatch [:wallet-connect/disconnect-session topic]]]}))) (rf/reg-event-fx :wallet-connect/reset-current-session-proposal @@ -104,28 +115,18 @@ (fn [{:keys [db]}] {:db (dissoc db :wallet-connect/current-request)})) -(rf/reg-event-fx - :wallet-connect/set-pairings - (fn [{:keys [db]} [pairings]] - {:db (assoc db :wallet-connect/pairings pairings)})) - -(rf/reg-event-fx - :wallet-connect/remove-pairing-by-topic - (fn [{:keys [db]} [topic]] - {:db (update db - :wallet-connect/pairings - (fn [pairings] - (remove #(= (:topic %) topic) pairings)))})) - (rf/reg-event-fx :wallet-connect/disconnect-dapp - (fn [{:keys [db]} [{:keys [topic on-success on-fail]}]] + (fn [{:keys [db]} [{:keys [pairing-topic on-success on-fail]}]] (let [web3-wallet (get db :wallet-connect/web3-wallet)] {:fx [[:effects.wallet-connect/disconnect {:web3-wallet web3-wallet - :topic topic + :topic pairing-topic :on-fail on-fail - :on-success on-success}]]}))) + :on-success (fn [] + (rf/dispatch [:wallet-connect/disconnect-session pairing-topic]) + (when on-success + (on-success)))}]]}))) (rf/reg-event-fx :wallet-connect/pair @@ -137,15 +138,6 @@ :on-fail #(log/error "Failed to pair with dApp" {:error %}) :on-success #(log/info "dApp paired successfully")}]]}))) -(rf/reg-event-fx - :wallet-connect/fetch-active-sessions - (fn [{:keys [db]}] - (let [web3-wallet (get db :wallet-connect/web3-wallet)] - {:fx [[:effects.wallet-connect/fetch-active-sessions - {:web3-wallet web3-wallet - :on-fail #(log/error "Failed to get active sessions" {:error %}) - :on-success #(log/info "Got active sessions successfully" {:sessions %})}]]}))) - (rf/reg-event-fx :wallet-connect/approve-session (fn [{:keys [db]}] @@ -204,10 +196,55 @@ {:version version}))}]]]} {:fx [[:dispatch [:wallet-connect/pair scanned-text]]]})))) +;; We first load sessions from database, then we initiate a call to Wallet Connect SDK and +;; then replace the list we have stored in the database with the one that came from the SDK. +;; In addition to that, we also update the backend state by marking sessions that are not +;; active anymore by calling `:wallet-connect/disconnect-session`. +(rf/reg-event-fx + :wallet-connect/fetch-active-sessions-success + (fn [{:keys [db now]} [sessions]] + (let [persisted-sessions (:wallet-connect/sessions db) + sessions (->> (js->clj sessions :keywordize-keys true) + vals + (map wallet-connect-core/sdk-session->db-session)) + expired-sessions (remove + (fn [{:keys [expiry]}] + (> expiry (/ now 1000))) + persisted-sessions)] + {:fx (mapv (fn [{:keys [pairingTopic]}] + [:wallet-connect/disconnect-session pairingTopic]) + expired-sessions) + :db (assoc db :wallet-connect/sessions sessions)}))) + +(rf/reg-event-fx + :wallet-connect/fetch-active-sessions + (fn [{:keys [db]}] + (let [web3-wallet (get db :wallet-connect/web3-wallet)] + {:fx [[:effects.wallet-connect/fetch-active-sessions + {:web3-wallet web3-wallet + :on-fail #(log/error "Failed to get active sessions" {:error %}) + :on-success #(rf/dispatch [:wallet-connect/fetch-active-sessions-success %])}]]}))) + (rf/reg-event-fx :wallet-connect/fetch-persisted-sessions-success (fn [{:keys [db]} [sessions]] - {:db (assoc db :wallet-connect/persisted-sessions sessions)})) + (let [sessions' (mapv (fn [{:keys [sessionJson] :as session}] + (assoc session + :accounts + (-> sessionJson + types/json->clj + :namespaces + :eip155 + :accounts))) + sessions)] + {:fx [[:dispatch [:wallet-connect/fetch-active-sessions]]] + :db (assoc db :wallet-connect/sessions sessions')}))) + +(rf/reg-event-fx + :wallet-connect/fetch-persisted-sessions-fail + (fn [_ [error]] + (log/info "Wallet Connect fetch persisted sessions failed" error) + {:fx [[:dispatch [:wallet-connect/fetch-active-sessions]]]})) (rf/reg-event-fx :wallet-connect/fetch-persisted-sessions @@ -218,7 +255,7 @@ ;; 0 means, return everything :params [0] :on-success [:wallet-connect/fetch-persisted-sessions-success] - :on-error #(log/info "Wallet Connect fetch persisted sessions failed" %)}]]]})) + :on-error [:wallet-connect/fetch-persisted-sessions-fail]}]]]})) (rf/reg-event-fx :wallet-connect/persist-session @@ -226,5 +263,22 @@ {:fx [[:json-rpc/call [{:method "wallet_addWalletConnectSession" :params [(js/JSON.stringify session-info)] - :on-success #(log/info "Wallet Connect session persisted") + :on-success (fn [] + (log/info "Wallet Connect session persisted") + (rf/dispatch [:wallet-connect/fetch-persisted-sessions])) + :on-error #(log/info "Wallet Connect session persistence failed" %)}]]]})) + +(rf/reg-event-fx + :wallet-connect/disconnect-session + (fn [{:keys [db]} [pairing-topic]] + {:db (update db + :wallet-connect/sessions + (fn [sessions] + (->> sessions + (remove #(= (:pairingTopic %) pairing-topic)) + (into [])))) + :fx [[:json-rpc/call + [{:method "wallet_disconnectWalletConnectSession" + :params [pairing-topic] + :on-success #(log/info "Wallet Connect session disconnected") :on-error #(log/info "Wallet Connect session persistence failed" %)}]]]})) diff --git a/src/status_im/contexts/wallet/wallet_connect/modals/common/header/view.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/common/header/view.cljs index 991949fcaa..5a13a0401e 100644 --- a/src/status_im/contexts/wallet/wallet_connect/modals/common/header/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/modals/common/header/view.cljs @@ -10,12 +10,12 @@ [quo/text {:size :heading-1 :weight :semi-bold} - (let [{:keys [name icons]} (:peerMetadata dapp)] + (let [{:keys [name iconUrl]} dapp] [rn/view {:style style/header-dapp-name} [quo/summary-tag {:type :dapp :label name - :image-source (first icons)}]]) + :image-source iconUrl}]]) (str " " label " ") (let [{:keys [emoji customization-color name]} account] [rn/view {:style style/header-account-name} diff --git a/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs b/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs index 838788576f..2a665fde6e 100644 --- a/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs @@ -132,7 +132,7 @@ :proposal current-proposal :on-success #(log/info "Wallet Connect session proposal rejected") :on-error #(log/error "Wallet Connect unable to reject session proposal")}] - [:dispatch [:wallet-connect/reset-current-session]]]}))) + [:dispatch [:wallet-connect/reset-current-session-proposal]]]}))) ;; NOTE: Currently we only reject a session if the user rejected it ;; But this needs to be solidified to ensure other cases: diff --git a/src/status_im/contexts/wallet/wallet_connect/session_proposal/style.cljs b/src/status_im/contexts/wallet/wallet_connect/session_proposal/style.cljs index b8438fb73d..a828d46a23 100644 --- a/src/status_im/contexts/wallet/wallet_connect/session_proposal/style.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/session_proposal/style.cljs @@ -6,12 +6,13 @@ :padding-top 12}) (def approval-note-container - {:margin-horizontal 20 - :padding 12 - :border-radius 16 - :border-width 1 - :border-color colors/neutral-10 - :background-color colors/neutral-2_5}) + {:margin-horizontal 20 + :padding-horizontal 16 + :padding-vertical 12 + :border-radius 16 + :border-width 1 + :border-color colors/neutral-10 + :background-color colors/neutral-2_5}) (def approval-note-title {:color colors/neutral-50 @@ -20,10 +21,8 @@ (def approval-note-li {:flex 1 :flex-direction :row - :align-items :center}) - -(def approval-li-spacer - {:width 8}) + :align-items :center + :gap 8}) (def account-switcher-title {:padding-horizontal 20}) @@ -31,3 +30,6 @@ (def account-switcher-list {:margin-top 8 :padding-horizontal 8}) + +(def footer-buttons-container + {:padding-horizontal 0}) diff --git a/src/status_im/contexts/wallet/wallet_connect/session_proposal/view.cljs b/src/status_im/contexts/wallet/wallet_connect/session_proposal/view.cljs index 3c953d686b..fab5752cf6 100644 --- a/src/status_im/contexts/wallet/wallet_connect/session_proposal/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/session_proposal/view.cljs @@ -33,16 +33,21 @@ labels [(i18n/label :t/check-your-account-balance-and-activity) (i18n/label :t/request-txns-and-message-signing)]] [rn/view {:style style/approval-note-container} - [quo/text {:style style/approval-note-title} + [quo/text + {:style style/approval-note-title + :weight :regular + :size :paragraph-2} (i18n/label :t/dapp-will-be-able-to {:dapp-name dapp-name})] (map-indexed (fn [idx label] ^{:key (str idx label)} [rn/view {:style style/approval-note-li} [quo/icon :i/bullet - {:color colors/neutral-50}] - [rn/view {:style style/approval-li-spacer}] - [quo/text label]]) + {:color colors/neutral-40}] + [quo/text + {:weight :regular + :size :paragraph-2} + label]]) labels)])) (defn- format-network-name @@ -57,16 +62,14 @@ (defn- accounts-list [] - (let [accounts (rf/sub [:wallet/accounts-without-watched-accounts]) + (let [accounts (rf/sub [:wallet/operable-accounts-without-watched-accounts]) selected-address (rf/sub [:wallet-connect/current-proposal-address])] [rn/view {:style style/account-switcher-list} - (for [account accounts] - ^{:key (-> account :address str)} + (for [{:keys [address] :as account} accounts] + ^{:key (str address)} [quo/account-item {:type :default - :state (if (and selected-address - (= (account :address) - selected-address)) + :state (if (= address selected-address) :selected :default) :account-props account @@ -101,12 +104,10 @@ (map format-network-name) (string/join ", ")) network-images (mapv :source session-networks) - data-item-common-props {:blur? false - :description :default - :card? false - :label :preview - :status :default - :size :large} + data-item-common-props {:blur? false + :card? false + :status :default + :size :large} account-data-item-props (assoc data-item-common-props :right-content {:type :accounts :size :size-32 @@ -116,9 +117,7 @@ :on-press show-account-switcher-bottom-sheet :title (i18n/label :t/account-title) :subtitle name - :icon-right? true - :right-icon :i/chevron-right - :icon-color colors/neutral-10) + :right-icon :i/chevron-right) networks-data-item-props (assoc data-item-common-props :right-content {:type :network :data network-images} @@ -136,18 +135,21 @@ [] (let [customization-color (rf/sub [:profile/customization-color])] [quo/bottom-actions - {:actions :two-actions - :button-two-label (i18n/label :t/decline) - :button-two-props {:type :grey - :accessibility-label :wc-deny-connection - :on-press #(do (rf/dispatch [:navigate-back]) - (rf/dispatch - [:wallet-connect/reject-session-proposal]))} - :button-one-label (i18n/label :t/connect) - :button-one-props {:customization-color customization-color - :type :primary - :accessibility-label :wc-connect - :on-press #(rf/dispatch [:wallet-connect/approve-session])}}])) + {:actions :two-actions + :buttons-container-style style/footer-buttons-container + :button-two-label (i18n/label :t/decline) + :button-two-props {:type :grey + :accessibility-label :wc-deny-connection + :on-press (fn [] + (rf/dispatch [:navigate-back]) + (rf/dispatch + [:wallet-connect/reject-session-proposal]))} + :button-one-label (i18n/label :t/connect) + :button-one-props {:customization-color customization-color + :type :primary + :accessibility-label :wc-connect + :on-press #(rf/dispatch + [:wallet-connect/approve-session])}}])) (defn- header [] diff --git a/src/status_im/contexts/wallet/wallet_connect/utils.cljs b/src/status_im/contexts/wallet/wallet_connect/utils.cljs index 736bab1d88..0f53bcc768 100644 --- a/src/status_im/contexts/wallet/wallet_connect/utils.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/utils.cljs @@ -11,14 +11,14 @@ (defn timestamp-expired? [expiry-timestamp] - (> (current-timestamp) expiry-timestamp)) + (when expiry-timestamp + (> (current-timestamp) expiry-timestamp))) (defn valid-wc-uri? [parsed-uri] - (let [{:keys [topic version expiryTimestamp]} parsed-uri] + (let [{:keys [topic version]} parsed-uri] (and (seq topic) - (number? version) - (number? expiryTimestamp)))) + (number? version)))) (defn valid-uri? "Check if the uri is in the wallet-connect format. diff --git a/src/status_im/subs/root.cljs b/src/status_im/subs/root.cljs index a364cd1a40..7e2051d06f 100644 --- a/src/status_im/subs/root.cljs +++ b/src/status_im/subs/root.cljs @@ -169,7 +169,7 @@ (reg-root-key-sub :wallet-connect/web3-wallet :wallet-connect/web3-wallet) (reg-root-key-sub :wallet-connect/current-proposal :wallet-connect/current-proposal) (reg-root-key-sub :wallet-connect/current-request :wallet-connect/current-request) -(reg-root-key-sub :wallet-connect/pairings :wallet-connect/pairings) +(reg-root-key-sub :wallet-connect/sessions :wallet-connect/sessions) ;;biometrics (reg-root-key-sub :biometrics :biometrics) diff --git a/src/status_im/subs/wallet/wallet_connect.cljs b/src/status_im/subs/wallet/wallet_connect.cljs index 763bd8b8c1..da205dcee9 100644 --- a/src/status_im/subs/wallet/wallet_connect.cljs +++ b/src/status_im/subs/wallet/wallet_connect.cljs @@ -1,5 +1,6 @@ (ns status-im.subs.wallet.wallet-connect - (:require [re-frame.core :as rf] + (:require [clojure.string :as string] + [re-frame.core :as rf] [status-im.contexts.wallet.common.utils :as wallet-utils] [status-im.contexts.wallet.common.utils.networks :as networks] [status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core] @@ -37,14 +38,24 @@ (rf/reg-sub :wallet-connect/current-request-dapp :<- [:wallet-connect/current-request] - :<- [:wallet-connect/pairings] - (fn [[request pairings]] + :<- [:wallet-connect/sessions] + (fn [[request sessions]] (let [dapp-url (get-in request [:event :verifyContext :verified :origin])] - (->> pairings - (filter (fn [pairing] - (= dapp-url (get-in pairing [:peerMetadata :url])))) + (->> sessions + (filter (fn [session] + (= dapp-url (get session :url)))) (first))))) +(rf/reg-sub + :wallet-connect/sessions-for-current-account + :<- [:wallet-connect/sessions] + :<- [:wallet/current-viewing-account-address] + (fn [[sessions address]] + (filter + (fn [{:keys [accounts]}] + (some #(string/includes? % address) accounts)) + sessions))) + (rf/reg-sub :wallet-connect/current-request-network :<- [:wallet-connect/current-request] From 59374543efb87460d5cc98a9298d2c7b3cad815f Mon Sep 17 00:00:00 2001 From: Yevheniia Berdnyk Date: Thu, 18 Jul 2024 19:45:20 +0300 Subject: [PATCH 57/79] e2e: disabled activity verification in wallet tests --- test/appium/tests/critical/test_wallet.py | 34 ++++++++++++----------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/test/appium/tests/critical/test_wallet.py b/test/appium/tests/critical/test_wallet.py index 195583f15f..d6e65da56f 100644 --- a/test/appium/tests/critical/test_wallet.py +++ b/test/appium/tests/critical/test_wallet.py @@ -144,14 +144,15 @@ class TestWalletMultipleDevice(MultipleSharedDeviceTestCase): self._check_balances_after_tx(amount_to_send, sender_balance, receiver_balance, eth_amount_sender, eth_amount_receiver) - self.loop.run_until_complete( - run_in_parallel(((self._check_last_transaction_in_activity, {'wallet_view': self.wallet_1, - 'device_time': device_time, - 'amount_to_send': amount_to_send}), - (self._check_last_transaction_in_activity, {'wallet_view': self.wallet_2, - 'device_time': device_time, - 'amount_to_send': amount_to_send, - 'sender': False})))) + # ToDo: enable when issues 20807 and 20808 are fixed + # self.loop.run_until_complete( + # run_in_parallel(((self._check_last_transaction_in_activity, {'wallet_view': self.wallet_1, + # 'device_time': device_time, + # 'amount_to_send': amount_to_send}), + # (self._check_last_transaction_in_activity, {'wallet_view': self.wallet_2, + # 'device_time': device_time, + # 'amount_to_send': amount_to_send, + # 'sender': False})))) self.errors.verify_no_errors() @marks.testrail_id(727230) @@ -171,14 +172,15 @@ class TestWalletMultipleDevice(MultipleSharedDeviceTestCase): self._check_balances_after_tx(amount_to_send, sender_balance, receiver_balance, eth_amount_sender, eth_amount_receiver) - self.loop.run_until_complete( - run_in_parallel(((self._check_last_transaction_in_activity, {'wallet_view': self.wallet_1, - 'device_time': device_time, - 'amount_to_send': amount_to_send}), - (self._check_last_transaction_in_activity, {'wallet_view': self.wallet_2, - 'device_time': device_time, - 'amount_to_send': amount_to_send, - 'sender': False})))) + # ToDo: enable when issues 20807 and 20808 are fixed + # self.loop.run_until_complete( + # run_in_parallel(((self._check_last_transaction_in_activity, {'wallet_view': self.wallet_1, + # 'device_time': device_time, + # 'amount_to_send': amount_to_send}), + # (self._check_last_transaction_in_activity, {'wallet_view': self.wallet_2, + # 'device_time': device_time, + # 'amount_to_send': amount_to_send, + # 'sender': False})))) self.errors.verify_no_errors() From 21cc8a199a2b2d98e02475f7e68fb7797ee2f0bf Mon Sep 17 00:00:00 2001 From: Omar Basem Date: Fri, 19 Jul 2024 08:13:50 +0400 Subject: [PATCH 58/79] fix: button width (#20792) * fix: button width (#20792) --- src/quo/components/drawers/bottom_actions/style.cljs | 3 +++ src/quo/components/drawers/bottom_actions/view.cljs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/quo/components/drawers/bottom_actions/style.cljs b/src/quo/components/drawers/bottom_actions/style.cljs index 9e958c69fe..2747053b7a 100644 --- a/src/quo/components/drawers/bottom_actions/style.cljs +++ b/src/quo/components/drawers/bottom_actions/style.cljs @@ -30,6 +30,9 @@ :padding-horizontal 20} container-style)) +(def button-container + {:flex-grow 1}) + (def description-top {:flex-direction :row :align-items :center diff --git a/src/quo/components/drawers/bottom_actions/view.cljs b/src/quo/components/drawers/bottom_actions/view.cljs index e3ef4ce46c..c79ec9ccd8 100644 --- a/src/quo/components/drawers/bottom_actions/view.cljs +++ b/src/quo/components/drawers/bottom_actions/view.cljs @@ -79,6 +79,7 @@ [button/button (merge {:size 40 + :container-style style/button-container :background (when (or blur? scroll?) :blur) :theme theme :accessibility-label :button-two} @@ -87,6 +88,7 @@ [button/button (merge {:size 40 + :container-style style/button-container :background (when (or blur? scroll?) :blur) :theme theme :accessibility-label :button-one} From c52b3e457ed648c668934140102d389977b80feb Mon Sep 17 00:00:00 2001 From: Ajay Sivan Date: Fri, 19 Jul 2024 13:30:38 +0530 Subject: [PATCH 59/79] Wallet/Swap Input Component (#20318) --- .../components/avatars/token_avatar/view.cljs | 14 +- .../wallet/swap_input/component_spec.cljs | 31 +++++ .../components/wallet/swap_input/style.cljs | 74 +++++++++++ .../components/wallet/swap_input/view.cljs | 121 ++++++++++++++++++ src/quo/core.cljs | 2 + src/quo/core_spec.cljs | 1 + src/status_im/contexts/preview/quo/main.cljs | 2 + .../preview/quo/wallet/swap_input.cljs | 68 ++++++++++ 8 files changed, 308 insertions(+), 5 deletions(-) create mode 100644 src/quo/components/wallet/swap_input/component_spec.cljs create mode 100644 src/quo/components/wallet/swap_input/style.cljs create mode 100644 src/quo/components/wallet/swap_input/view.cljs create mode 100644 src/status_im/contexts/preview/quo/wallet/swap_input.cljs diff --git a/src/quo/components/avatars/token_avatar/view.cljs b/src/quo/components/avatars/token_avatar/view.cljs index a5a1100d90..6f97695fb5 100644 --- a/src/quo/components/avatars/token_avatar/view.cljs +++ b/src/quo/components/avatars/token_avatar/view.cljs @@ -1,5 +1,6 @@ (ns quo.components.avatars.token-avatar.view (:require [quo.components.avatars.token-avatar.style :as style] + [quo.components.utilities.token.view :as token] [react-native.core :as rn] [react-native.hole-view :as hole-view] [react-native.platform :as platform] @@ -12,13 +13,14 @@ [:map {:closed true} [:type {:optional true} [:enum :asset :collectible]] [:context? {:optional true} [:maybe :boolean]] - [:image :schema.common/image-source] + [:image {:optional true} [:maybe :schema.common/image-source]] + [:token {:optional true} [:maybe [:or :keyword :string]]] [:network-image {:optional true} [:maybe :schema.common/image-source]] [:container-style {:optional true} [:maybe :map]]]]] :any]) (defn- view-internal - [{:keys [type context? image network-image container-style]}] + [{:keys [type context? image token network-image container-style]}] [rn/view {:style (merge style/container container-style) :accessibility-label :token-avatar} @@ -32,9 +34,11 @@ []) :style style/hole-view} platform/android? (assoc :key context?)) - [rn/image - {:source image - :style (style/image type)}]] + [token/view + {:size :size-32 + :token token + :style (style/image type) + :image-source image}]] (when context? [rn/image {:source network-image diff --git a/src/quo/components/wallet/swap_input/component_spec.cljs b/src/quo/components/wallet/swap_input/component_spec.cljs new file mode 100644 index 0000000000..607ac1ebba --- /dev/null +++ b/src/quo/components/wallet/swap_input/component_spec.cljs @@ -0,0 +1,31 @@ +(ns quo.components.wallet.swap-input.component-spec + (:require [quo.components.wallet.swap-input.view :as swap-input] + [test-helpers.component :as h])) + +(h/describe "Wallet: Swap Input" + (h/test "should render correctly with props" + (h/render-with-theme-provider + [swap-input/view + {:type :pay + :error? false + :token "SNT" + :status :default + :currency-symbol "€" + :value "5" + :fiat-value "1.50" + :network-tag-props {:title "Max: 200 SNT"}}]) + (h/is-truthy (h/get-by-label-text :swap-input)) + (h/is-truthy (h/get-by-text "SNT")) + (h/is-truthy (h/get-by-text "€1.50")) + (h/is-truthy (h/get-by-text "Max: 200 SNT"))) + + (h/test "should render correctly with approval label" + (h/render-with-theme-provider + [swap-input/view + {:type :pay + :show-approval-label? true + :approval-label-props + {:status :approve + :token-value "10" + :token-symbol "SNT"}}]) + (h/is-truthy (h/get-by-text "Approve 10 SNT")))) diff --git a/src/quo/components/wallet/swap_input/style.cljs b/src/quo/components/wallet/swap_input/style.cljs new file mode 100644 index 0000000000..a32e944c1f --- /dev/null +++ b/src/quo/components/wallet/swap_input/style.cljs @@ -0,0 +1,74 @@ +(ns quo.components.wallet.swap-input.style + (:require [quo.foundations.colors :as colors] + [quo.foundations.typography :as typography])) + +(defn- border-color + [theme] + (colors/theme-colors colors/neutral-10 colors/neutral-80 theme)) + +(defn- loader-color + [theme] + (colors/theme-colors colors/neutral-5 colors/neutral-90 theme)) + +(defn content + [theme] + {:border-width 1 + :border-radius 16 + :border-color (border-color theme) + :background-color (colors/theme-colors colors/white colors/neutral-95 theme)}) + +(defn row-1 + [loading?] + {:padding 12 + :gap 8 + :align-items (if loading? :center :flex-end) + :flex-direction :row}) + +(defn row-1-loader + [theme] + {:width 74 + :height 14 + :border-radius 6 + :background-color (loader-color theme)}) + +(def input-container + {:flex 1 + :flex-direction :row + :gap 5 + :height 32 + :align-items :flex-end}) + +(defn input + [disabled? error? theme] + (assoc typography/font-semi-bold + :font-size 27 + :flex-shrink 1 + :padding 0 + :color (cond + error? (colors/resolve-color :danger theme) + disabled? (colors/theme-colors colors/neutral-50 colors/neutral-40 theme) + :else (colors/theme-colors colors/neutral-100 colors/white theme)) + :line-height 32)) + +(defn token-symbol + [theme] + {:padding-bottom 3 + :color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}) + +(defn row-2 + [align-right?] + {:flex-direction :row + :justify-content (if align-right? :flex-end :space-between) + :align-items :center + :padding 12}) + +(defn row-2-loader + [theme] + {:width 80 + :height 10 + :margin-vertical 7 + :border-radius 6 + :background-color (loader-color theme)}) + +(def fiat-amount + {:color colors/neutral-50}) diff --git a/src/quo/components/wallet/swap_input/view.cljs b/src/quo/components/wallet/swap_input/view.cljs new file mode 100644 index 0000000000..4f5b7767b2 --- /dev/null +++ b/src/quo/components/wallet/swap_input/view.cljs @@ -0,0 +1,121 @@ +(ns quo.components.wallet.swap-input.view + (:require [oops.core :as oops] + [quo.components.avatars.token-avatar.view :as token-avatar] + [quo.components.buttons.button.view :as buttons] + [quo.components.dividers.divider-line.view :as divider-line] + [quo.components.markdown.text :as text] + [quo.components.tags.network-tags.view :as network-tag] + [quo.components.wallet.approval-label.schema :as approval-label.schema] + [quo.components.wallet.approval-label.view :as approval-label] + [quo.components.wallet.swap-input.style :as style] + [quo.foundations.colors :as colors] + quo.theme + [react-native.core :as rn] + [schema.core :as schema])) + +(def ?schema + [:=> + [:catn + [:props + [:map {:closed true} + [:type {:optional true} [:maybe [:enum :pay :receive]]] + [:status {:optional true} [:maybe [:enum :default :disabled :loading]]] + [:token {:optional true} [:maybe :string]] + [:value {:optional true} [:maybe :string]] + [:default-value {:optional true} [:maybe :string]] + [:currency-symbol {:optional true} [:maybe :string]] + [:fiat-value {:optional true} [:maybe :string]] + [:show-approval-label? {:optional true} [:maybe :boolean]] + [:error? {:optional true} [:maybe :boolean]] + [:show-keyboard? {:optional true} [:maybe :boolean]] + [:approval-label-props {:optional true} [:maybe approval-label.schema/?schema]] + [:network-tag-props {:optional true} [:maybe :map]] + [:on-change-text {:optional true} [:maybe fn?]] + [:enable-swap? {:optional true} [:maybe :boolean]] + [:on-swap-press {:optional true} [:maybe fn?]] + [:on-token-press {:optional true} [:maybe fn?]] + [:on-max-press {:optional true} [:maybe fn?]] + [:customization-color {:optional true} [:maybe :schema.common/customization-color]] + [:container-style {:optional true} [:maybe :map]]]]] + :any]) + +(defn view-internal + [{:keys [type status token value fiat-value show-approval-label? error? network-tag-props + approval-label-props default-value enable-swap? + currency-symbol on-change-text show-keyboard? + container-style on-swap-press on-token-press on-max-press]}] + (let [theme (quo.theme/use-theme) + pay? (= type :pay) + disabled? (= status :disabled) + loading? (= status :loading) + controlled-input? (some? value) + input-ref (rn/use-ref-atom nil) + set-input-ref (rn/use-callback (fn [ref] (reset! input-ref ref)) []) + focus-input (rn/use-callback (fn [] + (some-> @input-ref + (oops/ocall "focus"))) + [input-ref])] + [rn/view + {:style container-style + :accessibility-label :swap-input} + [rn/view {:style (style/content theme)} + [rn/view + {:style (style/row-1 loading?)} + [rn/pressable {:on-press on-token-press} + [token-avatar/view + {:type :asset + :token token}]] + (if loading? + [rn/view {:style (style/row-1-loader theme)}] + [:<> + [rn/pressable + {:style style/input-container + :on-press focus-input} + [rn/text-input + (cond-> {:ref set-input-ref + :style (style/input disabled? error? theme) + :placeholder-text-color (colors/theme-colors colors/neutral-40 + colors/neutral-50 + theme) + :keyboard-type :numeric + :auto-focus true + :on-change-text on-change-text + :show-soft-input-on-focus show-keyboard? + :default-value default-value + :placeholder "0"} + controlled-input? (assoc :value value))] + [text/text + {:size :paragraph-2 + :weight :semi-bold + :style (style/token-symbol theme)} + token]] + (when (and pay? enable-swap?) + [buttons/button + {:type :outline + :size 32 + :on-press on-swap-press + :icon-only? true} + :i/reorder])])] + [divider-line/view] + [rn/view + {:style (style/row-2 (or (not pay?) loading?))} + (when-not loading? + [:<> + (when pay? + [rn/pressable {:on-press on-max-press} + [network-tag/view + (assoc network-tag-props + :status + (if error? :error :default))]]) + [text/text + {:size :paragraph-2 + :style style/fiat-amount + :weight :medium} + (str currency-symbol fiat-value)]]) + (when loading? + [rn/view {:style (style/row-2-loader theme)}])]] + (when (and (not= status :loading) (= type :pay) show-approval-label?) + [approval-label/view + approval-label-props])])) + +(def view (schema/instrument #'view-internal ?schema)) diff --git a/src/quo/core.cljs b/src/quo/core.cljs index a819ee74d7..808ff8e3d2 100644 --- a/src/quo/core.cljs +++ b/src/quo/core.cljs @@ -186,6 +186,7 @@ quo.components.wallet.progress-bar.view quo.components.wallet.required-tokens.view quo.components.wallet.summary-info.view + quo.components.wallet.swap-input.view quo.components.wallet.token-input.view quo.components.wallet.transaction-progress.view quo.components.wallet.transaction-summary.view @@ -472,6 +473,7 @@ (def progress-bar quo.components.wallet.progress-bar.view/view) (def required-tokens quo.components.wallet.required-tokens.view/view) (def summary-info quo.components.wallet.summary-info.view/view) +(def swap-input quo.components.wallet.swap-input.view/view) (def network-link quo.components.wallet.network-link.view/view) (def token-input quo.components.wallet.token-input.view/view) (def wallet-overview quo.components.wallet.wallet-overview.view/view) diff --git a/src/quo/core_spec.cljs b/src/quo/core_spec.cljs index 59d76a356e..0daf83ee30 100644 --- a/src/quo/core_spec.cljs +++ b/src/quo/core_spec.cljs @@ -112,6 +112,7 @@ quo.components.wallet.progress-bar.component-spec quo.components.wallet.required-tokens.component-spec quo.components.wallet.summary-info.component-spec + quo.components.wallet.swap-input.component-spec quo.components.wallet.token-input.component-spec quo.components.wallet.transaction-progress.component-spec quo.components.wallet.transaction-summary.component-spec diff --git a/src/status_im/contexts/preview/quo/main.cljs b/src/status_im/contexts/preview/quo/main.cljs index 795cb39300..8c846eda92 100644 --- a/src/status_im/contexts/preview/quo/main.cljs +++ b/src/status_im/contexts/preview/quo/main.cljs @@ -212,6 +212,7 @@ [status-im.contexts.preview.quo.wallet.progress-bar :as progress-bar] [status-im.contexts.preview.quo.wallet.required-tokens :as required-tokens] [status-im.contexts.preview.quo.wallet.summary-info :as summary-info] + [status-im.contexts.preview.quo.wallet.swap-input :as swap-input] [status-im.contexts.preview.quo.wallet.token-input :as token-input] [status-im.contexts.preview.quo.wallet.transaction-progress :as transaction-progress] [status-im.contexts.preview.quo.wallet.transaction-summary :as @@ -570,6 +571,7 @@ {:name :required-tokens :component required-tokens/view} {:name :summary-info :component summary-info/view} + {:name :swap-input :component swap-input/view} {:name :token-input :component token-input/view} {:name :wallet-activity :component wallet-activity/view} {:name :transaction-progress :component transaction-progress/view} diff --git a/src/status_im/contexts/preview/quo/wallet/swap_input.cljs b/src/status_im/contexts/preview/quo/wallet/swap_input.cljs new file mode 100644 index 0000000000..ffdcec2022 --- /dev/null +++ b/src/status_im/contexts/preview/quo/wallet/swap_input.cljs @@ -0,0 +1,68 @@ +(ns status-im.contexts.preview.quo.wallet.swap-input + (:require + [quo.core :as quo] + [quo.foundations.resources :as resources] + [react-native.core :as rn] + [status-im.contexts.preview.quo.preview :as preview])) + +(def descriptor + [{:type :select + :key :type + :options [{:key :pay} + {:key :receive}]} + {:type :select + :key :status + :options [{:key :default} + {:key :disabled} + {:key :loading}]} + {:type :select + :key :value + :options [{:key :token} + {:key :fiat}]} + {:type :boolean + :key :error?} + {:type :boolean + :key :enable-swap?} + {:type :boolean + :key :show-approval-label?} + (preview/customization-color-option)]) + +(defn view + [] + (let [[state set-state] (rn/use-state {:type :pay + :error? false + :token "SNT" + :customization-color :blue + :show-approval-label? false + :enable-swap? true + :status :default + :currency-symbol "€"}) + [value set-value] (rn/use-state "") + on-press (fn [v] (set-value (str value v))) + delete (fn [] (set-value #(subs % 0 (dec (count %)))))] + [preview/preview-container + {:state state + :set-state set-state + :descriptor descriptor} + [quo/swap-input + (assoc state + :on-swap-press #(js/alert "Swap Pressed") + :on-token-press #(js/alert "Token Pressed") + :on-max-press #(js/alert "Max Pressed") + :value value + :fiat-value (str (.toFixed (* value 0.3) 2)) + :container-style {:margin-bottom 20} + :network-tag-props {:title "Max: 200 SNT" + :networks [{:source (resources/get-network :ethereum)}]} + :approval-label-props + {:status :approve + :token-value "10" + :button-props {:on-press + #(js/alert "Approve Pressed")} + :customization-color (:customization-color state) + :token-symbol "SNT"})] + [quo/numbered-keyboard + {:left-action :dot + :delete-key? true + :on-press on-press + :on-delete delete}]])) From 84b8943fd447c1bd2086ea31b643d89f57e874d2 Mon Sep 17 00:00:00 2001 From: frank Date: Fri, 19 Jul 2024 17:17:53 +0800 Subject: [PATCH 60/79] fix_:still return the image when image server is unable to get public key (#20538) https://github.com/status-im/status-go/compare/cba3ac57...4a43b2b2 --- status-go-version.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/status-go-version.json b/status-go-version.json index 1c8e4ea8b6..4ccf855801 100644 --- a/status-go-version.json +++ b/status-go-version.json @@ -3,7 +3,7 @@ "_comment": "Instead use: scripts/update-status-go.sh ", "owner": "status-im", "repo": "status-go", - "version": "v0.182.36", - "commit-sha1": "8458cafef9f876c11a58dc9ede14fc58a5a0f968", - "src-sha256": "0nkb0zpqgpklh8mlrgcglh5v32qlfly9hd7ia3a1icx5kdp2q2wp" + "version": "v0.182.37", + "commit-sha1": "4a43b2b2bebe45df2100d1a5c5034105d93e50b8", + "src-sha256": "0f5mm7lx6s2qcy9xpa9v7piqb60yazi6p677fy105yz7hg731cw6" } From 02e24208db8c3f6b7ced096aefb186edaa66e38c Mon Sep 17 00:00:00 2001 From: Lungu Cristian Date: Fri, 19 Jul 2024 14:16:51 +0300 Subject: [PATCH 61/79] Wallet Connect transactions (#20755) * feat: updated signing endpoints and refactor https://github.com/status-im/status-go/compare/6e056348...e8aec741 * fix: using the generic warning at the bottom * fix: show parsed transaction parameters * feat: adding fees to transactions https://github.com/status-im/status-go/compare/6e056348...b2e5e7a8 * feat: added fees * feat: added eip-1559 fee estimation & tx priority * feat: added fees and failed processing handling * fix: show testnet name in the request * fix: address review comments * feat: added max-fee color when not enough balance * ref: broke down tx fees subscription * fix: handle gas estimation on status-go https://github.com/status-im/status-go/compare/1ef2434b...5389f281 * fix: don't overwrite dynamic fees if already there * fix: malli schema and review comments * fix: addressed review comments 1 * fix: addressed comments 2 * fix: removed unused require * fix: addressed QA review https://github.com/status-im/status-go/compare/484b8aca...d07f9b5b * fix: requests being shown simultaneously * fix: removed support for eth_signTransaction --- .../components/settings/data_item/view.cljs | 1 + src/status_im/constants.cljs | 6 +- .../wallet/common/utils/networks.cljs | 25 ++-- .../wallet/send/input_amount/view.cljs | 2 +- .../contexts/wallet/wallet_connect/core.cljs | 11 ++ .../wallet/wallet_connect/effects.cljs | 19 ++- .../wallet/wallet_connect/events.cljs | 2 +- .../modals/common/fees_data_item/view.cljs | 31 ++++ .../modals/common/footer/view.cljs | 4 +- .../modals/send_transaction/view.cljs | 15 +- .../modals/sign_message/view.cljs | 12 +- .../modals/sign_transaction/view.cljs | 14 +- .../wallet_connect/processing_events.cljs | 137 +++++++++++------- .../wallet_connect/responding_events.cljs | 20 ++- .../contexts/wallet/wallet_connect/rpc.cljs | 5 + .../wallet/wallet_connect/transactions.cljs | 118 +++++++++++++-- src/status_im/subs/wallet/wallet_connect.cljs | 130 ++++++++++------- src/utils/money.cljs | 4 + translations/en.json | 6 +- 19 files changed, 393 insertions(+), 169 deletions(-) create mode 100644 src/status_im/contexts/wallet/wallet_connect/modals/common/fees_data_item/view.cljs diff --git a/src/quo/components/settings/data_item/view.cljs b/src/quo/components/settings/data_item/view.cljs index 712a577d48..cce92cfeca 100644 --- a/src/quo/components/settings/data_item/view.cljs +++ b/src/quo/components/settings/data_item/view.cljs @@ -121,6 +121,7 @@ [:card? {:optional true} [:maybe :boolean]] [:right-icon {:optional true} [:maybe :keyword]] [:right-content {:optional true} [:maybe :map]] + [:icon-color {:optional true} [:maybe :schema.common/customization-color]] [:status {:optional true} [:maybe [:enum :default :loading]]] [:subtitle-type {:optional true} [:maybe [:enum :default :icon :network :account :editable]]] [:size {:optional true} [:maybe [:enum :default :small :large]]] diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index 755e56d620..4299c6bb59 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -281,7 +281,8 @@ #{wallet-connect-personal-sign-method wallet-connect-eth-sign-method wallet-connect-eth-send-transaction-method - wallet-connect-eth-sign-transaction-method + ;; NOTE: disabled, as we have no clear use cases for it and other wallets don't support it + ;; wallet-connect-eth-sign-transaction-method wallet-connect-eth-sign-typed-method wallet-connect-eth-sign-typed-v4-method}) (def ^:const wallet-connect-supported-events #{"accountsChanged" "chainChanged"}) @@ -512,6 +513,9 @@ (def ^:const optimism-full-name "Optimism") (def ^:const arbitrum-full-name "Arbitrum") +(def ^:const sepolia-full-name "Sepolia") +(def ^:const goerli-full-name "Goerli") + (def ^:const mainnet-network-name :mainnet) (def ^:const ethereum-network-name :ethereum) (def ^:const optimism-network-name :optimism) diff --git a/src/status_im/contexts/wallet/common/utils/networks.cljs b/src/status_im/contexts/wallet/common/utils/networks.cljs index ad20445e0b..bc34b8f857 100644 --- a/src/status_im/contexts/wallet/common/utils/networks.cljs +++ b/src/status_im/contexts/wallet/common/utils/networks.cljs @@ -172,17 +172,20 @@ (defn get-network-details [chain-id] - (condp contains? chain-id - #{constants/ethereum-mainnet-chain-id constants/ethereum-goerli-chain-id - constants/ethereum-sepolia-chain-id} - mainnet-network-details + (as-> chain-id $ + (condp contains? $ + #{constants/ethereum-mainnet-chain-id constants/ethereum-goerli-chain-id + constants/ethereum-sepolia-chain-id} + mainnet-network-details - #{constants/arbitrum-mainnet-chain-id constants/arbitrum-goerli-chain-id - constants/arbitrum-sepolia-chain-id} - arbitrum-network-details + #{constants/arbitrum-mainnet-chain-id constants/arbitrum-goerli-chain-id + constants/arbitrum-sepolia-chain-id} + arbitrum-network-details - #{constants/optimism-mainnet-chain-id constants/optimism-goerli-chain-id - constants/optimism-sepolia-chain-id} - optimism-network-details + #{constants/optimism-mainnet-chain-id constants/optimism-goerli-chain-id + constants/optimism-sepolia-chain-id} + optimism-network-details - nil)) + nil) + (when $ + (assoc $ :chain-id chain-id)))) diff --git a/src/status_im/contexts/wallet/send/input_amount/view.cljs b/src/status_im/contexts/wallet/send/input_amount/view.cljs index 52373ea80c..63d7275321 100644 --- a/src/status_im/contexts/wallet/send/input_amount/view.cljs +++ b/src/status_im/contexts/wallet/send/input_amount/view.cljs @@ -120,7 +120,7 @@ [] [quo/alert-banner {:action? true - :text (i18n/label :t/not-enough-assets) + :text (i18n/label :t/not-enough-assets-to-pay-gas-fees) :button-text (i18n/label :t/buy-eth) :on-button-press #(rf/dispatch [:show-bottom-sheet {:content buy-token/view}])}]) diff --git a/src/status_im/contexts/wallet/wallet_connect/core.cljs b/src/status_im/contexts/wallet/wallet_connect/core.cljs index 047ccaa190..0bf0ffb1cc 100644 --- a/src/status_im/contexts/wallet/wallet_connect/core.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/core.cljs @@ -100,6 +100,17 @@ networks (get-in db [:wallet :networks (if test-mode? :test :prod)])] (mapv #(-> % :chain-id) networks))) +(defn add-full-testnet-name + "Updates the `:full-name` key with the full testnet name if using testnet `:chain-id`.\n + e.g. `{:full-name \"Mainnet\"}` -> `{:full-name \"Mainnet Sepolia\"`}`" + [network] + (let [add-testnet-name (fn [testnet-name] + (update network :full-name #(str % " " testnet-name)))] + (condp #(contains? %1 %2) (:chain-id network) + constants/sepolia-chain-ids (add-testnet-name constants/sepolia-full-name) + constants/goerli-chain-ids (add-testnet-name constants/goerli-full-name) + network))) + (defn event-should-be-handled? [db {:keys [topic]}] (some #(= topic %) diff --git a/src/status_im/contexts/wallet/wallet_connect/effects.cljs b/src/status_im/contexts/wallet/wallet_connect/effects.cljs index bbab603f33..94680770a4 100644 --- a/src/status_im/contexts/wallet/wallet_connect/effects.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/effects.cljs @@ -83,22 +83,33 @@ (promesa/then on-success) (promesa/catch on-error))))) +(rf/reg-fx + :effects.wallet-connect/prepare-transaction + (fn [{:keys [tx chain-id on-success on-error]}] + (-> (transactions/prepare-transaction tx + chain-id + transactions/default-tx-priority) + (promesa/then on-success) + (promesa/catch on-error)))) + (rf/reg-fx :effects.wallet-connect/sign-transaction - (fn [{:keys [password address chain-id tx on-success on-error]}] + (fn [{:keys [password address chain-id tx-hash tx-args on-success on-error]}] (-> (transactions/sign-transaction (security/safe-unmask-data password) address - tx + tx-hash + tx-args chain-id) (promesa/then on-success) (promesa/catch on-error)))) (rf/reg-fx :effects.wallet-connect/send-transaction - (fn [{:keys [password address chain-id tx on-success on-error]}] + (fn [{:keys [password address chain-id tx-hash tx-args on-success on-error]}] (-> (transactions/send-transaction (security/safe-unmask-data password) address - tx + tx-hash + tx-args chain-id) (promesa/then on-success) (promesa/catch on-error)))) diff --git a/src/status_im/contexts/wallet/wallet_connect/events.cljs b/src/status_im/contexts/wallet/wallet_connect/events.cljs index bfc9d9e3ce..d69f7a20ac 100644 --- a/src/status_im/contexts/wallet/wallet_connect/events.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/events.cljs @@ -63,7 +63,7 @@ networks) required-networks-supported? (wallet-connect-core/required-networks-supported? proposal networks)] - (if required-networks-supported? + (if (and (not-empty session-networks) required-networks-supported?) {:db (update db :wallet-connect/current-proposal assoc :request proposal diff --git a/src/status_im/contexts/wallet/wallet_connect/modals/common/fees_data_item/view.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/common/fees_data_item/view.cljs new file mode 100644 index 0000000000..276addd6d5 --- /dev/null +++ b/src/status_im/contexts/wallet/wallet_connect/modals/common/fees_data_item/view.cljs @@ -0,0 +1,31 @@ +(ns status-im.contexts.wallet.wallet-connect.modals.common.fees-data-item.view + (:require [quo.core :as quo] + [quo.foundations.colors :as colors] + [quo.theme] + [status-im.contexts.wallet.wallet-connect.modals.common.style :as style] + [utils.i18n :as i18n])) + +(defn- fees-subtitle + [{:keys [text error?]}] + (let [theme (quo.theme/use-theme)] + [quo/text + {:weight :medium + :size :paragraph-2 + :style {:color (if error? + (colors/resolve-color :danger theme) + (colors/theme-colors colors/neutral-100 + colors/white + theme))}} + text])) + +(defn view + [{:keys [fees fees-error]}] + [quo/data-item + {:size :small + :status :default + :card? false + :container-style style/data-item + :title (i18n/label :t/max-fees) + :custom-subtitle (fn [] [fees-subtitle + {:text (or fees (i18n/label :t/no-fees)) + :error? (= fees-error :not-enough-assets-to-pay-gas-fees)}])}]) diff --git a/src/status_im/contexts/wallet/wallet_connect/modals/common/footer/view.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/common/footer/view.cljs index 4d422c1bf9..14e853eae1 100644 --- a/src/status_im/contexts/wallet/wallet_connect/modals/common/footer/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/modals/common/footer/view.cljs @@ -13,7 +13,7 @@ (rf/dispatch [:wallet-connect/respond-current-session password])) (defn view - [{:keys [warning-label slide-button-text disabed?]} & children] + [{:keys [warning-label slide-button-text disabled?]} & children] (let [{:keys [customization-color]} (rf/sub [:wallet-connect/current-request-account-details])] [rn/view {:style style/content-container} (into [rn/view @@ -23,7 +23,7 @@ [standard-authentication/slide-button {:size :size-48 :track-text slide-button-text - :disabled? disabed? + :disabled? disabled? :customization-color customization-color :on-auth-success on-auth-success :auth-button-label (i18n/label :t/confirm)}]] diff --git a/src/status_im/contexts/wallet/wallet_connect/modals/send_transaction/view.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/send_transaction/view.cljs index 3f4724972f..6314945770 100644 --- a/src/status_im/contexts/wallet/wallet_connect/modals/send_transaction/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/modals/send_transaction/view.cljs @@ -1,8 +1,11 @@ (ns status-im.contexts.wallet.wallet-connect.modals.send-transaction.view (:require [quo.core :as quo] + [quo.theme] [react-native.core :as rn] [react-native.safe-area :as safe-area] [status-im.contexts.wallet.wallet-connect.modals.common.data-block.view :as data-block] + [status-im.contexts.wallet.wallet-connect.modals.common.fees-data-item.view :as + fees-data-item] [status-im.contexts.wallet.wallet-connect.modals.common.footer.view :as footer] [status-im.contexts.wallet.wallet-connect.modals.common.header.view :as header] [status-im.contexts.wallet.wallet-connect.modals.common.page-nav.view :as page-nav] @@ -40,7 +43,7 @@ :not-enough-assets :t/not-enough-assets))}]) [footer/view - {:warning-label (i18n/label :t/wallet-connect-send-transaction-warning) + {:warning-label (i18n/label :t/wallet-connect-sign-warning) :slide-button-text (i18n/label :t/slide-to-send) :disabled? error-state} [quo/data-item @@ -51,11 +54,7 @@ :subtitle-type :network :network-image (:source network) :subtitle (:full-name network)}] - [quo/data-item - {:size :small - :status :default - :card? false - :container-style style/data-item - :title (i18n/label :t/max-fees) - :subtitle (or max-fees-fiat-formatted (i18n/label :t/no-fees))}]]]])) + [fees-data-item/view + {:fees max-fees-fiat-formatted + :fees-error error-state}]]]])) diff --git a/src/status_im/contexts/wallet/wallet_connect/modals/sign_message/view.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/sign_message/view.cljs index 7ba821434d..32b802d8c5 100644 --- a/src/status_im/contexts/wallet/wallet_connect/modals/sign_message/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/modals/sign_message/view.cljs @@ -3,6 +3,8 @@ [react-native.core :as rn] [react-native.safe-area :as safe-area] [status-im.contexts.wallet.wallet-connect.modals.common.data-block.view :as data-block] + [status-im.contexts.wallet.wallet-connect.modals.common.fees-data-item.view :as + fees-data-item] [status-im.contexts.wallet.wallet-connect.modals.common.footer.view :as footer] [status-im.contexts.wallet.wallet-connect.modals.common.header.view :as header] [status-im.contexts.wallet.wallet-connect.modals.common.page-nav.view :as page-nav] @@ -28,13 +30,7 @@ :account account}] [data-block/view]] [footer/view - {:warning-label (i18n/label :t/wallet-connect-sign-message-warning) + {:warning-label (i18n/label :t/wallet-connect-sign-warning) :slide-button-text (i18n/label :t/slide-to-sign)} - [quo/data-item - {:size :small - :status :default - :card? false - :container-style style/data-item - :title (i18n/label :t/max-fees) - :subtitle (i18n/label :t/no-fees)}]]]])) + [fees-data-item/view]]]])) diff --git a/src/status_im/contexts/wallet/wallet_connect/modals/sign_transaction/view.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/sign_transaction/view.cljs index 6504f21118..5c4dfc61c1 100644 --- a/src/status_im/contexts/wallet/wallet_connect/modals/sign_transaction/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/modals/sign_transaction/view.cljs @@ -3,6 +3,8 @@ [react-native.core :as rn] [react-native.safe-area :as safe-area] [status-im.contexts.wallet.wallet-connect.modals.common.data-block.view :as data-block] + [status-im.contexts.wallet.wallet-connect.modals.common.fees-data-item.view :as + fees-data-item] [status-im.contexts.wallet.wallet-connect.modals.common.footer.view :as footer] [status-im.contexts.wallet.wallet-connect.modals.common.header.view :as header] [status-im.contexts.wallet.wallet-connect.modals.common.page-nav.view :as page-nav] @@ -40,7 +42,7 @@ :not-enough-assets :t/not-enough-assets))}]) [footer/view - {:warning-label (i18n/label :t/wallet-connect-sign-transaction-warning) + {:warning-label (i18n/label :t/wallet-connect-sign-warning) :slide-button-text (i18n/label :t/slide-to-sign) :disabled? error-state} [quo/data-item @@ -51,11 +53,7 @@ :subtitle-type :network :network-image (:source network) :subtitle (:full-name network)}] - [quo/data-item - {:size :small - :status :default - :card? false - :container-style style/data-item - :title (i18n/label :t/max-fees) - :subtitle (or max-fees-fiat-formatted (i18n/label :t/no-fees))}]]]])) + [fees-data-item/view + {:fees max-fees-fiat-formatted + :fees-error error-state}]]]])) diff --git a/src/status_im/contexts/wallet/wallet_connect/processing_events.cljs b/src/status_im/contexts/wallet/wallet_connect/processing_events.cljs index 2d5e7814a0..33adb4fc1d 100644 --- a/src/status_im/contexts/wallet/wallet_connect/processing_events.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/processing_events.cljs @@ -1,18 +1,32 @@ (ns status-im.contexts.wallet.wallet-connect.processing-events - (:require [clojure.string :as string] + (:require [cljs-bean.core :as bean] + [clojure.string :as string] [native-module.core :as native-module] [re-frame.core :as rf] [status-im.constants :as constants] [status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core] + [status-im.contexts.wallet.wallet-connect.transactions :as transactions] [taoensso.timbre :as log] [utils.transforms :as transforms])) (rf/reg-event-fx - :wallet-connect/process-session-request - (fn [{:keys [db]} [event]] - (let [method (wallet-connect-core/get-request-method event) + :wallet-connect/show-request-modal + (fn [{:keys [db]}] + (let [event (get-in db [:wallet-connect/current-request :event]) + method (wallet-connect-core/get-request-method event) screen (wallet-connect-core/method-to-screen method)] (if screen + {:fx [[:dispatch [:open-modal screen]]]} + (log/error "Didn't find screen for Wallet Connect method" + {:method method + :event :wallet-connect/process-session-request}))))) +(rf/reg-event-fx + :wallet-connect/process-session-request + (fn [{:keys [db]} [event]] + (let [method (wallet-connect-core/get-request-method event) + existing-event (get-in db [:wallet-connect/current-request :event])] + ;; NOTE: make sure we don't show two requests at the same time + (when-not existing-event {:db (assoc-in db [:wallet-connect/current-request :event] event) :fx [(condp = method constants/wallet-connect-eth-send-transaction-method @@ -31,12 +45,7 @@ [:dispatch [:wallet-connect/process-sign-typed]] constants/wallet-connect-personal-sign-method - [:dispatch [:wallet-connect/process-personal-sign]]) - - [:dispatch [:open-modal screen]]]} - (log/error "Didn't find screen for Wallet Connect method" - {:method method - :event :wallet-connect/process-session-request}))))) + [:dispatch [:wallet-connect/process-personal-sign]])]})))) (rf/reg-event-fx :wallet-connect/process-personal-sign @@ -48,7 +57,8 @@ assoc :address (string/lower-case address) :raw-data raw-data - :display-data (or parsed-data raw-data))}))) + :display-data (or parsed-data raw-data)) + :fx [[:dispatch [:wallet-connect/show-request-modal]]]}))) (rf/reg-event-fx :wallet-connect/process-eth-sign @@ -60,44 +70,53 @@ assoc :address (string/lower-case address) :raw-data raw-data - :display-data (or parsed-data raw-data))}))) + :display-data (or parsed-data raw-data)) + :fx [[:dispatch [:wallet-connect/show-request-modal]]]}))) + +(rf/reg-event-fx + :wallet-connect/prepare-transaction-success + (fn [{:keys [db]} [prepared-tx chain-id]] + (let [{:keys [tx-args]} prepared-tx + tx (bean/->clj tx-args) + address (-> tx :from string/lower-case) + display-data (transactions/beautify-transaction tx)] + {:db (update-in db + [:wallet-connect/current-request] + assoc + :address address + :raw-data prepared-tx + :transaction tx + :chain-id chain-id + :display-data display-data) + :fx [[:dispatch [:wallet-connect/show-request-modal]]]}))) (rf/reg-event-fx :wallet-connect/process-eth-send-transaction (fn [{:keys [db]}] - (let [event (wallet-connect-core/get-db-current-request-event db) - display-data (-> event - clj->js - (js/JSON.stringify nil 2)) - - {:keys [from] :as tx} (-> event wallet-connect-core/get-request-params first) - chain-id (-> event - (get-in [:params :chainId]) - wallet-connect-core/eip155->chain-id)] - {:db (update-in db - [:wallet-connect/current-request] - assoc - :address (string/lower-case from) - :raw-data tx - :chain-id chain-id - :display-data display-data)}))) + (let [event (wallet-connect-core/get-db-current-request-event db) + tx (-> event wallet-connect-core/get-request-params first) + chain-id (-> event + (get-in [:params :chainId]) + wallet-connect-core/eip155->chain-id)] + {:fx [[:effects.wallet-connect/prepare-transaction + {:tx tx + :chain-id chain-id + :on-success #(rf/dispatch [:wallet-connect/prepare-transaction-success % chain-id]) + :on-error #(rf/dispatch [:wallet-connect/on-processing-error %])}]]}))) (rf/reg-event-fx :wallet-connect/process-eth-sign-transaction (fn [{:keys [db]}] - (let [event (wallet-connect-core/get-db-current-request-event db) - display-data (.stringify js/JSON (clj->js event) nil 2) - {:keys [from] :as tx} (-> event wallet-connect-core/get-request-params first) - chain-id (-> event - (get-in [:params :chainId]) - wallet-connect-core/eip155->chain-id)] - {:db (update-in db - [:wallet-connect/current-request] - assoc - :address (string/lower-case from) - :raw-data tx - :chain-id chain-id - :display-data display-data)}))) + (let [event (wallet-connect-core/get-db-current-request-event db) + tx (-> event wallet-connect-core/get-request-params first) + chain-id (-> event + (get-in [:params :chainId]) + wallet-connect-core/eip155->chain-id)] + {:fx [[:effects.wallet-connect/prepare-transaction + {:tx tx + :chain-id chain-id + :on-success #(rf/dispatch [:wallet-connect/prepare-transaction-success % chain-id]) + :on-error #(rf/dispatch [:wallet-connect/on-processing-error %])}]]}))) (rf/reg-event-fx :wallet-connect/process-sign-typed @@ -108,11 +127,31 @@ (transforms/js-dissoc :types :primaryType) (transforms/js-stringify 2)) (catch js/Error _ nil))] - ;; TODO: decide if we should proceed if the typed-data is invalid JSON or fail ahead of time - (when (nil? parsed-data) (log/error "Invalid typed data")) - {:db (update-in db - [:wallet-connect/current-request] - assoc - :address (string/lower-case address) - :display-data (or parsed-data raw-data) - :raw-data raw-data)}))) + (if (nil? parsed-data) + {:fx [[:dispatch + [:wallet-connect/on-processing-error + (ex-info "Failed to parse JSON typed data" {:data raw-data})]]]} + {:db (update-in db + [:wallet-connect/current-request] + assoc + :address (string/lower-case address) + :display-data (or parsed-data raw-data) + :raw-data raw-data) + :fx [[:dispatch [:wallet-connect/show-request-modal]]]})))) + +;; TODO: we should reject a request if processing fails +(rf/reg-event-fx + :wallet-connect/on-processing-error + (fn [{:keys [db]} [error]] + (let [{:keys [address event]} (get db :wallet-connect/current-request) + method (wallet-connect-core/get-request-method event) + screen (wallet-connect-core/method-to-screen method)] + (log/error "Failed to process Wallet Connect request" + {:error error + :address address + :method method + :wallet-connect-event event + :event :wallet-connect/on-processing-error}) + + {:fx [[:dispatch [:dismiss-modal screen]] + [:dispatch [:wallet-connect/reset-current-request]]]}))) diff --git a/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs b/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs index 2a665fde6e..16c2c9a6e7 100644 --- a/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs @@ -58,34 +58,38 @@ (rf/reg-event-fx :wallet-connect/respond-send-transaction-data (fn [{:keys [db]} [password]] - (let [{:keys [chain-id raw-data address]} (get db :wallet-connect/current-request)] + (let [{:keys [chain-id raw-data address]} (get db :wallet-connect/current-request) + {:keys [tx-hash tx-args]} raw-data] {:fx [[:effects.wallet-connect/send-transaction {:password password :address address :chain-id chain-id - :tx raw-data + :tx-hash tx-hash + :tx-args tx-args :on-error #(rf/dispatch [:wallet-connect/on-sign-error %]) :on-success #(rf/dispatch [:wallet-connect/send-response {:result %}])}]]}))) (rf/reg-event-fx :wallet-connect/respond-sign-transaction-data (fn [{:keys [db]} [password]] - (let [{:keys [chain-id raw-data address]} (get db :wallet-connect/current-request)] + (let [{:keys [chain-id raw-data address]} (get db :wallet-connect/current-request) + {:keys [tx-hash tx-args]} raw-data] {:fx [[:effects.wallet-connect/sign-transaction {:password password :address address :chain-id chain-id - :tx raw-data + :tx-hash tx-hash + :tx-params tx-args :on-error #(rf/dispatch [:wallet-connect/on-sign-error %]) :on-success #(rf/dispatch [:wallet-connect/send-response {:result %}])}]]}))) +;; TODO: should reject if "signing" fails (rf/reg-event-fx :wallet-connect/on-sign-error (fn [{:keys [db]} [error]] - (let [event (get-in db [:wallet-connect/current-request :event]) - {:keys [raw-data address]} (get db :wallet-connect/current-request) - method (wallet-connect-core/get-request-method event) - screen (wallet-connect-core/method-to-screen method)] + (let [{:keys [raw-data address event]} (get db :wallet-connect/current-request) + method (wallet-connect-core/get-request-method event) + screen (wallet-connect-core/method-to-screen method)] (log/error "Failed to sign Wallet Connect request" {:error error :address address diff --git a/src/status_im/contexts/wallet/wallet_connect/rpc.cljs b/src/status_im/contexts/wallet/wallet_connect/rpc.cljs index 3b1ce23afe..cfba96da43 100644 --- a/src/status_im/contexts/wallet/wallet_connect/rpc.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/rpc.cljs @@ -59,3 +59,8 @@ password chain-id legacy?)) + +(defn wallet-get-suggested-fees + [chain-id] + (-> (call-rpc "wallet_getSuggestedFees" chain-id) + (promesa/then transforms/js->clj))) diff --git a/src/status_im/contexts/wallet/wallet_connect/transactions.cljs b/src/status_im/contexts/wallet/wallet_connect/transactions.cljs index e23f196ecd..d74b5686cc 100644 --- a/src/status_im/contexts/wallet/wallet_connect/transactions.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/transactions.cljs @@ -1,10 +1,24 @@ (ns status-im.contexts.wallet.wallet-connect.transactions (:require [cljs-bean.core :as bean] [clojure.string :as string] + [native-module.core :as native-module] [promesa.core :as promesa] + [status-im.constants :as constants] + [status-im.contexts.wallet.wallet-connect.core :as core] [status-im.contexts.wallet.wallet-connect.rpc :as rpc] + [utils.money :as money] [utils.transforms :as transforms])) +(defn transaction-request? + [event] + (->> (core/get-request-method event) + (contains? #{constants/wallet-connect-eth-send-transaction-method + constants/wallet-connect-eth-sign-transaction-method}))) + +;; NOTE: Currently we don't allow the user to configure the tx priority as we don't +;; show the estimated time, but when we implement it, we should allow to change it +(def ^:constant default-tx-priority :medium) + (defn- strip-hex-prefix "Strips the extra 0 in hex value if present" [hex-value] @@ -15,12 +29,12 @@ (defn- format-tx-hex-values "Due to how status-go expects hex values, we should remove the extra 0s in transaction hex values e.g. 0x0f -> 0xf" - [tx] + [tx f] (let [tx-keys [:gasLimit :gas :gasPrice :nonce :value :maxFeePerGas :maxPriorityFeePerGas]] (reduce (fn [acc tx-key] (if (and (contains? tx tx-key) (not (nil? (get tx tx-key)))) - (update acc tx-key strip-hex-prefix) + (update acc tx-key f) acc)) tx tx-keys))) @@ -29,26 +43,100 @@ "Formats the transaction and transforms it into a stringified JS object, ready to be passed to an RPC call." [tx] (-> tx - format-tx-hex-values + ;; NOTE: removing `:nonce` to compute it when building the transaction on status-go + (dissoc :nonce) + (format-tx-hex-values strip-hex-prefix) bean/->js (transforms/js-stringify 0))) +(defn beautify-transaction + [tx] + (let [hex->number #(-> % (subs 2) native-module/hex-to-number)] + (-> tx + (format-tx-hex-values hex->number) + clj->js + (js/JSON.stringify nil 2)))) + +(defn- gwei->hex + [gwei] + (->> gwei + money/gwei->wei + native-module/number-to-hex + (str "0x"))) + +(defn- get-max-fee-per-gas-key + "Mapping transaction priority (which determines how quickly a tx is processed) + to the `suggested-routes` key that should be used for `:maxPriorityFeePerGas`. + + Returns `:high` | `:medium` | `:low`" + [tx-priority] + (get {:high :maxFeePerGasHigh + :medium :maxFeePerGasMedium + :low :maxFeePerGasLow} + tx-priority)) + +(defn- dynamic-fee-tx? + "Checks if a transaction has dynamic fees (EIP1559)" + [tx] + (every? tx [:maxFeePerGas :maxPriorityFeePerGas])) + +(defn- tx->eip1559-tx + "Adds `:maxFeePerGas` and `:maxPriorityFeePerGas` for dynamic fee support (EIP1559) and + removes `:gasPrice`, if the chain supports EIP1559 and the transaction doesn't already + have dynamic fees." + [tx suggested-fees tx-priority] + (if (and (:eip1559Enabled suggested-fees) + (not (dynamic-fee-tx? tx))) + (let [max-fee-per-gas-key (get-max-fee-per-gas-key tx-priority) + max-fee-per-gas (-> suggested-fees max-fee-per-gas-key gwei->hex) + max-priority-fee-per-gas (-> suggested-fees :maxPriorityFeePerGas gwei->hex)] + (-> tx + (assoc + :maxFeePerGas max-fee-per-gas + :maxPriorityFeePerGas max-priority-fee-per-gas) + ;; NOTE: `:gasPrice` is used only for legacy Tx, so we discard it in favor of dynamic fees + (dissoc :gasPrice))) + tx)) + +(defn- prepare-transaction-fees + "Makes sure the transaction has the correct gas and fees properties" + [tx tx-priority suggested-fees] + (-> (assoc tx + ;; NOTE: `gasLimit` is ignored on status-go when building a transaction + ;; (`wallet_buildTransaction`), so we're setting it as the `gas` property + :gas + (or (:gasLimit tx) + (:gas tx))) + (dissoc :gasLimit) + (tx->eip1559-tx suggested-fees tx-priority))) + +(defn prepare-transaction + "Formats and builds the incoming transaction, adding the missing properties and returning the final + transaction, along with the transaction hash and the suggested fees" + [tx chain-id tx-priority] + (promesa/let [suggested-fees (rpc/wallet-get-suggested-fees chain-id) + {:keys [tx-args message-to-sign]} (->> + (prepare-transaction-fees tx + tx-priority + suggested-fees) + prepare-transaction-for-rpc + (rpc/wallet-build-transaction chain-id))] + {:tx-args tx-args + :tx-hash message-to-sign + :suggested-fees suggested-fees})) + (defn sign-transaction - [password address tx chain-id] + [password address tx-hash tx-args chain-id] (promesa/let - [formatted-tx (prepare-transaction-for-rpc tx) - {:keys [message-to-sign tx-args]} (rpc/wallet-build-transaction chain-id formatted-tx) - signature (rpc/wallet-sign-message message-to-sign address password) - raw-tx (rpc/wallet-build-raw-transaction chain-id tx-args signature)] + [signature (rpc/wallet-sign-message tx-hash address password) + raw-tx (rpc/wallet-build-raw-transaction chain-id tx-args signature)] raw-tx)) (defn send-transaction - [password address tx chain-id] + [password address tx-hash tx-args chain-id] (promesa/let - [formatted-tx (prepare-transaction-for-rpc tx) - {:keys [message-to-sign tx-args]} (rpc/wallet-build-transaction chain-id formatted-tx) - signature (rpc/wallet-sign-message message-to-sign address password) - tx (rpc/wallet-send-transaction-with-signature chain-id - tx-args - signature)] + [signature (rpc/wallet-sign-message tx-hash address password) + tx (rpc/wallet-send-transaction-with-signature chain-id + tx-args + signature)] tx)) diff --git a/src/status_im/subs/wallet/wallet_connect.cljs b/src/status_im/subs/wallet/wallet_connect.cljs index da205dcee9..6b4fe542a6 100644 --- a/src/status_im/subs/wallet/wallet_connect.cljs +++ b/src/status_im/subs/wallet/wallet_connect.cljs @@ -4,6 +4,7 @@ [status-im.contexts.wallet.common.utils :as wallet-utils] [status-im.contexts.wallet.common.utils.networks :as networks] [status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core] + [status-im.contexts.wallet.wallet-connect.transactions :as transactions] [utils.money :as money])) (rf/reg-sub @@ -57,66 +58,95 @@ sessions))) (rf/reg-sub - :wallet-connect/current-request-network + :wallet-connect/chain-id :<- [:wallet-connect/current-request] (fn [request] (-> request (get-in [:event :params :chainId]) - (wallet-connect-core/eip155->chain-id) - (networks/get-network-details)))) + (wallet-connect-core/eip155->chain-id)))) + +(rf/reg-sub + :wallet-connect/current-request-network + :<- [:wallet-connect/chain-id] + (fn [chain-id] + (-> chain-id + (networks/get-network-details) + (wallet-connect-core/add-full-testnet-name)))) + +(rf/reg-sub + :wallet-connect/transaction-args + :<- [:wallet-connect/current-request] + (fn [{:keys [event transaction]}] + (when (transactions/transaction-request? event) + transaction))) + +(rf/reg-sub + :wallet-connect/transaction-suggested-fees + :<- [:wallet-connect/current-request] + (fn [{:keys [event raw-data]}] + (when (transactions/transaction-request? event) + (:suggested-fees raw-data)))) + +(rf/reg-sub + :wallet-connect/transaction-max-fees-wei + :<- [:wallet-connect/transaction-args] + :<- [:wallet-connect/transaction-suggested-fees] + (fn [[transaction suggested-fees]] + (when transaction + (let [{:keys [gasPrice gas gasLimit maxFeePerGas]} transaction + eip-1559-chain? (:eip1559Enabled suggested-fees) + gas-limit (or gasLimit gas) + max-gas-fee (if eip-1559-chain? maxFeePerGas gasPrice)] + (money/bignumber (* max-gas-fee gas-limit)))))) + +(rf/reg-sub + :wallet-connect/account-eth-token + :<- [:wallet-connect/current-request-address] + :<- [:wallet/accounts] + (fn [[address accounts]] + (let [fee-token "ETH" + find-account #(when (= (:address %) address) %) + find-token #(when (= (:symbol %) fee-token) %)] + (->> accounts + (some find-account) + :tokens + (some find-token))))) (rf/reg-sub :wallet-connect/current-request-transaction-information - :<- [:wallet-connect/current-request] - :<- [:wallet/accounts] + :<- [:wallet-connect/chain-id] + :<- [:wallet-connect/transaction-max-fees-wei] + :<- [:wallet-connect/transaction-args] + :<- [:wallet-connect/account-eth-token] :<- [:profile/currency] :<- [:profile/currency-symbol] - (fn [[request accounts currency currency-symbol]] - (let [chain-id (-> request - (get-in [:raw-data :params :chainId]) - (wallet-connect-core/eip155->chain-id)) - all-tokens (->> accounts - (filter #(= (:address %) - (:address request))) - (first) - :tokens) - eth-token (->> all-tokens - (filter #(= (:symbol %) "ETH")) - (first)) - {:keys [gasPrice gasLimit value]} (-> request - :raw-data - wallet-connect-core/get-request-params - first) - max-fees-wei (money/bignumber (* gasPrice gasLimit))] - (when (and gasPrice gasLimit) - (let [max-fees-ether (money/wei->ether max-fees-wei) - token-fiat-value (wallet-utils/calculate-token-fiat-value {:currency currency - :balance max-fees-ether - :token eth-token}) - crypto-formatted (wallet-utils/get-standard-crypto-format eth-token max-fees-ether) - fiat-formatted (wallet-utils/get-standard-fiat-format crypto-formatted - currency-symbol - token-fiat-value) - balance (-> eth-token - (get-in [:balances-per-chain chain-id :raw-balance]) - (money/bignumber)) - value (money/bignumber value) - total-transaction-value (money/add max-fees-wei value)] - {:total-transaction-value total-transaction-value - :balance balance - :max-fees max-fees-wei - :max-fees-fiat-value token-fiat-value - :max-fees-fiat-formatted fiat-formatted - :error-state (cond - (and - (money/sufficient-funds? value balance) - (not (money/sufficient-funds? total-transaction-value balance))) - :not-enough-assets-to-pay-gas-fees + (fn [[chain-id max-fees-wei transaction eth-token currency currency-symbol]] + (when transaction + (let [max-fees-ether (money/wei->ether max-fees-wei) + max-fees-fiat (wallet-utils/calculate-token-fiat-value {:currency currency + :balance max-fees-ether + :token eth-token}) + max-fees-fiat-formatted (-> max-fees-ether + (wallet-utils/get-standard-crypto-format eth-token) + (wallet-utils/get-standard-fiat-format currency-symbol + max-fees-fiat)) + balance (-> eth-token + (get-in [:balances-per-chain chain-id :raw-balance]) + money/bignumber) + tx-value (money/bignumber (:value transaction)) + total-transaction-value (money/add max-fees-wei tx-value)] + {:total-transaction-value total-transaction-value + :balance balance + :max-fees max-fees-wei + :max-fees-fiat-value max-fees-fiat + :max-fees-fiat-formatted max-fees-fiat-formatted + :error-state (cond + (not (money/sufficient-funds? tx-value balance)) + :not-enough-assets - (not (money/sufficient-funds? value balance)) - :not-enough-assets - - :else nil)}))))) + (not (money/sufficient-funds? total-transaction-value + balance)) + :not-enough-assets-to-pay-gas-fees)})))) (rf/reg-sub :wallet-connect/current-proposal-request diff --git a/src/utils/money.cljs b/src/utils/money.cljs index a160ec065c..1af5125d59 100644 --- a/src/utils/money.cljs +++ b/src/utils/money.cljs @@ -148,6 +148,10 @@ [n] (wei-> :gwei n)) +(defn gwei->wei + [n] + (->wei :gwei n)) + (defn ether->wei [^js bn] (when bn diff --git a/translations/en.json b/translations/en.json index de6b4c281e..26fbdc5afa 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1941,8 +1941,8 @@ "pinned-by": "Pinned by", "pin-limit-reached": "Pin limit reached. Unpin a previous message first.", "no-fees": "No fees", - "not-enough-assets-to-pay-gas-fees": "Not enough assets to pay gas fees.", - "not-enough-assets": "Not enough assets to complete transaction.", + "not-enough-assets-to-pay-gas-fees": "Not enough assets to pay gas fees", + "not-enough-assets": "Not enough assets to complete transaction", "max-fee": "Max fee", "max-fees": "Max fees", "max-priority-fee": "Max priority fee", @@ -2052,6 +2052,7 @@ "wallet-connect-sign-message-header": "wants you to sign the message with", "wallet-connect-send-transaction-header": "wants you to send this transaction with", "wallet-connect-sign-transaction-header": "wants you to sign this transaction with", + "wallet-connect-sign-warning": "Sign only if you trust the dApp", "wallet-connect-sign-message-warning": "Sign messages only if you trust the dApp", "wallet-connect-send-transaction-warning": "Send transactions only if you trust the dApp", "wallet-connect-sign-transaction-warning": "Sign transactions only if you trust the dApp", @@ -2746,7 +2747,6 @@ "saved-address-network-preference-selection-description": "Only change if you know which networks the address owner is happy to to receive funds on", "add-preferences": "Add preferences", "buy-eth": "Buy ETH", - "not-enough-assets": "Not enough assets to pay gas fees", "send-from-network": "Send from {{network}}", "define-amount-sent-from-network": "Define amount sent from {{network}} network", "dont-auto-recalculate-network": "Don't auto recalculate {{network}}", From 06b29961670625495a1874885fae91efe08edd2b Mon Sep 17 00:00:00 2001 From: Volodymyr Kozieiev Date: Fri, 19 Jul 2024 15:10:13 +0100 Subject: [PATCH 62/79] Do not update disabled networks when receiver network updated (#20756) --- src/status_im/contexts/wallet/send/events.cljs | 11 ++--------- src/status_im/contexts/wallet/send/events_test.cljs | 13 ------------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/src/status_im/contexts/wallet/send/events.cljs b/src/status_im/contexts/wallet/send/events.cljs index 43e035a178..b2d219b44c 100644 --- a/src/status_im/contexts/wallet/send/events.cljs +++ b/src/status_im/contexts/wallet/send/events.cljs @@ -187,15 +187,8 @@ (rf/reg-event-fx :wallet/update-receiver-networks (fn [{:keys [db]} [selected-networks]] - (let [amount (get-in db [:wallet :ui :send :amount]) - disabled-from-chain-ids (get-in db [:wallet :ui :send :disabled-from-chain-ids]) - filtered-disabled-from-chain-ids (filter (fn [chain-id] - (some #(= chain-id %) - selected-networks)) - disabled-from-chain-ids)] - {:db (-> db - (assoc-in [:wallet :ui :send :receiver-networks] selected-networks) - (assoc-in [:wallet :ui :send :disabled-from-chain-ids] filtered-disabled-from-chain-ids)) + (let [amount (get-in db [:wallet :ui :send :amount])] + {:db (assoc-in db [:wallet :ui :send :receiver-networks] selected-networks) :fx [[:dispatch [:wallet/get-suggested-routes {:amount amount}]]]}))) (rf/reg-event-fx diff --git a/src/status_im/contexts/wallet/send/events_test.cljs b/src/status_im/contexts/wallet/send/events_test.cljs index 8d2740b7ac..5809459e0d 100644 --- a/src/status_im/contexts/wallet/send/events_test.cljs +++ b/src/status_im/contexts/wallet/send/events_test.cljs @@ -23,19 +23,6 @@ selected-networks-after [:ethereum :optimism] expected-db {:wallet {:ui {:send {:receiver-networks selected-networks-after}}}}] (reset! rf-db/app-db {:wallet {:ui {:send {:receiver-networks selected-networks-before}}}}) - (is (match? expected-db (:db (dispatch [event-id selected-networks-after])))))) - - (testing "if receiver network removed, it is also removed from disabled ones" - (let [selected-networks-before [:ethereum :optimism :arbitrum] - selected-networks-after [:ethereum :optimism] - disabled-from-chain-ids-before [:optimism :arbitrum] - disabled-from-chain-ids-after [:optimism] - expected-db {:wallet {:ui {:send {:receiver-networks selected-networks-after - :disabled-from-chain-ids - disabled-from-chain-ids-after}}}}] - (reset! rf-db/app-db {:wallet {:ui {:send {:receiver-networks selected-networks-before - :disabled-from-chain-ids - disabled-from-chain-ids-before}}}}) (is (match? expected-db (:db (dispatch [event-id selected-networks-after]))))))) (h/deftest-event :wallet/set-token-to-send From cec46f985aedc2daecf73a96bc05ee3ad5b44eb4 Mon Sep 17 00:00:00 2001 From: Icaro Motta Date: Sun, 21 Jul 2024 22:39:50 -0300 Subject: [PATCH 63/79] chore(style): Sort JSON keys in translations/en.json (#20785) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sorts keys (non-recursively) in translations/en.json. The solution uses the prettier plugin https://github.com/Gudahtt/prettier-plugin-sort-json. We only format translations/en.json because it’s the only file we need to manually change. - Dependency added: prettier-plugin-sort-json version 4.0.0 - Dependency upgraded: prettier, from version 2.8.8 to 3.3.3. --- .prettierignore | 23 +- .prettierrc.js | 4 + Makefile | 2 +- package.json | 3 +- translations/en.json | 5543 +++++++++++++++++++++--------------------- yarn.lock | 13 +- 6 files changed, 2800 insertions(+), 2788 deletions(-) diff --git a/.prettierignore b/.prettierignore index 2456f95334..238132a698 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,10 +1,15 @@ -* +/* + +# Format top-level js files. !*.js -!*/ -*.clj-kondo -*.shadow-cljs -modules -result -target -component-spec -/app + +# Ignore all except src/js/**/*.js +!/src/ +/src/* +!/src/js +!/src/js/**/*.js + +# Ignore all except translations/en.json +!/translations/ +/translations/* +!/translations/en.json diff --git a/.prettierrc.js b/.prettierrc.js index 3e061010f1..a000e79b5c 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -6,4 +6,8 @@ module.exports = { tabWidth: 2, trailingComma: 'all', useTabs: false, + + // JSON sorting + jsonSortOrder: '{ "/.*/": "caseInsensitiveLexical" } ', + plugins: ['prettier-plugin-sort-json'], }; diff --git a/Makefile b/Makefile index 256f164bb7..1b117ca003 100644 --- a/Makefile +++ b/Makefile @@ -317,7 +317,7 @@ lint: ##@test Run code style checks scripts/lint/translations.clj && \ zprint '{:search-config? true}' -sfc $$ALL_CLOJURE_FILES && \ sh scripts/lint/trailing-newline.sh && \ - node_modules/.bin/prettier --write . + node_modules/.bin/prettier --check . # NOTE: We run the linter twice because of https://github.com/kkinnear/zprint/issues/271 lint-fix: export TARGET := clojure diff --git a/package.json b/package.json index cd8b9414f3..1a6bbfe7e3 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,8 @@ "jest-silent-reporter": "^0.5.0", "nodemon": "^2.0.16", "nyc": "^14.1.1", - "prettier": "^2.8.8", + "prettier": "^3.3.3", + "prettier-plugin-sort-json": "^4.0.0", "process": "0.11.10", "react-test-renderer": "18.1.0", "shadow-cljs": "2.26.2", diff --git a/translations/en.json b/translations/en.json index 26fbdc5afa..32cd4ae561 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1,2775 +1,2772 @@ { - "about-app": "About", - "about-key-storage-content": "Status will never access your private key. Be sure to backup your seed phrase. If you lose your phone it is the only way to access your keys.", - "about-key-storage-title": "About key storage", - "about-names-content": "No one can pretend to be you! You’re anonymous by default and never have to reveal your real name. You can register a custom name for a small fee.", - "about-names-title": "Names can’t be changed", - "accent-colour": "Accent colour", - "accent-colour-updated": "Accent colour updated", - "access-key": "Access key", - "access-existing-keys": "Access existing keys", - "accept-and-share-address": "Accept and share address", - "accept-community-rules": "I agree with the community rules", - "account-added": "Account added", - "account-color": "Account color", - "added-to-group-chat": "Added to group chat", - "added-you-to": "added you to", - "address-name": "Address name", - "anyone": "Anyone", - "allow-new-contact-requests": "Allow new contact requests", - "messages-from-contacts-only-subtitle": "Only people you added as contacts can start a new chat with you or invite you to a group", - "messages-gap-warning": "Some messages might be missing", - "accept-new-chats-from": "Accept new chats from", - "account-name": "Account name", - "account-settings": "Account settings", - "accounts": "Accounts", - "active-online": "Online", - "active-unknown": "Unknown", - "add": "Add", - "added": "added", - "add-a-watch-account": "Add a watch-only address", - "add-account-incorrect-password": "Password seems to be incorrect. Enter the password you use to unlock the app.", - "add-an-account": "Add an account", - "add-bootnode": "Add bootnode", - "add-contact": "Add contact", - "add-custom-token": "Add custom token", - "add-mailserver": "Add Status node", - "add-members": "Add members", - "add-node": "Add node", - "add-to-contacts": "Add to contacts", - "add-to-contacts-text": "By adding a user to your contact list, you share your wallet address", - "address": "Address", - "address-received": "Address received", - "address-requested": "Address requested", - "address-request-accepted": "Address request accepted", - "advanced": "Advanced", - "advanced-settings": "Advanced settings", - "agree-by-continuing": "By continuing you agree\n to our ", - "add-me-to-your-contacts": "Please add me to your contacts", - "all": "All", - "allow": "Allow", - "allowing-authorizes-this-dapp": "Allowing authorizes this DApp to retrieve your wallet address and enable Web3", - "already-have-asset": "You already have this asset", - "amount": "Amount", - "are-not-allowed": "{{check} are not allowed", - "are-you-sure-description": "You will not be able to see the whole seed phrase again", - "are-you-sure?": "Are you sure?", - "ask-in-status": "Ask a question or report a bug", - "at": "at", - "authorize": "Authorize", - "available": "Available", - "available-participants": { - "one": "You can select one more participant", - "other": "You can select {{count}} more participants" - }, - "back": "Back", - "back-up-seed-phrase": "Backup recovery phrase", - "back-up-your-seed-phrase": "Backup your recovery phrase", - "balance": "Balance", - "begin-set-up": "Begin setup", - "bio": "Bio", - "bio-added": "Bio added", - "bio-is-too-long": "Bio is too long", - "bio-updated": "Bio updated", - "biometric": "Biometric", - "biometric-auth-android-sensor-desc": "Touch sensor", - "biometric-auth-android-sensor-error-desc": "Failed", - "biometric-auth-android-title": "Authentication Required", - "biometric-auth-confirm-logout": "Relogin", - "biometric-auth-confirm-message": "Biometric authentication is required to continue, if not possible please unlock your keys with your password or passcode", - "biometric-auth-confirm-title": "You must authenticate!", - "biometric-auth-confirm-try-again": "Try again", - "biometric-auth-error": "Unable perform biometric authentication ({{code}})", - "biometric-auth-login-error-title": "Biometric authentication error", - "biometric-auth-login-ios-fallback-label": "Enter Password", - "biometric-auth-reason-login": "Login in Status", - "biometric-auth-reason-verify": "Verify authentication", - "biometric-secure-with": "Secure with {{bio-type-label}}", - "biometric-enable-keycard": "If you don't want to use your Keycard each time to access the app, enable {{bio-type-label}} sign in", - "biometric-enable": "If you don't want to enter your password each time to access the app, enable {{bio-type-label}} sign in", - "biometric-disable-bioauth": "disable {{bio-type-label}}", - "biometric-disable-password-title": "Disable password saving", - "biometric-disable-password-description": "If you disable this, you will also ", - "biometric-enable-button": "Enable {{bio-type-label}}", - "biometric-fingerprint": "Fingerprint", - "biometric-faceid": "Face ID", - "biometric-too-many-attempts": "Too many attempts. Enter the password manually or try again after some time", - "biometric-touchid": "Touch ID", - "blank-keycard-text": "You can proceed with your keycard once you've generated your keys and name", - "blank-keycard-title": "Looks like you’ve tapped \na blank keycard", - "block": "Block", - "user-blocked": "{{username}} blocked", - "user-unblocked": "{{username}} unblocked", - "unblock": "Unblock", - "block-contact": "Block this user", - "block-contact-details": "Blocking will delete this user's previous messages and stop new ones from reaching you", - "blocked-users": "Blocked users", - "bootnode-address": "Bootnode address", - "bootnode-details": "Bootnode details", - "bootnode-format": "enode://{enode-id}@{ip-address}:{port}", - "bootnodes": "Bootnodes", - "bootnodes-enabled": "Bootnodes enabled", - "bootnodes-settings": "Bootnodes settings", - "browsed-websites": "Browser history will appear here", - "browser": "Browser", - "browser-not-secure": "Connection is not secure! Do not sign transactions or send personal data on this site.", - "browser-secure": "Connection is secure. Make sure you really trust this site before signing transactions or entering personal data.", - "browsers": "Browsers", - "browsing-cancel": "Cancel", - "browsing-open-in-android-web-browser": "Open in Android", - "browsing-open-in-ios-web-browser": "Open in iOS", - "browsing-open-in-status": "Open in Status", - "browsing-site-blocked-description1": "We detected potential malicious activity from this address. To protect you and your wallet, we're preventing further navigation.\n\nIf you think this is an error, let us know in the ", - "browsing-site-blocked-description2": " public chat.", - "browsing-site-blocked-go-back": "Go back", - "browsing-site-blocked-title": "This site is blocked", - "browsing-title": "Browse", - "bug-report": "Report a bug", - "bug-report-description": "* Description", - "bug-report-description-placeholder": "Required, can't be empty", - "bug-report-steps": "Steps to reproduce", - "bug-report-steps-placeholder": "- open app\n- do something\n- and then somethig else...", - "bug-report-submit-email": "Submit by email with logs archive", - "bug-report-submit-gh-issue": "Submit a GitHub issue without logs", - "bug-report-too-short-description": "Description is too short", - "by-continuing-you-accept": "By continuing you accept our ", - "camera-access-error": "To grant the required camera permission, please go to your system settings and make sure that Status > Camera is selected.", - "can-not-add-yourself": "That's you, to start a chat choose someone else", - "cancel": "Cancel", - "cancel-keycard-setup": "Cancel Keycard setup", - "cancel-request?": "Cancel request?", - "cancel-request-to-join": "Cancel request to join", - "cannot-read-card": "Can't read card.\nPlease hold it to the back of your phone", - "cannot-use-default-pin": "Passcode 000000 is not allowed.\nPlease use another number", - "card-is-blank": "This card is blank", - "card-reseted": "Card has been reseted", - "card-unpaired": "Card has been unpaired from current device", - "change-fleet": "Change fleet to {{fleet}}", - "change-log-level": "Confirm and restart the app to change log level to {{log-level}}", - "change-logging-enabled": "Are you sure you want to {{enable}} logging?", - "change-passcode": "Change Passcode", - "change-password": "Change password", - "change-password-confirm-description": "Your data must now be re-encrypted with your new password. Please do not quit the app or turn off your device.", - "change-password-confirm-warning": "Re-encrypting data after changing password may take up to 3 min. Closing the app or locking phone while re-encryption is in progress will lead to data corruption and loss of your Status profile.", - "change-password-description": "Change password used to log in to Status and sign transactions", - "change-password-loading-header": "Keep app open on screen!", - "change-password-loading-description": "Data is now being re-encrypted with your new password, this may take up to 3min.", - "change-password-done-header": "Re-encryption complete!", - "change-password-done-description": "Please log out Status and log in using your new password", - "change-password-loading-warning": "Do not quit the app or turn off your device while in-progress.\n \nClosing the app or locking your phone while re-encryption is taking place will lead to data corruption and the loss of your Status profile.", - "change-password-new-password-label": "New password", - "change-password-new-password-placeholder": "Enter new password", - "change-password-old-password-label": "Current password", - "change-password-old-password-placeholder": "Enter current password", - "change-password-repeat-password-placeholder": "Repeat new password", - "change-pin": "Change 6-digit passcode", - "change-puk": "Change 12-digit PUK", - "change-pairing": "Change pairing code", - "change-pairing-title": "Create a new pairing code", - "change-pairing-description": "Changing the pairing code does not affect the current pairings. However, any new pairing will require the new code.", - "changed-amount-warning": "Amount was changed from {{old}} to {{new}}", - "changed-asset-warning": "Asset was changed from {{old}} to {{new}}", - "channel-on-status": "Channel on Status", - "community-on-status": "Community on Status", - "chaos-mode": "Chaos mode", - "chaos-unicorn-day": "Chaos Unicorn Day", - "chaos-unicorn-day-details": "🦄🦄🦄🦄🦄🦄🦄🚀!", - "chat": "Chat", - "chat-and-transact": "Chat and transact privately with friends", - "chat-key": "Chat key", - "chat-name": "Chat name", - "chat-settings": "Chat settings", - "chat-with-friends": "Chat privately with friends", - "chats": "Chats", - "check-before-syncing": "Check before syncing", - "check-before-syncing-doc-description": "To sync your devices successfully, make sure to check and complete these steps:", - "check-before-syncing-doc-checkbox-1": "Connect both devices to the same network", - "check-before-syncing-doc-checkbox-2": "Make sure you are logged in on the other device", - "check-before-syncing-doc-checkbox-3": "Disable the firewall and VPN on your devices", - "check-your-recovery-phrase": "Check your seed phrase", - "choose-authentication-method": "Choose an authentication method", - "clear": "Clear", - "clear-all": "Clear all", - "clear-history": "Clear history", - "clear-history-action": "Clear", - "clear-history-confirmation": "Clear history?", - "clear-history-confirmation-content": "Are you sure you want to clear this chat history?", - "clear-history-title": "Clear history?", - "close": "Close", - "close-app-button": "Confirm", - "close-app-content": "The app will stop and close. When you reopen it, the selected network will be used", - "close-app-title": "Warning!", - "command-button-send": "Send", - "common-names": "Common names", - "communities": "Communities", - "community-members": { - "one": "{{count}} member", - "other": "{{count}} members" - }, - "community-rules": "Community rules", - "share-all-current-and-future-addresses": "Share all current and future addresses", - "address-to-share": "Addresses to share", - "addresses-for-permissions": "Addresses for permissions", - "no-addresses-selected": "At least 1 address must be shared with community", - "confirm-changes": "Confirm changes", - "airdrop-addresses": "Address for airdrops", - "join-as": "Join as {{role}}", - "all-addresses": "All addresses", - "all-networks": "All networks", - "for-airdrops": "For airdrops", - "members-label": "Members", - "open-membership": "Open membership", - "member-kick": "Kick member", - "member-ban": "Ban member", - "membership-requests": "Membership requests", - "membership-request-denied": "Membership request denied", - "community-members-title": "Members", - "community-requests-to-join-title": "Membership requests", - "name-your-channel": "Name your channel", - "name-your-channel-placeholder": "Channel name", - "give-a-short-description": "Give a short description", - "describe-channel": "Describe the channel", - "communities-alpha": "Communities (alpha)", - "communities-verified": "✓ Verified Status Community", - "communities-enabled": "Communities enabled", - "request-access": "Request access", - "requesting": "Requesting", - "membership-request-pending": "Membership request pending", - "create-community": "Create a community", - "create-category": "Create category", - "rearrange-categories": "Rearrange Categories", - "edited": "Edited", - "edit-community": "Edit community", - "editing-message": "Editing message", - "community-edit-title": "Edit community", - "community-invite-title": "Invite", - "community-share-title": "Share", - "invite": "Invite", - "invite-contacts": "Invite people from contacts list", - "create-channel": "Create a channel", - "import-community": "Import a community", - "import-community-title": "Import a community", - "name-your-community": "Name your community", - "name-your-community-placeholder": "A catchy name", - "give-a-short-description-community": "Give it a short description", - "new-community-title": "New community", - "new-category": "New category", - "category-title": "Category title", - "membership-title": "Membership requirement", - "create-channel-title": "New channel", - "edit-channel-title": "Edit channel", - "community-thumbnail-image": "Thumbnail image", - "community-emoji-thumbnail-title": "Thumbnail", - "community-thumbnail-upload": "Upload", - "community-image-take": "Take a photo", - "community-image-pick": "Pick an image", - "community-image-delete": "", - "community-image-remove": "Remove", - "community-join-requirements-met": "Join requirements met", - "community-join-requirements-not-met": "Join requirements not met", - "community-join-requirements-changed": "Token requirements have changed", - "community-join-requirements-tokens-lost": "You no longer have the tokens required", - "community-channel-read-requirements-met": "View only requirements met", - "community-channel-read-requirements-not-met": "View only requirements not met", - "community-channel-write-requirements-met": "View and post requirements met", - "community-channel-write-requirements-not-met": "View and post requirements not met", - "community-enter-channel-info": "Entering channel will reveal your public addresses to the node owner", - "community-color": "Community colour", - "community-link": "Community link", - "community-color-placeholder": "Pick a colour", - "community-overview": "Community Overview", - "membership-button": "Membership requirement", - "membership-none": "None", - "membership-none-placeholder": "You can require new members to meet certain criteria before they can join. This can be changed at any time", - "membership-approval": "Require approval", - "membership-approval-description": "Your community is free to join, but new members are required to be approved by the community creator first", - "membership-invite": "Require invite from another member", - "membership-invite-description": "Your community can only be joined by an invitation from existing community members", - "membership-ens": "Require ENS username", - "membership-ens-description": "Your community requires an ENS username to be able to join", - "membership-free": "No requirement", - "membership-free-description": "Your community is free for anyone to join", - "community-roles": "Roles", - "community-key": "Community private key", - "community-key-placeholder": "Type your community private key", - "leave-community": "Leave community", - "enter-user-pk": "Enter user public key", - "import": "Import", - "complete-hardwallet-setup": "This card is now linked. You need it to sign transactions and unlock your keys", - "chat-notification-preferences": "Notification settings", - "completed": "Completed", - "confirm": "Confirm", - "confirmation-request": "Confirmation request", - "confirmations": "Confirmations", - "confirmations-helper-text": "When the transaction has 12 confirmations you can consider it settled.", - "connect": "Connect", - "connect-mailserver-content": "Connect to {{name}}?", - "connected": "Connected", - "connected-to": "Connected to", - "connecting": "Connecting...", - "connecting-requires-login": "Connecting to another network requires login", - "connection-with-the-card-lost": "Connection with the card\n has been lost", - "connection-with-the-card-lost-setup-text": "To resume the setup hold the card to\n the back of your phone and maintain\n card to phone contact", - "connection-with-the-card-lost-text": "To proceed hold the card to the back of your phone", - "contact-code": "Chat key", - "contact-s": { - "one": "contact", - "other": "contacts" - }, - "contacts": "Contacts", - "continue": "Continue", - "contract-address": "Contract address", - "contract-interaction": "Contract interaction", - "copy-info": "Copy info", - "copy-qr": "Copy code", - "copy-to-clipboard": "Copy", - "copy-transaction-hash": "Copy transaction ID", - "cost-fee": "Cost/Fee", - "counter-9-plus": "9+", - "counter-99-plus": "99+", - "create": "Create", - "create-profile": "Create profile", - "create-profile-password-info-box-title": "About your profile password", - "create-profile-password-info-box-description": "Your Status keys are the foundation of your self-sovereign identity in Web3. You have complete control over these keys, which you can use to sign transactions, access your data, and interact with Web3 services.\n\nYour keys are always securely stored on your device and protected by your Status profile password. Status doesn't know your password and can't reset it for you. If you forget your password, you may lose access to your Status profile and wallet funds.\n\nRemember your Status password and don't share it with anyone.", - "create-account": "Create account", - "create-a-pin": "Create a 6-digit passcode", - "create-a-puk": "Create a 12-digit PUK", - "create-group-chat": "Create group chat", - "name-your-group": "Name your group", - "create-multiaccount": "Generate keys", - "create-new-key": "Get new keys", - "create-pin": "Create 6-digit passcode", - "create-pin-description": "You'll need your card + this 6-digit passcode to unlock Status and to confirm transactions", - "created-group-chat-description": "You created the group {{group-name}}", - "members-count": "{{count}} members", - "cryptokitty-name": "CryptoKitty #{{id}}", - "currency": "Currency", - "currency-display-name-aed": "Emirati Dirham", - "currency-display-name-afn": "Afghanistan Afghani", - "currency-display-name-ars": "Argentine Peso", - "currency-display-name-aud": "Australian Dollar", - "currency-display-name-bbd": "Barbados Dollar", - "currency-display-name-bdt": "Bangladeshi Taka", - "currency-display-name-bgn": "Bulgarian Lev", - "currency-display-name-bhd": "Bahraini Dinar", - "currency-display-name-bnd": "Brunei Darussalam Dollar", - "currency-display-name-bob": "Bolivia Bolíviano", - "currency-display-name-brl": "Brazil Real", - "currency-display-name-btn": "Bhutanese Ngultrum", - "currency-display-name-cad": "Canada Dollar", - "currency-display-name-chf": "Switzerland Franc", - "currency-display-name-clp": "Chile Peso", - "currency-display-name-cny": "China Yuan Renminbi", - "currency-display-name-cop": "Colombia Peso", - "currency-display-name-crc": "Costa Rica Colon", - "currency-display-name-czk": "Czech Koruna", - "currency-display-name-dkk": "Denmark Krone", - "currency-display-name-dop": "Dominican Republic Peso", - "currency-display-name-egp": "Egypt Pound", - "currency-display-name-etb": "Ethiopian Birr", - "currency-display-name-eur": "Euro", - "currency-display-name-gbp": "British Pound", - "currency-display-name-gel": "Georgian Lari", - "currency-display-name-ghs": "Ghana Cedi", - "currency-display-name-hkd": "Hong Kong Dollar", - "currency-display-name-hrk": "Croatia Kuna", - "currency-display-name-huf": "Hungary Forint", - "currency-display-name-idr": "Indonesia Rupiah", - "currency-display-name-ils": "Israel Shekel", - "currency-display-name-inr": "India Rupee", - "currency-display-name-isk": "Iceland Krona", - "currency-display-name-jmd": "Jamaica Dollar", - "currency-display-name-jpy": "Japanese Yen", - "currency-display-name-kes": "Kenyan Shilling", - "currency-display-name-krw": "Korea (South) Won", - "currency-display-name-kwd": "Kuwaiti Dinar", - "currency-display-name-kzt": "Kazakhstan Tenge", - "currency-display-name-lkr": "Sri Lanka Rupee", - "currency-display-name-mad": "Moroccan Dirham", - "currency-display-name-mdl": "Moldovan Leu", - "currency-display-name-mur": "Mauritius Rupee", - "currency-display-name-mwk": "Malawian Kwacha", - "currency-display-name-mxn": "Mexico Peso", - "currency-display-name-myr": "Malaysia Ringgit", - "currency-display-name-mzn": "Mozambique Metical", - "currency-display-name-nad": "Namibia Dollar", - "currency-display-name-ngn": "Nigeria Naira", - "currency-display-name-nok": "Norway Krone", - "currency-display-name-npr": "Nepal Rupee", - "currency-display-name-nzd": "New Zealand Dollar", - "currency-display-name-omr": "Oman Rial", - "currency-display-name-pen": "Peru Sol", - "currency-display-name-pgk": "Papua New Guinean Kina", - "currency-display-name-php": "Philippines Peso", - "currency-display-name-pkr": "Pakistan Rupee", - "currency-display-name-pln": "Polish Zloty", - "currency-display-name-pyg": "Paraguay Guarani", - "currency-display-name-qar": "Qatar Riyal", - "currency-display-name-ron": "Romania Leu", - "currency-display-name-rsd": "Serbia Dinar", - "currency-display-name-rub": "Russia Ruble", - "currency-display-name-sar": "Saudi Arabia Riyal", - "currency-display-name-sek": "Sweden Krona", - "currency-display-name-sgd": "Singapore Dollar", - "currency-display-name-thb": "Thailand Baht", - "currency-display-name-try": "Turkish Lira", - "currency-display-name-ttd": "Trinidad and Tobago Dollar", - "currency-display-name-twd": "Taiwan New Dollar", - "currency-display-name-tzs": "Tanzanian Shilling", - "currency-display-name-uah": "Ukraine Hryvnia", - "currency-display-name-ugx": "Ugandan Shilling", - "currency-display-name-usd": "United States Dollar", - "currency-display-name-uyu": "Uruguay Peso", - "currency-display-name-vef": "Venezuela Bolívar", - "currency-display-name-vnd": "Vietnam Dong", - "currency-display-name-zar": "South Africa Rand", - "current-network": "Current network", - "current-pin": "Enter 6-digit passcode", - "current-pin-description": "Enter your 6-digit passcode to proceed", - "custom": "Custom", - "dapp": "ÐApp", - "dapp-would-like-to-connect-wallet": "would like to connect to", - "dapps": "dApps", - "dapps-permissions": "DApp permissions", - "data": "Data", - "datetime-ago": "ago", - "datetime-ago-format": "{{number}} {{time-intervals}} {{ago}}", - "datetime-ago-format-short": "{{number}}{{time-intervals}}", - "datetime-day": { - "one": "day", - "other": "days" - }, - "datetime-hour": { - "one": "hour", - "other": "hours" - }, - "datetime-minute": { - "one": "minute", - "other": "minutes" - }, - "datetime-second": { - "one": "second", - "other": "seconds" - }, - "datetime-day-short": { - "one": "D", - "other": "D" - }, - "datetime-hour-short": { - "one": "H", - "other": "H" - }, - "datetime-minute-short": { - "one": "M", - "other": "M" - }, - "datetime-second-short": { - "one": "S", - "other": "S" - }, - "datetime-today": "today", - "datetime-yesterday": "Yesterday", - "decimals": "Decimals", - "decline": "Decline", - "decryption-failed-content": "An error occured decrypting your data. You might need to erase your old data and generate a new account. Tap “Apply” to erase or “Cancel” to try again", - "default": "Default", - "default-account-placeholder": "Account Name", - "default-watched-address-placeholder": "Watched address", - "delete": "Delete", - "delete-and-leave-group": "Delete and leave group", - "delete-bootnode": "Delete bootnode", - "delete-bootnode-are-you-sure": "Are you sure you want to delete this bootnode?", - "delete-bootnode-title": "Delete bootnode", - "close-chat": "Close chat", - "close-chat-confirmation": "This conversation will disappear from the list.\nIt will appear again if the contact sends a new message.", - "delete-category-confirmation": "Are you sure you want to delete this category?", - "delete-confirmation": "Delete?", - "delete-for-me": "Delete for me", - "delete-mailserver": "Delete Status node", - "delete-mailserver-are-you-sure": "Are you sure you want to delete this Status node?", - "delete-mailserver-title": "Delete Status node", - "delete-message": "Delete message", - "delete-my-account": "Delete my account", - "delete-network-confirmation": "Are you sure you want to delete this network?", - "delete-network-error": "Please connect to a different network before deleting this one", - "delete-network-title": "Delete network?", - "delete-node": "Delete node", - "delete-node-are-you-sure": "Are you sure you want to delete this node?", - "delete-node-title": "Delete node", - "delete-profile": "Delete profile", - "delete-my-profile": "Delete my profile", - "delete-profile-warning": "Warning: If you don’t have your seed phrase written down, you will lose access to your funds after you delete your profile", - "deleted-this-message": "deleted this message", - "enter-channel": "Enter channel", - "profile-deleted-title": "Profile deleted", - "profile-deleted-content": "Your profile was successfully deleted", - "profile-deleted-keycard": "You can now restore another key pair on your Keycard", - "deny": "Deny", - "description": "Description", - "dev-mode": "Development mode", - "dev-mode-settings": "Development mode settings", - "device-syncing": "Device syncing", - "devices": "Devices", - "disable": "disable", - "disabled": "Disabled", - "disconnected": "Chat offline", - "discover": "Discover", - "discover-web3": "Discover web3", - "dismiss": "Dismiss", - "discover-communities": "Discover communities", - "done": "Done", - "edit": "Edit", - "edit-group": "Edit group", - "edit-profile": "Edit Profile", - "emojihash": "Emojihash", - "emojihash-description": "A visual representation of your chat key. It will help other users recognize your profile.", - "emojis": "Emojis", - "empty-chat-description": "There are no messages \nin this chat yet", - "empty-chat-description-one-to-one": "Any messages you send here are encrypted and can only be read by you and ", - "empty-chat-description-public": "It's been quiet here for the last {{quiet-hours}}. Start the conversation or ", - "cleared-chat-description-public": "It's been quiet here. Start the conversation or ", - "empty-chat-description-community": "It's been quiet here for the last {{quiet-hours}}.", - "empty-chat-description-public-share-this": "share this chat.", - "enable": "Enable", - "enable-notifications-sub-title": "Receive notifications about your new messages or wallet transactions", - "encrypt-with-password": "Encrypt with password", - "encrypted-key-pairs": "Encrypted key pairs", - "encrypted-key-pairs-code": "Encrypted key pairs code", - "ending-not-allowed": "{{ending}} ending is not allowed", - "ends-with-space": "Cannot end with space", - "ens-10-SNT": "10 SNT", - "ens-add-username": "Add username", - "ens-agree-to": "Agree to ", - "ens-chat-settings": "Chat settings", - "ens-custom-domain": "Custom domain", - "ens-custom-username-hints": "Type the entire username including the custom domain like username.domain.eth", - "ens-custom-username-taken": "Username doesn’t belong to you :(", - "ens-deposit": "Deposit", - "ens-displayed-with": "Your messages are displayed to others with", - "ens-get-name": "Get a universal username", - "ens-got-it": "Ok, got it", - "ens-locked": "Username locked. You won’t be able to release it until {{date}}", - "ens-network-restriction": "Only available on Mainnet", - "ens-no-usernames": "You don't have any username connected", - "ens-powered-by": "Powered by Ethereum Name Services", - "ens-primary-username": "Primary username", - "ens-register": "Register", - "ens-registration-in-progress": "Registration in progress...", - "ens-registration-failure": "Registration failed", - "ens-dismiss-message": "Click here to dismiss", - "ens-registration-failed": "To register the username, please try again.", - "ens-registration-failed-title": "Transaction failed", - "ens-release-username": "Release username", - "ens-remove-hints": "Removing will detach the username from your key.", - "ens-remove-username": "Remove username", - "ens-saved": " is now connected with your chat key and can be used in Status.", - "ens-saved-title": "Username added", - "ens-show-username": "Show my ENS username in chats", - "ens-terms-header": "Terms of name registration", - "ens-terms-point-1": "Funds are deposited for 1 year. Your SNT will be locked, but not spent.", - "ens-terms-point-10": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e (ENS Registry).", - "ens-terms-point-2": "After 1 year, you can release the name and get your deposit back, or take no action to keep the name.", - "ens-terms-point-3": "If terms of the contract change — e.g. Status makes contract upgrades — user has the right to release the username regardless of time held.", - "ens-terms-point-4": "The contract controller cannot access your deposited funds. They can only be moved back to the address that sent them.", - "ens-terms-point-5": "Your address(es) will be publicly associated with your ENS name.", - "ens-terms-point-6": "Usernames are created as subdomain nodes of stateofus.eth and are subject to the ENS smart contract terms.", - "ens-terms-point-7": "You authorize the contract to transfer SNT on your behalf. This can only occur when you approve a transaction to authorize the transfer.", - "ens-terms-point-8": "These terms are guaranteed by the smart contract logic at addresses:", - "ens-terms-point-9": "{{address}} (Status UsernameRegistrar) ", - "ens-terms-registration": "Terms of name registration.", - "ens-test-message": "Hey", - "ens-transaction-pending": "Transaction pending...", - "ens-understand": "I understand that my wallet address will be publicly connected to my username.", - "ens-username": "ENS username", - "ens-username-available": "✓ Username available!", - "ens-username-connected": "This user name is owned by you and connected with your chat key.", - "ens-username-connection-confirmation": "{{username}} will be connected once the transaction is complete.", - "ens-username-hints": "At least 4 characters. Latin letters, numbers, and lowercase only.", - "ens-username-invalid": "Letters and numbers only.", - "ens-username-owned": "✓ Username is owned by you. ", - "ens-username-registration-confirmation": "Nice! You own {{username}} once the transaction is complete.", - "ens-username-you-can-follow-progress": "You can follow the progress in the Transaction History section of your wallet.", - "ens-usernames": "ENS usernames", - "ens-usernames-details": "Register a universal username to be easily recognized by other users", - "wallet-address": "Wallet address", - "ens-want-custom-domain": "I own a name on another domain", - "ens-want-domain": "I want a stateofus.eth domain", - "ens-welcome-hints": "ENS names transform those crazy-long addresses into unique usernames.", - "ens-welcome-point-customize": "An ENS name can replace your random 3-word name in chat. Be @yourname instead of {{name}}.", - "ens-welcome-point-customize-title": "Customize your chat name", - "ens-welcome-point-simplify": "You can receive funds to your easy-to-share ENS name rather than your hexadecimal hash (0x...).", - "ens-welcome-point-simplify-title": "Simplify your ETH address", - "ens-welcome-point-receive": "Others can send you funds via chat in one simple step.", - "ens-welcome-point-receive-title": "Receive transactions in chat", - "ens-welcome-point-register": "Register once to keep the name forever. After 1 year you can release the name and get your SNT back.", - "ens-welcome-point-register-title": "10 SNT to register", - "ens-welcome-point-verify": "You can verify and add any usernames you own in the next steps.", - "ens-welcome-point-verify-title": "Already own a username?", - "ens-your-username": "Your username", - "ens-your-usernames": "Your usernames", - "ens-your-your-name": "Your ENS name", - "ens-username-already-added": "Username is already connected with your chat key and can be used inside Status.", - "ens-username-connected-continue": "Continue to set `Show my ENS username in chats`.", - "ens-username-connected-with-different-key": "Continuing will require a transaction to connect the username with your current chat key.", - "ens-username-owned-continue": "Continuing will connect this username with your chat key.", - "ens-username-taken": "Username already taken :(", - "ens-name-not-found": "Cannot resolve ENS name", - "ens-username-registration-invalid": "Warning! Registration process has finished in invalid state. DO NOT USE the name for wallet transactions and reach out to our support at support@status.im", - "ens-username-invalid-name-warning": "Registration process of one of your ens names has finished in invalid state. DO NOT USE the name for wallet transactions and reach out to our support at support@status.im", - "enter-12-words": "Enter the 12 words of your seed phrase, separated by single spaces", - "enter-a-private-key": "Enter a private key", - "enter-a-seed-phrase": "Enter a seed phrase", - "enter-address": "Enter address", - "enter-contact-code": "ENS (vitalik94) or chat key (0x04…)", - "enter-pair-code": "Enter your pairing code", - "pair-code-placeholder": "Pair code...", - "enter-pair-code-description": "Pairing code can be set from an already paired Status client", - "enter-password": "Enter password", - "enter-password-migration-prompt": "Enter your password to move contacts, chats and settings along with your keys", - "migration-successful": "Migration successful", - "migration-successful-text": "Account succesfully migrated to Keycard", - "skip": "Skip", - "password-placeholder": "Password...", - "confirm-password-placeholder": "Confirm your password...", - "ens-or-chat-key": "ENS or Chatkey", - "user-found": "User found", - "enter-pin": "Enter 6-digit passcode", - "enter-puk-code": "Enter PUK code", - "enter-puk-code-description": "6-digit passcode has been blocked.\n Please enter PUK code to unblock passcode.", - "enter-recipient-address-or-username": "Enter address or username of the recipient", - "enter-seed-phrase": "Enter seed phrase", - "enter-url": "Enter URL", - "enter-watch-account-address": "Scan a QR code\nor\nenter the address to watch", - "enter-word": "Enter word", - "enter-your-code": "Enter your 6-digit passcode", - "enter-your-password": "Enter your password", - "error": "Error", - "error-unable-to-get-balance": "Unable to get balance", - "error-unable-to-get-prices": "Currency conversion error. Refresh your screen to try again.", - "error-unable-to-get-token-balance": "Unable to get token balance", - "errors": "Errors", - "eth": "ETH", - "ethereum-node-started-incorrectly-description": "Ethereum node was started with incorrect configuration, application will be stopped to recover from that condition. Configured network id = {{network-id}}, actual = {{fetched-network-id}}", - "ethereum-node-started-incorrectly-title": "Ethereum node started incorrectly", - "etherscan-lookup": "Look up on Etherscan", - "explore-the-decentralized-web": "Explore and interact with the decentralized web", - "export-account": "Export account", - "export-key": "Export private key", - "community-private-key": "Community private key", - "failed": "Failed", - "faq": "Frequently asked questions", - "fetch-messages": "Fetch messages", - "fetch-timeline": "↓ Fetch", - "find": "Find", - "find-your-friends": "Find your friends with their ENS or Chatkey", - "finish": "Finish", - "finishing-card-setup": "Finishing card setup", - "fleet": "Fleet", - "fleet-settings": "Fleet settings", - "follow-your-interests": "Jump into a public chat and meet new people", - "follow": "Follow", - "free": "↓ Free", - "from-capitalized": "From", - "from": "from", - "gas-limit": "Gas limit", - "gas-price": "Gas price", - "gas-used": "Gas used", - "generate-a-key": "Generate keys", - "generate-a-new-account": "Generate an account", - "generate-a-new-key": "Generate a new key", - "generate-account": "Generate keys", - "generate-new-key": "Generate keys", - "your-keys": "Your keys", - "generating-codes-for-pairing": "> Downloading product software to card\n > Generating unlocking & pairing codes", - "generating-keys": "Generating keys...", - "you-will-need-this-code": "You'll need this code to open Status and sign transactions", - "generating-mnemonic": "Generating seed phrase", - "get-started": "Get started", - "get-status-at": "Get Status at http://status.im", - "get-stickers": "Get Stickers", - "gif": "GIF", - "go-to-settings": "Go to Settings...", - "got-it": "Got it", - "group-chat": "Group chat", - "group-chat-admin": "Admin", - "group-chat-admin-added": "**{{member}}** has been made admin", - "group-chat-created": "**{{member}}** created the group **{{name}}**", - "group-chat-decline-invitation": "Decline invitation", - "group-chat-member-added": "**{{member}}** has been invited", - "group-chat-member-joined": "**{{member}}** has joined the group", - "group-chat-member-removed": "**{{member}}** left the group", - "group-chat-members-count": "{{selected}}/{{max}} members", - "group-chat-name-changed": "**{{member}}** changed the group's name to **{{name}}**", - "group-chat-not-member": "You are not a member of this group", - "group-chat-no-contacts": "You don't have any contacts yet.\nInvite your friends to start chatting", - "leave-chat": "Leave chat", - "leave-confirmation": "Leave {{chat-name}}", - "leave-chat-confirmation": "Chat history will be removed from your device. After rejoining you won't be able to retrieve any of your history.", - "group-chat-all-contacts-invited": "All your contacts are already in the group", - "group-info": "Group info", - "gwei": "Gwei", - "hash": "Hash", - "have-a-sync-code?": "Have a sync code?", - "help": "help", - "help-capitalized": "Help", - "help-center": "Help Center", - "hide-content-when-switching-apps": "Block screenshots", - "hide-content-when-switching-apps-ios": "Hide preview", - "history": "History", - "history-nodes": "Status nodes", - "hold-card": "Hold card to the back\n of your phone", - "hold-to-post-1": "Hold", - "hold-to-post-2": "to post", - "home": "Home", - "hooks": "Hooks", - "how-to-scan": "How to scan", - "identifier": "Identifier", - "identicon-ring": "Identicon ring", - "identicon-ring-explanation": "This multicoloured ring around your profile picture represents your chat key.", - "if-you-cancel": "If you cancel, you can request to join this community at any point.", - "if-you-have-status-on-another-device": "If you have Status on another device", - "image-remove-current": "Remove current photo", - "image-source-gallery": "Select from gallery", - "image-source-make-photo": "Capture", - "image-source-title": "Edit picture", - "profile-pic-take": "Take photo", - "profile-pic-pick": "Select from gallery", - "profile-pic-remove": "Remove photo", - "in": "in", - "in-contacts": "In contacts", - "incoming": "Incoming", - "incoming-transaction": "Incoming transaction", - "incorrect-code": [ - "str", - "Sorry the code was incorrect, please enter it again" - ], - "initialization": "Initialization", - "input-data": "Input data", - "install": "↓ Install", - "intro-message1": "Welcome to Status!\nTap this message to set your password and get started.", - "intro-privacy-policy-note1": "Status does not collect or profit from your personal data. By continuing, you agree with the ", - "intro-privacy-policy-note2": "privacy policy", - "intro-text": "Status is your gateway to the decentralized web", - "intro-text1": "Chat over a peer-to-peer, encrypted network where messages can’t be censored or hacked", - "intro-text2": "Send and receive digital assets anywhere in the world—no bank account required", - "intro-text3": "Explore games, exchanges and social networks where you alone own your data", - "intro-title1": "Truly private communication", - "intro-title2": "Secure crypto wallet", - "intro-title3": "Decentralized apps", - "intro-wizard-text1": "A set of keys controls your account. Your keys live on your phone, so only you can use them", - "intro-wizard-text2": "One key is for chat. It comes with a readable name that can’t be changed.", - "intro-wizard-text3": "If you own a Keycard, store your keys there for enhanced security.", - "intro-wizard-text4": "Secure and encrypt your keys", - "intro-wizard-text6": "Status will notify you about new messages. You can edit your notification preferences later in settings", - "intro-wizard-title-alt4": "Create a password", - "intro-wizard-title-alt5": "Confirm your password", - "intro-wizard-title1": "Get your keys", - "intro-wizard-title2": "Choose a chat name", - "intro-wizard-title3": "Choose key storage", - "intro-wizard-title4": "Create a 6-digit passcode", - "intro-wizard-title5": "Confirm the passcode", - "intro-wizard-title6": "Enable notifications", - "are-you-sure-to-cancel": "Are you sure you want to cancel?", - "you-will-start-from-scratch": "You will start from scratch with a new set of keys", - "invalid-address-qr-code": "Scanned QR code doesn't contain a valid address", - "invalid-format": "Invalid format\nMust be {{format}}", - "invalid-key-confirm": "Apply", - "invalid-key-content": "Database can’t be encrypted because a file is corrupt. Your funds and chat key are safe. Other data, like your chats and contacts, cannot be restored. “{{erase-multiaccounts-data-button-text}}” button, will remove all other data and allows you to access your funds and send messages", - "invalid-number": "Invalid number", - "invalid-pairing-password": "Invalid pairing password", - "invalid-range": "Invalid format, must be between {{min}} and {{max}}", - "invalid-username-or-key": "Invalid username or chat key", - "invalid-ens-or-key": "Invalid ENS or Chat key", - "invalid-characters-bio": "Invalid characters. Standard keyboard characters and emojis only.", - "join-me": "Hey join me on Status: {{url}}", - "join-a-community": "or join a community", - "join-open-community": "Join Community", - "joined-community": "You joined “{{community}}”", - "join-decentralised-communities": "Join Decentralized Communities", - "join-community-to-post": "Join community to post", - "no-permissions-to-post": "Sorry, you don't have permissions to post in this channel", - "http-gateway-error": "Oops, request failed!", - "sign-request-failed": "Could not sign message", - "simple": "Simple", - "invite-friends": "Invite friends", - "invite-people": "Invite people", - "invite-people-from-contacts": "Invite people from contact list", - "invite-reward": "Earn crypto for every friend you invite!", - "invite-select-account": "Select an account to receive your referral bonus", - "invited": "invited", - "invite-button": "Invite", - "invite-receive-account": "Account to receive your referral bonus", - "how-it-works": "How it works", - "invite-warning": "This promotion is only valid for users of an Android device, who aren't residents of US. Friend needs to confirm referral within 7 days", - "invite-instruction-first": "You send a unique invite link to your friend to download and join Status", - "invite-instruction-second": "Your friend downloads Status and creates an account (on Android)", - "invite-instruction-third": "A chat with your friend is started, where they confirm your referral", - "invite-instruction-fourth": "You receive your referral bonus and your friend the Starter Pack", - "invite-instruction-fifth": "You can choose to redeem your referral bonus anytime.", - "invite-reward-you": "You: ", - "invite-reward-you-name": "Referral bonus", - "invite-reward-you-description": "Invite a friend and receive {{reward}} as referral bonus. Use it to get stickers, an ENS name and try dapps", - "invite-reward-friend": "Friend: ", - "invite-reward-friend-name": "Starter Pack", - "invite-reward-friend-description": "Your friend will receive a Starter Pack consisting of some {{reward}} to get started", - "invite-privacy-policy1": "By accepting you agree to the referral program", - "invite-privacy-policy2": "Terms and Conditions.", - "invite-privacy-policy-public": "You installed Status through a referral link. By joining this chat you attribute your referrer and agree to the", - "invite-chat-name": "Friend referral", - "invite-chat-starter-pack": "Starter Pack", - "invite-chat-intro": "You were referred by a friend to join Status. Here’s some crypto to get you started! Use it to register an ENS name or buy a sticker pack", - "invite-public-chat-home": "Referral invitation", - "invite-public-chat-intro": "Here’s some crypto to get you started! Use it to register an ENS name or buy a sticker pack", - "invite-chat-accept": "Accept", - "invite-chat-pending": "Pending", - "invite-chat-accept-join": "Accept and Join", - "invite-chat-rule": "Accepting will also reward your friend with a crypto referral bonus", - "redeem-now": "Redeem now", - "redeem-amount": "{{quantity}} bonuses available", - "redeem-success": "Redeem bonus success!", - "attribution-received": "{{attrib}} out of {{max}} bonuses received", - "advertiser-starter-pack-title": "Starter Pack", - "advertiser-starter-pack-description": "Here’s some crypto to get you started! Use it to get stickers, an ENS name and try dapps", - "advertiser-title": "Privacy by default", - "advertiser-description": "You’ve discovered Status thanks to a partner. Do you mind if Status checks your IP address once so they get rewarded? This information will not be used for anything else and it will be removed completely after 7 days.", - "advertiser-starter-pack-accept": "Accept", - "advertiser-starter-pack-decline": "Decline", - "dapp-starter-pack-title": "Starter Pack", - "dapp-starter-pack-description": "Here’s some crypto to get you started! Use it to get stickers, an ENS name and try dapps", - "dapp-starter-pack-accept": "Accept and Open", - "duration-estimate": "Duration estimate", - "starter-pack-coming": "Starter Pack coming your way", - "starter-pack-coming-description": "Can take a few minutes to hours", - "starter-pack-received": "Starter Pack received", - "starter-pack-received-description": "Here’s some crypto to get you started! Use it to get stickers, an ENS name and try dapps", - "join-group-chat": "Join group", - "join-group-chat-description": "{{username}} invited you to join the group {{group-name}}", - "joined-group-chat-description": "You've joined {{group-name}} from invitation by {{username}}", - "key": "Key", - "keycard": "Keycard", - "keycard-access-reset": "Keycard access is reset", - "keycard-can-use-with-new-passcode": "You can use this card with your new passcode", - "keycard-applet-install-instructions": "To install the applet please follow the instructions on https://github.com/status-im/keycard-cli#keycard-applet-installation", - "keycard-blocked": "Keycard has been blocked.\nYou need to reset card to continue using it.", - "keycard-cancel-setup-text": "This will cancel keycard setup. It's highly recommended to finish the setup in order to use keycard. Do you really want to cancel?", - "keycard-cancel-setup-title": "Dangerous operation", - "keycard-desc": "Own a Keycard? Store your keys on it; you’ll need it for transactions", - "keycard-dont-ask-card": "Don't ask for card to sign in", - "keycard-reset-passcode": "Reset passcode", - "keycard-factory-reset": "Return card to factory settings", - "keycard-factory-reset-title": "Are you sure you want to perform a factory reset?", - "keycard-factory-reset-text": "Performing this will delete any mnemonic phrase stored on the card. Make sure you have a backup of the mnemonic phrase you've been using with this Keycard.", - "keycard-enter-new-passcode": "Enter new passcode {{step}}/2", - "keycard-has-multiaccount-on-it": "This card is full. Each card can hold one main key pair", - "keycard-onboarding-finishing-header": "Finishing up", - "keycard-onboarding-intro-header": "Store your keys on Keycard", - "keycard-onboarding-intro-text": "Get ready, this might take a few minutes, but it's important to secure your account", - "keycard-onboarding-pairing-header": "Pairing the card...", - "keycard-onboarding-preparing-header": "Preparing the card...", - "keycard-onboarding-puk-code-header": "Write codes down\n and store them securely", - "keycard-onboarding-recovery-phrase-description": "You need this seed phrase to get your key back. Write it down. Keep it safe, offline, and separate from this device.", - "keycard-onboarding-recovery-phrase-header": "Back up seed phrase", - "keycard-onboarding-recovery-phrase-text": "For your eyes only. This is the magical seed used to generate your key.", - "keycard-onboarding-start-header": "Hold card to the back\n of your phone to start", - "keycard-onboarding-pin-text": "You will need to create a 6-digit passcode which will be used to protect access to your Keycard.", - "keycard-onboarding-mnemonic-text": "You will also need a piece of paper and a pencil to write down your seed phrase.", - "keycard-onboarding-start-step1": "Create a passcode", - "keycard-onboarding-start-step1-text": "Around 1 minute. Create a 6-digit passcode to encrypt your keys", - "keycard-onboarding-start-step2": "Write down PUK and the pairing code", - "keycard-onboarding-start-step2-text": "Around 1 minute. You are going to need a piece of paper and a pencil for that", - "keycard-onboarding-start-step3": "Back up the seed phrase", - "keycard-onboarding-start-step3-text": "Around 1 minute. Also a piece of paper and a pencil are necessary", - "keycard-onboarding-start-text": "And maintain card to phone contact\n during the setup. The setup will take around 4 minutes", - "keycard-recovery-intro-button-text": "Begin recovery", - "keycard-recovery-intro-header": "Recover keys stored on keycard", - "keycard-recovery-intro-text": "If you generated keys using a keycard before and now want to use these keys on this device", - "keycard-recovery-no-key-header": "There’s nothing to \nrecover here", - "keycard-recovery-no-key-text": "Your Keycard has no key stored on it. In order to use it, generate a new key and choose your Keycard to store the key", - "keycard-recovery-phrase-confirm-header": "Confirm seed phrase", - "keycard-recovery-phrase-confirmation-text": "You won’t have a second chance! If you lose access, for example by losing your keycard, you can only access your keys with your seed phrase. No one, but you has your seed phrase. Write it down. Keep it safe.", - "keycard-recovery-phrase-confirmation-title": "Written the seed phrase down?", - "keycard-recovery-success-header": "Your keys have been\n successfully recovered", - "keycard-redeem-title": "Redeem to", - "keycard-redeem-tx": "Redeem assets", - "keycard-redeem-tx-desc": "Tap the card to sign and receive assets", - "keycard-unauthorized-operation": "You're unauthorized to perform this operation.\n Please tap valid card and try again.", - "keycard-is-frozen-title": "Keycard is frozen", - "keycard-is-frozen-details": "To protect your assets, your card is frozen. Reset your card to unfreeze it and be able to send transactions. You can do this with your PUK or your mnemonic.", - "keycard-is-frozen-reset": "Reset with PUK", - "keycard-is-frozen-factory-reset": "Reset with mnemonic", - "your-card-is-frozen": "Your Keycard is frozen. Reset card access", - "keycard-is-blocked-title": "Keycard is blocked", - "keycard-is-blocked-details": "You can no longer use this card to access or sign for this account. There have been too many failed passcode and PUK attempts.", - "keycard-is-blocked-instructions": "To access your account you will need to factory reset your card. Tap the button below to start the procedure, you will need your mnemonic.", - "language": "Language", - "learn-more": "Learn more", - "learn-more-about-keycard": "Learn more about Keycard", - "leave": "Leave", - "leave-community?": "Leave community?", - "leave-community-message": "We’ll be sad to see you go but remember, you can come back at any time!", - "left-community": "You left “{{community}}”", - "legacy": "Legacy", - "joined": "Joined", - "leave-group": "Leave group", - "left": "left", - "lets-go": "Let's go!", - "les-ulc": "LES/ULC", - "links": "Links", - "linked-on": "Linked on {{date}}", - "load-messages-before": "before {{date}}", - "load-more-messages": "↓ Fetch more messages", - "load-more-timeline": "↓ Fetch more", - "loading": "Loading...", - "log-level": "Log level", - "log-level-settings": "Log level settings", - "logging": "Logging", - "logging-enabled": "Logging enabled?", - "login-pin-description": "Enter your 6-digit passcode to unlock your keys", - "logout": "Log out", - "logout-app-content": "The account will be logged out. When you unlock it again, the selected network will be used", - "logout-are-you-sure": "Are you sure you want\nto log out?", - "logout-title": "Log out?", - "logout-key-management": "You need to log out to access key management.", - "looking-for-cards": "Looking for cards...", - "lost-connection": "Lost connection", - "mailserver-address": "Status node address", - "mailserver-automatic": "Automatic selection", - "mailserver-automatic-switch-explanation": "Choose the fastest Status node available", - "mailserver-connection-error": "Could not connect to Status node", - "mailserver-details": "Status node details", - "mailserver-error-content": "The Status node you selected couldn't be reached.", - "mailserver-error-title": "Error connecting to Status node", - "mailserver-format": "enode://{enode-id}@{ip-address}:{port}", - "mailserver-pick-another": "Pick another Status node", - "mailserver-reconnect": "Could not connect to Status node. Tap to reconnect", - "mailserver-request-error-content": "The following error was returned by the Status node: {{error}}", - "mailserver-request-error-status": "An error occured while fetching history, check the logs for details", - "mailserver-request-error-title": "Status node request error", - "mailserver-request-retry": "Retry request", - "mailserver-retry": "Retry", - "main-currency": "Main currency", - "main-networks": "Main networks", - "main-wallet": "Main Wallet", - "make-admin": "Make admin", - "make-moderator": "Make moderator", - "make-sure-no-camera-warning": "Make sure no camera or person can see this screen before revealing", - "manage-keys-and-storage": "Manage keys and storage", - "mark-as-read": "Mark as read", - "mark-all-read": "Mark all read", - "members": { - "one": "1 member", - "other": "{{count}} members" - }, - "members-active": { - "one": "1 member", - "other": "{{count}} members" - }, - "members-active-none": "no members", - "members-title": "Members", - "message": "Message", - "message-deleted": "Message deleted", - "message-deleted-for-everyone": "Message deleted for everyone", - "message-deleted-for-everyone-count": { - "one": "1 message deleted for everyone", - "other": "{{count}} messages deleted for everyone" - }, - "message-deleted-for-you": "Message deleted for you", - "message-deleted-for-you-count": { - "one": "1 message deleted for you", - "other": "{{count}} messages deleted for you" - }, - "message-not-sent": "Message not sent", - "message-options-cancel": "Cancel", - "message-reply": "Reply", - "replying-to": "Replying to {{author}}", - "data-syncing": "Data syncing", - "messages": "Messages", - "chat-is-a-contact": "Contact", - "chat-is-not-a-contact": "Not a contact", - "might-break": "Might break some ÐApps", - "migrations-failed-content": "{{message}}\nschema version: initial {{initial-version}}, current {{current-version}}, last {{last-version}}\n\nA database error occured. Your funds and chat key are safe. Other data, like your chats and contacts, cannot be restored. \"{{erase-multiaccounts-data-button-text}}\" button, will remove all other data and allows you to access your funds and send messages.", - "mobile-network-ask-me": "Ask me when on mobile network", - "mobile-network-continue-syncing": "Continue syncing", - "mobile-network-continue-syncing-details": "You can change this later in settings", - "mobile-network-go-to-settings": "Go to settings", - "mobile-network-settings": "Mobile data", - "mobile-network-sheet-configure": "You can configure syncing in more \ndetail in", - "mobile-network-sheet-offline": "No Wi-fi, message syncing disabled.", - "mobile-network-sheet-offline-details": "Syncing using mobile network is off", - "mobile-network-sheet-remember-choice": "Remember my choice", - "mobile-network-sheet-settings": "settings", - "mobile-network-start-syncing": "Start syncing", - "mobile-network-stop-syncing": "Stop syncing", - "mobile-network-stop-syncing-details": "Until connected to Wi-Fi?", - "mobile-network-use-mobile": "Use mobile data", - "mobile-network-use-mobile-data": "Status uses a lot of data when syncing chats and wallet.", - "mobile-network-use-wifi": "Wi-Fi only", - "mobile-syncing-sheet-details": "Status uses a lot of data when syncing chats and wallet.", - "mobile-syncing-sheet-title": "Sync using mobile data?", - "more": "more", - "multiaccount-exists-title": "Keys for this account already exist", - "multiaccount-exists-content": "Keys for this account already exist and can’t be added again. If you’ve lost your password, passcode or Keycard, uninstall the app, reinstall and access your keys by entering your seed phrase", - "multiaccounts-recover-enter-phrase-text": "Enter 12, 15, 18, 21 or 24 words.\nSeparate words by a single space.", - "multiaccounts-recover-enter-phrase-title": "Enter your seed phrase", - "multichain": "Multichain", - "name": "Name", - "name-updated": "Name updated", - "name-of-token": "The name of your token", - "need-help": "Need help?", - "new-to-status": "I’m new to Status", - "getting-started-with-status": "Getting started with Status", - "getting-started-description": "Status is a unique messaging app that combines decentralized technology, privacy-focused design, and Web3 capabilities to offer a unique and secure communication experience.\n\nYour Status keys are the foundation of your self-sovereign identity in Web3. They serve as a secure means of accessing and managing your personal information and identity. With Status, you own, control and manage your data and digital identity without relying on centralized organisations.\n\nChoose one of these options to create your Status profile:", - "getting-started-generate-keys-description": "Create a pair of cryptographic keys representing your Status identity. Unlike centralized apps, Status doesn't rely on your username or email address to identify your profile. Instead, it uses your private and public keys. This option creates a new Ethereum address.", - "getting-started-generate-keys-from-recovery-phrase": "Generate keys from recovery phrase", - "getting-started-generate-keys-from-recovery-phrase-description": "You can use your existing Ethereum address to create your Status profile. If you already use a non-custodial Ethereum wallet (like Metamask or Trust Wallet), you already have an Ethereum address and a recovery phrase.\n\nYour keys are always securely stored on your device and protected by your Status password.", - "getting-started-generate-keys-on-keycard": "Generate keys on a Keycard", - "getting-started-generate-keys-on-keycard-description": "A Keycard is a physical card (similar to a credit card) that securely stores your Status keys. Using a Keycard adds an extra layer of security to your digital assets and identity. This option creates a new Ethereum address.", - "generate-keys": "Generate keys", - "generate-keys-subtitle": "Create your new self-sovereign identity", - "experienced-web3": "Experienced in Web3?", - "enter-recovery-phrase": "Enter recovery phrase", - "import-key-pair": "Import key pair", - "import-by-entering-recovery-phrase": "Import by entering recovery phrase", - "import-by-entering-private-key": "Import by entering private key", - "use-recovery-phrase": "Use recovery phrase", - "use-recovery-phrase-subtitle": "If you already have an Ethereum address", - "use-keycard": "Use Keycard", - "use-keycard-subtitle": "Keys will be stored on your Keycard", - "glossary": "Glossary", - "account-title": "Account", - "account-content": "You can compare accounts in Status to bank accounts. Like a bank account, an account typically has an address and a balance; You use this account to transact on Ethereum. You can have multiple accounts in your wallet. All accessed by unlocking Status.", - "chat-key-description": "Your unique public ID in Status. Others can use it to send you a contact request.", - "chat-key-title": "Chat Key", - "chat-key-content": "Messages on the Status chat protocol are sent and received using encryption keys. The public chat key is a string of characters you share with others so they can send you messages in Status.", - "chat-name-title": "Chat Name", - "chat-name-content": "Three random words, derived algorithmically from your chat key and used as your default alias in chat. Chat names are completely unique; no other user can have the same three words.", - "ens-name-title": "ENS Name", - "ens-name-content": "Custom alias for your chat key that you can register using the Ethereum Name Service. ENS names are decentralized usernames.", - "mailserver-title": "Status node", - "mailserver-content": "A node in the Status network that routes and stores messages, for up to 30 days.", - "peer-title": "Peer", - "peer-content": "A device connected to the Status chat network. Each user can represent one or more peers, depending on their number of devices.", - "seed-phrase-title": "Seed Phrase", - "seed-phrase-content": "A set of friendly-to-read words, randomly selected from the BIP39 standard list and used to recover or access your Ethereum account on other wallets and devices. Also referred to as a “mnemonic phrase,” “recovery phrase” or “wallet backup” across the crypto ecosystem. Most crypto apps use this same standard to generate accounts.", - "wallet-key-title": "Account address", - "wallet-key-content": "A 64 character hex address based on the Ethereum standard and beginning with 0x. Public-facing, your account address is shared with others when you want to receive funds. Also referred to as an “Ethereum address” or “wallet address.”", - "buy-crypto-title": "Looks like your wallet is empty", - "buy-crypto-description": "Find a dapp to buy crypto now", - "buy-crypto": "Buy crypto", - "buy-crypto-choose-a-service": "Choose a service you'd like to use to buy crypto", - "buy-crypto-leaving": "You are leaving Status and entering a third party website to complete your purchase", - "data-usage": "Data usage", - "language-and-currency": "Language and currency", - "opening-buy-crypto": "Opening {{site}}...", - "network": "Network", - "networks": "Networks", - "network-chain": "Network chain", - "network-fee": "Network fee", - "network-id": "Network ID", - "network-invalid-network-id": "Specified network id doesn't correspond to network id by RPC url", - "network-invalid-status-code": "Invalid status code: {{code}}", - "network-invalid-url": "Network URL is invalid", - "network-settings": "Network settings", - "new": "New", - "new-chat": "New chat", - "new-contact": "New contact", - "new-contract": "New Contract", - "new-group": "New group", - "new-group-chat": "New group chat", - "new-group-limit": "You can only add {{max-contacts}} contacts to the group chat", - "new-network": "New network", - "new-pin-description": "Enter new 6-digit passcode", - "new-puk-description": "Enter new 12-digit PUK", - "new-public-group-chat": "Join public chat", - "next": "Next", - "no": "No", - "no-collectibles": "No collectibles", - "no-keycard-applet-on-card": "No Keycard applet on card", - "no-pairing-slots-available": "This card is already paired to 5 devices and cannot pair to this one. Please use one of the paired devices, log in with this card and free up pairing slots on the card", - "no-result": "No results", - "no-tokens-found": "No tokens found", - "node-info": "Node info", - "node-address": "Node address", - "node-details": "Node details", - "node-version": "Node version", - "nonce": "Nonce", - "none": "None", - "no-one": "No one", - "not-a-chatkey": "This is not a chatkey", - "not-applicable": "Not applicable for unsigned transactions", - "not-keycard-text": "The card you used is not a Keycard. You need to purchase a Keycard to use it", - "not-keycard-title": "Not a Keycard", - "not-paired-with-this-device": "Not paired with this device", - "notifications": "Notifications", - "local-notifications": "Local notifications", - "local-notifications-subtitle": "Enable background service", - "remote-notifications": "Remote notifications", - "remote-notifications-subtitle": "Enable google push notifications", - "show-notifications": "Show notifications", - "notification-settings": "Notification settings", - "notifications-servers": "Notification servers", - "notifications-preferences": "Notification preferences", - "notifications-switch": "Show notifications", - "notifications-non-contacts": "Notifications from non-contacts", - "notifications-transactions": "Wallet transactions", - "send-push-notifications": "Send Push Notifications", - "send-push-notifications-description": "When disabled, the person receiving your messages won't be notified of their arrival", - "push-notifications-server-enabled": "Server enabled", - "push-notifications-servers": "Push notification servers", - "push-inbound-transaction": "You received {{value}} {{currency}}", - "push-outbound-transaction": "You sent {{value}} {{currency}}", - "push-failed-transaction": "Your transaction failed", - "push-inbound-transaction-body": "From {{from}} to {{to}}", - "push-outbound-transaction-body": "From {{from}} to {{to}}", - "push-failed-transaction-body": "{{value}} {{currency}} to {{to}}", - "allow-mention-notifications": "Show @ mentions", - "server": "Server", - "read": "Read", - "unread": "Unread", - "specify-server-public-key": "Enter server public key", - "notify": "Notify", - "off": "Off", - "offline": "Offline", - "offline-messaging-use-history-nodes": "Use Status nodes", - "offline-messaging-use-history-explanation": "Enable Status nodes to fetch messages that were sent while the app was closed. When enabled, a Status node gets your IP address. When disabled you will not receive messages when the app is closed and will not see them when you open the app later.", - "ok": "OK", - "ok-continue": "Okay, continue", - "ok-got-it": "Okay, got it", - "okay": "Okay", - "on-capitalized": "On", - "on": "on", - "on-the-web": "On the web", - "only-mentions": "Only @mentions", - "open": "Open", - "open-home": "Open...", - "open-dapp": "Open ÐApp", - "open-dapp-store": "Discover ÐApps", - "open-nfc-settings": "Open NFC settings", - "open-on-block-explorer": "Open on block explorer", - "optional": "optional", - "or": "OR", - "outgoing": "Outgoing", - "outgoing-transaction": "Outgoing transaction", - "own-your-crypto": "Own your crypto", - "pair": "Pair", - "pair-devices": "Pair devices", - "pair-device-toast": "Device successfully paired", - "pair-card": "Pair to this device", - "pair-code": "Pair code", - "pair-code-explanation": "Pairs card to a different device (up to 5) to unlock keys and sign transactions with the same Keycard", - "pair-this-card": "Pair this card", - "pair-this-device": "Advertise device", - "pair-this-device-description": "Pair your devices to sync contacts and chats between them", - "paired-devices": "Paired devices", - "paired-with-this-device": "Paired with this device", - "pairing": "Pairing", - "pairing-card": "Pairing card", - "pairing-code-placeholder": "Pairing code...", - "pairing-code_error1": "Pairing codes don't match.", - "confirm-pairing-code-placeholder": "Confirm your pairing code...", - "pairing-go-to-installation": "Go to pairing settings", - "pairing-maximum-number-reached-content": "Please disable one of your devices before enabling a new one.", - "pairing-maximum-number-reached-title": "Max number of devices reached", - "pairing-new-installation-detected-content": "A new device has been detected.\nIn order to use your devices correctly, it's important to pair and enable them before using them.\nPlease go to the device section under settings to pair your devices.", - "pairing-new-installation-detected-title": "New device detected", - "pairing-no-info": "No info", - "pairing-please-set-a-name": "Please set a name for your device.", - "participate-in-the-metaverse": "Participate in the truly free metaverse", - "password-creation-title": "Create profile password", - "password-creation-subtitle": "To log in to Status and sign transactions", - "password-creation-match": "Passwords match", - "password-creation-dont-match": "Passwords do not match", - "password-creation-hint": "Minimum 10 characters", - "password-creation-placeholder-1": "Type password", - "password-creation-placeholder-2": "Repeat password", - "password-creation-tips-title": "Tips to improve", - "password-creation-tips-1": "Lower case", - "password-creation-tips-2": "Upper case", - "password-creation-tips-3": "Numbers", - "password-creation-tips-4": "Symbols", - "password-creation-disclaimer": "I understand my password can't be recovered", - "password-creation-confirm": "Confirm password", - "passphrase": "Passphrase", - "password": "Password", - "password-description": "At least 6 characters. Your password protects your keys. You need it to unlock Status and transact.", - "password-placeholder2": "Confirm your password", - "password_error1": "Passwords don't match.", - "paste": "Paste", - "paste-json": "Paste JSON", - "pay-to-chat": "Pay to chat", - "peers": "Peers", - "peers-count": "Peers count", - "peers-stats": "Peers stats", - "pending": "Pending", - "pending-confirmation": "Pending confirmation...", - "permissions": "Permissions", - "phone-e164": "International 1", - "photos-access-error": "To grant the required photos permission, please go to your system settings and make sure that Status > Photos is selected.", - "pin-changed": "6-digit passcode has been changed", - "puk-changed": "12-digit PUK has been changed", - "pairing-changed": "Pairing code has been changed", - "pin-code": "6-digit passcode", - "pin-mismatch": "Wrong passcode", - "pin-retries-left": "{{number}} attempts left", - "pin-one-attempt-blocked-before": "Be careful, you have only", - "pin-one-attempt-frozen-before": "Be careful, you have only", - "pin-one-attempt": " one attempt ", - "pin-one-attempt-blocked-after": "before your Keycard gets blocked", - "pin-one-attempt-frozen-after": "before your Keycard gets frozen", - "preview-privacy": "Preview privacy mode", - "privacy": "Privacy", - "privacy-photos": "Profile Photo Privacy", - "privacy-and-security": "Privacy and security", - "privacy-policy": "Privacy policy", - "privacy-show-to-warning": "People who have already seen your profile picture will continue to do so", - "processing": "Just a moment", - "product-information": "Product Information", - "profile": "Profile", - "profile-bio": "Profile bio", - "profile-details": "Profile details", - "profile-name": "Profile name", - "profile-name-is-too-long": "Profile name is too long", - "profile-picture-added": "Profile picture added", - "profile-picture-removed": "Profile picture removed", - "public-chat": "Public chat", - "public-chats": "Public chats", - "public-group-status": "Public", - "public-group-topic": "Topic", - "join-new-public-chat": "Join a public chat", - "join-new-private-chat": "Start a new private chat", - "search-no-chat-found": "No search results. Do you mean", - "public-key": "Public key", - "puk-and-pairing-codes-displayed": "PUK and pairing codes displayed", - "puk-code": "PUK code", - "puk-code-explanation": "If you forget your 6-digit passcode or enter it incorrectly 3 times, you'll need this code to unlock your card.", - "puk-mismatch": "Wrong PUK code", - "quiet-days": "{{quiet-days}} days", - "quiet-hours": "{{quiet-hours}} hours", - "re-encrypt": "Re-encrypt", - "re-encrypt-key": "Re-encrypt your keys", - "re-encrypt-data": "Re-encrypt your data", - "receive": "Receive", - "receive-transaction": "Receive transaction", - "recent": "Recent", - "recent-recipients": "Contacts", - "recently-used-stickers": "Recently used stickers will appear here", - "recipient": "Recipient", - "recipient-code": "Enter recipient address", - "recipient-code-placeholder": "0x... or username.domain.eth", - "recover": "Recover", - "recover-key": "Access existing keys", - "recover-keycard-multiaccount-not-supported": "Keys for this account already exist and can't be added again. If you've lost your password, passcode or Keycard, uninstall the app, reinstall and access your keys by entering your seed phrase", - "recover-with-keycard": "Recover with Keycard", - "recovering-key": "Accessing keys...", - "recovery-confirm-phrase": "Confirm seed phrase", - "recovery-phrase": "Seed phrase", - "recovery-success-text": "You will have to create a new code or password to re-encrypt your keys", - "recovery-typo-dialog-description": "Please note, your seed phrase must use the exact same words and order as you received it", - "recovery-typo-dialog-title": "Is the seed phrase correct?", - "remember-me": "Remember me", - "remind-me-later": "Show me this again", - "remove": "Remove", - "remove-from-chat": "Remove from chat", - "remove-from-contacts": "Remove from contacts", - "remove-contact": "Remove contact", - "remove-from-contacts-text": "By removing a user from your contact list you do not hide your wallet address from them", - "remove-network": "Remove network", - "remove-token": "Remove token", - "removed": "removed", - "remove-key-pair-and-derived-accounts": "Remove key pair and derived accounts", - "the-key-pair-and-derived-accounts-will-be-removed": "The key pair and derived accounts will be removed from all of your synced devices. Make sure you have a backup of your keys or seed phrase before proceeding.", - "rename-key-pair": "Rename key pair", - "repeat-pin": "Repeat new 6-digit passcode", - "repeat-puk": "Repeat new 12-digit PUK", - "report-bug-email-template": "1. Issue Description\n{{description}}\n\n\n2. Steps to reproduce\n{{steps}}\n\n\n3. Attach screenshots that can demo the problem, please\n", - "request-processed-after-node-online": "Community control node is offline. Request will be processed once it's back online.", - "request-to-join-disclaimer": "Joining the community will reveal your public addresses to the owner", - "request-to-join": "Request to join", - "request-to-join-community": "Request to join community", - "request-to-join-community-pending": "Request to join community pending", - "requested-to-join-community": "You requested to join “{{community}}”", - "request-transaction": "Request transaction", - "required-field": "Required field", - "resend-message": "Resend", - "reset-card": "Reset card", - "reset-card-description": "This operation will reset card to initial state. It will erase all card data including private keys. Operation is not reversible.", - "retry": "Retry", - "reveal-sync-code": "Reveal sync code", - "reveal-qr-code": "Reveal QR code", - "revoke-access": "Revoke access", - "save": "Save", - "save-changes": "Save changes", - "save-address": "Save address", - "save-bio": "Save bio", - "save-colour": "Save colour", - "save-name": "Save name", - "save-password": "Save password", - "save-password-unavailable": "Set device passcode to save password", - "save-password-unavailable-android": "Save password is unavailable: your device may be rooted or lacks necessary security features.", - "scan-or-enter-sync-code": "Scan or enter sync code", - "scan-qr": "Scan QR", - "scan-qr-code": "Scan QR code", - "scan-with-status-app": "Scan with the Status app on another device", - "scan-key-pairs-qr-code": "Scan key pairs QR code", - "invalid-qr": "Oops! This QR doesn’t work with Status", - "invalid-key-pair-qr": "This does not look like a key pair QR code", - "this-qr-does-not-contain-key-pair": "This QR does not contain {{name}} key pair", - "this-qr-does-not-contain-any-missing-key-pair": "This QR does not contain any missing key pairs", - "search": "Search", - "search-discover-communities": "Search communities or categories", - "secret-keys-confirmation-text": "You will need them to continue to use your Keycard in case you ever lose your phone.", - "secret-keys-confirmation-title": "Written the codes down?", - "security": "Security", - "see-details": "See details", - "see-it-again": "SEE IT AGAIN", - "select-account-first": "Select an account first", - "select-chat": "Select chat to start messaging", - "selected": "Selected", - "select": "Select", - "select-account": "Select account", - "send-logs": "Report a bug", - "send-logs-to": "Report a bug to {{email}}", - "send-message": "Send message", - "send-request": "Send request", - "send-reply": "Send reply", - "send-request-amount": "Amount", - "send-request-amount-max-decimals": "Max number of decimals is {{asset-decimals}}", - "send-request-unknown-token": "Unknown token - {{asset}}", - "send-sending-to": "to {{recipient-name}}", - "send-transaction": "Send transaction", - "sending": "Sending", - "sent-at": "Sent at", - "set-a-topic": "Create a topic", - "set-currency": "Set currency", - "set-dapp-access-permissions": "Set DApp access permissions", - "settings": "Settings", - "share": "Share", - "shared": "Shared", - "share-channel": "Share channel", - "share-address": "Share address", - "share-chat": "Share chat", - "share-collectible": "Share collectible", - "share-contact-code": "Share my chat key", - "share-community": "Share community", - "share-dapp-text": "Check out this DApp I'm using on Status: {{link}}", - "share-link": "Share link", - "share-my-profile": "Share my profile", - "share-profile": "Share profile", - "share-profile-link": "Share profile link", - "share-public-chat-text": "Check out this public chat on the Status app: {{link}}", - "sharing-copied-to-clipboard": "Copied", - "sharing-copy-to-clipboard": "Copy", - "share-logs": "Share logs", - "sharing-share": "Share", - "showcase": "Showcase", - "show-less": "Show less", - "show-more": "Show more", - "show-qr": "Show QR code", - "show-transaction-data": "Show transaction data", - "show-encrypted-qr-of-key-pair": "Show encrypted QR of key pair", - "sign-and-send": "Sign and send", - "sign-in": "Sign in", - "sign-message": "Sign Message", - "sign-out": "Sign out", - "sign-with": "Sign with", - "sign-with-password": "Sign with password", - "sign-you-in": "Signing you in…", - "signing": "Signing", - "signing-a-message": "Signing a message", - "signing-phrase": "Signing phrase", - "something-about-you": "Something about you", - "something-went-wrong": "Something went wrong", - "soon": "Soon", - "special-characters": "Special characters", - "specify-address": "Specify address", - "specify-name": "Specify a name", - "specify-symbol": "Specify a symbol", - "specify-network-id": "Specify network id", - "start-chat": "Start chat", - "start-conversation": "Start conversation", - "start-group-chat": "Start group chat", - "start-new-chat": "Start new chat", - "start-using-status": "Start using Status", - "start-with-space": "Cannot start with space", - "status": "Status", - "status-confirmed": "Confirmed", - "status-hardwallet": "Status hardwallet", - "status-keycard": "Status Keycard", - "status-pending": "Pending", - "status-tx-not-found": "TX not found", - "status-sent": "Sent", - "status-help": "Status Help", - "status-not-sent-tap": "Not confirmed. Tap for options", - "status-not-sent-click": "Not confirmed. Click for options", - "step-i-of-n": "Step {{step}} of {{number}}", - "sticker-market": "Sticker market", - "sticker": "Sticker", - "submit": "Submit", - "submit-bug": "Submit a bug", - "success": "Success", - "symbol": "Symbol", - "sync-all-devices": "Sync all devices", - "sync-in-progress": "Syncing...", - "sync-or-recover-profile": "Sync or recover profile", - "sync-settings": "Sync settings", - "sync-synced": "In sync", - "syncing-devices": "Syncing...", - "sign-up": "Sign up", - "tag-was-lost": "Tag was lost", - "tap-card-again": "Tap the card to the back of your phone again", - "text-input-disabled": "Please wait a moment...", - "this-device": "This device", - "this-device-desc": "Your keys will be encrypted and securely stored on your device", - "this-is-you-signing": "This is your signing phrase", - "this-will-take-few-seconds": "This will take a few seconds", - "three-words-description": "You should see these 3 words before signing each transaction", - "three-words-description-2": "If you see a different combination, cancel the transaction and sign out", - "to": "to", - "to-capitalized": "To", - "to-block": "Block", - "to-encrypt-enter-password": "To encrypt the account please enter your password", - "to-see-this-message": "To see this message,", - "token-auto-validate-decimals-error": "Wrong decimals for token {{symbol}} at address {{address}} - set to {{expected}} but detected as {{actual}}", - "token-auto-validate-name-error": "Wrong name for token {{symbol}} at address {{address}} - set to {{expected}} but detected as {{actual}}", - "token-auto-validate-symbol-error": "Wrong symbol for token {{symbol}} at address {{address}} - set to {{expected}} but detected as {{actual}}", - "token-details": "Token details", - "token-price": "Token Price", - "topic-name-error": "Use only lowercase letters (a to z), numbers & dashes (-). Do not use chat keys", - "transaction": "Transaction", - "transaction-data": "Transaction data", - "transaction-declined": "Transaction declined", - "transactions-management-enabled": "Transaction management (alpha)", - "transaction-description": "Consider it complete after 12 confirmations on the network.", - "transaction-details": "Transaction details", - "transaction-failed": "Transaction failed", - "transaction-history": "Transaction history", - "transaction-request": "Transaction Request", - "transaction-sent": "Transaction sent", - "transaction-signed": "The transaction has been successfully signed", - "transactions": "Transactions", - "transactions-filter-select-all": "Select all", - "transactions-filter-title": "Filter history", - "type": "Type", - "transactions-history": "Transaction history", - "transactions-history-empty": "No transactions in your history yet", - "transactions-history-loading": "Loading transaction history. This might take a while.", - "transactions-sign": "Sign", - "tribute-required-by-multiaccount": "{{multiaccount-name}} requires SNT to start a chat.", - "tribute-state-paid": "Tribute paid", - "tribute-state-pending": "Tribute pending", - "tribute-state-required": "Requires {{snt-amount}} SNT tribute", - "tribute-to-talk": "Tribute to talk", - "tribute-to-talk-add-friends": "Add friends as a contact to allow chats without tribute payment.", - "tribute-to-talk-are-you-friends": "Are you friends?", - "tribute-to-talk-ask-to-be-added": "Ask to be added as a contact", - "tribute-to-talk-contact-received-your-tribute": " received your tribute. You can now securely chat with each other.", - "tribute-to-talk-desc": "Monetize your attention by requiring SNT for new people to start a chat", - "tribute-to-talk-disabled": "Tribute to Talk disabled", - "tribute-to-talk-disabled-note": "From now on, new people can start a chat with you without sending SNT.", - "tribute-to-talk-enabled": "You have Tribute to Talk enabled.", - "tribute-to-talk-finish-desc": "From now on, you will only receive chats from contacts, and people who paid ", - "tribute-to-talk-learn-more-1": "Your time and attention are your most valuable assets. Tribute to Talk lets you set an amount of SNT required for new people to start a chat with you.", - "tribute-to-talk-learn-more-2": "Anyone who is not in your contact list will be asked to pay, and you can respond once they have.", - "tribute-to-talk-learn-more-3": "You can always send the money back, but to ensure that friends can reach you freely, add them as a contact first.", - "tribute-to-talk-paywall-learn-more-1": "Our time and attention are our most valuable assets. Tribute to Talk lets you contact new people in exchange for an SNT payment.", - "tribute-to-talk-paywall-learn-more-2": "To start a chat with someone who has a tribute set, simply pay the required SNT and you will be added as a contact.", - "tribute-to-talk-paywall-learn-more-3": "If you know them, you can share your profile outside of Status to be added for free.", - "tribute-to-talk-pending": "Tribute pending confirmation", - "tribute-to-talk-pending-note": "Tribute transaction is pending confirmation on the network. You can check its status in transaction history", - "tribute-to-talk-removing-note": "Removing Tribute to Talk will allow new people to start a chat without sending SNT. Requires a transaction to be made.", - "tribute-to-talk-set-snt-amount": "Set the amount of SNT required for new people to start a chat", - "tribute-to-talk-signing": "Waiting to sign transaction", - "tribute-to-talk-transaction-failed-note": "Transaction has failed and your Tribute to Talk settings have not been changed", - "tribute-to-talk-tribute-received1": "Tribute received. You and ", - "tribute-to-talk-tribute-received2": " are now contacts and can securely chat with each other.", - "tribute-to-talk-you-require-snt": "You require SNT for new people to start a chat.", - "try-again": "Try again", - "try-keeping-the-card-still": "Try keeping the card still", - "turn-nfc-on": "Turn NFC on to continue", - "turn-nfc-description": "NFC is disabled on yor device. You can enable it in settings", - "testnet-mode-prompt-title": "Warning!", - "testnet-mode-prompt-content": "You are about to switch the network mode. This will log you out and you will have to login again.", - "keycard-init-title": "Looking for cards...", - "keycard-init-description": "Put the card to the back of your phone to continue", - "keycard-awaiting-title": "Still looking...", - "keycard-awaiting-description": "Try moving the card around to find the NFC reader on your device", - "keycard-processing-title": "Processing...", - "keycard-processing-description": "Try keeping the card still", - "keycard-connected-title": "Connected", - "keycard-connected-description": "Try keeping the card still", - "keycard-error-title": "Connection lost", - "keycard-error-description": "Connect the card again to continue", - "keycard-success-title": "Success", - "keycard-success-description": "You may remove the card now", - "keycard-recover": "lost or frozen card?", - "keycard-recover-title": "Create a new card for this account?", - "keycard-recover-text": "If you have your mnemonic phrase you can create a new Keycard associated with this account. You can use either a new Keycard or perform factory reset on a frozen one.", - "keycard-backup": "Create a backup Keycard", - "keycard-backup-success-title": "Backup successful", - "keycard-backup-success-body": "Backup card created successfully. You can now use it with your account just like the primary card.", - "keys-saved": "Keys saved!", - "type-a-message": "Message", - "ulc-enabled": "ULC enabled", - "backup-enabled": "Enabled", - "backup-disabled": "Disabled", - "backup-settings": "Backup settings", - "backup-through-waku": "Backup through waku", - "perform-backup": "Perform backup", - "backing-up": "Backing up...", - "last-backup-performed": "Last backup performed:", - "unable-to-read-this-code": "Unable to read this code", - "unblock-contact": "Unblock this user", - "undo": "Undo", - "unique-identifiers": "Unique profile identifiers", - "unknown-status-go-error": "Unknown status-go error", - "unlock": "Unlock", - "unpair": "Unpair", - "unpair-card": "Unpair card", - "unpair-card-confirmation": "This operation will unpair card from current device. Requires 6-digit passcode authorization. Do you want to proceed?", - "unpair-device": "Unpair device?", - "unpair-device-description": "This device will no longer synchronize with {{name}} until you pair them again.", - "unpair-device-toast": "Device successfully unpaired", - "unpaired-keycard-text": "The Keycard you tapped is not associated with this phone", - "unpaired-keycard-title": "Looks like your card has been unpaired", - "unpair-keycard": "Unpair Keycard from this phone", - "unpair-keycard-warning": "Your pairing code/PUK & PIN remain unchanged", - "update": "Update", - "url": "URL", - "usd-currency": "USD", - "use-the-multichain-wallet": "Use the leading multi-chain self-custodial wallet", - "use-valid-contact-code": "Please enter or scan a valid chat key or username", - "valid-for-time": "Valid for {{valid-for}}", - "validation-amount-invalid-number": "Amount is not a valid number", - "validation-amount-is-too-precise": "Amount is too precise. Max number of decimals is {{decimals}}.", - "version": "App version", - "app-commit": "App commit", - "view": "View", - "view-community-rules": "View Community Rules", - "view-cryptokitties": "View in CryptoKitties", - "view-cryptostrikers": "View in CryptoStrikers", - "view-etheremon": "View in Etheremon", - "view-gitcoin": "View in Gitcoin", - "view-members": "View members", - "view-profile": "View profile", - "view-channel-members-and-details": "View channel members and details", - "view-details": "View Details", - "view-signing": "View signing phrase", - "view-superrare": "View in SuperRare", - "view-token-gating": "View token requirements", - "vote-to-feature": "Vote to feature this community", - "waiting-for-wifi": "No Wi-fi, message syncing disabled.", - "waiting-for-wifi-change": "Settings", - "waiting-to-sign": "Waiting to sign transaction...", - "wallet": "Wallet", - "wallet-asset": "Asset", - "wallet-assets": "Assets", - "wallet-backup-recovery-title": "Back up your seed phrase", - "wallet-choose-recipient": "Choose Recipient", - "wallet-collectibles": "Collectibles", - "wallet-insufficient-funds": "Insufficient funds", - "wallet-insufficient-gas": "Not enough ETH for gas", - "wallet-invalid-address": "Invalid address: \n {{data}}", - "wallet-invalid-address-checksum": "Error in address: \n {{data}}", - "wallet-invalid-chain-id": "Network does not match: \n {{data}} but current chain is {{chain}}", - "wallet-manage-assets": "Manage assets", - "wallet-manage-accounts": "Manage accounts", - "wallet-request": "Request", - "wallet-send": "Send", - "wallet-send-min-units": "Min 21000 units", - "wallet-send-min-wei": "Min 1 wei", - "wallet-settings": "Wallet settings", - "wallet-total-value": "Total value", - "wallet-transaction-total-fee": "Total Fee", - "wants-to-access-profile": "wants to access to your profile", - "warning": "Warning", - "warning-message": "Sorry, we limit sending several messages in quick succession to prevent spam. Please try again in a moment", - "web-view-error": "Unable to load page", - "welcome-screen-text": "Set up your wallet, invite friends to chat\n and browse popular dapps!", - "welcome-to-status": "Welcome to Status!", - "welcome-to-web3": "Welcome to web3!", - "welcome-back": "Welcome back!", - "welcome-to-web3-sub-title": "What are you waiting for? Go explore!", - "welcome-to-status-description": "Set up your crypto wallet, invite friends to chat and browse decentralized apps", - "welcome-blank-message": "Your chats will appear here. To start new chats press the ⊕ button", - "welcome-community-blank-message": "Your channels will appear here. To create a new channel, click on the ⊕ button and select \"Create a channel\"", - "welcome-community-blank-message-edit-chats": "Your channels will appear here. To create a new channel, go back to the community screen, click on the ⊕ button and select \"Create a channel\"", - "welcome-blank-community-message": "Your communities will appear here.", - "with-full-encryption": "With full metadata privacy and e2e encryption", - "fetch-community": "Fetch community", - "fetching-community": "Fetching community...", - "failed-to-fetch-community": "Failed to fetch community", - "seed-phrase-placeholder": "Type or paste your recovery phrase", - "seed-phrase-words-exceeded": "Recovery phrase cannot exceed 24 words", - "seed-phrase-words-uppercase": "Recovery phrase cannot contain uppercase characters", - "seed-phrase-error": "Recovery phrase contains invalid words", - "seed-phrase-invalid": "Invalid recovery phrase", - "seed-phrase-incorrect": "Recovery phrase does not match key pair", - "seed-phrase-info": "Enter 12, 18 or 24 words separated by spaces", - "word-count": "Word count", - "word-n": "Word #{{number}}", - "word-n-description": "In order to check if you have backed up your seed phrase correctly, enter the word #{{number}} above.", - "words-n": { - "one": "1 word", - "other": "{{count}} words" - }, - "write-down-and-store-securely": "Write codes down\n & store them securely", - "wrong-address": "Wrong address", - "wrong-card": "Wrong card", - "wrong-card-text": "Tapped card does not correspond to the keys you selected", - "wrong-contract": "Wrong contract", - "contract-isnt-supported": "Contract is not supported", - "wrong-keycard-text": "The Keycard you tapped is not associated with this phone", - "wrong-keycard-title": "Looks like you’ve tapped \na wrong keycard", - "wrong-password": "Wrong password", - "wrong-word": "Wrong word", - "yes": "Yes", - "You": "You", - "you": "you", - "you-already-have-an-asset": "You already have an asset {{value}}", - "you-already-use-status": "You already use Status", - "you-are-all-set": "You’re all set!", - "you-are-all-set-description": "If you lose your phone, you can now access your funds and chat key using your seed phrase", - "you-can-change-account": "You can change the account name and color to what you wish", - "you-canceled-the-request": "You canceled the request to join", - "you-dont-have-stickers": "You don’t have any stickers yet", - "you-dont-have-contacts-invite-friends": "You don’t have any contacts yet.\nInvite your friends to start chatting.", - "your-contact-code": "Granting access authorizes this DApp to retrieve your chat key", - "your-data-belongs-to-you": "If you lose your seed phrase you lose your data and funds", - "your-data-belongs-to-you-description": "If you lose access, for example by losing your phone, you can only access your keys with your seed phrase. No one, but you has your seed phrase. Write it down. Keep it safe", - "your-identifiers": "Your identicon ring, chat key and emojihash will help others tell you from impersonators", - "your-name": "Your name", - "your-recovery-phrase": "Your seed phrase", - "your-recovery-phrase-description": "This is your seed phrase. You use it to prove that this is your wallet. You only get to see it once! Write it on paper and keep it in a secure place. You will need it if you lose or reinstall your wallet.", - "custom-seed-phrase": "Invalid seed phrase", - "custom-seed-phrase-text-1": "This seed phrase doesn't match our supported dictionary. Check for misspelled words.", - "to-enable-biometric": "To enable {{bio-type-label}}, you must save your password on the unlock screen", - "ok-save-pass": "OK, save password", - "lock-app-with": "Lock app with", - "grant-face-id-permissions": "To grant the required Face ID permission, please go to your system settings and make sure that Status > Face ID is selected", - "grant-fingerprints-permissions": "To grant the required fingerprints permission, please go to your system settings and make sure that Status > Fingerprints is selected", - "request-feature": "Request a feature", - "select-account-dapp": "Select the account you wish to use with Dapps", - "apply": "Apply", - "on-status-tree": "On Status tree", - "off-status-tree": "Off Status tree", - "derivation-path": "Derivation path", - "storage": "Storage", - "keycard-free-pairing-slots": "Keycard has {{n}} free pairing slots", - "public-chat-description": "Join public chats for your interests! Anyone can start a new one.", - "delete-account": "Delete account", - "delete-keys-keycard": "Delete keys from Keycard", - "watch-only": "Watch-only", - "watched-address": "Watched address", - "add-watched-address": "Add watched address", - "cant-report-bug": "Can't report a bug", - "mail-should-be-configured": "Mail client should be configured", - "check-on-block-explorer": "Check on block explorer", - "check-on-opensea": "Check on opensea", - "transactions-load-more": "Load more", - "private-key": "Private key", - "generate-an-account": "Generate an account", - "add-watch-account": "Add a watch-only account", - "add-seed-account": "Add account with a seed phrase", - "account-exists-title": "Account already exists", - "add-private-key-account": "Add account from private key", - "profile-not-found": "Profile not found", - "waku-bloom-filter-mode": "Waku bloom filter mode", - "wakuv2-settings": "Waku v2 settings", - "wakuv2-node-format": "/ip4/{node-ip}/tcp/{port}/p2p/{id}", - "wakuv2-change-nodes": "Are you sure you want to change Wakuv2 nodes?", - "appearance": "Appearance", - "preference": "Preference", - "light": "Light", - "dark": "Dark", - "system": "System", - "give-permissions-camera": "Give permission\nto access camera", - "photos": "Photos", - "one-photo": "1 photo", - "n-photos": "{{count}} photos", - "image": "Image", - "photo": "Photo", - "sign-anyway": "Sign anyway", - "tx-fail-description1": "This transaction is likely to fail. Sign at your own risk using custom network fee.", - "tx-fail-description2": "This transaction is likely to fail. Set a custom network fee to sign at your own risk.", - "set-custom-fee": "Set custom fee", - "not-enough-snt": "Not enough SNT", - "add-new-contact": "Add new contact", - "add-a-contact": "Add a contact", - "enter-chat-key": "Enter chat key or scan a QR", - "you-dont-have-contacts": "You don’t have any contacts yet.", - "set-max": "Set max", - "continue-anyway": "Continue anyway", - "private-notifications": "Private notifications", - "private-notifications-descr": "Status will notify you about new messages. You can edit your notification preferences later in settings.", - "maybe-later": "Maybe later", - "join": "Join", - "registered": "registered", - "not-registered": "not registered", - "audio-recorder-error": "Recorder error", - "audio-recorder": "Recorder", - "audio-recorder-max-ms-reached": "Maximum recording time reached", - "audio-recorder-permissions-error": "You have to give permission to send audio messages", - "audio": "Audio", - "audio-message": "Audio message", - "update-to-see-image": "Update to latest version to see a nice image here!", - "update-to-listen-audio": "Update to latest version to listen to an audio message here!", - "update-to-see-sticker": "Update to latest version to see a nice sticker here!", - "webview-camera-permission-requests": "Webview camera permission requests", - "webview-camera-permission-requests-subtitle": "When enabled, websites and dapps can ask to use your camera", - "page-would-like-to-use-camera": "would like to use your camera", - "page-camera-request-blocked": "camera requests blocked. To enable camera requests go to Settings", - "nickname": "Nickname", - "nickname-is-too-long": "Nickname is too long", - "add-nickname": "Add a nickname (optional)", - "edit-nickname": "Edit nickname", - "add-nickname-title": "Add nickname", - "update-nickname-title": "Update nickname", - "nickname-visible-to-you": "Nickname will only be visible to you", - "type-nickname": "Type nickname", - "remove-nickname": "Remove nickname", - "nickname-removed": "Nickname removed", - "nickname-added": "Nickname for {{primary-name}} added", - "nickname-updated": "Nickname for {{primary-name}} updated", - "set-nickname-toast": "You have renamed {{primary-name}} as {{nickname}}", - "remove-nickname-toast": "You have removed {{secondary-name}}'s nickname", - "nickname-description": "Nicknames help you identify others in Status.\nOnly you can see the nicknames you’ve added", - "accept": "Accept", - "ignore": "Ignore", - "group-invite": "Group invite", - "group-invite-link": "Group invite link", - "pending-invitations": "Pending membership requests", - "empty-pending-invitations-descr": "People who wish to join the group\nvia an invite link will appear here", - "introduce-yourself": "Introduce yourself with a brief message", - "request-pending": "Request pending…", - "membership-declined": "Membership request was declined", - "remove-group": "Remove group", - "request-membership": "Request membership", - "membership-description": "Group membership requires you to be accepted by the group admin", - "group-membership-request": "Group membership request", - "members-limit-reached": "Members limit reached", - "favourite": "Favourite", - "favourites": "Favourites", - "new-favourite": "New favourite", - "edit-favourite": "Edit favourite", - "remove-favourite": "Remove favourite", - "add-favourite": "Add favourite", - "add-to-favourites": "Add to favourites", - "favourites-empty": "Addresses added to favourites will appear here", - "contacts-empty": "Contacts with ENS names will appear here", - "my-accounts": "My accounts", - "my-accounts-empty": "Your available accounts will appear here", - "recent-empty": "Recently used addresses will appear here", - "address-or-ens-name": "Address or ENS name", - "name-optional": "Name (optional)", - "mute": "Mute", - "unmute": "Unmute", - "mute-chat": "Mute chat", - "mute-chat-capitialized": "Mute Chat", - "unmute-chat": "Unmute chat", - "mute-community": "Mute community", - "unmute-community": "Unmute community", - "scan-tokens": "Scan tokens", - "my-status": "My status", - "contacts-descr": "Your contacts will appear here. You will receive status updates from anyone you add as a contact", - "status-updates-descr": "Status updates will appear here. Add the profile as a contact to receive updates on your timeline.", - "whats-on-your-mind": "What’s on your mind…", - "cant-open-public-chat": "Can't open public chat", - "invalid-public-chat-topic": "Invalid public chat topic", - "now": "Now", - "statuses-my-status-descr": "Share what’s on your mind. Anyone visiting your profile will be able to see your status. People who add you as their contact will receive your updates on their timeline", - "statuses-descr": "Share what’s on your mind and stay updated with your contacts", - "new-status": "New status", - "chat-link-previews": "Chat link previews", - "you-can-choose-preview-websites": "You can choose which of the following websites can preview link of descriptions and pictures in chats", - "previewing-may-share-metadata": "Previewing links from these websites may share your metadata with their owners", - "websites": "Websites", - "enable-all": "Enable all", - "disable-all": "Disable all", - "warning-sending-to-contract-descr": "The address you entered is a smart contract, sending funds to this address may result in loss of funds. To interact with a DApp, open the DApp in the Status DApp Browser.", - "dont-ask": "Don't ask me again", - "enable-link-previews": "Enable link previews in chat?", - "once-enabled-share-metadata": "Once enabled, links posted in the chat may share your metadata with the site", - "external-storage-denied": "Access to external storage is denied", - "timeline": "Timeline", - "main-account": "Main account", - "ethereum-address": "Ethereum address", - "default-assets": "Default ERC20 and ERC721", - "increase-gas": "Increase Gas", - "cancelling": "Cancelling", - "refresh": "Refresh", - "close-all": "Close all", - "tabs": "Tabs", - "new-tab": "New tab", - "empty-tab": "Empty tab", - "open-in-new-tab": "Open in new tab", - "has-permissions": "has permission to access", - "open-chat": "Open chat", - "favourite-description": "Your favourite websites will appear here", - "transfers-fetching-failure": "Transfers history could not be updated. Check your connection and pull down to try again", - "move-and-reset": "Move and Reset", - "move-keystore-file-to-keycard": "Move keystore file to keycard?", - "database-reset-title": "Database reset", - "database-reset-content": "Chats, contacts and settings have been deleted. You can use your account with your Keycard", - "database-reset-warning": "Database will be reset. Chats, contacts and settings will be deleted", - "empty-keycard-required": "Requires an empty Keycard", - "empty-keycard": "Empty Keycard", - "user-keycard": "{{name}} Card", - "current": "Current", - "choose-storage": "Choose storage", - "choose-new-location-for-keystore": "Choose a new location to save your keystore file", - "get-a-keycard": "Get a Keycard", - "keycard-upsell-subtitle": "Enhanced security and convenience", - "actions": "Actions", - "move-keystore-file": "Move keystore file", - "select-new-location-for-keys": "Select a new location to save your private key(s)", - "reset-database": "Reset database", - "reset-database-warning": "Delete chats, contacts and settings. Required when you’ve lost your password", - "reset-database-warning-keycard": "Delete chats, contacts and settings.", - "key-managment": "Key management", - "choose-actions": "Choose actions", - "master-account": "Master account", - "back-up": "Back up", - "key-on-device": "Private key is saved on this device", - "favorite-communities": "Your favourite communities", - "seed-key-uid-mismatch": "Seed doesn't match", - "seed-key-uid-mismatch-desc-1": "The seed phrase you entered does not match {{multiaccount-name}}", - "seed-key-uid-mismatch-desc-2": "To manage keys for this account verify your seed phrase and try again.", - "recover-with-seed-phrase": "Recover with seed phrase", - "transfer-ma-unknown-error-desc-1": "It looks like your multiaccount was not deleted. Database may have been reset", - "transfer-ma-unknown-error-desc-2": "Please check your account list and try again. If the account is not listed go to Access existing keys to recover with seed phrase", - "everyone": "Everyone", - "everyone-mention": "everyone", - "show-profile-pictures": "See profile pictures from", - "show-profile-pictures-to": "Show your profile picture to", - "non-archival-node": "RPC endpoint doesn't support archival requests. Your local transfers history might be incomplete.", - "custom-node": "You are using custom RPC endpoint. Your local transfers history might be incomplete.", - "connection-status": "Connection status", - "peer-to-peer": "Peer to peer", - "not-connected-to-peers": "Not connected to any peers", - "unable-to-send-messages": "Unable to send and receive messages", - "can-send-messages": "You can send and receive new messages", - "not-connected-nodes": "Not connected to a status node", - "unable-to-fetch": "Unable to fetch chat history", - "nodes-disabled": "Status nodes disabled", - "waiting-wi-fi": "Waiting for Wi-Fi…", - "you-can-fetch": "You can fetch chat history", - "youre-on-mobile-network": "You’re on mobile network", - "status-mobile-descr": "Status tends to use a lot of data when syncing chats. You can choose not to sync when on mobile network", - "restore-defaults": "Restore Defaults", - "rpc-usage-info": "RPC usage stats", - "rpc-usage-get-stats": "Refresh", - "rpc-usage-reset": "Reset", - "rpc-usage-filter": "Filter", - "rpc-usage-filter-methods": "Filter methods", - "rpc-usage-copy": "Copy", - "rpc-usage-total": "Total", - "rpc-usage-filtered-total": "{{filtered-total}} of {{total}}", - "community-message-preview": "Invitation to join {{community-name}}", - "non-contacts": "Non contacts", - "community": "Community", - "community-channel": "Community channel", - "verified-community": "✓ Verified community", - "community-info-not-found": "Community information not found", - "community-info": "Community info", - "not-found": "Not found", - "nothing-found": "Nothing found", - "try-to-search-something-else": "Try to search something else", - "activity": "Activity", - "reject-and-delete": "Reject and delete", - "accept-and-add": "Accept and add", - "one-day": "One day", - "three-days": "Three days", - "nine-days": "Nine days", - "one-week": "One week", - "one-month": "One month", - "my-profile": "My profile", - "bip39-password-placeholder": "BIP39 password", - "public-channel": "Public channel", - "default-sync-period": "Sync history for", - "what-is-shared": "What is shared", - "view-data": "View data", - "data-collected": "Data collected", - "data-collected-subtitle": "The table below shows the exact data that is stored and will be sent. Data is validated against public rules to ensure no sensitive data is sent. Don’t trust, verify.", - "view-rules": "View rules", - "expand-all": "Expand all", - "about-sharing-data": "About sharing data", - "sharing-data-desc-1": "Data is validated against public rules to ensure no sensitive data is sent. Don’t trust, verify.", - "sharing-data-desc-2": "Usage data is sent end-to-end encrypted over Status’ peer-to-peer network", - "sharing-data-desc-3": "Instead of your regular chat key, a single use key is used", - "sharing-data-desc-4": "Usage data cannot be associated with your IP address", - "sharing-data-desc-5": "Cumulative data of all users is publicaly available", - "view-public-dashboard": "View public dashboard", - "sharing-data-desc-6": "The data is removed from your phone after they are sent", - "allow-and-send": "Allow and send", - "no-thanks": "No thanks", - "help-improve-status": "Help improve Status", - "thank-you": "Thank you", - "current-password": "Current password", - "reset-password": "Reset password", - "password-reset-success": "Password changed", - "password-reset-success-message": "You will need to sign in again", - "password-reset-in-progress": "Changing password...", - "new-password": "New password", - "confirm-new-password": "Confirm new password", - "password-mismatch": "New password and confirmation does not match", - "terms-of-service": "Terms of Use", - "accept-status-tos-prefix": "I accept Status", - "updates-to-tos": "Updates to Terms of Use", - "updates-to-tos-desc": "Before you continue, please review the Terms of Use and confirm you take full responsibility for how you use the app.", - "what-changed": "What changed", - "wc-new-tos-based-on-principles-prefix": "New Terms of Use designed based on our", - "principles": "Principles", - "wc-how-to-use-status-app": "How to use the Status app including privacy and security", - "wc-brand-guide": "Guidance on using branding such as trademarks and logos", - "wc-disclaimer": "Disclaimers (including third party providers), warranties, and legal releases", - "wc-dispute": "Dispute resolution provisions", - "status-is-open-source": "Status is open-source", - "build-yourself": "To use the app without these Terms of Use, you can build your own version", - "accept-and-continue": "Accept and continue", - "empty-activity-center": "Your chat notifications\nwill appear here", - "pinned-messages": "Pinned messages", - "pinned-messages-2": "Pinned \nmessages", - "pinned-a-message": "pinned a message", - "pin": "Pin", - "unpin": "Unpin", - "no-pinned-messages": "No pinned messages", - "pinned-messages-count": { - "one": "1 pinned message", - "other": "{{count}} pinned messages" - }, - "pinned-messages-empty": "Pinned messages will appear here. To pin a message, press and hold it and tap `Pin`", - "pinned-by": "Pinned by", - "pin-limit-reached": "Pin limit reached. Unpin a previous message first.", - "no-fees": "No fees", - "not-enough-assets-to-pay-gas-fees": "Not enough assets to pay gas fees", - "not-enough-assets": "Not enough assets to complete transaction", - "max-fee": "Max fee", - "max-fees": "Max fees", - "max-priority-fee": "Max priority fee", - "miners-higher-fee": "Miners will likely inlcude your transaction earlier if you pay a higher fee.", - "gas-amount-limit": "Gas amount limit", - "per-gas-tip-limit": "Per-gas tip limit", - "per-gas-price-limit": "Per-gas price limit", - "current-base-fee": "Current base fee", - "fee-explanation": "Maximum overall price for the transaction. If the block base fee exceeds this, it will be included in a following block with a lower base fee.", - "slow": "Slow", - "optimal": "Optimal", - "fast": "Fast", - "see-suggestions": "See suggestions", - "maximum-fee": "Maximum fee", - "low-tip": "tip is too low", - "lower-than-average-tip": "lower than average tip", - "below-base-fee": "max fee below base fee", - "reduced-tip": "priority tip will be reduced", - "are-you-sure": "Are you sure?", - "bad-fees-description": "Your priority fee is below our suggested parameters.", - "change-tip": "Change tip", - "current-minimum-tip": "Current minimum tip", - "current-average-tip": "Current average tip", - "your-tip-limit": "Your tip limit", - "your-price-limit": "Your price limit", - "suggested-min-tip": "Suggested min. tip", - "sort-communities": "Sort communities", - "alphabetically": "Alphabetically", - "active-members": "Active members", - "total-members": "Total members", - "mutal-contacts": "Mutual contacts", - "suggested-price-limit": "Suggested price limit", - "include": "Include", - "category": "Category", - "edit-chats": "Edit chats", - "edit-categories": "Edit Categories", - "hide": "Hide", - "account-is-used": "The account is being used with Dapps in the browser.", - "normal": "Normal", - "never": "Never", - "online-now": "Online now", - "fee-options": "Suggested fee options", - "fee-cap": "Fee cap", - "tip-cap": "Tip cap", - "collectibles-leak-metadata": "You can display your NFTs here. If you do, you will share your wallet and IP address", - "display-collectibles": "Display collectibles", - "disable-later-in-settings": "You can disable this later in Settings", - "use-as-profile-picture": "Use as profile picture", - "view-on-opensea": "View on OpenSea", - "profile-picture-updated": "Profile picture updated", - "status-automatic": "Automatic", - "status-automatic-subtitle": "Set status automatically", - "status-dnd": "Do not disturb", - "status-dnd-subtitle": "Mutes all notifications", - "status-always-online": "Always Online", - "status-inactive": "Inactive", - "status-inactive-subtitle": "Hides your online status", - "two-minutes": "two minutes", - "swap": "Swap", - "slippage-settings": "Slippage settings", - "slippage-settings-description": "Your transaction will revert if the price changes more than the slippage percentage", - "slippage-may-be-higher-than-necessary": "Slippage may be higher than necessary", - "slippage-should-be-more-than-0": "Slippage should be more than 0", - "max-2-decimals": "Max. 2 decimals", - "type-slippage": "Type slippage", - "receive-at-least": "Receive at least", - "select-token-to-swap": "Select token to Swap", - "select-token-to-receive": "Select token to receive", - "slide-to-request-to-join": "Slide to request to join", - "slide-to-import": "Slide to import", - "slide-to-reveal-code": "Slide to reveal code", - "slide-to-create-account": "Slide to create account", - "slide-to-remove-key-pair": "Slide to remove key pair", - "slide-to-reveal-qr-code": "Slide to reveal QR code", - "minimum-received": "Minimum received", - "powered-by-paraswap": "Powered by Paraswap", - "priority": "Priority", - "switch-to-simple-interface": "Switch to simple interface", - "transaction-fee": "Transaction fee", - "swap-details": "Swap details", - "slippage": "Slippage", - "price-impact": "Price impact", - "total-gas": "Total gas", - "token": "Token", - "approve-limit": "Approve limit", - "approve-token": "Approve token", - "approve-token-contract-desc": "Approving a token with a contract allows it to spend your token balance. If you feel that a project is untrustworthy, don’t approve the token with them, or approve only the amount you will use with them.", - "unlimited": "Unlimited", - "approve": "Approve", - "approve-amount-symbol": "Approve {{amount}} {{symbol}}", - "approving-amount-symbol": "Approving {{amount}} {{symbol}}...", - "approved-amount-symbol": "Approved {{amount}} {{symbol}}", - "limit": "Limit", - "last-transaction": "Last transaction", - "price-impact-desc": "Estimated price impact for this transaction. If the current block base fee exceeds this, your transaction will be included in a following block with a lower base fee.", - "safe-estimate": "Safe estimate", - "current-average": "Current average", - "current-base": "Current base", - "maximum-fee-desc": "Maximum overall price for the transaction. If the current block base fee exceeds this, your transaction will be included in a following block with a lower base fee.", - "insufficient-balance-to-cover-fee": "not enough balance to cover transaction fee", - "wallet-connect-proposal-title": "Would like to connect with your wallet", - "wallet-connect-proposal-description": "By connecting you allow {{name}} to retrieve your account address and enable Web3", - "wallet-connect-app-connected": "is connected", - "wallet-connect-go-back": "Go back to your browser or dapp", - "wallet-connect-2.0": "Wallet Connect 2.0", - "wallet-connect": "Wallet Connect", - "wallet-connect-sign-message-header": "wants you to sign the message with", - "wallet-connect-send-transaction-header": "wants you to send this transaction with", - "wallet-connect-sign-transaction-header": "wants you to sign this transaction with", - "wallet-connect-sign-warning": "Sign only if you trust the dApp", - "wallet-connect-sign-message-warning": "Sign messages only if you trust the dApp", - "wallet-connect-send-transaction-warning": "Send transactions only if you trust the dApp", - "wallet-connect-sign-transaction-warning": "Sign transactions only if you trust the dApp", - "wallet-connect-networks-not-supported": "{{dapp}} requires an unsupported network.", - "reject": "Reject", - "manage-connections": "Manage connections from within Application Connections", - "contact-request-was-ignored": "Contact request ignored", - "wallet-manage-app-connections": "Manage app connections", - "connection-request": "Connection Request", - "disconnect": "Disconnect", - "app-connections": "App connections", - "new-ui": "New UI", - "send-contact-request-message": "To start a chat you need to become contacts", - "contact-request": "Contact request", - "send-contact-request": "Send contact request", - "contact-request-was-sent": "Contact request sent", - "contact-request-sent-to": "Contact request sent to", - "contact-request-was-accepted": "Contact request accepted", - "contact-request-is-now-a-contact": "is now a contact", - "contact-request-removed-you-as-contact": "removed you as a contact", - "contact-request-removed-as-contact": "removed as a contact", - "contact-requests": "Contact requests", - "contact-request-message-prompt": "Why should they accept your request?", - "say-hi": "Say hi", - "opened": "Opened", - "accepted": "Accepted", - "declined": "Declined", - "contact-request-sent": "sent contact request", - "contact-request-review": "Review contact request", - "contact-request-sent-toast": "{{name}} sent you a contact request", - "contact-request-accepted-toast": "{{name}} accepted your contact request", - "contact-request-outgoing": "You’re trying to connect with", - "contact-request-header": "👋 Contact requests", - "contact-request-declined": "Declined ⓧ", - "contact-request-accepted": "Accepted ✓", - "contact-request-pending": "Pending...", - "removed-from-contacts": "Removed from contacts", - "mutual-contact-requests": "Mutual contact requests", - "negative": "Negative", - "positive": "Positive", - "public": "Public", - "successful-connection": "Successful Connection", - "all-connections": "All Connections", - "K": "K", - "M": "M", - "gated": "Gated", - "featured": "Featured", - "music": "Music", - "lifestyle": "Lifestyle", - "podcasts": "Podcasts", - "NFT": "NFT", - "new-messages-header": "New Messages", - "link-to-community": "Link to community", - "external-link": "External link", - "kicked": "Kicked", - "delete-for-everyone": "Delete for everyone", - "pin-to-chat": "Pin to the chat", - "pin-to-channel": "Pin to the channel", - "unpin-from-chat": "Unpin from the chat", - "unpin-from-channel": "Unpin from the channel", - "cannot-pin-title": "You can't pin this message!", - "cannot-pin-desc": "You can only pin a max of 3 messages.\nUnpin at least one to pin a new one.", - "view-pinned-messages": "View pinned messages", - "copy-text": "Copy text", - "edit-message": "Edit message", - "save-image-library": "Save image to library", - "share-image": "Share image", - "see-sticker-set": "See the full sticker set", - "mentions": "Mentions", - "mention": "Mention", - "admin": "Admin", - "member": "Member", - "token-master": "Token Master", - "token-owner": "Token Owner", - "replies": "Replies", - "replied": "Replied", - "identity-verification": "Identity verification", - "identity-verification-request": "Identity verification", - "identity-verification-request-sent": "asks", - "type-something": "Type something", - "type-some-chat-key": "zQ3...1sgt5N", - "your-answer": "Your answer", - "membership": "Membership", - "jump-to": "Jump to", - "untrustworthy": "Untrustworthy", - "blank-messages-text": "Your messages will be here", - "blank-contacts-text": "Your contacts will be here", - "groups": "Groups", - "you-must-hold": "You must hodl:", - "you-must-now-hold": "You must now hold:", - "you-must-always-hold": "You must always hold:", - "shell-placeholder-title": "Your open tabs will be here", - "shell-placeholder-subtitle": "Jump between your communities, messages,\nwallet accounts and browser tabs", - "no-pinned-messages-desc": "Just keep pinning, just keep pinning\nWhat do we do? We pin, pin, pin", - "invite-friends-to-status": "Invite friends to Status", - "share-invite-link": "Share an invite link", - "pending-requests": "Pending requests", - "received": "Received", - "sent": "Sent", - "and": "and", - "rename": "Rename", - "mark-untrustworthy": "Mark as Untrustworthy", - "block-user": "Block user", - "block-user-title-message": "You will not see {{username}}'s messages, but {{username}} still can see your messages in mutual group chats and communities. {{username}} will be unable to message you.", - "blocking-a-user-message": "Blocking a user purges the database of all messages that you’ve previously received from {{username}} in all contexts.", - "unblocking-a-user-message": "After unblocking {{username}}, you will be able to connect with them as a contact and see their messages in group chats and communities.", - "group-details": "Group details", - "edit-name-and-image": "Edit name and image", - "change-group-privacy": "Change group privacy", - "manage-members": "Manage members", - "mute-group": "Mute group", - "mark-user-untrustworthy": "Mark {{username}} as untrustworthy", - "leave-group?": "Leave Group?", - "block-user?": "Block User?", - "clear-history?": "Clear History?", - "cut": "Cut", - "biu": "BIU", - "bold": "Bold", - "italic": "Italic", - "strikethrough": "Strikethrough", - "empty-notifications-all-tab": "No unread notifications", - "empty-notifications-admin-tab": "No unread admin notifications", - "empty-notifications-mentions-tab": "No unread mention notifications", - "empty-notifications-replies-tab": "No unread reply notifications to your messages", - "empty-notifications-contact-requests-tab": "No contact requests notifications", - "empty-notifications-identity-verification-tab": "No identity verification requests", - "empty-notifications-transactions-tab": "No transaction notifications", - "empty-notifications-membership-tab": "No membership notifications", - "empty-notifications-system-tab": "No system notifications", - "empty-notifications-title-unread": "You're up to date", - "empty-notifications-subtitle-unread": "Unread notifications will be here", - "empty-notifications-title-read": "No notifications", - "empty-notifications-subtitle-read": "Your notifications will be here", - "add-text": "Add text", - "send": "Send", - "unmute-group": "Unmute group", - "remove-user-from-group": "Remove {{username}} from the group", - "edit-name-image": "Edit name and image", - "owner": "Owner", - "local-pairing-experimental-mode": "Local Pairing Mode (alpha)", - "syncing": "Syncing", - "synced-devices": "Synced Devices", - "setup-syncing": "Pair devices to sync", - "sync-code": "Sync Code", - "sync-code-generated": "Sync code generated", - "generate-scan-sync-code": "Generate Scan Sync Code", - "try-your-luck-again": "Try your luck again!", - "instruction-after-qr-generated": "On your other device, navigate to the Syncing screen and select “Scan sync”", - "show-existing-keys": "Show Existing Keys", - "scan-sync-code": "Scan Sync Code", - "confirm-selection": "Confirm selection", - "chat-with": "Chat with {{selected-user}}", - "setup-group-chat": "Setup group chat", - "search-contacts": "Search contacts", - "who-are-you-looking-for": "Who are you looking for ?", - "close-contact-search": "Close contact search", - "selected-count-from-max": "{{selected}}/{{max}}", - "online": "Online", - "contact-request-chat-pending": "Your contact request is pending", - "contact-profile-request-pending": "Contact request pending", - "contact-request-chat-add": "Add {{name}} as contact to send a message", - "contact-request-chat-received": "{{name}} sent you a contact request", - "join-request": "Join request", - "join-one-user": "Join {{user}}", - "join-two-users": "Join {{user1}} and {{user2}}", - "join-more-users": "Join {{user1}}, {{user2}} and {{left-count}} more", - "wants-to-join": "wants to join", - "connect-with-users": "Connect with users", - "invite-friends-and-family": "Invite your friends and family to Status", - "you-have-no-contacts": "You have no contacts", - "my-albums": "My albums", - "images": "images", - "album-images-count": "{{album-images-count}} photos", - "hit-photos-limit": "You can only add {{max-photos}} photos to your message", - "delivered": "Delivered", - "mark-all-notifications-as-read": "Mark all notifications as read", - "notifications-marked-as-read": "{{count}} notifications marked as read", - "strength-divider-very-weak-label": "Very weak", - "strength-divider-weak-label": "Weak", - "strength-divider-okay-label": "Okay", - "strength-divider-strong-label": "Strong", - "strength-divider-very-strong-label": "Very strong", - "logged-in": "Logged in", - "profiles-on-device": "Profiles on device", - "profile-password": "Profile password", - "forgot-password": "Forgot password?", - "forgot-your-password-info-remove-app": "Remove the Status app", - "forgot-your-password-info-remove-app-description": "This will erase all your data from the device, including your password.", - "forgot-your-password-info-reinstall-app": "Reinstall the Status app", - "forgot-your-password-info-reinstall-app-description": "Re-download the app from your store.", - "forgot-your-password-info-signup-with-key": "with your existing keys", - "forgot-your-password-info-signup-with-key-description": "Access with your seed phrase or with your Keycard.", - "forgot-your-password-info-create-new-password": "Create a new password", - "forgot-your-password-info-create-new-password-description": "Enter a new password and you're all set! You will be able to use your new password.", - "forgot-your-password-info-title": "Forgot your password?", - "forgot-your-password-info-description": "To recover your password follow these steps:", - "log-in": "Log in", - "type-your-password": "Type your password", - "oops-wrong-password": "Oops, wrong password!", - "remove-profile?": "Remove profile?", - "remove-profile-message": "Remove profile from this device", - "remove-profile-confirm-message": "All profile data will removed from device.", - "create-new-profile": "Create new profile", - "add-existing-status-profile": "Add existing Status profile", - "find-sync-code": "Find sync code", - "find-it-in-setting": "Find it in Settings on your other synced device", - "sign-in-by-syncing": "Sign in by syncing", - "synchronise-your-data-across-your-devices": "Synchronise your data across your devices", - "scan-sync-qr-code": "Scan QR code", - "enter-sync-code": "Enter sync code", - "enable-access-to-local-network": "Enable access to local network", - "to-pair-with-your-other-device-in-the-network": "To pair with your other device in the network", - "enable-network-access": "Enable network access", - "enable-access-to-camera": "Enable access to camera", - "link-preview-loading-message": "Generating preview", - "to-scan-a-qr-enable-your-camera": "To scan a QR, enable your camera", - "enable-camera": "Enable camera", - "i-dont-have-status-on-another-device": "I don’t have Status on another device", - "ensure-qr-code-is-in-focus-to-scan": "Ensure that the QR code is in focus to scan", - "ensure-both-devices-are-on-the-same-network": "Ensure both devices are on the same network", - "error-this-is-not-a-sync-qr-code": "Oops! This is not a sync QR code", - "error-syncing-connection-failed": "Oops! Connection failed. Try again", - "camera-permission-denied": "Permission denied", - "enable-biometrics": "Enable biometrics", - "use-biometrics": "Use biometrics to fill in your password", - "minimum-characters": "Minimum {{min-chars}} characters", - "no-communities": "No communities", - "no-communities-description-strikethrough": "Never", - "no-communities-description": "go full nyan, find your community", - "no-pending-communities": "No pending communities", - "no-pending-communities-description": "It's toaster", - "no-opened-communities": "No opened communities", - "no-opened-communities-description": "But... feels good man", - "no-contacts": "No contacts", - "no-contacts-description": "This is fine, just invite your mother", - "no-group-chats": "No group chats", - "no-group-chats-description": "Much fun. Have friends. Wow!", - "no-messages": "No messages", - "no-messages-description": "Here’s a cat in a box instead", - "mute-for-15-mins": "For 15 min", - "mute-for-1-hour": "For 1 hour", - "mute-for-8-hours": "For 8 hours", - "mute-for-1-week": "For 7 days", - "mute-till-unmute": "Until you turn it back on", - "mute-channel": "Mute channel", - "community-request-accepted": "Request accepted", - "community-request-accepted-body-text": "Now you are a member of", - "community-request-not-accepted": "Request hasn't been accepted", - "community-request-not-accepted-body-text-prefix": "Your request to join", - "community-request-not-accepted-body-text-suffix": "hasn't been accepted", - "community-request-pending": "Request pending", - "community-request-pending-body-text": "You requested to join", - "community-kicked-heading": "Kicked from community", - "community-kicked-body": "You were kicked from", - "signing-in-from-another-device": "Signing in from the other device", - "open-status-on-your-other-device": "Open Status on your other device", - "im-new": "I'm new", - "scan-or-enter-sync-code-seen-on-this-device": "Scan or enter sync code seen on this device", - "press": "Press", - "tap": "Tap", - "or-tap": "or tap", - "tap-on": "Tap on", - "and-go-to": "and go to", - "saving-keys-to-device": "Saving keys to device...", - "scan-or-enter-a-sync-code": "Scan or enter a sync code", - "scan-the-qr-code-or-copy-the-sync-code": "Scan the QR code or copy the sync code", - "already-logged-in-on-the-other-device": "Already logged in on the other device", - "open-your": "Open your", - "go-to": "Go to", - "sync-new-device": "Sync new device", - "set-up-sync": "Set up sync", - "how-to-pair": "How to pair", - "mobile": "Mobile", - "desktop": "Desktop", - "error-loading-audio": "Error while loading audio", - "you-colon": "You:", - "you-replied": "You replied", - "user-replied": "{{user}} replied", - "you-pinned-a-message": "You pinned a message", - "Pinned-a-message": "Pinned a message", - "user-pinned-a-message": "{{user}} pinned a message", - "you-sent-a-sticker": "You sent a Sticker", - "sent-a-sticker": "Sent a Sticker", - "user-sent-a-sticker": "{{user}} sent a Sticker", - "you-sent-a-photo": "You sent a photo", - "sent-a-photo": "Sent a photo", - "user-sent-a-photo": "{{user}} sent a photo", - "you-sent-n-photos": "You sent {{number}} photos", - "sent-n-photos": "Sent {{number}} photos", - "user-sent-n-photos": "{{user}} sent {{number}} photos", - "you-sent-audio-message": "You sent audio message", - "sent-audio-message": "Sent audio message", - "user-sent-audio-message": "{{user}} sent audio message", - "you-sent-a-gif": "You sent a GIF", - "sent-a-gif": "Sent a GIF", - "user-sent-a-gif": "{{user}} sent a GIF", - "you-shared-a-community": "You shared a community", - "shared-a-community": "Shared a community", - "user-shared-a-community": "{{user}} shared a community", - "you-deleted-a-message": "You deleted a message", - "this-message-was-deleted": "This message was deleted", - "user-deleted-a-message": "{{user}} deleted a message", - "link-to-profile": "Link to profile", - "emoji-hash": "Emoji Hash", - "emoji-hash-copied": "Emojihash copied to clipboard", - "link-to-profile-copied": "Link to Profile copied to clipboard", - "sync-devices-result-sub-title": "Your devices are now in sync", - "sync-devices-title": "Syncing devices...", - "sync-devices-sub-title": "Please keep both devices switched on and connected to the internet until sync is complete", - "sync-devices-error-title": "Oops, something’s wrong", - "sync-devices-error-sub-title": "Make sure both devices are powered on and connected to the internet.", - "sync-devices-complete-title": "Device sync complete!", - "sync-devices-complete-sub-title": "Your devices are now in sync", - "synced-with": "Synced with", - "confirm-and-leave": "Confirm and leave", - "change-testnet-mode-logout-info": "You’ll be logged out of the app", - "membership-requirements-not-met": "Membership requirements not met", - "edit-shared-addresses": "Edit shared addresses", - "leave-community-farewell": "We’ll be sad to see you go but remember, you can come back at any time! All shared addresses will be unshared.", - "all-changes-will-be-discarded": "All changes in shared addresses for permissions will be discarded.", - "discard": "Discard", - "discard-changes?": "Discard changes?", - "you-will-be-a": "You’ll be a", - "you-are-a": "You’re a", - "you-are-a-role": "You’re a {{role}}", - "you-eligible-to-join": "You’re eligible to join", - "you-eligible-to-join-as": "You’re eligible to join as {{role}}", - "eligible-to-join-as": "Eligible to join as", - "you-not-eligible-to-join": "You’re not eligible to join", - "you-not-eligible-to-join-as": "You’re not eligible to join as {{role}}", - "you-hold-number-of-hold-tokens-of-these": "You hold {{number-of-hold-tokens}} of these:", - "addresses-dont-contain-tokens-needed": "These addresses don’t contain tokens needed to join", - "you-hodl": "You hodl:", - "network-not-supported": "Networks not supported", - "token-gated-communities": "Token gated communities", - "read-more": "Read more", - "token-gated-communities-info": "Here will be something relevant about this topic. This will help the user get more context and therefore have a better understanding of it.", - "dont-yell-at-me": "Don’t yell at me", - "dont-have-statatus-on-another-device": "Don't have Status on another device?", - "unmute-channel": "Unmute channel", - "all-messages": "All messages", - "muted-until": "Muted until {{duration}}", - "until-you-turn-it-back-on": "you turn it back on", - "mo": "Mo", - "tu": "Tu", - "we": "We", - "th": "Th", - "fr": "Fr", - "sa": "Sa", - "su": "Su", - "mon": "Mon", - "tue": "Tue", - "wed": "Wed", - "thu": "Thu", - "fri": "Fri", - "sat": "Sat", - "sun": "Sun", - "jan": "Jan", - "feb": "Feb", - "mar": "Mar", - "apr": "Apr", - "may": "May", - "jun": "Jun", - "jul": "Jul", - "aug": "Aug", - "sep": "Sep", - "oct": "Oct", - "nov": "Nov", - "dec": "Dec", - "channel-muted-for-15-minutes": "Channel muted for 15 minutes \n(until {{duration}})", - "channel-muted-for-1-hour": "Channel muted for 1 hour \n(until {{duration}})", - "channel-muted-for-8-hours": "Channel muted for 8 hours \n(until {{duration}})", - "channel-muted-for-1-week": "Channel muted for 1 week \n(until {{duration}})", - "channel-muted-till-unmuted": "Channel muted till unmuted \n(until {{duration}})", - "chat-muted-for-15-minutes": "Chat muted for 15 minutes \n(until {{duration}})", - "chat-muted-for-1-hour": "Chat muted for 1 hour \n(until {{duration}})", - "chat-muted-for-8-hours": "Chat muted for 8 hours \n(until {{duration}})", - "chat-muted-for-1-week": "Chat muted for 1 week \n(until {{duration}})", - "chat-muted-till-unmuted": "Chat muted till unmuted \n(until {{duration}})", - "until": "until", - "chat-unmuted-successfully": "Chat unmuted successfully!", - "channel-unmuted-successfully": "Channel unmuted successfully!", - "photo-saved": "Photo saved to your device", - "community-unmuted": "Community unmuted", - "all-time": "All time", - "one-week-int": "1 week", - "one-month-int": "1 month", - "three-months-int": "3 months", - "one-year": "1 year", - "retake": "Retake", - "use-photo": "Use Photo", - "photo-caps": "PHOTO", - "assets": "Assets", - "collectibles": "Collectibles", - "no-collectibles-description": "Don't be a bored ape", - "no-activity": "No activity", - "empty-tab-description": "C'mon do something...", - "buy": "Buy", - "bridge": "Bridge", - "bridge-from": "Bridge from {{bridge-name}}", - "bridge-to": "Bridge {{name}} to", - "on-device": "On device", - "on-keycard": "On Keycard", - "keypair-title": "{{name}}'s default key pair", - "about": "About", - "no-permissions": "No permissions", - "connected-dapps": "Connected dApps", - "no-dapps": "No connected dApps", - "days": "Days", - "add-account": "Add account", - "add-account-description": "Create new or import existing account", - "add-address-to-watch": "Add address to watch", - "add-address-to-watch-description": "Watch a public address or ENS name", - "colour": "Colour", - "origin": "Origin", - "slide-to-sign": "Slide to sign", - "user-keypair": "{{name}}'s Key pair", - "trip-accounts": "Trip accounts", - "destroy": "Destroy", - "mint": "Mint", - "via": "via", - "x-counter": "x{{counter}}", - "code-snippet": "Code snippet", - "name-ens-or-address": "Name, ENS, or address", - "emoji-search-placeholder": "Search emojis", - "emoji-recent": "Recent", - "emoji-people": "People", - "emoji-nature": "Nature", - "emoji-food": "Food", - "emoji-activity": "Activity", - "emoji-travel": "Travel", - "emoji-objects": "Objects", - "emoji-symbols": "Symbols", - "emoji-flags": "Flags", - "emoji-no-results-title": "No emojis match your search", - "emoji-no-results-description": "Try something like “rainbow”", - "send-to": "Send to", - "saved": "Saved", - "no-recent-transactions": "No recent transactions", - "make-one-it-is-easy-we-promise": "Make one, it’s easy, we promise!", - "saved-addresses": "Saved addresses", - "no-saved-addresses": "No saved addresses", - "you-like-to-type-43-characters": "Do you like to type 43 characters?", - "no-other-accounts": "No other accounts", - "here-is-a-cat-in-a-box-instead": "Here’s a cat in a box instead", - "accounts-count": "{{count}} accounts", - "key-pairs-successfully-imported": "{{count}} key pairs successfully imported", - "key-pair-imported-successfully": "{{name}} key pair imported successfully", - "n-m-people": "{{n}}/{{m}} people", - "enter-eth": "Enter any ETH address or ENS name.", - "eth-or-ens": "ETH address or ENS name.", - "type-pairing-code": "Type or paste pairing code", - "overview": "Overview", - "traits": "Traits", - "opensea": "OpenSea", - "mainnet": "Mainnet", - "view-on-eth": "View on Etherscan", - "view-on-oeth": "View on Optimism Explorer", - "view-on-arb": "View on Arbiscan", - "copy-address": "Copy address", - "show-address-qr": "Show address QR", - "scan-sync-code-placeholder": "cs2:4FH...", - "visit-dapp": "Visit dApp", - "disconnect-dapp": "Disconnect dApp", - "disconnect-dapp-confirmation": "Are you sure you want to disconnect {{dapp}}?", - "disconnect-dapp-success": "{{dapp}} disconnected from {{account}}", - "disconnect-dapp-fail": "Failed to disconnect {{dapp}} from {{account}}", - "edit-account": "Edit account", - "edit-details": "Edit details", - "share-account": "Share account", - "remove-account": "Remove account", - "select-another-account": "Select another account", - "oops-this-qr-does-not-contain-an-address": "Oops! This QR does not contain an address", - "scan-an-account-qr-code": "Scan an account QR code", - "scan-an-address-qr-code": "Scan an address QR code", - "buy-assets": "Buy assets", - "one-time": "One time", - "recurrent": "Recurrent", - "account-info": "Account info", - "network-preferences": "Network preferences", - "network-preferences-desc-1": "Select which networks this address is happy to receive funds on", - "network-preferences-desc-2": "Select which networks to receive funds on", - "layer-2": "Layer 2", - "manage-tokens": "Manage tokens", - "edit-derivation-path": "Edit derivation path", - "path-format": "Path format", - "reset": "Reset", - "light-client-enabled": "Light client", - "reveal-address": "Reveal address", - "derive-addresses": "Derive addresses", - "sign transactions": "sign transactions", - "account-created": "{{name}} created", - "update-account-name": "Update account name", - "edit-wallet-account-emoji-updated-message": "Account emoji has been updated", - "edit-wallet-account-name-updated-message": "Account name has been updated", - "edit-wallet-account-colour-updated-message": "Account colour has been updated", - "edit-wallet-network-preferences-updated-message": "Account network preferences has been updated", - "search-assets": "Search assets", - "address-activity": "This address has activity", - "address-no-activity": "This address has no activity", - "scanning": "Scanning for activity...", - "keypairs": "Key pairs", - "keypairs-and-accounts": "Key pairs and accounts", - "keypairs-accounts-and-addresses": "Key pairs, accounts and addresses", - "keypairs-description": "Select key pair to derive your new account from", - "confirm-account-origin": "Confirm account origin", - "confirmed-on": "Confirmed on", - "pending-on": "Pending on", - "finalized-on": "Finalized on", - "failed-on": "Failed on", - "epoch-number": "Epoch {{number}}", - "arbitrum": "Arbitrum", - "optimism": "Optimism", - "address-placeholder": "0x123abc... or bob.eth", - "invalid-address": "It’s not Ethereum address or ENS name", - "address-already-in-use": "Address already being used", - "address-copied": "Address copied", - "no-dapps-description": "We want dApps!", - "select-asset": "Select asset", - "select-asset-to-pay": "Select asset to pay", - "send-limit": "Max: {{limit}}", - "searching-for-activity": "Searching for activity...", - "this-address-has-no-activity": "This address has no activity", - "details": "Details", - "est-time": "Est. time", - "recipient-gets": "Recipient gets", - "slide-to-send": "Slide to send", - "generate-new-keypair": "Generate new key pair", - "import-using-phrase": "Import using recovery phrase", - "import-from-keycard": "Import from Keycard", - "import-private-key": "Import private key", - "import-by-scanning-encrypted-qr": "Import by scanning encrypted QR", - "backup-recovery-phrase": "Backup recovery phrase", - "backup-recovery-phrase-description": "Save in a secure place that only you control, these 12 words give access to all of your funds.", - "how-to-backup": "How to backup your recovery phrase", - "backup-step-1": "Find pen and paper", - "backup-step-2": "Write down your recovery phrase", - "backup-step-3": "Find a place to store it", - "backup-step-4": "I know I can only see it once", - "reveal-phrase": "Reveal phrase", - "i-have-written": "I have written it down on paper", - "next-you-will": "Next, you will be asked to confirm the position of certain words in your recovery phrase", - "share-opensea-link": "Share OpenSea link", - "save-image-to-photos": "Save image to Photos", - "copy-all-details": "Copy all details", - "share-details": "Share details", - "what-are-you-waiting-for": "What are you waiting for?", - "no-relevant-tokens": "No relevant tokens", - "sending-with-ellipsis": "Sending...", - "sending-with-elipsis": "Sending...", - "transaction-confirmed": "Transaction confirmed!", - "transacation-finalised": "Transaction finalised!", - "from-label": "From", - "to-label": "To", - "oops-wrong-word": "Oops! Wrong word", - "time-in-mins": "{{minutes}} min", - "amount-with-currency-symbol": "{{symbol}} {{amount}}", - "no-routes-found": "No routes found", - "remove-watched-address-title": "Remove watched address", - "remove-watched-address-desc": "The watched address will be removed from all of your synced devices.", - "remove-account-title": "Remove account", - "remove-account-desc": "The account will be removed from all of your synced devices. Make sure you have a backup of your key pair or recovery phrase and derivation path (if it’s not default).", - "remove-private-key-address-desc": "The account will be removed from all of your synced devices. Make sure you have a backup of your key pair or recovery phrase.", - "derivation-path-copied": "Derivation path copied", - "remove-account-confirmation": "I have taken note of the derivation path", - "edit-receiver-networks": "Edit receiver networks", - "preferred-by-receiver": "Preferred by receiver", - "not-preferred-by-receiver": "Not preferred by receiver", - "apply-changes": "Apply changes", - "receiver-networks-warning": "Changing these settings may result in sending tokens to networks the recipient doesn't use", - "no-routes-found-confirmation": "No routes found. Token type may not be supported or you may don't have enough ETH to cover gas.", - "watched-account-removed": "Watched address has been removed", - "account-removed": "Account has been removed", - "share-address-title": "{{address}} address", - "confirm-backup": "Confirm backup", - "confirm-the-position": "Confirm the position of certain words in your recovery phrase", - "do-not-cheat": "Don't try to cheat", - "do-not-cheat-description": "These 12 words give access to all of your funds so it is important that you write them in the correct order, take it seriously.", - "see-recovery-phrase-again": "See recovery phrase again", - "fees": "Fees", - "account-origin-header": "Account origin", - "account-origin-desc": "To create a new account, you can generate a key pair in the app or import an existing account.", - "origin-header": "Origin", - "origin-desc": "Origin is where your key pair (your private and public key) comes from. You can generate a new key pair or import an existing private key.", - "derivation-path-header": "Derivation path", - "derivation-path-desc": "Derivation paths are the routes your Status Wallet uses to generate addresses from your private key.", - "select-networks": "Select networks", - "select-network": "Select network", - "generating-keypair": "Generating key pair...", - "keypair-name": "Key pair name", - "keypair-name-description": "Name key pair for your own personal reference", - "keypair-name-input-placeholder": "Collectibles key pair, Old vault....", - "key-pair-name-updated": "Key pair name updated", - "key-pair-removed": "Key pair and derived accounts has been removed", - "goerli-testnet-toggle-confirmation": "Are you sure you want to toggle Goerli? This will log you out and you will have to login again.", - "sepolia-active": "Sepolia active", - "testnet-not-available": "Testnet not available", - "bridged-to": "Bridged to {{network}}", - "slide-to-bridge": "Slide to bridge", - "slide-to-swap": "Slide to swap", - "provider-is-down": "The provider for the following chain(s) is down: {{chains}}", - "unknown": "Unknown", - "unsupported-file": "Unsupported file", - "cant-fetch-info": "Can't fetch info", - "default-ethereum-format": "Default Ethereum format", - "ropsten-testnet": "Ropsten Testnet", - "ledger": "Ledger", - "ledger-live": "Ledger live", - "keep-key": "KeepKey", - "type-your-path": "Type your own derivation path", - "default-format": "Default format", - "send-to-this-address": "Send to this address", - "address-count": { - "one": "1 address", - "other": "{{count}} addresses" - }, - "max": "Max: {{number}}", - "your-key-pair-name-is-too-long": "Your key pair name is too long", - "your-key-pair-name-is-too-short": "Your key pair name is too short", - "key-name-error-taken": "Key pair name already in use", - "key-name-error-length": "Key name too long", - "key-name-error-emoji": "Emojis are not allowed", - "key-name-error-special-char": "Special characters are not allowed", - "key-name-error-too-short": "Key pair name must be at least {{count}} characters", - "name-must-differ-error": "Name must differ from other accounts", - "emoji-and-colors-unique-error": "Emoji and colour combination must be unique", - "display": "Display", - "testnet-mode": "Testnet mode", - "turn-on-testnet-mode": "Turn on testnet mode", - "turn-off-testnet-mode": "Turn off testnet mode", - "testnet-mode-enable-description": "In this mode, all blockchain data displayed will come from testnets and all blockchain interactions will be with testnets.\nTestnet mode switches the entire app to using testnets only. Please switch this mode on only if you know exactly why you need to use it.", - "testnet-mode-disable-description": "Are you sure you want to turn off Testnet mode? All future transactions will be performed on live networks with real funds.", - "testnet-mode-enabled": "Testnet mode enabled", - "online-community-member": "Online", - "offline-community-member": "Offline", - "invite-to-community": "Invite to community", - "invite-n-users": "Invite {{count}} users", - "invite-1-user": "Invite 1 user", - "one-user-was-invited": "1 user was invited", - "n-users-were-invited": "{{count}} users were invited", - "invite-friend-to-status": "Invite friends to Status", - "enter-private-key": "Enter the private key of an address", - "enter-private-key-placeholder": "Enter your private key", - "import-to-use-derived-accounts": "Import to use derived accounts", - "amount-missing-keypairs": "{{amount} missing key pairs", - "import-private-key-info": "New addresses cannot be derived from an account imported from a private key. Import using a seed phrase if you wish to derive addresses.", - "invalid-private-key": "It’s not a valid private key", - "correct-private-key": "Correct private key", - "incorrect-private-key": "This is not the private key for this key pair", - "private-key-public-address": "Public address of private key", - "this-account-has-no-activity": "This account has no activity", - "this-address-has-activity": "This address has activity", - "scanning-for-activity": "Scanning for activity...", - "at-least-one-network-must-be-activated": "At least 1 network must be activated", - "send-community-link": "Send community link", - "status-is-a-secure-messaging-app": "Status is a secure messaging app, crypto wallet and web3 browser built with the state of the art technology", - "token-not-available-on-receiver-networks": "{{token-symbol}} is not available on the recipient’s desired networks. Proceed with caution.", - "add-networks-token-can-be-sent-to": "Add networks {{token-symbol}} can be sent to", - "not-available": "Not available", - "token-not-available-on-networks": "{{token-symbol}} is not available on {{networks}}.", - "sending-to-networks-the-receiver-does-not-prefer": "Sending to networks the receiver does not prefer may result in recipient having difficulty accessing the sent tokens.", - "proceed-anyway": "Proceed anyway", - "sending-to-unpreferred-networks": "Sending to unpreferred networks", - "review-send": "Review send", - "review-bridge": "Review bridge", - "send-to-user": "Send to {{user}}", - "view-address-on-etherscan": "View address on Etherscan", - "view-address-on-optimistic": "View address on Optimistic", - "view-address-on-arbiscan": "View address on Arbiscan", - "saved-address-removed": "Saved address removed", - "remove-address": "Remove address", - "remove-saved-address": "Remove saved address", - "remove-saved-address-description": "Transaction history relating to this address will no longer be labelled ‘{{name}}’.", - "add-address": "Add address", - "add-address-to-save-description": "Paste, scan or type ETH address or ENS name", - "existing-saved-address": "Existing saved address", - "this-is-not-an-eth-address-or-ens-name": "This is not an Ethereum address or ENS name", - "you-cannot-add-your-own-account-as-a-saved-address": "You cannot add your own account as a saved address", - "this-address-is-already-saved": "This address is already saved", - "this-ens-name-is-not-registered-yet": "This ENS name is not registered yet", - "address-saved": "Address saved", - "address-edited": "Address edited", - "dapp-will-be-able-to": "{{dapp-name}} will be able to:", - "check-your-account-balance-and-activity": "Check your account balance and activity", - "request-txns-and-message-signing": "Request transactions and message signing", - "wallet-connect-label": "WalletConnect", - "wallet-connect-via": "via", - "wallet-connect-qr-expired": "WalletConnect QR has expired", - "wallet-connect-version-not-supported": "WalletConnect version {{version}} is not supported", - "wallet-connect-wrong-qr": "It’s not a WalletConnect QR", - "add-network-preferences": "Add network preferences", - "saved-address-network-preference-selection-description": "Only change if you know which networks the address owner is happy to to receive funds on", - "add-preferences": "Add preferences", - "buy-eth": "Buy ETH", - "send-from-network": "Send from {{network}}", - "define-amount-sent-from-network": "Define amount sent from {{network}} network", - "dont-auto-recalculate-network": "Don't auto recalculate {{network}}", - "import-keypair-to-use-account": "Import key pair to use this account", - "import-keypair-steps": "{{account-name}} was derived from your {{keypair-name}} key pair, which has not yet been imported to this device. To transact using this account, you will need to import the {{keypair-name}} key pair first.", - "not-now": "Not now", - "share-usage-data": "Share usage data with Status", - "value-higher-than-send-amount": "This value is higher than entered amount to send", - "swaps-powered-by": "Swaps powered by {{provider}}", - "max-slippage": "Max slippage", - "pay": "Pay", - "store-confirmations": "Store confirmations", - "help-us-improve-status": "Help us improve Status", - "collecting-usage-data": "Collecting usage data helps us improve Status.", - "what-we-will-receive":"What we will receive:", - "ip-address":"IP address", - "universally-unique-identifiers-of-device":"Universally Unique Identifiers of device", - "logs-of-actions-withing-the-app":"Logs of actions within the app, including button presses and screen visits", - "what-we-wont-receive":"What we won't receive:", - "your-profile-information":"Your profile information", - "your-addresses":"Your addresses", - "information-you-input-and-send":"Information you input and send", - "sharing-usage-data-can-be-turned-off":"Sharing usage data can be turned off anytime in Settings / Privacy and Security.", - "share-usage-data":"Share usage data", - "do-not-share":"Do not share" + "about": "About", + "about-app": "About", + "about-key-storage-content": "Status will never access your private key. Be sure to backup your seed phrase. If you lose your phone it is the only way to access your keys.", + "about-key-storage-title": "About key storage", + "about-names-content": "No one can pretend to be you! You’re anonymous by default and never have to reveal your real name. You can register a custom name for a small fee.", + "about-names-title": "Names can’t be changed", + "about-sharing-data": "About sharing data", + "accent-colour": "Accent colour", + "accent-colour-updated": "Accent colour updated", + "accept": "Accept", + "accept-and-add": "Accept and add", + "accept-and-continue": "Accept and continue", + "accept-and-share-address": "Accept and share address", + "accept-community-rules": "I agree with the community rules", + "accept-new-chats-from": "Accept new chats from", + "accept-status-tos-prefix": "I accept Status", + "accepted": "Accepted", + "access-existing-keys": "Access existing keys", + "access-key": "Access key", + "account-added": "Account added", + "account-color": "Account color", + "account-content": "You can compare accounts in Status to bank accounts. Like a bank account, an account typically has an address and a balance; You use this account to transact on Ethereum. You can have multiple accounts in your wallet. All accessed by unlocking Status.", + "account-created": "{{name}} created", + "account-exists-title": "Account already exists", + "account-info": "Account info", + "account-is-used": "The account is being used with Dapps in the browser.", + "account-name": "Account name", + "account-origin-desc": "To create a new account, you can generate a key pair in the app or import an existing account.", + "account-origin-header": "Account origin", + "account-removed": "Account has been removed", + "account-settings": "Account settings", + "account-title": "Account", + "accounts": "Accounts", + "accounts-count": "{{count}} accounts", + "actions": "Actions", + "active-members": "Active members", + "active-online": "Online", + "active-unknown": "Unknown", + "activity": "Activity", + "add": "Add", + "add-a-contact": "Add a contact", + "add-a-watch-account": "Add a watch-only address", + "add-account": "Add account", + "add-account-description": "Create new or import existing account", + "add-account-incorrect-password": "Password seems to be incorrect. Enter the password you use to unlock the app.", + "add-address": "Add address", + "add-address-to-save-description": "Paste, scan or type ETH address or ENS name", + "add-address-to-watch": "Add address to watch", + "add-address-to-watch-description": "Watch a public address or ENS name", + "add-an-account": "Add an account", + "add-bootnode": "Add bootnode", + "add-contact": "Add contact", + "add-custom-token": "Add custom token", + "add-existing-status-profile": "Add existing Status profile", + "add-favourite": "Add favourite", + "add-mailserver": "Add Status node", + "add-me-to-your-contacts": "Please add me to your contacts", + "add-members": "Add members", + "add-network-preferences": "Add network preferences", + "add-networks-token-can-be-sent-to": "Add networks {{token-symbol}} can be sent to", + "add-new-contact": "Add new contact", + "add-nickname": "Add a nickname (optional)", + "add-nickname-title": "Add nickname", + "add-node": "Add node", + "add-preferences": "Add preferences", + "add-private-key-account": "Add account from private key", + "add-seed-account": "Add account with a seed phrase", + "add-text": "Add text", + "add-to-contacts": "Add to contacts", + "add-to-contacts-text": "By adding a user to your contact list, you share your wallet address", + "add-to-favourites": "Add to favourites", + "add-watch-account": "Add a watch-only account", + "add-watched-address": "Add watched address", + "added": "added", + "added-to-group-chat": "Added to group chat", + "added-you-to": "added you to", + "address": "Address", + "address-activity": "This address has activity", + "address-already-in-use": "Address already being used", + "address-copied": "Address copied", + "address-count": { + "one": "1 address", + "other": "{{count}} addresses" + }, + "address-edited": "Address edited", + "address-name": "Address name", + "address-no-activity": "This address has no activity", + "address-or-ens-name": "Address or ENS name", + "address-placeholder": "0x123abc... or bob.eth", + "address-received": "Address received", + "address-request-accepted": "Address request accepted", + "address-requested": "Address requested", + "address-saved": "Address saved", + "address-to-share": "Addresses to share", + "addresses-dont-contain-tokens-needed": "These addresses don’t contain tokens needed to join", + "addresses-for-permissions": "Addresses for permissions", + "admin": "Admin", + "advanced": "Advanced", + "advanced-settings": "Advanced settings", + "advertiser-description": "You’ve discovered Status thanks to a partner. Do you mind if Status checks your IP address once so they get rewarded? This information will not be used for anything else and it will be removed completely after 7 days.", + "advertiser-starter-pack-accept": "Accept", + "advertiser-starter-pack-decline": "Decline", + "advertiser-starter-pack-description": "Here’s some crypto to get you started! Use it to get stickers, an ENS name and try dapps", + "advertiser-starter-pack-title": "Starter Pack", + "advertiser-title": "Privacy by default", + "agree-by-continuing": "By continuing you agree\n to our ", + "airdrop-addresses": "Address for airdrops", + "album-images-count": "{{album-images-count}} photos", + "all": "All", + "all-addresses": "All addresses", + "all-changes-will-be-discarded": "All changes in shared addresses for permissions will be discarded.", + "all-connections": "All Connections", + "all-messages": "All messages", + "all-networks": "All networks", + "all-time": "All time", + "allow": "Allow", + "allow-and-send": "Allow and send", + "allow-mention-notifications": "Show @ mentions", + "allow-new-contact-requests": "Allow new contact requests", + "allowing-authorizes-this-dapp": "Allowing authorizes this DApp to retrieve your wallet address and enable Web3", + "alphabetically": "Alphabetically", + "already-have-asset": "You already have this asset", + "already-logged-in-on-the-other-device": "Already logged in on the other device", + "amount": "Amount", + "amount-missing-keypairs": "{{amount} missing key pairs", + "amount-with-currency-symbol": "{{symbol}} {{amount}}", + "and": "and", + "and-go-to": "and go to", + "anyone": "Anyone", + "app-commit": "App commit", + "app-connections": "App connections", + "appearance": "Appearance", + "apply": "Apply", + "apply-changes": "Apply changes", + "approve": "Approve", + "approve-amount-symbol": "Approve {{amount}} {{symbol}}", + "approve-limit": "Approve limit", + "approve-token": "Approve token", + "approve-token-contract-desc": "Approving a token with a contract allows it to spend your token balance. If you feel that a project is untrustworthy, don’t approve the token with them, or approve only the amount you will use with them.", + "approved-amount-symbol": "Approved {{amount}} {{symbol}}", + "approving-amount-symbol": "Approving {{amount}} {{symbol}}...", + "apr": "Apr", + "arbitrum": "Arbitrum", + "are-not-allowed": "{{check} are not allowed", + "are-you-sure": "Are you sure?", + "are-you-sure-description": "You will not be able to see the whole seed phrase again", + "are-you-sure-to-cancel": "Are you sure you want to cancel?", + "are-you-sure?": "Are you sure?", + "ask-in-status": "Ask a question or report a bug", + "assets": "Assets", + "at": "at", + "at-least-one-network-must-be-activated": "At least 1 network must be activated", + "attribution-received": "{{attrib}} out of {{max}} bonuses received", + "audio": "Audio", + "audio-message": "Audio message", + "audio-recorder": "Recorder", + "audio-recorder-error": "Recorder error", + "audio-recorder-max-ms-reached": "Maximum recording time reached", + "audio-recorder-permissions-error": "You have to give permission to send audio messages", + "aug": "Aug", + "authorize": "Authorize", + "available": "Available", + "available-participants": { + "one": "You can select one more participant", + "other": "You can select {{count}} more participants" + }, + "back": "Back", + "back-up": "Back up", + "back-up-seed-phrase": "Backup recovery phrase", + "back-up-your-seed-phrase": "Backup your recovery phrase", + "backing-up": "Backing up...", + "backup-disabled": "Disabled", + "backup-enabled": "Enabled", + "backup-recovery-phrase": "Backup recovery phrase", + "backup-recovery-phrase-description": "Save in a secure place that only you control, these 12 words give access to all of your funds.", + "backup-settings": "Backup settings", + "backup-step-1": "Find pen and paper", + "backup-step-2": "Write down your recovery phrase", + "backup-step-3": "Find a place to store it", + "backup-step-4": "I know I can only see it once", + "backup-through-waku": "Backup through waku", + "bad-fees-description": "Your priority fee is below our suggested parameters.", + "balance": "Balance", + "begin-set-up": "Begin setup", + "below-base-fee": "max fee below base fee", + "bio": "Bio", + "bio-added": "Bio added", + "bio-is-too-long": "Bio is too long", + "bio-updated": "Bio updated", + "biometric": "Biometric", + "biometric-auth-android-sensor-desc": "Touch sensor", + "biometric-auth-android-sensor-error-desc": "Failed", + "biometric-auth-android-title": "Authentication Required", + "biometric-auth-confirm-logout": "Relogin", + "biometric-auth-confirm-message": "Biometric authentication is required to continue, if not possible please unlock your keys with your password or passcode", + "biometric-auth-confirm-title": "You must authenticate!", + "biometric-auth-confirm-try-again": "Try again", + "biometric-auth-error": "Unable perform biometric authentication ({{code}})", + "biometric-auth-login-error-title": "Biometric authentication error", + "biometric-auth-login-ios-fallback-label": "Enter Password", + "biometric-auth-reason-login": "Login in Status", + "biometric-auth-reason-verify": "Verify authentication", + "biometric-disable-bioauth": "disable {{bio-type-label}}", + "biometric-disable-password-description": "If you disable this, you will also ", + "biometric-disable-password-title": "Disable password saving", + "biometric-enable": "If you don't want to enter your password each time to access the app, enable {{bio-type-label}} sign in", + "biometric-enable-button": "Enable {{bio-type-label}}", + "biometric-enable-keycard": "If you don't want to use your Keycard each time to access the app, enable {{bio-type-label}} sign in", + "biometric-faceid": "Face ID", + "biometric-fingerprint": "Fingerprint", + "biometric-secure-with": "Secure with {{bio-type-label}}", + "biometric-too-many-attempts": "Too many attempts. Enter the password manually or try again after some time", + "biometric-touchid": "Touch ID", + "bip39-password-placeholder": "BIP39 password", + "biu": "BIU", + "blank-contacts-text": "Your contacts will be here", + "blank-keycard-text": "You can proceed with your keycard once you've generated your keys and name", + "blank-keycard-title": "Looks like you’ve tapped \na blank keycard", + "blank-messages-text": "Your messages will be here", + "block": "Block", + "block-contact": "Block this user", + "block-contact-details": "Blocking will delete this user's previous messages and stop new ones from reaching you", + "block-user": "Block user", + "block-user-title-message": "You will not see {{username}}'s messages, but {{username}} still can see your messages in mutual group chats and communities. {{username}} will be unable to message you.", + "block-user?": "Block User?", + "blocked-users": "Blocked users", + "blocking-a-user-message": "Blocking a user purges the database of all messages that you’ve previously received from {{username}} in all contexts.", + "bold": "Bold", + "bootnode-address": "Bootnode address", + "bootnode-details": "Bootnode details", + "bootnode-format": "enode://{enode-id}@{ip-address}:{port}", + "bootnodes": "Bootnodes", + "bootnodes-enabled": "Bootnodes enabled", + "bootnodes-settings": "Bootnodes settings", + "bridge": "Bridge", + "bridge-from": "Bridge from {{bridge-name}}", + "bridge-to": "Bridge {{name}} to", + "bridged-to": "Bridged to {{network}}", + "browsed-websites": "Browser history will appear here", + "browser": "Browser", + "browser-not-secure": "Connection is not secure! Do not sign transactions or send personal data on this site.", + "browser-secure": "Connection is secure. Make sure you really trust this site before signing transactions or entering personal data.", + "browsers": "Browsers", + "browsing-cancel": "Cancel", + "browsing-open-in-android-web-browser": "Open in Android", + "browsing-open-in-ios-web-browser": "Open in iOS", + "browsing-open-in-status": "Open in Status", + "browsing-site-blocked-description1": "We detected potential malicious activity from this address. To protect you and your wallet, we're preventing further navigation.\n\nIf you think this is an error, let us know in the ", + "browsing-site-blocked-description2": " public chat.", + "browsing-site-blocked-go-back": "Go back", + "browsing-site-blocked-title": "This site is blocked", + "browsing-title": "Browse", + "bug-report": "Report a bug", + "bug-report-description": "* Description", + "bug-report-description-placeholder": "Required, can't be empty", + "bug-report-steps": "Steps to reproduce", + "bug-report-steps-placeholder": "- open app\n- do something\n- and then somethig else...", + "bug-report-submit-email": "Submit by email with logs archive", + "bug-report-submit-gh-issue": "Submit a GitHub issue without logs", + "bug-report-too-short-description": "Description is too short", + "build-yourself": "To use the app without these Terms of Use, you can build your own version", + "buy": "Buy", + "buy-assets": "Buy assets", + "buy-crypto": "Buy crypto", + "buy-crypto-choose-a-service": "Choose a service you'd like to use to buy crypto", + "buy-crypto-description": "Find a dapp to buy crypto now", + "buy-crypto-leaving": "You are leaving Status and entering a third party website to complete your purchase", + "buy-crypto-title": "Looks like your wallet is empty", + "buy-eth": "Buy ETH", + "by-continuing-you-accept": "By continuing you accept our ", + "camera-access-error": "To grant the required camera permission, please go to your system settings and make sure that Status > Camera is selected.", + "camera-permission-denied": "Permission denied", + "can-not-add-yourself": "That's you, to start a chat choose someone else", + "can-send-messages": "You can send and receive new messages", + "cancel": "Cancel", + "cancel-keycard-setup": "Cancel Keycard setup", + "cancel-request-to-join": "Cancel request to join", + "cancel-request?": "Cancel request?", + "cancelling": "Cancelling", + "cannot-pin-desc": "You can only pin a max of 3 messages.\nUnpin at least one to pin a new one.", + "cannot-pin-title": "You can't pin this message!", + "cannot-read-card": "Can't read card.\nPlease hold it to the back of your phone", + "cannot-use-default-pin": "Passcode 000000 is not allowed.\nPlease use another number", + "cant-fetch-info": "Can't fetch info", + "cant-open-public-chat": "Can't open public chat", + "cant-report-bug": "Can't report a bug", + "card-is-blank": "This card is blank", + "card-reseted": "Card has been reseted", + "card-unpaired": "Card has been unpaired from current device", + "category": "Category", + "category-title": "Category title", + "change-fleet": "Change fleet to {{fleet}}", + "change-group-privacy": "Change group privacy", + "change-log-level": "Confirm and restart the app to change log level to {{log-level}}", + "change-logging-enabled": "Are you sure you want to {{enable}} logging?", + "change-pairing": "Change pairing code", + "change-pairing-description": "Changing the pairing code does not affect the current pairings. However, any new pairing will require the new code.", + "change-pairing-title": "Create a new pairing code", + "change-passcode": "Change Passcode", + "change-password": "Change password", + "change-password-confirm-description": "Your data must now be re-encrypted with your new password. Please do not quit the app or turn off your device.", + "change-password-confirm-warning": "Re-encrypting data after changing password may take up to 3 min. Closing the app or locking phone while re-encryption is in progress will lead to data corruption and loss of your Status profile.", + "change-password-description": "Change password used to log in to Status and sign transactions", + "change-password-done-description": "Please log out Status and log in using your new password", + "change-password-done-header": "Re-encryption complete!", + "change-password-loading-description": "Data is now being re-encrypted with your new password, this may take up to 3min.", + "change-password-loading-header": "Keep app open on screen!", + "change-password-loading-warning": "Do not quit the app or turn off your device while in-progress.\n \nClosing the app or locking your phone while re-encryption is taking place will lead to data corruption and the loss of your Status profile.", + "change-password-new-password-label": "New password", + "change-password-new-password-placeholder": "Enter new password", + "change-password-old-password-label": "Current password", + "change-password-old-password-placeholder": "Enter current password", + "change-password-repeat-password-placeholder": "Repeat new password", + "change-pin": "Change 6-digit passcode", + "change-puk": "Change 12-digit PUK", + "change-testnet-mode-logout-info": "You’ll be logged out of the app", + "change-tip": "Change tip", + "changed-amount-warning": "Amount was changed from {{old}} to {{new}}", + "changed-asset-warning": "Asset was changed from {{old}} to {{new}}", + "channel-muted-for-1-hour": "Channel muted for 1 hour \n(until {{duration}})", + "channel-muted-for-1-week": "Channel muted for 1 week \n(until {{duration}})", + "channel-muted-for-15-minutes": "Channel muted for 15 minutes \n(until {{duration}})", + "channel-muted-for-8-hours": "Channel muted for 8 hours \n(until {{duration}})", + "channel-muted-till-unmuted": "Channel muted till unmuted \n(until {{duration}})", + "channel-on-status": "Channel on Status", + "channel-unmuted-successfully": "Channel unmuted successfully!", + "chaos-mode": "Chaos mode", + "chaos-unicorn-day": "Chaos Unicorn Day", + "chaos-unicorn-day-details": "🦄🦄🦄🦄🦄🦄🦄🚀!", + "chat": "Chat", + "chat-and-transact": "Chat and transact privately with friends", + "chat-is-a-contact": "Contact", + "chat-is-not-a-contact": "Not a contact", + "chat-key": "Chat key", + "chat-key-content": "Messages on the Status chat protocol are sent and received using encryption keys. The public chat key is a string of characters you share with others so they can send you messages in Status.", + "chat-key-description": "Your unique public ID in Status. Others can use it to send you a contact request.", + "chat-key-title": "Chat Key", + "chat-link-previews": "Chat link previews", + "chat-muted-for-1-hour": "Chat muted for 1 hour \n(until {{duration}})", + "chat-muted-for-1-week": "Chat muted for 1 week \n(until {{duration}})", + "chat-muted-for-15-minutes": "Chat muted for 15 minutes \n(until {{duration}})", + "chat-muted-for-8-hours": "Chat muted for 8 hours \n(until {{duration}})", + "chat-muted-till-unmuted": "Chat muted till unmuted \n(until {{duration}})", + "chat-name": "Chat name", + "chat-name-content": "Three random words, derived algorithmically from your chat key and used as your default alias in chat. Chat names are completely unique; no other user can have the same three words.", + "chat-name-title": "Chat Name", + "chat-notification-preferences": "Notification settings", + "chat-settings": "Chat settings", + "chat-unmuted-successfully": "Chat unmuted successfully!", + "chat-with": "Chat with {{selected-user}}", + "chat-with-friends": "Chat privately with friends", + "chats": "Chats", + "check-before-syncing": "Check before syncing", + "check-before-syncing-doc-checkbox-1": "Connect both devices to the same network", + "check-before-syncing-doc-checkbox-2": "Make sure you are logged in on the other device", + "check-before-syncing-doc-checkbox-3": "Disable the firewall and VPN on your devices", + "check-before-syncing-doc-description": "To sync your devices successfully, make sure to check and complete these steps:", + "check-on-block-explorer": "Check on block explorer", + "check-on-opensea": "Check on opensea", + "check-your-account-balance-and-activity": "Check your account balance and activity", + "check-your-recovery-phrase": "Check your seed phrase", + "choose-actions": "Choose actions", + "choose-authentication-method": "Choose an authentication method", + "choose-new-location-for-keystore": "Choose a new location to save your keystore file", + "choose-storage": "Choose storage", + "clear": "Clear", + "clear-all": "Clear all", + "clear-history": "Clear history", + "clear-history-action": "Clear", + "clear-history-confirmation": "Clear history?", + "clear-history-confirmation-content": "Are you sure you want to clear this chat history?", + "clear-history-title": "Clear history?", + "clear-history?": "Clear History?", + "cleared-chat-description-public": "It's been quiet here. Start the conversation or ", + "close": "Close", + "close-all": "Close all", + "close-app-button": "Confirm", + "close-app-content": "The app will stop and close. When you reopen it, the selected network will be used", + "close-app-title": "Warning!", + "close-chat": "Close chat", + "close-chat-confirmation": "This conversation will disappear from the list.\nIt will appear again if the contact sends a new message.", + "close-contact-search": "Close contact search", + "code-snippet": "Code snippet", + "collectibles": "Collectibles", + "collectibles-leak-metadata": "You can display your NFTs here. If you do, you will share your wallet and IP address", + "collecting-usage-data": "Collecting usage data helps us improve Status.", + "colour": "Colour", + "command-button-send": "Send", + "common-names": "Common names", + "communities": "Communities", + "communities-alpha": "Communities (alpha)", + "communities-enabled": "Communities enabled", + "communities-verified": "✓ Verified Status Community", + "community": "Community", + "community-channel": "Community channel", + "community-channel-read-requirements-met": "View only requirements met", + "community-channel-read-requirements-not-met": "View only requirements not met", + "community-channel-write-requirements-met": "View and post requirements met", + "community-channel-write-requirements-not-met": "View and post requirements not met", + "community-color": "Community colour", + "community-color-placeholder": "Pick a colour", + "community-edit-title": "Edit community", + "community-emoji-thumbnail-title": "Thumbnail", + "community-enter-channel-info": "Entering channel will reveal your public addresses to the node owner", + "community-image-delete": "", + "community-image-pick": "Pick an image", + "community-image-remove": "Remove", + "community-image-take": "Take a photo", + "community-info": "Community info", + "community-info-not-found": "Community information not found", + "community-invite-title": "Invite", + "community-join-requirements-changed": "Token requirements have changed", + "community-join-requirements-met": "Join requirements met", + "community-join-requirements-not-met": "Join requirements not met", + "community-join-requirements-tokens-lost": "You no longer have the tokens required", + "community-key": "Community private key", + "community-key-placeholder": "Type your community private key", + "community-kicked-body": "You were kicked from", + "community-kicked-heading": "Kicked from community", + "community-link": "Community link", + "community-members": { + "one": "{{count}} member", + "other": "{{count}} members" + }, + "community-members-title": "Members", + "community-message-preview": "Invitation to join {{community-name}}", + "community-on-status": "Community on Status", + "community-overview": "Community Overview", + "community-private-key": "Community private key", + "community-request-accepted": "Request accepted", + "community-request-accepted-body-text": "Now you are a member of", + "community-request-not-accepted": "Request hasn't been accepted", + "community-request-not-accepted-body-text-prefix": "Your request to join", + "community-request-not-accepted-body-text-suffix": "hasn't been accepted", + "community-request-pending": "Request pending", + "community-request-pending-body-text": "You requested to join", + "community-requests-to-join-title": "Membership requests", + "community-roles": "Roles", + "community-rules": "Community rules", + "community-share-title": "Share", + "community-thumbnail-image": "Thumbnail image", + "community-thumbnail-upload": "Upload", + "community-unmuted": "Community unmuted", + "complete-hardwallet-setup": "This card is now linked. You need it to sign transactions and unlock your keys", + "completed": "Completed", + "confirm": "Confirm", + "confirm-account-origin": "Confirm account origin", + "confirm-and-leave": "Confirm and leave", + "confirm-backup": "Confirm backup", + "confirm-changes": "Confirm changes", + "confirm-new-password": "Confirm new password", + "confirm-pairing-code-placeholder": "Confirm your pairing code...", + "confirm-password-placeholder": "Confirm your password...", + "confirm-selection": "Confirm selection", + "confirm-the-position": "Confirm the position of certain words in your recovery phrase", + "confirmation-request": "Confirmation request", + "confirmations": "Confirmations", + "confirmations-helper-text": "When the transaction has 12 confirmations you can consider it settled.", + "confirmed-on": "Confirmed on", + "connect": "Connect", + "connect-mailserver-content": "Connect to {{name}}?", + "connect-with-users": "Connect with users", + "connected": "Connected", + "connected-dapps": "Connected dApps", + "connected-to": "Connected to", + "connecting": "Connecting...", + "connecting-requires-login": "Connecting to another network requires login", + "connection-request": "Connection Request", + "connection-status": "Connection status", + "connection-with-the-card-lost": "Connection with the card\n has been lost", + "connection-with-the-card-lost-setup-text": "To resume the setup hold the card to\n the back of your phone and maintain\n card to phone contact", + "connection-with-the-card-lost-text": "To proceed hold the card to the back of your phone", + "contact-code": "Chat key", + "contact-profile-request-pending": "Contact request pending", + "contact-request": "Contact request", + "contact-request-accepted": "Accepted ✓", + "contact-request-accepted-toast": "{{name}} accepted your contact request", + "contact-request-chat-add": "Add {{name}} as contact to send a message", + "contact-request-chat-pending": "Your contact request is pending", + "contact-request-chat-received": "{{name}} sent you a contact request", + "contact-request-declined": "Declined ⓧ", + "contact-request-header": "👋 Contact requests", + "contact-request-is-now-a-contact": "is now a contact", + "contact-request-message-prompt": "Why should they accept your request?", + "contact-request-outgoing": "You’re trying to connect with", + "contact-request-pending": "Pending...", + "contact-request-removed-as-contact": "removed as a contact", + "contact-request-removed-you-as-contact": "removed you as a contact", + "contact-request-review": "Review contact request", + "contact-request-sent": "sent contact request", + "contact-request-sent-to": "Contact request sent to", + "contact-request-sent-toast": "{{name}} sent you a contact request", + "contact-request-was-accepted": "Contact request accepted", + "contact-request-was-ignored": "Contact request ignored", + "contact-request-was-sent": "Contact request sent", + "contact-requests": "Contact requests", + "contact-s": { + "one": "contact", + "other": "contacts" + }, + "contacts": "Contacts", + "contacts-descr": "Your contacts will appear here. You will receive status updates from anyone you add as a contact", + "contacts-empty": "Contacts with ENS names will appear here", + "continue": "Continue", + "continue-anyway": "Continue anyway", + "contract-address": "Contract address", + "contract-interaction": "Contract interaction", + "contract-isnt-supported": "Contract is not supported", + "copy-address": "Copy address", + "copy-all-details": "Copy all details", + "copy-info": "Copy info", + "copy-qr": "Copy code", + "copy-text": "Copy text", + "copy-to-clipboard": "Copy", + "copy-transaction-hash": "Copy transaction ID", + "correct-private-key": "Correct private key", + "cost-fee": "Cost/Fee", + "counter-9-plus": "9+", + "counter-99-plus": "99+", + "create": "Create", + "create-a-pin": "Create a 6-digit passcode", + "create-a-puk": "Create a 12-digit PUK", + "create-account": "Create account", + "create-category": "Create category", + "create-channel": "Create a channel", + "create-channel-title": "New channel", + "create-community": "Create a community", + "create-group-chat": "Create group chat", + "create-multiaccount": "Generate keys", + "create-new-key": "Get new keys", + "create-new-profile": "Create new profile", + "create-pin": "Create 6-digit passcode", + "create-pin-description": "You'll need your card + this 6-digit passcode to unlock Status and to confirm transactions", + "create-profile": "Create profile", + "create-profile-password-info-box-description": "Your Status keys are the foundation of your self-sovereign identity in Web3. You have complete control over these keys, which you can use to sign transactions, access your data, and interact with Web3 services.\n\nYour keys are always securely stored on your device and protected by your Status profile password. Status doesn't know your password and can't reset it for you. If you forget your password, you may lose access to your Status profile and wallet funds.\n\nRemember your Status password and don't share it with anyone.", + "create-profile-password-info-box-title": "About your profile password", + "created-group-chat-description": "You created the group {{group-name}}", + "cryptokitty-name": "CryptoKitty #{{id}}", + "currency": "Currency", + "currency-display-name-aed": "Emirati Dirham", + "currency-display-name-afn": "Afghanistan Afghani", + "currency-display-name-ars": "Argentine Peso", + "currency-display-name-aud": "Australian Dollar", + "currency-display-name-bbd": "Barbados Dollar", + "currency-display-name-bdt": "Bangladeshi Taka", + "currency-display-name-bgn": "Bulgarian Lev", + "currency-display-name-bhd": "Bahraini Dinar", + "currency-display-name-bnd": "Brunei Darussalam Dollar", + "currency-display-name-bob": "Bolivia Bolíviano", + "currency-display-name-brl": "Brazil Real", + "currency-display-name-btn": "Bhutanese Ngultrum", + "currency-display-name-cad": "Canada Dollar", + "currency-display-name-chf": "Switzerland Franc", + "currency-display-name-clp": "Chile Peso", + "currency-display-name-cny": "China Yuan Renminbi", + "currency-display-name-cop": "Colombia Peso", + "currency-display-name-crc": "Costa Rica Colon", + "currency-display-name-czk": "Czech Koruna", + "currency-display-name-dkk": "Denmark Krone", + "currency-display-name-dop": "Dominican Republic Peso", + "currency-display-name-egp": "Egypt Pound", + "currency-display-name-etb": "Ethiopian Birr", + "currency-display-name-eur": "Euro", + "currency-display-name-gbp": "British Pound", + "currency-display-name-gel": "Georgian Lari", + "currency-display-name-ghs": "Ghana Cedi", + "currency-display-name-hkd": "Hong Kong Dollar", + "currency-display-name-hrk": "Croatia Kuna", + "currency-display-name-huf": "Hungary Forint", + "currency-display-name-idr": "Indonesia Rupiah", + "currency-display-name-ils": "Israel Shekel", + "currency-display-name-inr": "India Rupee", + "currency-display-name-isk": "Iceland Krona", + "currency-display-name-jmd": "Jamaica Dollar", + "currency-display-name-jpy": "Japanese Yen", + "currency-display-name-kes": "Kenyan Shilling", + "currency-display-name-krw": "Korea (South) Won", + "currency-display-name-kwd": "Kuwaiti Dinar", + "currency-display-name-kzt": "Kazakhstan Tenge", + "currency-display-name-lkr": "Sri Lanka Rupee", + "currency-display-name-mad": "Moroccan Dirham", + "currency-display-name-mdl": "Moldovan Leu", + "currency-display-name-mur": "Mauritius Rupee", + "currency-display-name-mwk": "Malawian Kwacha", + "currency-display-name-mxn": "Mexico Peso", + "currency-display-name-myr": "Malaysia Ringgit", + "currency-display-name-mzn": "Mozambique Metical", + "currency-display-name-nad": "Namibia Dollar", + "currency-display-name-ngn": "Nigeria Naira", + "currency-display-name-nok": "Norway Krone", + "currency-display-name-npr": "Nepal Rupee", + "currency-display-name-nzd": "New Zealand Dollar", + "currency-display-name-omr": "Oman Rial", + "currency-display-name-pen": "Peru Sol", + "currency-display-name-pgk": "Papua New Guinean Kina", + "currency-display-name-php": "Philippines Peso", + "currency-display-name-pkr": "Pakistan Rupee", + "currency-display-name-pln": "Polish Zloty", + "currency-display-name-pyg": "Paraguay Guarani", + "currency-display-name-qar": "Qatar Riyal", + "currency-display-name-ron": "Romania Leu", + "currency-display-name-rsd": "Serbia Dinar", + "currency-display-name-rub": "Russia Ruble", + "currency-display-name-sar": "Saudi Arabia Riyal", + "currency-display-name-sek": "Sweden Krona", + "currency-display-name-sgd": "Singapore Dollar", + "currency-display-name-thb": "Thailand Baht", + "currency-display-name-try": "Turkish Lira", + "currency-display-name-ttd": "Trinidad and Tobago Dollar", + "currency-display-name-twd": "Taiwan New Dollar", + "currency-display-name-tzs": "Tanzanian Shilling", + "currency-display-name-uah": "Ukraine Hryvnia", + "currency-display-name-ugx": "Ugandan Shilling", + "currency-display-name-usd": "United States Dollar", + "currency-display-name-uyu": "Uruguay Peso", + "currency-display-name-vef": "Venezuela Bolívar", + "currency-display-name-vnd": "Vietnam Dong", + "currency-display-name-zar": "South Africa Rand", + "current": "Current", + "current-average": "Current average", + "current-average-tip": "Current average tip", + "current-base": "Current base", + "current-base-fee": "Current base fee", + "current-minimum-tip": "Current minimum tip", + "current-network": "Current network", + "current-password": "Current password", + "current-pin": "Enter 6-digit passcode", + "current-pin-description": "Enter your 6-digit passcode to proceed", + "custom": "Custom", + "custom-node": "You are using custom RPC endpoint. Your local transfers history might be incomplete.", + "custom-seed-phrase": "Invalid seed phrase", + "custom-seed-phrase-text-1": "This seed phrase doesn't match our supported dictionary. Check for misspelled words.", + "cut": "Cut", + "dapp": "ÐApp", + "dapp-starter-pack-accept": "Accept and Open", + "dapp-starter-pack-description": "Here’s some crypto to get you started! Use it to get stickers, an ENS name and try dapps", + "dapp-starter-pack-title": "Starter Pack", + "dapp-will-be-able-to": "{{dapp-name}} will be able to:", + "dapp-would-like-to-connect-wallet": "would like to connect to", + "dapps": "dApps", + "dapps-permissions": "DApp permissions", + "dark": "Dark", + "data": "Data", + "data-collected": "Data collected", + "data-collected-subtitle": "The table below shows the exact data that is stored and will be sent. Data is validated against public rules to ensure no sensitive data is sent. Don’t trust, verify.", + "data-syncing": "Data syncing", + "data-usage": "Data usage", + "database-reset-content": "Chats, contacts and settings have been deleted. You can use your account with your Keycard", + "database-reset-title": "Database reset", + "database-reset-warning": "Database will be reset. Chats, contacts and settings will be deleted", + "datetime-ago": "ago", + "datetime-ago-format": "{{number}} {{time-intervals}} {{ago}}", + "datetime-ago-format-short": "{{number}}{{time-intervals}}", + "datetime-day": { + "one": "day", + "other": "days" + }, + "datetime-day-short": { + "one": "D", + "other": "D" + }, + "datetime-hour": { + "one": "hour", + "other": "hours" + }, + "datetime-hour-short": { + "one": "H", + "other": "H" + }, + "datetime-minute": { + "one": "minute", + "other": "minutes" + }, + "datetime-minute-short": { + "one": "M", + "other": "M" + }, + "datetime-second": { + "one": "second", + "other": "seconds" + }, + "datetime-second-short": { + "one": "S", + "other": "S" + }, + "datetime-today": "today", + "datetime-yesterday": "Yesterday", + "days": "Days", + "dec": "Dec", + "decimals": "Decimals", + "decline": "Decline", + "declined": "Declined", + "decryption-failed-content": "An error occured decrypting your data. You might need to erase your old data and generate a new account. Tap “Apply” to erase or “Cancel” to try again", + "default": "Default", + "default-account-placeholder": "Account Name", + "default-assets": "Default ERC20 and ERC721", + "default-ethereum-format": "Default Ethereum format", + "default-format": "Default format", + "default-sync-period": "Sync history for", + "default-watched-address-placeholder": "Watched address", + "define-amount-sent-from-network": "Define amount sent from {{network}} network", + "delete": "Delete", + "delete-account": "Delete account", + "delete-and-leave-group": "Delete and leave group", + "delete-bootnode": "Delete bootnode", + "delete-bootnode-are-you-sure": "Are you sure you want to delete this bootnode?", + "delete-bootnode-title": "Delete bootnode", + "delete-category-confirmation": "Are you sure you want to delete this category?", + "delete-confirmation": "Delete?", + "delete-for-everyone": "Delete for everyone", + "delete-for-me": "Delete for me", + "delete-keys-keycard": "Delete keys from Keycard", + "delete-mailserver": "Delete Status node", + "delete-mailserver-are-you-sure": "Are you sure you want to delete this Status node?", + "delete-mailserver-title": "Delete Status node", + "delete-message": "Delete message", + "delete-my-account": "Delete my account", + "delete-my-profile": "Delete my profile", + "delete-network-confirmation": "Are you sure you want to delete this network?", + "delete-network-error": "Please connect to a different network before deleting this one", + "delete-network-title": "Delete network?", + "delete-node": "Delete node", + "delete-node-are-you-sure": "Are you sure you want to delete this node?", + "delete-node-title": "Delete node", + "delete-profile": "Delete profile", + "delete-profile-warning": "Warning: If you don’t have your seed phrase written down, you will lose access to your funds after you delete your profile", + "deleted-this-message": "deleted this message", + "delivered": "Delivered", + "deny": "Deny", + "derivation-path": "Derivation path", + "derivation-path-copied": "Derivation path copied", + "derivation-path-desc": "Derivation paths are the routes your Status Wallet uses to generate addresses from your private key.", + "derivation-path-header": "Derivation path", + "derive-addresses": "Derive addresses", + "describe-channel": "Describe the channel", + "description": "Description", + "desktop": "Desktop", + "destroy": "Destroy", + "details": "Details", + "dev-mode": "Development mode", + "dev-mode-settings": "Development mode settings", + "device-syncing": "Device syncing", + "devices": "Devices", + "disable": "disable", + "disable-all": "Disable all", + "disable-later-in-settings": "You can disable this later in Settings", + "disabled": "Disabled", + "discard": "Discard", + "discard-changes?": "Discard changes?", + "disconnect": "Disconnect", + "disconnect-dapp": "Disconnect dApp", + "disconnect-dapp-confirmation": "Are you sure you want to disconnect {{dapp}}?", + "disconnect-dapp-fail": "Failed to disconnect {{dapp}} from {{account}}", + "disconnect-dapp-success": "{{dapp}} disconnected from {{account}}", + "disconnected": "Chat offline", + "discover": "Discover", + "discover-communities": "Discover communities", + "discover-web3": "Discover web3", + "dismiss": "Dismiss", + "display": "Display", + "display-collectibles": "Display collectibles", + "do-not-cheat": "Don't try to cheat", + "do-not-cheat-description": "These 12 words give access to all of your funds so it is important that you write them in the correct order, take it seriously.", + "do-not-share": "Do not share", + "done": "Done", + "dont-ask": "Don't ask me again", + "dont-auto-recalculate-network": "Don't auto recalculate {{network}}", + "dont-have-statatus-on-another-device": "Don't have Status on another device?", + "dont-yell-at-me": "Don’t yell at me", + "duration-estimate": "Duration estimate", + "edit": "Edit", + "edit-account": "Edit account", + "edit-categories": "Edit Categories", + "edit-channel-title": "Edit channel", + "edit-chats": "Edit chats", + "edit-community": "Edit community", + "edit-derivation-path": "Edit derivation path", + "edit-details": "Edit details", + "edit-favourite": "Edit favourite", + "edit-group": "Edit group", + "edit-message": "Edit message", + "edit-name-and-image": "Edit name and image", + "edit-name-image": "Edit name and image", + "edit-nickname": "Edit nickname", + "edit-profile": "Edit Profile", + "edit-receiver-networks": "Edit receiver networks", + "edit-shared-addresses": "Edit shared addresses", + "edit-wallet-account-colour-updated-message": "Account colour has been updated", + "edit-wallet-account-emoji-updated-message": "Account emoji has been updated", + "edit-wallet-account-name-updated-message": "Account name has been updated", + "edit-wallet-network-preferences-updated-message": "Account network preferences has been updated", + "edited": "Edited", + "editing-message": "Editing message", + "eligible-to-join-as": "Eligible to join as", + "emoji-activity": "Activity", + "emoji-and-colors-unique-error": "Emoji and colour combination must be unique", + "emoji-flags": "Flags", + "emoji-food": "Food", + "emoji-hash": "Emoji Hash", + "emoji-hash-copied": "Emojihash copied to clipboard", + "emoji-nature": "Nature", + "emoji-no-results-description": "Try something like “rainbow”", + "emoji-no-results-title": "No emojis match your search", + "emoji-objects": "Objects", + "emoji-people": "People", + "emoji-recent": "Recent", + "emoji-search-placeholder": "Search emojis", + "emoji-symbols": "Symbols", + "emoji-travel": "Travel", + "emojihash": "Emojihash", + "emojihash-description": "A visual representation of your chat key. It will help other users recognize your profile.", + "emojis": "Emojis", + "empty-activity-center": "Your chat notifications\nwill appear here", + "empty-chat-description": "There are no messages \nin this chat yet", + "empty-chat-description-community": "It's been quiet here for the last {{quiet-hours}}.", + "empty-chat-description-one-to-one": "Any messages you send here are encrypted and can only be read by you and ", + "empty-chat-description-public": "It's been quiet here for the last {{quiet-hours}}. Start the conversation or ", + "empty-chat-description-public-share-this": "share this chat.", + "empty-keycard": "Empty Keycard", + "empty-keycard-required": "Requires an empty Keycard", + "empty-notifications-admin-tab": "No unread admin notifications", + "empty-notifications-all-tab": "No unread notifications", + "empty-notifications-contact-requests-tab": "No contact requests notifications", + "empty-notifications-identity-verification-tab": "No identity verification requests", + "empty-notifications-membership-tab": "No membership notifications", + "empty-notifications-mentions-tab": "No unread mention notifications", + "empty-notifications-replies-tab": "No unread reply notifications to your messages", + "empty-notifications-subtitle-read": "Your notifications will be here", + "empty-notifications-subtitle-unread": "Unread notifications will be here", + "empty-notifications-system-tab": "No system notifications", + "empty-notifications-title-read": "No notifications", + "empty-notifications-title-unread": "You're up to date", + "empty-notifications-transactions-tab": "No transaction notifications", + "empty-pending-invitations-descr": "People who wish to join the group\nvia an invite link will appear here", + "empty-tab": "Empty tab", + "empty-tab-description": "C'mon do something...", + "enable": "Enable", + "enable-access-to-camera": "Enable access to camera", + "enable-access-to-local-network": "Enable access to local network", + "enable-all": "Enable all", + "enable-biometrics": "Enable biometrics", + "enable-camera": "Enable camera", + "enable-link-previews": "Enable link previews in chat?", + "enable-network-access": "Enable network access", + "enable-notifications-sub-title": "Receive notifications about your new messages or wallet transactions", + "encrypt-with-password": "Encrypt with password", + "encrypted-key-pairs": "Encrypted key pairs", + "encrypted-key-pairs-code": "Encrypted key pairs code", + "ending-not-allowed": "{{ending}} ending is not allowed", + "ends-with-space": "Cannot end with space", + "ens-10-SNT": "10 SNT", + "ens-add-username": "Add username", + "ens-agree-to": "Agree to ", + "ens-chat-settings": "Chat settings", + "ens-custom-domain": "Custom domain", + "ens-custom-username-hints": "Type the entire username including the custom domain like username.domain.eth", + "ens-custom-username-taken": "Username doesn’t belong to you :(", + "ens-deposit": "Deposit", + "ens-dismiss-message": "Click here to dismiss", + "ens-displayed-with": "Your messages are displayed to others with", + "ens-get-name": "Get a universal username", + "ens-got-it": "Ok, got it", + "ens-locked": "Username locked. You won’t be able to release it until {{date}}", + "ens-name-content": "Custom alias for your chat key that you can register using the Ethereum Name Service. ENS names are decentralized usernames.", + "ens-name-not-found": "Cannot resolve ENS name", + "ens-name-title": "ENS Name", + "ens-network-restriction": "Only available on Mainnet", + "ens-no-usernames": "You don't have any username connected", + "ens-or-chat-key": "ENS or Chatkey", + "ens-powered-by": "Powered by Ethereum Name Services", + "ens-primary-username": "Primary username", + "ens-register": "Register", + "ens-registration-failed": "To register the username, please try again.", + "ens-registration-failed-title": "Transaction failed", + "ens-registration-failure": "Registration failed", + "ens-registration-in-progress": "Registration in progress...", + "ens-release-username": "Release username", + "ens-remove-hints": "Removing will detach the username from your key.", + "ens-remove-username": "Remove username", + "ens-saved": " is now connected with your chat key and can be used in Status.", + "ens-saved-title": "Username added", + "ens-show-username": "Show my ENS username in chats", + "ens-terms-header": "Terms of name registration", + "ens-terms-point-1": "Funds are deposited for 1 year. Your SNT will be locked, but not spent.", + "ens-terms-point-10": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e (ENS Registry).", + "ens-terms-point-2": "After 1 year, you can release the name and get your deposit back, or take no action to keep the name.", + "ens-terms-point-3": "If terms of the contract change — e.g. Status makes contract upgrades — user has the right to release the username regardless of time held.", + "ens-terms-point-4": "The contract controller cannot access your deposited funds. They can only be moved back to the address that sent them.", + "ens-terms-point-5": "Your address(es) will be publicly associated with your ENS name.", + "ens-terms-point-6": "Usernames are created as subdomain nodes of stateofus.eth and are subject to the ENS smart contract terms.", + "ens-terms-point-7": "You authorize the contract to transfer SNT on your behalf. This can only occur when you approve a transaction to authorize the transfer.", + "ens-terms-point-8": "These terms are guaranteed by the smart contract logic at addresses:", + "ens-terms-point-9": "{{address}} (Status UsernameRegistrar) ", + "ens-terms-registration": "Terms of name registration.", + "ens-test-message": "Hey", + "ens-transaction-pending": "Transaction pending...", + "ens-understand": "I understand that my wallet address will be publicly connected to my username.", + "ens-username": "ENS username", + "ens-username-already-added": "Username is already connected with your chat key and can be used inside Status.", + "ens-username-available": "✓ Username available!", + "ens-username-connected": "This user name is owned by you and connected with your chat key.", + "ens-username-connected-continue": "Continue to set `Show my ENS username in chats`.", + "ens-username-connected-with-different-key": "Continuing will require a transaction to connect the username with your current chat key.", + "ens-username-connection-confirmation": "{{username}} will be connected once the transaction is complete.", + "ens-username-hints": "At least 4 characters. Latin letters, numbers, and lowercase only.", + "ens-username-invalid": "Letters and numbers only.", + "ens-username-invalid-name-warning": "Registration process of one of your ens names has finished in invalid state. DO NOT USE the name for wallet transactions and reach out to our support at support@status.im", + "ens-username-owned": "✓ Username is owned by you. ", + "ens-username-owned-continue": "Continuing will connect this username with your chat key.", + "ens-username-registration-confirmation": "Nice! You own {{username}} once the transaction is complete.", + "ens-username-registration-invalid": "Warning! Registration process has finished in invalid state. DO NOT USE the name for wallet transactions and reach out to our support at support@status.im", + "ens-username-taken": "Username already taken :(", + "ens-username-you-can-follow-progress": "You can follow the progress in the Transaction History section of your wallet.", + "ens-usernames": "ENS usernames", + "ens-usernames-details": "Register a universal username to be easily recognized by other users", + "ens-want-custom-domain": "I own a name on another domain", + "ens-want-domain": "I want a stateofus.eth domain", + "ens-welcome-hints": "ENS names transform those crazy-long addresses into unique usernames.", + "ens-welcome-point-customize": "An ENS name can replace your random 3-word name in chat. Be @yourname instead of {{name}}.", + "ens-welcome-point-customize-title": "Customize your chat name", + "ens-welcome-point-receive": "Others can send you funds via chat in one simple step.", + "ens-welcome-point-receive-title": "Receive transactions in chat", + "ens-welcome-point-register": "Register once to keep the name forever. After 1 year you can release the name and get your SNT back.", + "ens-welcome-point-register-title": "10 SNT to register", + "ens-welcome-point-simplify": "You can receive funds to your easy-to-share ENS name rather than your hexadecimal hash (0x...).", + "ens-welcome-point-simplify-title": "Simplify your ETH address", + "ens-welcome-point-verify": "You can verify and add any usernames you own in the next steps.", + "ens-welcome-point-verify-title": "Already own a username?", + "ens-your-username": "Your username", + "ens-your-usernames": "Your usernames", + "ens-your-your-name": "Your ENS name", + "ensure-both-devices-are-on-the-same-network": "Ensure both devices are on the same network", + "ensure-qr-code-is-in-focus-to-scan": "Ensure that the QR code is in focus to scan", + "enter-12-words": "Enter the 12 words of your seed phrase, separated by single spaces", + "enter-a-private-key": "Enter a private key", + "enter-a-seed-phrase": "Enter a seed phrase", + "enter-address": "Enter address", + "enter-channel": "Enter channel", + "enter-chat-key": "Enter chat key or scan a QR", + "enter-contact-code": "ENS (vitalik94) or chat key (0x04…)", + "enter-eth": "Enter any ETH address or ENS name.", + "enter-pair-code": "Enter your pairing code", + "enter-pair-code-description": "Pairing code can be set from an already paired Status client", + "enter-password": "Enter password", + "enter-password-migration-prompt": "Enter your password to move contacts, chats and settings along with your keys", + "enter-pin": "Enter 6-digit passcode", + "enter-private-key": "Enter the private key of an address", + "enter-private-key-placeholder": "Enter your private key", + "enter-puk-code": "Enter PUK code", + "enter-puk-code-description": "6-digit passcode has been blocked.\n Please enter PUK code to unblock passcode.", + "enter-recipient-address-or-username": "Enter address or username of the recipient", + "enter-recovery-phrase": "Enter recovery phrase", + "enter-seed-phrase": "Enter seed phrase", + "enter-sync-code": "Enter sync code", + "enter-url": "Enter URL", + "enter-user-pk": "Enter user public key", + "enter-watch-account-address": "Scan a QR code\nor\nenter the address to watch", + "enter-word": "Enter word", + "enter-your-code": "Enter your 6-digit passcode", + "enter-your-password": "Enter your password", + "epoch-number": "Epoch {{number}}", + "error": "Error", + "error-loading-audio": "Error while loading audio", + "error-syncing-connection-failed": "Oops! Connection failed. Try again", + "error-this-is-not-a-sync-qr-code": "Oops! This is not a sync QR code", + "error-unable-to-get-balance": "Unable to get balance", + "error-unable-to-get-prices": "Currency conversion error. Refresh your screen to try again.", + "error-unable-to-get-token-balance": "Unable to get token balance", + "errors": "Errors", + "est-time": "Est. time", + "eth": "ETH", + "eth-or-ens": "ETH address or ENS name.", + "ethereum-address": "Ethereum address", + "ethereum-node-started-incorrectly-description": "Ethereum node was started with incorrect configuration, application will be stopped to recover from that condition. Configured network id = {{network-id}}, actual = {{fetched-network-id}}", + "ethereum-node-started-incorrectly-title": "Ethereum node started incorrectly", + "etherscan-lookup": "Look up on Etherscan", + "everyone": "Everyone", + "everyone-mention": "everyone", + "existing-saved-address": "Existing saved address", + "expand-all": "Expand all", + "experienced-web3": "Experienced in Web3?", + "explore-the-decentralized-web": "Explore and interact with the decentralized web", + "export-account": "Export account", + "export-key": "Export private key", + "external-link": "External link", + "external-storage-denied": "Access to external storage is denied", + "failed": "Failed", + "failed-on": "Failed on", + "failed-to-fetch-community": "Failed to fetch community", + "faq": "Frequently asked questions", + "fast": "Fast", + "favorite-communities": "Your favourite communities", + "favourite": "Favourite", + "favourite-description": "Your favourite websites will appear here", + "favourites": "Favourites", + "favourites-empty": "Addresses added to favourites will appear here", + "featured": "Featured", + "feb": "Feb", + "fee-cap": "Fee cap", + "fee-explanation": "Maximum overall price for the transaction. If the block base fee exceeds this, it will be included in a following block with a lower base fee.", + "fee-options": "Suggested fee options", + "fees": "Fees", + "fetch-community": "Fetch community", + "fetch-messages": "Fetch messages", + "fetch-timeline": "↓ Fetch", + "fetching-community": "Fetching community...", + "finalized-on": "Finalized on", + "find": "Find", + "find-it-in-setting": "Find it in Settings on your other synced device", + "find-sync-code": "Find sync code", + "find-your-friends": "Find your friends with their ENS or Chatkey", + "finish": "Finish", + "finishing-card-setup": "Finishing card setup", + "fleet": "Fleet", + "fleet-settings": "Fleet settings", + "follow": "Follow", + "follow-your-interests": "Jump into a public chat and meet new people", + "for-airdrops": "For airdrops", + "forgot-password": "Forgot password?", + "forgot-your-password-info-create-new-password": "Create a new password", + "forgot-your-password-info-create-new-password-description": "Enter a new password and you're all set! You will be able to use your new password.", + "forgot-your-password-info-description": "To recover your password follow these steps:", + "forgot-your-password-info-reinstall-app": "Reinstall the Status app", + "forgot-your-password-info-reinstall-app-description": "Re-download the app from your store.", + "forgot-your-password-info-remove-app": "Remove the Status app", + "forgot-your-password-info-remove-app-description": "This will erase all your data from the device, including your password.", + "forgot-your-password-info-signup-with-key": "with your existing keys", + "forgot-your-password-info-signup-with-key-description": "Access with your seed phrase or with your Keycard.", + "forgot-your-password-info-title": "Forgot your password?", + "fr": "Fr", + "free": "↓ Free", + "fri": "Fri", + "from": "from", + "from-capitalized": "From", + "from-label": "From", + "gas-amount-limit": "Gas amount limit", + "gas-limit": "Gas limit", + "gas-price": "Gas price", + "gas-used": "Gas used", + "gated": "Gated", + "generate-a-key": "Generate keys", + "generate-a-new-account": "Generate an account", + "generate-a-new-key": "Generate a new key", + "generate-account": "Generate keys", + "generate-an-account": "Generate an account", + "generate-keys": "Generate keys", + "generate-keys-subtitle": "Create your new self-sovereign identity", + "generate-new-key": "Generate keys", + "generate-new-keypair": "Generate new key pair", + "generate-scan-sync-code": "Generate Scan Sync Code", + "generating-codes-for-pairing": "> Downloading product software to card\n > Generating unlocking & pairing codes", + "generating-keypair": "Generating key pair...", + "generating-keys": "Generating keys...", + "generating-mnemonic": "Generating seed phrase", + "get-a-keycard": "Get a Keycard", + "get-started": "Get started", + "get-status-at": "Get Status at http://status.im", + "get-stickers": "Get Stickers", + "getting-started-description": "Status is a unique messaging app that combines decentralized technology, privacy-focused design, and Web3 capabilities to offer a unique and secure communication experience.\n\nYour Status keys are the foundation of your self-sovereign identity in Web3. They serve as a secure means of accessing and managing your personal information and identity. With Status, you own, control and manage your data and digital identity without relying on centralized organisations.\n\nChoose one of these options to create your Status profile:", + "getting-started-generate-keys-description": "Create a pair of cryptographic keys representing your Status identity. Unlike centralized apps, Status doesn't rely on your username or email address to identify your profile. Instead, it uses your private and public keys. This option creates a new Ethereum address.", + "getting-started-generate-keys-from-recovery-phrase": "Generate keys from recovery phrase", + "getting-started-generate-keys-from-recovery-phrase-description": "You can use your existing Ethereum address to create your Status profile. If you already use a non-custodial Ethereum wallet (like Metamask or Trust Wallet), you already have an Ethereum address and a recovery phrase.\n\nYour keys are always securely stored on your device and protected by your Status password.", + "getting-started-generate-keys-on-keycard": "Generate keys on a Keycard", + "getting-started-generate-keys-on-keycard-description": "A Keycard is a physical card (similar to a credit card) that securely stores your Status keys. Using a Keycard adds an extra layer of security to your digital assets and identity. This option creates a new Ethereum address.", + "getting-started-with-status": "Getting started with Status", + "gif": "GIF", + "give-a-short-description": "Give a short description", + "give-a-short-description-community": "Give it a short description", + "give-permissions-camera": "Give permission\nto access camera", + "glossary": "Glossary", + "go-to": "Go to", + "go-to-settings": "Go to Settings...", + "goerli-testnet-toggle-confirmation": "Are you sure you want to toggle Goerli? This will log you out and you will have to login again.", + "got-it": "Got it", + "grant-face-id-permissions": "To grant the required Face ID permission, please go to your system settings and make sure that Status > Face ID is selected", + "grant-fingerprints-permissions": "To grant the required fingerprints permission, please go to your system settings and make sure that Status > Fingerprints is selected", + "group-chat": "Group chat", + "group-chat-admin": "Admin", + "group-chat-admin-added": "**{{member}}** has been made admin", + "group-chat-all-contacts-invited": "All your contacts are already in the group", + "group-chat-created": "**{{member}}** created the group **{{name}}**", + "group-chat-decline-invitation": "Decline invitation", + "group-chat-member-added": "**{{member}}** has been invited", + "group-chat-member-joined": "**{{member}}** has joined the group", + "group-chat-member-removed": "**{{member}}** left the group", + "group-chat-members-count": "{{selected}}/{{max}} members", + "group-chat-name-changed": "**{{member}}** changed the group's name to **{{name}}**", + "group-chat-no-contacts": "You don't have any contacts yet.\nInvite your friends to start chatting", + "group-chat-not-member": "You are not a member of this group", + "group-details": "Group details", + "group-info": "Group info", + "group-invite": "Group invite", + "group-invite-link": "Group invite link", + "group-membership-request": "Group membership request", + "groups": "Groups", + "gwei": "Gwei", + "has-permissions": "has permission to access", + "hash": "Hash", + "have-a-sync-code?": "Have a sync code?", + "help": "help", + "help-capitalized": "Help", + "help-center": "Help Center", + "help-improve-status": "Help improve Status", + "help-us-improve-status": "Help us improve Status", + "here-is-a-cat-in-a-box-instead": "Here’s a cat in a box instead", + "hide": "Hide", + "hide-content-when-switching-apps": "Block screenshots", + "hide-content-when-switching-apps-ios": "Hide preview", + "history": "History", + "history-nodes": "Status nodes", + "hit-photos-limit": "You can only add {{max-photos}} photos to your message", + "hold-card": "Hold card to the back\n of your phone", + "hold-to-post-1": "Hold", + "hold-to-post-2": "to post", + "home": "Home", + "hooks": "Hooks", + "how-it-works": "How it works", + "how-to-backup": "How to backup your recovery phrase", + "how-to-pair": "How to pair", + "how-to-scan": "How to scan", + "http-gateway-error": "Oops, request failed!", + "i-dont-have-status-on-another-device": "I don’t have Status on another device", + "i-have-written": "I have written it down on paper", + "identicon-ring": "Identicon ring", + "identicon-ring-explanation": "This multicoloured ring around your profile picture represents your chat key.", + "identifier": "Identifier", + "identity-verification": "Identity verification", + "identity-verification-request": "Identity verification", + "identity-verification-request-sent": "asks", + "if-you-cancel": "If you cancel, you can request to join this community at any point.", + "if-you-have-status-on-another-device": "If you have Status on another device", + "ignore": "Ignore", + "im-new": "I'm new", + "image": "Image", + "image-remove-current": "Remove current photo", + "image-source-gallery": "Select from gallery", + "image-source-make-photo": "Capture", + "image-source-title": "Edit picture", + "images": "images", + "import": "Import", + "import-by-entering-private-key": "Import by entering private key", + "import-by-entering-recovery-phrase": "Import by entering recovery phrase", + "import-by-scanning-encrypted-qr": "Import by scanning encrypted QR", + "import-community": "Import a community", + "import-community-title": "Import a community", + "import-from-keycard": "Import from Keycard", + "import-key-pair": "Import key pair", + "import-keypair-steps": "{{account-name}} was derived from your {{keypair-name}} key pair, which has not yet been imported to this device. To transact using this account, you will need to import the {{keypair-name}} key pair first.", + "import-keypair-to-use-account": "Import key pair to use this account", + "import-private-key": "Import private key", + "import-private-key-info": "New addresses cannot be derived from an account imported from a private key. Import using a seed phrase if you wish to derive addresses.", + "import-to-use-derived-accounts": "Import to use derived accounts", + "import-using-phrase": "Import using recovery phrase", + "in": "in", + "in-contacts": "In contacts", + "include": "Include", + "incoming": "Incoming", + "incoming-transaction": "Incoming transaction", + "incorrect-code": ["str", "Sorry the code was incorrect, please enter it again"], + "incorrect-private-key": "This is not the private key for this key pair", + "increase-gas": "Increase Gas", + "information-you-input-and-send": "Information you input and send", + "initialization": "Initialization", + "input-data": "Input data", + "install": "↓ Install", + "instruction-after-qr-generated": "On your other device, navigate to the Syncing screen and select “Scan sync”", + "insufficient-balance-to-cover-fee": "not enough balance to cover transaction fee", + "intro-message1": "Welcome to Status!\nTap this message to set your password and get started.", + "intro-privacy-policy-note1": "Status does not collect or profit from your personal data. By continuing, you agree with the ", + "intro-privacy-policy-note2": "privacy policy", + "intro-text": "Status is your gateway to the decentralized web", + "intro-text1": "Chat over a peer-to-peer, encrypted network where messages can’t be censored or hacked", + "intro-text2": "Send and receive digital assets anywhere in the world—no bank account required", + "intro-text3": "Explore games, exchanges and social networks where you alone own your data", + "intro-title1": "Truly private communication", + "intro-title2": "Secure crypto wallet", + "intro-title3": "Decentralized apps", + "intro-wizard-text1": "A set of keys controls your account. Your keys live on your phone, so only you can use them", + "intro-wizard-text2": "One key is for chat. It comes with a readable name that can’t be changed.", + "intro-wizard-text3": "If you own a Keycard, store your keys there for enhanced security.", + "intro-wizard-text4": "Secure and encrypt your keys", + "intro-wizard-text6": "Status will notify you about new messages. You can edit your notification preferences later in settings", + "intro-wizard-title-alt4": "Create a password", + "intro-wizard-title-alt5": "Confirm your password", + "intro-wizard-title1": "Get your keys", + "intro-wizard-title2": "Choose a chat name", + "intro-wizard-title3": "Choose key storage", + "intro-wizard-title4": "Create a 6-digit passcode", + "intro-wizard-title5": "Confirm the passcode", + "intro-wizard-title6": "Enable notifications", + "introduce-yourself": "Introduce yourself with a brief message", + "invalid-address": "It’s not Ethereum address or ENS name", + "invalid-address-qr-code": "Scanned QR code doesn't contain a valid address", + "invalid-characters-bio": "Invalid characters. Standard keyboard characters and emojis only.", + "invalid-ens-or-key": "Invalid ENS or Chat key", + "invalid-format": "Invalid format\nMust be {{format}}", + "invalid-key-confirm": "Apply", + "invalid-key-content": "Database can’t be encrypted because a file is corrupt. Your funds and chat key are safe. Other data, like your chats and contacts, cannot be restored. “{{erase-multiaccounts-data-button-text}}” button, will remove all other data and allows you to access your funds and send messages", + "invalid-key-pair-qr": "This does not look like a key pair QR code", + "invalid-number": "Invalid number", + "invalid-pairing-password": "Invalid pairing password", + "invalid-private-key": "It’s not a valid private key", + "invalid-public-chat-topic": "Invalid public chat topic", + "invalid-qr": "Oops! This QR doesn’t work with Status", + "invalid-range": "Invalid format, must be between {{min}} and {{max}}", + "invalid-username-or-key": "Invalid username or chat key", + "invite": "Invite", + "invite-1-user": "Invite 1 user", + "invite-button": "Invite", + "invite-chat-accept": "Accept", + "invite-chat-accept-join": "Accept and Join", + "invite-chat-intro": "You were referred by a friend to join Status. Here’s some crypto to get you started! Use it to register an ENS name or buy a sticker pack", + "invite-chat-name": "Friend referral", + "invite-chat-pending": "Pending", + "invite-chat-rule": "Accepting will also reward your friend with a crypto referral bonus", + "invite-chat-starter-pack": "Starter Pack", + "invite-contacts": "Invite people from contacts list", + "invite-friend-to-status": "Invite friends to Status", + "invite-friends": "Invite friends", + "invite-friends-and-family": "Invite your friends and family to Status", + "invite-friends-to-status": "Invite friends to Status", + "invite-instruction-fifth": "You can choose to redeem your referral bonus anytime.", + "invite-instruction-first": "You send a unique invite link to your friend to download and join Status", + "invite-instruction-fourth": "You receive your referral bonus and your friend the Starter Pack", + "invite-instruction-second": "Your friend downloads Status and creates an account (on Android)", + "invite-instruction-third": "A chat with your friend is started, where they confirm your referral", + "invite-n-users": "Invite {{count}} users", + "invite-people": "Invite people", + "invite-people-from-contacts": "Invite people from contact list", + "invite-privacy-policy-public": "You installed Status through a referral link. By joining this chat you attribute your referrer and agree to the", + "invite-privacy-policy1": "By accepting you agree to the referral program", + "invite-privacy-policy2": "Terms and Conditions.", + "invite-public-chat-home": "Referral invitation", + "invite-public-chat-intro": "Here’s some crypto to get you started! Use it to register an ENS name or buy a sticker pack", + "invite-receive-account": "Account to receive your referral bonus", + "invite-reward": "Earn crypto for every friend you invite!", + "invite-reward-friend": "Friend: ", + "invite-reward-friend-description": "Your friend will receive a Starter Pack consisting of some {{reward}} to get started", + "invite-reward-friend-name": "Starter Pack", + "invite-reward-you": "You: ", + "invite-reward-you-description": "Invite a friend and receive {{reward}} as referral bonus. Use it to get stickers, an ENS name and try dapps", + "invite-reward-you-name": "Referral bonus", + "invite-select-account": "Select an account to receive your referral bonus", + "invite-to-community": "Invite to community", + "invite-warning": "This promotion is only valid for users of an Android device, who aren't residents of US. Friend needs to confirm referral within 7 days", + "invited": "invited", + "ip-address": "IP address", + "italic": "Italic", + "jan": "Jan", + "join": "Join", + "join-a-community": "or join a community", + "join-as": "Join as {{role}}", + "join-community-to-post": "Join community to post", + "join-decentralised-communities": "Join Decentralized Communities", + "join-group-chat": "Join group", + "join-group-chat-description": "{{username}} invited you to join the group {{group-name}}", + "join-me": "Hey join me on Status: {{url}}", + "join-more-users": "Join {{user1}}, {{user2}} and {{left-count}} more", + "join-new-private-chat": "Start a new private chat", + "join-new-public-chat": "Join a public chat", + "join-one-user": "Join {{user}}", + "join-open-community": "Join Community", + "join-request": "Join request", + "join-two-users": "Join {{user1}} and {{user2}}", + "joined": "Joined", + "joined-community": "You joined “{{community}}”", + "joined-group-chat-description": "You've joined {{group-name}} from invitation by {{username}}", + "jul": "Jul", + "jump-to": "Jump to", + "jun": "Jun", + "K": "K", + "keep-key": "KeepKey", + "key": "Key", + "key-managment": "Key management", + "key-name-error-emoji": "Emojis are not allowed", + "key-name-error-length": "Key name too long", + "key-name-error-special-char": "Special characters are not allowed", + "key-name-error-taken": "Key pair name already in use", + "key-name-error-too-short": "Key pair name must be at least {{count}} characters", + "key-on-device": "Private key is saved on this device", + "key-pair-imported-successfully": "{{name}} key pair imported successfully", + "key-pair-name-updated": "Key pair name updated", + "key-pair-removed": "Key pair and derived accounts has been removed", + "key-pairs-successfully-imported": "{{count}} key pairs successfully imported", + "keycard": "Keycard", + "keycard-access-reset": "Keycard access is reset", + "keycard-applet-install-instructions": "To install the applet please follow the instructions on https://github.com/status-im/keycard-cli#keycard-applet-installation", + "keycard-awaiting-description": "Try moving the card around to find the NFC reader on your device", + "keycard-awaiting-title": "Still looking...", + "keycard-backup": "Create a backup Keycard", + "keycard-backup-success-body": "Backup card created successfully. You can now use it with your account just like the primary card.", + "keycard-backup-success-title": "Backup successful", + "keycard-blocked": "Keycard has been blocked.\nYou need to reset card to continue using it.", + "keycard-can-use-with-new-passcode": "You can use this card with your new passcode", + "keycard-cancel-setup-text": "This will cancel keycard setup. It's highly recommended to finish the setup in order to use keycard. Do you really want to cancel?", + "keycard-cancel-setup-title": "Dangerous operation", + "keycard-connected-description": "Try keeping the card still", + "keycard-connected-title": "Connected", + "keycard-desc": "Own a Keycard? Store your keys on it; you’ll need it for transactions", + "keycard-dont-ask-card": "Don't ask for card to sign in", + "keycard-enter-new-passcode": "Enter new passcode {{step}}/2", + "keycard-error-description": "Connect the card again to continue", + "keycard-error-title": "Connection lost", + "keycard-factory-reset": "Return card to factory settings", + "keycard-factory-reset-text": "Performing this will delete any mnemonic phrase stored on the card. Make sure you have a backup of the mnemonic phrase you've been using with this Keycard.", + "keycard-factory-reset-title": "Are you sure you want to perform a factory reset?", + "keycard-free-pairing-slots": "Keycard has {{n}} free pairing slots", + "keycard-has-multiaccount-on-it": "This card is full. Each card can hold one main key pair", + "keycard-init-description": "Put the card to the back of your phone to continue", + "keycard-init-title": "Looking for cards...", + "keycard-is-blocked-details": "You can no longer use this card to access or sign for this account. There have been too many failed passcode and PUK attempts.", + "keycard-is-blocked-instructions": "To access your account you will need to factory reset your card. Tap the button below to start the procedure, you will need your mnemonic.", + "keycard-is-blocked-title": "Keycard is blocked", + "keycard-is-frozen-details": "To protect your assets, your card is frozen. Reset your card to unfreeze it and be able to send transactions. You can do this with your PUK or your mnemonic.", + "keycard-is-frozen-factory-reset": "Reset with mnemonic", + "keycard-is-frozen-reset": "Reset with PUK", + "keycard-is-frozen-title": "Keycard is frozen", + "keycard-onboarding-finishing-header": "Finishing up", + "keycard-onboarding-intro-header": "Store your keys on Keycard", + "keycard-onboarding-intro-text": "Get ready, this might take a few minutes, but it's important to secure your account", + "keycard-onboarding-mnemonic-text": "You will also need a piece of paper and a pencil to write down your seed phrase.", + "keycard-onboarding-pairing-header": "Pairing the card...", + "keycard-onboarding-pin-text": "You will need to create a 6-digit passcode which will be used to protect access to your Keycard.", + "keycard-onboarding-preparing-header": "Preparing the card...", + "keycard-onboarding-puk-code-header": "Write codes down\n and store them securely", + "keycard-onboarding-recovery-phrase-description": "You need this seed phrase to get your key back. Write it down. Keep it safe, offline, and separate from this device.", + "keycard-onboarding-recovery-phrase-header": "Back up seed phrase", + "keycard-onboarding-recovery-phrase-text": "For your eyes only. This is the magical seed used to generate your key.", + "keycard-onboarding-start-header": "Hold card to the back\n of your phone to start", + "keycard-onboarding-start-step1": "Create a passcode", + "keycard-onboarding-start-step1-text": "Around 1 minute. Create a 6-digit passcode to encrypt your keys", + "keycard-onboarding-start-step2": "Write down PUK and the pairing code", + "keycard-onboarding-start-step2-text": "Around 1 minute. You are going to need a piece of paper and a pencil for that", + "keycard-onboarding-start-step3": "Back up the seed phrase", + "keycard-onboarding-start-step3-text": "Around 1 minute. Also a piece of paper and a pencil are necessary", + "keycard-onboarding-start-text": "And maintain card to phone contact\n during the setup. The setup will take around 4 minutes", + "keycard-processing-description": "Try keeping the card still", + "keycard-processing-title": "Processing...", + "keycard-recover": "lost or frozen card?", + "keycard-recover-text": "If you have your mnemonic phrase you can create a new Keycard associated with this account. You can use either a new Keycard or perform factory reset on a frozen one.", + "keycard-recover-title": "Create a new card for this account?", + "keycard-recovery-intro-button-text": "Begin recovery", + "keycard-recovery-intro-header": "Recover keys stored on keycard", + "keycard-recovery-intro-text": "If you generated keys using a keycard before and now want to use these keys on this device", + "keycard-recovery-no-key-header": "There’s nothing to \nrecover here", + "keycard-recovery-no-key-text": "Your Keycard has no key stored on it. In order to use it, generate a new key and choose your Keycard to store the key", + "keycard-recovery-phrase-confirm-header": "Confirm seed phrase", + "keycard-recovery-phrase-confirmation-text": "You won’t have a second chance! If you lose access, for example by losing your keycard, you can only access your keys with your seed phrase. No one, but you has your seed phrase. Write it down. Keep it safe.", + "keycard-recovery-phrase-confirmation-title": "Written the seed phrase down?", + "keycard-recovery-success-header": "Your keys have been\n successfully recovered", + "keycard-redeem-title": "Redeem to", + "keycard-redeem-tx": "Redeem assets", + "keycard-redeem-tx-desc": "Tap the card to sign and receive assets", + "keycard-reset-passcode": "Reset passcode", + "keycard-success-description": "You may remove the card now", + "keycard-success-title": "Success", + "keycard-unauthorized-operation": "You're unauthorized to perform this operation.\n Please tap valid card and try again.", + "keycard-upsell-subtitle": "Enhanced security and convenience", + "keypair-name": "Key pair name", + "keypair-name-description": "Name key pair for your own personal reference", + "keypair-name-input-placeholder": "Collectibles key pair, Old vault....", + "keypair-title": "{{name}}'s default key pair", + "keypairs": "Key pairs", + "keypairs-accounts-and-addresses": "Key pairs, accounts and addresses", + "keypairs-and-accounts": "Key pairs and accounts", + "keypairs-description": "Select key pair to derive your new account from", + "keys-saved": "Keys saved!", + "kicked": "Kicked", + "language": "Language", + "language-and-currency": "Language and currency", + "last-backup-performed": "Last backup performed:", + "last-transaction": "Last transaction", + "layer-2": "Layer 2", + "learn-more": "Learn more", + "learn-more-about-keycard": "Learn more about Keycard", + "leave": "Leave", + "leave-chat": "Leave chat", + "leave-chat-confirmation": "Chat history will be removed from your device. After rejoining you won't be able to retrieve any of your history.", + "leave-community": "Leave community", + "leave-community-farewell": "We’ll be sad to see you go but remember, you can come back at any time! All shared addresses will be unshared.", + "leave-community-message": "We’ll be sad to see you go but remember, you can come back at any time!", + "leave-community?": "Leave community?", + "leave-confirmation": "Leave {{chat-name}}", + "leave-group": "Leave group", + "leave-group?": "Leave Group?", + "ledger": "Ledger", + "ledger-live": "Ledger live", + "left": "left", + "left-community": "You left “{{community}}”", + "legacy": "Legacy", + "les-ulc": "LES/ULC", + "lets-go": "Let's go!", + "lifestyle": "Lifestyle", + "light": "Light", + "light-client-enabled": "Light client", + "limit": "Limit", + "link-preview-loading-message": "Generating preview", + "link-to-community": "Link to community", + "link-to-profile": "Link to profile", + "link-to-profile-copied": "Link to Profile copied to clipboard", + "linked-on": "Linked on {{date}}", + "links": "Links", + "load-messages-before": "before {{date}}", + "load-more-messages": "↓ Fetch more messages", + "load-more-timeline": "↓ Fetch more", + "loading": "Loading...", + "local-notifications": "Local notifications", + "local-notifications-subtitle": "Enable background service", + "local-pairing-experimental-mode": "Local Pairing Mode (alpha)", + "lock-app-with": "Lock app with", + "log-in": "Log in", + "log-level": "Log level", + "log-level-settings": "Log level settings", + "logged-in": "Logged in", + "logging": "Logging", + "logging-enabled": "Logging enabled?", + "login-pin-description": "Enter your 6-digit passcode to unlock your keys", + "logout": "Log out", + "logout-app-content": "The account will be logged out. When you unlock it again, the selected network will be used", + "logout-are-you-sure": "Are you sure you want\nto log out?", + "logout-key-management": "You need to log out to access key management.", + "logout-title": "Log out?", + "logs-of-actions-withing-the-app": "Logs of actions within the app, including button presses and screen visits", + "looking-for-cards": "Looking for cards...", + "lost-connection": "Lost connection", + "low-tip": "tip is too low", + "lower-than-average-tip": "lower than average tip", + "M": "M", + "mail-should-be-configured": "Mail client should be configured", + "mailserver-address": "Status node address", + "mailserver-automatic": "Automatic selection", + "mailserver-automatic-switch-explanation": "Choose the fastest Status node available", + "mailserver-connection-error": "Could not connect to Status node", + "mailserver-content": "A node in the Status network that routes and stores messages, for up to 30 days.", + "mailserver-details": "Status node details", + "mailserver-error-content": "The Status node you selected couldn't be reached.", + "mailserver-error-title": "Error connecting to Status node", + "mailserver-format": "enode://{enode-id}@{ip-address}:{port}", + "mailserver-pick-another": "Pick another Status node", + "mailserver-reconnect": "Could not connect to Status node. Tap to reconnect", + "mailserver-request-error-content": "The following error was returned by the Status node: {{error}}", + "mailserver-request-error-status": "An error occured while fetching history, check the logs for details", + "mailserver-request-error-title": "Status node request error", + "mailserver-request-retry": "Retry request", + "mailserver-retry": "Retry", + "mailserver-title": "Status node", + "main-account": "Main account", + "main-currency": "Main currency", + "main-networks": "Main networks", + "main-wallet": "Main Wallet", + "mainnet": "Mainnet", + "make-admin": "Make admin", + "make-moderator": "Make moderator", + "make-one-it-is-easy-we-promise": "Make one, it’s easy, we promise!", + "make-sure-no-camera-warning": "Make sure no camera or person can see this screen before revealing", + "manage-connections": "Manage connections from within Application Connections", + "manage-keys-and-storage": "Manage keys and storage", + "manage-members": "Manage members", + "manage-tokens": "Manage tokens", + "mar": "Mar", + "mark-all-notifications-as-read": "Mark all notifications as read", + "mark-all-read": "Mark all read", + "mark-as-read": "Mark as read", + "mark-untrustworthy": "Mark as Untrustworthy", + "mark-user-untrustworthy": "Mark {{username}} as untrustworthy", + "master-account": "Master account", + "max": "Max: {{number}}", + "max-2-decimals": "Max. 2 decimals", + "max-fee": "Max fee", + "max-fees": "Max fees", + "max-priority-fee": "Max priority fee", + "max-slippage": "Max slippage", + "maximum-fee": "Maximum fee", + "maximum-fee-desc": "Maximum overall price for the transaction. If the current block base fee exceeds this, your transaction will be included in a following block with a lower base fee.", + "may": "May", + "maybe-later": "Maybe later", + "member": "Member", + "member-ban": "Ban member", + "member-kick": "Kick member", + "members": { + "one": "1 member", + "other": "{{count}} members" + }, + "members-active": { + "one": "1 member", + "other": "{{count}} members" + }, + "members-active-none": "no members", + "members-count": "{{count}} members", + "members-label": "Members", + "members-limit-reached": "Members limit reached", + "members-title": "Members", + "membership": "Membership", + "membership-approval": "Require approval", + "membership-approval-description": "Your community is free to join, but new members are required to be approved by the community creator first", + "membership-button": "Membership requirement", + "membership-declined": "Membership request was declined", + "membership-description": "Group membership requires you to be accepted by the group admin", + "membership-ens": "Require ENS username", + "membership-ens-description": "Your community requires an ENS username to be able to join", + "membership-free": "No requirement", + "membership-free-description": "Your community is free for anyone to join", + "membership-invite": "Require invite from another member", + "membership-invite-description": "Your community can only be joined by an invitation from existing community members", + "membership-none": "None", + "membership-none-placeholder": "You can require new members to meet certain criteria before they can join. This can be changed at any time", + "membership-request-denied": "Membership request denied", + "membership-request-pending": "Membership request pending", + "membership-requests": "Membership requests", + "membership-requirements-not-met": "Membership requirements not met", + "membership-title": "Membership requirement", + "mention": "Mention", + "mentions": "Mentions", + "message": "Message", + "message-deleted": "Message deleted", + "message-deleted-for-everyone": "Message deleted for everyone", + "message-deleted-for-everyone-count": { + "one": "1 message deleted for everyone", + "other": "{{count}} messages deleted for everyone" + }, + "message-deleted-for-you": "Message deleted for you", + "message-deleted-for-you-count": { + "one": "1 message deleted for you", + "other": "{{count}} messages deleted for you" + }, + "message-not-sent": "Message not sent", + "message-options-cancel": "Cancel", + "message-reply": "Reply", + "messages": "Messages", + "messages-from-contacts-only-subtitle": "Only people you added as contacts can start a new chat with you or invite you to a group", + "messages-gap-warning": "Some messages might be missing", + "might-break": "Might break some ÐApps", + "migration-successful": "Migration successful", + "migration-successful-text": "Account succesfully migrated to Keycard", + "migrations-failed-content": "{{message}}\nschema version: initial {{initial-version}}, current {{current-version}}, last {{last-version}}\n\nA database error occured. Your funds and chat key are safe. Other data, like your chats and contacts, cannot be restored. \"{{erase-multiaccounts-data-button-text}}\" button, will remove all other data and allows you to access your funds and send messages.", + "miners-higher-fee": "Miners will likely inlcude your transaction earlier if you pay a higher fee.", + "minimum-characters": "Minimum {{min-chars}} characters", + "minimum-received": "Minimum received", + "mint": "Mint", + "mo": "Mo", + "mobile": "Mobile", + "mobile-network-ask-me": "Ask me when on mobile network", + "mobile-network-continue-syncing": "Continue syncing", + "mobile-network-continue-syncing-details": "You can change this later in settings", + "mobile-network-go-to-settings": "Go to settings", + "mobile-network-settings": "Mobile data", + "mobile-network-sheet-configure": "You can configure syncing in more \ndetail in", + "mobile-network-sheet-offline": "No Wi-fi, message syncing disabled.", + "mobile-network-sheet-offline-details": "Syncing using mobile network is off", + "mobile-network-sheet-remember-choice": "Remember my choice", + "mobile-network-sheet-settings": "settings", + "mobile-network-start-syncing": "Start syncing", + "mobile-network-stop-syncing": "Stop syncing", + "mobile-network-stop-syncing-details": "Until connected to Wi-Fi?", + "mobile-network-use-mobile": "Use mobile data", + "mobile-network-use-mobile-data": "Status uses a lot of data when syncing chats and wallet.", + "mobile-network-use-wifi": "Wi-Fi only", + "mobile-syncing-sheet-details": "Status uses a lot of data when syncing chats and wallet.", + "mobile-syncing-sheet-title": "Sync using mobile data?", + "mon": "Mon", + "more": "more", + "move-and-reset": "Move and Reset", + "move-keystore-file": "Move keystore file", + "move-keystore-file-to-keycard": "Move keystore file to keycard?", + "multiaccount-exists-content": "Keys for this account already exist and can’t be added again. If you’ve lost your password, passcode or Keycard, uninstall the app, reinstall and access your keys by entering your seed phrase", + "multiaccount-exists-title": "Keys for this account already exist", + "multiaccounts-recover-enter-phrase-text": "Enter 12, 15, 18, 21 or 24 words.\nSeparate words by a single space.", + "multiaccounts-recover-enter-phrase-title": "Enter your seed phrase", + "multichain": "Multichain", + "music": "Music", + "mutal-contacts": "Mutual contacts", + "mute": "Mute", + "mute-channel": "Mute channel", + "mute-chat": "Mute chat", + "mute-chat-capitialized": "Mute Chat", + "mute-community": "Mute community", + "mute-for-1-hour": "For 1 hour", + "mute-for-1-week": "For 7 days", + "mute-for-15-mins": "For 15 min", + "mute-for-8-hours": "For 8 hours", + "mute-group": "Mute group", + "mute-till-unmute": "Until you turn it back on", + "muted-until": "Muted until {{duration}}", + "mutual-contact-requests": "Mutual contact requests", + "my-accounts": "My accounts", + "my-accounts-empty": "Your available accounts will appear here", + "my-albums": "My albums", + "my-profile": "My profile", + "my-status": "My status", + "n-m-people": "{{n}}/{{m}} people", + "n-photos": "{{count}} photos", + "n-users-were-invited": "{{count}} users were invited", + "name": "Name", + "name-ens-or-address": "Name, ENS, or address", + "name-must-differ-error": "Name must differ from other accounts", + "name-of-token": "The name of your token", + "name-optional": "Name (optional)", + "name-updated": "Name updated", + "name-your-channel": "Name your channel", + "name-your-channel-placeholder": "Channel name", + "name-your-community": "Name your community", + "name-your-community-placeholder": "A catchy name", + "name-your-group": "Name your group", + "need-help": "Need help?", + "negative": "Negative", + "network": "Network", + "network-chain": "Network chain", + "network-fee": "Network fee", + "network-id": "Network ID", + "network-invalid-network-id": "Specified network id doesn't correspond to network id by RPC url", + "network-invalid-status-code": "Invalid status code: {{code}}", + "network-invalid-url": "Network URL is invalid", + "network-not-supported": "Networks not supported", + "network-preferences": "Network preferences", + "network-preferences-desc-1": "Select which networks this address is happy to receive funds on", + "network-preferences-desc-2": "Select which networks to receive funds on", + "network-settings": "Network settings", + "networks": "Networks", + "never": "Never", + "new": "New", + "new-category": "New category", + "new-chat": "New chat", + "new-community-title": "New community", + "new-contact": "New contact", + "new-contract": "New Contract", + "new-favourite": "New favourite", + "new-group": "New group", + "new-group-chat": "New group chat", + "new-group-limit": "You can only add {{max-contacts}} contacts to the group chat", + "new-messages-header": "New Messages", + "new-network": "New network", + "new-password": "New password", + "new-pin-description": "Enter new 6-digit passcode", + "new-public-group-chat": "Join public chat", + "new-puk-description": "Enter new 12-digit PUK", + "new-status": "New status", + "new-tab": "New tab", + "new-to-status": "I’m new to Status", + "new-ui": "New UI", + "next": "Next", + "next-you-will": "Next, you will be asked to confirm the position of certain words in your recovery phrase", + "NFT": "NFT", + "nickname": "Nickname", + "nickname-added": "Nickname for {{primary-name}} added", + "nickname-description": "Nicknames help you identify others in Status.\nOnly you can see the nicknames you’ve added", + "nickname-is-too-long": "Nickname is too long", + "nickname-removed": "Nickname removed", + "nickname-updated": "Nickname for {{primary-name}} updated", + "nickname-visible-to-you": "Nickname will only be visible to you", + "nine-days": "Nine days", + "no": "No", + "no-activity": "No activity", + "no-addresses-selected": "At least 1 address must be shared with community", + "no-collectibles": "No collectibles", + "no-collectibles-description": "Don't be a bored ape", + "no-communities": "No communities", + "no-communities-description": "go full nyan, find your community", + "no-communities-description-strikethrough": "Never", + "no-contacts": "No contacts", + "no-contacts-description": "This is fine, just invite your mother", + "no-dapps": "No connected dApps", + "no-dapps-description": "We want dApps!", + "no-fees": "No fees", + "no-group-chats": "No group chats", + "no-group-chats-description": "Much fun. Have friends. Wow!", + "no-keycard-applet-on-card": "No Keycard applet on card", + "no-messages": "No messages", + "no-messages-description": "Here’s a cat in a box instead", + "no-one": "No one", + "no-opened-communities": "No opened communities", + "no-opened-communities-description": "But... feels good man", + "no-other-accounts": "No other accounts", + "no-pairing-slots-available": "This card is already paired to 5 devices and cannot pair to this one. Please use one of the paired devices, log in with this card and free up pairing slots on the card", + "no-pending-communities": "No pending communities", + "no-pending-communities-description": "It's toaster", + "no-permissions": "No permissions", + "no-permissions-to-post": "Sorry, you don't have permissions to post in this channel", + "no-pinned-messages": "No pinned messages", + "no-pinned-messages-desc": "Just keep pinning, just keep pinning\nWhat do we do? We pin, pin, pin", + "no-recent-transactions": "No recent transactions", + "no-relevant-tokens": "No relevant tokens", + "no-result": "No results", + "no-routes-found": "No routes found", + "no-routes-found-confirmation": "No routes found. Token type may not be supported or you may don't have enough ETH to cover gas.", + "no-saved-addresses": "No saved addresses", + "no-thanks": "No thanks", + "no-tokens-found": "No tokens found", + "node-address": "Node address", + "node-details": "Node details", + "node-info": "Node info", + "node-version": "Node version", + "nodes-disabled": "Status nodes disabled", + "non-archival-node": "RPC endpoint doesn't support archival requests. Your local transfers history might be incomplete.", + "non-contacts": "Non contacts", + "nonce": "Nonce", + "none": "None", + "normal": "Normal", + "not-a-chatkey": "This is not a chatkey", + "not-applicable": "Not applicable for unsigned transactions", + "not-available": "Not available", + "not-connected-nodes": "Not connected to a status node", + "not-connected-to-peers": "Not connected to any peers", + "not-enough-assets": "Not enough assets to complete transaction", + "not-enough-assets-to-pay-gas-fees": "Not enough assets to pay gas fees", + "not-enough-snt": "Not enough SNT", + "not-found": "Not found", + "not-keycard-text": "The card you used is not a Keycard. You need to purchase a Keycard to use it", + "not-keycard-title": "Not a Keycard", + "not-now": "Not now", + "not-paired-with-this-device": "Not paired with this device", + "not-preferred-by-receiver": "Not preferred by receiver", + "not-registered": "not registered", + "nothing-found": "Nothing found", + "notification-settings": "Notification settings", + "notifications": "Notifications", + "notifications-marked-as-read": "{{count}} notifications marked as read", + "notifications-non-contacts": "Notifications from non-contacts", + "notifications-preferences": "Notification preferences", + "notifications-servers": "Notification servers", + "notifications-switch": "Show notifications", + "notifications-transactions": "Wallet transactions", + "notify": "Notify", + "nov": "Nov", + "now": "Now", + "oct": "Oct", + "off": "Off", + "off-status-tree": "Off Status tree", + "offline": "Offline", + "offline-community-member": "Offline", + "offline-messaging-use-history-explanation": "Enable Status nodes to fetch messages that were sent while the app was closed. When enabled, a Status node gets your IP address. When disabled you will not receive messages when the app is closed and will not see them when you open the app later.", + "offline-messaging-use-history-nodes": "Use Status nodes", + "ok": "OK", + "ok-continue": "Okay, continue", + "ok-got-it": "Okay, got it", + "ok-save-pass": "OK, save password", + "okay": "Okay", + "on": "on", + "on-capitalized": "On", + "on-device": "On device", + "on-keycard": "On Keycard", + "on-status-tree": "On Status tree", + "on-the-web": "On the web", + "once-enabled-share-metadata": "Once enabled, links posted in the chat may share your metadata with the site", + "one-day": "One day", + "one-month": "One month", + "one-month-int": "1 month", + "one-photo": "1 photo", + "one-time": "One time", + "one-user-was-invited": "1 user was invited", + "one-week": "One week", + "one-week-int": "1 week", + "one-year": "1 year", + "online": "Online", + "online-community-member": "Online", + "online-now": "Online now", + "only-mentions": "Only @mentions", + "oops-this-qr-does-not-contain-an-address": "Oops! This QR does not contain an address", + "oops-wrong-password": "Oops, wrong password!", + "oops-wrong-word": "Oops! Wrong word", + "open": "Open", + "open-chat": "Open chat", + "open-dapp": "Open ÐApp", + "open-dapp-store": "Discover ÐApps", + "open-home": "Open...", + "open-in-new-tab": "Open in new tab", + "open-membership": "Open membership", + "open-nfc-settings": "Open NFC settings", + "open-on-block-explorer": "Open on block explorer", + "open-status-on-your-other-device": "Open Status on your other device", + "open-your": "Open your", + "opened": "Opened", + "opening-buy-crypto": "Opening {{site}}...", + "opensea": "OpenSea", + "optimal": "Optimal", + "optimism": "Optimism", + "optional": "optional", + "or": "OR", + "or-tap": "or tap", + "origin": "Origin", + "origin-desc": "Origin is where your key pair (your private and public key) comes from. You can generate a new key pair or import an existing private key.", + "origin-header": "Origin", + "outgoing": "Outgoing", + "outgoing-transaction": "Outgoing transaction", + "overview": "Overview", + "own-your-crypto": "Own your crypto", + "owner": "Owner", + "page-camera-request-blocked": "camera requests blocked. To enable camera requests go to Settings", + "page-would-like-to-use-camera": "would like to use your camera", + "pair": "Pair", + "pair-card": "Pair to this device", + "pair-code": "Pair code", + "pair-code-explanation": "Pairs card to a different device (up to 5) to unlock keys and sign transactions with the same Keycard", + "pair-code-placeholder": "Pair code...", + "pair-device-toast": "Device successfully paired", + "pair-devices": "Pair devices", + "pair-this-card": "Pair this card", + "pair-this-device": "Advertise device", + "pair-this-device-description": "Pair your devices to sync contacts and chats between them", + "paired-devices": "Paired devices", + "paired-with-this-device": "Paired with this device", + "pairing": "Pairing", + "pairing-card": "Pairing card", + "pairing-changed": "Pairing code has been changed", + "pairing-code-placeholder": "Pairing code...", + "pairing-code_error1": "Pairing codes don't match.", + "pairing-go-to-installation": "Go to pairing settings", + "pairing-maximum-number-reached-content": "Please disable one of your devices before enabling a new one.", + "pairing-maximum-number-reached-title": "Max number of devices reached", + "pairing-new-installation-detected-content": "A new device has been detected.\nIn order to use your devices correctly, it's important to pair and enable them before using them.\nPlease go to the device section under settings to pair your devices.", + "pairing-new-installation-detected-title": "New device detected", + "pairing-no-info": "No info", + "pairing-please-set-a-name": "Please set a name for your device.", + "participate-in-the-metaverse": "Participate in the truly free metaverse", + "passphrase": "Passphrase", + "password": "Password", + "password-creation-confirm": "Confirm password", + "password-creation-disclaimer": "I understand my password can't be recovered", + "password-creation-dont-match": "Passwords do not match", + "password-creation-hint": "Minimum 10 characters", + "password-creation-match": "Passwords match", + "password-creation-placeholder-1": "Type password", + "password-creation-placeholder-2": "Repeat password", + "password-creation-subtitle": "To log in to Status and sign transactions", + "password-creation-tips-1": "Lower case", + "password-creation-tips-2": "Upper case", + "password-creation-tips-3": "Numbers", + "password-creation-tips-4": "Symbols", + "password-creation-tips-title": "Tips to improve", + "password-creation-title": "Create profile password", + "password-description": "At least 6 characters. Your password protects your keys. You need it to unlock Status and transact.", + "password-mismatch": "New password and confirmation does not match", + "password-placeholder": "Password...", + "password-placeholder2": "Confirm your password", + "password-reset-in-progress": "Changing password...", + "password-reset-success": "Password changed", + "password-reset-success-message": "You will need to sign in again", + "password_error1": "Passwords don't match.", + "paste": "Paste", + "paste-json": "Paste JSON", + "path-format": "Path format", + "pay": "Pay", + "pay-to-chat": "Pay to chat", + "peer-content": "A device connected to the Status chat network. Each user can represent one or more peers, depending on their number of devices.", + "peer-title": "Peer", + "peer-to-peer": "Peer to peer", + "peers": "Peers", + "peers-count": "Peers count", + "peers-stats": "Peers stats", + "pending": "Pending", + "pending-confirmation": "Pending confirmation...", + "pending-invitations": "Pending membership requests", + "pending-on": "Pending on", + "pending-requests": "Pending requests", + "per-gas-price-limit": "Per-gas price limit", + "per-gas-tip-limit": "Per-gas tip limit", + "perform-backup": "Perform backup", + "permissions": "Permissions", + "phone-e164": "International 1", + "photo": "Photo", + "photo-caps": "PHOTO", + "photo-saved": "Photo saved to your device", + "photos": "Photos", + "photos-access-error": "To grant the required photos permission, please go to your system settings and make sure that Status > Photos is selected.", + "pin": "Pin", + "pin-changed": "6-digit passcode has been changed", + "pin-code": "6-digit passcode", + "pin-limit-reached": "Pin limit reached. Unpin a previous message first.", + "pin-mismatch": "Wrong passcode", + "pin-one-attempt": " one attempt ", + "pin-one-attempt-blocked-after": "before your Keycard gets blocked", + "pin-one-attempt-blocked-before": "Be careful, you have only", + "pin-one-attempt-frozen-after": "before your Keycard gets frozen", + "pin-one-attempt-frozen-before": "Be careful, you have only", + "pin-retries-left": "{{number}} attempts left", + "pin-to-channel": "Pin to the channel", + "pin-to-chat": "Pin to the chat", + "Pinned-a-message": "Pinned a message", + "pinned-a-message": "pinned a message", + "pinned-by": "Pinned by", + "pinned-messages": "Pinned messages", + "pinned-messages-2": "Pinned \nmessages", + "pinned-messages-count": { + "one": "1 pinned message", + "other": "{{count}} pinned messages" + }, + "pinned-messages-empty": "Pinned messages will appear here. To pin a message, press and hold it and tap `Pin`", + "podcasts": "Podcasts", + "positive": "Positive", + "powered-by-paraswap": "Powered by Paraswap", + "preference": "Preference", + "preferred-by-receiver": "Preferred by receiver", + "press": "Press", + "preview-privacy": "Preview privacy mode", + "previewing-may-share-metadata": "Previewing links from these websites may share your metadata with their owners", + "price-impact": "Price impact", + "price-impact-desc": "Estimated price impact for this transaction. If the current block base fee exceeds this, your transaction will be included in a following block with a lower base fee.", + "principles": "Principles", + "priority": "Priority", + "privacy": "Privacy", + "privacy-and-security": "Privacy and security", + "privacy-photos": "Profile Photo Privacy", + "privacy-policy": "Privacy policy", + "privacy-show-to-warning": "People who have already seen your profile picture will continue to do so", + "private-key": "Private key", + "private-key-public-address": "Public address of private key", + "private-notifications": "Private notifications", + "private-notifications-descr": "Status will notify you about new messages. You can edit your notification preferences later in settings.", + "proceed-anyway": "Proceed anyway", + "processing": "Just a moment", + "product-information": "Product Information", + "profile": "Profile", + "profile-bio": "Profile bio", + "profile-deleted-content": "Your profile was successfully deleted", + "profile-deleted-keycard": "You can now restore another key pair on your Keycard", + "profile-deleted-title": "Profile deleted", + "profile-details": "Profile details", + "profile-name": "Profile name", + "profile-name-is-too-long": "Profile name is too long", + "profile-not-found": "Profile not found", + "profile-password": "Profile password", + "profile-pic-pick": "Select from gallery", + "profile-pic-remove": "Remove photo", + "profile-pic-take": "Take photo", + "profile-picture-added": "Profile picture added", + "profile-picture-removed": "Profile picture removed", + "profile-picture-updated": "Profile picture updated", + "profiles-on-device": "Profiles on device", + "provider-is-down": "The provider for the following chain(s) is down: {{chains}}", + "public": "Public", + "public-channel": "Public channel", + "public-chat": "Public chat", + "public-chat-description": "Join public chats for your interests! Anyone can start a new one.", + "public-chats": "Public chats", + "public-group-status": "Public", + "public-group-topic": "Topic", + "public-key": "Public key", + "puk-and-pairing-codes-displayed": "PUK and pairing codes displayed", + "puk-changed": "12-digit PUK has been changed", + "puk-code": "PUK code", + "puk-code-explanation": "If you forget your 6-digit passcode or enter it incorrectly 3 times, you'll need this code to unlock your card.", + "puk-mismatch": "Wrong PUK code", + "push-failed-transaction": "Your transaction failed", + "push-failed-transaction-body": "{{value}} {{currency}} to {{to}}", + "push-inbound-transaction": "You received {{value}} {{currency}}", + "push-inbound-transaction-body": "From {{from}} to {{to}}", + "push-notifications-server-enabled": "Server enabled", + "push-notifications-servers": "Push notification servers", + "push-outbound-transaction": "You sent {{value}} {{currency}}", + "push-outbound-transaction-body": "From {{from}} to {{to}}", + "quiet-days": "{{quiet-days}} days", + "quiet-hours": "{{quiet-hours}} hours", + "re-encrypt": "Re-encrypt", + "re-encrypt-data": "Re-encrypt your data", + "re-encrypt-key": "Re-encrypt your keys", + "read": "Read", + "read-more": "Read more", + "rearrange-categories": "Rearrange Categories", + "receive": "Receive", + "receive-at-least": "Receive at least", + "receive-transaction": "Receive transaction", + "received": "Received", + "receiver-networks-warning": "Changing these settings may result in sending tokens to networks the recipient doesn't use", + "recent": "Recent", + "recent-empty": "Recently used addresses will appear here", + "recent-recipients": "Contacts", + "recently-used-stickers": "Recently used stickers will appear here", + "recipient": "Recipient", + "recipient-code": "Enter recipient address", + "recipient-code-placeholder": "0x... or username.domain.eth", + "recipient-gets": "Recipient gets", + "recover": "Recover", + "recover-key": "Access existing keys", + "recover-keycard-multiaccount-not-supported": "Keys for this account already exist and can't be added again. If you've lost your password, passcode or Keycard, uninstall the app, reinstall and access your keys by entering your seed phrase", + "recover-with-keycard": "Recover with Keycard", + "recover-with-seed-phrase": "Recover with seed phrase", + "recovering-key": "Accessing keys...", + "recovery-confirm-phrase": "Confirm seed phrase", + "recovery-phrase": "Seed phrase", + "recovery-success-text": "You will have to create a new code or password to re-encrypt your keys", + "recovery-typo-dialog-description": "Please note, your seed phrase must use the exact same words and order as you received it", + "recovery-typo-dialog-title": "Is the seed phrase correct?", + "recurrent": "Recurrent", + "redeem-amount": "{{quantity}} bonuses available", + "redeem-now": "Redeem now", + "redeem-success": "Redeem bonus success!", + "reduced-tip": "priority tip will be reduced", + "refresh": "Refresh", + "registered": "registered", + "reject": "Reject", + "reject-and-delete": "Reject and delete", + "remember-me": "Remember me", + "remind-me-later": "Show me this again", + "remote-notifications": "Remote notifications", + "remote-notifications-subtitle": "Enable google push notifications", + "remove": "Remove", + "remove-account": "Remove account", + "remove-account-confirmation": "I have taken note of the derivation path", + "remove-account-desc": "The account will be removed from all of your synced devices. Make sure you have a backup of your key pair or recovery phrase and derivation path (if it’s not default).", + "remove-account-title": "Remove account", + "remove-address": "Remove address", + "remove-contact": "Remove contact", + "remove-favourite": "Remove favourite", + "remove-from-chat": "Remove from chat", + "remove-from-contacts": "Remove from contacts", + "remove-from-contacts-text": "By removing a user from your contact list you do not hide your wallet address from them", + "remove-group": "Remove group", + "remove-key-pair-and-derived-accounts": "Remove key pair and derived accounts", + "remove-network": "Remove network", + "remove-nickname": "Remove nickname", + "remove-nickname-toast": "You have removed {{secondary-name}}'s nickname", + "remove-private-key-address-desc": "The account will be removed from all of your synced devices. Make sure you have a backup of your key pair or recovery phrase.", + "remove-profile-confirm-message": "All profile data will removed from device.", + "remove-profile-message": "Remove profile from this device", + "remove-profile?": "Remove profile?", + "remove-saved-address": "Remove saved address", + "remove-saved-address-description": "Transaction history relating to this address will no longer be labelled ‘{{name}}’.", + "remove-token": "Remove token", + "remove-user-from-group": "Remove {{username}} from the group", + "remove-watched-address-desc": "The watched address will be removed from all of your synced devices.", + "remove-watched-address-title": "Remove watched address", + "removed": "removed", + "removed-from-contacts": "Removed from contacts", + "rename": "Rename", + "rename-key-pair": "Rename key pair", + "repeat-pin": "Repeat new 6-digit passcode", + "repeat-puk": "Repeat new 12-digit PUK", + "replied": "Replied", + "replies": "Replies", + "replying-to": "Replying to {{author}}", + "report-bug-email-template": "1. Issue Description\n{{description}}\n\n\n2. Steps to reproduce\n{{steps}}\n\n\n3. Attach screenshots that can demo the problem, please\n", + "request-access": "Request access", + "request-feature": "Request a feature", + "request-membership": "Request membership", + "request-pending": "Request pending…", + "request-processed-after-node-online": "Community control node is offline. Request will be processed once it's back online.", + "request-to-join": "Request to join", + "request-to-join-community": "Request to join community", + "request-to-join-community-pending": "Request to join community pending", + "request-to-join-disclaimer": "Joining the community will reveal your public addresses to the owner", + "request-transaction": "Request transaction", + "request-txns-and-message-signing": "Request transactions and message signing", + "requested-to-join-community": "You requested to join “{{community}}”", + "requesting": "Requesting", + "required-field": "Required field", + "resend-message": "Resend", + "reset": "Reset", + "reset-card": "Reset card", + "reset-card-description": "This operation will reset card to initial state. It will erase all card data including private keys. Operation is not reversible.", + "reset-database": "Reset database", + "reset-database-warning": "Delete chats, contacts and settings. Required when you’ve lost your password", + "reset-database-warning-keycard": "Delete chats, contacts and settings.", + "reset-password": "Reset password", + "restore-defaults": "Restore Defaults", + "retake": "Retake", + "retry": "Retry", + "reveal-address": "Reveal address", + "reveal-phrase": "Reveal phrase", + "reveal-qr-code": "Reveal QR code", + "reveal-sync-code": "Reveal sync code", + "review-bridge": "Review bridge", + "review-send": "Review send", + "revoke-access": "Revoke access", + "ropsten-testnet": "Ropsten Testnet", + "rpc-usage-copy": "Copy", + "rpc-usage-filter": "Filter", + "rpc-usage-filter-methods": "Filter methods", + "rpc-usage-filtered-total": "{{filtered-total}} of {{total}}", + "rpc-usage-get-stats": "Refresh", + "rpc-usage-info": "RPC usage stats", + "rpc-usage-reset": "Reset", + "rpc-usage-total": "Total", + "sa": "Sa", + "safe-estimate": "Safe estimate", + "sat": "Sat", + "save": "Save", + "save-address": "Save address", + "save-bio": "Save bio", + "save-changes": "Save changes", + "save-colour": "Save colour", + "save-image-library": "Save image to library", + "save-image-to-photos": "Save image to Photos", + "save-name": "Save name", + "save-password": "Save password", + "save-password-unavailable": "Set device passcode to save password", + "save-password-unavailable-android": "Save password is unavailable: your device may be rooted or lacks necessary security features.", + "saved": "Saved", + "saved-address-network-preference-selection-description": "Only change if you know which networks the address owner is happy to to receive funds on", + "saved-address-removed": "Saved address removed", + "saved-addresses": "Saved addresses", + "saving-keys-to-device": "Saving keys to device...", + "say-hi": "Say hi", + "scan-an-account-qr-code": "Scan an account QR code", + "scan-an-address-qr-code": "Scan an address QR code", + "scan-key-pairs-qr-code": "Scan key pairs QR code", + "scan-or-enter-a-sync-code": "Scan or enter a sync code", + "scan-or-enter-sync-code": "Scan or enter sync code", + "scan-or-enter-sync-code-seen-on-this-device": "Scan or enter sync code seen on this device", + "scan-qr": "Scan QR", + "scan-qr-code": "Scan QR code", + "scan-sync-code": "Scan Sync Code", + "scan-sync-code-placeholder": "cs2:4FH...", + "scan-sync-qr-code": "Scan QR code", + "scan-the-qr-code-or-copy-the-sync-code": "Scan the QR code or copy the sync code", + "scan-tokens": "Scan tokens", + "scan-with-status-app": "Scan with the Status app on another device", + "scanning": "Scanning for activity...", + "scanning-for-activity": "Scanning for activity...", + "search": "Search", + "search-assets": "Search assets", + "search-contacts": "Search contacts", + "search-discover-communities": "Search communities or categories", + "search-no-chat-found": "No search results. Do you mean", + "searching-for-activity": "Searching for activity...", + "secret-keys-confirmation-text": "You will need them to continue to use your Keycard in case you ever lose your phone.", + "secret-keys-confirmation-title": "Written the codes down?", + "security": "Security", + "see-details": "See details", + "see-it-again": "SEE IT AGAIN", + "see-recovery-phrase-again": "See recovery phrase again", + "see-sticker-set": "See the full sticker set", + "see-suggestions": "See suggestions", + "seed-key-uid-mismatch": "Seed doesn't match", + "seed-key-uid-mismatch-desc-1": "The seed phrase you entered does not match {{multiaccount-name}}", + "seed-key-uid-mismatch-desc-2": "To manage keys for this account verify your seed phrase and try again.", + "seed-phrase-content": "A set of friendly-to-read words, randomly selected from the BIP39 standard list and used to recover or access your Ethereum account on other wallets and devices. Also referred to as a “mnemonic phrase,” “recovery phrase” or “wallet backup” across the crypto ecosystem. Most crypto apps use this same standard to generate accounts.", + "seed-phrase-error": "Recovery phrase contains invalid words", + "seed-phrase-incorrect": "Recovery phrase does not match key pair", + "seed-phrase-info": "Enter 12, 18 or 24 words separated by spaces", + "seed-phrase-invalid": "Invalid recovery phrase", + "seed-phrase-placeholder": "Type or paste your recovery phrase", + "seed-phrase-title": "Seed Phrase", + "seed-phrase-words-exceeded": "Recovery phrase cannot exceed 24 words", + "seed-phrase-words-uppercase": "Recovery phrase cannot contain uppercase characters", + "select": "Select", + "select-account": "Select account", + "select-account-dapp": "Select the account you wish to use with Dapps", + "select-account-first": "Select an account first", + "select-another-account": "Select another account", + "select-asset": "Select asset", + "select-asset-to-pay": "Select asset to pay", + "select-chat": "Select chat to start messaging", + "select-network": "Select network", + "select-networks": "Select networks", + "select-new-location-for-keys": "Select a new location to save your private key(s)", + "select-token-to-receive": "Select token to receive", + "select-token-to-swap": "Select token to Swap", + "selected": "Selected", + "selected-count-from-max": "{{selected}}/{{max}}", + "send": "Send", + "send-community-link": "Send community link", + "send-contact-request": "Send contact request", + "send-contact-request-message": "To start a chat you need to become contacts", + "send-from-network": "Send from {{network}}", + "send-limit": "Max: {{limit}}", + "send-logs": "Report a bug", + "send-logs-to": "Report a bug to {{email}}", + "send-message": "Send message", + "send-push-notifications": "Send Push Notifications", + "send-push-notifications-description": "When disabled, the person receiving your messages won't be notified of their arrival", + "send-reply": "Send reply", + "send-request": "Send request", + "send-request-amount": "Amount", + "send-request-amount-max-decimals": "Max number of decimals is {{asset-decimals}}", + "send-request-unknown-token": "Unknown token - {{asset}}", + "send-sending-to": "to {{recipient-name}}", + "send-to": "Send to", + "send-to-this-address": "Send to this address", + "send-to-user": "Send to {{user}}", + "send-transaction": "Send transaction", + "sending": "Sending", + "sending-to-networks-the-receiver-does-not-prefer": "Sending to networks the receiver does not prefer may result in recipient having difficulty accessing the sent tokens.", + "sending-to-unpreferred-networks": "Sending to unpreferred networks", + "sending-with-elipsis": "Sending...", + "sending-with-ellipsis": "Sending...", + "sent": "Sent", + "sent-a-gif": "Sent a GIF", + "sent-a-photo": "Sent a photo", + "sent-a-sticker": "Sent a Sticker", + "sent-at": "Sent at", + "sent-audio-message": "Sent audio message", + "sent-n-photos": "Sent {{number}} photos", + "sep": "Sep", + "sepolia-active": "Sepolia active", + "server": "Server", + "set-a-topic": "Create a topic", + "set-currency": "Set currency", + "set-custom-fee": "Set custom fee", + "set-dapp-access-permissions": "Set DApp access permissions", + "set-max": "Set max", + "set-nickname-toast": "You have renamed {{primary-name}} as {{nickname}}", + "set-up-sync": "Set up sync", + "settings": "Settings", + "setup-group-chat": "Setup group chat", + "setup-syncing": "Pair devices to sync", + "share": "Share", + "share-account": "Share account", + "share-address": "Share address", + "share-address-title": "{{address}} address", + "share-all-current-and-future-addresses": "Share all current and future addresses", + "share-channel": "Share channel", + "share-chat": "Share chat", + "share-collectible": "Share collectible", + "share-community": "Share community", + "share-contact-code": "Share my chat key", + "share-dapp-text": "Check out this DApp I'm using on Status: {{link}}", + "share-details": "Share details", + "share-image": "Share image", + "share-invite-link": "Share an invite link", + "share-link": "Share link", + "share-logs": "Share logs", + "share-my-profile": "Share my profile", + "share-opensea-link": "Share OpenSea link", + "share-profile": "Share profile", + "share-profile-link": "Share profile link", + "share-public-chat-text": "Check out this public chat on the Status app: {{link}}", + "share-usage-data": "Share usage data with Status", + "share-usage-data": "Share usage data", + "shared": "Shared", + "shared-a-community": "Shared a community", + "sharing-copied-to-clipboard": "Copied", + "sharing-copy-to-clipboard": "Copy", + "sharing-data-desc-1": "Data is validated against public rules to ensure no sensitive data is sent. Don’t trust, verify.", + "sharing-data-desc-2": "Usage data is sent end-to-end encrypted over Status’ peer-to-peer network", + "sharing-data-desc-3": "Instead of your regular chat key, a single use key is used", + "sharing-data-desc-4": "Usage data cannot be associated with your IP address", + "sharing-data-desc-5": "Cumulative data of all users is publicaly available", + "sharing-data-desc-6": "The data is removed from your phone after they are sent", + "sharing-share": "Share", + "sharing-usage-data-can-be-turned-off": "Sharing usage data can be turned off anytime in Settings / Privacy and Security.", + "shell-placeholder-subtitle": "Jump between your communities, messages,\nwallet accounts and browser tabs", + "shell-placeholder-title": "Your open tabs will be here", + "show-address-qr": "Show address QR", + "show-encrypted-qr-of-key-pair": "Show encrypted QR of key pair", + "show-existing-keys": "Show Existing Keys", + "show-less": "Show less", + "show-more": "Show more", + "show-notifications": "Show notifications", + "show-profile-pictures": "See profile pictures from", + "show-profile-pictures-to": "Show your profile picture to", + "show-qr": "Show QR code", + "show-transaction-data": "Show transaction data", + "showcase": "Showcase", + "sign transactions": "sign transactions", + "sign-and-send": "Sign and send", + "sign-anyway": "Sign anyway", + "sign-in": "Sign in", + "sign-in-by-syncing": "Sign in by syncing", + "sign-message": "Sign Message", + "sign-out": "Sign out", + "sign-request-failed": "Could not sign message", + "sign-up": "Sign up", + "sign-with": "Sign with", + "sign-with-password": "Sign with password", + "sign-you-in": "Signing you in…", + "signing": "Signing", + "signing-a-message": "Signing a message", + "signing-in-from-another-device": "Signing in from the other device", + "signing-phrase": "Signing phrase", + "simple": "Simple", + "skip": "Skip", + "slide-to-bridge": "Slide to bridge", + "slide-to-create-account": "Slide to create account", + "slide-to-import": "Slide to import", + "slide-to-remove-key-pair": "Slide to remove key pair", + "slide-to-request-to-join": "Slide to request to join", + "slide-to-reveal-code": "Slide to reveal code", + "slide-to-reveal-qr-code": "Slide to reveal QR code", + "slide-to-send": "Slide to send", + "slide-to-sign": "Slide to sign", + "slide-to-swap": "Slide to swap", + "slippage": "Slippage", + "slippage-may-be-higher-than-necessary": "Slippage may be higher than necessary", + "slippage-settings": "Slippage settings", + "slippage-settings-description": "Your transaction will revert if the price changes more than the slippage percentage", + "slippage-should-be-more-than-0": "Slippage should be more than 0", + "slow": "Slow", + "something-about-you": "Something about you", + "something-went-wrong": "Something went wrong", + "soon": "Soon", + "sort-communities": "Sort communities", + "special-characters": "Special characters", + "specify-address": "Specify address", + "specify-name": "Specify a name", + "specify-network-id": "Specify network id", + "specify-server-public-key": "Enter server public key", + "specify-symbol": "Specify a symbol", + "start-chat": "Start chat", + "start-conversation": "Start conversation", + "start-group-chat": "Start group chat", + "start-new-chat": "Start new chat", + "start-using-status": "Start using Status", + "start-with-space": "Cannot start with space", + "starter-pack-coming": "Starter Pack coming your way", + "starter-pack-coming-description": "Can take a few minutes to hours", + "starter-pack-received": "Starter Pack received", + "starter-pack-received-description": "Here’s some crypto to get you started! Use it to get stickers, an ENS name and try dapps", + "status": "Status", + "status-always-online": "Always Online", + "status-automatic": "Automatic", + "status-automatic-subtitle": "Set status automatically", + "status-confirmed": "Confirmed", + "status-dnd": "Do not disturb", + "status-dnd-subtitle": "Mutes all notifications", + "status-hardwallet": "Status hardwallet", + "status-help": "Status Help", + "status-inactive": "Inactive", + "status-inactive-subtitle": "Hides your online status", + "status-is-a-secure-messaging-app": "Status is a secure messaging app, crypto wallet and web3 browser built with the state of the art technology", + "status-is-open-source": "Status is open-source", + "status-keycard": "Status Keycard", + "status-mobile-descr": "Status tends to use a lot of data when syncing chats. You can choose not to sync when on mobile network", + "status-not-sent-click": "Not confirmed. Click for options", + "status-not-sent-tap": "Not confirmed. Tap for options", + "status-pending": "Pending", + "status-sent": "Sent", + "status-tx-not-found": "TX not found", + "status-updates-descr": "Status updates will appear here. Add the profile as a contact to receive updates on your timeline.", + "statuses-descr": "Share what’s on your mind and stay updated with your contacts", + "statuses-my-status-descr": "Share what’s on your mind. Anyone visiting your profile will be able to see your status. People who add you as their contact will receive your updates on their timeline", + "step-i-of-n": "Step {{step}} of {{number}}", + "sticker": "Sticker", + "sticker-market": "Sticker market", + "storage": "Storage", + "store-confirmations": "Store confirmations", + "strength-divider-okay-label": "Okay", + "strength-divider-strong-label": "Strong", + "strength-divider-very-strong-label": "Very strong", + "strength-divider-very-weak-label": "Very weak", + "strength-divider-weak-label": "Weak", + "strikethrough": "Strikethrough", + "su": "Su", + "submit": "Submit", + "submit-bug": "Submit a bug", + "success": "Success", + "successful-connection": "Successful Connection", + "suggested-min-tip": "Suggested min. tip", + "suggested-price-limit": "Suggested price limit", + "sun": "Sun", + "swap": "Swap", + "swap-details": "Swap details", + "swaps-powered-by": "Swaps powered by {{provider}}", + "switch-to-simple-interface": "Switch to simple interface", + "symbol": "Symbol", + "sync-all-devices": "Sync all devices", + "sync-code": "Sync Code", + "sync-code-generated": "Sync code generated", + "sync-devices-complete-sub-title": "Your devices are now in sync", + "sync-devices-complete-title": "Device sync complete!", + "sync-devices-error-sub-title": "Make sure both devices are powered on and connected to the internet.", + "sync-devices-error-title": "Oops, something’s wrong", + "sync-devices-result-sub-title": "Your devices are now in sync", + "sync-devices-sub-title": "Please keep both devices switched on and connected to the internet until sync is complete", + "sync-devices-title": "Syncing devices...", + "sync-in-progress": "Syncing...", + "sync-new-device": "Sync new device", + "sync-or-recover-profile": "Sync or recover profile", + "sync-settings": "Sync settings", + "sync-synced": "In sync", + "synced-devices": "Synced Devices", + "synced-with": "Synced with", + "synchronise-your-data-across-your-devices": "Synchronise your data across your devices", + "syncing": "Syncing", + "syncing-devices": "Syncing...", + "system": "System", + "tabs": "Tabs", + "tag-was-lost": "Tag was lost", + "tap": "Tap", + "tap-card-again": "Tap the card to the back of your phone again", + "tap-on": "Tap on", + "terms-of-service": "Terms of Use", + "testnet-mode": "Testnet mode", + "testnet-mode-disable-description": "Are you sure you want to turn off Testnet mode? All future transactions will be performed on live networks with real funds.", + "testnet-mode-enable-description": "In this mode, all blockchain data displayed will come from testnets and all blockchain interactions will be with testnets.\nTestnet mode switches the entire app to using testnets only. Please switch this mode on only if you know exactly why you need to use it.", + "testnet-mode-enabled": "Testnet mode enabled", + "testnet-mode-prompt-content": "You are about to switch the network mode. This will log you out and you will have to login again.", + "testnet-mode-prompt-title": "Warning!", + "testnet-not-available": "Testnet not available", + "text-input-disabled": "Please wait a moment...", + "th": "Th", + "thank-you": "Thank you", + "the-key-pair-and-derived-accounts-will-be-removed": "The key pair and derived accounts will be removed from all of your synced devices. Make sure you have a backup of your keys or seed phrase before proceeding.", + "this-account-has-no-activity": "This account has no activity", + "this-address-has-activity": "This address has activity", + "this-address-has-no-activity": "This address has no activity", + "this-address-is-already-saved": "This address is already saved", + "this-device": "This device", + "this-device-desc": "Your keys will be encrypted and securely stored on your device", + "this-ens-name-is-not-registered-yet": "This ENS name is not registered yet", + "this-is-not-an-eth-address-or-ens-name": "This is not an Ethereum address or ENS name", + "this-is-you-signing": "This is your signing phrase", + "this-message-was-deleted": "This message was deleted", + "this-qr-does-not-contain-any-missing-key-pair": "This QR does not contain any missing key pairs", + "this-qr-does-not-contain-key-pair": "This QR does not contain {{name}} key pair", + "this-will-take-few-seconds": "This will take a few seconds", + "three-days": "Three days", + "three-months-int": "3 months", + "three-words-description": "You should see these 3 words before signing each transaction", + "three-words-description-2": "If you see a different combination, cancel the transaction and sign out", + "thu": "Thu", + "time-in-mins": "{{minutes}} min", + "timeline": "Timeline", + "tip-cap": "Tip cap", + "to": "to", + "to-block": "Block", + "to-capitalized": "To", + "to-enable-biometric": "To enable {{bio-type-label}}, you must save your password on the unlock screen", + "to-encrypt-enter-password": "To encrypt the account please enter your password", + "to-label": "To", + "to-pair-with-your-other-device-in-the-network": "To pair with your other device in the network", + "to-scan-a-qr-enable-your-camera": "To scan a QR, enable your camera", + "to-see-this-message": "To see this message,", + "token": "Token", + "token-auto-validate-decimals-error": "Wrong decimals for token {{symbol}} at address {{address}} - set to {{expected}} but detected as {{actual}}", + "token-auto-validate-name-error": "Wrong name for token {{symbol}} at address {{address}} - set to {{expected}} but detected as {{actual}}", + "token-auto-validate-symbol-error": "Wrong symbol for token {{symbol}} at address {{address}} - set to {{expected}} but detected as {{actual}}", + "token-details": "Token details", + "token-gated-communities": "Token gated communities", + "token-gated-communities-info": "Here will be something relevant about this topic. This will help the user get more context and therefore have a better understanding of it.", + "token-master": "Token Master", + "token-not-available-on-networks": "{{token-symbol}} is not available on {{networks}}.", + "token-not-available-on-receiver-networks": "{{token-symbol}} is not available on the recipient’s desired networks. Proceed with caution.", + "token-owner": "Token Owner", + "token-price": "Token Price", + "topic-name-error": "Use only lowercase letters (a to z), numbers & dashes (-). Do not use chat keys", + "total-gas": "Total gas", + "total-members": "Total members", + "traits": "Traits", + "transacation-finalised": "Transaction finalised!", + "transaction": "Transaction", + "transaction-confirmed": "Transaction confirmed!", + "transaction-data": "Transaction data", + "transaction-declined": "Transaction declined", + "transaction-description": "Consider it complete after 12 confirmations on the network.", + "transaction-details": "Transaction details", + "transaction-failed": "Transaction failed", + "transaction-fee": "Transaction fee", + "transaction-history": "Transaction history", + "transaction-request": "Transaction Request", + "transaction-sent": "Transaction sent", + "transaction-signed": "The transaction has been successfully signed", + "transactions": "Transactions", + "transactions-filter-select-all": "Select all", + "transactions-filter-title": "Filter history", + "transactions-history": "Transaction history", + "transactions-history-empty": "No transactions in your history yet", + "transactions-history-loading": "Loading transaction history. This might take a while.", + "transactions-load-more": "Load more", + "transactions-management-enabled": "Transaction management (alpha)", + "transactions-sign": "Sign", + "transfer-ma-unknown-error-desc-1": "It looks like your multiaccount was not deleted. Database may have been reset", + "transfer-ma-unknown-error-desc-2": "Please check your account list and try again. If the account is not listed go to Access existing keys to recover with seed phrase", + "transfers-fetching-failure": "Transfers history could not be updated. Check your connection and pull down to try again", + "tribute-required-by-multiaccount": "{{multiaccount-name}} requires SNT to start a chat.", + "tribute-state-paid": "Tribute paid", + "tribute-state-pending": "Tribute pending", + "tribute-state-required": "Requires {{snt-amount}} SNT tribute", + "tribute-to-talk": "Tribute to talk", + "tribute-to-talk-add-friends": "Add friends as a contact to allow chats without tribute payment.", + "tribute-to-talk-are-you-friends": "Are you friends?", + "tribute-to-talk-ask-to-be-added": "Ask to be added as a contact", + "tribute-to-talk-contact-received-your-tribute": " received your tribute. You can now securely chat with each other.", + "tribute-to-talk-desc": "Monetize your attention by requiring SNT for new people to start a chat", + "tribute-to-talk-disabled": "Tribute to Talk disabled", + "tribute-to-talk-disabled-note": "From now on, new people can start a chat with you without sending SNT.", + "tribute-to-talk-enabled": "You have Tribute to Talk enabled.", + "tribute-to-talk-finish-desc": "From now on, you will only receive chats from contacts, and people who paid ", + "tribute-to-talk-learn-more-1": "Your time and attention are your most valuable assets. Tribute to Talk lets you set an amount of SNT required for new people to start a chat with you.", + "tribute-to-talk-learn-more-2": "Anyone who is not in your contact list will be asked to pay, and you can respond once they have.", + "tribute-to-talk-learn-more-3": "You can always send the money back, but to ensure that friends can reach you freely, add them as a contact first.", + "tribute-to-talk-paywall-learn-more-1": "Our time and attention are our most valuable assets. Tribute to Talk lets you contact new people in exchange for an SNT payment.", + "tribute-to-talk-paywall-learn-more-2": "To start a chat with someone who has a tribute set, simply pay the required SNT and you will be added as a contact.", + "tribute-to-talk-paywall-learn-more-3": "If you know them, you can share your profile outside of Status to be added for free.", + "tribute-to-talk-pending": "Tribute pending confirmation", + "tribute-to-talk-pending-note": "Tribute transaction is pending confirmation on the network. You can check its status in transaction history", + "tribute-to-talk-removing-note": "Removing Tribute to Talk will allow new people to start a chat without sending SNT. Requires a transaction to be made.", + "tribute-to-talk-set-snt-amount": "Set the amount of SNT required for new people to start a chat", + "tribute-to-talk-signing": "Waiting to sign transaction", + "tribute-to-talk-transaction-failed-note": "Transaction has failed and your Tribute to Talk settings have not been changed", + "tribute-to-talk-tribute-received1": "Tribute received. You and ", + "tribute-to-talk-tribute-received2": " are now contacts and can securely chat with each other.", + "tribute-to-talk-you-require-snt": "You require SNT for new people to start a chat.", + "trip-accounts": "Trip accounts", + "try-again": "Try again", + "try-keeping-the-card-still": "Try keeping the card still", + "try-to-search-something-else": "Try to search something else", + "try-your-luck-again": "Try your luck again!", + "tu": "Tu", + "tue": "Tue", + "turn-nfc-description": "NFC is disabled on yor device. You can enable it in settings", + "turn-nfc-on": "Turn NFC on to continue", + "turn-off-testnet-mode": "Turn off testnet mode", + "turn-on-testnet-mode": "Turn on testnet mode", + "two-minutes": "two minutes", + "tx-fail-description1": "This transaction is likely to fail. Sign at your own risk using custom network fee.", + "tx-fail-description2": "This transaction is likely to fail. Set a custom network fee to sign at your own risk.", + "type": "Type", + "type-a-message": "Message", + "type-nickname": "Type nickname", + "type-pairing-code": "Type or paste pairing code", + "type-slippage": "Type slippage", + "type-some-chat-key": "zQ3...1sgt5N", + "type-something": "Type something", + "type-your-password": "Type your password", + "type-your-path": "Type your own derivation path", + "ulc-enabled": "ULC enabled", + "unable-to-fetch": "Unable to fetch chat history", + "unable-to-read-this-code": "Unable to read this code", + "unable-to-send-messages": "Unable to send and receive messages", + "unblock": "Unblock", + "unblock-contact": "Unblock this user", + "unblocking-a-user-message": "After unblocking {{username}}, you will be able to connect with them as a contact and see their messages in group chats and communities.", + "undo": "Undo", + "unique-identifiers": "Unique profile identifiers", + "universally-unique-identifiers-of-device": "Universally Unique Identifiers of device", + "unknown": "Unknown", + "unknown-status-go-error": "Unknown status-go error", + "unlimited": "Unlimited", + "unlock": "Unlock", + "unmute": "Unmute", + "unmute-channel": "Unmute channel", + "unmute-chat": "Unmute chat", + "unmute-community": "Unmute community", + "unmute-group": "Unmute group", + "unpair": "Unpair", + "unpair-card": "Unpair card", + "unpair-card-confirmation": "This operation will unpair card from current device. Requires 6-digit passcode authorization. Do you want to proceed?", + "unpair-device": "Unpair device?", + "unpair-device-description": "This device will no longer synchronize with {{name}} until you pair them again.", + "unpair-device-toast": "Device successfully unpaired", + "unpair-keycard": "Unpair Keycard from this phone", + "unpair-keycard-warning": "Your pairing code/PUK & PIN remain unchanged", + "unpaired-keycard-text": "The Keycard you tapped is not associated with this phone", + "unpaired-keycard-title": "Looks like your card has been unpaired", + "unpin": "Unpin", + "unpin-from-channel": "Unpin from the channel", + "unpin-from-chat": "Unpin from the chat", + "unread": "Unread", + "unsupported-file": "Unsupported file", + "until": "until", + "until-you-turn-it-back-on": "you turn it back on", + "untrustworthy": "Untrustworthy", + "update": "Update", + "update-account-name": "Update account name", + "update-nickname-title": "Update nickname", + "update-to-listen-audio": "Update to latest version to listen to an audio message here!", + "update-to-see-image": "Update to latest version to see a nice image here!", + "update-to-see-sticker": "Update to latest version to see a nice sticker here!", + "updates-to-tos": "Updates to Terms of Use", + "updates-to-tos-desc": "Before you continue, please review the Terms of Use and confirm you take full responsibility for how you use the app.", + "url": "URL", + "usd-currency": "USD", + "use-as-profile-picture": "Use as profile picture", + "use-biometrics": "Use biometrics to fill in your password", + "use-keycard": "Use Keycard", + "use-keycard-subtitle": "Keys will be stored on your Keycard", + "use-photo": "Use Photo", + "use-recovery-phrase": "Use recovery phrase", + "use-recovery-phrase-subtitle": "If you already have an Ethereum address", + "use-the-multichain-wallet": "Use the leading multi-chain self-custodial wallet", + "use-valid-contact-code": "Please enter or scan a valid chat key or username", + "user-blocked": "{{username}} blocked", + "user-deleted-a-message": "{{user}} deleted a message", + "user-found": "User found", + "user-keycard": "{{name}} Card", + "user-keypair": "{{name}}'s Key pair", + "user-pinned-a-message": "{{user}} pinned a message", + "user-replied": "{{user}} replied", + "user-sent-a-gif": "{{user}} sent a GIF", + "user-sent-a-photo": "{{user}} sent a photo", + "user-sent-a-sticker": "{{user}} sent a Sticker", + "user-sent-audio-message": "{{user}} sent audio message", + "user-sent-n-photos": "{{user}} sent {{number}} photos", + "user-shared-a-community": "{{user}} shared a community", + "user-unblocked": "{{username}} unblocked", + "valid-for-time": "Valid for {{valid-for}}", + "validation-amount-invalid-number": "Amount is not a valid number", + "validation-amount-is-too-precise": "Amount is too precise. Max number of decimals is {{decimals}}.", + "value-higher-than-send-amount": "This value is higher than entered amount to send", + "verified-community": "✓ Verified community", + "version": "App version", + "via": "via", + "view": "View", + "view-address-on-arbiscan": "View address on Arbiscan", + "view-address-on-etherscan": "View address on Etherscan", + "view-address-on-optimistic": "View address on Optimistic", + "view-channel-members-and-details": "View channel members and details", + "view-community-rules": "View Community Rules", + "view-cryptokitties": "View in CryptoKitties", + "view-cryptostrikers": "View in CryptoStrikers", + "view-data": "View data", + "view-details": "View Details", + "view-etheremon": "View in Etheremon", + "view-gitcoin": "View in Gitcoin", + "view-members": "View members", + "view-on-arb": "View on Arbiscan", + "view-on-eth": "View on Etherscan", + "view-on-oeth": "View on Optimism Explorer", + "view-on-opensea": "View on OpenSea", + "view-pinned-messages": "View pinned messages", + "view-profile": "View profile", + "view-public-dashboard": "View public dashboard", + "view-rules": "View rules", + "view-signing": "View signing phrase", + "view-superrare": "View in SuperRare", + "view-token-gating": "View token requirements", + "visit-dapp": "Visit dApp", + "vote-to-feature": "Vote to feature this community", + "waiting-for-wifi": "No Wi-fi, message syncing disabled.", + "waiting-for-wifi-change": "Settings", + "waiting-to-sign": "Waiting to sign transaction...", + "waiting-wi-fi": "Waiting for Wi-Fi…", + "waku-bloom-filter-mode": "Waku bloom filter mode", + "wakuv2-change-nodes": "Are you sure you want to change Wakuv2 nodes?", + "wakuv2-node-format": "/ip4/{node-ip}/tcp/{port}/p2p/{id}", + "wakuv2-settings": "Waku v2 settings", + "wallet": "Wallet", + "wallet-address": "Wallet address", + "wallet-asset": "Asset", + "wallet-assets": "Assets", + "wallet-backup-recovery-title": "Back up your seed phrase", + "wallet-choose-recipient": "Choose Recipient", + "wallet-collectibles": "Collectibles", + "wallet-connect": "Wallet Connect", + "wallet-connect-2.0": "Wallet Connect 2.0", + "wallet-connect-app-connected": "is connected", + "wallet-connect-go-back": "Go back to your browser or dapp", + "wallet-connect-label": "WalletConnect", + "wallet-connect-networks-not-supported": "{{dapp}} requires an unsupported network.", + "wallet-connect-proposal-description": "By connecting you allow {{name}} to retrieve your account address and enable Web3", + "wallet-connect-proposal-title": "Would like to connect with your wallet", + "wallet-connect-qr-expired": "WalletConnect QR has expired", + "wallet-connect-send-transaction-header": "wants you to send this transaction with", + "wallet-connect-send-transaction-warning": "Send transactions only if you trust the dApp", + "wallet-connect-sign-message-header": "wants you to sign the message with", + "wallet-connect-sign-message-warning": "Sign messages only if you trust the dApp", + "wallet-connect-sign-transaction-header": "wants you to sign this transaction with", + "wallet-connect-sign-transaction-warning": "Sign transactions only if you trust the dApp", + "wallet-connect-sign-warning": "Sign only if you trust the dApp", + "wallet-connect-version-not-supported": "WalletConnect version {{version}} is not supported", + "wallet-connect-via": "via", + "wallet-connect-wrong-qr": "It’s not a WalletConnect QR", + "wallet-insufficient-funds": "Insufficient funds", + "wallet-insufficient-gas": "Not enough ETH for gas", + "wallet-invalid-address": "Invalid address: \n {{data}}", + "wallet-invalid-address-checksum": "Error in address: \n {{data}}", + "wallet-invalid-chain-id": "Network does not match: \n {{data}} but current chain is {{chain}}", + "wallet-key-content": "A 64 character hex address based on the Ethereum standard and beginning with 0x. Public-facing, your account address is shared with others when you want to receive funds. Also referred to as an “Ethereum address” or “wallet address.”", + "wallet-key-title": "Account address", + "wallet-manage-accounts": "Manage accounts", + "wallet-manage-app-connections": "Manage app connections", + "wallet-manage-assets": "Manage assets", + "wallet-request": "Request", + "wallet-send": "Send", + "wallet-send-min-units": "Min 21000 units", + "wallet-send-min-wei": "Min 1 wei", + "wallet-settings": "Wallet settings", + "wallet-total-value": "Total value", + "wallet-transaction-total-fee": "Total Fee", + "wants-to-access-profile": "wants to access to your profile", + "wants-to-join": "wants to join", + "warning": "Warning", + "warning-message": "Sorry, we limit sending several messages in quick succession to prevent spam. Please try again in a moment", + "warning-sending-to-contract-descr": "The address you entered is a smart contract, sending funds to this address may result in loss of funds. To interact with a DApp, open the DApp in the Status DApp Browser.", + "watch-only": "Watch-only", + "watched-account-removed": "Watched address has been removed", + "watched-address": "Watched address", + "wc-brand-guide": "Guidance on using branding such as trademarks and logos", + "wc-disclaimer": "Disclaimers (including third party providers), warranties, and legal releases", + "wc-dispute": "Dispute resolution provisions", + "wc-how-to-use-status-app": "How to use the Status app including privacy and security", + "wc-new-tos-based-on-principles-prefix": "New Terms of Use designed based on our", + "we": "We", + "web-view-error": "Unable to load page", + "websites": "Websites", + "webview-camera-permission-requests": "Webview camera permission requests", + "webview-camera-permission-requests-subtitle": "When enabled, websites and dapps can ask to use your camera", + "wed": "Wed", + "welcome-back": "Welcome back!", + "welcome-blank-community-message": "Your communities will appear here.", + "welcome-blank-message": "Your chats will appear here. To start new chats press the ⊕ button", + "welcome-community-blank-message": "Your channels will appear here. To create a new channel, click on the ⊕ button and select \"Create a channel\"", + "welcome-community-blank-message-edit-chats": "Your channels will appear here. To create a new channel, go back to the community screen, click on the ⊕ button and select \"Create a channel\"", + "welcome-screen-text": "Set up your wallet, invite friends to chat\n and browse popular dapps!", + "welcome-to-status": "Welcome to Status!", + "welcome-to-status-description": "Set up your crypto wallet, invite friends to chat and browse decentralized apps", + "welcome-to-web3": "Welcome to web3!", + "welcome-to-web3-sub-title": "What are you waiting for? Go explore!", + "what-are-you-waiting-for": "What are you waiting for?", + "what-changed": "What changed", + "what-is-shared": "What is shared", + "what-we-will-receive": "What we will receive:", + "what-we-wont-receive": "What we won't receive:", + "whats-on-your-mind": "What’s on your mind…", + "who-are-you-looking-for": "Who are you looking for ?", + "with-full-encryption": "With full metadata privacy and e2e encryption", + "word-count": "Word count", + "word-n": "Word #{{number}}", + "word-n-description": "In order to check if you have backed up your seed phrase correctly, enter the word #{{number}} above.", + "words-n": { + "one": "1 word", + "other": "{{count}} words" + }, + "write-down-and-store-securely": "Write codes down\n & store them securely", + "wrong-address": "Wrong address", + "wrong-card": "Wrong card", + "wrong-card-text": "Tapped card does not correspond to the keys you selected", + "wrong-contract": "Wrong contract", + "wrong-keycard-text": "The Keycard you tapped is not associated with this phone", + "wrong-keycard-title": "Looks like you’ve tapped \na wrong keycard", + "wrong-password": "Wrong password", + "wrong-word": "Wrong word", + "x-counter": "x{{counter}}", + "yes": "Yes", + "You": "You", + "you": "you", + "you-already-have-an-asset": "You already have an asset {{value}}", + "you-already-use-status": "You already use Status", + "you-are-a": "You’re a", + "you-are-a-role": "You’re a {{role}}", + "you-are-all-set": "You’re all set!", + "you-are-all-set-description": "If you lose your phone, you can now access your funds and chat key using your seed phrase", + "you-can-change-account": "You can change the account name and color to what you wish", + "you-can-choose-preview-websites": "You can choose which of the following websites can preview link of descriptions and pictures in chats", + "you-can-fetch": "You can fetch chat history", + "you-canceled-the-request": "You canceled the request to join", + "you-cannot-add-your-own-account-as-a-saved-address": "You cannot add your own account as a saved address", + "you-colon": "You:", + "you-deleted-a-message": "You deleted a message", + "you-dont-have-contacts": "You don’t have any contacts yet.", + "you-dont-have-contacts-invite-friends": "You don’t have any contacts yet.\nInvite your friends to start chatting.", + "you-dont-have-stickers": "You don’t have any stickers yet", + "you-eligible-to-join": "You’re eligible to join", + "you-eligible-to-join-as": "You’re eligible to join as {{role}}", + "you-have-no-contacts": "You have no contacts", + "you-hodl": "You hodl:", + "you-hold-number-of-hold-tokens-of-these": "You hold {{number-of-hold-tokens}} of these:", + "you-like-to-type-43-characters": "Do you like to type 43 characters?", + "you-must-always-hold": "You must always hold:", + "you-must-hold": "You must hodl:", + "you-must-now-hold": "You must now hold:", + "you-not-eligible-to-join": "You’re not eligible to join", + "you-not-eligible-to-join-as": "You’re not eligible to join as {{role}}", + "you-pinned-a-message": "You pinned a message", + "you-replied": "You replied", + "you-sent-a-gif": "You sent a GIF", + "you-sent-a-photo": "You sent a photo", + "you-sent-a-sticker": "You sent a Sticker", + "you-sent-audio-message": "You sent audio message", + "you-sent-n-photos": "You sent {{number}} photos", + "you-shared-a-community": "You shared a community", + "you-will-be-a": "You’ll be a", + "you-will-need-this-code": "You'll need this code to open Status and sign transactions", + "you-will-start-from-scratch": "You will start from scratch with a new set of keys", + "your-addresses": "Your addresses", + "your-answer": "Your answer", + "your-card-is-frozen": "Your Keycard is frozen. Reset card access", + "your-contact-code": "Granting access authorizes this DApp to retrieve your chat key", + "your-data-belongs-to-you": "If you lose your seed phrase you lose your data and funds", + "your-data-belongs-to-you-description": "If you lose access, for example by losing your phone, you can only access your keys with your seed phrase. No one, but you has your seed phrase. Write it down. Keep it safe", + "your-identifiers": "Your identicon ring, chat key and emojihash will help others tell you from impersonators", + "your-key-pair-name-is-too-long": "Your key pair name is too long", + "your-key-pair-name-is-too-short": "Your key pair name is too short", + "your-keys": "Your keys", + "your-name": "Your name", + "your-price-limit": "Your price limit", + "your-profile-information": "Your profile information", + "your-recovery-phrase": "Your seed phrase", + "your-recovery-phrase-description": "This is your seed phrase. You use it to prove that this is your wallet. You only get to see it once! Write it on paper and keep it in a secure place. You will need it if you lose or reinstall your wallet.", + "your-tip-limit": "Your tip limit", + "youre-on-mobile-network": "You’re on mobile network" } diff --git a/yarn.lock b/yarn.lock index e6e5fae548..8b648ded99 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9058,10 +9058,15 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= -prettier@^2.8.8: - version "2.8.8" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" - integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== +prettier-plugin-sort-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/prettier-plugin-sort-json/-/prettier-plugin-sort-json-4.0.0.tgz#b72a44b57183dd7f8cef5c07a0614f21d29a0e4a" + integrity sha512-zV5g+bWFD2zAqyQ8gCkwUTC49o9FxslaUdirwivt5GZHcf57hCocavykuyYqbExoEsuBOg8IU36OY7zmVEMOWA== + +prettier@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" + integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== pretty-format@^25.5.0: version "25.5.0" From 3b446963ef2a0ec8d51afa954b1354981e050f29 Mon Sep 17 00:00:00 2001 From: Omar Basem Date: Mon, 22 Jul 2024 14:16:40 +0400 Subject: [PATCH 64/79] fix: assets sort order on bridge and assets screen (#20813) * fix: assets sort order on bridge and assets screen (#20813) --- src/status_im/constants.cljs | 2 ++ src/status_im/contexts/wallet/common/utils.cljs | 10 +++++++--- src/status_im/contexts/wallet/common/utils_test.cljs | 9 +++++++++ src/status_im/subs/wallet/wallet.cljs | 2 +- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index 4299c6bb59..ccee37f4d9 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -293,6 +293,8 @@ (def ^:const transaction-pending-type-wallet-connect-transfer "WalletConnectTransfer") +(def ^:const token-sort-priority {"SNT" 1 "STT" 1 "ETH" 2 "DAI" 3}) + (def ^:const dapp-permission-contact-code "contact-code") (def ^:const dapp-permission-web3 "web3") (def ^:const dapp-permission-qr-code "qr-code") diff --git a/src/status_im/contexts/wallet/common/utils.cljs b/src/status_im/contexts/wallet/common/utils.cljs index 5232d1a4b9..65f3844f65 100644 --- a/src/status_im/contexts/wallet/common/utils.cljs +++ b/src/status_im/contexts/wallet/common/utils.cljs @@ -307,10 +307,14 @@ :color color :currency currency :currency-symbol currency-symbol})) - calculated-tokens (map calculate-token tokens) - token-priority {"SNT" 1 "STT" 1 "ETH" 2 "DAI" 3}] + calculated-tokens (map calculate-token tokens)] (sort-by (fn [token] (let [fiat-value (get-in token [:values :fiat-unformatted-value]) - priority (get token-priority (:token token) 999)] + priority (get constants/token-sort-priority (:token token) ##Inf)] [(- fiat-value) priority])) calculated-tokens))) + +(defn sort-tokens + [tokens] + (let [priority #(get constants/token-sort-priority (:symbol %) ##Inf)] + (sort-by (juxt (comp - :balance) priority) tokens))) diff --git a/src/status_im/contexts/wallet/common/utils_test.cljs b/src/status_im/contexts/wallet/common/utils_test.cljs index 281f8f06fd..f72f9bbb37 100644 --- a/src/status_im/contexts/wallet/common/utils_test.cljs +++ b/src/status_im/contexts/wallet/common/utils_test.cljs @@ -149,5 +149,14 @@ expected-order ["DAI" "ETH" "SNT"]] (is (= expected-order sorted-tokens)))))))) +(deftest sort-tokens-test + (testing "sort-tokens function" + (let [mock-tokens [{:symbol "ETH" :balance 5} + {:symbol "DAI" :balance 10} + {:symbol "SNT" :balance 1}] + sorted-tokens (map :symbol (utils/sort-tokens mock-tokens)) + expected-order ["DAI" "ETH" "SNT"]] + (is (= expected-order sorted-tokens))))) + diff --git a/src/status_im/subs/wallet/wallet.cljs b/src/status_im/subs/wallet/wallet.cljs index b8b9b8bc75..bebb790b90 100644 --- a/src/status_im/subs/wallet/wallet.cljs +++ b/src/status_im/subs/wallet/wallet.cljs @@ -416,7 +416,7 @@ :total-balance (utils/calculate-total-token-balance token chain-ids))) (:tokens account)) - sorted-tokens (sort-by :name compare tokens)] + sorted-tokens (utils/sort-tokens tokens)] (if query (let [query-string (string/lower-case query)] (filter #(or (string/starts-with? (string/lower-case (:name %)) query-string) From 4b7c906df27fd3bdf90dd80048cff5f87b139514 Mon Sep 17 00:00:00 2001 From: Brian Sztamfater Date: Mon, 22 Jul 2024 08:50:29 -0300 Subject: [PATCH 65/79] feat(swap): set spending cap screen (#20727) Signed-off-by: Brian Sztamfater --- resources/images/networks/Paraswap@2x.png | Bin 0 -> 3192 bytes resources/images/networks/Paraswap@3x.png | Bin 0 -> 5229 bytes .../approval_info/component_spec.cljs | 1 + .../list_items/approval_info/view.cljs | 2 +- src/quo/foundations/resources.cljs | 1 + src/status_im/constants.cljs | 5 +- .../wallet/swap/set_spending_cap/style.cljs | 40 +++ .../wallet/swap/set_spending_cap/view.cljs | 253 ++++++++++++++++++ .../wallet/swap/swap_proposal/view.cljs | 5 +- src/status_im/navigation/screens.cljs | 5 + translations/en.json | 5 + 11 files changed, 314 insertions(+), 3 deletions(-) create mode 100644 resources/images/networks/Paraswap@2x.png create mode 100644 resources/images/networks/Paraswap@3x.png create mode 100644 src/status_im/contexts/wallet/swap/set_spending_cap/style.cljs create mode 100644 src/status_im/contexts/wallet/swap/set_spending_cap/view.cljs diff --git a/resources/images/networks/Paraswap@2x.png b/resources/images/networks/Paraswap@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d37384748a98d34da4a507ed99d34189449bd040 GIT binary patch literal 3192 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 eA{AoegZ~57qYc!!;1OE@00004yNBY2$I4 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 + [data-item + {:title (i18n/label :t/network) + :subtitle (:full-name network) + :network-image (:source network)}] + [data-item + {:title (i18n/label :t/est-time) + :subtitle (i18n/label :t/time-in-mins {:minutes (str estimated-time-min)})}] + [data-item + {:title (i18n/label :t/max-fees) + :subtitle max-fees + :loading? loading-fees? + :size :small}]]]) + +(defn footer + [{:keys [estimated-time-min native-currency-symbol network theme account-color loading-fees?]}] + (let [native-token (when native-currency-symbol + (rf/sub [:wallet/token-by-symbol + native-currency-symbol])) + fee-formatted (rf/sub [:wallet/wallet-send-fee-fiat-formatted + native-token]) + on-auth-success (rn/use-callback #(js/alert "Not implemented yet"))] + [rn/view {:style {:margin-bottom -10}} + [transaction-details + {:estimated-time-min estimated-time-min + :max-fees fee-formatted + :network network + :loading-fees? loading-fees? + :theme theme}] + [standard-auth/slide-button + {:size :size-48 + :track-text (i18n/label :t/slide-to-swap) + :container-style {:z-index 2} + :customization-color account-color + :disabled? loading-fees? + :on-auth-success on-auth-success + :auth-button-label (i18n/label :t/confirm)}]])) + +(defn view + [] + (let [theme (quo.theme/use-theme) + swap-transaction-data (rf/sub [:wallet/swap]) + {:keys [asset-to-pay network pay-amount + providers swap-proposal + loading-fees?]} swap-transaction-data + estimated-time-min (:estimated-time swap-proposal) + pay-token-symbol (:symbol asset-to-pay) + pay-token-address (:address asset-to-pay) + native-currency-symbol (get-in swap-proposal [:from :native-currency-symbol]) + account (rf/sub [:wallet/current-viewing-account]) + account-color (:color account) + provider (first providers)] + [rn/view {:style style/container} + [floating-button-page/view + {:footer-container-padding 0 + :header [quo/page-nav + {:icon-name :i/close + :on-press navigation/navigate-back + :margin-top 8 + :background :blur + :accessibility-label :top-bar}] + :footer [footer + {:estimated-time-min estimated-time-min + :native-currency-symbol native-currency-symbol + :network network + :account-color account-color + :provider provider + :loading-fees? loading-fees? + :theme theme}] + :gradient-cover? true + :customization-color account-color} + [:<> + [swap-title + {:pay-token-symbol pay-token-symbol + :pay-amount pay-amount + :account account + :provider provider}] + [spending-cap-section + {:token-symbol pay-token-symbol + :amount pay-amount + :theme theme}] + [account-section + {:account account + :pay-token-symbol pay-token-symbol + :pay-token-amount pay-amount + :theme theme}] + [token-section + {:token-symbol pay-token-symbol + :token-address pay-token-address + :network-chain-id (:chain-id network) + :theme theme}] + [spender-contract-section + {:provider provider + :network-chain-id (:chain-id network) + :theme theme}]]]])) diff --git a/src/status_im/contexts/wallet/swap/swap_proposal/view.cljs b/src/status_im/contexts/wallet/swap/swap_proposal/view.cljs index 0354c63709..c0fab70cac 100644 --- a/src/status_im/contexts/wallet/swap/swap_proposal/view.cljs +++ b/src/status_im/contexts/wallet/swap/swap_proposal/view.cljs @@ -16,4 +16,7 @@ [quo/button {:on-press #(rf/dispatch [:navigate-to-within-stack [:screen/wallet.swap-confirmation :screen/wallet.swap-propasal]])} - "Swap confirmation"]])) + "Swap confirmation"] + [quo/button + {:on-press #(rf/dispatch [:open-modal :screen/wallet.swap-set-spending-cap])} + "Set spending cap"]])) diff --git a/src/status_im/navigation/screens.cljs b/src/status_im/navigation/screens.cljs index c0ab4609a9..a0c97cf949 100644 --- a/src/status_im/navigation/screens.cljs +++ b/src/status_im/navigation/screens.cljs @@ -118,6 +118,7 @@ [status-im.contexts.wallet.send.transaction-confirmation.view :as wallet-transaction-confirmation] [status-im.contexts.wallet.send.transaction-progress.view :as wallet-transaction-progress] [status-im.contexts.wallet.swap.select-asset-to-pay.view :as wallet-swap-select-asset-to-pay] + [status-im.contexts.wallet.swap.set-spending-cap.view :as wallet-swap-set-spending-cap] [status-im.contexts.wallet.swap.swap-confirmation.view :as wallet-swap-confirmation] [status-im.contexts.wallet.swap.swap-proposal.view :as wallet-swap-propasal] [status-im.contexts.wallet.wallet-connect.modals.send-transaction.view :as @@ -526,6 +527,10 @@ :options {:modalPresentationStyle :overCurrentContext} :component wallet-swap-confirmation/view} + {:name :screen/wallet.swap-set-spending-cap + :options {:sheet? true} + :component wallet-swap-set-spending-cap/view} + {:name :scan-profile-qr-code :options (merge options/dark-screen diff --git a/translations/en.json b/translations/en.json index 32cd4ae561..cf047a9325 100644 --- a/translations/en.json +++ b/translations/en.json @@ -18,6 +18,7 @@ "accepted": "Accepted", "access-existing-keys": "Access existing keys", "access-key": "Access key", + "account": "Account", "account-added": "Account added", "account-color": "Account color", "account-content": "You can compare accounts in Status to bank accounts. Like a bank account, an account typically has an address and a balance; You use this account to transact on Ethereum. You can have multiple accounts in your wallet. All accessed by unlocking Status.", @@ -1016,6 +1017,7 @@ "fleet-settings": "Fleet settings", "follow": "Follow", "follow-your-interests": "Jump into a public chat and meet new people", + "for": "for", "for-airdrops": "For airdrops", "forgot-password": "Forgot password?", "forgot-your-password-info-create-new-password": "Create a new password", @@ -2189,6 +2191,7 @@ "set-dapp-access-permissions": "Set DApp access permissions", "set-max": "Set max", "set-nickname-toast": "You have renamed {{primary-name}} as {{nickname}}", + "set-spending-cap-of": "Set spending cap of", "set-up-sync": "Set up sync", "settings": "Settings", "setup-group-chat": "Setup group chat", @@ -2285,6 +2288,8 @@ "specify-network-id": "Specify network id", "specify-server-public-key": "Enter server public key", "specify-symbol": "Specify a symbol", + "spender-contract": "Spender contract", + "spending-cap": "Spending cap", "start-chat": "Start chat", "start-conversation": "Start conversation", "start-group-chat": "Start group chat", From d623cc8444fd72d28a60cc09b2d535a86bcfe20e Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 22 Jul 2024 14:57:45 +0200 Subject: [PATCH 66/79] Only show the Wallet Connect requests for the logged in user (#20815) Co-authored-by: Lungu Cristian --- .../contexts/wallet/wallet_connect/core.cljs | 16 ++++++++++++++++ .../contexts/wallet/wallet_connect/events.cljs | 12 ++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/status_im/contexts/wallet/wallet_connect/core.cljs b/src/status_im/contexts/wallet/wallet_connect/core.cljs index 0bf0ffb1cc..e8a0196dfc 100644 --- a/src/status_im/contexts/wallet/wallet_connect/core.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/core.cljs @@ -127,3 +127,19 @@ :url (get-in session [:peer :metadata :url]) :accounts (get-in session [:namespaces :eip155 :accounts]) :disconnected false}) + +(defn filter-operable-accounts + [accounts] + (filter #(and (:operable? %) + (not (:watch-only? %))) + accounts)) + +(defn filter-sessions-for-account-addresses + [account-addresses sessions] + (filter (fn [{:keys [accounts]}] + (some (fn [account] + (some (fn [account-address] + (clojure.string/includes? account account-address)) + account-addresses)) + accounts)) + sessions)) diff --git a/src/status_im/contexts/wallet/wallet_connect/events.cljs b/src/status_im/contexts/wallet/wallet_connect/events.cljs index d69f7a20ac..3e54f1d156 100644 --- a/src/status_im/contexts/wallet/wallet_connect/events.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/events.cljs @@ -55,9 +55,7 @@ (log/info "Received Wallet Connect session proposal: " {:id (:id proposal)}) (let [accounts (get-in db [:wallet :accounts]) current-viewing-address (get-in db [:wallet :current-viewing-account-address]) - available-accounts (filter #(and (:operable? %) - (not (:watch-only? %))) - (vals accounts)) + available-accounts (wallet-connect-core/filter-operable-accounts (vals accounts)) networks (wallet-connect-core/get-networks-by-mode db) session-networks (wallet-connect-core/proposal-networks-intersection proposal networks) @@ -204,9 +202,15 @@ :wallet-connect/fetch-active-sessions-success (fn [{:keys [db now]} [sessions]] (let [persisted-sessions (:wallet-connect/sessions db) + account-addresses (->> (get-in db [:wallet :accounts]) + vals + wallet-connect-core/filter-operable-accounts + (map :address)) sessions (->> (js->clj sessions :keywordize-keys true) vals - (map wallet-connect-core/sdk-session->db-session)) + (map wallet-connect-core/sdk-session->db-session) + (wallet-connect-core/filter-sessions-for-account-addresses + account-addresses)) expired-sessions (remove (fn [{:keys [expiry]}] (> expiry (/ now 1000))) From 08b65cbcb7f8496b227bfb206f5c57f11f1f9c90 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 22 Jul 2024 15:20:52 +0200 Subject: [PATCH 67/79] Proper handling of disconnection, both from the dapp itself and from the list of connected dapps (#20817) --- src/react_native/wallet_connect.cljs | 13 ++++---- src/status_im/constants.cljs | 1 + .../contexts/wallet/connected_dapps/view.cljs | 33 +++++++++---------- .../wallet/wallet_connect/effects.cljs | 6 ++-- .../wallet/wallet_connect/events.cljs | 26 ++++++++------- 5 files changed, 43 insertions(+), 36 deletions(-) diff --git a/src/react_native/wallet_connect.cljs b/src/react_native/wallet_connect.cljs index cd2a1c2625..5001f2d7e4 100644 --- a/src/react_native/wallet_connect.cljs +++ b/src/react_native/wallet_connect.cljs @@ -62,16 +62,17 @@ (bean/->js {:id id :namespaces approved-namespaces}))) +(defn disconnect-session + [{:keys [web3-wallet reason topic]}] + (oops/ocall web3-wallet + "disconnectSession" + (bean/->js {:topic topic + :reason reason}))) + (defn get-active-sessions [web3-wallet] (oops/ocall web3-wallet "getActiveSessions")) -(defn core-pairing-disconnnect - [web3-wallet topic] - (oops/ocall web3-wallet - "core.pairing.disconnect" - (bean/->js {:topic topic}))) - (defn core-pairing-pair [web3-wallet url] (oops/ocall web3-wallet diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index 44ee9bc1a4..44d687d7a8 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -290,6 +290,7 @@ (def ^:const wallet-connect-session-request-event "session_request") (def ^:const wallet-connect-session-delete-event "session_delete") (def ^:const wallet-connect-user-rejected-error-key "USER_REJECTED") +(def ^:const wallet-connect-user-disconnected-reason-key "USER_DISCONNECTED") (def ^:const transaction-pending-type-wallet-connect-transfer "WalletConnectTransfer") diff --git a/src/status_im/contexts/wallet/connected_dapps/view.cljs b/src/status_im/contexts/wallet/connected_dapps/view.cljs index 01d7ad5cea..ca72c9cccf 100644 --- a/src/status_im/contexts/wallet/connected_dapps/view.cljs +++ b/src/status_im/contexts/wallet/connected_dapps/view.cljs @@ -13,26 +13,25 @@ [utils.re-frame :as rf])) (defn- on-disconnect - [wallet-account {:keys [name topic pairing-topic]}] + [wallet-account {:keys [name topic]}] (rf/dispatch [:hide-bottom-sheet]) (rf/dispatch [:wallet-connect/disconnect-dapp - {:topic topic - :pairing-topic pairing-topic - :on-success (fn [] - (rf/dispatch [:toasts/upsert - {:id :dapp-disconnect-success - :type :positive - :text (i18n/label :t/disconnect-dapp-success - {:dapp name - :account (:name wallet-account)})}])) - :on-fail (fn [] - (rf/dispatch [:toasts/upsert - {:id :dapp-disconnect-failure - :type :negative - :text (i18n/label :t/disconnect-dapp-fail - {:dapp name - :account (:name wallet-account)})}]))}])) + {:topic topic + :on-success (fn [] + (rf/dispatch [:toasts/upsert + {:id :dapp-disconnect-success + :type :positive + :text (i18n/label :t/disconnect-dapp-success + {:dapp name + :account (:name wallet-account)})}])) + :on-fail (fn [] + (rf/dispatch [:toasts/upsert + {:id :dapp-disconnect-failure + :type :negative + :text (i18n/label :t/disconnect-dapp-fail + {:dapp name + :account (:name wallet-account)})}]))}])) (defn- on-dapp-disconnect-press [wallet-account dapp] diff --git a/src/status_im/contexts/wallet/wallet_connect/effects.cljs b/src/status_im/contexts/wallet/wallet_connect/effects.cljs index 94680770a4..b38b1a7fcc 100644 --- a/src/status_im/contexts/wallet/wallet_connect/effects.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/effects.cljs @@ -41,8 +41,10 @@ (rf/reg-fx :effects.wallet-connect/disconnect - (fn [{:keys [web3-wallet topic on-success on-fail]}] - (-> (wallet-connect/core-pairing-disconnnect web3-wallet topic) + (fn [{:keys [web3-wallet topic reason on-success on-fail]}] + (-> (wallet-connect/disconnect-session {:web3-wallet web3-wallet + :topic topic + :reason reason}) (promesa/then on-success) (promesa/catch on-fail)))) diff --git a/src/status_im/contexts/wallet/wallet_connect/events.cljs b/src/status_im/contexts/wallet/wallet_connect/events.cljs index 3e54f1d156..c5f9632308 100644 --- a/src/status_im/contexts/wallet/wallet_connect/events.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/events.cljs @@ -115,14 +115,16 @@ (rf/reg-event-fx :wallet-connect/disconnect-dapp - (fn [{:keys [db]} [{:keys [pairing-topic on-success on-fail]}]] + (fn [{:keys [db]} [{:keys [topic on-success on-fail]}]] (let [web3-wallet (get db :wallet-connect/web3-wallet)] {:fx [[:effects.wallet-connect/disconnect {:web3-wallet web3-wallet - :topic pairing-topic + :topic topic + :reason (wallet-connect/get-sdk-error + constants/wallet-connect-user-disconnected-reason-key) :on-fail on-fail :on-success (fn [] - (rf/dispatch [:wallet-connect/disconnect-session pairing-topic]) + (rf/dispatch [:wallet-connect/disconnect-session topic]) (when on-success (on-success)))}]]}))) @@ -211,12 +213,14 @@ (map wallet-connect-core/sdk-session->db-session) (wallet-connect-core/filter-sessions-for-account-addresses account-addresses)) - expired-sessions (remove - (fn [{:keys [expiry]}] - (> expiry (/ now 1000))) + session-topics (set (map :topic sessions)) + expired-sessions (filter + (fn [{:keys [expiry topic]}] + (or (< expiry (/ now 1000)) + (not (contains? session-topics topic)))) persisted-sessions)] - {:fx (mapv (fn [{:keys [pairingTopic]}] - [:wallet-connect/disconnect-session pairingTopic]) + {:fx (mapv (fn [{:keys [topic]}] + [:dispatch [:wallet-connect/disconnect-session topic]]) expired-sessions) :db (assoc db :wallet-connect/sessions sessions)}))) @@ -274,15 +278,15 @@ (rf/reg-event-fx :wallet-connect/disconnect-session - (fn [{:keys [db]} [pairing-topic]] + (fn [{:keys [db]} [topic]] {:db (update db :wallet-connect/sessions (fn [sessions] (->> sessions - (remove #(= (:pairingTopic %) pairing-topic)) + (remove #(= (:topic %) topic)) (into [])))) :fx [[:json-rpc/call [{:method "wallet_disconnectWalletConnectSession" - :params [pairing-topic] + :params [topic] :on-success #(log/info "Wallet Connect session disconnected") :on-error #(log/info "Wallet Connect session persistence failed" %)}]]]})) From 540094c2295958417baa5026f447dfcbf73b7fa3 Mon Sep 17 00:00:00 2001 From: Parvesh Monu Date: Mon, 22 Jul 2024 22:55:50 +0530 Subject: [PATCH 68/79] fix Missing 'Mark as read' option on community channels (#20838) --- .../contexts/communities/actions/chat/view.cljs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/status_im/contexts/communities/actions/chat/view.cljs b/src/status_im/contexts/communities/actions/chat/view.cljs index b747dad365..52e8772320 100644 --- a/src/status_im/contexts/communities/actions/chat/view.cljs +++ b/src/status_im/contexts/communities/actions/chat/view.cljs @@ -47,12 +47,11 @@ :label (i18n/label :t/view-token-gating)})) (defn- action-mark-as-read - [] - (when config/show-not-implemented-features? - {:icon :i/mark-as-read - :accessibility-label :chat-mark-as-read - :on-press not-implemented/alert - :label (i18n/label :t/mark-as-read)})) + [chat-id] + {:icon :i/mark-as-read + :accessibility-label :chat-mark-as-read + :on-press #(hide-sheet-and-dispatch [:chat.ui/mark-all-read-pressed chat-id]) + :label (i18n/label :t/mark-as-read)}) (defn- action-toggle-muted [id muted? muted-till chat-type] @@ -130,7 +129,7 @@ (and (not inside-chat?) (not locked?)) [quo/action-drawer [[(when-not hide-view-members? (action-view-members-and-details community-id chat-id)) - (action-mark-as-read) + (action-mark-as-read chat-id) (action-toggle-muted chat-id muted muted-till chat-type) (action-notification-settings) (action-pinned-messages chat-id) @@ -143,7 +142,7 @@ [[(action-view-members-and-details community-id chat-id) (when token-gated? (action-token-requirements)) - (action-mark-as-read) + (action-mark-as-read chat-id) (action-toggle-muted chat-id muted muted-till chat-type) (action-notification-settings) (when config/fetch-messages-enabled? From 1a4987478c275f3b8af18351a87a11a0086900f4 Mon Sep 17 00:00:00 2001 From: Parvesh Monu Date: Tue, 23 Jul 2024 16:09:04 +0530 Subject: [PATCH 69/79] fix UI elements are misplaced on the 'Sign in by syncing' screen when adding a new profile (#20837) --- src/status_im/contexts/syncing/scan_sync_code/view.cljs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/status_im/contexts/syncing/scan_sync_code/view.cljs b/src/status_im/contexts/syncing/scan_sync_code/view.cljs index fbcf311195..e35047661a 100644 --- a/src/status_im/contexts/syncing/scan_sync_code/view.cljs +++ b/src/status_im/contexts/syncing/scan_sync_code/view.cljs @@ -346,7 +346,8 @@ #(reset! camera-permission-granted? %) #(reset! camera-permission-granted? false))))) [:<> - background + [rn/view {:style style/absolute-fill} + background] (when camera-ready-to-scan? [render-camera {:torch-mode torch-mode From 3bb7c308e378ce8fc6e4d26c4a4fad8585edac1e Mon Sep 17 00:00:00 2001 From: Shivek Khurana Date: Tue, 23 Jul 2024 13:34:03 +0200 Subject: [PATCH 70/79] =?UTF-8?q?=F0=9F=A6=81=20Fix=20issues=20with=20miss?= =?UTF-8?q?ing=20dapp=20images=20and=20dapp=20names=20(#20811)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🦁 Beast mode * 🗑️ Remove `prn` calls * 🌎 Add utils to remove http:// prefix * ⏭️ Extracted name and avatar computation * 🧹 Cleanup * ✏️ Fix lint and remove REPL flow * ❌ Remove broken test * 🖊️ Fix lint --- src/quo/components/list_items/dapp/style.cljs | 9 +++++ src/quo/components/list_items/dapp/view.cljs | 13 +++++-- .../contexts/preview/quo/list_items/dapp.cljs | 17 +++++---- .../contexts/wallet/connected_dapps/view.cljs | 19 ++++++---- .../contexts/wallet/wallet_connect/core.cljs | 21 +++++++++++ .../modals/common/footer/view.cljs | 8 +++-- .../modals/common/header/view.cljs | 14 +++++--- .../session_proposal/style.cljs | 7 ++-- .../wallet_connect/session_proposal/view.cljs | 24 ++++++++----- src/status_im/subs/wallet/wallet_connect.cljs | 9 +++-- .../subs/wallet/wallet_connect_test.cljs | 35 +++++++++++++++++++ src/utils/string.cljs | 15 ++++++++ src/utils/string_test.cljs | 22 ++++++++++++ 13 files changed, 175 insertions(+), 38 deletions(-) diff --git a/src/quo/components/list_items/dapp/style.cljs b/src/quo/components/list_items/dapp/style.cljs index d9f67b62b4..d0592bf14b 100644 --- a/src/quo/components/list_items/dapp/style.cljs +++ b/src/quo/components/list_items/dapp/style.cljs @@ -47,3 +47,12 @@ (defn style-text-value [theme] {:color (colors/theme-colors colors/neutral-50 colors/white theme)}) + +(def initials-avatar-container + {:width 32 + :height 32}) + +(def image-avatar + {:width 32 + :height 32 + :border-radius 32}) diff --git a/src/quo/components/list_items/dapp/view.cljs b/src/quo/components/list_items/dapp/view.cljs index 0626158480..5a5fbdd7a6 100644 --- a/src/quo/components/list_items/dapp/view.cljs +++ b/src/quo/components/list_items/dapp/view.cljs @@ -1,5 +1,6 @@ (ns quo.components.list-items.dapp.view (:require + [quo.components.avatars.user-avatar.view :as user-avatar] [quo.components.list-items.dapp.style :as style] [quo.components.markdown.text :as text] [quo.theme :as quo.theme] @@ -20,9 +21,15 @@ :on-press-in on-press-in :on-press-out on-press-out} [rn/view {:style style/container-info} - [fast-image/fast-image - {:source (:avatar dapp) - :style {:width 32 :height 32}}] + (if (:avatar dapp) + [fast-image/fast-image + {:source (:avatar dapp) + :style style/image-avatar}] + [rn/view {:style style/initials-avatar-container} + [user-avatar/initials-avatar + {:full-name (:name dapp) + :size :small + :customization-color (:customization-color dapp)}]]) [rn/view {:style style/user-info} [text/text {:weight :semi-bold diff --git a/src/status_im/contexts/preview/quo/list_items/dapp.cljs b/src/status_im/contexts/preview/quo/list_items/dapp.cljs index f14e18f7fe..6530797f96 100644 --- a/src/status_im/contexts/preview/quo/list_items/dapp.cljs +++ b/src/status_im/contexts/preview/quo/list_items/dapp.cljs @@ -13,14 +13,17 @@ :value "Default"} {:key :active :value "Active"}]} - {:key :action + {:key :dapp :type :select - :options [{:key :none - :value "None"} - {:key :icon - :value "Icon"}]} - {:key :blur? - :type :boolean}]) + :options [{:value "With icon" + :key {:avatar (resources/get-dapp :coingecko) + :name "Coingecko" + :value "coingecko.com"}} + {:value "Without icon" + :key {:avatar nil + :name "Coingecko" + :value "coingecko.com" + :customization-color :blue}}]}]) (defn preview [] diff --git a/src/status_im/contexts/wallet/connected_dapps/view.cljs b/src/status_im/contexts/wallet/connected_dapps/view.cljs index ca72c9cccf..69d24e44bb 100644 --- a/src/status_im/contexts/wallet/connected_dapps/view.cljs +++ b/src/status_im/contexts/wallet/connected_dapps/view.cljs @@ -9,8 +9,10 @@ [status-im.common.resources :as resources] [status-im.contexts.wallet.connected-dapps.disconnect-dapp.view :as disconnect-dapp] [status-im.contexts.wallet.connected-dapps.style :as style] + [status-im.contexts.wallet.wallet-connect.core :as core] [utils.i18n :as i18n] - [utils.re-frame :as rf])) + [utils.re-frame :as rf] + [utils.string])) (defn- on-disconnect [wallet-account {:keys [name topic]}] @@ -84,7 +86,8 @@ {:keys [color] :as wallet-account} (rf/sub [:wallet/current-viewing-account]) sessions (rf/sub [:wallet-connect/sessions-for-current-account]) - theme (quo.theme/use-theme)] + theme (quo.theme/use-theme) + customization-color (rf/sub [:profile/customization-color])] [rn/view {:flex 1} [header {:title (i18n/label :t/connected-dapps) @@ -104,11 +107,13 @@ :content-container-style (style/dapps-list theme) :render-fn (fn [{:keys [topic pairingTopic name url iconUrl]}] [quo/dapp - {:dapp {:avatar iconUrl - :name name - :value url - :topic topic - :pairing-topic pairingTopic} + {:dapp {:avatar (core/compute-dapp-icon-path iconUrl + url) + :name (core/compute-dapp-name name url) + :value url + :topic topic + :pairing-topic pairingTopic + :customization-color customization-color} :accessibility-label (str "dapp-" topic) :state :default :action :icon diff --git a/src/status_im/contexts/wallet/wallet_connect/core.cljs b/src/status_im/contexts/wallet/wallet_connect/core.cljs index e8a0196dfc..1a2f994461 100644 --- a/src/status_im/contexts/wallet/wallet_connect/core.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/core.cljs @@ -4,6 +4,7 @@ [native-module.core :as native-module] [status-im.constants :as constants] [utils.security.core :as security] + [utils.string] [utils.transforms :as transforms])) (def method-to-screen @@ -143,3 +144,23 @@ account-addresses)) accounts)) sessions)) + +(defn compute-dapp-name + "Sometimes dapps have no name or an empty name. Return url as name in that case" + [name url] + (if (seq name) + name + (when (seq url) + (-> url + utils.string/remove-trailing-slash + utils.string/remove-http-prefix + string/capitalize)))) + +(defn compute-dapp-icon-path + "Some dapps have icons with relative paths, make paths absolute in those cases, send nil if icon is missing" + [icon-path url] + (when (and (seq icon-path) + (seq url)) + (if (string/starts-with? icon-path "http") + icon-path + (str (utils.string/remove-trailing-slash url) icon-path)))) diff --git a/src/status_im/contexts/wallet/wallet_connect/modals/common/footer/view.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/common/footer/view.cljs index 14e853eae1..ddfc222b1a 100644 --- a/src/status_im/contexts/wallet/wallet_connect/modals/common/footer/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/modals/common/footer/view.cljs @@ -1,6 +1,7 @@ (ns status-im.contexts.wallet.wallet-connect.modals.common.footer.view (:require [quo.core :as quo] [quo.foundations.colors :as colors] + [quo.theme] [react-native.core :as rn] [status-im.common.standard-authentication.core :as standard-authentication] [status-im.contexts.wallet.wallet-connect.modals.common.footer.style :as style] @@ -14,7 +15,8 @@ (defn view [{:keys [warning-label slide-button-text disabled?]} & children] - (let [{:keys [customization-color]} (rf/sub [:wallet-connect/current-request-account-details])] + (let [{:keys [customization-color]} (rf/sub [:wallet-connect/current-request-account-details]) + theme (quo.theme/use-theme)] [rn/view {:style style/content-container} (into [rn/view {:style style/data-items-container}] @@ -30,6 +32,8 @@ [rn/view {:style style/warning-container} [quo/text {:size :paragraph-2 - :style {:color colors/neutral-80-opa-70} + :style {:color (if (= theme :dark) + colors/white-opa-70 + colors/neutral-80-opa-70)} :weight :medium} warning-label]]])) diff --git a/src/status_im/contexts/wallet/wallet_connect/modals/common/header/view.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/common/header/view.cljs index 5a13a0401e..ab76aee8d5 100644 --- a/src/status_im/contexts/wallet/wallet_connect/modals/common/header/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/modals/common/header/view.cljs @@ -1,7 +1,10 @@ (ns status-im.contexts.wallet.wallet-connect.modals.common.header.view - (:require [quo.core :as quo] - [react-native.core :as rn] - [status-im.contexts.wallet.wallet-connect.modals.common.header.style :as style])) + (:require + [quo.core :as quo] + [react-native.core :as rn] + [status-im.contexts.wallet.wallet-connect.core :as core] + [status-im.contexts.wallet.wallet-connect.modals.common.header.style :as style] + [utils.string])) (defn view [{:keys [label dapp account]}] @@ -10,12 +13,13 @@ [quo/text {:size :heading-1 :weight :semi-bold} - (let [{:keys [name iconUrl]} dapp] + (let [{:keys [name iconUrl url]} dapp + image-source (core/compute-dapp-icon-path iconUrl url)] [rn/view {:style style/header-dapp-name} [quo/summary-tag {:type :dapp :label name - :image-source iconUrl}]]) + :image-source image-source}]]) (str " " label " ") (let [{:keys [emoji customization-color name]} account] [rn/view {:style style/header-account-name} diff --git a/src/status_im/contexts/wallet/wallet_connect/session_proposal/style.cljs b/src/status_im/contexts/wallet/wallet_connect/session_proposal/style.cljs index a828d46a23..af73ef6c62 100644 --- a/src/status_im/contexts/wallet/wallet_connect/session_proposal/style.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/session_proposal/style.cljs @@ -5,14 +5,15 @@ {:padding-horizontal 20 :padding-top 12}) -(def approval-note-container +(defn approval-note-container + [theme] {:margin-horizontal 20 :padding-horizontal 16 :padding-vertical 12 :border-radius 16 :border-width 1 - :border-color colors/neutral-10 - :background-color colors/neutral-2_5}) + :border-color (colors/theme-colors colors/neutral-10 colors/black-opa-30 theme) + :background-color (colors/theme-colors colors/neutral-2_5 colors/black-opa-30 theme)}) (def approval-note-title {:color colors/neutral-50 diff --git a/src/status_im/contexts/wallet/wallet_connect/session_proposal/view.cljs b/src/status_im/contexts/wallet/wallet_connect/session_proposal/view.cljs index fab5752cf6..42231f81d1 100644 --- a/src/status_im/contexts/wallet/wallet_connect/session_proposal/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/session_proposal/view.cljs @@ -6,21 +6,27 @@ [quo.theme] [react-native.core :as rn] [status-im.common.floating-button-page.view :as floating-button-page] + [status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core] [status-im.contexts.wallet.wallet-connect.session-proposal.style :as style] [utils.i18n :as i18n] - [utils.re-frame :as rf])) + [utils.re-frame :as rf] + [utils.string])) (defn- dapp-metadata [] (let [proposer (rf/sub [:wallet-connect/session-proposer]) - {:keys [icons name url]} (:metadata proposer)] + {:keys [icons name url]} (:metadata proposer) + first-icon (first icons) + dapp-name (wallet-connect-core/compute-dapp-name name url) + profile-picture (wallet-connect-core/compute-dapp-icon-path first-icon url)] [:<> [rn/view {:style style/dapp-avatar} [quo/user-avatar - {:profile-picture (first icons) - :size :big}]] + {:profile-picture profile-picture + :size :big + :full-name dapp-name}]] [quo/page-top - {:title name + {:title dapp-name :description :context-tag :context-tag {:type :icon :size 32 @@ -31,8 +37,9 @@ [] (let [dapp-name (rf/sub [:wallet-connect/session-proposer-name]) labels [(i18n/label :t/check-your-account-balance-and-activity) - (i18n/label :t/request-txns-and-message-signing)]] - [rn/view {:style style/approval-note-container} + (i18n/label :t/request-txns-and-message-signing)] + theme (quo.theme/use-theme)] + [rn/view {:style (style/approval-note-container theme)} [quo/text {:style style/approval-note-title :weight :regular @@ -46,7 +53,8 @@ {:color colors/neutral-40}] [quo/text {:weight :regular - :size :paragraph-2} + :size :paragraph-2 + :color colors/neutral-40} label]]) labels)])) diff --git a/src/status_im/subs/wallet/wallet_connect.cljs b/src/status_im/subs/wallet/wallet_connect.cljs index 6b4fe542a6..eae794c7c7 100644 --- a/src/status_im/subs/wallet/wallet_connect.cljs +++ b/src/status_im/subs/wallet/wallet_connect.cljs @@ -5,7 +5,8 @@ [status-im.contexts.wallet.common.utils.networks :as networks] [status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core] [status-im.contexts.wallet.wallet-connect.transactions :as transactions] - [utils.money :as money])) + [utils.money :as money] + [utils.string])) (rf/reg-sub :wallet-connect/current-request-address @@ -44,7 +45,8 @@ (let [dapp-url (get-in request [:event :verifyContext :verified :origin])] (->> sessions (filter (fn [session] - (= dapp-url (get session :url)))) + (= (utils.string/remove-trailing-slash dapp-url) + (utils.string/remove-trailing-slash (get session :url))))) (first))))) (rf/reg-sub @@ -168,7 +170,8 @@ :wallet-connect/session-proposer-name :<- [:wallet-connect/session-proposer] (fn [proposer] - (-> proposer :metadata :name))) + (let [{:keys [name url]} (-> proposer :metadata)] + (wallet-connect-core/compute-dapp-name name url)))) (rf/reg-sub :wallet-connect/session-proposal-network-details diff --git a/src/status_im/subs/wallet/wallet_connect_test.cljs b/src/status_im/subs/wallet/wallet_connect_test.cljs index ac39debacb..936f48fc78 100644 --- a/src/status_im/subs/wallet/wallet_connect_test.cljs +++ b/src/status_im/subs/wallet/wallet_connect_test.cljs @@ -42,6 +42,41 @@ :origin "https://lab.web3modal.com" :isScam false}}}}) +(def sample-session-empty-name + {:session-proposal + {:id 1716798889093634 + :params + {:id 1716798889093634 + :pairingTopic "9b18e1348817a548bbc97f9b4a09278f4fdf7c984e4a61ddf461bd1f57710d33" + :expiryTimestamp 1716799189 + :requiredNamespaces {} + :optionalNamespaces {:eip155 + {:chains ["eip155:1" "eip155:42161" "eip155:137" "eip155:43114" "eip155:56" + "eip155:10" "eip155:100" + "eip155:324" "eip155:7777777" "eip155:8453" "eip155:42220" + "eip155:1313161554" "eip155:11155111" "eip155:11155420"] + :methods ["personal_sign" "eth_accounts" "eth_requestAccounts" + "eth_sendRawTransaction" "eth_sendTransaction" + "eth_sign" "eth_signTransaction" "eth_signTypedData" + "eth_signTypedData_v3" "eth_signTypedData_v4" + "wallet_addEthereumChain" "wallet_getCallsStatus" + "wallet_getCapabilities" "wallet_getPermissions" + "wallet_registerOnboarding" "wallet_requestPermissions" + "wallet_scanQRCode" "wallet_sendCalls" + "wallet_showCallsStatus" "wallet_switchEthereumChain" + "wallet_watchAsset"] + :events ["chainChanged" "accountsChanged"]}} + :relays [{:protocol "irn"}] + :proposer {:publicKey "cddea055b8974d93380e6c7e72110145506c06524047866f8034f3db0990137a" + :metadata {:name "" + :description "Web3Modal Laboratory" + :url "https://lab.web3modal.com" + :icons ["https://avatars.githubusercontent.com/u/37784886"]}}} + :verifyContext {:verified {:verifyUrl "https://verify.walletconnect.com" + :validation "VALID" + :origin "https://lab.web3modal.com" + :isScam false}}}}) + (h/deftest-sub :wallet-connect/session-proposer [sub-name] (testing "Return the session proposer public key and metadata" diff --git a/src/utils/string.cljs b/src/utils/string.cljs index 9de548234a..b5ffe92562 100644 --- a/src/utils/string.cljs +++ b/src/utils/string.cljs @@ -73,3 +73,18 @@ (defn contains-special-character? [s] (re-find #"[^a-zA-Z0-9\s]" s)) + +(defn remove-trailing-slash + "Given a URL, checks if it has a trailing slash and removes it. + Returns the URL as-is if there is no trailing slash." + [url] + (if (and (string? url) (string/ends-with? url "/")) + (subs url 0 (dec (count url))) + url)) + +(defn remove-http-prefix + "Given a URL, removes the 'http://' or 'https://' prefix if present. + Returns the URL without the prefix." + [url] + (when (string? url) + (string/replace url #"^https?://" ""))) diff --git a/src/utils/string_test.cljs b/src/utils/string_test.cljs index 3f898b1361..cea239f22e 100644 --- a/src/utils/string_test.cljs +++ b/src/utils/string_test.cljs @@ -18,3 +18,25 @@ "AB" "a b" 2 "ABC" "a b c d" 3 "ABC" " a b c d" 3)) + +(deftest remove-trailing-slash-test + (are [expected input] + (= expected (utils.string/remove-trailing-slash input)) + "http://example.com" "http://example.com/" + "http://example.com" "http://example.com" + "http://example.com/path" "http://example.com/path/" + "http://example.com/path" "http://example.com/path" + "" "" + nil nil)) + +(deftest remove-http-prefix-test + (are [expected input] + (= expected (utils.string/remove-http-prefix input)) + "example.com" "http://example.com" + "example.com" "https://example.com" + "example.com" "example.com" + "example.com/path" "http://example.com/path" + "example.com/path" "https://example.com/path" + "example.com/path" "example.com/path" + "" "" + nil nil)) From 89bb3ea5c37133e5cbe64aafb246570de83c0bac Mon Sep 17 00:00:00 2001 From: mmilad75 <55688834+mmilad75@users.noreply.github.com> Date: Tue, 23 Jul 2024 14:45:02 +0200 Subject: [PATCH 71/79] Watch-only collectibles should not be included on the main wallet page by default #20735 (#20761) --- .../contexts/wallet/collectible/utils.cljs | 11 -------- .../contexts/wallet/collectible/view.cljs | 8 +++--- .../wallet/common/collectibles_tab/view.cljs | 22 ++++++++------- .../contexts/wallet/home/tabs/view.cljs | 2 +- src/status_im/subs/wallet/collectibles.cljs | 28 ++++++++++++++----- 5 files changed, 38 insertions(+), 33 deletions(-) diff --git a/src/status_im/contexts/wallet/collectible/utils.cljs b/src/status_im/contexts/wallet/collectible/utils.cljs index b48ccc6a89..c0a0e8c6a4 100644 --- a/src/status_im/contexts/wallet/collectible/utils.cljs +++ b/src/status_im/contexts/wallet/collectible/utils.cljs @@ -28,17 +28,6 @@ (log/debug "unsupported collectible file type:" (or collectible-type "Unknown type")) false))) -(defn total-owned-collectible - ([ownership] - (total-owned-collectible ownership false)) - ([ownership address] - (reduce (fn [acc item] - (if (or (not address) (= (:address item) address)) - (+ acc (js/parseInt (:balance item))) - acc)) - 0 - ownership))) - (defn collectible-owned-counter [total] (when (> total 1) (str "x" total))) diff --git a/src/status_im/contexts/wallet/collectible/view.cljs b/src/status_im/contexts/wallet/collectible/view.cljs index cb86c75c04..2f814fd6a1 100644 --- a/src/status_im/contexts/wallet/collectible/view.cljs +++ b/src/status_im/contexts/wallet/collectible/view.cljs @@ -160,6 +160,9 @@ collectible-owner (rf/sub [:wallet/collectible-details-owner collectible]) aspect-ratio (rf/sub [:wallet/collectible-aspect-ratio]) gradient-color (rf/sub [:wallet/collectible-gradient-color]) + total-owned (rf/sub [:wallet/total-owned-collectible + (:ownership collectible) + (:address collectible-owner)]) {:keys [id preview-url collection-data @@ -182,10 +185,7 @@ :image-height 300 :id token-id :header collectible-name - :description collection-name} - total-owned (utils/total-owned-collectible - (:ownership collectible) - (:address collectible-owner))] + :description collection-name}] [rn/view {:style style/container} [rn/view [gradient-layer preview-uri] diff --git a/src/status_im/contexts/wallet/common/collectibles_tab/view.cljs b/src/status_im/contexts/wallet/common/collectibles_tab/view.cljs index 81c793232e..0f5cecd68a 100644 --- a/src/status_im/contexts/wallet/common/collectibles_tab/view.cljs +++ b/src/status_im/contexts/wallet/common/collectibles_tab/view.cljs @@ -7,7 +7,8 @@ [status-im.contexts.wallet.collectible.utils :as utils] [status-im.contexts.wallet.common.collectibles-tab.style :as style] [status-im.contexts.wallet.common.empty-tab.view :as empty-tab] - [utils.i18n :as i18n])) + [utils.i18n :as i18n] + [utils.re-frame :as rf])) (defn- collectible-item [{:keys [preview-url collection-data collectible-data total-owned on-press on-long-press] @@ -61,15 +62,16 @@ ;; TODO: https://github.com/status-im/status-mobile/issues/20137 ;; 1. If possible, move `collectibles-data` calculation to a subscription ;; 2. Optimization: do not recalculate all the collectibles, process only the new ones - (let [collectibles-data (map-indexed (fn [index {:keys [ownership] :as collectible}] - (assoc collectible - :total-owned (utils/total-owned-collectible - ownership - current-account-address) - :on-long-press on-collectible-long-press - :on-press on-collectible-press - :collectible-index index)) - collectibles)] + (let [collectibles-data (map-indexed + (fn [index {:keys [ownership] :as collectible}] + (let [total-owned (rf/sub [:wallet/total-owned-collectible ownership + current-account-address])] + (assoc collectible + :total-owned total-owned + :on-long-press on-collectible-long-press + :on-press on-collectible-press + :collectible-index index))) + collectibles)] [rn/flat-list {:data collectibles-data :style {:flex 1} diff --git a/src/status_im/contexts/wallet/home/tabs/view.cljs b/src/status_im/contexts/wallet/home/tabs/view.cljs index 4011ab6225..6475ce10f1 100644 --- a/src/status_im/contexts/wallet/home/tabs/view.cljs +++ b/src/status_im/contexts/wallet/home/tabs/view.cljs @@ -21,7 +21,7 @@ (defn view [{:keys [selected-tab]}] - (let [collectible-list (rf/sub [:wallet/all-collectibles-list-in-selected-networks]) + (let [collectible-list (rf/sub [:wallet/owned-collectibles-list-in-selected-networks]) request-collectibles #(rf/dispatch [:wallet/request-collectibles-for-all-accounts {}])] [rn/view {:style style/container} diff --git a/src/status_im/subs/wallet/collectibles.cljs b/src/status_im/subs/wallet/collectibles.cljs index d1f6fb7b35..48c310610d 100644 --- a/src/status_im/subs/wallet/collectibles.cljs +++ b/src/status_im/subs/wallet/collectibles.cljs @@ -55,13 +55,13 @@ (filter-collectibles-in-chains collectibles chain-ids))) (re-frame/reg-sub - :wallet/all-collectibles-list - :<- [:wallet] - (fn [{:keys [accounts]}] + :wallet/owned-collectibles-list + :<- [:wallet/accounts-without-watched-accounts] + (fn [accounts] (let [max-collectibles (->> accounts - (map (comp count :collectibles val)) + (map (comp count :collectibles)) (apply max)) - all-collectibles (map (fn [[_address {:keys [collectibles]}]] + all-collectibles (map (fn [{:keys [collectibles]}] (let [amount-to-add (- max-collectibles (count collectibles)) empty-collectibles (repeat amount-to-add nil)] (reduce conj collectibles empty-collectibles))) @@ -72,8 +72,8 @@ (add-collectibles-preview-url))))) (re-frame/reg-sub - :wallet/all-collectibles-list-in-selected-networks - :<- [:wallet/all-collectibles-list] + :wallet/owned-collectibles-list-in-selected-networks + :<- [:wallet/owned-collectibles-list] :<- [:wallet/selected-networks->chain-ids] (fn [[all-collectibles chain-ids]] (filter-collectibles-in-chains all-collectibles chain-ids))) @@ -123,3 +123,17 @@ %) accounts)))) +(re-frame/reg-sub + :wallet/total-owned-collectible + :<- [:wallet/accounts-without-watched-accounts] + (fn [accounts [_ ownership address]] + (let [addresses (map :address accounts)] + (reduce (fn [acc item] + (if (or + (and (not address) + (contains? (set addresses) (:address item))) + (= (:address item) address)) + (+ acc (js/parseInt (:balance item))) + acc)) + 0 + ownership)))) From a5c81824613ca931e9a770506b1d1c4f686cd58e Mon Sep 17 00:00:00 2001 From: John Ngei Date: Tue, 23 Jul 2024 15:18:43 +0200 Subject: [PATCH 72/79] fix onboarding navigation to enable notification screen and blur issues (#20725) --- .../onboarding/enable_notifications/view.cljs | 4 ++-- src/status_im/contexts/onboarding/events.cljs | 15 +++------------ src/status_im/navigation/roots.cljs | 9 --------- src/status_im/navigation/screens.cljs | 14 +++++++++----- 4 files changed, 14 insertions(+), 28 deletions(-) diff --git a/src/status_im/contexts/onboarding/enable_notifications/view.cljs b/src/status_im/contexts/onboarding/enable_notifications/view.cljs index 0c043b8081..54caa8777b 100644 --- a/src/status_im/contexts/onboarding/enable_notifications/view.cljs +++ b/src/status_im/contexts/onboarding/enable_notifications/view.cljs @@ -38,7 +38,7 @@ (rf/dispatch [:push-notifications/switch true]) (rf/dispatch [:navigate-to-within-stack [:screen/onboarding.welcome - :screen/onboarding.enable-notifications]])) + :screen/onboarding.generating-keys]])) :type :primary :icon-left :i/notifications :accessibility-label :enable-notifications-button @@ -52,7 +52,7 @@ nil) (rf/dispatch [:navigate-to-within-stack [:screen/onboarding.welcome - :screen/onboarding.enable-notifications]])) + :screen/onboarding.generating-keys]])) :accessibility-label :enable-notifications-later-button :type :grey :background :blur diff --git a/src/status_im/contexts/onboarding/events.cljs b/src/status_im/contexts/onboarding/events.cljs index b5a5a75a7f..e368cabdbb 100644 --- a/src/status_im/contexts/onboarding/events.cljs +++ b/src/status_im/contexts/onboarding/events.cljs @@ -73,7 +73,9 @@ :onboarding/navigated-to-enter-seed-phrase-from-screen :screen/onboarding.new-to-status)]] :dispatch-later [{:ms constants/onboarding-generating-keys-animation-duration-ms - :dispatch [:init-root :screen/onboarding.enable-notifications]}] + :dispatch [:navigate-to-within-stack + [:screen/onboarding.enable-notifications + :screen/onboarding.generating-keys]]}] :db (-> db (dissoc :profile/login) (dissoc :auth-method) @@ -187,14 +189,3 @@ {:key-uid key-uid :error %})})))) -(rf/defn navigate-to-identifiers - {:events [:onboarding/navigate-to-identifiers]} - [{:keys [db]}] - (if (:onboarding/generated-keys? db) - {:dispatch [:navigate-to-within-stack - [:screen/onboarding.identifiers - (get db - :onboarding/navigated-to-enter-seed-phrase-from-screen - :screen/onboarding.new-to-status)]]} - {:dispatch-later [{:ms constants/onboarding-generating-keys-navigation-retry-ms - :dispatch [:onboarding/navigate-to-identifiers]}]})) diff --git a/src/status_im/navigation/roots.cljs b/src/status_im/navigation/roots.cljs index 65268e8228..6c938f8b6b 100644 --- a/src/status_im/navigation/roots.cljs +++ b/src/status_im/navigation/roots.cljs @@ -37,15 +37,6 @@ :id :screen/profile.profiles :options (options/dark-root-options)}}]}}} - :screen/onboarding.enable-notifications - {:root {:stack {:children [{:component {:name :screen/onboarding.enable-notifications - :id :screen/onboarding.enable-notifications - :options (options/dark-root-options)}}]}}} - - :screen/onboarding.welcome - {:root {:stack {:children [{:component {:name :screen/onboarding.welcome - :id :screen/onboarding.welcome - :options (options/dark-root-options)}}]}}} :screen/onboarding.syncing-results {:root {:stack {:children [{:component {:name :screen/onboarding.syncing-results :id :screen/onboarding.syncing-results diff --git a/src/status_im/navigation/screens.cljs b/src/status_im/navigation/screens.cljs index a0c97cf949..4b8f0708bd 100644 --- a/src/status_im/navigation/screens.cljs +++ b/src/status_im/navigation/screens.cljs @@ -348,11 +348,15 @@ :component enter-seed-phrase/view} {:name :screen/onboarding.enable-notifications - :options {:theme :dark - :layout options/onboarding-transparent-layout - :animations (merge - transitions/new-to-status-modal-animations - transitions/push-animations-for-transparent-background)} + :options {:theme :dark + :layout options/onboarding-transparent-layout + :animations (merge + transitions/new-to-status-modal-animations + transitions/push-animations-for-transparent-background) + :popGesture false + :modalPresentationStyle :overCurrentContext + :hardwareBackButton {:dismissModalOnPress false + :popStackOnPress false}} :component enable-notifications/view} {:name :screen/onboarding.identifiers From 8597b899bdb914c2bdfdd74ec550872ffd339e0a Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 23 Jul 2024 17:57:27 +0200 Subject: [PATCH 73/79] Remove dApps tab from Wallet (#20818) --- .../wallet/account/tabs/dapps/style.cljs | 18 --------- .../wallet/account/tabs/dapps/view.cljs | 37 ------------------- .../contexts/wallet/account/tabs/view.cljs | 2 - .../contexts/wallet/account/view.cljs | 14 +++---- 4 files changed, 6 insertions(+), 65 deletions(-) delete mode 100644 src/status_im/contexts/wallet/account/tabs/dapps/style.cljs delete mode 100644 src/status_im/contexts/wallet/account/tabs/dapps/view.cljs diff --git a/src/status_im/contexts/wallet/account/tabs/dapps/style.cljs b/src/status_im/contexts/wallet/account/tabs/dapps/style.cljs deleted file mode 100644 index f5fde1c6ed..0000000000 --- a/src/status_im/contexts/wallet/account/tabs/dapps/style.cljs +++ /dev/null @@ -1,18 +0,0 @@ -(ns status-im.contexts.wallet.account.tabs.dapps.style - (:require - [quo.foundations.colors :as colors])) - -(def dapps-container - {:padding-horizontal 20 - :padding-vertical 8}) - -(defn dapps-list - [theme] - {:border-radius 16 - :border-width 1 - :border-color (colors/theme-colors colors/neutral-10 colors/neutral-80 theme)}) - -(defn separator - [theme] - {:height 1 - :background-color (colors/theme-colors colors/neutral-10 colors/neutral-80 theme)}) diff --git a/src/status_im/contexts/wallet/account/tabs/dapps/view.cljs b/src/status_im/contexts/wallet/account/tabs/dapps/view.cljs deleted file mode 100644 index 60561f958b..0000000000 --- a/src/status_im/contexts/wallet/account/tabs/dapps/view.cljs +++ /dev/null @@ -1,37 +0,0 @@ -(ns status-im.contexts.wallet.account.tabs.dapps.view - (:require - [quo.core :as quo] - [quo.theme :as quo.theme] - [react-native.core :as rn] - [status-im.common.resources :as resources] - [status-im.contexts.wallet.account.tabs.dapps.style :as style] - [status-im.contexts.wallet.common.empty-tab.view :as empty-tab] - [utils.i18n :as i18n])) - -(defn dapp-options - [] - [quo/action-drawer - [[{:icon :i/browser - :accessibility-label :visit-dapp - :label (i18n/label :t/visit-dapp)} - {:icon :i/disconnect - :accessibility-label :disconnect-dapp - :label (i18n/label :t/disconnect-dapp) - :add-divider? true - :danger? true}]]]) - -(defn view - [] - (let [theme (quo.theme/use-theme) - dapps-list []] - (if (empty? dapps-list) - [empty-tab/view - {:title (i18n/label :t/no-dapps) - :description (i18n/label :t/no-dapps-description) - :image (resources/get-themed-image :no-dapps theme)}] - [rn/view {:style style/dapps-container} - [rn/flat-list - {:data dapps-list - :style (style/dapps-list theme) - :render-fn (fn [item] [quo/dapp item]) - :separator [rn/view {:style (style/separator theme)}]}]]))) diff --git a/src/status_im/contexts/wallet/account/tabs/view.cljs b/src/status_im/contexts/wallet/account/tabs/view.cljs index 45d62bd530..10d499267a 100644 --- a/src/status_im/contexts/wallet/account/tabs/view.cljs +++ b/src/status_im/contexts/wallet/account/tabs/view.cljs @@ -3,7 +3,6 @@ [react-native.core :as rn] [status-im.contexts.wallet.account.tabs.about.view :as about] [status-im.contexts.wallet.account.tabs.assets.view :as assets] - [status-im.contexts.wallet.account.tabs.dapps.view :as dapps] [status-im.contexts.wallet.collectible.options.view :as options-drawer] [status-im.contexts.wallet.common.activity-tab.view :as activity] [status-im.contexts.wallet.common.collectibles-tab.view :as collectibles] @@ -45,5 +44,4 @@ {:title (i18n/label :t/no-permissions) :description (i18n/label :t/no-collectibles-description) :placeholder? true}] - :dapps [dapps/view] [about/view])])) diff --git a/src/status_im/contexts/wallet/account/view.cljs b/src/status_im/contexts/wallet/account/view.cljs index f11421d1ef..9448fb9aac 100644 --- a/src/status_im/contexts/wallet/account/view.cljs +++ b/src/status_im/contexts/wallet/account/view.cljs @@ -13,13 +13,11 @@ (def first-tab-id :assets) -(defn tabs-data - [watch-only?] - (cond-> [{:id :assets :label (i18n/label :t/assets) :accessibility-label :assets-tab} - {:id :collectibles :label (i18n/label :t/collectibles) :accessibility-label :collectibles-tab} - {:id :activity :label (i18n/label :t/activity) :accessibility-label :activity-tab}] - (not watch-only?) (conj {:id :dapps :label (i18n/label :t/dapps) :accessibility-label :dapps}) - :always (conj {:id :about :label (i18n/label :t/about) :accessibility-label :about}))) +(def tabs-data + [{:id :assets :label (i18n/label :t/assets) :accessibility-label :assets-tab} + {:id :collectibles :label (i18n/label :t/collectibles) :accessibility-label :collectibles-tab} + {:id :activity :label (i18n/label :t/activity) :accessibility-label :activity-tab} + {:id :about :label (i18n/label :t/about) :accessibility-label :about}]) (defn- change-tab [id] (rf/dispatch [:wallet/select-account-tab id])) @@ -69,7 +67,7 @@ {:style style/tabs :size 32 :active-tab-id selected-tab - :data (tabs-data watch-only?) + :data tabs-data :on-change change-tab :scrollable? true :scroll-on-press? true}] From e4639c153f4ff499fd4aa535fd92ad4291e6644e Mon Sep 17 00:00:00 2001 From: Ulises Manuel <90291778+ulisesmac@users.noreply.github.com> Date: Tue, 23 Jul 2024 16:08:33 -0600 Subject: [PATCH 74/79] feat(wallet): Skip From page while sending an asset only owned by one account (#20773) - Update tests - Omit from page while sending a token in home page - Hide send and bridge option for not owned tokens - Fix subscription to return accounts owning an asset --- .../addresses_for_permissions/view.cljs | 2 +- .../wallet/common/token_value/view.cljs | 23 ++++--- src/status_im/contexts/wallet/events.cljs | 19 ++++-- .../wallet/home/tabs/assets/view.cljs | 1 + .../contexts/wallet/send/events.cljs | 60 ++++++++++++------- .../contexts/wallet/send/from/view.cljs | 1 - .../wallet/send/select_address/view.cljs | 4 +- .../wallet/sheets/select_account/view.cljs | 2 +- .../wallet_connect/session_proposal/view.cljs | 2 +- .../subs/community/account_selection.cljs | 4 +- src/status_im/subs/wallet/wallet.cljs | 53 ++++++++++++---- src/status_im/subs/wallet/wallet_test.cljs | 30 +++++++--- 12 files changed, 140 insertions(+), 61 deletions(-) diff --git a/src/status_im/contexts/communities/actions/addresses_for_permissions/view.cljs b/src/status_im/contexts/communities/actions/addresses_for_permissions/view.cljs index 591c89233c..cef25d3846 100644 --- a/src/status_im/contexts/communities/actions/addresses_for_permissions/view.cljs +++ b/src/status_im/contexts/communities/actions/addresses_for_permissions/view.cljs @@ -262,7 +262,7 @@ can-edit-addresses? (rf/sub [:communities/can-edit-shared-addresses? id]) - wallet-accounts (rf/sub [:wallet/operable-accounts-without-watched-accounts]) + wallet-accounts (rf/sub [:wallet/operable-accounts]) joined (rf/sub [:communities/community-joined id]) unmodified-addresses-to-reveal (rf/sub [:communities/addresses-to-reveal id]) [addresses-to-reveal set-addresses-to-reveal] (rn/use-state unmodified-addresses-to-reveal) diff --git a/src/status_im/contexts/wallet/common/token_value/view.cljs b/src/status_im/contexts/wallet/common/token_value/view.cljs index 078eaf5507..0dffb3dc94 100644 --- a/src/status_im/contexts/wallet/common/token_value/view.cljs +++ b/src/status_im/contexts/wallet/common/token_value/view.cljs @@ -16,14 +16,14 @@ :right-icon :i/external}) (defn- action-send - [send-params] + [send-params entry-point] {:icon :i/send :accessibility-label :send :label (i18n/label :t/send) :on-press (fn [] (rf/dispatch [:hide-bottom-sheet]) (rf/dispatch [:wallet/clean-send-data]) - (rf/dispatch [:wallet/set-token-to-send send-params]))}) + (rf/dispatch [:wallet/set-token-to-send send-params entry-point]))}) (defn- action-receive [selected-account?] @@ -66,36 +66,41 @@ :on-press #(js/alert "to be implemented")}) (defn token-value-drawer - [token watch-only?] + [token watch-only? entry-point] (let [token-symbol (:token token) token-data (first (rf/sub [:wallet/current-viewing-account-tokens-filtered token-symbol])) selected-account? (rf/sub [:wallet/current-viewing-account-address]) + token-owners (rf/sub [:wallet/operable-addresses-with-token-symbol token-symbol]) send-or-bridge-params (if selected-account? {:token token-data :stack-id :screen/wallet.accounts - :start-flow? true} + :start-flow? true + :owners token-owners} {:token-symbol token-symbol :stack-id :wallet-stack - :start-flow? true})] + :start-flow? true + :owners token-owners})] [quo/action-drawer [(cond->> [(when (ff/enabled? ::ff/wallet.assets-modal-manage-tokens) (action-manage-tokens watch-only?)) (when (ff/enabled? ::ff/wallet.assets-modal-hide) (action-hide))] (not watch-only?) (concat [(action-buy) - (action-send send-or-bridge-params) + (when (seq token-owners) + (action-send send-or-bridge-params entry-point)) (action-receive selected-account?) (when (ff/enabled? ::ff/wallet.swap) (action-swap)) - (action-bridge send-or-bridge-params)]))]])) + (when (seq (seq token-owners)) + (action-bridge send-or-bridge-params))]))]])) (defn view - [item _ _ {:keys [watch-only?]}] + [item _ _ {:keys [watch-only? entry-point]}] [quo/token-value (cond-> item (or (not watch-only?) (ff/enabled? ::ff/wallet.long-press-watch-only-asset)) (assoc :on-long-press #(rf/dispatch [:show-bottom-sheet - {:content (fn [] [token-value-drawer item watch-only?]) + {:content (fn [] [token-value-drawer item watch-only? entry-point]) :selected-item (fn [] [quo/token-value item])}])))]) diff --git a/src/status_im/contexts/wallet/events.cljs b/src/status_im/contexts/wallet/events.cljs index 1bcdc760b3..ddd0104d1d 100644 --- a/src/status_im/contexts/wallet/events.cljs +++ b/src/status_im/contexts/wallet/events.cljs @@ -65,10 +65,21 @@ {:db (assoc-in db [:wallet :current-viewing-account-address] address)})) (rf/reg-event-fx :wallet/clean-current-viewing-account - (fn [{:keys [db]}] - (let [just-completed-transaction? (get-in db [:wallet :ui :send :just-completed-transaction?])] - (when-not just-completed-transaction? - {:db (update db :wallet dissoc :current-viewing-account-address)})))) + (fn [{:keys [db]} [ignore-just-completed-transaction?]] + (let [{:keys [entry-point just-completed-transaction?]} (-> db :wallet :ui :send) + entry-point-wallet-home? (= entry-point :wallet-stack)] + {:db (cond-> db + (and (not entry-point) + (not ignore-just-completed-transaction?) + (not just-completed-transaction?)) + (update :wallet dissoc :current-viewing-account-address) + + entry-point-wallet-home? + (update-in [:wallet :ui :send] dissoc :entry-point) + + (and entry-point-wallet-home? + (not just-completed-transaction?)) + (update :wallet dissoc :current-viewing-account-address))}))) (rf/reg-event-fx :wallet/close-account-page (fn [{:keys [db]}] diff --git a/src/status_im/contexts/wallet/home/tabs/assets/view.cljs b/src/status_im/contexts/wallet/home/tabs/assets/view.cljs index 0fd82dacae..4aaf664698 100644 --- a/src/status_im/contexts/wallet/home/tabs/assets/view.cljs +++ b/src/status_im/contexts/wallet/home/tabs/assets/view.cljs @@ -18,4 +18,5 @@ [rn/flat-list {:render-fn token-value/view :data tokens + :render-data {:entry-point :wallet-stack} :content-container-style style/list-container}]))) diff --git a/src/status_im/contexts/wallet/send/events.cljs b/src/status_im/contexts/wallet/send/events.cljs index b2d219b44c..0863879426 100644 --- a/src/status_im/contexts/wallet/send/events.cljs +++ b/src/status_im/contexts/wallet/send/events.cljs @@ -193,25 +193,35 @@ (rf/reg-event-fx :wallet/set-token-to-send - (fn [{:keys [db]} [{:keys [token-symbol token stack-id start-flow?]}]] + (fn [{:keys [db]} [{:keys [token-symbol token stack-id start-flow? owners]} entry-point]] ;; `token` is a map extracted from the sender, but in the wallet home page we don't know the ;; sender yet, so we only provide the `token-symbol`, later in ;; `:wallet/select-from-account` the `token` key will be set. - (let [{token-networks :networks} token - receiver-networks (get-in db [:wallet :ui :send :receiver-networks]) - token-networks-ids (mapv #(:chain-id %) token-networks) - token-not-supported-in-receiver-networks? (not-any? (set receiver-networks) - token-networks-ids)] - (when (or token token-symbol) + (let [{:keys [networks]} token + receiver-networks (get-in db [:wallet :ui :send :receiver-networks]) + token-networks-ids (map :chain-id networks) + unsupported-token? (not-any? (set receiver-networks) token-networks-ids) + unique-owner (when (= (count owners) 1) + (first owners)) + unique-owner-tokens (get-in db [:wallet :accounts unique-owner :tokens]) + token-data (or token + (when (and token-symbol unique-owner) + (some #(when (= (:symbol %) token-symbol) %) + unique-owner-tokens)))] + (when (or token-data token-symbol) {:db (cond-> db - :always (update-in [:wallet :ui :send] dissoc :collectible) - :always (assoc-in - [:wallet :ui :send :token-not-supported-in-receiver-networks?] - token-not-supported-in-receiver-networks?) - token (assoc-in [:wallet :ui :send :token] token) - token (assoc-in [:wallet :ui :send :token-display-name] - (:symbol token)) - token-symbol (assoc-in [:wallet :ui :send :token-symbol] token-symbol)) + :always (update-in [:wallet :ui :send] + #(-> % + (dissoc :collectible) + (assoc :token-not-supported-in-receiver-networks? + unsupported-token?))) + token-symbol (assoc-in [:wallet :ui :send :token-symbol] token-symbol) + token-data (update-in [:wallet :ui :send] + #(assoc % + :token token-data + :token-display-name (:symbol token-data))) + unique-owner (assoc-in [:wallet :current-viewing-account-address] unique-owner) + entry-point (assoc-in [:wallet :ui :send :entry-point] entry-point)) :fx [[:dispatch [:wallet/clean-suggested-routes]] [:dispatch [:wallet/wizard-navigate-forward @@ -225,9 +235,9 @@ (let [{token-networks :networks token-symbol :symbol} token receiver-networks (get-in db [:wallet :ui :send :receiver-networks]) - token-networks-ids (mapv #(:chain-id %) token-networks) - token-not-supported-in-receiver-networks? (not (some (set receiver-networks) - token-networks-ids))] + token-networks-ids (map :chain-id token-networks) + token-not-supported-in-receiver-networks? (not-any? (set receiver-networks) + token-networks-ids)] {:db (-> db (assoc-in [:wallet :ui :send :token] token) (assoc-in [:wallet :ui :send :token-display-name] token-symbol) @@ -256,7 +266,9 @@ (rf/reg-event-fx :wallet/set-collectible-to-send (fn [{db :db} [{:keys [collectible current-screen start-flow?]}]] - (let [collection-data (:collection-data collectible) + (let [viewing-account? (some? (-> db :wallet :current-viewing-account-address)) + entry-point (when-not viewing-account? :wallet-stack) + collection-data (:collection-data collectible) collectible-data (:collectible-data collectible) contract-type (:contract-type collectible) tx-type (if (= contract-type constants/wallet-contract-type-erc-1155) @@ -271,6 +283,7 @@ collectible (str (:name collection-data) " #" collectible-id)) + owner-address (-> collectible :ownership first :address) collectible-tx (-> db (update-in [:wallet :ui :send] dissoc :token) (assoc-in [:wallet :ui :send :collectible] collectible) @@ -278,7 +291,14 @@ (assoc-in [:wallet :ui :send :tx-type] tx-type)) recipient-set? (-> db :wallet :ui :send :recipient)] {:db (cond-> collectible-tx - one-collectible? (assoc-in [:wallet :ui :send :amount] 1)) + :always + (assoc-in [:wallet :ui :send :entry-point] entry-point) + + (not viewing-account?) + (assoc-in [:wallet :current-viewing-account-address] owner-address) + + one-collectible? + (assoc-in [:wallet :ui :send :amount] 1)) :fx [(when (and one-collectible? recipient-set?) [:dispatch [:wallet/get-suggested-routes {:amount 1}]]) [:dispatch diff --git a/src/status_im/contexts/wallet/send/from/view.cljs b/src/status_im/contexts/wallet/send/from/view.cljs index 43fc1b78a9..8dc110291a 100644 --- a/src/status_im/contexts/wallet/send/from/view.cljs +++ b/src/status_im/contexts/wallet/send/from/view.cljs @@ -42,7 +42,6 @@ {:on-press #(rf/dispatch [:navigate-back]) :margin-top (safe-area/get-top) :switcher-type :select-account}]} - [quo/page-top {:title (i18n/label :t/from-label) :title-accessibility-label :title-label}] diff --git a/src/status_im/contexts/wallet/send/select_address/view.cljs b/src/status_im/contexts/wallet/send/select_address/view.cljs index 8e7d0ca2ac..b16770b34e 100644 --- a/src/status_im/contexts/wallet/send/select_address/view.cljs +++ b/src/status_im/contexts/wallet/send/select_address/view.cljs @@ -177,7 +177,9 @@ (rf/dispatch [:wallet/clean-selected-collectible]) (rf/dispatch [:wallet/clean-send-address]) (rf/dispatch [:wallet/clean-disabled-from-networks]) - (rf/dispatch [:wallet/select-address-tab nil])) + (rf/dispatch [:wallet/select-address-tab nil]) + (rf/dispatch [:wallet/clean-current-viewing-account + :ignore-just-complete-transaction])) on-change-tab #(rf/dispatch [:wallet/select-address-tab %]) input-value (reagent/atom "") input-focused? (reagent/atom false)] diff --git a/src/status_im/contexts/wallet/sheets/select_account/view.cljs b/src/status_im/contexts/wallet/sheets/select_account/view.cljs index df468a6222..8f3da1294c 100644 --- a/src/status_im/contexts/wallet/sheets/select_account/view.cljs +++ b/src/status_im/contexts/wallet/sheets/select_account/view.cljs @@ -20,7 +20,7 @@ (defn view [] (let [selected-account-address (rf/sub [:wallet/current-viewing-account-address]) - accounts (rf/sub [:wallet/operable-accounts-without-watched-accounts])] + accounts (rf/sub [:wallet/operable-accounts])] [:<> [quo/drawer-top {:title (i18n/label :t/select-account)}] [gesture/flat-list diff --git a/src/status_im/contexts/wallet/wallet_connect/session_proposal/view.cljs b/src/status_im/contexts/wallet/wallet_connect/session_proposal/view.cljs index 42231f81d1..8e321bc067 100644 --- a/src/status_im/contexts/wallet/wallet_connect/session_proposal/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/session_proposal/view.cljs @@ -70,7 +70,7 @@ (defn- accounts-list [] - (let [accounts (rf/sub [:wallet/operable-accounts-without-watched-accounts]) + (let [accounts (rf/sub [:wallet/operable-accounts]) selected-address (rf/sub [:wallet-connect/current-proposal-address])] [rn/view {:style style/account-switcher-list} (for [{:keys [address] :as account} accounts] diff --git a/src/status_im/subs/community/account_selection.cljs b/src/status_im/subs/community/account_selection.cljs index a4220067ef..8bc3bbb5ae 100644 --- a/src/status_im/subs/community/account_selection.cljs +++ b/src/status_im/subs/community/account_selection.cljs @@ -53,7 +53,7 @@ (re-frame/reg-sub :communities/accounts-to-reveal (fn [[_ community-id]] - [(re-frame/subscribe [:wallet/operable-accounts-without-watched-accounts]) + [(re-frame/subscribe [:wallet/operable-accounts]) (re-frame/subscribe [:communities/addresses-to-reveal community-id])]) (fn [[accounts addresses] _] (filter #(contains? addresses (:address %)) @@ -61,7 +61,7 @@ (re-frame/reg-sub :communities/airdrop-account (fn [[_ community-id]] - [(re-frame/subscribe [:wallet/operable-accounts-without-watched-accounts]) + [(re-frame/subscribe [:wallet/operable-accounts]) (re-frame/subscribe [:communities/airdrop-address community-id])]) (fn [[accounts airdrop-address] _] (->> accounts diff --git a/src/status_im/subs/wallet/wallet.cljs b/src/status_im/subs/wallet/wallet.cljs index bebb790b90..e9501df6ee 100644 --- a/src/status_im/subs/wallet/wallet.cljs +++ b/src/status_im/subs/wallet/wallet.cljs @@ -134,10 +134,12 @@ (:chain-id %)))) (map :chain-id) set)] - (assoc token - :networks (network-utils/network-list token networks) - :available-balance (utils/calculate-total-token-balance token) - :total-balance (utils/calculate-total-token-balance token enabled-from-chain-ids))))) + (some-> token + (assoc :networks (network-utils/network-list token networks) + :available-balance (utils/calculate-total-token-balance token) + :total-balance (utils/calculate-total-token-balance + token + enabled-from-chain-ids)))))) (rf/reg-sub :wallet/wallet-send-token-symbol @@ -464,22 +466,47 @@ keep-operable-accounts) (rf/reg-sub - :wallet/operable-accounts-without-watched-accounts + :wallet/operable-accounts :<- [:wallet/accounts-without-watched-accounts] keep-operable-accounts) +(rf/reg-sub + :wallet/operable-addresses-tokens-with-positive-balance + :<- [:wallet/operable-accounts] + (fn [accounts] + (let [positive-balance-in-any-chain? (fn [{:keys [balances-per-chain]}] + (->> balances-per-chain + (map (comp :raw-balance val)) + (some pos?)))] + (as-> accounts $ + (group-by :address $) + (update-vals $ #(filter positive-balance-in-any-chain? (:tokens (first %)))))))) + (rf/reg-sub :wallet/accounts-with-current-asset - :<- [:wallet/operable-accounts-without-watched-accounts] + :<- [:wallet/operable-accounts] + :<- [:wallet/operable-addresses-tokens-with-positive-balance] :<- [:wallet/wallet-send-token-symbol] :<- [:wallet/wallet-send-token] - (fn [[accounts token-symbol token]] - (let [asset-symbol (or token-symbol (:symbol token))] - (if asset-symbol - (filter (fn [account] - (some #(= (:symbol %) asset-symbol) (:tokens account))) - accounts) - accounts)))) + (fn [[accounts addresses-tokens token-symbol token]] + (if-let [asset-symbol (or token-symbol (:symbol token))] + (let [addresses-with-asset (as-> addresses-tokens $ + (update-vals $ #(set (map :symbol %))) + (keep (fn [[address token-symbols]] + (when (token-symbols asset-symbol) address)) + $) + (set $))] + (filter #(addresses-with-asset (:address %)) accounts)) + accounts))) + +(rf/reg-sub + :wallet/operable-addresses-with-token-symbol + :<- [:wallet/operable-addresses-tokens-with-positive-balance] + (fn [addresses-tokens [_ token-symbol]] + (keep (fn [[address tokens]] + (some #(when (= (:symbol %) token-symbol) address) + tokens)) + addresses-tokens))) (rf/reg-sub :wallet/account-tab diff --git a/src/status_im/subs/wallet/wallet_test.cljs b/src/status_im/subs/wallet/wallet_test.cljs index 5893fb6b62..571f6981ba 100644 --- a/src/status_im/subs/wallet/wallet_test.cljs +++ b/src/status_im/subs/wallet/wallet_test.cljs @@ -13,16 +13,22 @@ {:before #(reset! rf-db/app-db {})}) (def ^:private accounts-with-tokens - {:0x1 {:tokens [{:symbol "ETH"} {:symbol "SNT"}] + {:0x1 {:tokens [{:symbol "ETH" + :balances-per-chain {1 {:raw-balance "100"}}} + {:symbol "SNT" + :balances-per-chain {1 {:raw-balance "100"}}}] :network-preferences-names #{} :customization-color nil :operable? true - :operable :fully} - :0x2 {:tokens [{:symbol "SNT"}] + :operable :fully + :address "0x1"} + :0x2 {:tokens [{:symbol "SNT" + :balances-per-chain {1 {:raw-balance "200"}}}] :network-preferences-names #{} :customization-color nil :operable? true - :operable :partially}}) + :operable :partially + :address "0x2"}}) (def tokens-0x1 [{:decimals 1 @@ -495,11 +501,15 @@ (assoc-in [:wallet :ui :send :token-symbol] "ETH"))) (let [result (rf/sub [sub-name])] (is (match? result - [{:tokens [{:symbol "ETH"} {:symbol "SNT"}] + [{:tokens [{:symbol "ETH" + :balances-per-chain {1 {:raw-balance "100"}}} + {:symbol "SNT" + :balances-per-chain {1 {:raw-balance "100"}}}] :network-preferences-names #{} :customization-color nil :operable? true - :operable :fully}])))) + :operable :fully + :address "0x1"}])))) (testing "returns the accounts list with the current asset using token" (swap! rf-db/app-db @@ -508,11 +518,15 @@ (assoc-in [:wallet :ui :send :token] {:symbol "ETH"}))) (let [result (rf/sub [sub-name])] (is (match? result - [{:tokens [{:symbol "ETH"} {:symbol "SNT"}] + [{:tokens [{:symbol "ETH" + :balances-per-chain {1 {:raw-balance "100"}}} + {:symbol "SNT" + :balances-per-chain {1 {:raw-balance "100"}}}] :network-preferences-names #{} :customization-color nil :operable? true - :operable :fully}])))) + :operable :fully + :address "0x1"}])))) (testing "returns the full accounts list with the current asset using token-symbol if each account has the asset" From 4b8a612df4d87f7290aa8e799fd656549219db33 Mon Sep 17 00:00:00 2001 From: Icaro Motta Date: Tue, 23 Jul 2024 23:45:14 -0300 Subject: [PATCH 75/79] chore(docs): Document some of our existing testing practices (#20691) Document some of our current testing practices in hopes of helping reduce friction in PRs and communication in general. In theory, nothing in the text should be a surprise because these are things we have been discussing over many months (some things for almost 1.5 years) and are already present in the code. --- doc/README.md | 78 +++----- doc/pipeline_process.md | 18 +- doc/{ => tests}/component-tests-overview.md | 0 doc/{ => tests}/how-to-launch-e2e.md | 22 +-- .../how-to-run-local-tests.md} | 0 doc/tests/tests-overview.md | 167 ++++++++++++++++++ 6 files changed, 214 insertions(+), 71 deletions(-) rename doc/{ => tests}/component-tests-overview.md (100%) rename doc/{ => tests}/how-to-launch-e2e.md (95%) rename doc/{testing.md => tests/how-to-run-local-tests.md} (100%) create mode 100644 doc/tests/tests-overview.md diff --git a/doc/README.md b/doc/README.md index 301629677b..a4eeefaa09 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,67 +1,43 @@ -## Getting Started - -[Starting Guide](starting-guide.md) - -[IDE Setup](ide-setup.md) - +## Getting Started +- [Starting Guide](starting-guide.md) +- [IDE Setup](ide-setup.md) ## Development Process -[Coding guidelines](new-guidelines.md) - -[UI components coding guidelines](ui-guidelines.md) - -[Release Checklist](release-checklist.md) - -[Release Guide](release-guide.md) - -[Merging PR process](merging-pr-process.md) - -[PR Review Policy](pr-review-policy.md) - -[Working on PR together with QA team](pipeline_process.md) - -[Debugging](debugging.md) - -[Patching](patching.md) - -[Creating a pixel perfect UI](pixel-perfection.md) - -[Contributing to status-go](status-go-changes.md) - -[Malli schemas (recorded demo)](https://www.youtube.com/watch?v=SlRio70aYVI) ([slides](files/forging-code-with-schemas-sep-2023-slides.pdf)) +- [Coding guidelines](new-guidelines.md) +- [UI components coding guidelines](ui-guidelines.md) +- [Release Checklist](release-checklist.md) +- [Release Guide](release-guide.md) +- [Merging PR process](merging-pr-process.md) +- [PR Review Policy](pr-review-policy.md) +- [Working on PR together with QA team](pipeline_process.md) +- [Debugging](debugging.md) +- [Patching](patching.md) +- [Creating a pixel perfect UI](pixel-perfection.md) +- [Contributing to status-go](status-go-changes.md) +- [Malli schemas (recorded demo)](https://www.youtube.com/watch?v=SlRio70aYVI) ([slides](files/forging-code-with-schemas-sep-2023-slides.pdf)) ## Testing -[How to run local tests](testing.md) - -[End-to-end tests (e2e) overview](how-to-launch-e2e.md) - -[Component tests (jest) overview](component-tests-overview.md) - +- [Overview](tests/tests-overview.md) +- [How to run local tests](tests/how-to-run-local-tests.md) +- [End-to-end tests (e2e) overview](tests/how-to-launch-e2e.md) +- [Component tests (jest) overview](tests/component-tests-overview.md) ## Project details -[status-go introduction (recorded meeting)](https://drive.google.com/file/d/1B7TljmTZ8fHkqJH8ChU1Cp4FGDFM03gq/view) - -[re-frame usage (recorded meeting)](https://drive.google.com/file/d/1qv_E0CEGzQpu_zGXD0gCTU5EvhC2k8Jy/view) - -[status app functionality demo](https://drive.google.com/file/u/0/d/1PmwsMLTLDNNIdv5_6wvMOwoj2PfT50c6/view) +- [status-go introduction (recorded meeting)](https://drive.google.com/file/d/1B7TljmTZ8fHkqJH8ChU1Cp4FGDFM03gq/view) +- [re-frame usage (recorded meeting)](https://drive.google.com/file/d/1qv_E0CEGzQpu_zGXD0gCTU5EvhC2k8Jy/view) +- [status app functionality demo](https://drive.google.com/file/u/0/d/1PmwsMLTLDNNIdv5_6wvMOwoj2PfT50c6/view) ## Misc -[Importing icons from Figma into project](export-icons.md) - -[Updating Status APK builds for the F-Droid Android application catalogue](fdroid.md) - -[Troubleshooting for known errors](troubleshooting.md) - - +- [Importing icons from Figma into project](export-icons.md) +- [Updating Status APK builds for the F-Droid Android application catalogue](fdroid.md) +- [Troubleshooting for known errors](troubleshooting.md) ## Outdated: -[Old guidelines](codebase-structure-and-guidelines.md) - -[Post mortem analysis](post-mortem.md) - +- [Old guidelines](codebase-structure-and-guidelines.md) +- [Post mortem analysis](post-mortem.md) diff --git a/doc/pipeline_process.md b/doc/pipeline_process.md index 8d73e26282..6324639fc7 100644 --- a/doc/pipeline_process.md +++ b/doc/pipeline_process.md @@ -32,9 +32,9 @@ Ready for testing, a PR should meet the following criteria: ### E2E tests and analyzing the results The PR **MUST** be moved to the E2E column when it is ready for testing (**mandatory for all PRs**). -That will also trigger e2e tests run. QAs are monitoring PRs from E2E column and take it into test. +That will also trigger e2e tests run. QAs are monitoring PRs from E2E column and take it into test. This step cannot be skipped. So, at least one comment from the `status-im-auto` bot with results is a prerequisite for moving forward. -Information on how to analyze tests can be found [here](https://github.com/status-im/status-mobile/blob/develop/doc/how-to-launch-e2e.md). +Information on how to analyze tests can be found [here](https://github.com/status-im/status-mobile/blob/develop/doc/tests/how-to-launch-e2e.md). Tests might be flaky, as they depend on infrastructure - SauceLabs and Waku. If there are `Failed tests` and you are not sure about the reason, you can always ping the mobile QAs for help (preferably in PRs by `@status-im/mobile-qa`). @@ -46,14 +46,14 @@ Please, respect this rule.** ### Adding `skip-manual-qa` -**Do not hesitate to use a `skip-manual-qa`** if you're sure that it is a simple flow and you checked it. +**Do not hesitate to use a `skip-manual-qa`** if you're sure that it is a simple flow and you checked it. - Please ask another team member before adding the `skip-manual-qa` label (PR/Status community/DMs) so that there's a second opinion. - The PR MUST have a proper reasoning why manual QA is skipped. - The PR MUST include the steps of testing that has been done by the developer prior to moving it forward. **NOTE:** Make sure that QAs are OK with that; -Before merging PRs, please make sure that information is added about how you tested the PRs, that e2s have been passed and their results have been reviewed. +Before merging PRs, please make sure that information is added about how you tested the PRs, that e2s have been passed and their results have been reviewed. The QA team appreciates your help! @@ -72,7 +72,7 @@ The QA team appreciates your help! - QA engineer picks up one of PRs with the ```request-manual-qa``` label, drags the item to the ```IN TESTING``` column and assigns it to themselves. - During testing, QA will add comments describing the issues found, and also review automation tests results. Usually found issues are numbered as "Issue 1, Issue 2", etc. -When the first round of testing is completed and all issues for this stage are found, the QA can add the ```Tested - Issues``` label and drag the card to the ```CONTRIBUTOR``` column. These two actions are optional. +When the first round of testing is completed and all issues for this stage are found, the QA can add the ```Tested - Issues``` label and drag the card to the ```CONTRIBUTOR``` column. These two actions are optional. **IMPORTANT NOTE:** when the issues are fixed, developer **MUST** notify the QA that it is ready to be re-tested again by mention them in the PR. @@ -86,7 +86,7 @@ After that the developer merges PR into develop. _**How do I know if a design review is needed?**_ There are three cases here depending on the changes in the PR: -1. **Functional PRs with UI changes:** after the ```Tested - OK``` label is added, the QA moves the PR to the ```Design review``` column + mentions ```@Francesca-G``` in comments. +1. **Functional PRs with UI changes:** after the ```Tested - OK``` label is added, the QA moves the PR to the ```Design review``` column + mentions ```@Francesca-G``` in comments. 2. **Component PRs:** once the PR has received a review from developers and e2e tests results, it can be moved directly to the ```Design review``` column by the developer (manual testing step can be skipped) + the developer mentions ```@Francesca-G``` in comments. 3. **Functional PRs changes in which are not related to UI (e.g. a crash fix):** skip the ```Design review``` step (the PR should only be manually tested by QA). @@ -99,7 +99,7 @@ There are three possible scenarios when the design review is completed: **Notes:** - If your PR has a long story and started from `develop` branch several days ago, please rebase it to current develop before adding label - if PR can be tested by developer (in case of small changes) and/or developer is sure that the changes made cannot introduce a regression, then PR can be merged without manual testing. Also, currently, PRs are not manually tested if the changes relate only the design (creation of components, etc.) and do not affect the functionality (see `skip-manual-qa` label) ---- +--- #### Why my PR is in `Contributor` column? PR can be moved to this column by the ```status-github-bot``` or by QA engineer with label `Tested-issues` or if one of the requirements for manual QA was not met. @@ -120,6 +120,6 @@ In the second case - after fixing of all found issues, the developer should ping 6. In case of manual testing - the label ```Tested - OK``` from QA 7. In case of design review - the approval from the designer -You can merge your PR into develop - some useful clues you can find [here](https://notes.status.im/setup-e2e#3-Merging-PR) +You can merge your PR into develop - some useful clues you can find [here](https://notes.status.im/setup-e2e#3-Merging-PR) -HAPPY DEVELOPMENT! :tada: +HAPPY DEVELOPMENT! :tada: diff --git a/doc/component-tests-overview.md b/doc/tests/component-tests-overview.md similarity index 100% rename from doc/component-tests-overview.md rename to doc/tests/component-tests-overview.md diff --git a/doc/how-to-launch-e2e.md b/doc/tests/how-to-launch-e2e.md similarity index 95% rename from doc/how-to-launch-e2e.md rename to doc/tests/how-to-launch-e2e.md index 66cceee035..f694347218 100644 --- a/doc/how-to-launch-e2e.md +++ b/doc/tests/how-to-launch-e2e.md @@ -8,7 +8,7 @@ As a part of CI for Status mobile app and in order to ensure there are no regres - Automated tests written on Python 3.9 and pytest. - Appium (server) and Selenium WebDriver (protocol) are the base of test automation framework. -TestRail is a test case management system tool where we have test cases. +TestRail is a test case management system tool where we have test cases. Each of the test case gets a priority (Critical/High/Medium) @@ -20,21 +20,21 @@ For now we support e2e for Android only. Whenever we need to push set of test scripts we create 16 parallel sessions (max, but depending on amount of cases that are included in job) and each thread: 1) uploads Android .apk file to SauceLabs -> 2) runs through the test steps -> 3) receives results whether test failed on particular step or succeeded with no errors -> 3) Parse test results and push them as a Github comment (if the suite ran against respective PR) and into TestRail. We push **whole automation test suite (currently 155, amount is changing)** against each nightly build (if the nightly builds job succeeded). Results of the test run are saved in TestRail. -And also we push set of autotests whenever PR with successful builds got moved in to `E2E Tests` column from [Pipeline for QA dashboard ](https://github.com/status-im/status-react/projects/7). +And also we push set of autotests whenever PR with successful builds got moved in to `E2E Tests` column from [Pipeline for QA dashboard ](https://github.com/status-im/status-react/projects/7). In that case we save results in TestRail as well and push a comment with test results in a respective PR. For example: https://github.com/status-im/status-react/pull/9147#issuecomment-540008770 -![](images/how-to-launch-e2e/how-to-launch-e2e-1.png) +![](../images/how-to-launch-e2e/how-to-launch-e2e-1.png) The test_send_stt_from_wallet opens link in TestRail https://ethstatus.testrail.net/index.php?/tests/view/890885 where performed steps could be found -List of all runs performed by test jobs could be found here https://ethstatus.testrail.net/index.php?/runs/overview/14 +List of all runs performed by test jobs could be found here https://ethstatus.testrail.net/index.php?/runs/overview/14 **For credentials for TestRail to see results ping Chu in DM**: Opening any test run navigates you to list of test cases with results: -![](images/how-to-launch-e2e/how-to-launch-e2e-2.png) +![](../images/how-to-launch-e2e/how-to-launch-e2e-2.png) ## What about launching e2e manually @@ -53,12 +53,12 @@ Params to specify: - test_marks: tests by priorities (by default: `critical or high or medium`, which corresponds the whole suite; to launch the same suite as in PRs, use `critical or high`) - testrail_case_id: here is the list of test cases which you may find in test rail (4-digit value) -For easier access you can hit `Rerun tests` in GH comment and testrail_case_id/ apk_name/ pr_id will be filled automatically. For making sure that tests are being rerun on most recent e2e build it is recommended to paste link to the last e2e build in apk_name field. The list of PR builds can be found in Jenkins Builds block on PR page. -![](images/how-to-launch-e2e/how-to-launch-e2e-3.png) +For easier access you can hit `Rerun tests` in GH comment and testrail_case_id/ apk_name/ pr_id will be filled automatically. For making sure that tests are being rerun on most recent e2e build it is recommended to paste link to the last e2e build in apk_name field. The list of PR builds can be found in Jenkins Builds block on PR page. +![](../images/how-to-launch-e2e/how-to-launch-e2e-3.png) And then hit ‘Build’. Once the job starts it picks up specified tests, runs them against provided apk and sends results to pull request. -Even we have 16 parallel sessions for testing it’s a time consuming operation (whole test suite we have automated at the moment takes ~140 minutes to finish). +Even we have 16 parallel sessions for testing it’s a time consuming operation (whole test suite we have automated at the moment takes ~140 minutes to finish). So for PRs we pick only set of `critical or high` (you can also use this in TEST_MARKS param for job) tests (otherwise some PRs could wait their turn of the scheduled Jenkins job till the next day). @@ -78,9 +78,9 @@ Several examples of when test fails to succeed: - **Valid issue in the automated test scripts** - that's what we're looking for -Example: here is the test results https://github.com/status-im/status-react/pull/13015#issuecomment-1016495043 where one test failed. +Example: here is the test results https://github.com/status-im/status-react/pull/13015#issuecomment-1016495043 where one test failed. 1. Open the test in TestRail and open session recorded for this test in SauceLabs -![](images/how-to-launch-e2e/how-to-launch-e2e-4.png) +![](../images/how-to-launch-e2e/how-to-launch-e2e-4.png) In TestRail you may find all the steps performed by the test. @@ -98,6 +98,6 @@ Not all features of the app could be covered by e2e at the moment: ## Brief flow for test to be automated Whenever there is a need to have a new test: -1) Create a test scenario in TestRail. +1) Create a test scenario in TestRail. 2) If certain item could be checked in scope of existing test case we update existing one (otherwise we may have thousands of test cases which is overkill to manage in TestRail as well as in automated test scripts). And also complex autotests increase probability to not catch regressions by stopping test execution (due to valid bug or changed feature) keeping the rest test steps uncovered. So here we need to balance when it makes sense to update existing test case with more checks. 3) Then we create test script based on the test case, ensure test passes for the build and pushing the changes to repo. diff --git a/doc/testing.md b/doc/tests/how-to-run-local-tests.md similarity index 100% rename from doc/testing.md rename to doc/tests/how-to-run-local-tests.md diff --git a/doc/tests/tests-overview.md b/doc/tests/tests-overview.md new file mode 100644 index 0000000000..6a60149aff --- /dev/null +++ b/doc/tests/tests-overview.md @@ -0,0 +1,167 @@ +# Tests + +## Introduction + +This document provides a general overview of the types of tests we use and when +to use them. It is not meant to be a tutorial or a detailed documentation about +testing in software development. + +## Types of tests + +Tests in `status-mobile` are comprised of: + +- Unit tests + - Subscription tests + - Event tests + - Tests for various utilities +- [Component tests](./component-tests-overview.md) +- Integration/contract tests +- [End-to-end tests](./how-to-launch-e2e.md) + +We apply the [test +pyramid](https://en.wikipedia.org/wiki/Test_automation#Testing_at_different_levels) +strategy, which means we want the majority of tests at the bottom of the +pyramid. Those should be fast and deterministic and support REPL-Driven +development (RDD). Slightly above them, we have component tests, then +integration/contract tests and finally end-to-end tests. The closer to the top +of the pyramid, the more valuable a test can be, but also more difficult to +pinpoint why it failed and harder to make it dependable. + +*Note*: there are literally dozens of [types of +tests](https://en.wikipedia.org/wiki/Software_testing), each with its strengths +and weaknesses. + +We tend not to stub or mock implementations in our tests, which means our tests +are [sociable](https://martinfowler.com/bliki/UnitTest.html). + +## What to test? + +The UI is driven by global & local state changes caused by events. Global state +is managed by re-frame and local state by Reagent atoms or React hooks. Except +for component and end-to-end tests, we test only non-UI code in `status-mobile`. +Given that the UI is greatly derived from global state, by guaranteeing the +state is correct we can prevent bugs and, more importantly, reduce the [cost of +change](https://www.pmi.org/disciplined-agile/agile/costofchange). + +We strive to minimize the amount of _business logic_ in views (UI code). We +achieve this by moving capabilities to status-go and also by adhering to +re-frame's architecture. + +Whenever appropriate (see section `When to test?`), we _may_ test: + +- Re-frame events. +- Re-frame subscriptions. +- Utility functions. +- User journeys through integration/contract tests. + +Interestingly, we don't test re-frame _effects_ in isolation. + +### What are status-mobile integration and contract tests? + +The mobile _integration tests_ can be used to "simulate" user interactions and +make actual calls to status-go via the RPC layer and actually receive signals. +We can also use these tests to verify the app-db and multiple subscriptions are +correct. We use the word _simulate_ because there is no UI. Basically, any flow +that can be driven by re-frame events is possible to automatically test. There +is no way to change or inspect local state managed by React. + +A _contract test_ has the same capabilities as an integration test, but we want +to draw the line that they should focus more on a particupar RPC endpoint or +signal, and not on a user journey (e.g. create a wallet account). In the future, +we may consider running them automatically in status-go. + +**Note:** integration tests and contract tests are currently overlapping in +their responsibilities and still require a clearer distinction. + +## When to test? + +(Automated) tests basically exist to support rapid software changes, but not +every piece of code should be tested. The following are general recommendations, +not rules. + +- What would be the consequences to the user of a bug in the implementation you + are working on? +- Can a QA exercise all the branches in the code you changed? Not surprisingly, + usually QAs can't test many code paths (it may be nearly impossible), and + because PRs are not often tested by reviewers, many PRs can get into `develop` + without the necessary quality assurance. +- How costly was it for you to verify a function/event/etc was correct? Now + consider that this cost will be dispersed to every developer who needs to + change the implementation if there are no tests. +- Check the number of conditionals, and if they nest as well. Every conditional + may require two different assertions, and the number of assertions can grow + exponentially. +- How complicated are the arguments to the function? If they contain nested maps + or data that went through a few transformations, it may be tricky to decipher + what they are, unless you are familiar with the code. A test would be able to + capture the data, however complex they are. + +### When to unit-test subscriptions? + +Only test [layer-3 +subscriptions](https://day8.github.io/re-frame/subscriptions/#the-four-layers), +i.e. don't bother testing extractor subscriptions (check the related +[guideline](https://github.com/status-im/status-mobile/blob/7774c4eac16fdee950a17bf5d07630c45a980f41/doc/new-guidelines.md#subscription-tests)). +Some layer-3 subscriptions can still be straightforward and may not be worth +testing. + +- Check the number of _inputs_ to the sub (from the graph). The higher this + number, the greater the chance the subscription can break if any of the + input's implementation changes. + +**Note**: if a tested subscription changes inadvertently, even if its own tests +still pass, other subscriptions that depend on it and have tests may still fail. +This is why we don't directly test the subscription handler, but instead, use +the macro `test-helpers.unit/deftest-sub`. + +### When to unit-test events? + +A good hint is to ask if you and other CCs need to rely on re-frisk, UI, REPL, +or FlowStorm to understand the event. If the answer is yes or probably, then a +test would be prudent. + +- Many events only receive arguments and pass them along without much or any + transformation to an RPC call. These are straightforward and usually don't + need tests ([example](https://github.com/status-im/status-mobile/blob/7774c4eac16fdee950a17bf5d07630c45a980f41/src/status_im/contexts/contact/blocking/events.cljs#L79-L85)). +- Overall, every event basically returns two effects at most, `:fx` and/or + `:db`. Usually, the complicated part lies in the computation to return the new + app-db. If the event doesn't perform transformations in the app-db or just + does a trivial `assoc`, for example, it may not be worth testing. + +For reference, the re-frame author particularly [suggests testing events and +subscriptions](https://github.com/day8/re-frame/blob/09e2d7132c479aa43f2a64164e54e42bf8511902/docs/Testing.md#what-to-test). + +### When to unit-test utility functions? + +Most utility functions in `status-mobile` are pure and can be readily and +cheaply tested. + +- If the utility is used in an event/subscription and if the event/subscription + has tests, you may prefer to test the event/subscription and not the utility, + or the other way around sometimes. +- If the utility is tricky to verify, such as functions manipulating time, write + tests ([example](https://github.com/status-im/status-mobile/blob/7774c4eac16fdee950a17bf5d07630c45a980f41/src/utils/datetime.cljs#L1)). +- Utilities can be particularly hard to verify by QAs because they can be lower + level and require very particular inputs. In such cases, consider writing + tests. + +### When to write integration/contract tests? + +- You want to make real calls to status-go because you think the unit tests are + not enough (test pyramid strategy). +- You constantly need to retest the same things on the UI, sometimes over + multiple screens. +- The flow is too important to rely only on manual QA, which can't always be + done due to resource limits, so an integration/contract test fills this gap. +- You want to rely less on end-to-end tests, which can be more unreliable and + slower to change. +- You want automatic verifications for some area of the mobile app whenever + status-go is upgraded. + +**Note**: the feedback cycle to write integration tests is longer than unit +tests because they are slower and harder to debug. Using the REPL with them is +difficult due to their stateful nature. + +### When to test Quo components? + +This is covered in [quo/README.md#component-tests](https://github.com/status-im/status-mobile/blob/7774c4eac16fdee950a17bf5d07630c45a980f41/src/quo/README.md#component-tests). From cef1308b3a897772ffdde2acec182046c2cc0c6d Mon Sep 17 00:00:00 2001 From: Icaro Motta Date: Wed, 24 Jul 2024 00:18:27 -0300 Subject: [PATCH 76/79] chore(tests): Allow test-watch-for-repl target to run while the app is running (#20827) Fix a long standing problem where we can't run in parallel the make target run-clojure and test-watch-for-repl, or in other words, we can't run tests via the REPL while the app is running. Details: I found out that shadow-cljs fails to build because it does not expand the environment variables SHADOW_OUTPUT_TO and SHADOW_NS_REGEXP if and only if the run-clojure target was executed. This is the top of the stacktrace, and it happens because it doesn't know where to output the test build because the env var wasn't expanded: [build] NullPointerException: [build] shadow.build.node/configure (node.clj:59) [build] shadow.build.node/configure (node.clj:45) [build] shadow.build.targets.node-script/configure (node_script.clj:37) The solution is to pass the option --config-merge to shadow-cljs and override both :ns-regexp and :output-to because CLI args override options from env vars in shadow-cljs. --- Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1b117ca003..e912afca0b 100644 --- a/Makefile +++ b/Makefile @@ -353,13 +353,17 @@ test: export SHADOW_NS_REGEXP := .*-test$$ test: ##@test Run all Clojure tests test: _test-clojure +# Note: we need to override the :output-to and :ns-regexp options because +# shadow-cljs has a bug where it will not read from the env vars to expand the +# configuration when the shadow-cljs mobile target is already running. test-watch-for-repl: export TARGET := default test-watch-for-repl: export SHADOW_OUTPUT_TO := target/test/test.js test-watch-for-repl: export SHADOW_NS_REGEXP := .*-test$$ test-watch-for-repl: ##@test Watch all Clojure tests and support REPL connections + rm -f "$$SHADOW_OUTPUT_TO" && \ yarn install && shadow-cljs compile mocks && \ concurrently --kill-others --prefix-colors 'auto' --names 'build,repl' \ - 'yarn shadow-cljs watch test --verbose' \ + "yarn shadow-cljs watch test --verbose --config-merge '{:output-to \"$(SHADOW_OUTPUT_TO)\" :ns-regexp \"$(SHADOW_NS_REGEXP)\"}'" \ "until [ -f $$SHADOW_OUTPUT_TO ] ; do sleep 1 ; done ; node --require ./test-resources/override.js $$SHADOW_OUTPUT_TO --repl" test-unit: export SHADOW_OUTPUT_TO := target/unit_test/test.js From 3ab345563cb144c97e97f3fdd8d1cfe89b894994 Mon Sep 17 00:00:00 2001 From: Jamie Caprani Date: Wed, 24 Jul 2024 09:54:12 +0100 Subject: [PATCH 77/79] fix(wallet): adjust max amount to not include network being bridged to (#20604) --- .../wallet/bridge/input_amount/view.cljs | 23 +++++++++++-------- .../wallet/send/input_amount/view.cljs | 8 +++---- .../wallet/send/send_amount/view.cljs | 15 +++++++----- src/status_im/subs/wallet/send.cljs | 19 +++++++++++++++ 4 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/status_im/contexts/wallet/bridge/input_amount/view.cljs b/src/status_im/contexts/wallet/bridge/input_amount/view.cljs index 78a1902aff..a6a19583f5 100644 --- a/src/status_im/contexts/wallet/bridge/input_amount/view.cljs +++ b/src/status_im/contexts/wallet/bridge/input_amount/view.cljs @@ -12,13 +12,16 @@ (hot-reload/use-safe-unmount #(rf/dispatch [:wallet/clean-routes-calculation])) [rn/view {:style style/bridge-send-wrapper} [input-amount/view - {:current-screen-id :screen/wallet.bridge-input-amount - :button-one-label (i18n/label :t/review-bridge) - :button-one-props {:icon-left :i/bridge} - :on-confirm (fn [amount] - (rf/dispatch [:wallet/set-token-amount-to-bridge - {:amount amount - :stack-id :screen/wallet.bridge-input-amount}])) - :on-navigate-back (fn [] - (rf/dispatch [:wallet/clean-disabled-from-networks]) - (rf/dispatch [:wallet/clean-send-amount]))}]]) + {:current-screen-id :screen/wallet.bridge-input-amount + :button-one-label (i18n/label :t/review-bridge) + :button-one-props {:icon-left :i/bridge} + :enabled-from-chain-ids (rf/sub + [:wallet/bridge-from-chain-ids]) + :from-enabled-networks (rf/sub [:wallet/bridge-from-networks]) + :on-confirm (fn [amount] + (rf/dispatch [:wallet/set-token-amount-to-bridge + {:amount amount + :stack-id :screen/wallet.bridge-input-amount}])) + :on-navigate-back (fn [] + (rf/dispatch [:wallet/clean-disabled-from-networks]) + (rf/dispatch [:wallet/clean-send-amount]))}]]) diff --git a/src/status_im/contexts/wallet/send/input_amount/view.cljs b/src/status_im/contexts/wallet/send/input_amount/view.cljs index 63d7275321..0e8c1cca88 100644 --- a/src/status_im/contexts/wallet/send/input_amount/view.cljs +++ b/src/status_im/contexts/wallet/send/input_amount/view.cljs @@ -146,6 +146,8 @@ button-one-props :button-one-props current-screen-id :current-screen-id initial-crypto-currency? :initial-crypto-currency? + enabled-from-chain-ids :enabled-from-chain-ids + from-enabled-networks :from-enabled-networks :or {initial-crypto-currency? true}}] (let [_ (rn/dismiss-keyboard!) bottom (safe-area/get-bottom) @@ -164,9 +166,6 @@ token-decimals :decimals :as token} (rf/sub [:wallet/wallet-send-token]) - send-enabled-networks (rf/sub [:wallet/wallet-send-enabled-networks]) - enabled-from-chain-ids (rf/sub - [:wallet/wallet-send-enabled-from-chain-ids]) send-from-locked-amounts (rf/sub [:wallet/wallet-send-from-locked-amounts]) {token-balance :total-balance available-balance :available-balance @@ -355,7 +354,7 @@ :currency-symbol currency-symbol :crypto-decimals (min token-decimals 6) :error? (controlled-input/input-error input-state) - :networks (seq send-enabled-networks) + :networks (seq from-enabled-networks) :title (i18n/label :t/send-limit {:limit (if crypto-currency? @@ -429,3 +428,4 @@ (set-just-toggled-mode? false) (set-input-state controlled-input/delete-all) (rf/dispatch [:wallet/clean-suggested-routes]))}]])) + diff --git a/src/status_im/contexts/wallet/send/send_amount/view.cljs b/src/status_im/contexts/wallet/send/send_amount/view.cljs index 1277caf7d6..b11e69c9a4 100644 --- a/src/status_im/contexts/wallet/send/send_amount/view.cljs +++ b/src/status_im/contexts/wallet/send/send_amount/view.cljs @@ -8,9 +8,12 @@ (defn view [] [input-amount/view - {:current-screen-id :screen/wallet.send-input-amount - :button-one-label (i18n/label :t/review-send) - :on-navigate-back (fn [] - (rf/dispatch [:wallet/clean-disabled-from-networks]) - (rf/dispatch [:wallet/clean-from-locked-amounts]) - (rf/dispatch [:wallet/clean-send-amount]))}]) + {:current-screen-id :screen/wallet.send-input-amount + :button-one-label (i18n/label :t/review-send) + :enabled-from-chain-ids (rf/sub + [:wallet/wallet-send-enabled-from-chain-ids]) + :from-enabled-networks (rf/sub [:wallet/wallet-send-enabled-networks]) + :on-navigate-back (fn [] + (rf/dispatch [:wallet/clean-disabled-from-networks]) + (rf/dispatch [:wallet/clean-from-locked-amounts]) + (rf/dispatch [:wallet/clean-send-amount]))}]) diff --git a/src/status_im/subs/wallet/send.cljs b/src/status_im/subs/wallet/send.cljs index ad1045ba51..783adbda0f 100644 --- a/src/status_im/subs/wallet/send.cljs +++ b/src/status_im/subs/wallet/send.cljs @@ -61,3 +61,22 @@ :wallet/send-token-not-supported-in-receiver-networks? :<- [:wallet/wallet-send] :-> :token-not-supported-in-receiver-networks?) + +(rf/reg-sub + :wallet/bridge-from-networks + :<- [:wallet/wallet-send] + :<- [:wallet/network-details] + (fn [[{:keys [bridge-to-chain-id]} networks]] + (set (filter (fn [network] + (not= (:chain-id network) bridge-to-chain-id)) + networks)))) + +(rf/reg-sub + :wallet/bridge-from-chain-ids + :<- [:wallet/wallet-send] + :<- [:wallet/networks-by-mode] + (fn [[{:keys [bridge-to-chain-id]} networks]] + (keep (fn [network] + (when (not= (:chain-id network) bridge-to-chain-id) + (:chain-id network))) + networks))) From 07005f8ad5c1d9dcb76c49662709488510924bb0 Mon Sep 17 00:00:00 2001 From: Lungu Cristian Date: Wed, 24 Jul 2024 12:12:40 +0300 Subject: [PATCH 78/79] Reject typeddata request when wrong chainId inside typed data (#20821) * fix: reject typeddata request if wrong chainid * fix: lint --- .../contexts/wallet/wallet_connect/core.cljs | 9 +++- .../modals/common/page_nav/view.cljs | 2 +- .../wallet_connect/processing_events.cljs | 53 ++++++++++++++++--- .../wallet_connect/responding_events.cljs | 36 ++++++++----- .../wallet/wallet_connect/signing.cljs | 14 +++++ src/status_im/subs/wallet/wallet_connect.cljs | 6 +-- translations/en.json | 1 + 7 files changed, 94 insertions(+), 27 deletions(-) diff --git a/src/status_im/contexts/wallet/wallet_connect/core.cljs b/src/status_im/contexts/wallet/wallet_connect/core.cljs index 1a2f994461..00c568b603 100644 --- a/src/status_im/contexts/wallet/wallet_connect/core.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/core.cljs @@ -3,6 +3,7 @@ [clojure.string :as string] [native-module.core :as native-module] [status-im.constants :as constants] + [status-im.contexts.wallet.common.utils.networks :as networks] [utils.security.core :as security] [utils.string] [utils.transforms :as transforms])) @@ -101,7 +102,7 @@ networks (get-in db [:wallet :networks (if test-mode? :test :prod)])] (mapv #(-> % :chain-id) networks))) -(defn add-full-testnet-name +(defn- add-full-testnet-name "Updates the `:full-name` key with the full testnet name if using testnet `:chain-id`.\n e.g. `{:full-name \"Mainnet\"}` -> `{:full-name \"Mainnet Sepolia\"`}`" [network] @@ -112,6 +113,12 @@ constants/goerli-chain-ids (add-testnet-name constants/goerli-full-name) network))) +(defn chain-id->network-details + [chain-id] + (-> chain-id + (networks/get-network-details) + (add-full-testnet-name))) + (defn event-should-be-handled? [db {:keys [topic]}] (some #(= topic %) diff --git a/src/status_im/contexts/wallet/wallet_connect/modals/common/page_nav/view.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/common/page_nav/view.cljs index b16a526223..0a35fb8778 100644 --- a/src/status_im/contexts/wallet/wallet_connect/modals/common/page_nav/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/modals/common/page_nav/view.cljs @@ -7,6 +7,6 @@ [quo/page-nav {:icon-name :i/close :background :blur - :on-press #(do (rf/dispatch [:navigate-back]) + :on-press #(do (rf/dispatch [:wallet-connect/dismiss-request-modal]) (rf/dispatch [:wallet-connect/reject-session-request])) :accessibility-label accessibility-label}]) diff --git a/src/status_im/contexts/wallet/wallet_connect/processing_events.cljs b/src/status_im/contexts/wallet/wallet_connect/processing_events.cljs index 33adb4fc1d..f432ee7c2e 100644 --- a/src/status_im/contexts/wallet/wallet_connect/processing_events.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/processing_events.cljs @@ -5,8 +5,10 @@ [re-frame.core :as rf] [status-im.constants :as constants] [status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core] + [status-im.contexts.wallet.wallet-connect.signing :as signing] [status-im.contexts.wallet.wallet-connect.transactions :as transactions] [taoensso.timbre :as log] + [utils.i18n :as i18n] [utils.transforms :as transforms])) (rf/reg-event-fx @@ -122,15 +124,30 @@ :wallet-connect/process-sign-typed (fn [{:keys [db]}] (let [[address raw-data] (wallet-connect-core/get-db-current-request-params db) - parsed-data (try (-> raw-data - transforms/js-parse + parsed-raw-data (transforms/js-parse raw-data) + session-chain-id (-> (wallet-connect-core/get-db-current-request-event db) + (get-in [:params :chainId]) + wallet-connect-core/eip155->chain-id) + data-chain-id (-> parsed-raw-data + transforms/js->clj + signing/typed-data-chain-id) + parsed-data (try (-> parsed-raw-data (transforms/js-dissoc :types :primaryType) (transforms/js-stringify 2)) (catch js/Error _ nil))] - (if (nil? parsed-data) + (cond + (nil? parsed-data) {:fx [[:dispatch [:wallet-connect/on-processing-error (ex-info "Failed to parse JSON typed data" {:data raw-data})]]]} + + (not= session-chain-id data-chain-id) + {:fx [[:dispatch + [:wallet-connect/wrong-typed-data-chain-id + {:expected-chain-id session-chain-id + :wrong-chain-id data-chain-id}]]]} + + :else {:db (update-in db [:wallet-connect/current-request] assoc @@ -139,19 +156,39 @@ :raw-data raw-data) :fx [[:dispatch [:wallet-connect/show-request-modal]]]})))) +(rf/reg-event-fx + :wallet-connect/wrong-typed-data-chain-id + (fn [_ [{:keys [expected-chain-id wrong-chain-id]}]] + (let [wrong-network-name (-> wrong-chain-id + wallet-connect-core/chain-id->network-details + :full-name) + expected-network-name (-> expected-chain-id + wallet-connect-core/chain-id->network-details + :full-name) + toast-message (i18n/label :t/wallet-connect-typed-data-wrong-chain-id-warning + {:wrong-chain wrong-network-name + :expected-chain expected-network-name})] + {:fx [[:dispatch + [:toasts/upsert + {:type :negative + :theme :dark + :text toast-message}]] + [:dispatch + [:wallet-connect/on-processing-error + (ex-info "Can't proceed signing typed data due to wrong chain-id included in the data" + {:expected-chain-id expected-chain-id + :wrong-chain-id wrong-chain-id})]]]}))) + ;; TODO: we should reject a request if processing fails (rf/reg-event-fx :wallet-connect/on-processing-error (fn [{:keys [db]} [error]] (let [{:keys [address event]} (get db :wallet-connect/current-request) - method (wallet-connect-core/get-request-method event) - screen (wallet-connect-core/method-to-screen method)] + method (wallet-connect-core/get-request-method event)] (log/error "Failed to process Wallet Connect request" {:error error :address address :method method :wallet-connect-event event :event :wallet-connect/on-processing-error}) - - {:fx [[:dispatch [:dismiss-modal screen]] - [:dispatch [:wallet-connect/reset-current-request]]]}))) + {:fx [[:dispatch [:wallet-connect/reject-session-request]]]}))) diff --git a/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs b/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs index 16c2c9a6e7..a487c09911 100644 --- a/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs @@ -39,7 +39,7 @@ :data raw-data :rpc-method rpc-method :on-error #(rf/dispatch [:wallet-connect/on-sign-error %]) - :on-success #(rf/dispatch [:wallet-connect/send-response {:result %}])}]]}))) + :on-success #(rf/dispatch [:wallet-connect/finish-session-request %])}]]}))) (rf/reg-event-fx :wallet-connect/respond-sign-typed-data @@ -53,7 +53,7 @@ :chain-id chain-id :version typed-data-version :on-error #(rf/dispatch [:wallet-connect/on-sign-error %]) - :on-success #(rf/dispatch [:wallet-connect/send-response {:result %}])}]]}))) + :on-success #(rf/dispatch [:wallet-connect/finish-session-request %])}]]}))) (rf/reg-event-fx :wallet-connect/respond-send-transaction-data @@ -67,7 +67,7 @@ :tx-hash tx-hash :tx-args tx-args :on-error #(rf/dispatch [:wallet-connect/on-sign-error %]) - :on-success #(rf/dispatch [:wallet-connect/send-response {:result %}])}]]}))) + :on-success #(rf/dispatch [:wallet-connect/finish-session-request %])}]]}))) (rf/reg-event-fx :wallet-connect/respond-sign-transaction-data @@ -81,15 +81,13 @@ :tx-hash tx-hash :tx-params tx-args :on-error #(rf/dispatch [:wallet-connect/on-sign-error %]) - :on-success #(rf/dispatch [:wallet-connect/send-response {:result %}])}]]}))) + :on-success #(rf/dispatch [:wallet-connect/finish-session-request %])}]]}))) -;; TODO: should reject if "signing" fails (rf/reg-event-fx :wallet-connect/on-sign-error (fn [{:keys [db]} [error]] (let [{:keys [raw-data address event]} (get db :wallet-connect/current-request) - method (wallet-connect-core/get-request-method event) - screen (wallet-connect-core/method-to-screen method)] + method (wallet-connect-core/get-request-method event)] (log/error "Failed to sign Wallet Connect request" {:error error :address address @@ -97,15 +95,14 @@ :method method :wallet-connect-event event :event :wallet-connect/on-sign-error}) - {:fx [[:dispatch [:dismiss-modal screen]] - [:dispatch [:wallet-connect/reset-current-request]]]}))) + {:fx [[:dispatch [:wallet-connect/reject-session-request]] + [:dispatch [:wallet-connect/dismiss-request-modal]]]}))) (rf/reg-event-fx :wallet-connect/send-response (fn [{:keys [db]} [{:keys [result error]}]] (let [{:keys [id topic] :as event} (get-in db [:wallet-connect/current-request :event]) method (wallet-connect-core/get-request-method event) - screen (wallet-connect-core/method-to-screen method) web3-wallet (get db :wallet-connect/web3-wallet)] {:fx [[:effects.wallet-connect/respond-session-request {:web3-wallet web3-wallet @@ -119,13 +116,26 @@ :method method :event :wallet-connect/send-response :wallet-connect-event event}) - (rf/dispatch [:dismiss-modal screen]) (rf/dispatch [:wallet-connect/reset-current-request])) :on-success (fn [] (log/info "Successfully sent Wallet Connect response to dApp") - (rf/dispatch [:dismiss-modal screen]) (rf/dispatch [:wallet-connect/reset-current-request]))}]]}))) +(rf/reg-event-fx + :wallet-connect/dismiss-request-modal + (fn [{:keys [db]} _] + (let [screen (-> db + (get-in [:wallet-connect/current-request :event]) + wallet-connect-core/get-request-method + wallet-connect-core/method-to-screen)] + {:fx [[:dispatch [:dismiss-modal screen]]]}))) + +(rf/reg-event-fx + :wallet-connect/finish-session-request + (fn [_ [result]] + {:fx [[:dispatch [:wallet-connect/send-response {:result result}]] + [:dispatch [:wallet-connect/dismiss-request-modal]]]})) + (rf/reg-event-fx :wallet-connect/reject-session-proposal (fn [{:keys [db]} _] @@ -143,6 +153,8 @@ ;; - Unsupported WC version ;; - Invalid params from dapps ;; - Unsupported method +;; - Failed processing of request +;; - Failed "responding" (signing or sending message/transaction) (rf/reg-event-fx :wallet-connect/reject-session-request (fn [_ _] diff --git a/src/status_im/contexts/wallet/wallet_connect/signing.cljs b/src/status_im/contexts/wallet/wallet_connect/signing.cljs index 29ecec1021..575661a902 100644 --- a/src/status_im/contexts/wallet/wallet_connect/signing.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/signing.cljs @@ -6,6 +6,20 @@ [utils.hex :as hex] [utils.transforms :as transforms])) +(defn typed-data-chain-id + "Returns the `:chain-id` from typed data if it's present and if the EIP712 domain defines it. Without + the `:chain-id` in the domain type, it will not be signed as part of the typed-data." + [typed-data] + (let [chain-id-type? (->> typed-data + :types + :EIP712Domain + (some #(= "chainId" (:name %)))) + data-chain-id (-> typed-data + :domain + :chainId)] + (when chain-id-type? + data-chain-id))) + (defn eth-sign [password address data] (-> {:data data diff --git a/src/status_im/subs/wallet/wallet_connect.cljs b/src/status_im/subs/wallet/wallet_connect.cljs index eae794c7c7..ea1eaf96ac 100644 --- a/src/status_im/subs/wallet/wallet_connect.cljs +++ b/src/status_im/subs/wallet/wallet_connect.cljs @@ -2,7 +2,6 @@ (:require [clojure.string :as string] [re-frame.core :as rf] [status-im.contexts.wallet.common.utils :as wallet-utils] - [status-im.contexts.wallet.common.utils.networks :as networks] [status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core] [status-im.contexts.wallet.wallet-connect.transactions :as transactions] [utils.money :as money] @@ -70,10 +69,7 @@ (rf/reg-sub :wallet-connect/current-request-network :<- [:wallet-connect/chain-id] - (fn [chain-id] - (-> chain-id - (networks/get-network-details) - (wallet-connect-core/add-full-testnet-name)))) + wallet-connect-core/chain-id->network-details) (rf/reg-sub :wallet-connect/transaction-args diff --git a/translations/en.json b/translations/en.json index cf047a9325..f94d2463a6 100644 --- a/translations/en.json +++ b/translations/en.json @@ -2642,6 +2642,7 @@ "wallet-connect-sign-transaction-header": "wants you to sign this transaction with", "wallet-connect-sign-transaction-warning": "Sign transactions only if you trust the dApp", "wallet-connect-sign-warning": "Sign only if you trust the dApp", + "wallet-connect-typed-data-wrong-chain-id-warning": "Wrong network in the request data. Expected '{{expected-chain}}', but got '{{wrong-chain}}'", "wallet-connect-version-not-supported": "WalletConnect version {{version}} is not supported", "wallet-connect-via": "via", "wallet-connect-wrong-qr": "It’s not a WalletConnect QR", From c6a63e30b281f631a47f0774fe2b4575150d8903 Mon Sep 17 00:00:00 2001 From: Lungu Cristian Date: Wed, 24 Jul 2024 13:54:30 +0300 Subject: [PATCH 79/79] fix: usage of web3-wallet (#20864) --- src/react_native/wallet_connect.cljs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/react_native/wallet_connect.cljs b/src/react_native/wallet_connect.cljs index 5001f2d7e4..168b61d5ac 100644 --- a/src/react_native/wallet_connect.cljs +++ b/src/react_native/wallet_connect.cljs @@ -51,9 +51,10 @@ (defn reject-session [{:keys [web3-wallet id reason]}] - (.rejectSession web3-wallet - (clj->js {:id id - :reason reason}))) + (oops/ocall web3-wallet + "rejectSession" + (bean/->js {:id id + :reason reason}))) (defn approve-session [{:keys [web3-wallet id approved-namespaces]}]