Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e03757b4d4 | ||
|
|
46501d611d | ||
|
|
f0e69b1f45 | ||
|
|
6a33c4723a | ||
|
|
6a26ebafa3 | ||
|
|
3c55fc1fa2 | ||
|
|
15a1a15c08 | ||
|
|
9e43aa04c7 | ||
|
|
18f7d05a61 | ||
|
|
70e325fd2b | ||
|
|
fba631c044 | ||
|
|
598617cf2b | ||
|
|
66f177370a | ||
|
|
ad7d58788f | ||
|
|
ad4ea0783f | ||
|
|
5c44cb6399 | ||
|
|
a9e0b3dd6b | ||
|
|
131da5f444 | ||
|
|
874ececc47 | ||
|
|
5f1d74a77a | ||
|
|
00b0d75fc4 | ||
|
|
4b93c8ff43 | ||
|
|
80ad2b8fca | ||
|
|
a8bc93eb17 | ||
|
|
9d3fc4286e |
@@ -51,7 +51,6 @@ in {
|
||||
};
|
||||
|
||||
# Package version adjustments
|
||||
gradle = super.gradle_8.override { java = super.openjdk17_headless; };
|
||||
nodejs = super.nodejs_20;
|
||||
ruby = super.ruby_3_1;
|
||||
yarn = super.yarn.override { nodejs = super.nodejs_20; };
|
||||
|
||||
+18
-2
@@ -6,8 +6,16 @@ let
|
||||
# For testing local version of nixpkgs
|
||||
#nixpkgsSrc = (import <nixpkgs> { }).lib.cleanSource "/home/jakubgs/work/nixpkgs";
|
||||
|
||||
# We use a commit from the unstable channel of nixpkgs for gradle 8.8
|
||||
# We follow release 24-05 of nixpkgs
|
||||
# https://github.com/NixOS/nixpkgs/releases/tag/24.05
|
||||
nixpkgsSrc = builtins.fetchTarball {
|
||||
url = "https://github.com/NixOS/nixpkgs/archive/df27247e6f3e636c119e2610bf12d38b5e98cc79.tar.gz";
|
||||
sha256 = "sha256:0bbvimk7xb7akrx106mmsiwf9nzxnssisqmqffla03zz51d0kz2n";
|
||||
};
|
||||
|
||||
# FIXME: remove this additional source when nixpkgs includes gradle 8.8 in stable channel
|
||||
# We use a commit from the unstable channel of nixpkgs for gradle 8.8
|
||||
gradleNixpkgsSrc = builtins.fetchTarball {
|
||||
url = "https://github.com/NixOS/nixpkgs/archive/48d567fc7b299de90f70a48e4263e31f690ba03e.tar.gz";
|
||||
sha256 = "sha256:0zynbk52khdfhg4qfv26h3r5156xff5p0cga2cin7b07i7lqminh";
|
||||
};
|
||||
@@ -21,6 +29,14 @@ let
|
||||
# Override some packages and utilities
|
||||
pkgsOverlay = import ./overlay.nix;
|
||||
|
||||
# FIXME: remove this additional source when nixpkgs includes gradle 8.8 in stable channel
|
||||
gradleOverlay = final: prev: {
|
||||
gradle = (import gradleNixpkgsSrc {
|
||||
inherit (prev) system;
|
||||
config = defaultConfig // config;
|
||||
}).gradle_8.override { java = prev.openjdk17_headless; };
|
||||
};
|
||||
|
||||
# Fix for lack of Android SDK for M1 Macs.
|
||||
systemOverride = let
|
||||
inherit (builtins) currentSystem getEnv;
|
||||
@@ -35,5 +51,5 @@ in
|
||||
(import nixpkgsSrc) {
|
||||
config = defaultConfig // config;
|
||||
system = systemOverride;
|
||||
overlays = [ pkgsOverlay ];
|
||||
overlays = [ pkgsOverlay gradleOverlay ];
|
||||
}
|
||||
|
||||
@@ -180,17 +180,17 @@
|
||||
},
|
||||
"shards.staging": {
|
||||
"tcp/p2p/waku/boot": {
|
||||
"boot-01.do-ams3.shards.staging": "/dns4/boot-01.do-ams3.shards.staging.status.im/tcp/30303/p2p/16Uiu2HAmEqqio4UR1SWqAc7KY19t6qyDvtmyjreZpzUBJvb4u65R",
|
||||
"boot-01.do-ams3.shards.staging": "/dns4/boot-01.do-ams3.shards.staging.status.im/tcp/30303/p2p/16Uiu2HAmQE7FXQc6iZHdBzYfw3qCSDa9dLc1wsBJKoP4aZvztq2d",
|
||||
"boot-01.gc-us-central1-a.shards.staging": "/dns4/boot-01.gc-us-central1-a.shards.staging.status.im/tcp/30303/p2p/16Uiu2HAmGAA54bBTE78MYidSy3P7Q9yAWFNTAEReJYD69VRvtL5r",
|
||||
"boot-01.ac-cn-hongkong-c.shards.staging": "/dns4/boot-01.ac-cn-hongkong-c.shards.staging.status.im/tcp/30303/p2p/16Uiu2HAmNTpGnyZ8W1BK2sXEmgSCNWiyDKgRU3NBR2DXST2HzxRU"
|
||||
},
|
||||
"enr/p2p/waku/boot": {
|
||||
"boot-01.do-ams3.shards.staging": "enr:-QEQuEDsh6FgAb_36cReaX7W4gWx_7_GNpsUki7bXMoMrrrWij5pDEyV3guR-urDW_6GJTAzpQiJV61F-CfNn_NxPbY-AYJpZIJ2NIJpcISPxvrpim11bHRpYWRkcnO4YAAtNihib290LTAxLmRvLWFtczMuc2hhcmRzLnN0YWdpbmcuc3RhdHVzLmltBnZfAC82KGJvb3QtMDEuZG8tYW1zMy5zaGFyZHMuc3RhZ2luZy5zdGF0dXMuaW0GAbveA4Jyc40AEAUAAQAgAEAAgAEAiXNlY3AyNTZrMaEDIH8BcuEzgnmwPQTu7BPYyg4u4om7K9qekKA2gT_H2wSDdGNwgnZfg3VkcIIjKIV3YWt1Mg0",
|
||||
"boot-01.do-ams3.shards.staging": "enr:-QEQuEBuiQgFlJNcv255042zwyl4pOBOivakX8N30Dr9vaaEU2q8-7N4GVY4Hk87iEKELjlIXTpE9Wj6EQq1lrBuc7ayAYJpZIJ2NIJpcISPxvrpim11bHRpYWRkcnO4YAAtNihib290LTAxLmRvLWFtczMuc3RhdHVzLnN0YWdpbmcuc3RhdHVzLmltBnZfAC82KGJvb3QtMDEuZG8tYW1zMy5zdGF0dXMuc3RhZ2luZy5zdGF0dXMuaW0GAbveA4Jyc40AEAUAAQAgAEAAgAEAiXNlY3AyNTZrMaEDq-yGgpuoUG6NKkbIDRmrMiT-bEVzFlpWLEK_rF3yKUaDdGNwgnZfg3VkcIIjKIV3YWt1Mg0",
|
||||
"boot-01.gc-us-central1-a.shards.staging": "enr:-QEiuECWCuk1CoefbPYUV_cff27t-471k_QhJ_MvgOUQmRE4vgI6h9fD4LgXy7TZNSFXeBBP9haWJB7lx8Wujx-LPmhrAYJpZIJ2NIJpcIRoxQVgim11bHRpYWRkcnO4cgA2NjFib290LTAxLmdjLXVzLWNlbnRyYWwxLWEuc2hhcmRzLnN0YWdpbmcuc3RhdHVzLmltBnZfADg2MWJvb3QtMDEuZ2MtdXMtY2VudHJhbDEtYS5zaGFyZHMuc3RhZ2luZy5zdGF0dXMuaW0GAbveA4Jyc40AEAUAAQAgAEAAgAEAiXNlY3AyNTZrMaEDNAvlGjekD1YV4WpmjwArGAH2g9kHFJnMRfgUhcIkoA2DdGNwgnZfg3VkcIIjKIV3YWt1Mg0",
|
||||
"boot-01.ac-cn-hongkong-c.shards.staging": "enr:-QEiuEAcHWW7aNBtJhigTCAnKu9-H1b98SWpBJkGm3PAxth5QjJA0kdiabOfou4HgCD6RuyZEnFE5L8ymahouw3kWRiWAYJpZIJ2NIJpcIQvTKi6im11bHRpYWRkcnO4cgA2NjFib290LTAxLmFjLWNuLWhvbmdrb25nLWMuc2hhcmRzLnN0YWdpbmcuc3RhdHVzLmltBnZfADg2MWJvb3QtMDEuYWMtY24taG9uZ2tvbmctYy5zaGFyZHMuc3RhZ2luZy5zdGF0dXMuaW0GAbveA4Jyc40AEAUAAQAgAEAAgAEAiXNlY3AyNTZrMaEDkbgV7oqPNmFtX5FzSPi9WH8kkmrPB1R3n9xRXge91M-DdGNwgnZfg3VkcIIjKIV3YWt1Mg0"
|
||||
},
|
||||
"wss/p2p/waku/boot": {
|
||||
"boot-01.do-ams3.shards.staging": "/dns4/boot-01.do-ams3.shards.staging.status.im/tcp/443/wss/p2p/16Uiu2HAmEqqio4UR1SWqAc7KY19t6qyDvtmyjreZpzUBJvb4u65R",
|
||||
"boot-01.do-ams3.shards.staging": "/dns4/boot-01.do-ams3.shards.staging.status.im/tcp/443/wss/p2p/16Uiu2HAmQE7FXQc6iZHdBzYfw3qCSDa9dLc1wsBJKoP4aZvztq2d",
|
||||
"boot-01.gc-us-central1-a.shards.staging": "/dns4/boot-01.gc-us-central1-a.shards.staging.status.im/tcp/443/wss/p2p/16Uiu2HAmGAA54bBTE78MYidSy3P7Q9yAWFNTAEReJYD69VRvtL5r",
|
||||
"boot-01.ac-cn-hongkong-c.shards.staging": "/dns4/boot-01.ac-cn-hongkong-c.shards.staging.status.im/tcp/443/wss/p2p/16Uiu2HAmNTpGnyZ8W1BK2sXEmgSCNWiyDKgRU3NBR2DXST2HzxRU"
|
||||
},
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
@@ -256,9 +256,10 @@
|
||||
{:events [:pairing/pair-and-sync]}
|
||||
[cofx installation-id]
|
||||
{:fx [[:json-rpc/call
|
||||
[{:method "wakuext_enableInstallationAndSync"
|
||||
:params [{:installationId installation-id}]
|
||||
:on-success #(log/debug "successfully synced devices")}]]]})
|
||||
[{:method "wakuext_enableInstallationAndSync"
|
||||
:params [{:installationId installation-id}]
|
||||
:js-response true
|
||||
:on-success [:sanitize-messages-and-process-response]}]]]})
|
||||
|
||||
(rf/defn enable-installation-success
|
||||
{:events [:pairing.callback/enable-installation-success]}
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
:type type
|
||||
:include-button? include-button?})
|
||||
style)}
|
||||
[icons/icon icon
|
||||
[icons/icon (or icon :i/info)
|
||||
{:color (style/get-color-by-type theme type :icon)
|
||||
:no-color no-icon-color?
|
||||
:size (or icon-size 16)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
(ns quo.components.list-items.network-list.view
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[quo.components.icon :as icon]
|
||||
[quo.components.list-items.network-list.style :as style]
|
||||
[quo.components.markdown.text :as text]
|
||||
[quo.theme :as quo.theme]
|
||||
@@ -42,8 +43,8 @@
|
||||
[:map {:closed true}
|
||||
[:network-image :int]
|
||||
[:label :string]
|
||||
[:fiat-value :string]
|
||||
[:token-value :string]
|
||||
[:fiat-value {:optional true} [:maybe :string]]
|
||||
[:token-value {:optional true} [:maybe :string]]
|
||||
[:container-style {:optional true} [:maybe :map]]
|
||||
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
|
||||
[:state {:optional true} [:enum :pressed :active :disabled :default]]
|
||||
@@ -51,7 +52,7 @@
|
||||
:any])
|
||||
|
||||
(defn- view-internal
|
||||
[{:keys [on-press container-style state customization-color]
|
||||
[{:keys [fiat-value token-value on-press container-style state customization-color]
|
||||
:as props
|
||||
:or {customization-color :blue}}]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
@@ -67,6 +68,8 @@
|
||||
:on-press (when-not (= state :disabled) on-press)
|
||||
:accessibility-label :network-list}
|
||||
[info props]
|
||||
[values props]]))
|
||||
(if (or token-value fiat-value)
|
||||
[values props]
|
||||
[icon/icon :i/chevron-right])]))
|
||||
|
||||
(def view (schema/instrument #'view-internal ?schema))
|
||||
|
||||
@@ -4,12 +4,10 @@
|
||||
[container-style]
|
||||
(merge container-style
|
||||
{:flex-direction :row
|
||||
:flex 1
|
||||
:align-items :flex-start}))
|
||||
|
||||
(def index
|
||||
{:margin-left 5})
|
||||
|
||||
(def text-container
|
||||
{:margin-left 8
|
||||
:flex 1})
|
||||
{:margin-left 8})
|
||||
|
||||
@@ -75,3 +75,16 @@
|
||||
|
||||
(def fiat-amount
|
||||
{:color colors/neutral-50})
|
||||
|
||||
(def gradient-common
|
||||
{:width 64
|
||||
:position :absolute
|
||||
:top 0
|
||||
:bottom 0
|
||||
:z-index 1})
|
||||
|
||||
(def gradient-start
|
||||
(assoc gradient-common :left 0))
|
||||
|
||||
(def gradient-end
|
||||
(assoc gradient-common :right 0))
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
[quo.foundations.colors :as colors]
|
||||
quo.theme
|
||||
[react-native.core :as rn]
|
||||
[react-native.linear-gradient :as linear-gradient]
|
||||
[schema.core :as schema]))
|
||||
|
||||
(def ?schema
|
||||
@@ -18,6 +19,7 @@
|
||||
[:catn
|
||||
[:props
|
||||
[:map {:closed true}
|
||||
[:get-ref {:optional true} [:maybe fn?]]
|
||||
[:type {:optional true} [:maybe [:enum :pay :receive]]]
|
||||
[:status {:optional true} [:maybe [:enum :default :typing :disabled :loading]]]
|
||||
[:token {:optional true} [:maybe :string]]
|
||||
@@ -42,26 +44,65 @@
|
||||
[:container-style {:optional true} [:maybe :map]]]]]
|
||||
:any])
|
||||
|
||||
(def icon-size 32)
|
||||
(def container-padding (* 2 (:padding (style/row-1 false))))
|
||||
(def max-cursor-position 5)
|
||||
|
||||
(defn view-internal
|
||||
[{:keys [type status token value fiat-value show-approval-label? error? network-tag-props
|
||||
approval-label-props default-value auto-focus? input-disabled? enable-swap?
|
||||
currency-symbol on-change-text show-keyboard?
|
||||
currency-symbol on-change-text show-keyboard? get-ref
|
||||
container-style on-swap-press on-token-press on-max-press on-input-focus]}]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
pay? (= type :pay)
|
||||
disabled? (= status :disabled)
|
||||
loading? (= status :loading)
|
||||
typing? (= status :typing)
|
||||
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])]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
pay? (= type :pay)
|
||||
disabled? (= status :disabled)
|
||||
loading? (= status :loading)
|
||||
typing? (= status :typing)
|
||||
controlled-input? (some? value)
|
||||
[container-width
|
||||
set-container-width] (rn/use-state)
|
||||
[overflow?
|
||||
set-overflow?] (rn/use-state false)
|
||||
[cursor-close-to-start?
|
||||
set-cursor-close-to-start?] (rn/use-state false)
|
||||
[label-width
|
||||
set-label-width] (rn/use-state 0)
|
||||
input-ref (rn/use-ref-atom nil)
|
||||
set-input-ref (rn/use-callback (fn [ref]
|
||||
(reset! input-ref ref)
|
||||
(when get-ref (get-ref ref)))
|
||||
[])
|
||||
focus-input (rn/use-callback (fn []
|
||||
(some-> @input-ref
|
||||
(oops/ocall "focus")))
|
||||
[input-ref])
|
||||
on-layout-container (rn/use-callback
|
||||
(fn [e]
|
||||
(let [width (oops/oget e "nativeEvent.layout.width")]
|
||||
(set-container-width width))))
|
||||
on-layout-text-input (rn/use-callback
|
||||
(fn [e]
|
||||
(let [width (oops/oget e "nativeEvent.layout.width")
|
||||
max-width (- container-width
|
||||
icon-size
|
||||
container-padding
|
||||
(* label-width 2))]
|
||||
(set-overflow? (> width max-width))))
|
||||
[container-width label-width])
|
||||
on-layout-label (rn/use-callback
|
||||
(fn [e]
|
||||
(let [width (oops/oget e "nativeEvent.layout.width")]
|
||||
(set-label-width width))))
|
||||
on-selection-change (rn/use-callback
|
||||
(fn [e]
|
||||
(let [selection-start (oops/oget e
|
||||
"nativeEvent.selection.start")]
|
||||
(set-cursor-close-to-start?
|
||||
(< selection-start max-cursor-position)))))]
|
||||
[rn/view
|
||||
{:style container-style
|
||||
:accessibility-label :swap-input}
|
||||
:accessibility-label :swap-input
|
||||
:on-layout on-layout-container}
|
||||
[rn/view {:style (style/content typing? theme)}
|
||||
[rn/view
|
||||
{:style (style/row-1 loading?)}
|
||||
@@ -75,25 +116,43 @@
|
||||
[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
|
||||
:editable (not input-disabled?)
|
||||
:auto-focus auto-focus?
|
||||
:on-focus on-input-focus
|
||||
:on-change-text on-change-text
|
||||
:show-soft-input-on-focus show-keyboard?
|
||||
:default-value default-value
|
||||
:placeholder "0"}
|
||||
controlled-input? (assoc :value value))]
|
||||
[rn/view {:style {:flex-shrink 1}}
|
||||
(when (and overflow? typing? (not cursor-close-to-start?))
|
||||
[linear-gradient/linear-gradient
|
||||
{:start {:x 0 :y 0}
|
||||
:end {:x 1 :y 0}
|
||||
:colors [(colors/theme-colors colors/white colors/neutral-100 theme)
|
||||
(colors/theme-colors colors/white-opa-10 colors/neutral-100-opa-10 theme)]
|
||||
:style style/gradient-start}])
|
||||
(when (and overflow? disabled?)
|
||||
[linear-gradient/linear-gradient
|
||||
{:start {:x 0 :y 0}
|
||||
:end {:x 1 :y 0}
|
||||
:colors [(colors/theme-colors colors/white-opa-10 colors/neutral-100-opa-10 theme)
|
||||
(colors/theme-colors colors/white colors/neutral-100 theme)]
|
||||
:style style/gradient-end}])
|
||||
[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
|
||||
:editable (not input-disabled?)
|
||||
:auto-focus auto-focus?
|
||||
:on-focus on-input-focus
|
||||
:on-change-text on-change-text
|
||||
:on-layout on-layout-text-input
|
||||
:on-selection-change on-selection-change
|
||||
: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)}
|
||||
{:size :paragraph-2
|
||||
:weight :semi-bold
|
||||
:style (style/token-symbol theme)
|
||||
:on-layout on-layout-label}
|
||||
token]]
|
||||
(when (and pay? enable-swap?)
|
||||
[buttons/button
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
(take 7)))
|
||||
|
||||
(defn recovery-phrase-screen
|
||||
[{:keys [banner-offset initial-insets keypair title recovering-keypair? render-controls]}]
|
||||
[{:keys [banner-offset initial-insets keypair title on-success render-controls]}]
|
||||
(reagent/with-let [keyboard-shown? (reagent/atom false)
|
||||
keyboard-show-listener (.addListener rn/keyboard
|
||||
"keyboardDidShow"
|
||||
@@ -120,14 +120,20 @@
|
||||
(reset! seed-phrase new-phrase))
|
||||
on-submit (fn []
|
||||
(swap! seed-phrase clean-seed-phrase)
|
||||
(if recovering-keypair?
|
||||
(rf/dispatch [:wallet/seed-phrase-entered
|
||||
(security/mask-data
|
||||
@seed-phrase)
|
||||
set-invalid-seed-phrase])
|
||||
(rf/dispatch [:onboarding/seed-phrase-entered
|
||||
(security/mask-data @seed-phrase)
|
||||
set-invalid-seed-phrase])))]
|
||||
(rf/dispatch
|
||||
[:profile.recover/validate-recovery-phrase
|
||||
(security/mask-data @seed-phrase)
|
||||
{:on-success (fn [mnemonic key-uid]
|
||||
(if on-success
|
||||
(on-success
|
||||
{:key-uid key-uid
|
||||
:phrase mnemonic
|
||||
:on-error
|
||||
set-invalid-seed-phrase})
|
||||
(rf/dispatch
|
||||
[:onboarding/seed-phrase-validated
|
||||
mnemonic key-uid])))
|
||||
:on-error set-invalid-seed-phrase}]))]
|
||||
(let [words-coll (mnemonic/passphrase->words @seed-phrase)
|
||||
last-word (peek words-coll)
|
||||
pick-suggested-word (fn [pressed-word]
|
||||
@@ -191,7 +197,7 @@
|
||||
[rn/view {:style style/keyboard-container}
|
||||
[quo/predictive-keyboard
|
||||
{:type suggestions-state
|
||||
:blur? (not recovering-keypair?)
|
||||
:blur? (not on-success)
|
||||
:text suggestions-text
|
||||
:words (keyboard-suggestions last-word)
|
||||
:on-press pick-suggested-word}]])])
|
||||
@@ -200,7 +206,7 @@
|
||||
(.remove keyboard-hide-listener))))
|
||||
|
||||
(defn screen
|
||||
[{:keys [title keypair navigation-icon recovering-keypair? render-controls]}]
|
||||
[{:keys [title keypair navigation-icon on-success render-controls]}]
|
||||
(let [[insets _] (rn/use-state (safe-area/get-insets))
|
||||
banner-offset (rf/sub [:alert-banners/top-margin])]
|
||||
[rn/view {:style style/full-layout}
|
||||
@@ -209,21 +215,22 @@
|
||||
{:margin-top (:top insets)
|
||||
:background :blur
|
||||
:icon-name (or navigation-icon
|
||||
(if recovering-keypair? :i/close :i/arrow-left))
|
||||
(if on-success :i/close :i/arrow-left))
|
||||
:on-press #(rf/dispatch [:navigate-back])}]
|
||||
[recovery-phrase-screen
|
||||
{:title title
|
||||
:keypair keypair
|
||||
:render-controls render-controls
|
||||
:banner-offset banner-offset
|
||||
:initial-insets insets
|
||||
:recovering-keypair? recovering-keypair?}]]]))
|
||||
{:title title
|
||||
:keypair keypair
|
||||
:render-controls render-controls
|
||||
:banner-offset banner-offset
|
||||
:initial-insets insets
|
||||
:on-success on-success}]]]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [{:keys [recovering-keypair?]} (rf/sub [:get-screen-params])]
|
||||
(let [{:keys [on-success]} (rf/sub [:get-screen-params])]
|
||||
(rn/use-unmount
|
||||
#(rf/dispatch [:onboarding/clear-navigated-to-enter-seed-phrase-from-screen]))
|
||||
(when-not on-success
|
||||
#(rf/dispatch [:onboarding/clear-navigated-to-enter-seed-phrase-from-screen])))
|
||||
[screen
|
||||
{:title (i18n/label :t/use-recovery-phrase)
|
||||
:recovering-keypair? recovering-keypair?}]))
|
||||
{:title (i18n/label :t/use-recovery-phrase)
|
||||
:on-success on-success}]))
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
:generate-keys1 (js/require "../resources/images/ui2/generating-keys-1.png")
|
||||
:ethereum-address (js/require "../resources/images/ui2/ethereum-address.png")
|
||||
:use-keycard (js/require "../resources/images/ui2/keycard.png")
|
||||
:check-your-keycard (js/require "../resources/images/ui2/check-your-keycard.png")
|
||||
:onboarding-illustration (js/require "../resources/images/ui2/onboarding_illustration.png")
|
||||
:qr-code (js/require "../resources/images/ui2/qr-code.png")
|
||||
:keycard-logo (js/require "../resources/images/ui2/keycard-logo.png")
|
||||
|
||||
@@ -45,3 +45,15 @@
|
||||
#(rf/dispatch [:profile.login/login-with-biometric-if-available
|
||||
(get-in db [:profile/login :key-uid])]))
|
||||
:shell? true}]]]})))
|
||||
|
||||
(rf/reg-fx :effects.centralized-metrics/track
|
||||
(fn [event]
|
||||
(native-module/add-centralized-metric event)))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:centralized-metrics/track
|
||||
(fn [{:keys [db]} [event-name data]]
|
||||
(let [event-id (name event-name)]
|
||||
(when (push-event? db)
|
||||
{:fx [[:effects.centralized-metrics/track
|
||||
(tracking/key-value-event event-id data)]]}))))
|
||||
|
||||
@@ -4,20 +4,20 @@
|
||||
[react-native.platform :as platform]))
|
||||
|
||||
(defn key-value-event
|
||||
[event-name val-key value]
|
||||
[event-name event-value]
|
||||
{:metric
|
||||
{:eventName event-name
|
||||
:platform platform/os
|
||||
:appVersion build/app-short-version
|
||||
:eventValue {val-key value}}})
|
||||
:eventValue event-value}})
|
||||
|
||||
(defn user-journey-event
|
||||
[action]
|
||||
(key-value-event "user-journey" :action action))
|
||||
(key-value-event "user-journey" {:action action}))
|
||||
|
||||
(defn navigation-event
|
||||
[view-id]
|
||||
(key-value-event "navigation" :viewId view-id))
|
||||
(key-value-event "navigation" {:viewId view-id}))
|
||||
|
||||
(def ^:const app-started-event "app-started")
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
(user-journey-event app-started-event)
|
||||
|
||||
:centralized-metrics/toggle-centralized-metrics
|
||||
(key-value-event "events.metrics-enabled" :enabled second-parameter)
|
||||
(key-value-event "events.metrics-enabled" {:enabled second-parameter})
|
||||
|
||||
:set-view-id
|
||||
(track-view-id-event second-parameter)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
:platform platform-os
|
||||
:appVersion app-version
|
||||
:eventValue {val-key value}}}]
|
||||
(is (= expected (tracking/key-value-event event-name val-key value))))))
|
||||
(is (= expected (tracking/key-value-event event-name {val-key value}))))))
|
||||
|
||||
(deftest user-journey-event-test
|
||||
(testing "creates correct user journey event"
|
||||
|
||||
+17
-19
@@ -11,25 +11,23 @@
|
||||
|
||||
(defn view
|
||||
[{:keys [contact-id]}]
|
||||
(let [customization-color (rf/sub [:profile/customization-color])
|
||||
[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity
|
||||
contact-id])
|
||||
{:keys [contact-request-state community-id]} (rf/sub [:chats/current-chat-chat-view])
|
||||
chat-type (rf/sub [:chats/chat-type])
|
||||
community-chat? (= chat-type :community-chat)
|
||||
joined (when community-chat?
|
||||
(rf/sub [:communities/community-joined
|
||||
community-id]))
|
||||
pending? (when community-chat?
|
||||
(rf/sub [:communities/my-pending-request-to-join
|
||||
community-id]))
|
||||
contact-request-send? (or (not contact-request-state)
|
||||
(= contact-request-state
|
||||
constants/contact-request-state-none))
|
||||
contact-request-received? (= contact-request-state
|
||||
constants/contact-request-state-received)
|
||||
contact-request-pending? (= contact-request-state
|
||||
constants/contact-request-state-sent)]
|
||||
(let [customization-color (rf/sub [:profile/customization-color])
|
||||
[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity contact-id])
|
||||
{:keys [contact-request-state
|
||||
community-id]} (rf/sub [:chats/current-chat-chat-view])
|
||||
chat-type (rf/sub [:chats/chat-type])
|
||||
community-chat? (= chat-type :community-chat)
|
||||
joined (when community-chat?
|
||||
(rf/sub [:communities/community-joined community-id]))
|
||||
pending? (when community-chat?
|
||||
(rf/sub [:communities/my-pending-request-to-join community-id]))
|
||||
contact-request-send? (or (not contact-request-state)
|
||||
(= contact-request-state
|
||||
constants/contact-request-state-none))
|
||||
contact-request-received? (= contact-request-state
|
||||
constants/contact-request-state-received)
|
||||
contact-request-pending? (= contact-request-state
|
||||
constants/contact-request-state-sent)]
|
||||
[rn/view {:style style/container}
|
||||
[quo/permission-context
|
||||
{:blur? true
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
(ns status-im.contexts.chat.messenger.messages.list.style
|
||||
(:require
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.foundations.shadows :as shadows]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[status-im.contexts.chat.messenger.messages.constants :as messages.constants]))
|
||||
|
||||
(def permission-context-height 46)
|
||||
(def distance-from-last-message 4)
|
||||
|
||||
(defn keyboard-avoiding-container
|
||||
[theme]
|
||||
@@ -13,70 +13,13 @@
|
||||
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
|
||||
:z-index 2})
|
||||
|
||||
(defn background-container
|
||||
[background-color background-opacity top-margin]
|
||||
(reanimated/apply-animations-to-style
|
||||
{:opacity background-opacity}
|
||||
{:background-color background-color
|
||||
:position :absolute
|
||||
:top 0
|
||||
:left 0
|
||||
:right 0
|
||||
:height (+ top-margin messages.constants/header-container-radius)}))
|
||||
(def permission-context-sheet {:flex 3}) ; Pushes composer to bottom
|
||||
|
||||
(defn header-bottom-container
|
||||
[bottom top-margin]
|
||||
(reanimated/apply-animations-to-style
|
||||
{:bottom bottom}
|
||||
{:margin-top top-margin}))
|
||||
|
||||
(defn header-bottom-part
|
||||
[theme]
|
||||
{:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
|
||||
:padding-horizontal 20
|
||||
:border-radius 20})
|
||||
|
||||
(defn header-bottom-shadow
|
||||
[theme]
|
||||
(assoc
|
||||
(shadows/get 2 theme :inverted)
|
||||
:left 0
|
||||
:right 0
|
||||
:height 40
|
||||
:position :absolute
|
||||
:border-radius 20))
|
||||
|
||||
(defn header-image
|
||||
[scale top left theme]
|
||||
(reanimated/apply-animations-to-style
|
||||
{:transform [{:scale scale}]
|
||||
:top top
|
||||
:left left}
|
||||
{:position :absolute
|
||||
:border-width 4
|
||||
:border-radius 50
|
||||
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
|
||||
:border-color (colors/theme-colors colors/white colors/neutral-95 theme)}))
|
||||
|
||||
(defn user-name-container
|
||||
[top left]
|
||||
(reanimated/apply-animations-to-style
|
||||
{:top top
|
||||
:left left}
|
||||
{:z-index -1}))
|
||||
|
||||
(def user-name
|
||||
{:align-items :center
|
||||
:flex-direction :row
|
||||
:margin-top 52})
|
||||
|
||||
(defn bio-and-actions
|
||||
[top]
|
||||
(reanimated/apply-animations-to-style
|
||||
{:top top}
|
||||
{:row-gap 16}))
|
||||
|
||||
(defn permission-context-sheet
|
||||
[margin-bottom?]
|
||||
{:flex 3 ;; Pushes composer to bottom
|
||||
:margin-bottom (when margin-bottom? permission-context-height)})
|
||||
(defn list-paddings
|
||||
[add-padding-bottom?]
|
||||
(let [{:keys [top bottom]} (safe-area/get-insets)]
|
||||
;; WARNING: the flat-list is reversed, so the paddings are applied inverted
|
||||
{:padding-top (if add-padding-bottom?
|
||||
(+ distance-from-last-message permission-context-height bottom)
|
||||
distance-from-last-message)
|
||||
:padding-bottom (+ top messages.constants/top-bar-height)}))
|
||||
|
||||
@@ -10,11 +10,9 @@
|
||||
[react-native.hooks :as hooks]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[status-im.common.home.actions.view :as home.actions]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.chat.messenger.composer.constants :as composer.constants]
|
||||
[status-im.contexts.chat.messenger.messages.constants :as messages.constants]
|
||||
[status-im.contexts.chat.messenger.messages.content.view :as message]
|
||||
[status-im.contexts.chat.messenger.messages.list.state :as state]
|
||||
[status-im.contexts.chat.messenger.messages.list.style :as style]
|
||||
@@ -24,7 +22,6 @@
|
||||
[utils.re-frame :as rf]
|
||||
[utils.worklets.chat.messenger.messages :as worklets]))
|
||||
|
||||
(def ^:const distance-from-last-message 4)
|
||||
(def ^:const loading-indicator-page-loading-height 100)
|
||||
|
||||
(defn list-key-fn [{:keys [message-id value]}] (or message-id value))
|
||||
@@ -194,10 +191,9 @@
|
||||
context (rf/sub [:chats/current-chat-message-list-view-context])
|
||||
able-to-send-message? (:able-to-send-message? context)
|
||||
messages (rf/sub [:chats/raw-chat-messages-stream chat-id])
|
||||
margin-bottom? (and community-channel? (not able-to-send-message?))
|
||||
recording? (rf/sub [:chats/recording?])
|
||||
top-margin (+ (safe-area/get-top) messages.constants/top-bar-height)]
|
||||
[rn/view {:style (style/permission-context-sheet margin-bottom?)}
|
||||
add-padding-bottom? (and community-channel? (not able-to-send-message?))
|
||||
recording? (rf/sub [:chats/recording?])]
|
||||
[rn/view {:style style/permission-context-sheet}
|
||||
[rn/view {:style {:flex-shrink 1}} ;; Keeps flat list on top
|
||||
[reanimated/flat-list
|
||||
{:key-fn list-key-fn
|
||||
@@ -237,8 +233,7 @@
|
||||
:style {:background-color (colors/theme-colors colors/white
|
||||
colors/neutral-95
|
||||
theme)}
|
||||
:content-container-style {:padding-top distance-from-last-message
|
||||
:padding-bottom top-margin}
|
||||
:content-container-style (style/list-paddings add-padding-bottom?)
|
||||
:inverted true
|
||||
:on-layout #(on-layout
|
||||
{:event %
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
(ns status-im.contexts.keycard.authorise.view
|
||||
(:require [quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[status-im.common.events-helper :as events-helper]
|
||||
[status-im.common.standard-authentication.core :as standard-auth]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [profile-name (rf/sub [:profile/name])
|
||||
profile-picture (rf/sub [:profile/image])
|
||||
customization-color (rf/sub [:profile/customization-color])]
|
||||
[:<>
|
||||
[quo/page-nav
|
||||
{:key :header
|
||||
:background :blur
|
||||
:icon-name :i/close
|
||||
:on-press events-helper/navigate-back}]
|
||||
[quo/page-top
|
||||
{:title (i18n/label :t/authorise-with-password)
|
||||
:description :context-tag
|
||||
:context-tag {:full-name profile-name
|
||||
:profile-picture profile-picture
|
||||
:customization-color customization-color}}]
|
||||
[rn/view {:style {:flex 1 :padding-horizontal 20 :justify-content :space-between}}
|
||||
[quo/text (i18n/label :t/migrate-key-pair-authorise)]
|
||||
[standard-auth/slide-button
|
||||
{:size :size-48
|
||||
:container-style {}
|
||||
:customization-color customization-color
|
||||
:track-text (i18n/label :t/slide-to-authorise)
|
||||
:on-auth-success #(println "TBD" (security/safe-unmask-data %))
|
||||
:auth-button-label (i18n/label :t/confirm)}]]]))
|
||||
@@ -0,0 +1,36 @@
|
||||
(ns status-im.contexts.keycard.check.view
|
||||
(:require [quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[status-im.common.events-helper :as events-helper]
|
||||
[status-im.common.resources :as resources]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
[:<>
|
||||
[quo/page-nav
|
||||
{:key :header
|
||||
:background :blur
|
||||
:icon-name :i/arrow-left
|
||||
:on-press events-helper/navigate-back}]
|
||||
[quo/page-top
|
||||
{:title (i18n/label :t/check-keycard)
|
||||
:description :text
|
||||
:description-text (i18n/label :t/see-keycard-ready)}]
|
||||
[rn/view {:style {:flex 1 :align-items :center :justify-content :center}}
|
||||
[rn/image
|
||||
{:resize-mode :contain
|
||||
:source (resources/get-image :check-your-keycard)}]]
|
||||
[quo/divider-label (i18n/label :t/tips-scan-keycard)]
|
||||
[rn/view {:style {:padding-horizontal 10}}
|
||||
[quo/markdown-list
|
||||
{:container-style {:padding-vertical 10}
|
||||
:description (i18n/label :t/remove-phone-case)}]
|
||||
[quo/markdown-list
|
||||
{:container-style {:padding-bottom 25}
|
||||
:description (i18n/label :t/keep-card-steady)}]]
|
||||
[quo/button
|
||||
{:on-press #(rf/dispatch [:keycard/connect])
|
||||
:container-style {:margin-horizontal 20}}
|
||||
(i18n/label :t/ready-to-scan)]])
|
||||
@@ -10,10 +10,15 @@
|
||||
|
||||
(defonce ^:private active-listeners (atom []))
|
||||
|
||||
(defn register-card-events
|
||||
(defn unregister-card-events
|
||||
[]
|
||||
(doseq [listener @active-listeners]
|
||||
(keycard/remove-event-listener listener))
|
||||
(reset! active-listeners nil))
|
||||
|
||||
(defn register-card-events
|
||||
[]
|
||||
(unregister-card-events)
|
||||
(reset! active-listeners
|
||||
[(keycard/on-card-connected #(rf/dispatch [:keycard/on-card-connected]))
|
||||
(keycard/on-card-disconnected #(rf/dispatch [:keycard/on-card-disconnected]))
|
||||
@@ -23,7 +28,9 @@
|
||||
(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)
|
||||
(rf/reg-fx :effects.keycard/unregister-card-events unregister-card-events)
|
||||
|
||||
(defn check-nfc-enabled
|
||||
[]
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
(ns status-im.contexts.keycard.empty.view
|
||||
(:require [quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[status-im.common.events-helper :as events-helper]
|
||||
[status-im.common.resources :as resources]
|
||||
[status-im.contexts.keycard.sheets.migrate.view :as sheets.migrate]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
[:<>
|
||||
[quo/page-nav
|
||||
{:key :header
|
||||
:background :blur
|
||||
:icon-name :i/arrow-left
|
||||
:on-press events-helper/navigate-back}]
|
||||
[quo/page-top
|
||||
{:title (i18n/label :t/keycard-empty)
|
||||
:description :text
|
||||
:description-text (i18n/label :t/what-to-do)}]
|
||||
[rn/view {:style {:padding-horizontal 28 :padding-top 20}}
|
||||
[quo/small-option-card
|
||||
{:variant :main
|
||||
:title (i18n/label :t/import-key-pair-keycard)
|
||||
:subtitle (i18n/label :t/use-keycard-login-sign)
|
||||
:button-label (i18n/label :t/import-profile-key-pair)
|
||||
:accessibility-label :get-keycard
|
||||
:image (resources/get-image :generate-keys)
|
||||
:on-press #(rf/dispatch [:show-bottom-sheet
|
||||
{:theme :dark
|
||||
:content (fn [] [sheets.migrate/view])}])}]]
|
||||
[quo/information-box
|
||||
{:type :default
|
||||
:style {:margin-top 32 :margin-horizontal 28}}
|
||||
(i18n/label :t/empty-card-info)]])
|
||||
@@ -0,0 +1,42 @@
|
||||
(ns status-im.contexts.keycard.error.view
|
||||
(:require [quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[status-im.common.events-helper :as events-helper]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(def titles
|
||||
{:keycard/error.keycard-wrong {:title "Keycard is not empty"
|
||||
:description "You can’t use it to store new keys right now"}
|
||||
:keycard/error.keycard-unpaired {:title "Keycard is full"
|
||||
:description "All pairing slots are occupied"}
|
||||
:keycard/error.keycard-frozen {:title "Keycard is locked"
|
||||
:description "You can’t use it right now"}
|
||||
:keycard/error.keycard-locked {:title "Keycard is locked"
|
||||
:description "You can’t use it right now"}})
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [{:keys [top bottom]} (safe-area/get-insets)
|
||||
error (rf/sub [:keycard/application-info-error])
|
||||
{:keys [title description]} (get titles error)]
|
||||
[quo/overlay
|
||||
{:type :shell
|
||||
:container-style {:padding-top top
|
||||
:padding-bottom bottom}}
|
||||
[quo/page-nav
|
||||
{:key :header
|
||||
:background :blur
|
||||
:icon-name :i/arrow-left
|
||||
:on-press events-helper/navigate-back}]
|
||||
[quo/page-top
|
||||
{:title title
|
||||
:description :text
|
||||
:description-text description}]
|
||||
[rn/view {:height 226}]
|
||||
[rn/view {:padding-horizontal 20}
|
||||
[quo/info-message
|
||||
{:container-style {:padding-top 15}
|
||||
:icon :i/info
|
||||
:size :default}
|
||||
"To unlock or factory reset the Keycard, please use the Status desktop app. If you'd like this features on mobile, feel free to upvote them and discuss in the Status community."]]]))
|
||||
@@ -1,8 +1,8 @@
|
||||
(ns status-im.contexts.keycard.events
|
||||
(:require [re-frame.core :as rf]
|
||||
status-im.contexts.keycard.login.events
|
||||
status-im.contexts.keycard.nfc-sheet.events
|
||||
status-im.contexts.keycard.pin.events
|
||||
status-im.contexts.keycard.sheet.events
|
||||
status-im.contexts.keycard.sign.events
|
||||
[status-im.contexts.keycard.utils :as keycard.utils]
|
||||
[taoensso.timbre :as log]))
|
||||
@@ -14,7 +14,9 @@
|
||||
(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)
|
||||
{:db (-> db
|
||||
(assoc-in [:keycard :pin :status] nil)
|
||||
(assoc-in [:keycard :on-nfc-cancelled-event-vector] nil))
|
||||
:fx [(when-let [on-nfc-cancelled-event-vector (get-in db [:keycard :on-nfc-cancelled-event-vector])]
|
||||
[:dispatch on-nfc-cancelled-event-vector])]}))
|
||||
|
||||
@@ -42,12 +44,6 @@
|
||||
{: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)
|
||||
@@ -60,7 +56,7 @@
|
||||
|
||||
(rf/reg-event-fx :keycard/on-action-with-pin-error
|
||||
(fn [{:keys [db]} [error]]
|
||||
(log/debug "[keycard] get keys error: " error)
|
||||
(log/debug "[keycard] on-action-with-pin-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?
|
||||
@@ -69,7 +65,52 @@
|
||||
{: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]]
|
||||
(assoc-in [:keycard :pin :status] :error))
|
||||
:fx [[:dispatch [:keycard/disconnect]]
|
||||
(when (zero? pin-retries-count)
|
||||
[:effects.utils/show-popup {:title "frozen-keycard"}])]})))))
|
||||
|
||||
(rf/reg-event-fx :keycard/on-get-application-info-success
|
||||
(fn [{:keys [db]} [application-info {:keys [key-uid on-success-fx]}]]
|
||||
(if-let [error (keycard.utils/validate-application-info key-uid application-info)]
|
||||
(case error
|
||||
:keycard/error.not-keycard
|
||||
{:fx [[:dispatch [:keycard/disconnect]]
|
||||
[:dispatch [:open-modal :screen/keycard.not-keycard]]]}
|
||||
:keycard/error.keycard-blank
|
||||
{:fx [[:dispatch [:keycard/disconnect]]
|
||||
[:dispatch [:open-modal :screen/keycard.empty]]]}
|
||||
{:db (assoc-in db [:keycard :application-info-error] error)
|
||||
:fx [[:dispatch [:keycard/disconnect]]
|
||||
[:dispatch [:open-modal :screen/keycard.error]]]})
|
||||
{:db (-> db
|
||||
(assoc-in [:keycard :application-info] application-info)
|
||||
(assoc-in [:keycard :pin :status] :verifying))
|
||||
:fx on-success-fx})))
|
||||
|
||||
(rf/reg-event-fx :keycard/get-application-info
|
||||
(fn [_ [{:keys [on-success on-failure]}]]
|
||||
{:effects.keycard/get-application-info {:on-success on-success :on-failure on-failure}}))
|
||||
|
||||
(rf/reg-event-fx :keycard/cancel-connection
|
||||
(fn [{:keys [db]}]
|
||||
{:db (-> db
|
||||
(assoc-in [:keycard :on-card-connected-event-vector] nil)
|
||||
(assoc-in [:keycard :on-nfc-cancelled-event-vector] nil))}))
|
||||
|
||||
(rf/reg-event-fx :keycard/disconnect
|
||||
(fn [_ _]
|
||||
{:fx [[:dispatch [:keycard/cancel-connection]]
|
||||
[:dispatch [:keycard/hide-connection-sheet]]]}))
|
||||
|
||||
(rf/reg-event-fx :keycard/connect
|
||||
(fn [{:keys [db]} [args]]
|
||||
(let [connected? (get-in db [:keycard :card-connected?])
|
||||
event-vector [:keycard/get-application-info
|
||||
{:on-success #(rf/dispatch [:keycard/on-get-application-info-success % args])}]]
|
||||
{:db (assoc-in db [:keycard :on-card-connected-event-vector] event-vector)
|
||||
:fx [[:dispatch
|
||||
[:keycard/show-connection-sheet
|
||||
{:on-cancel-event-vector [:keycard/cancel-connection]}]]
|
||||
(when connected?
|
||||
[:dispatch event-vector])]})))
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
(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]))
|
||||
(:require [utils.re-frame :as rf]))
|
||||
|
||||
(rf/reg-event-fx :keycard.login/on-get-keys-success
|
||||
(fn [{:keys [db]} [data]]
|
||||
@@ -16,7 +14,7 @@
|
||||
:password encryption-public-key
|
||||
:key-uid key-uid
|
||||
:name (:name profile)))
|
||||
:fx [[:dispatch [:keycard/hide-connection-sheet]]
|
||||
:fx [[:dispatch [:keycard/disconnect]]
|
||||
[:effects.keycard/login-with-keycard
|
||||
{:password encryption-public-key
|
||||
:whisper-private-key whisper-private-key
|
||||
@@ -33,36 +31,8 @@
|
||||
:password encryption-public-key
|
||||
:key-uid key-uid
|
||||
:name (:name profile)))
|
||||
:fx [[:dispatch [:keycard/hide-connection-sheet]]
|
||||
:fx [[:dispatch [:keycard/disconnect]]
|
||||
[: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 {:keys [key-uid on-read-fx]}]]
|
||||
(let [error (keycard.utils/validate-application-info key-uid 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))
|
||||
:fx on-read-fx}))))
|
||||
|
||||
(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
|
||||
(fn [{:keys [db]} [args]]
|
||||
(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 %
|
||||
args])}]]
|
||||
(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])]})))
|
||||
|
||||
+7
-9
@@ -1,18 +1,16 @@
|
||||
(ns status-im.contexts.keycard.sheet.events
|
||||
(ns status-im.contexts.keycard.nfc-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]]
|
||||
(fn [{:keys [db]} [{:keys [on-cancel-event-vector]} :as args]]
|
||||
(if platform/android?
|
||||
{:dispatch [:keycard/show-connection-sheet-component args]}
|
||||
{:db (assoc-in db
|
||||
[:keycard :connection-sheet-opts]
|
||||
{:on-close #(rf/dispatch on-cancel-event-vector)})
|
||||
:fx [[:dismiss-keyboard true]
|
||||
[:show-nfc-sheet nil]]}
|
||||
{:effects.keycard.ios/start-nfc
|
||||
{:on-success
|
||||
(fn []
|
||||
+6
-5
@@ -1,8 +1,9 @@
|
||||
(ns status-im.contexts.keycard.sheet.view
|
||||
(ns status-im.contexts.keycard.nfc-sheet.view
|
||||
(:require [quo.foundations.colors :as colors]
|
||||
quo.theme
|
||||
[react-native.core :as rn]
|
||||
[status-im.common.resources :as resources]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn connect-keycard
|
||||
@@ -18,13 +19,13 @@
|
||||
: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"]
|
||||
(i18n/label :t/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")]
|
||||
(i18n/label :t/connected-dont-move)
|
||||
(i18n/label :t/hold-phone-near-keycard))]
|
||||
[rn/pressable
|
||||
{:on-press (fn []
|
||||
(when on-close (on-close))
|
||||
@@ -33,4 +34,4 @@
|
||||
[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"]]]]]))
|
||||
(i18n/label :t/cancel)]]]]]))
|
||||
@@ -0,0 +1,28 @@
|
||||
(ns status-im.contexts.keycard.not-keycard.view
|
||||
(:require [quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[status-im.common.events-helper :as events-helper]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [{:keys [top bottom]} (safe-area/get-insets)]
|
||||
[quo/overlay
|
||||
{:type :shell
|
||||
:container-style {:padding-top top
|
||||
:padding-bottom bottom}}
|
||||
[quo/page-nav
|
||||
{:key :header
|
||||
:background :blur
|
||||
:icon-name :i/arrow-left
|
||||
:on-press events-helper/navigate-back}]
|
||||
[quo/page-top
|
||||
{:title (i18n/label :t/oops-not-keycard)
|
||||
:description :text
|
||||
:description-text (i18n/label :t/make-sure-keycard)}]
|
||||
[rn/view {:flex 1}]
|
||||
[rn/view {:padding-horizontal 20}
|
||||
[quo/button {:on-press #(rf/dispatch [:keycard/connect])}
|
||||
(i18n/label :t/try-again)]]]))
|
||||
@@ -0,0 +1,55 @@
|
||||
(ns status-im.contexts.keycard.pin.create.view
|
||||
(:require [clojure.string :as string]
|
||||
[quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[status-im.constants :as constants]
|
||||
[utils.i18n :as i18n]))
|
||||
|
||||
(defn view
|
||||
[{:keys [on-complete]}]
|
||||
(let [[pin set-pin] (rn/use-state "")
|
||||
[first-pin set-first-pin] (rn/use-state "")
|
||||
[error? set-error] (rn/use-state false)
|
||||
[stage set-stage] (rn/use-state :create)
|
||||
pin-empty? (string/blank? pin)
|
||||
on-delete (rn/use-callback
|
||||
(fn []
|
||||
(set-error false)
|
||||
(if (= (count pin) 1)
|
||||
(do
|
||||
(set-pin "")
|
||||
(set-stage :create))
|
||||
(when (and pin (pos? (count pin)))
|
||||
(set-pin (.slice pin 0 -1)))))
|
||||
[pin])
|
||||
on-press (rn/use-callback
|
||||
(fn [new-symbol]
|
||||
(let [new-pin (str pin new-symbol)]
|
||||
(when (<= (count new-pin) constants/pincode-length)
|
||||
(set-pin new-pin)
|
||||
(when (= constants/pincode-length (count new-pin))
|
||||
(if (= :repeat stage)
|
||||
(if (= first-pin new-pin)
|
||||
(on-complete new-pin)
|
||||
(set-error true))
|
||||
(do
|
||||
(set-pin "")
|
||||
(set-first-pin new-pin)
|
||||
(set-stage :repeat)))))))
|
||||
[pin stage first-pin])]
|
||||
[rn/view {:style {:padding-bottom 12 :flex 1}}
|
||||
[quo/drawer-top
|
||||
{:title (if (= :create stage)
|
||||
(i18n/label :t/create-keycard-pin)
|
||||
(i18n/label :t/repeat-keycard-pin))}]
|
||||
[rn/view {:style {:flex 1 :justify-content :center :align-items :center :padding-vertical 34}}
|
||||
[quo/pin-input
|
||||
{:blur? false
|
||||
:number-of-pins constants/pincode-length
|
||||
:number-of-filled-pins (count pin)
|
||||
:error? error?
|
||||
:info (when error? (i18n/label :t/pin-not-match))}]]
|
||||
[quo/numbered-keyboard
|
||||
{:delete-key? (not pin-empty?)
|
||||
:on-delete on-delete
|
||||
:on-press on-press}]]))
|
||||
@@ -0,0 +1,38 @@
|
||||
(ns status-im.contexts.keycard.sheets.migrate.view
|
||||
(:require [quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [profile-name (rf/sub [:profile/name])
|
||||
profile-picture (rf/sub [:profile/image])
|
||||
customization-color (rf/sub [:profile/customization-color])
|
||||
recovery-phrase-backed-up? (rf/sub [:profile/recovery-phrase-backed-up?])]
|
||||
[:<>
|
||||
[quo/drawer-top
|
||||
{:type :context-tag
|
||||
:context-tag-type :default
|
||||
:title (i18n/label :t/migrate-key-pair-keycard)
|
||||
:full-name profile-name
|
||||
:profile-picture profile-picture
|
||||
:customization-color customization-color}]
|
||||
[rn/view {:style {:padding-horizontal 20}}
|
||||
[quo/text {}
|
||||
(i18n/label :t/migrate-key-pair-keycard-default-key {:name profile-name})]
|
||||
[quo/information-box
|
||||
{:type :default
|
||||
:style {:margin-top 20 :margin-bottom 12}}
|
||||
(i18n/label :t/migrate-key-pair-keycard-info)]]
|
||||
[quo/bottom-actions
|
||||
{:actions :two-actions
|
||||
:button-one-label (i18n/label :t/continue)
|
||||
:button-one-props {:on-press #(if recovery-phrase-backed-up?
|
||||
(rf/dispatch [:open-modal :screen/use-recovery-phrase
|
||||
{:on-success (fn [])}])
|
||||
(rf/dispatch [:open-modal :screen/backup-recovery-phrase
|
||||
{:on-success (fn [])}]))}
|
||||
:button-two-label (i18n/label :t/cancel)
|
||||
:button-two-props {:type :grey
|
||||
:on-press #(rf/dispatch [:hide-bottom-sheet])}}]]))
|
||||
@@ -14,16 +14,17 @@
|
||||
path (get-in db [:wallet :accounts current-address :path])
|
||||
key-uid (get-in db [:profile/profile :key-uid])]
|
||||
{:fx [[:dispatch
|
||||
[:keycard/read-card
|
||||
{:key-uid key-uid
|
||||
:on-read-fx [[:effects.keycard/sign
|
||||
{:pin pin-text
|
||||
:path path
|
||||
:hash (utils.address/naked-address tx-hash)
|
||||
:on-success
|
||||
#(do
|
||||
(rf/dispatch [:keycard/hide-connection-sheet])
|
||||
(rf/dispatch
|
||||
[:wallet/proceed-with-transactions-signatures
|
||||
(get-signature-map tx-hash %)]))
|
||||
:on-failure #(rf/dispatch [:keycard/on-action-with-pin-error %])}]]}]]]})))
|
||||
[:keycard/connect
|
||||
{:key-uid key-uid
|
||||
:on-success-fx [[:effects.keycard/sign
|
||||
{:pin pin-text
|
||||
:path path
|
||||
:hash (utils.address/naked-address tx-hash)
|
||||
:on-success
|
||||
#(do
|
||||
(rf/dispatch [:keycard/disconnect])
|
||||
(rf/dispatch
|
||||
[:wallet/proceed-with-transactions-signatures
|
||||
(get-signature-map tx-hash %)]))
|
||||
:on-failure #(rf/dispatch [:keycard/on-action-with-pin-error
|
||||
%])}]]}]]]})))
|
||||
|
||||
@@ -18,28 +18,31 @@
|
||||
(defn validate-application-info
|
||||
[profile-key-uid {:keys [key-uid paired? pin-retry-counter puk-retry-counter] :as application-info}]
|
||||
(let [profile-mismatch? (or (nil? profile-key-uid) (not= profile-key-uid key-uid))]
|
||||
(log/debug "[keycard] login-with-keycard"
|
||||
(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
|
||||
:keycard/error.not-keycard
|
||||
|
||||
(empty? (:key-uid application-info))
|
||||
:keycard-blank
|
||||
:keycard/error.keycard-blank
|
||||
|
||||
profile-mismatch?
|
||||
:keycard-wrong
|
||||
:keycard/error.keycard-wrong
|
||||
|
||||
(not paired?)
|
||||
:keycard-unpaired
|
||||
:keycard/error.keycard-unpaired
|
||||
|
||||
(and (zero? pin-retry-counter)
|
||||
(or (nil? puk-retry-counter)
|
||||
(pos? puk-retry-counter)))
|
||||
nil
|
||||
:keycard/error.keycard-frozen
|
||||
|
||||
(zero? puk-retry-counter)
|
||||
:keycard/error.keycard-locked
|
||||
|
||||
:else
|
||||
nil)))
|
||||
|
||||
@@ -131,15 +131,6 @@
|
||||
[:open-modal :screen/onboarding.enable-biometrics]
|
||||
[:open-modal :screen/onboarding.enable-notifications])}))
|
||||
|
||||
(rf/defn seed-phrase-entered
|
||||
{:events [:onboarding/seed-phrase-entered]}
|
||||
[_ seed-phrase on-error]
|
||||
{:multiaccount/validate-mnemonic [seed-phrase
|
||||
(fn [mnemonic key-uid]
|
||||
(re-frame/dispatch [:onboarding/seed-phrase-validated
|
||||
mnemonic key-uid]))
|
||||
on-error]})
|
||||
|
||||
(rf/defn seed-phrase-validated
|
||||
{:events [:onboarding/seed-phrase-validated]}
|
||||
[{:keys [db]} seed-phrase key-uid]
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
(ns status-im.contexts.wallet.add-account.create-account.new-keypair.backup-recovery-phrase.style
|
||||
(ns status-im.contexts.profile.backup-recovery-phrase.style
|
||||
(:require
|
||||
[quo.foundations.colors :as colors]
|
||||
[react-native.platform :as platform]))
|
||||
+18
-19
@@ -1,4 +1,4 @@
|
||||
(ns status-im.contexts.wallet.add-account.create-account.new-keypair.backup-recovery-phrase.view
|
||||
(ns status-im.contexts.profile.backup-recovery-phrase.view
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[native-module.core :as native-module]
|
||||
@@ -6,8 +6,7 @@
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.contexts.wallet.add-account.create-account.new-keypair.backup-recovery-phrase.style :as
|
||||
style]
|
||||
[status-im.contexts.profile.backup-recovery-phrase.style :as style]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]))
|
||||
@@ -45,20 +44,20 @@
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [step-labels [:t/backup-step-1 :t/backup-step-2 :t/backup-step-3
|
||||
:t/backup-step-4]
|
||||
checked? (reagent/atom
|
||||
{:0 false
|
||||
:1 false
|
||||
:2 false
|
||||
:3 false})
|
||||
revealed? (reagent/atom false)
|
||||
customization-color (rf/sub [:profile/customization-color])
|
||||
seed-phrase (reagent/atom [])
|
||||
random-phrase (reagent/atom [])]
|
||||
(let [step-labels [:t/backup-step-1 :t/backup-step-2 :t/backup-step-3
|
||||
:t/backup-step-4]
|
||||
checked? (reagent/atom
|
||||
{:0 false
|
||||
:1 false
|
||||
:2 false
|
||||
:3 false})
|
||||
revealed? (reagent/atom false)
|
||||
customization-color (rf/sub [:profile/customization-color])
|
||||
{:keys [on-success]} (rf/sub [:get-screen-params])
|
||||
seed-phrase (reagent/atom [])
|
||||
random-phrase (reagent/atom [])]
|
||||
(fn []
|
||||
(let [theme (quo.theme/use-theme)]
|
||||
|
||||
(rn/use-mount
|
||||
(fn []
|
||||
(native-module/get-random-mnemonic #(reset! seed-phrase (string/split % #"\s")))
|
||||
@@ -107,10 +106,10 @@
|
||||
:button-one-label (i18n/label :t/i-have-written)
|
||||
:button-one-props {:disabled? (some false? (vals @checked?))
|
||||
:customization-color customization-color
|
||||
:on-press #(rf/dispatch [:wallet/store-new-seed-phrase
|
||||
{:seed-phrase (security/mask-data
|
||||
@seed-phrase)
|
||||
:random-phrase @random-phrase}])}}]
|
||||
:on-press #(on-success {:masked-seed-phrase
|
||||
(security/mask-data
|
||||
@seed-phrase)
|
||||
:random-phrase @random-phrase})}}]
|
||||
[quo/text
|
||||
{:size :paragraph-2
|
||||
:style (style/description-text theme)}
|
||||
@@ -242,12 +242,13 @@
|
||||
{:on-complete
|
||||
(fn [pin-text]
|
||||
(rf/dispatch
|
||||
[:keycard/read-card
|
||||
{:key-uid key-uid
|
||||
:on-read-fx [[:effects.keycard/get-keys
|
||||
{:pin pin-text
|
||||
:on-success #(rf/dispatch [:keycard.login/on-get-keys-success %])
|
||||
:on-failure #(rf/dispatch [:keycard/on-action-with-pin-error %])}]]}]))}]
|
||||
[:keycard/connect
|
||||
{:key-uid key-uid
|
||||
:on-success-fx [[:effects.keycard/get-keys
|
||||
{:pin pin-text
|
||||
:on-success #(rf/dispatch [:keycard.login/on-get-keys-success %])
|
||||
:on-failure #(rf/dispatch [:keycard/on-action-with-pin-error
|
||||
%])}]]}]))}]
|
||||
[password-input])]
|
||||
(when-not keycard-pairing
|
||||
[quo/button
|
||||
|
||||
@@ -1,9 +1,31 @@
|
||||
(ns status-im.contexts.profile.recover.effects
|
||||
(:require
|
||||
[native-module.core :as native-module]
|
||||
[utils.re-frame :as rf]))
|
||||
[promesa.core :as promesa]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]
|
||||
[utils.transforms :as transforms]))
|
||||
|
||||
(rf/reg-fx :effects.profile/restore-and-login
|
||||
(fn [request]
|
||||
;;"node.login" signal will be triggered as a callback
|
||||
(native-module/restore-account-and-login request)))
|
||||
|
||||
(defn validate-mnemonic
|
||||
[mnemonic]
|
||||
(-> mnemonic
|
||||
(security/safe-unmask-data)
|
||||
(native-module/validate-mnemonic)
|
||||
(promesa/then (fn [result]
|
||||
(let [{:keys [keyUID]} (transforms/json->clj result)]
|
||||
{:key-uid keyUID})))))
|
||||
|
||||
(rf/reg-fx :effects.profile/validate-recovery-phrase
|
||||
(fn [[mnemonic on-success on-error]]
|
||||
(-> (validate-mnemonic mnemonic)
|
||||
(promesa/then (fn [{:keys [key-uid]}]
|
||||
(when (fn? on-success)
|
||||
(on-success mnemonic key-uid))))
|
||||
(promesa/catch (fn [error]
|
||||
(when (and error (fn? on-error))
|
||||
(on-error error)))))))
|
||||
|
||||
@@ -24,3 +24,7 @@
|
||||
:imagePath (profile.config/strip-file-prefix image-path)
|
||||
:customizationColor (or color constants/profile-default-color)
|
||||
:fetchBackup true)}))
|
||||
|
||||
(rf/reg-event-fx :profile.recover/validate-recovery-phrase
|
||||
(fn [_ [phrase {:keys [on-success on-error]}]]
|
||||
{:effects.profile/validate-recovery-phrase [phrase on-success on-error]}))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
(ns status-im.contexts.profile.settings.list-items
|
||||
(:require [status-im.common.not-implemented :as not-implemented]
|
||||
[status-im.config :as config]
|
||||
[status-im.feature-flags :as ff]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
@@ -54,9 +55,9 @@
|
||||
:image :icon
|
||||
:blur? true
|
||||
:action :arrow})
|
||||
(when config/show-not-implemented-features?
|
||||
(when (ff/enabled? ::ff/keycard.migrate-profile)
|
||||
{:title (i18n/label :t/keycard)
|
||||
:on-press not-implemented/alert
|
||||
:on-press #(rf/dispatch [:open-modal :screen/settings.keycard])
|
||||
:image-props :i/keycard
|
||||
:image :icon
|
||||
:blur? true
|
||||
|
||||
@@ -49,13 +49,14 @@
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
insets (safe-area/get-insets)
|
||||
customization-color (rf/sub [:profile/customization-color])
|
||||
scroll-y (reanimated/use-shared-value 0)
|
||||
profile (rf/sub [:profile/profile])
|
||||
full-name (profile.utils/displayed-name profile)
|
||||
on-scroll (rn/use-callback #(scroll-handler % scroll-y))]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
insets (safe-area/get-insets)
|
||||
customization-color (rf/sub [:profile/customization-color])
|
||||
scroll-y (reanimated/use-shared-value 0)
|
||||
profile (rf/sub [:profile/profile])
|
||||
recovery-phrase-backed-up? (rf/sub [:profile/recovery-phrase-backed-up?])
|
||||
full-name (profile.utils/displayed-name profile)
|
||||
on-scroll (rn/use-callback #(scroll-handler % scroll-y))]
|
||||
[quo/overlay {:type :shell}
|
||||
[rn/view
|
||||
{:style (style/navigation-wrapper {:customization-color customization-color
|
||||
@@ -79,7 +80,7 @@
|
||||
profile)}}])}]}]]
|
||||
[rn/flat-list
|
||||
{:header [settings.header/view {:scroll-y scroll-y}]
|
||||
:data (settings.items/items (boolean (seq (:mnemonic profile))))
|
||||
:data (settings.items/items (not recovery-phrase-backed-up?))
|
||||
:shows-vertical-scroll-indicator false
|
||||
:render-fn settings-category-view
|
||||
:get-item-layout get-item-layout
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
(ns status-im.contexts.settings.keycard.view
|
||||
(:require [quo.core :as quo]
|
||||
[quo.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
[status-im.common.events-helper :as events-helper]
|
||||
[status-im.common.resources :as resources]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [keycard-profile? (rf/sub [:keycard/keycard-profile?])]
|
||||
[:<>
|
||||
[quo/page-nav
|
||||
{:key :header
|
||||
:background :blur
|
||||
:icon-name :i/arrow-left
|
||||
:on-press events-helper/navigate-back}]
|
||||
[quo/page-top
|
||||
{:title (i18n/label :t/keycard)}]
|
||||
(if keycard-profile?
|
||||
[:<>]
|
||||
[rn/view {:style {:padding-horizontal 28 :padding-top 20}}
|
||||
[quo/small-option-card
|
||||
{:variant :main
|
||||
:title (i18n/label :t/get-keycard)
|
||||
:subtitle (i18n/label :t/secure-wallet-card)
|
||||
:button-label (i18n/label :t/buy-keycard)
|
||||
:accessibility-label :get-keycard
|
||||
:image (resources/get-image :generate-keys)
|
||||
:on-press #()}]
|
||||
[rn/view {:style {:margin-top 24}}
|
||||
[quo/text
|
||||
{:style {:margin-bottom 12
|
||||
:color colors/white-opa-70}
|
||||
:size :paragraph-2
|
||||
:weight :medium}
|
||||
(i18n/label :t/own-keycard)]]
|
||||
[quo/small-option-card
|
||||
{:variant :icon
|
||||
:title (i18n/label :t/setup-keycard)
|
||||
:subtitle (i18n/label :t/ready-keycard)
|
||||
:accessibility-label :setup-keycard
|
||||
:image (resources/get-image :use-keycard)
|
||||
:on-press #(rf/dispatch [:open-modal :screen/keycard.check])}]])]))
|
||||
@@ -160,7 +160,7 @@
|
||||
|
||||
(defn wallet-validate-seed-phrase
|
||||
[_ [seed-phrase on-success on-error]]
|
||||
{:fx [[:multiaccount/validate-mnemonic [seed-phrase on-success on-error]]]})
|
||||
{:fx [[:effects.profile/validate-recovery-phrase [seed-phrase on-success on-error]]]})
|
||||
|
||||
(rf/reg-event-fx :wallet/validate-seed-phrase wallet-validate-seed-phrase)
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
seed-phrase-masked (security/mask-data "seed phrase")
|
||||
on-success #(prn "success")
|
||||
on-error #(prn "error")
|
||||
expected {:fx [[:multiaccount/validate-mnemonic
|
||||
expected {:fx [[:effects.profile/validate-recovery-phrase
|
||||
[seed-phrase-masked on-success on-error]]]}]
|
||||
(is (= expected
|
||||
(sut/wallet-validate-seed-phrase
|
||||
|
||||
@@ -61,16 +61,6 @@
|
||||
|
||||
(rf/reg-event-fx :wallet/seed-phrase-validated seed-phrase-validated)
|
||||
|
||||
(defn seed-phrase-entered
|
||||
[_ [seed-phrase on-error]]
|
||||
{:fx [[:multiaccount/validate-mnemonic
|
||||
[seed-phrase
|
||||
(fn [mnemonic key-uid]
|
||||
(rf/dispatch [:wallet/seed-phrase-validated mnemonic key-uid on-error]))
|
||||
on-error]]]})
|
||||
|
||||
(rf/reg-event-fx :wallet/seed-phrase-entered seed-phrase-entered)
|
||||
|
||||
(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)]
|
||||
|
||||
@@ -16,13 +16,21 @@
|
||||
[[{:icon :i/add
|
||||
:accessibility-label :generate-new-keypair
|
||||
:label (i18n/label :t/generate-new-keypair)
|
||||
:on-press #(rf/dispatch [:navigate-to :screen/wallet.backup-recovery-phrase])}
|
||||
:on-press #(rf/dispatch [:navigate-to :screen/backup-recovery-phrase
|
||||
{:on-success (fn [{:keys [masked-seed-phrase random-phrase]}]
|
||||
(rf/dispatch [:wallet/store-new-seed-phrase
|
||||
{:seed-phrase masked-seed-phrase
|
||||
:random-phrase
|
||||
random-phrase}]))}])}
|
||||
{:icon :i/seed
|
||||
:accessibility-label :import-using-phrase
|
||||
:label (i18n/label :t/import-using-phrase)
|
||||
:add-divider? true
|
||||
:on-press #(rf/dispatch [:navigate-to :screen/wallet.enter-seed-phrase
|
||||
{:recovering-keypair? true}])}
|
||||
:on-press (fn []
|
||||
(rf/dispatch [:navigate-to :screen/use-recovery-phrase
|
||||
{:on-success (fn [{:keys [key-uid phrase on-error]}]
|
||||
(rf/dispatch [:wallet/seed-phrase-validated
|
||||
phrase key-uid on-error]))}]))}
|
||||
(when (ff/enabled? ::ff/wallet.import-private-key)
|
||||
{:icon :i/key
|
||||
:accessibility-label :import-private-key
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
(ns status-im.contexts.wallet.buy-crypto.events
|
||||
(:require [re-frame.core :as rf]
|
||||
[react-native.core :as rn]
|
||||
[status-im.contexts.wallet.sheets.buy-network-selection.view :as buy-network-selection]
|
||||
[status-im.contexts.wallet.sheets.select-asset.view :as select-asset]
|
||||
[utils.i18n :as i18n]))
|
||||
|
||||
(rf/reg-event-fx :wallet.buy-crypto/select-provider
|
||||
(fn [{:keys [db]} [{:keys [account provider recurrent?]}]]
|
||||
(let [swap-network (get-in db [:wallet :ui :swap :network])]
|
||||
{:db (-> db
|
||||
(assoc-in [:wallet :ui :buy-crypto :account] account)
|
||||
(assoc-in [:wallet :ui :buy-crypto :provider] provider)
|
||||
(assoc-in [:wallet :ui :buy-crypto :recurrent?] recurrent?))
|
||||
:fx [(if swap-network
|
||||
[:dispatch [:wallet.buy-crypto/select-network swap-network]]
|
||||
[:dispatch
|
||||
[:show-bottom-sheet
|
||||
{:content
|
||||
(fn []
|
||||
[buy-network-selection/view
|
||||
{:provider provider
|
||||
:account account}])}]])]})))
|
||||
|
||||
(rf/reg-event-fx :wallet.buy-crypto/select-network
|
||||
(fn [{:keys [db]} [network]]
|
||||
(let [provider (get-in db [:wallet :ui :buy-crypto :provider])
|
||||
network-chain-id (:chain-id network)
|
||||
supported-tokens (:supported-tokens provider)
|
||||
supported-tokens-on-network (set (map :symbol
|
||||
(filter (fn [token]
|
||||
(= (:chain-id token) network-chain-id))
|
||||
supported-tokens)))]
|
||||
{:db (assoc-in db [:wallet :ui :buy-crypto :network] network)
|
||||
:fx [[:dispatch
|
||||
[:show-bottom-sheet
|
||||
{:content
|
||||
(fn []
|
||||
[select-asset/view
|
||||
{:title (i18n/label :t/select-asset-to-buy)
|
||||
:network network
|
||||
:provider provider
|
||||
:on-select (fn [token]
|
||||
(rf/dispatch [:wallet/get-crypto-on-ramp-url {:token token}]))
|
||||
:hide-token-fn (fn [_ token]
|
||||
(not (contains? supported-tokens-on-network
|
||||
(:symbol token))))}])}]]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet/get-crypto-on-ramp-url
|
||||
(fn [{:keys [db]} [{:keys [token]}]]
|
||||
(let [account (get-in db [:wallet :ui :buy-crypto :account])
|
||||
provider (get-in db [:wallet :ui :buy-crypto :provider])
|
||||
network (get-in db [:wallet :ui :buy-crypto :network])
|
||||
recurrent? (get-in db [:wallet :ui :buy-crypto :recurrent?])
|
||||
provider-id (:id provider)
|
||||
parameters {:symbol (:symbol token)
|
||||
:destAddress (:address account)
|
||||
:chainID (:chain-id network)
|
||||
:isRecurrent recurrent?}]
|
||||
{:fx [[:json-rpc/call
|
||||
[{:method "wallet_getCryptoOnRampURL"
|
||||
:params [provider-id parameters]
|
||||
:on-success (fn [url]
|
||||
(rf/dispatch [:navigate-back])
|
||||
(rf/dispatch [:wallet.buy-crypto/clean-all])
|
||||
(rn/open-url url))
|
||||
:on-error [:wallet/log-rpc-error {:event :wallet/get-crypto-on-ramps}]}]]]})))
|
||||
|
||||
(rf/reg-event-fx :wallet.buy-crypto/clean-all
|
||||
(fn [{:keys [db]}]
|
||||
{:db (update-in db [:wallet :ui] dissoc :buy-crypto)}))
|
||||
@@ -8,19 +8,20 @@
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn get-bottom-sheet-args
|
||||
[switcher-type]
|
||||
[switcher-type params]
|
||||
(case switcher-type
|
||||
:account-options {:content account-options/view
|
||||
:hide-handle? true}
|
||||
:select-account {:content select-account/view}
|
||||
:select-account {:content (fn []
|
||||
[select-account/view params])}
|
||||
nil))
|
||||
|
||||
(defn- on-dapps-press
|
||||
[switcher-type]
|
||||
(rf/dispatch [:show-bottom-sheet (get-bottom-sheet-args switcher-type)]))
|
||||
[switcher-type params]
|
||||
(rf/dispatch [:show-bottom-sheet (get-bottom-sheet-args switcher-type params)]))
|
||||
|
||||
(defn view
|
||||
[{:keys [type on-press accessibility-label icon-name switcher-type margin-top]
|
||||
[{:keys [type on-press accessibility-label icon-name switcher-type margin-top params]
|
||||
:or {icon-name :i/close
|
||||
accessibility-label :top-bar
|
||||
switcher-type :account-options
|
||||
@@ -45,6 +46,6 @@
|
||||
(when-not sending-collectible?
|
||||
{:content-type :account-switcher
|
||||
:customization-color color
|
||||
:on-press #(on-dapps-press switcher-type)
|
||||
:on-press #(on-dapps-press switcher-type params)
|
||||
:emoji emoji
|
||||
:type (when watch-only? :watch-only)})]}]))
|
||||
|
||||
@@ -32,9 +32,11 @@
|
||||
:selected)}]))
|
||||
|
||||
(defn view
|
||||
[{:keys [content-container-style search-text on-token-press preselected-token-symbol]
|
||||
[{:keys [content-container-style search-text on-token-press preselected-token-symbol chain-ids]
|
||||
:or {content-container-style {:padding-horizontal 8}}}]
|
||||
(let [filtered-tokens (rf/sub [:wallet/current-viewing-account-tokens-filtered {:query search-text}])
|
||||
(let [filtered-tokens (rf/sub [:wallet/current-viewing-account-tokens-filtered
|
||||
{:query search-text
|
||||
:chain-ids chain-ids}])
|
||||
currency (rf/sub [:profile/currency])
|
||||
currency-symbol (rf/sub [:profile/currency-symbol])]
|
||||
[gesture/flat-list
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
(rf/dispatch [:wallet/bridge-select-token params]))})
|
||||
|
||||
(defn- action-swap
|
||||
[{:keys [token token-symbol testnet-mode?]}]
|
||||
[{:keys [token asset-to-receive token-symbol testnet-mode?]}]
|
||||
{:icon :i/swap
|
||||
:accessibility-label :swap
|
||||
:label (i18n/label :t/swap)
|
||||
@@ -56,6 +56,7 @@
|
||||
(rf/dispatch [:hide-bottom-sheet])
|
||||
(rf/dispatch [:wallet.swap/start
|
||||
{:asset-to-pay (or token {:symbol token-symbol})
|
||||
:asset-to-receive asset-to-receive
|
||||
:open-new-screen? true}]))})
|
||||
|
||||
(defn- action-manage-tokens
|
||||
@@ -75,23 +76,26 @@
|
||||
|
||||
(defn token-value-drawer
|
||||
[token watch-only? entry-point]
|
||||
(let [token-symbol (:token token)
|
||||
token-data (first (rf/sub [:wallet/current-viewing-account-tokens-filtered
|
||||
{:query token-symbol}]))
|
||||
selected-account (rf/sub [:wallet/current-viewing-account-address])
|
||||
token-owners (rf/sub [:wallet/operable-addresses-with-token-symbol token-symbol])
|
||||
testnet-mode? (rf/sub [:profile/test-networks-enabled?])
|
||||
params (cond-> {:start-flow? true
|
||||
:owners token-owners
|
||||
:testnet-mode? testnet-mode?}
|
||||
selected-account
|
||||
(assoc :token token-data
|
||||
:stack-id :screen/wallet.accounts
|
||||
:has-balance? (-> (get-in token [:values :fiat-unformatted-value])
|
||||
(money/greater-than (money/bignumber "0"))))
|
||||
(not selected-account)
|
||||
(assoc :token-symbol token-symbol
|
||||
:stack-id :wallet-stack))]
|
||||
(let [token-symbol (:token token)
|
||||
token-data (first (rf/sub [:wallet/current-viewing-account-tokens-filtered
|
||||
{:query token-symbol}]))
|
||||
selected-account (rf/sub [:wallet/current-viewing-account-address])
|
||||
token-owners (rf/sub [:wallet/operable-addresses-with-token-symbol token-symbol])
|
||||
testnet-mode? (rf/sub [:profile/test-networks-enabled?])
|
||||
receive-token-symbol (if (= token-symbol "SNT") "ETH" "SNT")
|
||||
asset-to-receive (rf/sub [:wallet/token-by-symbol receive-token-symbol])
|
||||
params (cond-> {:start-flow? true
|
||||
:owners token-owners
|
||||
:testnet-mode? testnet-mode?
|
||||
:asset-to-receive asset-to-receive}
|
||||
selected-account
|
||||
(assoc :token token-data
|
||||
:stack-id :screen/wallet.accounts
|
||||
:has-balance? (-> (get-in token [:values :fiat-unformatted-value])
|
||||
(money/greater-than (money/bignumber "0"))))
|
||||
(not selected-account)
|
||||
(assoc :token-symbol token-symbol
|
||||
:stack-id :wallet-stack))]
|
||||
[quo/action-drawer
|
||||
[(cond->> [(when (ff/enabled? ::ff/wallet.assets-modal-manage-tokens)
|
||||
(action-manage-tokens watch-only?))
|
||||
|
||||
@@ -15,13 +15,17 @@
|
||||
[full-name]
|
||||
(first (string/split full-name #" ")))
|
||||
|
||||
(defn cut-fiat-balance-to-two-decimals
|
||||
[balance]
|
||||
(defn cut-fiat-balance
|
||||
[balance decimals]
|
||||
(let [valid-balance? (and balance
|
||||
(or (number? balance) (.-toFixed balance)))]
|
||||
(as-> balance $
|
||||
(if valid-balance? $ 0)
|
||||
(.toFixed $ 2))))
|
||||
(.toFixed $ decimals))))
|
||||
|
||||
(defn cut-fiat-balance-to-two-decimals
|
||||
[balance]
|
||||
(cut-fiat-balance balance 2))
|
||||
|
||||
(defn prettify-balance
|
||||
[currency-symbol balance]
|
||||
@@ -151,6 +155,10 @@
|
||||
[accounts address]
|
||||
(some #(when (= (:address %) address) %) accounts))
|
||||
|
||||
(defn get-default-account
|
||||
[accounts]
|
||||
(some #(when (:default-account? %) %) accounts))
|
||||
|
||||
(defn calculate-token-fiat-value
|
||||
"Returns the token fiat value for provided raw balance"
|
||||
[{:keys [currency balance token]}]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
[native-module.core :as native-module]
|
||||
[promesa.core :as promesa]
|
||||
[status-im.common.json-rpc.events :as json-rpc]
|
||||
[status-im.contexts.profile.recover.effects :as profile.recover.effects]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]
|
||||
@@ -26,26 +27,6 @@
|
||||
:paths paths}
|
||||
on-success))))
|
||||
|
||||
(defn validate-mnemonic
|
||||
[mnemonic]
|
||||
(-> mnemonic
|
||||
(security/safe-unmask-data)
|
||||
(native-module/validate-mnemonic)
|
||||
(promesa/then (fn [result]
|
||||
(let [{:keys [keyUID]} (transforms/json->clj result)]
|
||||
{:key-uid keyUID})))))
|
||||
|
||||
(rf/reg-fx
|
||||
:multiaccount/validate-mnemonic
|
||||
(fn [[mnemonic on-success on-error]]
|
||||
(-> (validate-mnemonic mnemonic)
|
||||
(promesa/then (fn [{:keys [key-uid]}]
|
||||
(when (fn? on-success)
|
||||
(on-success mnemonic key-uid))))
|
||||
(promesa/catch (fn [error]
|
||||
(when (and error (fn? on-error))
|
||||
(on-error error)))))))
|
||||
|
||||
(defn create-account-from-private-key
|
||||
[private-key]
|
||||
(-> private-key
|
||||
@@ -81,7 +62,7 @@
|
||||
|
||||
(defn import-missing-keypair-by-seed-phrase
|
||||
[keypair-key-uid seed-phrase password]
|
||||
(-> (validate-mnemonic seed-phrase)
|
||||
(-> (profile.recover.effects/validate-mnemonic seed-phrase)
|
||||
(promesa/then
|
||||
(fn [{:keys [key-uid]}]
|
||||
(if (not= keypair-key-uid key-uid)
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
[quo/alert-banner
|
||||
{:action? true
|
||||
:text (i18n/label :t/not-enough-assets-to-pay-gas-fees)
|
||||
:button-text (i18n/label :t/buy-eth)
|
||||
:button-text (i18n/label :t/add-eth)
|
||||
:on-button-press #(rf/dispatch [:show-bottom-sheet
|
||||
{:content buy-token/view}])}])
|
||||
|
||||
@@ -158,16 +158,14 @@
|
||||
active-screen? (= view-id current-screen-id)
|
||||
bottom (safe-area/get-bottom)
|
||||
[crypto-currency? set-crypto-currency] (rn/use-state initial-crypto-currency?)
|
||||
on-navigate-back on-navigate-back
|
||||
handle-on-confirm (fn [amount]
|
||||
(rf/dispatch [:wallet/set-token-amount-to-send
|
||||
{:amount amount
|
||||
:stack-id current-screen-id}]))
|
||||
{fiat-currency :currency} (rf/sub [:profile/profile])
|
||||
{token-symbol :symbol
|
||||
{token-symbol :symbol
|
||||
token-networks :networks
|
||||
:as
|
||||
token} (rf/sub [:wallet/wallet-send-token])
|
||||
:as token} (rf/sub [:wallet/wallet-send-token])
|
||||
send-from-locked-amounts (rf/sub [:wallet/wallet-send-from-locked-amounts])
|
||||
{:keys [total-balance]
|
||||
:as token-by-symbol} (rf/sub [:wallet/token-by-symbol
|
||||
@@ -196,10 +194,8 @@
|
||||
(utils/cut-crypto-decimals-to-fit-usd-cents
|
||||
token-balance
|
||||
usd-conversion-rate)
|
||||
(-> (money/crypto->fiat
|
||||
token-balance
|
||||
conversion-rate)
|
||||
utils/cut-fiat-balance-to-two-decimals))
|
||||
(utils/cut-fiat-balance-to-two-decimals
|
||||
(money/crypto->fiat token-balance conversion-rate)))
|
||||
input-value (controlled-input/input-value input-state)
|
||||
valid-input? (not (or (controlled-input/empty-value? input-state)
|
||||
(controlled-input/input-error input-state)))
|
||||
@@ -389,7 +385,8 @@
|
||||
:button-one-label (if should-try-again?
|
||||
(i18n/label :t/try-again)
|
||||
button-one-label)
|
||||
:button-one-props (merge (when-not should-try-again? button-one-props)
|
||||
:button-one-props (merge (when-not should-try-again?
|
||||
button-one-props)
|
||||
{:disabled? (or loading-routes?
|
||||
(and (not should-try-again?) confirm-disabled?))
|
||||
:on-press (cond
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
[input-amount/view
|
||||
{: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])
|
||||
: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-sync [:wallet/stop-and-clean-suggested-routes])
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
(ns status-im.contexts.wallet.sheets.buy-network-selection.style)
|
||||
|
||||
(defn network-list-container
|
||||
[mainnet?]
|
||||
{:margin-horizontal 8
|
||||
:padding-vertical (when mainnet? 8)})
|
||||
|
||||
(def header-container
|
||||
{:height 62
|
||||
:padding-horizontal 20})
|
||||
|
||||
(def context-tag
|
||||
{:margin-top 0})
|
||||
|
||||
(def divider-label
|
||||
{:padding-top 0
|
||||
:padding-bottom 0})
|
||||
|
||||
(def subheader-container
|
||||
{:flex-direction :row
|
||||
:align-items :center
|
||||
:margin-top 4})
|
||||
|
||||
(def on
|
||||
{:margin-right 4})
|
||||
|
||||
(def to
|
||||
{:margin-horizontal 4})
|
||||
@@ -0,0 +1,78 @@
|
||||
(ns status-im.contexts.wallet.sheets.buy-network-selection.view
|
||||
(:require [quo.core :as quo]
|
||||
[quo.foundations.resources :as quo.resources]
|
||||
[react-native.core :as rn]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.sheets.buy-network-selection.style :as style]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn- network-item
|
||||
[{:keys [network on-select-network]}]
|
||||
(let [{:keys [network-name]} network
|
||||
mainnet? (= network-name constants/mainnet-network-name)]
|
||||
[quo/network-list
|
||||
{:label (name network-name)
|
||||
:network-image (quo.resources/get-network network-name)
|
||||
:on-press #(on-select-network network)
|
||||
:container-style (style/network-list-container mainnet?)}]))
|
||||
|
||||
(defn- subheader
|
||||
[{:keys [provider account]}]
|
||||
[rn/view {:style style/subheader-container}
|
||||
[quo/text
|
||||
{:size :paragraph-2
|
||||
:weight :medium
|
||||
:style style/on}
|
||||
(i18n/label :t/on-uppercase)]
|
||||
[quo/context-tag
|
||||
{:type :network
|
||||
:network-logo (:logo-url provider)
|
||||
:network-name (:name provider)
|
||||
:size 24
|
||||
:container-style style/context-tag}]
|
||||
[quo/text
|
||||
{:size :paragraph-2
|
||||
:weight :medium
|
||||
:style style/to}
|
||||
(i18n/label :t/to)]
|
||||
[quo/context-tag
|
||||
{:type :account
|
||||
:account-name (:name account)
|
||||
:emoji (:emoji account)
|
||||
:size 24
|
||||
:container-style style/context-tag}]])
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [provider (rf/sub [:wallet/wallet-buy-crypto-provider])
|
||||
account (rf/sub [:wallet/wallet-buy-crypto-account])
|
||||
network-details (rf/sub [:wallet/network-details])
|
||||
mainnet-network (first network-details)
|
||||
layer-2-networks (rest network-details)
|
||||
render-fn (rn/use-callback (fn [network]
|
||||
[network-item
|
||||
{:network network
|
||||
:on-select-network
|
||||
(fn [network]
|
||||
(rf/dispatch [:wallet.buy-crypto/select-network
|
||||
network]))}]))]
|
||||
[:<>
|
||||
[rn/view {:style style/header-container}
|
||||
[quo/text
|
||||
{:size :heading-2
|
||||
:weight :semi-bold}
|
||||
(i18n/label :t/select-network-for-buying)]
|
||||
[subheader
|
||||
{:provider provider
|
||||
:account account}]]
|
||||
(when mainnet-network
|
||||
[network-item
|
||||
{:network mainnet-network
|
||||
:on-select-network #(rf/dispatch [:wallet.buy-crypto/select-network mainnet-network])}])
|
||||
[quo/divider-label {:container-style style/divider-label}
|
||||
(i18n/label :t/layer-2)]
|
||||
[rn/flat-list
|
||||
{:data (vec layer-2-networks)
|
||||
:render-fn render-fn
|
||||
:scroll-enabled false}]]))
|
||||
@@ -7,11 +7,20 @@
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn- crypto-on-ramp-item
|
||||
[{:keys [name description fees logo-url site-url recurrent-site-url]} _ _ {:keys [tab]}]
|
||||
(let [open-url (rn/use-callback (fn []
|
||||
(rf/dispatch [:open-url
|
||||
(if (= tab :recurrent) recurrent-site-url site-url)]))
|
||||
[site-url recurrent-site-url tab])]
|
||||
[{:keys [name description fees logo-url site-url recurrent-site-url urls-need-parameters]
|
||||
:as provider}
|
||||
_
|
||||
_
|
||||
{:keys [account tab]}]
|
||||
(let [open-url (rn/use-callback
|
||||
(fn []
|
||||
(if urls-need-parameters
|
||||
(rf/dispatch [:wallet.buy-crypto/select-provider
|
||||
{:account account
|
||||
:provider provider
|
||||
:recurrent? (= tab :recurrent)}])
|
||||
(rn/open-url (if (= tab :recurrent) recurrent-site-url site-url))))
|
||||
[site-url recurrent-site-url tab])]
|
||||
[quo/settings-item
|
||||
{:title name
|
||||
:description :text
|
||||
@@ -39,13 +48,14 @@
|
||||
(rn/use-mount (fn []
|
||||
(rf/dispatch [:wallet/get-crypto-on-ramps])))
|
||||
(let [crypto-on-ramps (rf/sub [:wallet/crypto-on-ramps])
|
||||
account (rf/sub [:wallet/current-viewing-account-or-default])
|
||||
[selected-tab set-selected-tab] (rn/use-state initial-tab)
|
||||
[min-height set-min-height] (rn/use-state 0)
|
||||
on-layout (rn/use-callback
|
||||
#(set-min-height
|
||||
(oops/oget % :nativeEvent :layout :height)))]
|
||||
[:<>
|
||||
[quo/drawer-top {:title (or title (i18n/label :t/buy-assets))}]
|
||||
[quo/drawer-top {:title (or title (i18n/label :t/ways-to-buy-assets))}]
|
||||
[quo/segmented-control
|
||||
{:size 32
|
||||
:container-style style/tabs
|
||||
@@ -58,5 +68,6 @@
|
||||
(:one-time crypto-on-ramps))
|
||||
:on-layout on-layout
|
||||
:style (style/list-container min-height)
|
||||
:render-data {:tab selected-tab}
|
||||
:render-data {:tab selected-tab
|
||||
:account account}
|
||||
:render-fn crypto-on-ramp-item}]]))
|
||||
|
||||
@@ -2,30 +2,60 @@
|
||||
(:require [quo.core :as quo]
|
||||
quo.theme
|
||||
[react-native.gesture :as gesture]
|
||||
[status-im.contexts.wallet.common.utils :as wallet.utils]
|
||||
[status-im.contexts.wallet.sheets.select-account.style :as style]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn- render-account-item
|
||||
[{:keys [color address] :as account} _ _ {:keys [selected-account-address]}]
|
||||
[quo/account-item
|
||||
{:type :default
|
||||
:account-props (assoc account :customization-color color)
|
||||
:customization-color color
|
||||
:state (if (= address selected-account-address) :selected :default)
|
||||
:on-press (fn []
|
||||
(rf/dispatch [:wallet/switch-current-viewing-account address])
|
||||
(rf/dispatch [:hide-bottom-sheet]))}])
|
||||
(defn list-item
|
||||
[{:keys [account selected-account-address]}]
|
||||
(let [{:keys [color address]} account]
|
||||
[quo/account-item
|
||||
{:type :default
|
||||
:account-props (assoc account :customization-color color)
|
||||
:customization-color color
|
||||
:state (if (= address selected-account-address) :selected :default)
|
||||
:on-press (fn []
|
||||
(rf/dispatch [:wallet/switch-current-viewing-account address])
|
||||
(rf/dispatch [:hide-bottom-sheet]))}]))
|
||||
|
||||
(defn list-item-with-balance
|
||||
[{:keys [account selected-account-address asset-symbol network]}]
|
||||
(let [{:keys [color address tokens]} account
|
||||
token (->> tokens
|
||||
(filter #(= (:symbol %) asset-symbol))
|
||||
first)
|
||||
token-balance (get-in token [:balances-per-chain (:chain-id network) :balance])]
|
||||
[quo/account-item
|
||||
{:type (if (= address selected-account-address) :default :tag)
|
||||
:token-props {:symbol asset-symbol
|
||||
:value (wallet.utils/cut-fiat-balance (or (js/parseFloat token-balance) 0)
|
||||
4)}
|
||||
:account-props (assoc account :customization-color color)
|
||||
:customization-color color
|
||||
:state (if (= address selected-account-address) :selected :default)
|
||||
:on-press (fn []
|
||||
(rf/dispatch [:wallet/switch-current-viewing-account address])
|
||||
(rf/dispatch [:hide-bottom-sheet]))}]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
[{:keys [show-account-balances? asset-symbol network]}]
|
||||
(let [selected-account-address (rf/sub [:wallet/current-viewing-account-address])
|
||||
accounts (rf/sub [:wallet/operable-accounts])]
|
||||
[:<>
|
||||
[quo/drawer-top {:title (i18n/label :t/select-account)}]
|
||||
[gesture/flat-list
|
||||
{:data accounts
|
||||
:render-fn render-account-item
|
||||
:render-fn (fn [account _ _ {:keys [selected-account-address]}]
|
||||
(if show-account-balances?
|
||||
[list-item-with-balance
|
||||
{:account account
|
||||
:selected-account-address selected-account-address
|
||||
:asset-symbol asset-symbol
|
||||
:network network}]
|
||||
[list-item
|
||||
{:account account
|
||||
:selected-account-address selected-account-address}]))
|
||||
:render-data {:selected-account-address selected-account-address}
|
||||
:content-container-style style/list-container
|
||||
:shows-vertical-scroll-indicator false}]]))
|
||||
|
||||
@@ -51,14 +51,31 @@
|
||||
:network-name full-name
|
||||
:type :network}])
|
||||
|
||||
(defn- provider-view
|
||||
[{:keys [name logo-url]}]
|
||||
[quo/context-tag
|
||||
{:size 24
|
||||
:network-logo logo-url
|
||||
:network-name name
|
||||
:type :network}])
|
||||
|
||||
(defn- subheader-view
|
||||
[{:keys [account network]}]
|
||||
[{:keys [provider account network]}]
|
||||
[rn/view {:style style/subheader-container}
|
||||
[quo/text
|
||||
{:size :paragraph-2
|
||||
:weight :medium}
|
||||
(i18n/label :t/select-asset-on)]
|
||||
[account-view account]
|
||||
(if provider
|
||||
[provider-view provider]
|
||||
[account-view account])
|
||||
(when provider
|
||||
[:<>
|
||||
[quo/text
|
||||
{:size :paragraph-2
|
||||
:weight :medium}
|
||||
(i18n/label :t/to)]
|
||||
[account-view account]])
|
||||
[quo/text
|
||||
{:size :paragraph-2
|
||||
:weight :medium}
|
||||
@@ -66,9 +83,8 @@
|
||||
[network-view network]])
|
||||
|
||||
(defn view
|
||||
[{:keys [title on-select hide-token-fn disable-token-fn]}]
|
||||
(let [account (rf/sub [:wallet/current-viewing-account])
|
||||
{:keys [network]} (rf/sub [:wallet/swap])
|
||||
[{:keys [title network provider on-select hide-token-fn disable-token-fn]}]
|
||||
(let [account (rf/sub [:wallet/current-viewing-account-or-default])
|
||||
[search-text set-search-text] (rn/use-state "")
|
||||
window-height (:height (rn/get-window))]
|
||||
[rn/safe-area-view {:style (style/container window-height)}
|
||||
@@ -81,8 +97,9 @@
|
||||
{:title title
|
||||
:title-accessibility-label :title-label}]
|
||||
[subheader-view
|
||||
{:account account
|
||||
:network network}]
|
||||
{:account account
|
||||
:network network
|
||||
:provider provider}]
|
||||
[assets-view
|
||||
{:search-text search-text
|
||||
:on-change-text #(set-search-text %)
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
[utils.number :as number]))
|
||||
|
||||
(rf/reg-event-fx :wallet.swap/start
|
||||
(fn [{:keys [db]} [{:keys [network open-new-screen?] :as data}]]
|
||||
(fn [{:keys [db]} [{:keys [network asset-to-receive open-new-screen?] :as data}]]
|
||||
(let [{:keys [wallet]} db
|
||||
test-networks-enabled? (get-in db [:profile/profile :test-networks-enabled?])
|
||||
account (swap-utils/wallet-account wallet)
|
||||
@@ -25,12 +25,6 @@
|
||||
:account account
|
||||
:test-networks-enabled? test-networks-enabled?
|
||||
:token-symbol (get-in data [:asset-to-pay :symbol])}))
|
||||
asset-to-receive (or (:asset-to-receive data)
|
||||
(swap-utils/select-default-asset-to-receive
|
||||
{:wallet wallet
|
||||
:account account
|
||||
:test-networks-enabled? test-networks-enabled?
|
||||
:asset-to-pay asset-to-pay}))
|
||||
network' (or network
|
||||
(swap-utils/select-network asset-to-pay))]
|
||||
{:db (-> db
|
||||
@@ -76,11 +70,7 @@
|
||||
|
||||
(rf/reg-event-fx :wallet.swap/select-asset-to-receive
|
||||
(fn [{:keys [db]} [{:keys [token]}]]
|
||||
{:db (update-in db
|
||||
[:wallet :ui :swap]
|
||||
#(-> %
|
||||
(assoc :asset-to-receive token)
|
||||
(assoc :loading-swap-proposal? true)))}))
|
||||
{:db (assoc-in db [:wallet :ui :swap :asset-to-receive] token)}))
|
||||
|
||||
(rf/reg-event-fx :wallet.swap/set-default-slippage
|
||||
(fn [{:keys [db]}]
|
||||
@@ -141,8 +131,6 @@
|
||||
:amount amount
|
||||
:amount-hex amount-in-hex
|
||||
:loading-swap-proposal? true)
|
||||
:always
|
||||
(dissoc :error-response)
|
||||
clean-approval-transaction?
|
||||
(dissoc :approval-transaction-id :approved-amount :swap-proposal)))
|
||||
:json-rpc/call [{:method "wallet_getSuggestedRoutesAsync"
|
||||
@@ -216,6 +204,12 @@
|
||||
(fn [{:keys [db]}]
|
||||
{:db (update-in db [:wallet :ui] dissoc :swap)}))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet/on-swap-done
|
||||
(fn [_]
|
||||
{:fx [[:dispatch [:wallet/select-account-tab :activity]]
|
||||
[:dispatch [:wallet/clean-swap]]]}))
|
||||
|
||||
(rf/reg-event-fx :wallet/swap-transaction
|
||||
(fn [{:keys [db]} [sha3-pwd]]
|
||||
(let [wallet-address (get-in db
|
||||
@@ -310,8 +304,6 @@
|
||||
:screen/wallet.swap-set-spending-cap
|
||||
:screen/wallet.swap-confirmation)])
|
||||
(when-not approval-required?
|
||||
(rf/dispatch [:wallet/select-account-tab :activity])
|
||||
(rf/dispatch [:wallet/clean-swap])
|
||||
(debounce/debounce-and-dispatch
|
||||
[:toasts/upsert
|
||||
{:id :swap-transaction-pending
|
||||
|
||||
@@ -20,10 +20,15 @@
|
||||
|
||||
(defn- assets-view
|
||||
[search-text on-change-text]
|
||||
(let [on-token-press (fn [token]
|
||||
(rf/dispatch [:wallet.swap/start
|
||||
{:asset-to-pay token
|
||||
:open-new-screen? false}]))]
|
||||
(let [snt-token (rf/sub [:wallet/token-by-symbol "SNT"])
|
||||
eth-token (rf/sub [:wallet/token-by-symbol "ETH"])
|
||||
on-token-press (fn [token]
|
||||
(let [pay-token-symbol (:symbol token)
|
||||
asset-to-receive (if (= pay-token-symbol "SNT") eth-token snt-token)]
|
||||
(rf/dispatch [:wallet.swap/start
|
||||
{:asset-to-pay token
|
||||
:asset-to-receive asset-to-receive
|
||||
:open-new-screen? false}])))]
|
||||
[:<>
|
||||
[search-input search-text on-change-text]
|
||||
[asset-list/view
|
||||
|
||||
@@ -10,8 +10,12 @@
|
||||
:height 36
|
||||
:background-color :transparent})
|
||||
|
||||
(def floating-button-page-content-container
|
||||
{:margin-bottom 168})
|
||||
|
||||
(def scroll-view-container
|
||||
{:padding-bottom 150})
|
||||
{:flex 1
|
||||
:padding-bottom 20})
|
||||
|
||||
(def content-container
|
||||
{:padding-top 12
|
||||
@@ -56,5 +60,5 @@
|
||||
|
||||
(defn footer-container
|
||||
[theme]
|
||||
{:margin-bottom -10
|
||||
:background-color (colors/theme-colors colors/white colors/neutral-100 theme)})
|
||||
{:margin-bottom -12
|
||||
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)})
|
||||
|
||||
@@ -254,6 +254,7 @@
|
||||
[rn/view {:style style/container}
|
||||
[floating-button-page/view
|
||||
{:footer-container-padding 0
|
||||
:content-container-style style/floating-button-page-content-container
|
||||
:header [quo/page-nav
|
||||
{:icon-name :i/close
|
||||
:on-press events-helper/navigate-back
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
(ns status-im.contexts.wallet.swap.setup-swap.view
|
||||
(:require [clojure.string :as string]
|
||||
[quo.core :as quo]
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[status-im.common.controlled-input.utils :as controlled-input]
|
||||
[status-im.common.events-helper :as events-helper]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.common.account-switcher.view :as account-switcher]
|
||||
[status-im.contexts.wallet.common.utils :as utils]
|
||||
[status-im.contexts.wallet.sheets.buy-token.view :as buy-token]
|
||||
[status-im.contexts.wallet.sheets.select-asset.view :as select-asset]
|
||||
[status-im.contexts.wallet.sheets.slippage-settings.view :as slippage-settings]
|
||||
[status-im.contexts.wallet.swap.setup-swap.style :as style]
|
||||
[status-im.contexts.wallet.swap.utils :as swap-utils]
|
||||
[utils.debounce :as debounce]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.money :as money]
|
||||
[utils.number :as number]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.string :as utils.string]))
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[oops.core :as oops]
|
||||
[quo.core :as quo]
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[status-im.common.controlled-input.utils :as controlled-input]
|
||||
[status-im.common.events-helper :as events-helper]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.common.account-switcher.view :as account-switcher]
|
||||
[status-im.contexts.wallet.common.utils :as utils]
|
||||
[status-im.contexts.wallet.sheets.buy-token.view :as buy-token]
|
||||
[status-im.contexts.wallet.sheets.select-asset.view :as select-asset]
|
||||
[status-im.contexts.wallet.sheets.slippage-settings.view :as slippage-settings]
|
||||
[status-im.contexts.wallet.swap.setup-swap.style :as style]
|
||||
[status-im.contexts.wallet.swap.utils :as swap-utils]
|
||||
[utils.debounce :as debounce]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.money :as money]
|
||||
[utils.number :as number]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.string :as utils.string]))
|
||||
|
||||
(def ^:private default-text-for-unfocused-input "0.00")
|
||||
|
||||
@@ -85,7 +87,7 @@
|
||||
[{:keys [input-state on-max-press on-input-focus on-token-press on-approve-press input-focused?]}]
|
||||
(let [account-color (rf/sub [:wallet/current-viewing-account-color])
|
||||
network (rf/sub [:wallet/swap-network])
|
||||
asset-to-pay (rf/sub [:wallet/swap-asset-to-pay])
|
||||
pay-token-symbol (:symbol (rf/sub [:wallet/swap-asset-to-pay]))
|
||||
currency (rf/sub [:profile/currency])
|
||||
loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?])
|
||||
swap-proposal (rf/sub [:wallet/swap-proposal-without-fees])
|
||||
@@ -96,14 +98,19 @@
|
||||
approval-transaction-id (rf/sub [:wallet/swap-approval-transaction-id])
|
||||
approved-amount (rf/sub [:wallet/swap-approved-amount])
|
||||
error-response (rf/sub [:wallet/swap-error-response])
|
||||
asset-to-pay (rf/sub [:wallet/token-by-symbol pay-token-symbol])
|
||||
overlay-shown? (boolean (:sheets (rf/sub [:bottom-sheet])))
|
||||
input-ref (rn/use-ref-atom nil)
|
||||
set-input-ref (rn/use-callback (fn [ref] (reset! input-ref ref)))
|
||||
pay-input-num-value (controlled-input/value-numeric input-state)
|
||||
pay-input-amount (controlled-input/input-value input-state)
|
||||
pay-token-symbol (:symbol asset-to-pay)
|
||||
pay-token-decimals (:decimals asset-to-pay)
|
||||
pay-token-balance-selected-chain (get-in asset-to-pay
|
||||
[:balances-per-chain
|
||||
(:chain-id network) :balance]
|
||||
0)
|
||||
pay-token-balance-selected-chain (number/convert-to-whole-number
|
||||
(get-in asset-to-pay
|
||||
[:balances-per-chain
|
||||
(:chain-id network) :raw-balance]
|
||||
0)
|
||||
pay-token-decimals)
|
||||
pay-token-fiat-value (str
|
||||
(utils/calculate-token-fiat-value
|
||||
{:currency currency
|
||||
@@ -111,10 +118,15 @@
|
||||
:token asset-to-pay}))
|
||||
available-crypto-limit (money/bignumber
|
||||
pay-token-balance-selected-chain)
|
||||
display-decimals (min pay-token-decimals
|
||||
constants/min-token-decimals-to-display)
|
||||
available-crypto-limit-display (number/remove-trailing-zeroes
|
||||
(.toFixed available-crypto-limit
|
||||
(min pay-token-decimals
|
||||
constants/min-token-decimals-to-display)))
|
||||
(.toFixed available-crypto-limit display-decimals))
|
||||
available-crypto-limit-display (if (and (= available-crypto-limit-display "0")
|
||||
(money/greater-than available-crypto-limit
|
||||
(money/bignumber 0)))
|
||||
(number/small-number-threshold display-decimals)
|
||||
available-crypto-limit-display)
|
||||
approval-amount-required-num (when approval-amount-required
|
||||
(str (number/hex->whole approval-amount-required
|
||||
pay-token-decimals)))
|
||||
@@ -122,8 +134,7 @@
|
||||
(money/greater-than
|
||||
(money/bignumber pay-input-amount)
|
||||
available-crypto-limit))
|
||||
(money/equal-to (money/bignumber
|
||||
available-crypto-limit-display)
|
||||
(money/equal-to available-crypto-limit
|
||||
(money/bignumber 0)))
|
||||
valid-pay-input? (and
|
||||
(not (string/blank?
|
||||
@@ -137,12 +148,20 @@
|
||||
:valid-input? valid-pay-input?
|
||||
:clean-approval-transaction? true}))
|
||||
[pay-input-amount])]
|
||||
(rn/use-unmount #(rf/dispatch [:wallet/on-swap-done]))
|
||||
(rn/use-effect
|
||||
(fn []
|
||||
(request-fetch-swap-proposal))
|
||||
[pay-input-amount])
|
||||
(rn/use-effect
|
||||
(fn []
|
||||
(when-not overlay-shown?
|
||||
(some-> @input-ref
|
||||
(oops/ocall "focus"))))
|
||||
[overlay-shown?])
|
||||
[quo/swap-input
|
||||
{:type :pay
|
||||
{:get-ref set-input-ref
|
||||
:type :pay
|
||||
:error? pay-input-error?
|
||||
:token pay-token-symbol
|
||||
:customization-color :blue
|
||||
@@ -156,7 +175,7 @@
|
||||
:else :disabled)
|
||||
:currency-symbol currency-symbol
|
||||
:on-token-press on-token-press
|
||||
:on-max-press #(on-max-press (str available-crypto-limit))
|
||||
:on-max-press #(on-max-press (str pay-token-balance-selected-chain))
|
||||
:on-input-focus on-input-focus
|
||||
:value pay-input-amount
|
||||
:fiat-value pay-token-fiat-value
|
||||
@@ -239,17 +258,15 @@
|
||||
(merge {:action? true
|
||||
:on-button-press #(rf/dispatch [:show-bottom-sheet
|
||||
{:content buy-token/view}])
|
||||
:button-text (i18n/label :t/buy-assets)})
|
||||
:button-text (i18n/label :t/add-assets)})
|
||||
(= error-response-code
|
||||
constants/router-error-code-not-enough-native-balance)
|
||||
(merge {:action? true
|
||||
:on-button-press #(rf/dispatch
|
||||
[:show-bottom-sheet
|
||||
{:content (fn []
|
||||
[buy-token/view
|
||||
{:title (i18n/label
|
||||
:t/buy-ethereum)}])}])
|
||||
:button-text (i18n/label :t/buy-eth)}))]
|
||||
[buy-token/view])}])
|
||||
:button-text (i18n/label :t/add-eth)}))]
|
||||
(when (or pay-input-error? error-response)
|
||||
[quo/alert-banner props])))
|
||||
|
||||
@@ -274,23 +291,28 @@
|
||||
|
||||
(defn- pay-token-bottom-sheet
|
||||
[]
|
||||
(let [asset-to-receive (rf/sub [:wallet/swap-asset-to-receive])]
|
||||
(let [asset-to-receive (rf/sub [:wallet/swap-asset-to-receive])
|
||||
network (rf/sub [:wallet/swap-network])]
|
||||
[select-asset/view
|
||||
{:title (i18n/label :t/select-asset-to-pay)
|
||||
:network network
|
||||
:on-select (fn [token]
|
||||
(rf/dispatch [:wallet.swap/select-asset-to-pay {:token token}]))
|
||||
:hide-token-fn (fn [type {:keys [available-balance]}]
|
||||
(and (= type constants/swap-tokens-my)
|
||||
(= available-balance 0)))
|
||||
:hide-token-fn (fn [type {:keys [balances-per-chain]}]
|
||||
(let [balance (get-in balances-per-chain [(:chain-id network) :balance] "0")]
|
||||
(and (= type constants/swap-tokens-my)
|
||||
(= balance "0"))))
|
||||
:disable-token-fn (fn [_ token]
|
||||
(= (:symbol token)
|
||||
(:symbol asset-to-receive)))}]))
|
||||
|
||||
(defn- receive-token-bottom-sheet
|
||||
[]
|
||||
(let [asset-to-pay (rf/sub [:wallet/swap-asset-to-pay])]
|
||||
(let [asset-to-pay (rf/sub [:wallet/swap-asset-to-pay])
|
||||
network (rf/sub [:wallet/swap-network])]
|
||||
[select-asset/view
|
||||
{:title (i18n/label :t/select-asset-to-receive)
|
||||
:network network
|
||||
:on-select (fn [token]
|
||||
(rf/dispatch [:wallet.swap/select-asset-to-receive {:token token}]))
|
||||
:disable-token-fn (fn [_ token]
|
||||
@@ -310,10 +332,12 @@
|
||||
network (rf/sub [:wallet/swap-network])
|
||||
pay-input-amount (controlled-input/input-value pay-input-state)
|
||||
pay-token-decimals (:decimals asset-to-pay)
|
||||
pay-token-balance-selected-chain (get-in asset-to-pay
|
||||
[:balances-per-chain
|
||||
(:chain-id network) :balance]
|
||||
0)
|
||||
pay-token-balance-selected-chain (number/convert-to-whole-number
|
||||
(get-in asset-to-pay
|
||||
[:balances-per-chain
|
||||
(:chain-id network) :raw-balance]
|
||||
0)
|
||||
pay-token-decimals)
|
||||
pay-input-error? (and (not (string/blank? pay-input-amount))
|
||||
(money/greater-than
|
||||
(money/bignumber pay-input-amount)
|
||||
@@ -415,13 +439,17 @@
|
||||
(when (and swap-amount refetch-interval)
|
||||
(js/clearTimeout @refetch-interval)
|
||||
(reset! refetch-interval nil))
|
||||
(if (and swap-amount (not= swap-amount pay-input-amount))
|
||||
(set-pay-input-state
|
||||
(fn [input-state]
|
||||
(controlled-input/set-input-value
|
||||
input-state
|
||||
swap-amount)))
|
||||
(refetch-swap-proposal)))))
|
||||
(cond (and swap-amount (not= swap-amount pay-input-amount))
|
||||
(set-pay-input-state
|
||||
(fn [input-state]
|
||||
(controlled-input/set-input-value
|
||||
input-state
|
||||
swap-amount)))
|
||||
(and pay-input-amount
|
||||
(not (number/valid-decimal-count? pay-input-amount (:decimals asset-to-pay))))
|
||||
(set-pay-input-state controlled-input/delete-all)
|
||||
:else
|
||||
(refetch-swap-proposal)))))
|
||||
[asset-to-pay])
|
||||
(rn/use-effect
|
||||
refetch-swap-proposal
|
||||
@@ -431,7 +459,10 @@
|
||||
{:on-press on-close
|
||||
:icon-name :i/arrow-left
|
||||
:margin-top (safe-area/get-top)
|
||||
:switcher-type :select-account}]
|
||||
:switcher-type :select-account
|
||||
:params {:show-account-balances? true
|
||||
:asset-symbol (:symbol asset-to-pay)
|
||||
:network network}}]
|
||||
[rn/view {:style style/inputs-container}
|
||||
[pay-token-input
|
||||
{:input-state pay-input-state
|
||||
@@ -453,7 +484,7 @@
|
||||
:on-token-press #(rf/dispatch [:show-bottom-sheet {:content receive-token-bottom-sheet}])
|
||||
:on-input-focus #(set-pay-input-focused? false)}]]
|
||||
[rn/view {:style style/footer-container}
|
||||
[alert-banner {:pay-input-error? pay-input-error?}]
|
||||
(when-not loading-swap-proposal? [alert-banner {:pay-input-error? pay-input-error?}])
|
||||
(when (or loading-swap-proposal? swap-proposal)
|
||||
[transaction-details])
|
||||
[action-button {:on-press on-review-swap-press}]]
|
||||
|
||||
@@ -34,9 +34,8 @@
|
||||
|
||||
(defn tokens-by-symbol
|
||||
[props]
|
||||
(tokens-by-key (assoc props
|
||||
:key-fn
|
||||
(fn [{:keys [address] :as token}]
|
||||
(if (get-in token [:communityData :id])
|
||||
address
|
||||
(:symbol token))))))
|
||||
(tokens-by-key
|
||||
(assoc props
|
||||
:key-fn
|
||||
(fn [{:keys [chainId address] :as token}]
|
||||
(str chainId "-" (if (get-in token [:communityData :id]) address (:symbol token)))))))
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
:image nil})
|
||||
:by-symbol '({:address "0x0000000000000000000000000000000000000000"
|
||||
:decimals 18
|
||||
:key "ETH"
|
||||
:key "1-ETH"
|
||||
:community-id nil
|
||||
:symbol "ETH"
|
||||
:sources ["native"]
|
||||
@@ -86,7 +86,7 @@
|
||||
:image nil}
|
||||
{:address "0x0000000000000000000000000000000000000001"
|
||||
:decimals 18
|
||||
:key "SNT"
|
||||
:key "1-SNT"
|
||||
:community-id nil
|
||||
:symbol "SNT"
|
||||
:sources ["native"]
|
||||
@@ -174,7 +174,7 @@
|
||||
:image nil})
|
||||
:by-symbol '({:address "0x0000000000000000000000000000000000000000"
|
||||
:decimals 18
|
||||
:key "ETH"
|
||||
:key "1-ETH"
|
||||
:community-id nil
|
||||
:symbol "ETH"
|
||||
:sources ["native"]
|
||||
@@ -185,7 +185,7 @@
|
||||
:image nil}
|
||||
{:address "0x0000000000000000000000000000000000000001"
|
||||
:decimals 18
|
||||
:key "SNT"
|
||||
:key "1-SNT"
|
||||
:community-id nil
|
||||
:symbol "SNT"
|
||||
:sources ["native" "second"]
|
||||
@@ -196,7 +196,7 @@
|
||||
:image nil}
|
||||
{:address "0x0000000000000000000000000000000000000002"
|
||||
:decimals 18
|
||||
:key "ANOTHER1"
|
||||
:key "1-ANOTHER1"
|
||||
:community-id nil
|
||||
:symbol "ANOTHER1"
|
||||
:sources ["second"]
|
||||
@@ -253,7 +253,7 @@
|
||||
:image nil})
|
||||
:by-symbol '({:address "0x0000000000000000000000000000000000000000"
|
||||
:decimals 18
|
||||
:key "ETH"
|
||||
:key "1-ETH"
|
||||
:community-id nil
|
||||
:symbol "ETH"
|
||||
:sources ["native"]
|
||||
@@ -264,7 +264,7 @@
|
||||
:image nil}
|
||||
{:address "0x0000000000000000000000000000000000000000"
|
||||
:decimals 18
|
||||
:key "ANOTHER1"
|
||||
:key "1-ANOTHER1"
|
||||
:community-id nil
|
||||
:symbol "ANOTHER1"
|
||||
:sources ["native" "second"]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
(ns status-im.contexts.wallet.wallet-connect.events.effects
|
||||
(:require
|
||||
[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]
|
||||
@@ -10,6 +9,7 @@
|
||||
[status-im.contexts.wallet.wallet-connect.utils.transactions :as transactions]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.typed-data :as typed-data]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]))
|
||||
|
||||
(rf/reg-fx
|
||||
@@ -56,8 +56,8 @@
|
||||
:proposal-request proposal-request
|
||||
:address address
|
||||
:session-networks session-networks})
|
||||
(promesa/then on-success)
|
||||
(promesa/catch on-fail))))
|
||||
(promesa/then (partial rf/call-continuation on-success))
|
||||
(promesa/catch (partial rf/call-continuation on-fail)))))
|
||||
|
||||
(rf/reg-fx
|
||||
:effects.wallet-connect/sign-message
|
||||
@@ -72,8 +72,8 @@
|
||||
(signing/eth-sign password address data)
|
||||
|
||||
(signing/personal-sign password address data))
|
||||
(promesa/then on-success)
|
||||
(promesa/catch on-error)))))
|
||||
(promesa/then (partial rf/call-continuation on-success))
|
||||
(promesa/catch (partial rf/call-continuation on-error))))))
|
||||
|
||||
(rf/reg-fx
|
||||
:effects.wallet-connect/prepare-transaction
|
||||
@@ -92,8 +92,8 @@
|
||||
tx-hash
|
||||
tx-args
|
||||
chain-id)
|
||||
(promesa/then on-success)
|
||||
(promesa/catch on-error))))
|
||||
(promesa/then (partial rf/call-continuation on-success))
|
||||
(promesa/catch (partial rf/call-continuation on-error)))))
|
||||
|
||||
(rf/reg-fx
|
||||
:effects.wallet-connect/send-transaction
|
||||
@@ -103,8 +103,8 @@
|
||||
tx-hash
|
||||
tx-args
|
||||
chain-id)
|
||||
(promesa/then on-success)
|
||||
(promesa/catch on-error))))
|
||||
(promesa/then (partial rf/call-continuation on-success))
|
||||
(promesa/catch (partial rf/call-continuation on-error)))))
|
||||
|
||||
(rf/reg-fx
|
||||
:effects.wallet-connect/sign-typed-data
|
||||
@@ -114,8 +114,8 @@
|
||||
data
|
||||
chain-id
|
||||
version)
|
||||
(promesa/then on-success)
|
||||
(promesa/catch on-error))))
|
||||
(promesa/then (partial rf/call-continuation on-success))
|
||||
(promesa/catch (partial rf/call-continuation on-error)))))
|
||||
|
||||
(rf/reg-fx
|
||||
:effects.wallet-connect/respond-session-request
|
||||
|
||||
@@ -135,17 +135,21 @@
|
||||
:proposal-request current-proposal
|
||||
:session-networks session-networks
|
||||
:address current-address
|
||||
:on-success #(rf/dispatch [:wallet-connect/approve-session-success %])
|
||||
:on-fail #(rf/dispatch [:wallet-connect/approve-session-error %])}])
|
||||
:on-success [:wallet-connect/approve-session-success]
|
||||
:on-fail [:wallet-connect/approve-session-error]}])
|
||||
[:dispatch [:dismiss-modal :screen/wallet.wallet-connect-session-proposal]]]}
|
||||
{:fx [[:dispatch [:wallet-connect/no-internet-toast]]]}))))
|
||||
|
||||
(rf/reg-event-fx :wallet-connect/approve-session-success
|
||||
(fn [_ [session]]
|
||||
(log/info "Wallet Connect session approved")
|
||||
{:fx [[:dispatch [:wallet-connect/on-new-session session]]
|
||||
[:dispatch [:wallet-connect/reset-current-session-proposal]]
|
||||
[:dispatch [:wallet-connect/redirect-to-dapp (data-store/get-dapp-redirect-url session)]]]}))
|
||||
(fn [{:keys [db]} [session]]
|
||||
(let [total-connected-dapps (data-store/get-total-connected-dapps db)]
|
||||
{:fx [[:dispatch [:wallet-connect/on-new-session session]]
|
||||
[:dispatch [:wallet-connect/reset-current-session-proposal]]
|
||||
[:dispatch [:wallet-connect/redirect-to-dapp (data-store/get-dapp-redirect-url session)]]
|
||||
[:dispatch
|
||||
[:centralized-metrics/track :metric/dapp-session-proposal
|
||||
{:action :approved
|
||||
:total_connected_dapps total-connected-dapps}]]]})))
|
||||
|
||||
(rf/reg-event-fx :wallet-connect/approve-session-error
|
||||
(fn [_ [error]]
|
||||
@@ -158,11 +162,18 @@
|
||||
:wallet-connect/reject-session-proposal
|
||||
(fn [{:keys [db]} [proposal]]
|
||||
(let [web3-wallet (get db :wallet-connect/web3-wallet)
|
||||
{:keys [request response-sent?]} (:wallet-connect/current-proposal db)]
|
||||
{:fx [(when-not response-sent?
|
||||
[:effects.wallet-connect/reject-session-proposal
|
||||
{:web3-wallet web3-wallet
|
||||
:proposal (or proposal request)
|
||||
: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-proposal]]]})))
|
||||
{:keys [request response-sent?]} (:wallet-connect/current-proposal db)
|
||||
networks (networks/get-proposal-networks (or proposal request))
|
||||
rejected? (nil? proposal)]
|
||||
{:fx (concat
|
||||
(when-not response-sent?
|
||||
[[:effects.wallet-connect/reject-session-proposal
|
||||
{:web3-wallet web3-wallet
|
||||
:proposal (or proposal request)
|
||||
:on-success #(log/info "Wallet Connect session proposal rejected")
|
||||
:on-error #(log/error "Wallet Connect unable to reject session proposal")}]
|
||||
[:dispatch
|
||||
[:centralized-metrics/track :metric/dapp-session-proposal
|
||||
{:action (if rejected? :rejected :not_supported)
|
||||
:networks networks}]]])
|
||||
[[:dispatch [:wallet-connect/reset-current-session-proposal]]])})))
|
||||
|
||||
@@ -51,8 +51,8 @@
|
||||
:address address
|
||||
:data raw-data
|
||||
:rpc-method rpc-method
|
||||
:on-error #(rf/dispatch [:wallet-connect/on-sign-error %])
|
||||
:on-success #(rf/dispatch [:wallet-connect/finish-session-request %])}]]})))
|
||||
:on-error [:wallet-connect/on-sign-error]
|
||||
:on-success [:wallet-connect/finish-session-request]}]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/respond-sign-typed-data
|
||||
@@ -65,8 +65,8 @@
|
||||
: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/finish-session-request %])}]]})))
|
||||
:on-error [:wallet-connect/on-sign-error]
|
||||
:on-success [:wallet-connect/finish-session-request]}]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/respond-send-transaction-data
|
||||
@@ -79,8 +79,8 @@
|
||||
:chain-id chain-id
|
||||
:tx-hash tx-hash
|
||||
:tx-args tx-args
|
||||
:on-error #(rf/dispatch [:wallet-connect/on-sign-error %])
|
||||
:on-success #(rf/dispatch [:wallet-connect/finish-session-request %])}]]})))
|
||||
:on-error [:wallet-connect/on-sign-error]
|
||||
:on-success [:wallet-connect/finish-session-request]}]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/respond-sign-transaction-data
|
||||
@@ -93,8 +93,8 @@
|
||||
:chain-id chain-id
|
||||
:tx-hash tx-hash
|
||||
:tx-params tx-args
|
||||
:on-error #(rf/dispatch [:wallet-connect/on-sign-error %])
|
||||
:on-success #(rf/dispatch [:wallet-connect/finish-session-request %])}]]})))
|
||||
:on-error [:wallet-connect/on-sign-error]
|
||||
:on-success [:wallet-connect/finish-session-request]}]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/on-sign-error
|
||||
@@ -161,9 +161,15 @@
|
||||
|
||||
(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]]]}))
|
||||
(fn [{:keys [db]} [result]]
|
||||
(let [event (get-in db [:wallet-connect/current-request :event])
|
||||
method (data-store/get-request-method event)]
|
||||
{:fx [[:dispatch
|
||||
[:centralized-metrics/track :metric/dapp-session-response
|
||||
{:method method
|
||||
:approved true}]]
|
||||
[:dispatch [:wallet-connect/send-response {:result result}]]
|
||||
[:dispatch [:wallet-connect/dismiss-request-modal]]]})))
|
||||
|
||||
;; NOTE: Currently we only reject a session if the user dismissed a modal
|
||||
;; without accepting the session first.
|
||||
@@ -176,9 +182,17 @@
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/on-request-modal-dismissed
|
||||
(fn [{:keys [db]}]
|
||||
{:fx [(when-not (get-in db [:wallet-connect/current-request :response-sent?])
|
||||
[:dispatch
|
||||
[:wallet-connect/send-response
|
||||
{:error (wallet-connect/get-sdk-error
|
||||
constants/wallet-connect-user-rejected-error-key)}]])
|
||||
[:dispatch [:wallet-connect/reset-current-request]]]}))
|
||||
(let [{:keys [response-sent? event]} (get db :wallet-connect/current-request)
|
||||
method (data-store/get-request-method event)]
|
||||
{:fx (concat
|
||||
(when-not response-sent?
|
||||
[[:dispatch
|
||||
[:centralized-metrics/track :metric/dapp-session-response
|
||||
{:method method
|
||||
:approved false}]]
|
||||
[:dispatch
|
||||
[:wallet-connect/send-response
|
||||
{:request event
|
||||
:error (wallet-connect/get-sdk-error
|
||||
constants/wallet-connect-user-rejected-error-key)}]]])
|
||||
[[:dispatch [:wallet-connect/reset-current-request]]])})))
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
[{:method "wallet_disconnectWalletConnectSession"
|
||||
:params [topic]
|
||||
:on-success [:wallet-connect/delete-session topic]
|
||||
:on-error #(log/info "Wallet Connect session persistence failed" %)}]]]})))
|
||||
:on-error #(log/info "Wallet Connect session persistence failed" %)}]]
|
||||
[:dispatch
|
||||
[:centralized-metrics/track
|
||||
:metric/dapp-session-disconnected]]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/disconnect-dapp
|
||||
@@ -26,10 +29,14 @@
|
||||
{:fx [[:effects.wallet-connect/disconnect
|
||||
{:web3-wallet web3-wallet
|
||||
:topic topic
|
||||
:on-fail on-fail
|
||||
:on-fail (fn []
|
||||
(when on-fail
|
||||
(on-fail)))
|
||||
:on-success (fn []
|
||||
(log/info "Successfully disconnected dApp session" topic)
|
||||
(rf/dispatch [:wallet-connect/delete-session topic])
|
||||
(rf/dispatch [:centralized-metrics/track
|
||||
:metric/dapp-session-disconnected])
|
||||
(when on-success
|
||||
(on-success)))}]]}
|
||||
{:fx [[:dispatch [:wallet-connect/no-internet-toast]]]}))))
|
||||
|
||||
@@ -31,9 +31,12 @@
|
||||
|
||||
:not-enough-assets
|
||||
:t/not-enough-assets-for-transaction)))
|
||||
:button-text (i18n/label :t/buy-eth)
|
||||
:on-button-press #(rf/dispatch [:show-bottom-sheet
|
||||
{:content buy-token/view}])}])
|
||||
:button-text (i18n/label :t/add-eth)
|
||||
:on-button-press (rn/use-callback (fn []
|
||||
(rf/dispatch [:centralized-metrics/track
|
||||
:metric/dapp-buy-eth])
|
||||
(rf/dispatch [:show-bottom-sheet
|
||||
{:content buy-token/view}])))}])
|
||||
[rn/view {:style style/content-container}
|
||||
(into [rn/view
|
||||
{:style style/data-items-container}]
|
||||
|
||||
@@ -73,3 +73,10 @@
|
||||
(-> db
|
||||
get-db-current-request-event
|
||||
get-request-params))
|
||||
|
||||
(defn get-total-connected-dapps
|
||||
[db]
|
||||
(-> db
|
||||
:wallet-connect/sessions
|
||||
count
|
||||
inc))
|
||||
|
||||
@@ -25,3 +25,17 @@
|
||||
(testing "returns nil if no redirect URL is found"
|
||||
(let [session {:peer {:metadata {}}}]
|
||||
(is (nil? (sut/get-dapp-redirect-url session))))))
|
||||
|
||||
(deftest get-total-connected-dapps-test
|
||||
(testing "returns the total number of connected dApps plus 1"
|
||||
(let [db {:wallet-connect/sessions [{:url "https://dapp1.com"}
|
||||
{:url "https://dapp2.com"}]}]
|
||||
(is (= 3 (sut/get-total-connected-dapps db)))))
|
||||
|
||||
(testing "returns 1 when there are no connected dApps"
|
||||
(let [db {:wallet-connect/sessions []}]
|
||||
(is (= 1 (sut/get-total-connected-dapps db)))))
|
||||
|
||||
(testing "handles nil sessions correctly"
|
||||
(let [db {:wallet-connect/sessions nil}]
|
||||
(is (= 1 (sut/get-total-connected-dapps db))))))
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
status-im.contexts.syncing.events
|
||||
status-im.contexts.wallet.add-account.add-address-to-watch.events
|
||||
status-im.contexts.wallet.add-account.create-account.events
|
||||
status-im.contexts.wallet.buy-crypto.events
|
||||
status-im.contexts.wallet.collectible.events
|
||||
status-im.contexts.wallet.common.wizard.events
|
||||
status-im.contexts.wallet.effects
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
::wallet.long-press-watch-only-asset (enabled-in-env? :FLAG_LONG_PRESS_WATCH_ONLY_ASSET_ENABLED)
|
||||
::wallet.saved-addresses (enabled-in-env? :WALLET_SAVED_ADDRESSES)
|
||||
::wallet.wallet-connect (enabled-in-env? :FLAG_WALLET_CONNECT_ENABLED)
|
||||
::wallet.custom-network-amounts (enabled-in-env? :FLAG_WALLET_CUSTOM_NETWORK_AMOUNTS_ENABLED)})
|
||||
::wallet.custom-network-amounts (enabled-in-env? :FLAG_WALLET_CUSTOM_NETWORK_AMOUNTS_ENABLED)
|
||||
::keycard.migrate-profile false})
|
||||
|
||||
(defonce ^:private feature-flags-config
|
||||
(reagent/atom initial-flags))
|
||||
|
||||
@@ -69,6 +69,12 @@
|
||||
transitions/new-to-status-modal-animations
|
||||
transitions/push-animations-for-transparent-background)}))
|
||||
|
||||
(def keycard-modal-screen-options
|
||||
(assoc transparent-modal-screen-options
|
||||
:layout nil
|
||||
:theme :dark
|
||||
:insets {:top? true :bottom? true}))
|
||||
|
||||
(def sheet-options
|
||||
{:layout {:componentBackgroundColor :transparent
|
||||
:orientation ["portrait"]
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
[status-im.contexts.communities.actions.share-community.view :as share-community]
|
||||
[status-im.contexts.communities.discover.view :as communities.discover]
|
||||
[status-im.contexts.communities.overview.view :as communities.overview]
|
||||
[status-im.contexts.keycard.authorise.view :as keycard.authorise]
|
||||
[status-im.contexts.keycard.check.view :as keycard.check]
|
||||
[status-im.contexts.keycard.empty.view :as keycard.empty]
|
||||
[status-im.contexts.keycard.error.view :as keycard.error]
|
||||
[status-im.contexts.keycard.not-keycard.view :as keycard.not-keycard]
|
||||
[status-im.contexts.onboarding.create-or-sync-profile.view :as create-or-sync-profile]
|
||||
[status-im.contexts.onboarding.create-password.view :as create-password]
|
||||
[status-im.contexts.onboarding.create-profile.view :as create-profile]
|
||||
@@ -42,6 +47,7 @@
|
||||
[status-im.contexts.preview.quo.component-preview.view :as component-preview]
|
||||
[status-im.contexts.preview.quo.main :as quo.preview]
|
||||
[status-im.contexts.preview.status-im.main :as status-im-preview]
|
||||
[status-im.contexts.profile.backup-recovery-phrase.view :as backup-recovery-phrase]
|
||||
[status-im.contexts.profile.contact.share.view :as share-contact]
|
||||
[status-im.contexts.profile.contact.view :as contact-profile]
|
||||
[status-im.contexts.profile.edit.accent-colour.view :as edit-accent-colour]
|
||||
@@ -58,6 +64,7 @@
|
||||
[status-im.contexts.profile.settings.screens.password.view :as settings-password]
|
||||
[status-im.contexts.profile.settings.screens.syncing.view :as settings.syncing]
|
||||
[status-im.contexts.profile.settings.view :as settings]
|
||||
[status-im.contexts.settings.keycard.view :as settings.keycard]
|
||||
[status-im.contexts.settings.language-and-currency.currency.view :as settings.currency-selection]
|
||||
[status-im.contexts.settings.language-and-currency.view :as settings.language-and-currency]
|
||||
[status-im.contexts.settings.privacy-and-security.share-usage.view :as settings.share-usage]
|
||||
@@ -101,8 +108,6 @@
|
||||
wallet-import-private-key]
|
||||
[status-im.contexts.wallet.add-account.create-account.key-pair-name.view :as
|
||||
wallet-key-pair-name]
|
||||
[status-im.contexts.wallet.add-account.create-account.new-keypair.backup-recovery-phrase.view :as
|
||||
wallet-backup-recovery-phrase]
|
||||
[status-im.contexts.wallet.add-account.create-account.new-keypair.confirm-backup.view :as
|
||||
wallet-confirm-backup]
|
||||
[status-im.contexts.wallet.add-account.create-account.select-keypair.view :as wallet-select-keypair]
|
||||
@@ -470,9 +475,9 @@
|
||||
:options {:insets {:top? true}}
|
||||
:component wallet-create-account/view}
|
||||
|
||||
{:name :screen/wallet.backup-recovery-phrase
|
||||
{:name :screen/backup-recovery-phrase
|
||||
:options {:insets {:top? true :bottom? true}}
|
||||
:component wallet-backup-recovery-phrase/view}
|
||||
:component backup-recovery-phrase/view}
|
||||
|
||||
{:name :screen/wallet.confirm-backup
|
||||
:options {:insets {:top? true :bottom? true}}
|
||||
@@ -482,7 +487,7 @@
|
||||
:options {:insets {:top? true}}
|
||||
:component wallet-key-pair-name/view}
|
||||
|
||||
{:name :screen/wallet.enter-seed-phrase
|
||||
{:name :screen/use-recovery-phrase
|
||||
:component enter-seed-phrase/view}
|
||||
|
||||
{:name :screen/wallet.share-address
|
||||
@@ -584,6 +589,10 @@
|
||||
:options options/transparent-modal-screen-options
|
||||
:component wallet-options/view}
|
||||
|
||||
{:name :screen/settings.keycard
|
||||
:options options/keycard-modal-screen-options
|
||||
:component settings.keycard/view}
|
||||
|
||||
{:name :screen/settings.rename-keypair
|
||||
:options options/transparent-screen-options
|
||||
:component keypair-rename/view}
|
||||
@@ -671,7 +680,28 @@
|
||||
:popGesture false
|
||||
:hardwareBackButton {:dismissModalOnPress false
|
||||
:popStackOnPress false})
|
||||
:component change-password-loading/view}]
|
||||
:component change-password-loading/view}
|
||||
|
||||
;; Keycard
|
||||
{:name :screen/keycard.check
|
||||
:options options/keycard-modal-screen-options
|
||||
:component keycard.check/view}
|
||||
|
||||
{:name :screen/keycard.empty
|
||||
:options options/keycard-modal-screen-options
|
||||
:component keycard.empty/view}
|
||||
|
||||
{:name :screen/keycard.error
|
||||
:options options/keycard-modal-screen-options
|
||||
:component keycard.error/view}
|
||||
|
||||
{:name :screen/keycard.not-keycard
|
||||
:options options/keycard-modal-screen-options
|
||||
:component keycard.not-keycard/view}
|
||||
|
||||
{:name :screen/keycard.authorise
|
||||
:options options/keycard-modal-screen-options
|
||||
:component keycard.authorise/view}]
|
||||
|
||||
[{:name :shell
|
||||
:options {:theme :dark}}]
|
||||
|
||||
@@ -11,7 +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.contexts.keycard.nfc-sheet.view :as keycard.sheet]
|
||||
[status-im.navigation.screens :as screens]
|
||||
[status-im.setup.hot-reload :as reloader]
|
||||
[utils.re-frame :as rf]))
|
||||
@@ -47,7 +47,7 @@
|
||||
(merge
|
||||
{:flex 1
|
||||
:margin-top alert-banners-top-margin
|
||||
:background-color (or background-color (colors/theme-colors colors/white colors/neutral-100 theme))}
|
||||
:background-color (or background-color (colors/theme-colors colors/white colors/neutral-95 theme))}
|
||||
(when bottom?
|
||||
{:padding-bottom (safe-area/get-bottom)})
|
||||
(when top?
|
||||
|
||||
@@ -36,3 +36,8 @@
|
||||
(fn [keycard]
|
||||
(:connection-sheet-opts keycard)))
|
||||
|
||||
(rf/reg-sub
|
||||
:keycard/application-info-error
|
||||
:<- [:keycard]
|
||||
(fn [keycard]
|
||||
(:application-info-error keycard)))
|
||||
|
||||
@@ -326,6 +326,12 @@
|
||||
(fn [profile]
|
||||
(profile.utils/photo profile)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:profile/name
|
||||
:<- [:profile/profile]
|
||||
(fn [profile]
|
||||
(profile.utils/displayed-name profile)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:profile/login-profile
|
||||
:<- [:profile/login]
|
||||
@@ -339,6 +345,12 @@
|
||||
(fn [{:keys [processing]}]
|
||||
processing))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:profile/recovery-phrase-backed-up?
|
||||
:<- [:profile/profile]
|
||||
(fn [profile]
|
||||
(not (boolean (seq (:mnemonic profile))))))
|
||||
|
||||
;; LINK PREVIEW
|
||||
;; ========================================================================================================
|
||||
|
||||
|
||||
@@ -5,3 +5,28 @@
|
||||
:wallet/crypto-on-ramps
|
||||
:<- [:wallet]
|
||||
:-> :crypto-on-ramps)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/wallet-buy-crypto
|
||||
:<- [:wallet/ui]
|
||||
:-> :buy-crypto)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/wallet-buy-crypto-account
|
||||
:<- [:wallet/wallet-buy-crypto]
|
||||
:-> :account)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/wallet-buy-crypto-provider
|
||||
:<- [:wallet/wallet-buy-crypto]
|
||||
:-> :provider)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/wallet-buy-crypto-recurrent?
|
||||
:<- [:wallet/wallet-buy-crypto]
|
||||
:-> :recurrent?)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/wallet-buy-crypto-network
|
||||
:<- [:wallet/wallet-buy-crypto]
|
||||
:-> :network)
|
||||
|
||||
@@ -399,6 +399,22 @@
|
||||
(assoc :balance balance
|
||||
:formatted-balance formatted-balance)))))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/current-viewing-account-or-default
|
||||
:<- [:wallet/accounts]
|
||||
:<- [:wallet/current-viewing-account-address]
|
||||
:<- [:wallet/balances-in-selected-networks]
|
||||
:<- [:profile/currency-symbol]
|
||||
(fn [[accounts current-viewing-account-address balances currency-symbol]]
|
||||
(let [account (or (utils/get-account-by-address accounts current-viewing-account-address)
|
||||
(utils/get-default-account accounts))
|
||||
address (:address account)
|
||||
balance (get balances address)
|
||||
formatted-balance (utils/prettify-balance currency-symbol balance)]
|
||||
(assoc account
|
||||
:balance balance
|
||||
:formatted-balance formatted-balance))))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/current-viewing-account-color
|
||||
:<- [:wallet/current-viewing-account]
|
||||
@@ -420,7 +436,7 @@
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/current-viewing-account-tokens-filtered
|
||||
:<- [:wallet/current-viewing-account]
|
||||
:<- [:wallet/current-viewing-account-or-default]
|
||||
:<- [:wallet/network-details]
|
||||
:<- [:wallet/wallet-send]
|
||||
(fn [[account networks send-data] [_ {:keys [query chain-ids hide-token-fn]}]]
|
||||
@@ -454,14 +470,19 @@
|
||||
(rf/reg-sub
|
||||
:wallet/tokens-filtered
|
||||
:<- [:wallet/tokens]
|
||||
(fn [{:keys [by-symbol market-values-per-token details-per-token]}
|
||||
:<- [:profile/currency]
|
||||
(fn [[{:keys [by-symbol market-values-per-token details-per-token prices-per-token]} currency]
|
||||
[_ {:keys [query chain-ids hide-token-fn]}]]
|
||||
(let [tokens (->> by-symbol
|
||||
(map (fn [token]
|
||||
(-> token
|
||||
(assoc :market-values
|
||||
(get market-values-per-token (:symbol token)))
|
||||
(assoc :details (get details-per-token (:symbol token))))))
|
||||
(let [token-symbol (keyword (:symbol token))
|
||||
price (get-in prices-per-token [token-symbol currency])]
|
||||
(-> token
|
||||
(assoc :market-values
|
||||
(get market-values-per-token token-symbol))
|
||||
(assoc :details (get details-per-token token-symbol))
|
||||
(assoc-in [:market-values-per-currency currency :price]
|
||||
price)))))
|
||||
(filter (fn [{:keys [chain-id]}]
|
||||
(some #{chain-id} chain-ids)))
|
||||
(remove #(when hide-token-fn
|
||||
@@ -476,7 +497,7 @@
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/token-by-symbol
|
||||
:<- [:wallet/current-viewing-account]
|
||||
:<- [:wallet/current-viewing-account-or-default]
|
||||
:<- [:wallet/network-details]
|
||||
(fn [[{:keys [tokens]} networks] [_ token-symbol chain-ids]]
|
||||
(->> (utils/tokens-with-balance tokens networks chain-ids)
|
||||
|
||||
@@ -239,9 +239,12 @@
|
||||
|
||||
(defn crypto->fiat
|
||||
[crypto fiat-price]
|
||||
(when-let [^js bn (bignumber crypto)]
|
||||
(-> (.times bn ^js (bignumber fiat-price))
|
||||
(with-precision 2))))
|
||||
(let [^js crypto-bn (bignumber crypto)
|
||||
^js fiat-price-bn (bignumber fiat-price)]
|
||||
(when (and crypto-bn fiat-price-bn)
|
||||
(-> crypto-bn
|
||||
(.times fiat-price-bn)
|
||||
(with-precision 2)))))
|
||||
|
||||
(defn sufficient-funds?
|
||||
[^js amount ^js balance]
|
||||
|
||||
+32
-13
@@ -2,7 +2,8 @@
|
||||
(:require [clojure.string :as string]
|
||||
[native-module.core :as native-module]
|
||||
[utils.hex :as utils.hex]
|
||||
[utils.money :as utils.money]))
|
||||
[utils.money :as utils.money]
|
||||
[utils.money :as money]))
|
||||
|
||||
(defn naive-round
|
||||
"Quickly and naively round number `n` up to `decimal-places`.
|
||||
@@ -18,17 +19,6 @@
|
||||
(/ (Math/round (* n scale))
|
||||
scale)))
|
||||
|
||||
(defn convert-to-whole-number
|
||||
"Converts a fractional `amount` to its corresponding whole number representation
|
||||
by dividing it by 10 raised to the power of `decimals`. This is often used in financial
|
||||
calculations where amounts are stored in their smallest units (e.g., cents) and need
|
||||
to be converted to their whole number equivalents (e.g., dollars).
|
||||
|
||||
Example usage:
|
||||
(convert-to-whole-number 12345 2) ; => 123.45"
|
||||
[amount decimals]
|
||||
(/ amount (Math/pow 10 decimals)))
|
||||
|
||||
(defn parse-int
|
||||
"Parses `n` as an integer. Defaults to zero or `default` instead of NaN."
|
||||
([n]
|
||||
@@ -65,15 +55,44 @@
|
||||
"")
|
||||
""))))
|
||||
|
||||
(defn convert-to-whole-number
|
||||
"Converts a fractional `amount` to its corresponding whole number representation
|
||||
by dividing it by 10 raised to the power of `decimals`. This is often used in financial
|
||||
calculations where amounts are stored in their smallest units (e.g., cents) and need
|
||||
to be converted to their whole number equivalents (e.g., dollars).
|
||||
|
||||
Example usage:
|
||||
(convert-to-whole-number 12345 2) ; => 123.45"
|
||||
[amount decimals]
|
||||
(-> amount
|
||||
(/ (Math/pow 10 decimals))
|
||||
(.toFixed decimals)
|
||||
remove-trailing-zeroes))
|
||||
|
||||
(defn hex->whole
|
||||
[num decimals]
|
||||
(-> num
|
||||
utils.hex/normalize-hex
|
||||
native-module/hex-to-number
|
||||
(convert-to-whole-number decimals)))
|
||||
(convert-to-whole-number decimals)
|
||||
money/bignumber))
|
||||
|
||||
(defn to-fixed
|
||||
[num decimals]
|
||||
(-> num
|
||||
(utils.money/to-fixed decimals)
|
||||
remove-trailing-zeroes))
|
||||
|
||||
(defn small-number-threshold
|
||||
"Receives a decimal count and returns a string like '<0.001' if the decimal count is 3,
|
||||
'<0.000001' if the decimal count is 6, etc."
|
||||
[decimal-count]
|
||||
(if (> decimal-count 0)
|
||||
(str "<0." (apply str (repeat (dec decimal-count) "0")) "1")
|
||||
"0"))
|
||||
|
||||
(defn valid-decimal-count?
|
||||
"Returns false if the number has more decimals than the decimal count, otherwise true."
|
||||
[num decimal-count]
|
||||
(let [decimal-part (second (string/split (str num) #"\."))]
|
||||
(or (nil? decimal-part) (<= (count decimal-part) decimal-count))))
|
||||
|
||||
+44
-11
@@ -5,23 +5,23 @@
|
||||
|
||||
(deftest convert-to-whole-number-test
|
||||
(testing "correctly converts fractional amounts to whole numbers"
|
||||
(is (= 123.45 (utils.number/convert-to-whole-number 12345 2)))
|
||||
(is (= 1.2345 (utils.number/convert-to-whole-number 12345 4)))
|
||||
(is (= 12345.0 (utils.number/convert-to-whole-number 1234500 2)))
|
||||
(is (= 0.123 (utils.number/convert-to-whole-number 123 3)))
|
||||
(is (= 1000.0 (utils.number/convert-to-whole-number 1000000 3))))
|
||||
(is (= "123.45" (utils.number/convert-to-whole-number 12345 2)))
|
||||
(is (= "1.2345" (utils.number/convert-to-whole-number 12345 4)))
|
||||
(is (= "12345" (utils.number/convert-to-whole-number 1234500 2)))
|
||||
(is (= "0.123" (utils.number/convert-to-whole-number 123 3)))
|
||||
(is (= "1000" (utils.number/convert-to-whole-number 1000000 3))))
|
||||
|
||||
(testing "handles zero decimals"
|
||||
(is (= 12345 (utils.number/convert-to-whole-number 12345 0))))
|
||||
(is (= "12345" (utils.number/convert-to-whole-number 12345 0))))
|
||||
|
||||
(testing "handles negative amounts"
|
||||
(is (= -123.45 (utils.number/convert-to-whole-number -12345 2)))
|
||||
(is (= -1.2345 (utils.number/convert-to-whole-number -12345 4)))
|
||||
(is (= -0.123 (utils.number/convert-to-whole-number -123 3))))
|
||||
(is (= "-123.45" (utils.number/convert-to-whole-number -12345 2)))
|
||||
(is (= "-1.2345" (utils.number/convert-to-whole-number -12345 4)))
|
||||
(is (= "-0.123" (utils.number/convert-to-whole-number -123 3))))
|
||||
|
||||
(testing "handles zero amount"
|
||||
(is (= 0 (utils.number/convert-to-whole-number 0 2)))
|
||||
(is (= 0 (utils.number/convert-to-whole-number 0 0)))))
|
||||
(is (= "0" (utils.number/convert-to-whole-number 0 2)))
|
||||
(is (= "0" (utils.number/convert-to-whole-number 0 0)))))
|
||||
|
||||
(deftest parse-int-test
|
||||
(testing "defaults to zero"
|
||||
@@ -50,3 +50,36 @@
|
||||
(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)))))
|
||||
|
||||
(deftest small-number-threshold-test
|
||||
(testing "correctly generates threshold strings based on decimal count"
|
||||
(is (= "<0.1" (utils.number/small-number-threshold 1)))
|
||||
(is (= "<0.01" (utils.number/small-number-threshold 2)))
|
||||
(is (= "<0.001" (utils.number/small-number-threshold 3)))
|
||||
(is (= "<0.000001" (utils.number/small-number-threshold 6)))
|
||||
(is (= "<0.0000000001" (utils.number/small-number-threshold 10)))
|
||||
(is (= "<0.000000000000000001" (utils.number/small-number-threshold 18)))
|
||||
(is (= "<0.000000000000000000001" (utils.number/small-number-threshold 21))))
|
||||
|
||||
(testing "handles edge cases for decimal count"
|
||||
(is (= "0" (utils.number/small-number-threshold 0)))
|
||||
(is (= "0" (utils.number/small-number-threshold -1)))))
|
||||
|
||||
(deftest valid-decimal-count?-test
|
||||
(testing "valid decimal count check for numbers with varying decimals"
|
||||
(is (true? (utils.number/valid-decimal-count? 123 2)))
|
||||
(is (true? (utils.number/valid-decimal-count? 123 0)))
|
||||
|
||||
(is (true? (utils.number/valid-decimal-count? 123.45 2)))
|
||||
(is (true? (utils.number/valid-decimal-count? 123.4 2)))
|
||||
(is (true? (utils.number/valid-decimal-count? 123.456 3)))
|
||||
|
||||
(is (false? (utils.number/valid-decimal-count? 123.456 2)))
|
||||
(is (false? (utils.number/valid-decimal-count? 123.456789 4)))
|
||||
|
||||
(is (true? (utils.number/valid-decimal-count? 123.0 1)))
|
||||
(is (true? (utils.number/valid-decimal-count? -123.45 2)))
|
||||
(is (false? (utils.number/valid-decimal-count? -123.4567 3)))
|
||||
|
||||
(is (true? (utils.number/valid-decimal-count? 1234567890.12 2)))
|
||||
(is (false? (utils.number/valid-decimal-count? 1234567890.12345 3)))))
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"_comment": "Instead use: scripts/update-status-go.sh <rev>",
|
||||
"owner": "status-im",
|
||||
"repo": "status-go",
|
||||
"version": "develop",
|
||||
"commit-sha1": "ba37c32c07d1b05a94d1c7f7cb1b38fb82730f7b",
|
||||
"src-sha256": "1f1bbw70iircicda9w0hch56hxr7p1vvzkrk58v8p5sq23xzdf6y"
|
||||
"version": "5a19cc2a6ebc23d5dd8fcac2530a2897b0443e42",
|
||||
"commit-sha1": "5a19cc2a6ebc23d5dd8fcac2530a2897b0443e42",
|
||||
"src-sha256": "0x910lkg3f78fd9psm8mxw6hl9dfjy5nmkm0nz62h1z3zhc6ijx6"
|
||||
}
|
||||
|
||||
@@ -125,6 +125,7 @@ class TestrailReport(BaseTestReport):
|
||||
test_cases['nightly']['activity_centre_contact_request'] = 50984
|
||||
test_cases['nightly']['activity_centre_other'] = 51005
|
||||
test_cases['nightly']['wallet'] = 59443
|
||||
test_cases['nightly']['fallback'] = 63472
|
||||
|
||||
## Upgrade e2e
|
||||
# test_cases['upgrade']['general'] = 881
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
import pytest
|
||||
from selenium.common import TimeoutException
|
||||
|
||||
from base_test_case import MultipleSharedDeviceTestCase, create_shared_drivers
|
||||
from tests import marks, run_in_parallel, transl
|
||||
from users import transaction_senders
|
||||
from views.sign_in_view import SignInView
|
||||
|
||||
|
||||
@pytest.mark.xdist_group(name="new_six_2")
|
||||
@marks.nightly
|
||||
class TestFallbackMultipleDevice(MultipleSharedDeviceTestCase):
|
||||
|
||||
def prepare_devices(self):
|
||||
self.drivers, self.loop = create_shared_drivers(3)
|
||||
self.sign_in_1, self.sign_in_2, self.sign_in_3 = SignInView(self.drivers[0]), SignInView(
|
||||
self.drivers[1]), SignInView(self.drivers[2])
|
||||
self.home_1, self.home_2, self.home_3 = self.sign_in_1.get_home_view(), self.sign_in_2.get_home_view(), \
|
||||
self.sign_in_3.get_home_view()
|
||||
self.sign_in_1.just_fyi("Device 1: create a new user")
|
||||
self.sign_in_3.just_fyi("Device 3: create a new user")
|
||||
self.user_name_1, self.user_name_3 = 'first user', 'third user'
|
||||
self.loop.run_until_complete(
|
||||
run_in_parallel(((self.sign_in_1.create_user, {'username': self.user_name_1}),
|
||||
(self.sign_in_3.create_user, {'username': self.user_name_3}))))
|
||||
self.profile_1 = self.home_1.profile_button.click()
|
||||
self.profile_2 = self.home_2.get_profile_view()
|
||||
self.sign_in_3.just_fyi("Device 3: get public key")
|
||||
self.recovery_phrase, self.public_key_3 = self.loop.run_until_complete(
|
||||
run_in_parallel(((self.profile_1.backup_recovery_phrase, {}),
|
||||
(self.home_3.get_public_key, {}))))
|
||||
self.home_2.driver.get_clipboard_text() # just pinging 2nd device to save the connection
|
||||
self.profile_1.click_system_back_button()
|
||||
self.home_1.chats_tab.click()
|
||||
self.home_1.just_fyi("Device 1: add the 3rd user as a contact")
|
||||
self.home_1.add_contact(self.public_key_3)
|
||||
self.home_3.just_fyi("Device 3: accepting contact request from the 1st user")
|
||||
self.home_3.handle_contact_request(self.user_name_1)
|
||||
self.profile_1.just_fyi("Device 1: get sync code")
|
||||
self.home_1.profile_button.click()
|
||||
self.sync_code = self.profile_1.get_sync_code()
|
||||
|
||||
@marks.testrail_id(740220)
|
||||
def test_fallback_sync_with_error(self):
|
||||
self.sign_in_2.just_fyi("Device 2: try syncing profile")
|
||||
self.sign_in_2.sync_profile(sync_code=self.sync_code)
|
||||
self.sign_in_2.progress_screen_title.wait_for_element()
|
||||
assert self.sign_in_2.progress_screen_title.text == "Oops, something’s wrong"
|
||||
self.home_3.chats_tab.is_element_displayed() # just pinging 3rd device to save the connection
|
||||
|
||||
@marks.testrail_id(740221)
|
||||
def test_fallback_with_correct_seed_phrase(self):
|
||||
self.sign_in_2.just_fyi("Device 2: recover a profile with backed up seed phrase")
|
||||
self.sign_in_2.try_seed_phrase_button.click()
|
||||
self.sign_in_2.recover_access(passphrase=self.recovery_phrase, after_sync_code=True)
|
||||
self.profile_1.click_system_back_button()
|
||||
|
||||
self.home_2.just_fyi("Getting device 2 name")
|
||||
self.home_2.profile_button.click()
|
||||
self.profile_2.syncing_button.scroll_and_click()
|
||||
self.profile_2.paired_devices_button.click()
|
||||
device_2_name = self.profile_2.get_current_device_name()
|
||||
self.profile_2.click_system_back_button(times=3)
|
||||
|
||||
self.home_3.chats_tab.is_element_displayed() # just pinging 3rd device to save the connection
|
||||
|
||||
self.profile_1.just_fyi("Check device 2 is shown in not paired devices list in profile 1")
|
||||
device_1_name = self.profile_1.get_current_device_name()
|
||||
device_element = self.profile_1.get_paired_device_by_name(device_2_name)
|
||||
if device_element.is_element_displayed():
|
||||
if not device_element.get_pair_button.is_element_displayed():
|
||||
self.errors.append(
|
||||
"Pair button is absent for the device 2 inside Paired devices list of profile 1 before pairing")
|
||||
else:
|
||||
self.errors.append("Device 2 is not shown in Paired devices list for device 1 before pairing")
|
||||
self.profile_1.click_system_back_button(times=3)
|
||||
|
||||
for home in self.home_1, self.home_2:
|
||||
home.notifications_unread_badge.wait_for_visibility_of_element(30)
|
||||
home.open_activity_center_button.click_until_presence_of_element(home.close_activity_centre)
|
||||
|
||||
self.home_1.just_fyi("Checking pairing request on device 1")
|
||||
a_c_element = self.home_1.get_activity_center_element_by_text(transl['review-pairing-request'])
|
||||
if a_c_element.title.text != transl['new-device-detected']:
|
||||
self.errors.append(
|
||||
"Notification with title '%s' is not shown in the activity center for the device 1" % transl[
|
||||
'new-device-detected'])
|
||||
a_c_element.review_pairing_request_button.click()
|
||||
device_id_1 = self.home_1.get_new_device_installation_id()
|
||||
|
||||
self.home_3.chats_tab.is_element_displayed() # just pinging 3rd device to save the connection
|
||||
|
||||
self.home_2.just_fyi("Checking sync profile on device 2")
|
||||
a_c_element = self.home_2.get_activity_center_element_by_text(transl['more-details'])
|
||||
if a_c_element.title.text != transl['sync-your-profile']:
|
||||
self.errors.append(
|
||||
"Notification with title '%s' is not shown in the activity center for the device 2" % transl[
|
||||
'sync-your-profile'])
|
||||
a_c_element.more_details_button.click()
|
||||
device_id_2 = self.home_2.get_new_device_installation_id()
|
||||
|
||||
if device_id_1 != device_id_2:
|
||||
self.errors.append("Device ids don't match on the activity center notifications")
|
||||
|
||||
self.home_1.just_fyi("Confirm pairing request on device 1")
|
||||
self.home_1.element_by_translation_id('pair-and-sync').click()
|
||||
self.home_2.element_by_translation_id('close').click()
|
||||
|
||||
self.home_1.close_activity_centre.click()
|
||||
self.home_2.close_activity_centre.click()
|
||||
|
||||
self.home_3.chats_tab.is_element_displayed() # just pinging 3rd device to save the connection
|
||||
|
||||
self.home_1.just_fyi("Device 1: Check that the device 2 is shown in paired devices list")
|
||||
self.home_1.profile_button.click()
|
||||
self.profile_1.syncing_button.scroll_and_click()
|
||||
self.profile_1.paired_devices_button.click()
|
||||
device_element = self.profile_1.get_paired_device_by_name(device_2_name)
|
||||
if device_element.is_element_displayed():
|
||||
if not device_element.get_unpair_button.is_element_displayed():
|
||||
self.errors.append(
|
||||
"Unpair button is absent for the device 2 inside Paired devices list of profile 1 after pairing")
|
||||
else:
|
||||
self.errors.append("Device 2 is not shown in Paired devices list for device 1 after pairing")
|
||||
|
||||
self.home_2.just_fyi("Device 2: Check that the device 1 is shown paired devices list")
|
||||
self.home_2.profile_button.click()
|
||||
self.profile_2.syncing_button.scroll_and_click()
|
||||
self.profile_2.paired_devices_button.click()
|
||||
device_element = self.profile_2.get_paired_device_by_name(device_1_name)
|
||||
if device_element.is_element_displayed():
|
||||
if not device_element.get_unpair_button.is_element_displayed():
|
||||
self.errors.append(
|
||||
"Unpair button is absent for the device 1 inside Paired devices list of profile 2 after pairing")
|
||||
else:
|
||||
self.errors.append("Device 1 is not shown in Paired devices list for device 2 after pairing")
|
||||
|
||||
self.home_3.just_fyi("Device 3: send a message to user 1")
|
||||
self.home_3.chats_tab.click()
|
||||
chat_3 = self.home_3.get_chat(self.user_name_1).click()
|
||||
message = "Test message"
|
||||
chat_3.send_message(message)
|
||||
|
||||
def _check_message(home_view, index):
|
||||
home_view.just_fyi("Device %s: check the message from the user 3 is received" % index)
|
||||
home_view.click_system_back_button(times=3)
|
||||
home_view.chats_tab.click()
|
||||
try:
|
||||
chat_element = home_view.get_chat(self.user_name_3)
|
||||
chat_element.wait_for_visibility_of_element(60)
|
||||
chat_view = chat_element.click()
|
||||
chat_view.chat_element_by_text(message).wait_for_visibility_of_element(60)
|
||||
except TimeoutException:
|
||||
self.errors.append("Message is not received by the user %s" % index)
|
||||
|
||||
self.loop.run_until_complete(
|
||||
run_in_parallel(((_check_message, {'home_view': self.home_1, 'index': 1}),
|
||||
(_check_message, {'home_view': self.home_2, 'index': 2}))))
|
||||
|
||||
self.errors.verify_no_errors()
|
||||
|
||||
@marks.testrail_id(740222)
|
||||
def test_fallback_validate_seed_phrase(self):
|
||||
self.sign_in_2.reopen_app(sign_in=False)
|
||||
|
||||
self.sign_in_2.just_fyi("Device 2: try syncing profile")
|
||||
self.sign_in_2.sync_profile(sync_code=self.sync_code, first_user=False)
|
||||
self.sign_in_2.progress_screen_title.wait_for_element()
|
||||
assert self.sign_in_2.progress_screen_title.text == "Oops, something’s wrong"
|
||||
|
||||
self.sign_in_2.just_fyi("Device 2: try invalid passphrase")
|
||||
self.sign_in_2.try_seed_phrase_button.click()
|
||||
self.sign_in_2.passphrase_edit_box.send_keys(' '.join(['asset'] * 12))
|
||||
self.sign_in_2.continue_button.click()
|
||||
if not self.sign_in_2.element_by_translation_id('seed-phrase-invalid').is_element_displayed():
|
||||
self.errors.append("Error message is not displayed for invalid recovery phrase")
|
||||
|
||||
self.sign_in_2.just_fyi("Device 2: try creating an account with another valid passphrase")
|
||||
self.sign_in_2.passphrase_edit_box.clear()
|
||||
self.sign_in_2.passphrase_edit_box.send_keys(transaction_senders['A']['passphrase'])
|
||||
self.sign_in_2.continue_button.click()
|
||||
if not self.sign_in_2.profile_title_input.is_element_displayed():
|
||||
self.errors.append("Can't recover an access with a valid passphrase")
|
||||
self.sign_in_2.click_system_back_button()
|
||||
|
||||
self.sign_in_2.just_fyi("Device 2: try recovering an account which is already synced")
|
||||
self.sign_in_2.passphrase_edit_box.clear()
|
||||
self.sign_in_2.passphrase_edit_box.send_keys(self.recovery_phrase)
|
||||
self.sign_in_2.continue_button.click()
|
||||
try:
|
||||
self.sign_in_2.native_alert_title.wait_for_element()
|
||||
shown_text = self.sign_in_2.native_alert_title.text
|
||||
if shown_text != "Keys for this account already exist":
|
||||
self.errors.append("Incorrect error message '%s' is shown for already synced account" % shown_text)
|
||||
self.sign_in_2.cancel_button.click()
|
||||
except TimeoutException:
|
||||
self.errors.append("Error is not shown for already synced account")
|
||||
|
||||
self.errors.verify_no_errors()
|
||||
@@ -4,7 +4,6 @@ import time
|
||||
import pytest
|
||||
from _pytest.outcomes import Failed
|
||||
from selenium.common import TimeoutException, NoSuchElementException
|
||||
|
||||
from base_test_case import MultipleSharedDeviceTestCase, create_shared_drivers
|
||||
from support.api.network_api import NetworkApi
|
||||
from tests import marks, run_in_parallel
|
||||
@@ -192,6 +191,7 @@ class TestWalletMultipleDevice(MultipleSharedDeviceTestCase):
|
||||
|
||||
@pytest.mark.xdist_group(name="new_one_2")
|
||||
@marks.nightly
|
||||
@marks.secured
|
||||
@marks.smoke
|
||||
class TestWalletOneDevice(MultipleSharedDeviceTestCase):
|
||||
|
||||
@@ -199,10 +199,57 @@ class TestWalletOneDevice(MultipleSharedDeviceTestCase):
|
||||
self.network_api = NetworkApi()
|
||||
self.drivers, self.loop = create_shared_drivers(1)
|
||||
self.sign_in_view = SignInView(self.drivers[0])
|
||||
self.sign_in_view.create_user()
|
||||
self.sender, self.receiver = transaction_senders['ETH_1'], transaction_senders['ETH_2']
|
||||
self.total_balance = {'Ether': 0.0052, 'USDCoin': 5.0, 'Status': 10.0, 'Uniswap': 0.627, 'Dai Stablecoin': 0.0}
|
||||
self.mainnet_balance = {'Ether': 0.005, 'USDCoin': 0.0, 'Status': 10.0, 'Uniswap': 0.127, 'Dai Stablecoin': 0.0}
|
||||
self.optimizm_balance = {'Ether': 0.0001, 'USDCoin': 5.0, 'Status': 0, 'Uniswap': 0, 'Dai Stablecoin': 0.0}
|
||||
self.arb_balance = {'Ether': 0.0001, 'USDCoin': 0.0, 'Status': 0.0, 'Uniswap': 0.5, 'Dai Stablecoin': 0.0}
|
||||
self.sender['wallet_address'] = '0x' + self.sender['address']
|
||||
self.receiver['wallet_address'] = '0x' + self.receiver['address']
|
||||
self.sender_username, self.receiver_username = 'sender', 'receiver'
|
||||
self.sign_in_view.recover_access(passphrase=self.sender['passphrase'], username=self.sender_username)
|
||||
|
||||
self.home_view = self.sign_in_view.get_home_view()
|
||||
self.wallet_view = self.home_view.wallet_tab.click()
|
||||
|
||||
@marks.testrail_id(740490)
|
||||
def test_wallet_balance_mainnet(self):
|
||||
self.profile_view = self.home_view.profile_button.click()
|
||||
self.profile_view.switch_network()
|
||||
self.sign_in_view.sign_in()
|
||||
self.sign_in_view.wallet_tab.click()
|
||||
|
||||
self.wallet_view.just_fyi("Checking total balance")
|
||||
real_balance = {}
|
||||
for asset in self.total_balance:
|
||||
real_balance[asset] = self.wallet_view.get_asset(asset).get_amount()
|
||||
|
||||
for asset in self.total_balance:
|
||||
if real_balance[asset] != self.total_balance[asset]:
|
||||
self.errors.append("For the %s the wrong value %s is shown, expected %s in total" %
|
||||
(asset, real_balance[asset], self.total_balance[asset]))
|
||||
expected_balances = {
|
||||
'Mainnet': self.mainnet_balance,
|
||||
'Arbitrum': self.arb_balance,
|
||||
'Optimism': self.optimizm_balance
|
||||
}
|
||||
|
||||
for network in expected_balances:
|
||||
self.wallet_view.just_fyi("Checking total balance on %s network" % network)
|
||||
self.wallet_view.select_network(network)
|
||||
real_balance = {}
|
||||
for asset in expected_balances[network]:
|
||||
real_balance[asset] = self.wallet_view.get_asset(asset).get_amount()
|
||||
self.wallet_view.just_fyi("real on %s %s" % (network, str(real_balance)))
|
||||
self.wallet_view.just_fyi("real on %s %s" % (network, str(expected_balances[network])))
|
||||
for asset in expected_balances[network]:
|
||||
if real_balance[asset] != expected_balances[network][asset]:
|
||||
self.errors.append("For the %s the wrong value %s is shown, expected %s on %s" %
|
||||
(asset, real_balance[asset], self.total_balance[asset], network))
|
||||
self.wallet_view.select_network(network)
|
||||
|
||||
self.errors.verify_no_errors()
|
||||
|
||||
@marks.testrail_id(727231)
|
||||
def test_wallet_add_remove_regular_account(self):
|
||||
self.wallet_view.just_fyi("Adding new regular account")
|
||||
|
||||
@@ -298,14 +298,13 @@ class BaseView(object):
|
||||
self.deny_button = Button(self.driver, translation_id="deny", uppercase=True)
|
||||
self.continue_button = Button(self.driver, translation_id="continue", uppercase=True)
|
||||
self.ok_button = Button(self.driver, xpath="//*[@text='OK' or @text='Ok']")
|
||||
self.next_button = Button(self.driver, translation_id="next")
|
||||
self.add_button = Button(self.driver, translation_id="add")
|
||||
self.save_button = Button(self.driver, translation_id="save")
|
||||
self.done_button = Button(self.driver, translation_id="done")
|
||||
self.done_button = Button(self.driver, accessibility_id="Done")
|
||||
self.delete_button = Button(self.driver, translation_id="delete", uppercase=True)
|
||||
self.ok_continue_button = Button(self.driver, xpath="//*[@text='OK, CONTINUE' or @text='Okay, continue']")
|
||||
self.ok_continue_button = Button(self.driver, accessibility_id="Okay, continue")
|
||||
self.discard_button = Button(self.driver, xpath="//*[@text='DISCARD']")
|
||||
self.confirm_button = Button(self.driver, translation_id='confirm', uppercase=True)
|
||||
self.confirm_button = Button(self.driver, accessibility_id='Confirm')
|
||||
|
||||
self.cross_icon = Button(self.driver, xpath="(//android.widget.ImageView[@content-desc='icon'])[1]")
|
||||
self.close_sticker_view_icon = Button(self.driver, xpath="//androidx.appcompat.widget.LinearLayoutCompat")
|
||||
@@ -314,7 +313,7 @@ class BaseView(object):
|
||||
self.navigate_up_button = Button(self.driver, accessibility_id="Navigate Up")
|
||||
self.show_roots_button = Button(self.driver, accessibility_id="Show roots")
|
||||
self.get_started_button = Button(self.driver, translation_id="get-started")
|
||||
self.ok_got_it_button = Button(self.driver, translation_id="ok-got-it")
|
||||
self.ok_got_it_button = Button(self.driver, accessibility_id="Okay, got it")
|
||||
self.cross_icon_inside_welcome_screen_button = Button(self.driver, accessibility_id='hide-home-button')
|
||||
self.status_in_background_button = Button(self.driver, xpath="//*[contains(@content-desc,'Status')]")
|
||||
self.cancel_button = Button(self.driver, translation_id="cancel", uppercase=True)
|
||||
@@ -324,6 +323,7 @@ class BaseView(object):
|
||||
self.sign_in_phrase = SignInPhraseText(self.driver)
|
||||
self.toast_content_element = BaseElement(self.driver, accessibility_id="toast-content")
|
||||
self.next_button = Button(self.driver, accessibility_id="next-button")
|
||||
self.native_alert_title = Text(self.driver, xpath="//*[@resource-id='android:id/alertTitle']")
|
||||
|
||||
# share contact screen
|
||||
self.show_qr_code_button = Button(self.driver, accessibility_id="show-qr-button")
|
||||
@@ -332,7 +332,8 @@ class BaseView(object):
|
||||
self.sharing_text_native = Text(self.driver, xpath="//*[@resource-id='android:id/content_preview_text']")
|
||||
|
||||
# checkboxes and toggles
|
||||
self.checkbox_button = CheckBox(self.driver, accessibility_id="checkbox-off")
|
||||
self.checkbox_button = CheckBox(
|
||||
self.driver, xpath="//*[@content-desc='checkbox-off'][@resource-id='checkbox-component']")
|
||||
self.slide_button_track = SlideButton(self.driver)
|
||||
|
||||
# external browser
|
||||
@@ -770,7 +771,7 @@ class BaseView(object):
|
||||
self.close_native_device_dialog("MmsService")
|
||||
|
||||
def set_device_to_offline(self):
|
||||
# setting network connection to data only and switching off wifi
|
||||
# setting network connection to data only and switching off Wi-Fi
|
||||
self.driver.set_network_connection(2)
|
||||
self.driver.toggle_wifi()
|
||||
|
||||
|
||||
@@ -152,6 +152,14 @@ class ActivityCenterElement(SilentButton):
|
||||
def pending_status_tag(self):
|
||||
return Text(self.driver, xpath=self.locator + '//*[@content-desc="status-tag-pending"]')
|
||||
|
||||
@property
|
||||
def review_pairing_request_button(self):
|
||||
return Button(self.driver, accessibility_id='Review pairing request')
|
||||
|
||||
@property
|
||||
def more_details_button(self):
|
||||
return Button(self.driver, accessibility_id='More details')
|
||||
|
||||
def handle_cr(self, element_accessibility: str):
|
||||
Button(
|
||||
self.driver,
|
||||
@@ -611,3 +619,8 @@ class HomeView(BaseView):
|
||||
xpath="//*[@content-desc='%s'][descendant::*[@content-desc='chat-name-text'][@text='%s']]" % (
|
||||
self.community_card_item.accessibility_id, community_name)
|
||||
)
|
||||
|
||||
def get_new_device_installation_id(self):
|
||||
element = Text(self.driver, accessibility_id='new-device-installation-id')
|
||||
element.wait_for_visibility_of_element()
|
||||
return element.text
|
||||
|
||||
@@ -4,7 +4,7 @@ from selenium.common import NoSuchElementException
|
||||
|
||||
from tests import common_password
|
||||
from tests.base_test_case import AbstractTestCase
|
||||
from views.base_element import Text, Button, EditBox, SilentButton
|
||||
from views.base_element import Text, Button, EditBox, SilentButton, BaseElement
|
||||
from views.base_view import BaseView
|
||||
|
||||
|
||||
@@ -68,15 +68,6 @@ class AdvancedButton(Button):
|
||||
return self.navigate()
|
||||
|
||||
|
||||
class BackupRecoveryPhraseButton(Button):
|
||||
def __init__(self, driver):
|
||||
super().__init__(driver, accessibility_id="back-up-recovery-phrase-button")
|
||||
|
||||
def click(self):
|
||||
self.scroll_to_element().click()
|
||||
return self.navigate()
|
||||
|
||||
|
||||
class RecoveryPhraseTable(Text):
|
||||
def __init__(self, driver):
|
||||
super().__init__(driver, translation_id="your-recovery-phrase",
|
||||
@@ -93,11 +84,6 @@ class RecoveryPhraseWordNumberText(Text):
|
||||
return int(self.find_element().text.split('#')[1])
|
||||
|
||||
|
||||
class RecoveryPhraseWordInput(EditBox):
|
||||
def __init__(self, driver):
|
||||
super(RecoveryPhraseWordInput, self).__init__(driver, xpath="//android.widget.EditText")
|
||||
|
||||
|
||||
class HelpButton(Button):
|
||||
def __init__(self, driver):
|
||||
super().__init__(driver, accessibility_id="help-button")
|
||||
@@ -235,10 +221,13 @@ class ProfileView(BaseView):
|
||||
self.show_profile_pictures_of = Button(self.driver, accessibility_id="show-profile-pictures")
|
||||
self.show_profile_pictures_to = Button(self.driver, accessibility_id="show-profile-pictures-to")
|
||||
## Backup recovery phrase
|
||||
self.backup_recovery_phrase_button = BackupRecoveryPhraseButton(self.driver)
|
||||
self.backup_recovery_phrase_button = Button(
|
||||
self.driver, accessibility_id="icon, Backup recovery phrase, label-component, icon")
|
||||
|
||||
self.recovery_phrase_table = RecoveryPhraseTable(self.driver)
|
||||
self.recovery_phrase_word_number = RecoveryPhraseWordNumberText(self.driver)
|
||||
self.recovery_phrase_word_input = RecoveryPhraseWordInput(self.driver)
|
||||
self.recovery_phrase_next_button = Button(self.driver, accessibility_id="Next, icon")
|
||||
self.recovery_phrase_word_input = EditBox(self.driver, xpath="//android.widget.EditText")
|
||||
## Dapps permissions
|
||||
self.dapp_permissions_button = DappPermissionsButton(self.driver)
|
||||
self.revoke_access_button = Button(self.driver, translation_id="revoke-access")
|
||||
@@ -290,8 +279,8 @@ class ProfileView(BaseView):
|
||||
self.advertise_device_button = Button(self.driver, accessibility_id="advertise-device")
|
||||
self.sync_all_button = Button(self.driver, translation_id="sync-all-devices")
|
||||
self.syncing_button = Button(self.driver, accessibility_id="icon, Syncing, label-component, icon")
|
||||
self.paired_devices_button = Button(self.driver,
|
||||
accessibility_id="icon, Paired devices, 0 devices, label-component, icon")
|
||||
self.paired_devices_button = Button(
|
||||
self.driver, xpath="//android.view.ViewGroup[contains(@content-desc,'icon, Paired devices,')]")
|
||||
self.sync_plus_button = Button(
|
||||
self.driver,
|
||||
xpath="//*[@text='Paired devices']/following-sibling::android.view.ViewGroup[@content-desc='icon']")
|
||||
@@ -316,6 +305,7 @@ class ProfileView(BaseView):
|
||||
suffix="/following-sibling::*[1]/android.widget.EditText")
|
||||
self.specify_network_id_input = EditBox(self.driver, translation_id="specify-network-id")
|
||||
self.connect_button = Button(self.driver, accessibility_id="network-connect-button")
|
||||
|
||||
## Toggles
|
||||
self.transaction_management_enabled_toggle = Button(self.driver,
|
||||
accessibility_id="transactions-management-enabled")
|
||||
@@ -371,6 +361,7 @@ class ProfileView(BaseView):
|
||||
self.confirm_testnet_mode_change_button = Button(self.driver, accessibility_id="confirm-testnet-mode-change")
|
||||
|
||||
def switch_network(self):
|
||||
self.driver.info("Toggling test mode")
|
||||
self.profile_wallet_button.click()
|
||||
self.network_settings_button.click()
|
||||
self.testnet_mode_toggle.click()
|
||||
@@ -425,20 +416,20 @@ class ProfileView(BaseView):
|
||||
return dict(zip(map(int, text[::2]), text[1::2]))
|
||||
|
||||
def backup_recovery_phrase(self):
|
||||
self.driver.info("## Back up seed phrase", device=False)
|
||||
self.just_fyi("Back up recovery phrase")
|
||||
self.backup_recovery_phrase_button.click()
|
||||
self.ok_continue_button.click()
|
||||
recovery_phrase = self.get_recovery_phrase()
|
||||
self.next_button.click()
|
||||
self.recovery_phrase_next_button.click()
|
||||
word_number = self.recovery_phrase_word_number.number
|
||||
self.recovery_phrase_word_input.send_keys(recovery_phrase[word_number])
|
||||
self.next_button.click()
|
||||
self.recovery_phrase_next_button.click()
|
||||
word_number_1 = self.recovery_phrase_word_number.number
|
||||
self.recovery_phrase_word_input.send_keys(recovery_phrase[word_number_1])
|
||||
self.done_button.click()
|
||||
self.yes_button.click()
|
||||
self.ok_got_it_button.click()
|
||||
self.driver.info("## Seed phrase is backed up!", device=False)
|
||||
return recovery_phrase
|
||||
return ' '.join(recovery_phrase.values())
|
||||
|
||||
def edit_profile_picture(self, image_index: int, update_by="Gallery"):
|
||||
self.driver.info("## Setting custom profile image", device=False)
|
||||
@@ -555,3 +546,25 @@ class ProfileView(BaseView):
|
||||
self.login_button.click()
|
||||
self.wait_for_staleness_of_element(password_input)
|
||||
return self.password_input.text
|
||||
|
||||
def get_current_device_name(self):
|
||||
element = BaseElement(
|
||||
self.driver,
|
||||
xpath="//android.view.ViewGroup/android.view.ViewGroup[@content-desc='status-tag-positive']/..")
|
||||
return element.attribute_value('content-desc').split(',')[1].strip()
|
||||
|
||||
def get_paired_device_by_name(self, device_name: str):
|
||||
|
||||
class PairedDeviceElement(BaseElement):
|
||||
def __init__(self, driver, device_name):
|
||||
super().__init__(driver, xpath="//*[@content-desc='icon, %s, label-component']" % device_name)
|
||||
|
||||
@property
|
||||
def get_pair_button(self):
|
||||
return Button(self.driver, xpath=self.locator + "//*[@content-desc='Pair']")
|
||||
|
||||
@property
|
||||
def get_unpair_button(self):
|
||||
return Button(self.driver, xpath=self.locator + "//*[@content-desc='Unpair']")
|
||||
|
||||
return PairedDeviceElement(self.driver, device_name)
|
||||
|
||||
@@ -154,6 +154,11 @@ class SignInView(BaseView):
|
||||
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.scan_sync_code_button = Button(self.driver, accessibility_id="scan-sync-code-option-card")
|
||||
self.enter_sync_code_button = Button(self.driver, accessibility_id="Enter sync code")
|
||||
self.enter_sync_code_input = EditBox(self.driver, accessibility_id="enter-sync-code-input")
|
||||
self.progress_screen_title = Text(self.driver, accessibility_id="progress-screen-title")
|
||||
self.try_seed_phrase_button = Button(self.driver, accessibility_id="try-seed-phrase-button")
|
||||
|
||||
self.migration_password_input = EditBox(self.driver, accessibility_id="enter-password-input")
|
||||
self.access_key_button = AccessKeyButton(self.driver)
|
||||
@@ -215,6 +220,7 @@ class SignInView(BaseView):
|
||||
self.show_profiles_button = Button(self.driver, accessibility_id="show-profiles")
|
||||
self.plus_profiles_button = Button(self.driver, accessibility_id="show-new-account-options")
|
||||
self.create_new_profile_button = Button(self.driver, accessibility_id="create-new-profile")
|
||||
self.sync_or_recover_new_profile_button = Button(self.driver, accessibility_id="multi-profile")
|
||||
self.remove_profile_button = Button(self.driver, accessibility_id="remove-profile")
|
||||
|
||||
def set_password(self, password: str):
|
||||
@@ -282,21 +288,24 @@ class SignInView(BaseView):
|
||||
return self.get_home_view()
|
||||
|
||||
def recover_access(self, passphrase: str, password: str = common_password, keycard=False,
|
||||
enable_notifications=False, second_user=False, username='Restore user', set_image=False):
|
||||
enable_notifications=False, second_user=False, username='Restore user', set_image=False,
|
||||
after_sync_code=False):
|
||||
self.driver.info("## Recover access(password:%s, keycard:%s)" % (password, str(keycard)), device=False)
|
||||
|
||||
if not second_user:
|
||||
self.terms_and_privacy_checkbox.click()
|
||||
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()
|
||||
self.create_new_profile_button.click()
|
||||
self.use_recovery_phrase_button.click()
|
||||
if not after_sync_code:
|
||||
if not second_user:
|
||||
self.terms_and_privacy_checkbox.click()
|
||||
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()
|
||||
self.create_new_profile_button.click()
|
||||
self.use_recovery_phrase_button.click()
|
||||
self.passphrase_edit_box.send_keys(passphrase)
|
||||
self.continue_button.click_until_presence_of_element(self.profile_title_input)
|
||||
self.set_profile(username, set_image)
|
||||
if not after_sync_code:
|
||||
self.set_profile(username, set_image)
|
||||
self.set_password(password)
|
||||
if enable_notifications:
|
||||
self.enable_notifications_button.wait_and_click()
|
||||
@@ -318,6 +327,23 @@ class SignInView(BaseView):
|
||||
self.driver.info("## Multiaccount is recovered successfully!", device=False)
|
||||
return self.get_home_view()
|
||||
|
||||
def sync_profile(self, sync_code: str, first_user: bool = True):
|
||||
if first_user:
|
||||
self.terms_and_privacy_checkbox.click()
|
||||
self.sync_or_recover_profile_button.click()
|
||||
self.not_now_button.click()
|
||||
else:
|
||||
self.show_profiles_button.click()
|
||||
self.plus_profiles_button.click()
|
||||
self.sync_or_recover_new_profile_button.click()
|
||||
self.scan_sync_code_button.click()
|
||||
for checkbox in self.checkbox_button.find_elements():
|
||||
checkbox.click()
|
||||
self.continue_button.click()
|
||||
self.enter_sync_code_button.click()
|
||||
self.enter_sync_code_input.send_keys(sync_code)
|
||||
self.confirm_button.click()
|
||||
|
||||
def sign_in(self, password=common_password):
|
||||
self.driver.info("## Sign in (password: %s)" % password, device=False)
|
||||
self.password_input.wait_for_visibility_of_element(10)
|
||||
|
||||
@@ -19,7 +19,7 @@ class AssetElement(Button):
|
||||
|
||||
def get_amount(self):
|
||||
element = Text(self.driver, xpath=self.locator + "/../android.widget.TextView[3]")
|
||||
element.scroll_to_element(down_start_y=0.89, down_end_y=0.8)
|
||||
element.scroll_to_element()
|
||||
try:
|
||||
amount = element.text.split()[0]
|
||||
if '<' in amount:
|
||||
|
||||
+37
-2
@@ -51,9 +51,11 @@
|
||||
"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-assets": "Add assets",
|
||||
"add-bootnode": "Add bootnode",
|
||||
"add-contact": "Add contact",
|
||||
"add-custom-token": "Add custom token",
|
||||
"add-eth": "Add ETH",
|
||||
"add-favourite": "Add favourite",
|
||||
"add-mailserver": "Add Status node",
|
||||
"add-me-to-your-contacts": "Please add me to your contacts",
|
||||
@@ -163,6 +165,7 @@
|
||||
"audio-recorder-max-ms-reached": "Maximum recording time reached",
|
||||
"audio-recorder-permissions-error": "You have to give permission to send audio messages",
|
||||
"aug": "Aug",
|
||||
"authorise-with-password": "Authorise with password",
|
||||
"authorize": "Authorize",
|
||||
"available": "Available",
|
||||
"available-participants": {
|
||||
@@ -266,14 +269,13 @@
|
||||
"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",
|
||||
"buy-ethereum": "Buy Ethereum",
|
||||
"buy-keycard": "Buy Keycard",
|
||||
"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",
|
||||
@@ -362,6 +364,7 @@
|
||||
"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-keycard": "Check your Keycard",
|
||||
"check-on-block-explorer": "Check on block explorer",
|
||||
"check-on-opensea": "Check on opensea",
|
||||
"check-other-device-for-pairing": "Check your other device for a pairing request.",
|
||||
@@ -470,6 +473,7 @@
|
||||
"connect-with-users": "Connect with users",
|
||||
"connected": "Connected",
|
||||
"connected-dapps": "Connected dApps",
|
||||
"connected-dont-move": "Connected. Don’t move your card.",
|
||||
"connected-peers": "Connected and discovered peers",
|
||||
"connected-to": "Connected to",
|
||||
"connecting": "Connecting...",
|
||||
@@ -536,6 +540,7 @@
|
||||
"create-channel-title": "New channel",
|
||||
"create-community": "Create a community",
|
||||
"create-group-chat": "Create group chat",
|
||||
"create-keycard-pin": "Create Keycard PIN",
|
||||
"create-multiaccount": "Generate keys",
|
||||
"create-new-key": "Get new keys",
|
||||
"create-new-profile": "Create new profile",
|
||||
@@ -827,6 +832,7 @@
|
||||
"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-card-info": "To generate new key pair, or import existent non-profile keys to the Keycard, please use the Status desktop app. If you'd like these features on mobile, feel free to upvote them and discuss them in Status community.",
|
||||
"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 ",
|
||||
@@ -1073,6 +1079,7 @@
|
||||
"generating-mnemonic": "Generating seed phrase",
|
||||
"generic-error": "Error: {{generic-error}}",
|
||||
"get-a-keycard": "Get a Keycard",
|
||||
"get-keycard": "Get Keycard",
|
||||
"get-started": "Get started",
|
||||
"get-status-at": "Get Status at http://status.im",
|
||||
"get-stickers": "Get Stickers",
|
||||
@@ -1132,6 +1139,7 @@
|
||||
"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-phone-near-keycard": "Hold your phone near a Status Keycard",
|
||||
"hold-to-post-1": "Hold",
|
||||
"hold-to-post-2": "to post",
|
||||
"home": "Home",
|
||||
@@ -1167,10 +1175,12 @@
|
||||
"import-community-title": "Import a community",
|
||||
"import-from-keycard": "Import from Keycard",
|
||||
"import-key-pair": "Import key pair",
|
||||
"import-key-pair-keycard": "Import profile key pair to Keycard",
|
||||
"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-profile-key-pair": "Import profile key pair",
|
||||
"import-to-use-derived-accounts": "Import to use derived accounts",
|
||||
"import-using-phrase": "Import using recovery phrase",
|
||||
"in": "in",
|
||||
@@ -1295,6 +1305,7 @@
|
||||
"jump-to": "Jump to",
|
||||
"jun": "Jun",
|
||||
"K": "K",
|
||||
"keep-card-steady": "Keep the card steady under the phone",
|
||||
"keep-key": "KeepKey",
|
||||
"key": "Key",
|
||||
"key-managment": "Key management",
|
||||
@@ -1324,6 +1335,7 @@
|
||||
"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-empty": "Keycard is empty",
|
||||
"keycard-enter-new-passcode": "Enter new passcode {{step}}/2",
|
||||
"keycard-error-description": "Connect the card again to continue",
|
||||
"keycard-error-title": "Connection lost",
|
||||
@@ -1478,6 +1490,7 @@
|
||||
"make-admin": "Make admin",
|
||||
"make-moderator": "Make moderator",
|
||||
"make-one-it-is-easy-we-promise": "Make one, it’s easy, we promise!",
|
||||
"make-sure-keycard": "Make sure the card you scanned is a Keycard.",
|
||||
"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",
|
||||
@@ -1557,6 +1570,10 @@
|
||||
"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",
|
||||
"migrate-key-pair-authorise": "To migrate profile key pair to Keycard you need to authorise with profile password.",
|
||||
"migrate-key-pair-keycard": "Migrate profile key pair to Keycard",
|
||||
"migrate-key-pair-keycard-default-key": "{{name}} is your default Status key pair. Migrating this key pair to Keycard will require you to use your Keycard to login to Status and to transact with the key pair’s derived accounts on all synced devices.\n\nKey pair will be removed from device and stored on Keycard.",
|
||||
"migrate-key-pair-keycard-info": "Re-encrypting your data with your new Keycard login method may take some time, during which you won’t be able to use the app.",
|
||||
"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.",
|
||||
@@ -1780,6 +1797,7 @@
|
||||
"on-keycard": "On Keycard",
|
||||
"on-status-tree": "On Status tree",
|
||||
"on-the-web": "On the web",
|
||||
"on-uppercase": "On",
|
||||
"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",
|
||||
@@ -1794,6 +1812,7 @@
|
||||
"online-community-member": "Online",
|
||||
"online-now": "Online now",
|
||||
"only-mentions": "Only @mentions",
|
||||
"oops-not-keycard": "Oops, this isn’t a Keycard",
|
||||
"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",
|
||||
@@ -1824,6 +1843,7 @@
|
||||
"outgoing": "Outgoing",
|
||||
"outgoing-transaction": "Outgoing transaction",
|
||||
"overview": "Overview",
|
||||
"own-keycard": "Already own a Keycard?",
|
||||
"own-your-crypto": "Own your crypto",
|
||||
"owner": "Owner",
|
||||
"page-camera-request-blocked": "camera requests blocked. To enable camera requests go to Settings",
|
||||
@@ -1912,6 +1932,7 @@
|
||||
"pin-code": "6-digit passcode",
|
||||
"pin-limit-reached": "Pin limit reached. Unpin a previous message first.",
|
||||
"pin-mismatch": "Wrong passcode",
|
||||
"pin-not-match": "PIN do not match",
|
||||
"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",
|
||||
@@ -2004,6 +2025,8 @@
|
||||
"re-encrypt-key": "Re-encrypt your keys",
|
||||
"read": "Read",
|
||||
"read-more": "Read more",
|
||||
"ready-keycard": "Get your Keycard ready",
|
||||
"ready-to-scan": "Ready to Scan",
|
||||
"rearrange-categories": "Rearrange Categories",
|
||||
"receive": "Receive",
|
||||
"receive-at-least": "Receive at least",
|
||||
@@ -2058,6 +2081,7 @@
|
||||
"remove-network": "Remove network",
|
||||
"remove-nickname": "Remove nickname",
|
||||
"remove-nickname-toast": "You have removed {{secondary-name}}'s nickname",
|
||||
"remove-phone-case": "Remove your phone case if you have one",
|
||||
"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",
|
||||
@@ -2072,6 +2096,7 @@
|
||||
"removed-from-contacts": "Removed from contacts",
|
||||
"rename": "Rename",
|
||||
"rename-key-pair": "Rename key pair",
|
||||
"repeat-keycard-pin": "Repeat Keycard PIN",
|
||||
"repeat-pin": "Repeat new 6-digit passcode",
|
||||
"repeat-puk": "Repeat new 12-digit PUK",
|
||||
"replied": "Replied",
|
||||
@@ -2166,9 +2191,11 @@
|
||||
"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?",
|
||||
"secure-wallet-card": "A secure and private cold wallet in a card format",
|
||||
"security": "Security",
|
||||
"see-details": "See details",
|
||||
"see-it-again": "SEE IT AGAIN",
|
||||
"see-keycard-ready": "Let’s see what’s on your Keycard. Ready?",
|
||||
"see-recovery-phrase-again": "See recovery phrase again",
|
||||
"see-sticker-set": "See the full sticker set",
|
||||
"see-suggestions": "See suggestions",
|
||||
@@ -2191,11 +2218,13 @@
|
||||
"select-another-account": "Select another account",
|
||||
"select-asset": "Select asset",
|
||||
"select-asset-on": "On",
|
||||
"select-asset-to-buy": "Select asset to buy",
|
||||
"select-asset-to-pay": "Select asset to pay",
|
||||
"select-asset-to-receive": "Select asset to receive",
|
||||
"select-asset-via": "via",
|
||||
"select-chat": "Select chat to start messaging",
|
||||
"select-network": "Select network",
|
||||
"select-network-for-buying": "Select network for buying",
|
||||
"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",
|
||||
@@ -2248,6 +2277,7 @@
|
||||
"set-up-sync": "Set up sync",
|
||||
"settings": "Settings",
|
||||
"setup-group-chat": "Setup group chat",
|
||||
"setup-keycard": "Setup Keycard",
|
||||
"setup-syncing": "Pair devices to sync",
|
||||
"share": "Share",
|
||||
"share-account": "Share account",
|
||||
@@ -2315,6 +2345,7 @@
|
||||
"signing-phrase": "Signing phrase",
|
||||
"simple": "Simple",
|
||||
"skip": "Skip",
|
||||
"slide-to-authorise": "Slide to authorise",
|
||||
"slide-to-bridge": "Slide to bridge",
|
||||
"slide-to-create-account": "Slide to create account",
|
||||
"slide-to-import": "Slide to import",
|
||||
@@ -2472,6 +2503,7 @@
|
||||
"time-in-mins": "{{minutes}} min",
|
||||
"timeline": "Timeline",
|
||||
"tip-cap": "Tip cap",
|
||||
"tips-scan-keycard": "Tips to scan your Keycard",
|
||||
"to": "to",
|
||||
"to-block": "Block",
|
||||
"to-capitalized": "To",
|
||||
@@ -2626,6 +2658,7 @@
|
||||
"use-as-profile-picture": "Use as profile picture",
|
||||
"use-biometrics": "Use biometrics to fill in your password",
|
||||
"use-keycard": "Use Keycard",
|
||||
"use-keycard-login-sign": "Use this Keycard to login and sign transactions",
|
||||
"use-keycard-subtitle": "Keys will be stored on your Keycard",
|
||||
"use-photo": "Use Photo",
|
||||
"use-recovery-phrase": "Use recovery phrase",
|
||||
@@ -2744,6 +2777,7 @@
|
||||
"watch-only": "Watch-only",
|
||||
"watched-account-removed": "Watched address has been removed",
|
||||
"watched-address": "Watched address",
|
||||
"ways-to-buy-assets": "Ways to buy assets",
|
||||
"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",
|
||||
@@ -2770,6 +2804,7 @@
|
||||
"what-are-you-waiting-for": "What are you waiting for?",
|
||||
"what-changed": "What changed",
|
||||
"what-is-shared": "What is shared",
|
||||
"what-to-do": "What you would like to do?",
|
||||
"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…",
|
||||
|
||||
Reference in New Issue
Block a user