Implement seed phrase fallback flow (#21090)

Co-authored-by: Parvesh Monu <parvesh.dhullmonu@gmail.com>
Co-authored-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
This commit is contained in:
Parvesh Monu 2024-09-19 15:23:23 +05:30 committed by GitHub
parent 2e61f94d84
commit 0eb065bbb7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 445 additions and 88 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -62,6 +62,7 @@
:chatId :chat-id
:contactVerificationStatus :contact-verification-status
:communityId :community-id
:installationId :installation-id
:membershipStatus :membership-status
:albumMessages :album-messages})
(update :last-message #(when % (messages/<-rpc %)))

View File

@ -123,7 +123,9 @@
notification-types/reply 18
notification-types/contact-request 19
notification-types/admin 20
notification-types/contact-verification 21}
notification-types/contact-verification 21
notification-types/new-installation-received 22
notification-types/new-installation-created 23}
(store/parse-notification-counts-response
{(keyword (str notification-types/one-to-one-chat)) 15
(keyword (str notification-types/private-group-chat)) 16
@ -132,5 +134,7 @@
(keyword (str notification-types/contact-request)) 19
(keyword (str notification-types/admin)) 20
(keyword (str notification-types/contact-verification)) 21
(keyword (str notification-types/new-installation-received)) 22
(keyword (str notification-types/new-installation-created)) 23
;; Unsupported type in the response is ignored
:999 100}))))

View File

@ -243,6 +243,23 @@
{}
installations))})
(rf/defn finish-seed-phrase-fallback-syncing
{:events [:pairing/finish-seed-phrase-fallback-syncing]}
[{:keys [db]}]
{:fx [[:json-rpc/call
[{:method "wakuext_enableInstallationAndPair"
:params [{:installationId (:syncing/installation-id db)}]
:js-response true
:on-success [:sanitize-messages-and-process-response]}]]]})
(rf/defn pair-and-sync
{: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")}]]]})
(rf/defn enable-installation-success
{:events [:pairing.callback/enable-installation-success]}
[cofx installation-id]

View File

@ -0,0 +1,23 @@
(ns status-im.common.new-device-sheet.style
(:require [quo.foundations.colors :as colors]))
(def heading
{:padding-left 20
:padding-bottom 8})
(def message
{:padding-horizontal 20
:padding-top 4})
(def warning
{:margin-horizontal 20
:margin-top 10})
(def drawer-container
{:padding-horizontal 13
:padding-top 16})
(def settings-subtext
{:color colors/white-opa-70
:align-self :center
:margin-bottom 12})

View File

@ -0,0 +1,75 @@
(ns status-im.common.new-device-sheet.view
(:require
[quo.core :as quo]
[react-native.core :as rn]
[status-im.common.events-helper :as events-helper]
[status-im.common.new-device-sheet.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- pair-and-sync
[installation-id]
(rf/dispatch [:pairing/pair-and-sync installation-id])
(events-helper/hide-bottom-sheet))
(defn installation-request-receiver-view
[installation-id]
(rn/use-mount events-helper/dismiss-keyboard)
[:<>
[quo/text
{:weight :semi-bold
:size :heading-2
:accessibility-label :new-device-sheet-heading
:style style/heading}
(i18n/label :t/pair-new-device-and-sync)]
[quo/text
{:weight :regular
:size :paragraph-1
:accessibility-label :new-device-sheet-message
:style style/message}
(i18n/label :t/new-device-detected-recovered-device-message)]
[quo/text
{:weight :semi-bold
:size :heading-2
:accessibility-label :new-device-installation-id
:style style/heading}
installation-id]
[quo/bottom-actions
{:actions :two-actions
:blur? true
:container-style {:margin-top 12}
:button-two-label (i18n/label :t/cancel)
:button-two-props {:type :grey
:on-press events-helper/hide-bottom-sheet}
:button-one-label (i18n/label :t/pair-and-sync)
:button-one-props {:on-press #(pair-and-sync installation-id)}}]])
(defn installation-request-creator-view
[installation-id]
(rn/use-mount events-helper/dismiss-keyboard)
[:<>
[quo/text
{:weight :semi-bold
:size :heading-2
:accessibility-label :new-device-sheet-heading
:style style/heading}
(i18n/label :t/pair-this-device-and-sync)]
[quo/text
{:weight :regular
:size :paragraph-1
:accessibility-label :new-device-sheet-message
:style style/message}
(i18n/label :t/new-device-detected-other-device-message)]
[quo/text
{:weight :semi-bold
:size :heading-2
:accessibility-label :new-device-installation-id
:style style/heading}
installation-id]
[quo/bottom-actions
{:actions :one-action
:blur? true
:container-style {:margin-top 12}
:button-one-label (i18n/label :t/close)
:button-one-props {:type :grey
:on-press events-helper/hide-bottom-sheet}}]])

View File

@ -28,6 +28,7 @@
:notifications (js/require "../resources/images/ui2/notifications.png")
:nfc-prompt (js/require "../resources/images/ui2/nfc-prompt.png")
:nfc-success (js/require "../resources/images/ui2/nfc-success.png")
:preparing-status (js/require "../resources/images/ui2/preparing-status.png")
:syncing-devices (js/require "../resources/images/ui2/syncing_devices.png")
:syncing-wrong (js/require "../resources/images/ui2/syncing_wrong.png")})

View File

@ -37,6 +37,7 @@
:screen/onboarding.enable-biometrics
:screen/onboarding.generating-keys
:screen/onboarding.enable-notifications
:screen/onboarding.preparing-status
:screen/onboarding.sign-in-intro
:screen/onboarding.sign-in
:screen/onboarding.syncing-progress

View File

@ -71,17 +71,20 @@
{:events [:onboarding/create-account-and-login]}
[{:keys [db] :as cofx}]
(let [{:keys [display-name seed-phrase password image-path color] :as profile}
(:onboarding/profile db)]
(:onboarding/profile db)
loading-screen (if (seq (:syncing/key-uid db))
:screen/onboarding.preparing-status
:screen/onboarding.generating-keys)]
(rf/merge cofx
{:dispatch [:navigate-to-within-stack
[:screen/onboarding.generating-keys
[loading-screen
(get db
:onboarding/navigated-to-enter-seed-phrase-from-screen
:screen/onboarding.new-to-status)]]
:dispatch-later [{:ms constants/onboarding-generating-keys-animation-duration-ms
:dispatch [:navigate-to-within-stack
[:screen/onboarding.enable-notifications
:screen/onboarding.generating-keys]]}]
loading-screen]]}]
:db (-> db
(dissoc :profile/login)
(dissoc :auth-method)
@ -134,6 +137,11 @@
(rf/defn seed-phrase-validated
{:events [:onboarding/seed-phrase-validated]}
[{:keys [db]} seed-phrase key-uid]
(let [syncing-account-recovered? (and (seq (:syncing/key-uid db))
(= (:syncing/key-uid db) key-uid))
next-screen (if syncing-account-recovered?
:screen/onboarding.create-profile-password
:screen/onboarding.create-profile)]
(if (contains? (:profile/profiles-overview db) key-uid)
{:effects.utils/show-confirmation
{:title (i18n/label :t/multiaccount-exists-title)
@ -144,12 +152,17 @@
(re-frame/dispatch
[:profile/profile-selected key-uid]))
:on-cancel #(re-frame/dispatch [:pop-to-root :multiaccounts])}}
{:db (assoc-in db [:onboarding/profile :seed-phrase] seed-phrase)
:dispatch [:navigate-to-within-stack
[:screen/onboarding.create-profile
{:db (-> db
(assoc-in [:onboarding/profile :seed-phrase] seed-phrase)
(assoc-in [:onboarding/profile :color] constants/profile-default-color))
:fx [[:dispatch
[:navigate-to-within-stack
[next-screen
(get db
:onboarding/navigated-to-enter-seed-phrase-from-screen
:screen/onboarding.new-to-status)]]}))
:screen/onboarding.new-to-status)]]]
(when-not syncing-account-recovered?
[:dispatch [:syncing/clear-syncing-data]])]})))
(rf/defn navigate-to-create-profile
{:events [:onboarding/navigate-to-create-profile]}

View File

@ -0,0 +1,11 @@
(ns status-im.contexts.onboarding.preparing-status.style)
(defn page-container
[insets]
{:flex 1
:padding-top (:top insets)})
(defn page-illustration
[width]
{:flex 1
:width width})

View File

@ -0,0 +1,36 @@
(ns status-im.contexts.onboarding.preparing-status.view
(:require
[quo.core :as quo]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[status-im.common.resources :as resources]
[status-im.contexts.onboarding.generating-keys.style :as style]
[utils.i18n :as i18n]))
(defn title
[]
[rn/view
{:style {:margin-top 56
:height 56
:margin-bottom 10}}
[quo/text-combinations
{:container-style {:margin-horizontal 20
:margin-vertical 12}
:title (i18n/label :t/preparing-app-for-you)
:description (i18n/label :t/hang-in-there)}]])
(defn content
[]
(let [width (:width (rn/get-window))]
[rn/image
{:resize-mode :stretch
:style (style/page-illustration width)
:source (resources/get-image :preparing-status)}]))
(defn view
[]
(let [insets (safe-area/get-insets)]
[rn/view {:style (style/page-container insets)}
[:<>
[title]
[content]]]))

View File

@ -27,7 +27,3 @@
:align-items :center
:align-self :center
:justify-content :center})
(def try-again-button
{:margin-top 20
:padding-horizontal 20})

View File

@ -5,6 +5,7 @@
[status-im.common.resources :as resources]
[status-im.contexts.onboarding.common.background.view :as background]
[status-im.contexts.onboarding.syncing.progress.style :as style]
[utils.debounce :as debounce]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@ -25,17 +26,40 @@
:title-accessibility-label :progress-screen-title
:description-accessibility-label :progress-screen-sub-title}])
(defn try-again-button
[profile-color]
[quo/button
{:on-press (fn []
(defn- navigate-to-enter-seed-phrase
[]
(debounce/debounce-and-dispatch
[:onboarding/navigate-to-sign-in-by-seed-phrase :screen/onboarding.sync-or-recover-profile]
500))
(defn- try-again
[logged-in?]
(rf/dispatch [:syncing/clear-states])
(rf/dispatch [:navigate-back]))
(if logged-in?
(rf/dispatch [:navigate-back])
(rf/dispatch [:navigate-back-to :screen/onboarding.sync-or-recover-profile])))
(defn try-again-button
[profile-color logged-in?]
[quo/bottom-actions
{:actions (if logged-in? :one-action :two-actions)
:blur? true
:button-one-label (i18n/label :t/recovery-phrase)
:button-one-props {:type :primary
:accessibility-label :try-seed-phrase-button
:customization-color profile-color
:container-style {:flex 1}
:size 40
:on-press navigate-to-enter-seed-phrase}
(if logged-in? :button-one-label :button-two-label)
(i18n/label :t/try-again)
(if logged-in? :button-one-props :button-two-props)
{:type (if logged-in? :primary :grey)
:accessibility-label :try-again-later-button
:customization-color profile-color
:container-style {:flex 1}
:size 40
:container-style style/try-again-button}
(i18n/label :t/try-again)])
:on-press #(try-again logged-in?)}}])
(defn- illustration
[pairing-progress?]
@ -47,6 +71,7 @@
(defn view
[in-onboarding?]
(let [pairing-status (rf/sub [:pairing/pairing-status])
logged-in? (rf/sub [:multiaccount/logged-in?])
pairing-progress? (pairing-progress pairing-status)
profile-color (or (:color (rf/sub [:onboarding/profile]))
(rf/sub [:profile/customization-color]))]
@ -58,7 +83,7 @@
[page-title pairing-progress?]
[illustration pairing-progress?]
(when-not (pairing-progress pairing-status)
[try-again-button profile-color])]))
[try-again-button profile-color logged-in?])]))
(defn view-onboarding
[]

View File

@ -142,6 +142,8 @@
(rf/dispatch [:chats-list/load-success result])
(rf/dispatch [:communities/get-user-requests-to-join])
(rf/dispatch [:profile.login/get-chats-callback]))}]
(when (:syncing/installation-id db)
[:dispatch [:pairing/finish-seed-phrase-fallback-syncing]])
(when-not new-account?
[:dispatch [:universal-links/process-stored-event]])]})))

View File

@ -1,6 +1,7 @@
(ns status-im.contexts.profile.recover.events
(:require
[native-module.core :as native-module]
[status-im.constants :as constants]
[status-im.contexts.profile.config :as profile.config]
status-im.contexts.profile.recover.effects
[utils.re-frame :as rf]
@ -16,10 +17,10 @@
(assoc-in [:syncing :login-sha3-password] login-sha3-password))
:effects.profile/restore-and-login
(merge (profile.config/create)
{:displayName display-name
(assoc (profile.config/create)
:displayName display-name
:mnemonic (security/safe-unmask-data seed-phrase)
:password login-sha3-password
:imagePath (profile.config/strip-file-prefix image-path)
:customizationColor color
:fetchBackup true})}))
:customizationColor (or color constants/profile-default-color)
:fetchBackup true)}))

View File

@ -0,0 +1,78 @@
(ns status-im.contexts.shell.activity-center.notification.syncing.view
(:require
[quo.core :as quo]
quo.theme
[react-native.core :as rn]
[status-im.common.new-device-sheet.view :as new-device-sheet]
[status-im.contexts.shell.activity-center.notification.common.view :as common]
[utils.datetime :as datetime]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- swipeable
[{:keys [extra-fn]} child]
[common/swipeable
{:left-button common/swipe-button-read-or-unread
:left-on-press common/swipe-on-press-toggle-read
:right-button common/swipe-button-delete
:right-on-press common/swipe-on-press-delete
:extra-fn extra-fn}
child])
(defn installation-created-view
[{:keys [notification extra-fn]}]
(let [{:keys [installation-id read timestamp]} notification
customization-color (rf/sub [:profile/customization-color])
theme (quo.theme/use-theme)
more-details (rn/use-callback
(fn []
(rf/dispatch [:show-bottom-sheet
{:content
(fn []
[new-device-sheet/installation-request-creator-view
installation-id])}]))
[installation-id])]
[swipeable {:extra-fn extra-fn}
[quo/activity-log
{:title (i18n/label :t/sync-your-profile)
:customization-color customization-color
:icon :i/mobile
:timestamp (datetime/timestamp->relative timestamp)
:unread? (not read)
:context [(i18n/label :t/check-other-device-for-pairing)]
:items [{:type :button
:subtype :positive
:key :review-pairing-request
:blur? true
:label (i18n/label :t/more-details)
:theme theme
:on-press more-details}]}]]))
(defn installation-received-view
[{:keys [notification extra-fn]}]
(let [{:keys [installation-id read timestamp]} notification
customization-color (rf/sub [:profile/customization-color])
theme (quo.theme/use-theme)
review-pairing-request (rn/use-callback
(fn []
(rf/dispatch [:show-bottom-sheet
{:content
(fn []
[new-device-sheet/installation-request-receiver-view
installation-id])}]))
[installation-id])]
[swipeable {:extra-fn extra-fn}
[quo/activity-log
{:title (i18n/label :t/new-device-detected)
:customization-color customization-color
:icon :i/mobile
:timestamp (datetime/timestamp->relative timestamp)
:unread? (not read)
:context [(i18n/label :t/new-device-detected)]
:items [{:type :button
:subtype :positive
:key :review-pairing-request
:blur? true
:label (i18n/label :t/review-pairing-request)
:theme theme
:on-press review-pairing-request}]}]]))

View File

@ -10,6 +10,8 @@
(def ^:const admin 8)
(def ^:const community-kicked 9)
(def ^:const contact-verification 10)
(def ^:const new-installation-received 23)
(def ^:const new-installation-created 24)
(def ^:const all-supported
#{one-to-one-chat
@ -20,7 +22,9 @@
community-request
admin
community-kicked
contact-verification})
contact-verification
new-installation-received
new-installation-created})
;; TODO: Replace with correct enum values once status-go implements them.
(def ^:const tx 66612)

View File

@ -19,6 +19,7 @@
[status-im.contexts.shell.activity-center.notification.membership.view :as membership]
[status-im.contexts.shell.activity-center.notification.mentions.view :as mentions]
[status-im.contexts.shell.activity-center.notification.reply.view :as reply]
[status-im.contexts.shell.activity-center.notification.syncing.view :as syncing]
[status-im.contexts.shell.activity-center.style :as style]
[status-im.contexts.shell.activity-center.tabs.empty-tab.view :as empty-tab]
[utils.re-frame :as rf]))
@ -48,6 +49,12 @@
(= notification-type types/admin)
[admin/view props]
(= notification-type types/new-installation-received)
[syncing/installation-received-view props]
(= notification-type types/new-installation-created)
[syncing/installation-created-view props]
(types/membership notification-type)
(condp = notification-type
types/private-group-chat [membership/view props]

View File

@ -2,6 +2,7 @@
(:require
[clojure.string :as string]
[native-module.core :as native-module]
[re-frame.core :as re-frame]
[react-native.platform :as platform]
[status-im.constants :as constants]
[status-im.contexts.profile.config :as profile.config]
@ -21,18 +22,45 @@
{:events [:syncing/clear-states]}
[{:keys [db]} role]
{:db (dissoc db :syncing)})
(defn- input-connection-string-callback
[res]
(log/info "[local-pairing] input-connection-string-for-bootstrapping callback"
{:response res
:event :syncing/input-connection-string-for-bootstrapping})
(let [error (when (sync-utils/extract-error res)
(str "generic-error: " res))]
(when (some? error)
(let [response (transforms/json->clj res)
installation-id (:installationId response)
key-uid (:keyUID response)
error (:error response)]
(when (seq installation-id)
(rf/dispatch [:syncing/set-syncing-installation-id installation-id key-uid]))
(when (seq error)
(rf/dispatch [:toasts/upsert
{:type :negative
:text error}]))))
(rf/defn initiate-pairing-process
{:events [:syncing/initiate-pairing-process]}
[{:keys [db]}]
{:db (assoc db :syncing/pairing-process-initiated? true)})
(rf/defn set-syncing-installation-id
{:events [:syncing/set-syncing-installation-id]}
[{:keys [db]} installation-id key-uid]
{:db (assoc db
:syncing/key-uid key-uid
:syncing/installation-id installation-id)})
(defn clear-syncing-data
[{:keys [db]}]
{:db (dissoc
db
:syncing/key-uid
:syncing/installation-id
:syncing/pairing-process-initiated?)})
(re-frame/reg-event-fx :syncing/clear-syncing-data clear-syncing-data)
(rf/defn preflight-outbound-check-for-local-pairing
{:events [:syncing/preflight-outbound-check]}
[_ set-checks-passed]
@ -67,6 +95,7 @@
(when (sync-utils/valid-connection-string? response)
(on-valid-connection-string response)
(rf/dispatch [:syncing/update-role constants/local-pairing-role-sender])
(rf/dispatch [:syncing/initiate-pairing-process])
(rf/dispatch [:hide-bottom-sheet])))]
(when-not (and error (string/blank? error))
(let [key-uid (get-in db [:profile/profile :key-uid])

View File

@ -34,6 +34,7 @@
[status-im.contexts.onboarding.generating-keys.view :as generating-keys]
[status-im.contexts.onboarding.identifiers.view :as identifiers]
[status-im.contexts.onboarding.intro.view :as intro]
[status-im.contexts.onboarding.preparing-status.view :as preparing-status]
[status-im.contexts.onboarding.sign-in.view :as sign-in]
[status-im.contexts.onboarding.syncing.progress.view :as syncing-devices]
[status-im.contexts.onboarding.syncing.results.view :as syncing-results]
@ -345,6 +346,15 @@
:popStackOnPress false}}
:component generating-keys/view}
{:name :screen/onboarding.preparing-status
:options {:theme :dark
:layout options/onboarding-transparent-layout
:animations transitions/push-animations-for-transparent-background
:popGesture false
:hardwareBackButton {:dismissModalOnPress false
:popStackOnPress false}}
:component preparing-status/view}
{:name :screen/onboarding.enter-seed-phrase
:options {:theme :dark
:layout options/onboarding-transparent-layout

View File

@ -28,7 +28,9 @@
types/contact-request 0
types/mention 0
types/reply 0
types/admin 0})
types/admin 0
types/new-installation-received 0
types/new-installation-created 0})
(is (= #{} (rf/sub [sub-name]))))
@ -41,13 +43,16 @@
types/contact-request 0
types/mention 3
types/reply 0
types/admin 2})
types/admin 2
types/new-installation-received 1
types/new-installation-created 0})
(let [actual (rf/sub [sub-name])]
(is (= #{types/one-to-one-chat
types/contact-verification
types/mention
types/admin}
types/admin
types/new-installation-received}
actual))
(is (set? actual)))))
@ -61,9 +66,11 @@
types/contact-request 4
types/mention 5
types/reply 6
types/admin 7})
types/admin 7
types/new-installation-received 8
types/new-installation-created 9})
(is (= 28 (rf/sub [sub-name]))))
(is (= 45 (rf/sub [sub-name]))))
(h/deftest-sub :activity-center/unread-indicator
[sub-name]

View File

@ -266,6 +266,11 @@
(fn [{:keys [preview-privacy?]}]
(boolean preview-privacy?)))
(re-frame/reg-sub :profile/installation-id
:<- [:profile/profile]
(fn [{:keys [installation-id]}]
installation-id))
(defn- replace-multiaccount-image-uri
[profile ens-names port font-file avatar-opts theme]
(let [{:keys [key-uid ens-name? images

View File

@ -3,7 +3,7 @@
"_comment": "Instead use: scripts/update-status-go.sh <rev>",
"owner": "status-im",
"repo": "status-go",
"version": "6e5a32c02215f36509b21645a32361fca85b10f7",
"commit-sha1": "6e5a32c02215f36509b21645a32361fca85b10f7",
"src-sha256": "0wp0a7xp7vw64kkiyrpkswb37dfabj1al4a4nrb71iyfrcyrzscl"
"version": "v2.0.0",
"commit-sha1": "50338d01d7e3c08661e1b788b828d35b257c6c5a",
"src-sha256": "18gvq8hb7isn39ggddsxwrn29ah4p6af6azf9cc0kc7sxmdi0awn"
}

View File

@ -363,6 +363,7 @@
"check-before-syncing-doc-description": "To sync your devices successfully, make sure to check and complete these steps:",
"check-on-block-explorer": "Check on block explorer",
"check-on-opensea": "Check on opensea",
"check-other-device-for-pairing": "Check your other device for a pairing request.",
"check-your-account-balance-and-activity": "Check your account balance and activity",
"check-your-recovery-phrase": "Check your seed phrase",
"choose-actions": "Choose actions",
@ -964,7 +965,6 @@
"enter-puk-code-description": "6-digit passcode has been blocked.\n Please enter PUK code to unblock passcode.",
"enter-recipient-address-or-username": "Enter address or username of the recipient",
"enter-recovery-phrase": "Enter recovery phrase",
"enter-seed-phrase": "Enter seed phrase",
"enter-sync-code": "Enter sync code",
"enter-url": "Enter URL",
"enter-user-pk": "Enter user public key",
@ -1111,6 +1111,7 @@
"group-membership-request": "Group membership request",
"groups": "Groups",
"gwei": "Gwei",
"hang-in-there": "Hang in there! Just a few more seconds!",
"has-permissions": "has permission to access",
"hash": "Hash",
"have-a-sync-code?": "Have a sync code?",
@ -1582,6 +1583,7 @@
"mobile-syncing-sheet-title": "Sync using mobile data?",
"mon": "Mon",
"more": "more",
"more-details": "More details",
"more-details-in-privacy-policy-1": "For more details refer to our ",
"more-details-in-privacy-policy-2": "Privacy Policy",
"more-details-in-privacy-policy-settings-1": "For details on this and other potential limited data processing by Status, see our ",
@ -1650,6 +1652,9 @@
"new-community-title": "New community",
"new-contact": "New contact",
"new-contract": "New Contract",
"new-device-detected": "New device detected",
"new-device-detected-other-device-message": "Check your other device for a pairing request. Ensure that the this device ID displayed on your other device. Only proceed with pairing and syncing if the IDs are identical.",
"new-device-detected-recovered-device-message": "A new device has been detected. You can see device ID below and on your other device. Only confirm the request if device ID matches.",
"new-favourite": "New favourite",
"new-group": "New group",
"new-group-chat": "New group chat",
@ -1817,14 +1822,17 @@
"page-camera-request-blocked": "camera requests blocked. To enable camera requests go to Settings",
"page-would-like-to-use-camera": "would like to use your camera",
"pair": "Pair",
"pair-and-sync": "Pair and Sync",
"pair-card": "Pair to this device",
"pair-code": "Pair code",
"pair-code-explanation": "Pairs card to a different device (up to 5) to unlock keys and sign transactions with the same Keycard",
"pair-code-placeholder": "Pair code...",
"pair-device-toast": "Device successfully paired",
"pair-devices": "Pair devices",
"pair-new-device-and-sync": "Pair new device and sync profile",
"pair-this-card": "Pair this card",
"pair-this-device": "Advertise device",
"pair-this-device-and-sync": "Pair this device and sync profile",
"pair-this-device-description": "Pair your devices to sync contacts and chats between them",
"paired-devices": "Paired devices",
"paired-with-this-device": "Paired with this device",
@ -1920,6 +1928,7 @@
"powered-by-paraswap": "Powered by Paraswap",
"preference": "Preference",
"preferred-by-receiver": "Preferred by receiver",
"preparing-app-for-you": "Preparing app for you...",
"press": "Press",
"preview-privacy": "Preview privacy mode",
"previewing-may-share-metadata": "Previewing links from these websites may share your metadata with their owners",
@ -2006,7 +2015,7 @@
"recover-with-seed-phrase": "Recover with seed phrase",
"recovering-key": "Accessing keys...",
"recovery-confirm-phrase": "Confirm seed phrase",
"recovery-phrase": "Seed phrase",
"recovery-phrase": "Recovery phrase",
"recovery-success-text": "You will have to create a new code or password to re-encrypt your keys",
"recovery-typo-dialog-description": "Please note, your seed phrase must use the exact same words and order as you received it",
"recovery-typo-dialog-title": "Is the seed phrase correct?",
@ -2089,6 +2098,7 @@
"reveal-qr-code": "Reveal QR code",
"reveal-sync-code": "Reveal sync code",
"review-bridge": "Review bridge",
"review-pairing-request": "Review pairing request",
"review-send": "Review send",
"review-swap": "Review swap",
"revoke-access": "Revoke access",
@ -2400,6 +2410,7 @@
"sync-or-recover-profile": "Sync or recover profile",
"sync-settings": "Sync settings",
"sync-synced": "In sync",
"sync-your-profile": "Sync your profile",
"synced-devices": "Synced Devices",
"synced-with": "Synced with",
"synchronise-your-data-across-your-devices": "Synchronise your data across your devices",