Compare commits

..
258 changed files with 10337 additions and 3515 deletions
-1
View File
@@ -35,4 +35,3 @@ LOCAL_PAIRING_ENABLED=1
TEST_STATEOFUS=1
FAST_CREATE_COMMUNITY_ENABLED=1
TEST_NETWORKS_ENABLED=1
SHOW_NOT_IMPLEMENTED_FEATURES=1
-3
View File
@@ -34,6 +34,3 @@ STICKERS_TEST_ENABLED=1
LOCAL_PAIRING_ENABLED=1
FAST_CREATE_COMMUNITY_ENABLED=1
TEST_NETWORKS_ENABLED=1
SHOW_NOT_IMPLEMENTED_FEATURES=1
DELETE_MESSAGE_FOR_ME_UNDO_TIME_LIMIT=10000
DELETE_MESSAGE_UNDO_TIME_LIMIT=10000
-1
View File
@@ -36,4 +36,3 @@ STICKERS_TEST_ENABLED=1
LOCAL_PAIRING_ENABLED=1
FAST_CREATE_COMMUNITY_ENABLED=1
TEST_NETWORKS_ENABLED=1
SHOW_NOT_IMPLEMENTED_FEATURES=1
+1 -8
View File
@@ -36,12 +36,6 @@ pipeline {
))
}
environment {
/* Avoid race conditions with other builds using virtualenv. */
VIRTUAL_ENV = "${WORKSPACE_TMP}/venv"
PATH = "${VIRTUAL_ENV}/bin:${PATH}"
}
stages {
stage('Fetch') {
steps { script {
@@ -58,8 +52,7 @@ pipeline {
stage('Setup') {
steps { script {
dir('test/appium') {
sh "python3 -m venv ${VIRTUAL_ENV}"
sh 'pip3 install -r requirements.txt'
sh 'pip3 install --user -r requirements.txt'
}
} }
}
+1 -8
View File
@@ -54,12 +54,6 @@ pipeline {
))
}
environment {
/* Avoid race conditions with other builds using virtualenv. */
VIRTUAL_ENV = "${WORKSPACE_TMP}/venv"
PATH = "${VIRTUAL_ENV}/bin:${PATH}"
}
stages {
stage('Prep') {
steps { script {
@@ -85,8 +79,7 @@ pipeline {
stage('Setup') {
steps { script {
dir('test/appium') {
sh "python3 -m venv ${VIRTUAL_ENV}"
sh 'pip3 install -r requirements.txt'
sh 'pip3 install --user -r requirements.txt'
}
} }
}
+1 -7
View File
@@ -35,11 +35,6 @@ pipeline {
))
}
environment {
/* Avoid race conditions with other builds using virtualenv. */
VIRTUAL_ENV = "${WORKSPACE_TMP}/venv"
PATH = "${VIRTUAL_ENV}/bin:${PATH}"
}
stages {
stage('Prep') {
@@ -56,8 +51,7 @@ pipeline {
stage('Setup') {
steps { script {
dir('test/appium') {
sh "python3 -m venv ${VIRTUAL_ENV}"
sh 'pip3 install -r requirements.txt'
sh 'pip3 install --user -r requirements.txt'
}
} }
}
+1
View File
@@ -8,6 +8,7 @@
1) Read our [PR Review Policy](pr-review-policy.md) for more details on our approach to PR reviews.
2) Learn more about our [Pipeline for QA](pipeline_process.md) process
6) Once e2e tests have run, they will report the result on the PR, if it's less than 100%, ask QA to take a look to make sure everything is in order (some might fail for legitimate reasons)
1) Try pinging @status-im/mobile-qa for help when reviewing the e2e results on the PR.
7) Ask QA for manual testing if the PR requires it (more details [here](pipeline_process.md))
8) Once it has been tested successfully, squash everything into one commit. rebase and merge. The commands we use:
```
-16
View File
@@ -128,19 +128,3 @@ Status-mobile uses `shadow-cljs` for hot reloading changes and uses its own [rel
### Solution
Open react native's [In-App Developer Menu](https://reactnative.dev/docs/debugging#accessing-the-in-app-developer-menu) and press "Disable Fast Refresh" or "Disable Hot Reloading"
# App crashing after running `make run-ios`
### Cause
It's possible that installing XCode from a `.xip` file might cause XCode to act funny
Since it's not installed from the App Store, Or you might be missing a Rosetta installation.
[Reference/Similar issue](https://github.com/expo/expo-cli/issues/3197)
You might see something like this after running `make run-ios`:
```
Underlying error (domain=FBSOpenApplicationServiceErrorDomain, code=1):
```
### Solution
Run `softwareupdate --install-rosetta --agree-to-license`
to accept XCode's license and install Rosetta.
-1
View File
@@ -27,7 +27,6 @@ nix_install_type() {
if [[ "$(os_name)" =~ NixOS ]]; then
echo "nixos"
else
USER=$(id -un) # Missing in Docker.
case "${NIX_STORE_DIR_GROUP}" in
"nixbld") echo "multi";;
"30000") echo "multi";;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

+131
View File
@@ -0,0 +1,131 @@
(ns legacy.status-im.bootnodes.core
(:require
[clojure.string :as string]
[legacy.status-im.multiaccounts.update.core :as multiaccounts.update]
[re-frame.core :as re-frame]
[status-im.navigation.events :as navigation]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(def address-regex #"enode://[a-zA-Z0-9]+:?(.*)\@\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b:(\d{1,5})")
(defn valid-address?
[address]
(re-matches address-regex address))
(defn- build
[id bootnode-name address chain]
{:address address
:chain chain
:id (string/replace id "-" "")
:name bootnode-name})
(rf/defn fetch
[cofx id]
(let [network (get-in cofx [:db :networks/current-network])]
(get-in cofx [:db :profile/profile :custom-bootnodes network id])))
(rf/defn set-input
{:events [:bootnodes.ui/input-changed]}
[{:keys [db]} input-key value]
{:db (update
db
:bootnodes/manage
assoc
input-key
{:value value
:error (case input-key
:id false
:name (string/blank? value)
:url (not (valid-address? value)))})})
(rf/defn edit
{:events [:bootnodes.ui/add-bootnode-pressed]}
[{:keys [db] :as cofx} id]
(let [{:keys [id
address
name]}
(fetch cofx id)
fxs (rf/merge cofx
(set-input :id id)
(set-input :url (str address))
(set-input :name (str name)))]
(assoc fxs :dispatch [:navigate-to :edit-bootnode])))
(defn custom-bootnodes-in-use?
[{:keys [db]}]
(let [network (:networks/current-network db)]
(get-in db [:profile/profile :custom-bootnodes-enabled? network])))
(rf/defn delete
[{{:profile/keys [profile] :as db} :db :as cofx} id]
(let [network (:networks/current-network db)
new-profile (update-in profile [:custom-bootnodes network] dissoc id)]
(rf/merge cofx
{:db (assoc db :profile/profile new-profile)}
(multiaccounts.update/multiaccount-update
:custom-bootnodes
(:custom-bootnodes new-profile)
{:success-event (when (custom-bootnodes-in-use? cofx)
[:multiaccounts.update.callback/save-settings-success])}))))
(rf/defn upsert
{:events [:bootnodes.ui/save-pressed]
:interceptors [(re-frame/inject-cofx :random-id-generator)]}
[{{:bootnodes/keys [manage] :profile/keys [profile] :as db} :db
random-id-generator :random-id-generator
:as cofx}]
(let [{:keys [name id url]} manage
network (:networks/current-network db)
bootnode (build
(or (:value id) (random-id-generator))
(:value name)
(:value url)
network)
new-bootnodes (assoc-in
(:custom-bootnodes profile)
[network (:id bootnode)]
bootnode)]
(rf/merge cofx
{:db (dissoc db :bootnodes/manage)
:dispatch [:navigate-back]}
(multiaccounts.update/multiaccount-update
:custom-bootnodes
new-bootnodes
{:success-event (when (custom-bootnodes-in-use? cofx)
[:multiaccounts.update.callback/save-settings-success])}))))
(rf/defn toggle-custom-bootnodes
{:events [:bootnodes.ui/custom-bootnodes-switch-toggled]}
[{:keys [db] :as cofx} value]
(let [current-network (:networks/current-network db)
bootnodes-settings (get-in db [:profile/profile :custom-bootnodes-enabled?])]
(multiaccounts.update/multiaccount-update
cofx
:custom-bootnodes-enabled?
(assoc bootnodes-settings current-network value)
{:success-event [:multiaccounts.update.callback/save-settings-success]})))
(rf/defn set-bootnodes-from-qr
{:events [:bootnodes.callback/qr-code-scanned]}
[cofx url]
(assoc (set-input cofx :url (string/trim url))
:dispatch
[:navigate-back]))
(rf/defn show-delete-bootnode-confirmation
{:events [:bootnodes.ui/delete-pressed]}
[_ bootnode-id]
{:ui/show-confirmation {:title (i18n/label :t/delete-bootnode-title)
:content (i18n/label :t/delete-bootnode-are-you-sure)
:confirm-button-text (i18n/label :t/delete-bootnode)
:on-accept #(re-frame/dispatch [:bootnodes.ui/delete-confirmed
bootnode-id])}})
(rf/defn delete-bootnode
{:events [:bootnodes.ui/delete-confirmed]}
[cofx bootnode-id]
(rf/merge cofx
(delete bootnode-id)
(navigation/navigate-back)))
@@ -0,0 +1,180 @@
(ns legacy.status-im.bootnodes.core-test
(:require
[cljs.test :refer-macros [deftest is testing]]
[legacy.status-im.bootnodes.core :as model]))
(def bootnode-id
"1da276e34126e93babf24ec88aac1a7602b4cbb2e11b0961d0ab5e989ca9c261aa7f7c1c85f15550a5f1e5a5ca2305b53b9280cf5894d5ecf7d257b173136d40")
(def host "167.99.209.61:30504")
(def valid-bootnode-address (str "enode://" bootnode-id "@" host))
(deftest upsert-bootnode
(testing "adding a new bootnode"
(let [new-bootnode {:name {:value "name"}
:url {:value "url"}}
expected {"mainnet_rpc" {"someid" {:name "name"
:address "url"
:chain "mainnet_rpc"
:id "someid"}}}
actual (model/upsert
{:random-id-generator (constantly "some-id")
:db {:bootnodes/manage new-bootnode
:networks/current-network "mainnet_rpc"
:profile/profile {:not-empty
"would throw an error if was empty"}}})]
(is (= expected (get-in actual [:db :profile/profile :custom-bootnodes])))))
(testing "adding an existing bootnode"
(let [new-bootnode {:id {:value "a"}
:name {:value "new-name"}
:url {:value "new-url"}}
expected {"mainnet_rpc" {"a" {:name "new-name"
:address "new-url"
:chain "mainnet_rpc"
:id "a"}}}
actual (model/upsert
{:random-id-generator (constantly "some-id")
:db {:bootnodes/manage new-bootnode
:networks/current-network "mainnet_rpc"
:profile/profile {:custom-bootnodes
{"mainnet_rpc"
{"a" {:name "name"
:address "url"
:chain "mainnet_rpc"
:id "a"}}}}}})]
(is (= expected (get-in actual [:db :profile/profile :custom-bootnodes]))))))
(deftest set-input-bootnode
(testing "it validates names"
(testing "correct name"
(is (= {:db {:bootnodes/manage {:name {:value "value"
:error false}}}}
(model/set-input {:db {}} :name "value"))))
(testing "blank name"
(is (= {:db {:bootnodes/manage {:name {:value ""
:error true}}}}
(model/set-input {:db {}} :name "")))))
(testing "it validates bootnode url"
(testing "correct url"
(is (= {:db {:bootnodes/manage {:url {:value valid-bootnode-address
:error false}}}}
(model/set-input {:db {}} :url valid-bootnode-address))))
(testing "broken url"
(is (= {:db {:bootnodes/manage {:url {:value "broken"
:error true}}}}
(model/set-input {:db {}} :url "broken"))))))
(deftest set-bootnode-from-qr
(testing "correct name"
(is (= {:dispatch [:navigate-back]
:db {:bootnodes/manage {:url {:value valid-bootnode-address
:error false}}}}
(model/set-bootnodes-from-qr {:db {}} (str valid-bootnode-address " "))))))
(deftest edit-bootnode
(let [db {:networks/current-network "mainnet_rpc"
:profile/profile
{:custom-bootnodes
{"mainnet_rpc"
{"a" {:id "a"
:name "name"
:address valid-bootnode-address}}}}}
cofx {:db db}]
(testing "when no id is given"
(let [actual (model/edit cofx nil)]
(testing "it resets :bootnodes/manage"
(is (= {:id {:value nil
:error false}
:url {:value ""
:error true}
:name {:value ""
:error true}}
(-> actual :db :bootnodes/manage))))
(testing "it navigates to edit-bootnode view"
(is (= [:navigate-to :edit-bootnode]
(-> actual :dispatch))))))
(testing "when an id is given"
(testing "when the bootnode is in the list"
(let [actual (model/edit cofx "a")]
(testing "it populates the fields with the correct values"
(is (= {:id {:value "a"
:error false}
:url {:value valid-bootnode-address
:error false}
:name {:value "name"
:error false}}
(-> actual :db :bootnodes/manage))))
(testing "it navigates to edit-bootnode view"
(is (= [:navigate-to :edit-bootnode]
(-> actual :dispatch))))))
(testing "when the bootnode is not in the list"
(let [actual (model/edit cofx "not-existing")]
(testing "it populates the fields with the correct values"
(is (= {:id {:value nil
:error false}
:url {:value ""
:error true}
:name {:value ""
:error true}}
(-> actual :db :bootnodes/manage))))
(testing "it navigates to edit-bootnode view"
(is (= [:navigate-to :edit-bootnode]
(-> actual :dispatch)))))))))
(deftest fetch-bootnode
(testing "it fetches the bootnode from the db"
(let [cofx {:db {:networks/current-network "mainnet_rpc"
:profile/profile {:custom-bootnodes
{"mainnet_rpc"
{"a" {:id "a"
:name "name"
:address
"enode://old-id:old-password@url:port"}}}}}}]
(is (model/fetch cofx "a")))))
(deftest custom-bootnodes-in-use?
(testing "is on the same network"
(testing "it returns false when not enabled"
(is (not (model/custom-bootnodes-in-use? {:db {:networks/current-network "mainnet_rpc"}}))))
(testing "it returns true when enabled"
(is (model/custom-bootnodes-in-use?
{:db {:networks/current-network "mainnet_rpc"
:profile/profile {:custom-bootnodes-enabled?
{"mainnet_rpc" true}}}}))))
(testing "is on a different network"
(testing "it returns false when not enabled"
(is (not (model/custom-bootnodes-in-use? {:db {:networks/current-network "goerli_rpc"}}))))
(testing "it returns true when enabled"
(is (not (model/custom-bootnodes-in-use?
{:db {:networks/current-network "goerli_rpc"
:profile/profile {:custom-bootnodes-enabled?
{"mainnnet_rpc" true}}}}))))))
(deftest delete-bootnode
(testing "non existing bootnode"
(let [cofx {:db {:networks/current-network "mainnet_rpc"
:profile/profile {:custom-bootnodes
{"mainnet_rpc"
{"a" {:id "a"
:name "name"
:address
"enode://old-id:old-password@url:port"}}}
:custom-bootnodes-enabled?
{"mainnnet_rpc" true}}}}
actual (model/delete cofx "b")]
(testing "it does not removes the bootnode"
(is (model/fetch actual "a")))))
(testing "existing bootnode"
(let [cofx {:db {:networks/current-network "mainnet_rpc"
:profile/profile {:custom-bootnodes
{"mainnet_rpc"
{"a" {:id "a"
:name "name"
:address
"enode://old-id:old-password@url:port"}}}
:custom-bootnodes-enabled?
{"mainnnet_rpc" true}}}}
actual (model/delete cofx "a")]
(testing "it removes the bootnode"
(is (not (model/fetch actual "a")))))))
+63 -2
View File
@@ -3,10 +3,12 @@
["eth-phishing-detect" :as eth-phishing-detect]
[clojure.string :as string]
[legacy.status-im.bottom-sheet.events :as bottom-sheet]
[legacy.status-im.browser.eip3326 :as eip3326]
[legacy.status-im.browser.permissions :as browser.permissions]
[legacy.status-im.browser.webview-ref :as webview-ref]
[legacy.status-im.ethereum.ens :as ens]
[legacy.status-im.multiaccounts.update.core :as multiaccounts.update]
[legacy.status-im.signing.core :as signing]
[legacy.status-im.ui.components.list-selection :as list-selection]
[legacy.status-im.utils.deprecated-types :as types]
[legacy.status-im.utils.random :as random]
@@ -391,6 +393,64 @@
constants/web3-eth-sign constants/web3-keycard-sign-typed-data}
method))
(rf/defn web3-send-async
[cofx dapp-name {:keys [method params id] :as payload} message-id]
(let [message? (web3-sign-message? method)
dapps-address (get-in cofx [:db :profile/profile :dapps-address])
typed? (and (not= constants/web3-personal-sign method)
(not= constants/web3-eth-sign method))]
(if (or message? (= constants/web3-send-transaction method))
(let [[address data] (cond (and (= method constants/web3-keycard-sign-typed-data)
(not (vector? params)))
;; We don't use signer argument for keycard sign-typed-data
["0x0" params]
message? (normalize-sign-message-params params
typed?)
:else [nil nil])]
(when (or (not message?) (and address data))
(signing/sign cofx
(merge
(if message?
{:message {:address address
:data data
:v4 (= constants/web3-sign-typed-data-v4 method)
:typed? typed?
:pinless? (= method constants/web3-keycard-sign-typed-data)
:from dapps-address}}
{:tx-obj (-> params
first
(update :from #(or % dapps-address))
(dissoc :gasPrice))})
{:on-result [:browser.dapp/transaction-on-result message-id id]
:on-error [:browser.dapp/transaction-on-error message-id]}))))
(cond
(#{"eth_accounts" "eth_coinbase"} method)
(send-to-bridge
cofx
{:type constants/web3-send-async-callback
:messageId message-id
:result {:jsonrpc "2.0"
:id (int id)
:result (if (= method "eth_coinbase") dapps-address [dapps-address])}})
(= method "personal_ecRecover")
{:signing.fx/recover-message {:params {:message (first params)
:signature (second params)}
:on-completed #(re-frame/dispatch
[:browser.callback/call-rpc
{:type constants/web3-send-async-callback
:messageId message-id
:result (types/json->clj %)}])}}
(= method "wallet_switchEthereumChain")
(eip3326/handle-switch-ethereum-chain cofx dapp-name id message-id (first params))
:else
{:browser/call-rpc [payload
#(re-frame/dispatch [:browser.callback/call-rpc
{:type constants/web3-send-async-callback
:messageId message-id
:error %1
:result %2}])]}))))
(rf/defn handle-no-permissions
[cofx {:keys [method id]} message-id]
(if (= method "eth_accounts")
@@ -419,8 +479,9 @@
(rf/defn web3-send-async-read-only
[{:keys [db] :as cofx} dapp-name {:keys [method] :as payload} message-id]
(when (has-permissions? db dapp-name method)
(handle-no-permissions cofx payload message-id)))
(if (has-permissions? db dapp-name method)
(handle-no-permissions cofx payload message-id)
(web3-send-async cofx dapp-name payload message-id)))
(rf/defn handle-scanned-qr-code
{:events [:browser.bridge.callback/qr-code-scanned]}
+38
View File
@@ -0,0 +1,38 @@
;reference https://eips.ethereum.org/EIPS/eip-3085 EIP-3085: Wallet Add Ethereum Chain RPC Method
;(`wallet_addEthereumChain`)
(ns legacy.status-im.browser.eip3085
(:require
[re-frame.core :as re-frame]
[status-im.constants :as constants]
[taoensso.timbre :as log]
[utils.re-frame :as rf]))
(rf/defn send-success-call-to-bridge
{:events [:eip3085/send-success-call-to-bridge]}
[_ id messageId]
{:browser/send-to-bridge {:type constants/web3-send-async-callback
:messageId messageId
:result {:jsonrpc "2.0"
:id (int id)
:result nil}}})
(rf/defn allow-permission
{:events [:eip3085.ui/dapp-permission-allowed]}
[{:keys [db] :as cofx} message-id {:keys [new-networks id]}]
{:db (assoc db :networks/networks new-networks)
:json-rpc/call [{:method "settings_saveSetting"
:params [:networks/networks (vals new-networks)]
:on-success #(re-frame/dispatch [:eip3085/send-success-call-to-bridge cofx id
message-id])
:on-error #(log/error "failed to perform settings_saveSetting" %)}]
:dispatch [:bottom-sheet/hide-old]})
(rf/defn deny-permission
{:events [:eip3085.ui/dapp-permission-denied]}
[_ message-id _]
{:browser/send-to-bridge {:type constants/web3-send-async-callback
:messageId message-id
:error {:code 4001
:message "User rejected the request."}}
:dispatch [:bottom-sheet/hide-old]})
+55
View File
@@ -0,0 +1,55 @@
;reference https://eips.ethereum.org/EIPS/eip-3326 EIP-3326: Wallet Switch Ethereum Chain RPC Method
;(`wallet_switchEthereumChain`)
(ns legacy.status-im.browser.eip3326
(:require
[legacy.status-im.ui.screens.browser.eip3326.sheet :as sheet]
[status-im.constants :as constants]
[utils.ethereum.chain :as chain]
[utils.re-frame :as rf]))
(rf/defn deny-permission
{:events [:eip3326.ui/dapp-permission-denied]}
[{:keys [db] :as cofx} message-id _]
{:browser/send-to-bridge {:type constants/web3-send-async-callback
:messageId message-id
:error {:code 4001
:message "User rejected the request."}}
:dispatch [:bottom-sheet/hide-old]})
(rf/defn handle-switch-ethereum-chain
{:events [:eip3326/handle-switch-ethereum-chain]}
[{:keys [db] :as cofx} dapp-name id message-id {:keys [chainId] :as params}]
(let [target-chain-id (js/parseInt chainId 16)
networks (vals (get-in db [:networks/networks]))
exist-chain-ids (set (map chain/network->chain-id networks))
current-chain-id (chain/chain-id db)]
(if (exist-chain-ids target-chain-id)
(if (= current-chain-id target-chain-id)
{:browser/send-to-bridge {:type constants/web3-send-async-callback
:messageId message-id
:result {:jsonrpc "2.0"
:id (int id)
:result nil}}}
(let [target-network (first (filter #(= (chain/network->chain-id %1) target-chain-id)
networks))
target-network-id (:id target-network)
current-network (chain/current-network db)
network-from (:name current-network)
network-to (:name target-network)
params (assoc params
:target-network-id target-network-id
:network-from network-from
:network-to network-to)]
{:dispatch [:bottom-sheet/show-sheet-old
{:content (fn []
[sheet/permissions-panel dapp-name message-id params])}]}))
{:browser/send-to-bridge {:type constants/web3-send-async-callback
:messageId message-id
:error
{:code 4902
:message
(str
"Unrecognized chain ID: "
target-chain-id
". Try adding the chain using wallet_addEthereumChain first.")}}})))
+50 -9
View File
@@ -4,7 +4,6 @@
[legacy.status-im.bottom-sheet.events :as bottom-sheet]
legacy.status-im.communities.e2e
[re-frame.core :as re-frame]
[status-im.contexts.shell.activity-center.events :as activity-center]
[status-im.navigation.events :as navigation]
[taoensso.timbre :as log]
[utils.re-frame :as rf]))
@@ -23,10 +22,60 @@
{}
requests))
(defn- fetch-community-id-input
[{:keys [db]}]
(:communities/community-id-input db))
(rf/defn handle-response
[_ response-js]
{:dispatch [:sanitize-messages-and-process-response response-js]})
(rf/defn invite-users
{:events [::invite-people-confirmation-pressed]}
[cofx user-pk contacts]
(let [community-id (fetch-community-id-input cofx)
pks (if (seq user-pk)
(conj contacts user-pk)
contacts)]
(when (seq pks)
{:json-rpc/call [{:method "wakuext_inviteUsersToCommunity"
:params [{:communityId community-id
:users pks}]
:js-response true
:on-success #(re-frame/dispatch [::people-invited %])
:on-error #(do
(log/error "failed to invite-user community" %)
(re-frame/dispatch [::failed-to-invite-people %]))}]})))
(rf/defn share-community
{:events [::share-community-confirmation-pressed]}
[cofx user-pk contacts]
(let [community-id (fetch-community-id-input cofx)
pks (if (seq user-pk)
(conj contacts user-pk)
contacts)]
(when (seq pks)
{:json-rpc/call [{:method "wakuext_shareCommunity"
:params [{:communityId community-id
:users pks}]
:js-response true
:on-success #(re-frame/dispatch [::people-invited %])
:on-error #(do
(log/error "failed to invite-user community" %)
(re-frame/dispatch [::failed-to-share-community %]))}]})))
(re-frame/reg-event-fx :communities/invite-people-pressed
(fn [{:keys [db]} [id]]
{:db (assoc db :communities/community-id-input id)
:fx [[:dispatch [:hide-bottom-sheet]]
[:dispatch [:open-modal :legacy-invite-people-community {:invite? true}]]]}))
(re-frame/reg-event-fx :communities/share-community-pressed
(fn [{:keys [db]} [id]]
{:db (assoc db :communities/community-id-input id)
:fx [[:dispatch [:hide-bottom-sheet]]
[:dispatch [:open-modal :legacy-invite-people-community {}]]]}))
(rf/defn people-invited
{:events [::people-invited]}
[cofx response-js]
@@ -41,14 +90,6 @@
[:sanitize-messages-and-process-response response-js]
[:activity-center.notifications/fetch-unread-count]]}))
(rf/defn member-banned
{:events [::member-banned]}
[cofx response-js]
(rf/merge cofx
(bottom-sheet/hide-bottom-sheet-old)
(handle-response response-js)
(activity-center/notifications-fetch-unread-count)))
(rf/defn member-ban
{:events [::member-ban]}
[cofx community-id public-key]
@@ -2,8 +2,18 @@
(:require
[clojure.set :as set]
[legacy.status-im.data-store.visibility-status-updates :as visibility-status-updates]
[status-im.config :as config]
[utils.ethereum.eip.eip55 :as eip55]))
(defn rpc->networks
[networks]
(reduce (fn [acc {:keys [id] :as network}]
(assoc acc id network))
{}
(if (seq networks)
networks
config/default-networks)))
(defn rpc->visible-tokens
[visible-tokens]
(reduce-kv (fn [acc chain visible-tokens]
@@ -18,14 +28,25 @@
{}
pinned-mailservers))
(defn rpc->custom-bootnodes
[custom-bootnodes]
(reduce-kv (fn [acc chain custom-bootnodes]
(assoc acc (name chain) custom-bootnodes))
{}
custom-bootnodes))
(defn rpc->settings
[settings]
(-> settings
(update :dapps-address eip55/address->checksum)
(update :address eip55/address->checksum)
(update :networks/networks rpc->networks)
(update :networks/current-network #(if (seq %) % config/default-network))
(update :wallet-legacy/visible-tokens rpc->visible-tokens)
(update :pinned-mailservers rpc->pinned-mailservers)
(update :link-previews-enabled-sites set)
(update :custom-bootnodes rpc->custom-bootnodes)
(update :custom-bootnodes-enabled? rpc->custom-bootnodes)
(update :currency keyword)
(visibility-status-updates/<-rpc-settings)
(set/rename-keys {:compressedKey :compressed-key
+10 -1
View File
@@ -2,6 +2,7 @@
(:require
clojure.set
legacy.status-im.backup.core
legacy.status-im.bootnodes.core
legacy.status-im.browser.core
legacy.status-im.browser.permissions
legacy.status-im.chat.models.loading
@@ -12,6 +13,7 @@
legacy.status-im.data-store.visibility-status-updates
legacy.status-im.fleet.core
legacy.status-im.group-chats.core
[legacy.status-im.keycard.core :as keycard]
legacy.status-im.log-level.core
legacy.status-im.mailserver.constants
[legacy.status-im.mailserver.core :as mailserver]
@@ -190,7 +192,14 @@
(not (get db :screens/was-focused-once?))
{:db (assoc db :screens/was-focused-once? true)})
))
#(case view-id
:keycard-settings (keycard/settings-screen-did-load %)
:reset-card (keycard/reset-card-screen-did-load %)
:enter-pin-settings (keycard/enter-pin-screen-did-load %)
:keycard-login-pin (keycard/login-pin-screen-did-load %)
:keycard-authentication-method (keycard/authentication-method-screen-did-load %)
:multiaccounts (keycard/multiaccounts-screen-did-load %)
nil)))
;;TODO :replace by named events
(rf/defn set-event
@@ -0,0 +1,73 @@
(ns legacy.status-im.keycard.backup-key
(:require
[legacy.status-im.ethereum.mnemonic :as mnemonic]
[legacy.status-im.keycard.common :as common]
[legacy.status-im.multiaccounts.recover.core :as multiaccounts.recover]
[legacy.status-im.signing.core :as signing.core]
[legacy.status-im.utils.utils :as utils]
[re-frame.core :as re-frame]
[status-im.navigation.events :as navigation]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(rf/defn backup-card-pressed
{:events [:keycard-settings.ui/backup-card-pressed]}
[{:keys [db] :as cofx} backup-type]
(log/debug "[keycard] start backup")
(rf/merge cofx
{:db (-> db
(assoc-in [:keycard :creating-backup?] backup-type))}
(when (:profile/profile db)
(navigation/navigate-to :my-profile nil))
(navigation/navigate-to :seed-phrase nil)))
(rf/defn recovery-card-pressed
{:events [:keycard-settings.ui/recovery-card-pressed]}
[{:keys [db] :as cofx} show-warning]
(rf/merge cofx
{:db (-> db
;setting pin-retry-counter is a workaround for the way the PIN view
;decides if it should accept PUK or PIN
(assoc-in [:keycard :application-info :pin-retry-counter] 3)
(assoc-in [:keycard :factory-reset-card?] true)
(dissoc :popover/popover))
:hide-popover nil}
(signing.core/discard)
(if show-warning
(utils/show-confirmation
{:title (i18n/label :t/keycard-recover-title)
:content (i18n/label :t/keycard-recover-text)
:confirm-button-text (i18n/label :t/yes)
:cancel-button-text (i18n/label :t/no)
:on-accept #(re-frame/dispatch [:keycard-settings.ui/backup-card-pressed
:recovery-card])
:on-cancel #()})
(backup-card-pressed :recovery-card))))
(rf/defn start-keycard-backup
{:events [::start-keycard-backup]}
[{:keys [db] :as cofx}]
{::multiaccounts.recover/import-multiaccount {:passphrase (-> db
:profile/key-storage
:seed-phrase
mnemonic/sanitize-passphrase)
:password nil
:success-event ::create-backup-card}})
(rf/defn create-backup-card
{:events [::create-backup-card]}
[{:keys [db] :as cofx} root-data derived-data]
(rf/merge cofx
{:db (-> db
(update :intro-wizard
assoc
:root-key root-data
:derived derived-data
:recovering? true
:selected-storage-type :advanced)
(assoc-in [:keycard :flow] :recovery)
(update :profile/key-storage dissoc :seed-phrase))
:dismiss-keyboard nil}
(common/listen-to-hardware-back-button)
(navigation/navigate-to :keycard-onboarding-intro nil)))
+565
View File
@@ -0,0 +1,565 @@
(ns legacy.status-im.keycard.card
(:require
[clojure.string :as string]
[keycard.keycard :as keycard]
[keycard.real-keycard :as real-keycard]
[legacy.status-im.keycard.simulated-keycard :as simulated-keycard]
[legacy.status-im.node.core :as node]
[native-module.core :as native-module]
[re-frame.core :as re-frame]
[status-im.config :as config]
[taoensso.timbre :as log]
[utils.transforms :as types]))
(defonce card
(if config/keycard-test-menu-enabled?
(simulated-keycard/SimulatedKeycard.)
(real-keycard/RealKeycard.)))
(defn check-nfc-support
[]
(log/debug "[keycard] check-nfc-support")
(keycard/check-nfc-support
card
{:on-success
(fn [response]
(log/debug "[keycard response] check-nfc-support")
(re-frame/dispatch
[:keycard.callback/check-nfc-support-success response]))}))
(defn check-nfc-enabled
[]
(log/debug "[keycard] check-nfc-enabled")
(keycard/check-nfc-enabled
card
{:on-success
(fn [response]
(log/debug "[keycard response] check-nfc-enabled")
(re-frame/dispatch
[:keycard.callback/check-nfc-enabled-success response]))}))
(defn open-nfc-settings
[]
(log/debug "[keycard] open-nfc-settings")
(keycard/open-nfc-settings card))
(defn remove-event-listener
[event]
(log/debug "[keycard] remove-event-listener")
(keycard/remove-event-listener card event))
(defn on-card-disconnected
[callback]
(log/debug "[keycard] on-card-disconnected")
(keycard/on-card-disconnected card callback))
(defn on-card-connected
[callback]
(log/debug "[keycard] on-card-connected")
(keycard/on-card-connected card callback))
(defn remove-event-listeners
[]
(log/debug "[keycard] remove-event-listeners")
(keycard/remove-event-listeners card))
(defn register-card-events
[]
(log/debug "[keycard] register-card-events")
(keycard/register-card-events
card
{:on-card-connected
#(re-frame/dispatch [:keycard.callback/on-card-connected])
:on-card-disconnected
#(re-frame/dispatch [:keycard.callback/on-card-disconnected])
:on-nfc-user-cancelled
#(re-frame/dispatch [:keycard.callback/on-nfc-user-cancelled])
:on-nfc-timeout
#(re-frame/dispatch [:keycard.callback/on-nfc-timeout])
:on-nfc-enabled
#(re-frame/dispatch [:keycard.callback/check-nfc-enabled-success true])
:on-nfc-disabled
#(re-frame/dispatch [:keycard.callback/check-nfc-enabled-success false])}))
(defn- error-object->map
[^js object]
{:code (.-code object)
:error (.-message object)})
(defn set-pairings
[pairings]
(log/debug "[keycard] open-nfc-settings")
(keycard/set-pairings card {:pairings pairings}))
(defn get-application-info
[{:keys [on-success] :as args}]
(log/debug "[keycard] get-application-info")
(keycard/get-application-info
card
(merge
args
{:on-success
(fn [response]
(log/debug "[keycard response succ] get-application-info")
(re-frame/dispatch
[:keycard.callback/on-get-application-info-success
response on-success]))
:on-failure
(fn [response]
(log/debug "[keycard response fail] get-application-info")
(re-frame/dispatch
[:keycard.callback/on-get-application-info-error
(error-object->map response)]))})))
(defn factory-reset
[{:keys [on-success] :as args}]
(log/debug "[keycard] factory-reset")
(keycard/factory-reset
card
(merge
args
{:on-success
(fn [response]
(log/debug "[keycard response succ] get-application-info")
(re-frame/dispatch
[:keycard.callback/on-get-application-info-success
response on-success]))
:on-failure
(fn [response]
(log/debug "[keycard response fail] get-application-info")
(re-frame/dispatch
[:keycard.callback/on-get-application-info-error
(error-object->map response)]))})))
(defn install-applet
[]
(log/debug "[keycard] install-applet")
(keycard/install-applet
card
{:on-success
(fn [response]
(log/debug "[keycard response succ] install-applet")
(re-frame/dispatch
[:keycard.callback/on-install-applet-success response]))
:on-failure
(fn [response]
(log/debug "[keycard response fail] install-applet")
(re-frame/dispatch
[:keycard.callback/on-install-applet-error
(error-object->map response)]))}))
(defn init-card
[pin]
(log/debug "[keycard] init-card")
(keycard/init-card
card
{:pin pin
:on-success
(fn [response]
(log/debug "[keycard response succ] init-card")
(re-frame/dispatch
[:keycard.callback/on-init-card-success response]))
:on-failure
(fn [response]
(log/debug "[keycard response fail] init-card")
(re-frame/dispatch
[:keycard.callback/on-init-card-error
(error-object->map response)]))}))
(defn install-applet-and-init-card
[pin]
(log/debug "[keycard] install-applet-and-init-card")
(keycard/install-applet-and-init-card
card
{:pin pin
:on-success
(fn [response]
(log/debug "[keycard response succ] install-applet-and-init-card")
#(re-frame/dispatch
[:keycard.callback/on-install-applet-and-init-card-success
response]))
:on-failure
(fn [response]
(log/debug "[keycard response fail] install-applet-and-init-card")
(re-frame/dispatch
[:keycard.callback/on-install-applet-and-init-card-error
(error-object->map response)]))}))
(defn pair
[args]
(log/debug "[keycard] pair")
(keycard/pair
card
(merge
args
{:on-success
(fn [response]
(log/debug "[keycard response succ] pair")
(re-frame/dispatch
[:keycard.callback/on-pair-success response]))
:on-failure
(fn [response]
(log/debug "[keycard response fail] pair")
(re-frame/dispatch
[:keycard.callback/on-pair-error (error-object->map response)]))})))
(defn generate-and-load-key
[args]
(log/debug "[keycard] generate-and-load-key")
(keycard/generate-and-load-key
card
(merge
args
{:on-success
(fn [response]
(log/debug "[keycard response succ] generate-and-load-key")
(re-frame/dispatch
[:keycard.callback/on-generate-and-load-key-success response]))
:on-failure
(fn [response]
(log/debug "[keycard response fail] generate-and-load-key")
(re-frame/dispatch
[:keycard.callback/on-generate-and-load-key-error
(error-object->map response)]))})))
(defn unblock-pin
[args]
(log/debug "[keycard] unblock-pin")
(keycard/unblock-pin
card
(merge
args
{:on-success
(fn [response]
(log/debug "[keycard response succ] unblock-pin")
(re-frame/dispatch
[:keycard.callback/on-unblock-pin-success response]))
:on-failure
(fn [response]
(log/debug "[keycard response fail] unblock-pin")
(re-frame/dispatch [:keycard.callback/on-unblock-pin-error
(error-object->map response)]))})))
(defn verify-pin
[args]
(log/debug "[keycard] verify-pin")
(keycard/verify-pin
card
(merge
args
{:on-success
(fn [response]
(log/debug "[keycard response succ] verify-pin")
(re-frame/dispatch [:keycard.callback/on-verify-pin-success
response]))
:on-failure
(fn [response]
(log/debug "[keycard response fail] verify-pin")
(re-frame/dispatch
[:keycard.callback/on-verify-pin-error
(error-object->map response)]))})))
(defn change-pin
[args]
(log/debug "[keycard] change-pin")
(keycard/change-pin
card
(merge
args
{:on-success
(fn [response]
(log/debug "[keycard response succ] change-pin")
(re-frame/dispatch
[:keycard.callback/on-change-pin-success response]))
:on-failure
(fn [response]
(log/debug "[keycard response fail] change-pin")
(re-frame/dispatch
[:keycard.callback/on-change-pin-error
(error-object->map response)]))})))
(defn change-puk
[args]
(log/debug "[keycard] change-puk")
(keycard/change-puk
card
(merge
args
{:on-success
(fn [response]
(log/debug "[keycard response succ] change-puk")
(re-frame/dispatch
[:keycard.callback/on-change-puk-success response]))
:on-failure
(fn [response]
(log/debug "[keycard response fail] change-puk")
(re-frame/dispatch
[:keycard.callback/on-change-pin-error
(error-object->map response)]))})))
(defn change-pairing
[args]
(log/debug "[keycard] change-pairing")
(keycard/change-pairing
card
(merge
args
{:on-success
(fn [response]
(log/debug "[keycard response succ] change-pairing")
(re-frame/dispatch
[:keycard.callback/on-change-pairing-success response]))
:on-failure
(fn [response]
(log/debug "[keycard response fail] change-pairing")
(re-frame/dispatch
[:keycard.callback/on-change-pin-error
(error-object->map response)]))})))
(defn unpair
[args]
(log/debug "[keycard] unpair")
(keycard/unpair
card
(merge
args
{:on-success
(fn [response]
(log/debug "[keycard response succ] unpair")
(re-frame/dispatch
[:keycard.callback/on-unpair-success response]))
:on-failure
(fn [response]
(log/debug "[keycard response fail] unpair")
(re-frame/dispatch
[:keycard.callback/on-unpair-error
(error-object->map response)]))})))
(defn delete
[]
(log/debug "[keycard] delete")
(keycard/delete
card
{:on-success
(fn [response]
(log/debug "[keycard response succ] delete")
(re-frame/dispatch
[:keycard.callback/on-delete-success response]))
:on-failure
(fn [response]
(log/debug "[keycard response fail] delete")
(re-frame/dispatch
[:keycard.callback/on-delete-error
(error-object->map response)]))}))
(defn remove-key
[args]
(log/debug "[keycard] remove-key")
(keycard/remove-key
card
(merge
args
{:on-success
(fn [response]
(log/debug "[keycard response succ] remove-key")
(re-frame/dispatch [:keycard.callback/on-remove-key-success
response]))
:on-failure
(fn [response]
(log/debug "[keycard response fail] remove-key")
(re-frame/dispatch [:keycard.callback/on-remove-key-error
(error-object->map response)]))})))
(defn remove-key-with-unpair
[args]
(log/debug "[keycard] remove-key-with-unpair")
(keycard/remove-key-with-unpair
card
(merge
args
{:on-success
(fn [response]
(log/debug "[keycard response succ] remove-key-with-unpair")
(re-frame/dispatch [:keycard.callback/on-remove-key-success
response]))
:on-failure
(fn [response]
(log/debug "[keycard response fail] remove-key-with-unpair")
(re-frame/dispatch [:keycard.callback/on-remove-key-error
(error-object->map response)]))})))
(defn export-key
[args]
(log/debug "[keycard] export-key")
(keycard/export-key
card
(merge
args
{:on-success
(fn [_response]
(log/debug "[keycard response succ] export-key")
(js/alert "feature no longer supported"))
:on-failure
(fn [_response]
(log/debug "[keycard response fail] export-key"))})))
(defn unpair-and-delete
[args]
(log/debug "[keycard] unpair-and-delete")
(keycard/unpair
card
(merge
args
{:on-success
(fn [response]
(log/debug "[keycard response succ] unpair-and-delete")
(re-frame/dispatch [:keycard.callback/on-unpair-and-delete-success
response]))
:on-failure
(fn [response]
(log/debug "[keycard response fail] unpair-and-delete")
(re-frame/dispatch [:keycard.callback/on-unpair-and-delete-error
(error-object->map response)]))})))
(defn import-keys
[{:keys [on-success] :as args}]
(log/debug "[keycard] import-keys")
(keycard/import-keys
card
(assoc
args
:on-success
(fn [response]
(log/debug "[keycard response succ] import-keys")
(re-frame/dispatch
[(or on-success :keycard.callback/on-generate-and-load-key-success)
response]))
:on-failure
(fn [response]
(log/warn "[keycard response fail] import-keys"
(error-object->map response))
(re-frame/dispatch [:keycard.callback/on-get-keys-error
(error-object->map response)])))))
(defn get-keys
[{:keys [on-success] :as args}]
(log/debug "[keycard] get-keys")
(keycard/get-keys
card
(assoc
args
:on-success
(fn [response]
(log/debug "[keycard response succ] get-keys")
(re-frame/dispatch
[(or on-success :keycard.callback/on-get-keys-success)
response]))
:on-failure
(fn [response]
(log/warn "[keycard response fail] get-keys"
(error-object->map response))
(re-frame/dispatch [:keycard.callback/on-get-keys-error
(error-object->map response)])))))
(defn sign
[{:keys [on-success on-failure] :as args}]
(log/debug "[keycard] sign")
(keycard/sign
card
(merge
args
{:on-success
(fn [response]
(log/debug "[keycard response succ] sign")
(if on-success
(on-success response)
(re-frame/dispatch [:keycard.callback/on-sign-success response])))
:on-failure
(fn [response]
(log/debug "[keycard response fail] sign")
(if on-failure
(on-failure response)
(re-frame/dispatch
[:keycard.callback/on-sign-error
(error-object->map response)])))})))
(defn install-cash-applet
[]
(log/debug "[keycard] install-cash-applet")
(keycard/install-cash-applet
card
{:on-success
(fn [response]
(log/debug "[keycard response succ] install-cash-applet")
(re-frame/dispatch
[:keycard.callback/on-install-applet-success response]))
:on-failure
(fn [response]
(log/debug "[keycard response fail] install-cash-applet")
(re-frame/dispatch
[:keycard.callback/on-install-applet-error
(error-object->map response)]))}))
(defn sign-typed-data
[{card-hash :hash}]
(log/debug "[keycard] sign-typed-data")
(keycard/sign-typed-data
card
{:hash card-hash
:on-success
(fn [response]
(log/debug "[keycard response succ] sign-typed-data")
(re-frame/dispatch [:keycard.callback/on-sign-success
response]))
:on-failure
(fn [response]
(log/debug "[keycard response fail] sign-typed-data")
(re-frame/dispatch
[:keycard.callback/on-sign-error
(error-object->map response)]))}))
(defn save-multiaccount-and-login
[_])
(defn login
[{:keys [key-uid multiaccount-data password] :as args}]
(if config/keycard-test-menu-enabled?
(native-module/login-with-config key-uid multiaccount-data password node/login-node-config)
(native-module/login-with-keycard (assoc args :node-config {:ProcessBackedupMessages false}))))
(def account-password (native-module/sha3 "no password"))
(defn send-transaction-with-signature
[{:keys [transaction signature on-completed]}]
(if config/keycard-test-menu-enabled?
(native-module/send-transaction transaction account-password on-completed)
(native-module/send-transaction-with-signature transaction signature on-completed)))
(defn start-nfc
[args]
(keycard/start-nfc card args))
(defn stop-nfc
[args]
(keycard/stop-nfc card args))
(defn set-nfc-message
[args]
(keycard/set-nfc-message card args))
(defn delete-multiaccount-before-migration
[{:keys [key-uid on-success on-error]}]
(if config/keycard-test-menu-enabled?
(on-success)
(native-module/delete-multiaccount
key-uid
(fn [result]
(let [{:keys [error]} (types/json->clj result)]
(if-not (string/blank? error)
(on-error error)
(on-success)))))))
@@ -0,0 +1,216 @@
(ns legacy.status-im.keycard.change-pin
(:require
[legacy.status-im.keycard.common :as common]
[legacy.status-im.keycard.onboarding :as onboarding]
[status-im.navigation.events :as navigation]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[utils.security.core :as security]))
(rf/defn change-credentials-pressed
{:events [:keycard-settings.ui/change-credentials-pressed]}
[{:keys [db] :as cofx} changing]
(rf/merge cofx
{:db
(assoc-in db
[:keycard :pin]
{:enter-step :current
:current []
:puk []
:original []
:confirmation []
:puk-original []
:puk-confirmation []
:status nil
:error-label nil
:on-verified (case changing
:pin :keycard/proceed-to-change-pin
:puk :keycard/proceed-to-change-puk
:pairing :keycard/proceed-to-change-pairing)})}
(common/navigate-to-enter-pin-screen)))
(rf/defn proceed-to-change-pin
{:events [:keycard/proceed-to-change-pin]}
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db (-> db
(assoc-in [:keycard :pin :enter-step] :original)
(assoc-in [:keycard :pin :status] nil))}
(navigation/navigate-replace :enter-pin-settings nil)))
(rf/defn proceed-to-change-puk
{:events [:keycard/proceed-to-change-puk]}
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db (-> db
(assoc-in [:keycard :pin :enter-step] :puk-original)
(assoc-in [:keycard :pin :status] nil))}
(navigation/navigate-replace :enter-pin-settings nil)))
(rf/defn proceed-to-change-pairing
{:events [:keycard/proceed-to-change-pairing]}
[{:keys [db] :as cofx}]
(navigation/navigate-replace cofx :change-pairing-code nil))
(rf/defn discard-pin-change
{:events [::on-cancel]}
[{:keys [db] :as cofx}]
(rf/merge cofx
(common/clear-pin)
(common/hide-connection-sheet)
(if (get-in db [:keycard :pin :puk-restore?])
(navigation/navigate-to :multiaccounts nil)
(navigation/set-stack-root :profile-stack [:my-profile :keycard-settings]))))
(rf/defn change-pin
{:events [:keycard/change-pin]}
[{:keys [db] :as cofx}]
(let [setup-step (get-in db [:keycard :setup-step])]
(log/debug "[keycard] change-pin"
"setup-step"
setup-step)
(if (= setup-step :pin)
(onboarding/load-preparing-screen cofx)
(common/show-connection-sheet
cofx
{:sheet-options {:on-cancel [::on-cancel]}
:on-card-connected :keycard/change-pin
:handler
(fn [{:keys [db] :as cofx}]
(let [new-pin (common/vector->string
(get-in db [:keycard :pin :original]))
current-pin (common/vector->string
(get-in db [:keycard :pin :current]))]
(rf/merge
cofx
{:db (assoc-in db [:keycard :pin :status] :verifying)
:keycard/change-pin
{:new-pin new-pin
:current-pin current-pin}})))}))))
(rf/defn change-puk
{:events [:keycard/change-puk]}
[{:keys [db] :as cofx}]
(common/show-connection-sheet
cofx
{:sheet-options {:on-cancel [::on-cancel]}
:on-card-connected :keycard/change-puk
:handler
(fn [{:keys [db] :as cofx}]
(let [puk (common/vector->string
(get-in db [:keycard :pin :puk-original]))
pin (common/vector->string
(get-in db [:keycard :pin :current]))]
(rf/merge
cofx
{:db (assoc-in db [:keycard :pin :status] :verifying)
:keycard/change-puk {:puk puk
:pin pin}})))}))
(rf/defn change-pairing
{:events [:keycard/change-pairing]}
[{:keys [db] :as cofx}]
(common/show-connection-sheet
cofx
{:sheet-options {:on-cancel [::on-cancel]}
:on-card-connected :keycard/change-pairing
:handler
(fn [{:keys [db] :as cofx}]
(let [pairing (get-in db [:keycard :pin :pairing-code])
pin (common/vector->string
(get-in db [:keycard :pin :current]))]
(rf/merge
cofx
{:db (assoc-in db [:keycard :pin :status] :verifying)
:keycard/change-pairing {:pairing pairing
:pin pin}})))}))
(rf/defn change-pairing-code
{:events [:keycard/change-pairing-code]}
[{:keys [db] :as cofx} pairing]
(rf/merge
cofx
{:db (assoc-in db [:keycard :pin :pairing-code] (security/unmask pairing))}
(change-pairing)))
(rf/defn on-change-pin-success
{:events [:keycard.callback/on-change-pin-success]}
[{:keys [db] :as cofx}]
(let [pin (get-in db [:keycard :pin :original])
puk-restore? (get-in db [:keycard :pin :puk-restore?])]
(rf/merge cofx
{:db (assoc-in db
[:keycard :pin]
{:status nil
:login pin
:confirmation []
:error-label nil})
:effects.utils/show-popup {:title ""
:content (i18n/label :t/pin-changed)}}
(common/hide-connection-sheet)
(if puk-restore?
(navigation/navigate-to :multiaccounts nil)
(navigation/set-stack-root :profile-stack [:my-profile :keycard-settings]))
(when (:profile/login db)
(common/get-keys-from-keycard)))))
(rf/defn on-change-puk-success
{:events [:keycard.callback/on-change-puk-success]}
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db (assoc-in db
[:keycard :pin]
{:status nil
:puk-original []
:puk-confirmation []
:error-label nil})
:effects.utils/show-popup {:title ""
:content (i18n/label :t/puk-changed)}}
(common/hide-connection-sheet)
(navigation/set-stack-root :profile-stack [:my-profile :keycard-settings])))
(rf/defn on-change-pairing-success
{:events [:keycard.callback/on-change-pairing-success]}
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db (assoc-in db
[:keycard :pin]
{:status nil
:pairing-code nil
:error-label nil})
:effects.utils/show-popup {:title ""
:content (i18n/label :t/pairing-changed)}}
(common/hide-connection-sheet)
(navigation/set-stack-root :profile-stack [:my-profile :keycard-settings])))
(rf/defn on-change-pin-error
{:events [:keycard.callback/on-change-pin-error]}
[{:keys [db] :as cofx} error]
(log/debug "[keycard] change pin error" error)
(let [tag-was-lost? (common/tag-lost? (:error error))
pin-retries (common/pin-retries (:error error))]
(rf/merge
cofx
(if tag-was-lost?
(rf/merge cofx
{:db (assoc-in db [:keycard :pin :status] nil)}
(common/set-on-card-connected :keycard/change-pin))
(if-not (nil? pin-retries)
(rf/merge cofx
{:db (-> db
(assoc-in [:keycard :application-info :pin-retry-counter] pin-retries)
(update-in [:keycard :pin]
assoc
:status :error
:enter-step :current
:puk []
:current []
:original []
:confirmation []
:sign []
:error-label :t/pin-mismatch))}
(when (zero? pin-retries) (common/frozen-keycard-popup))
(navigation/navigate-to :enter-pin-settings nil))
(common/show-wrong-keycard-alert))))))
+560
View File
@@ -0,0 +1,560 @@
(ns legacy.status-im.keycard.common
(:require
[clojure.string :as string]
[legacy.status-im.bottom-sheet.events :as bottom-sheet]
[legacy.status-im.keycard.nfc :as nfc]
[legacy.status-im.popover.core :as popover]
[legacy.status-im.utils.deprecated-types :as types]
[legacy.status-im.utils.keychain.core :as keychain]
[re-frame.core :as re-frame]
[react-native.platform :as platform]
[status-im.navigation.events :as navigation]
[taoensso.timbre :as log]
[utils.address :as address]
[utils.datetime :as datetime]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(def default-pin "000000")
(def pin-mismatch-error #"Unexpected error SW, 0x63C(\d+)|wrongPIN\(retryCounter: (\d+)\)")
(defn pin-retries
[error]
(when-let [matched-error (re-matches pin-mismatch-error error)]
(js/parseInt (second (filter some? matched-error)))))
(rf/defn dispatch-event
[_ event]
{:dispatch-n [[event]]})
(defn vector->string
"Converts numbers stored in vector into string,
e.g. [1 2 3 4 5 6] -> \"123456\""
[v]
(apply str v))
(defn get-card-state
[{:keys [has-master-key?
applet-installed?
initialized?
free-pairing-slots
paired?]}]
(cond
(not applet-installed?)
:blank
(not initialized?)
:pre-init
(not has-master-key?)
:init
has-master-key?
:profile/profile
(and (not paired?)
(zero? free-pairing-slots))
:no-pairing-slots))
(defn tag-lost?
[error]
(or
(= error "Tag was lost.")
(= error "NFCError:100")
(re-matches #".*NFCError:100.*" error)))
(defn find-multiaccount-by-keycard-instance-uid
[db keycard-instance-uid]
(when keycard-instance-uid
(->> (:profile/profiles-overview db)
vals
(filter #(= keycard-instance-uid (:keycard-instance-uid %)))
first)))
(defn find-multiaccount-by-key-uid
[db key-uid]
(when key-uid
(->> (:profile/profiles-overview db)
vals
(filter #(= (address/normalized-hex key-uid) (:key-uid %)))
first)))
(defn get-pairing
([db]
(get-pairing db (get-in db [:keycard :application-info :key-uid])))
([db key-uid]
(or
(get-in db [:profile/profile :keycard-pairing])
(get-in db [:keycard :secrets :pairing])
(when key-uid
(:keycard-pairing
(find-multiaccount-by-key-uid db key-uid))))))
(re-frame/reg-fx
:keycard/set-nfc-supported
(fn [supported?]
(nfc/set-nfc-supported? supported?)))
(rf/defn listen-to-hardware-back-button
[{:keys [db]}]
(when-not (get-in db [:keycard :back-button-listener])
{:keycard/listen-to-hardware-back-button nil}))
(rf/defn remove-listener-to-hardware-back-button
[{:keys [db]}]
(when-let [listener (get-in db [:keycard :back-button-listener])]
{:keycard/remove-listener-to-hardware-back-button listener}))
(rf/defn set-on-card-connected
[{:keys [db]} on-connect]
(log/debug "[keycard] set-on-card-connected" on-connect)
{:db (-> db
(assoc-in [:keycard :on-card-connected] on-connect)
(assoc-in [:keycard :last-on-card-connected] nil))})
(rf/defn stash-on-card-connected
[{:keys [db]}]
(let [on-connect (get-in db [:keycard :on-card-connected])]
(log/debug "[keycard] stash-on-card-connected" on-connect)
{:db (-> db
(assoc-in [:keycard :last-on-card-connected] on-connect)
(assoc-in [:keycard :on-card-connected] nil))}))
(rf/defn restore-on-card-connected
[{:keys [db]}]
(let [on-connect (or
(get-in db [:keycard :on-card-connected])
(get-in db [:keycard :last-on-card-connected]))]
(log/debug "[keycard] restore-on-card-connected" on-connect)
{:db (-> db
(assoc-in [:keycard :on-card-connected] on-connect)
(assoc-in [:keycard :last-on-card-connect] nil))}))
(rf/defn clear-on-card-connected
[{:keys [db]}]
(log/debug "[keycard] clear-on-card-connected")
{:db (-> db
(assoc-in [:keycard :on-card-connected] nil)
(assoc-in [:keycard :last-on-card-connected] nil))})
(rf/defn set-on-card-read
[{:keys [db]} on-connect]
(log/debug "[keycard] set-on-card-read" on-connect)
{:db (-> db
(assoc-in [:keycard :on-card-read] on-connect)
(assoc-in [:keycard :last-on-card-read] nil))})
(rf/defn stash-on-card-read
[{:keys [db]}]
(let [on-connect (get-in db [:keycard :on-card-read])]
(log/debug "[keycard] stash-on-card-read" on-connect)
{:db (-> db
(assoc-in [:keycard :last-on-card-read] on-connect)
(assoc-in [:keycard :on-card-read] nil))}))
(rf/defn restore-on-card-read
[{:keys [db]}]
(let [on-connect (or
(get-in db [:keycard :on-card-read])
(get-in db [:keycard :last-on-card-read]))]
(log/debug "[keycard] restore-on-card-read" on-connect)
{:db (-> db
(assoc-in [:keycard :on-card-read] on-connect)
(assoc-in [:keycard :last-on-card-connect] nil))}))
(rf/defn clear-on-card-read
[{:keys [db]}]
(log/debug "[keycard] clear-on-card-read")
{:db (-> db
(assoc-in [:keycard :on-card-read] nil)
(assoc-in [:keycard :last-on-card-read] nil))})
(rf/defn show-connection-sheet-component
[{:keys [db] :as cofx}
{:keys [on-card-connected on-card-read handler]
{:keys [on-cancel]
:or {on-cancel [::cancel-sheet-confirm]}}
:sheet-options}]
(assert (keyword? on-card-connected))
(assert (fn? handler))
(let [connected? (get-in db [:keycard :card-connected?])]
(log/debug "[keycard] show-sheet-with-connection-check"
"card-connected?"
connected?)
(rf/merge
cofx
(bottom-sheet/show-bottom-sheet-old
{:view {:transparent platform/ios?
:show-handle? false
:backdrop-dismiss? false
:disable-drag? true
:back-button-cancel false}})
(when on-card-read
(set-on-card-read on-card-read))
(set-on-card-connected on-card-connected)
(when connected?
(stash-on-card-connected))
(when connected?
handler))))
(rf/defn show-connection-sheet
[{:keys [db] :as cofx} args]
(let [nfc-running? (get-in db [:keycard :nfc-running?])]
(log/debug "show connection; already running?" nfc-running?)
(if nfc-running?
(show-connection-sheet-component cofx args)
{:keycard/start-nfc-and-show-connection-sheet args})))
(rf/defn on-nfc-ready-for-sheet
{:events [:keycard.callback/show-connection-sheet]}
[cofx args]
(log/debug "on-nfc-ready-for-sheet")
(show-connection-sheet-component cofx args))
(rf/defn hide-connection-sheet-component
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db (assoc-in db [:keycard :card-read-in-progress?] false)}
(restore-on-card-connected)
(restore-on-card-read)
(bottom-sheet/hide-bottom-sheet-old)))
(rf/defn hide-connection-sheet
[cofx]
(log/debug "hide-connection-sheet")
{:keycard/stop-nfc-and-hide-connection-sheet nil})
(rf/defn on-nfc-ready-to-close-sheet
{:events [:keycard.callback/hide-connection-sheet]}
[cofx]
(log/debug "on-nfc-ready-to-close-sheet")
(hide-connection-sheet-component cofx))
(rf/defn clear-pin
[{:keys [db] :as cofx}]
(rf/merge
cofx
{:db (assoc-in db
[:keycard :pin]
{:status nil
:login (get-in db [:keycard :pin :original])
:export-key []
:sign []
:puk []
:current []
:original []
:confirmation []
:error-label nil
:on-verified (get-in db [:keycard :pin :on-verified])
:on-verified-failure (get-in db [:keycard :pin :on-verified])})}))
(rf/defn cancel-sheet-confirm
{:events [::cancel-sheet-confirm
:keycard/back-button-pressed]}
[{:keys [db] :as cofx}]
(when-not (get-in db [:keycard :card-connected?])
(rf/merge cofx
(hide-connection-sheet)
(clear-pin))))
(rf/defn cancel-sheet
{:events [::cancel-sheet]}
[_]
{:ui/show-confirmation {:title (i18n/label :t/keycard-cancel-setup-title)
:content (i18n/label :t/keycard-cancel-setup-text)
:confirm-button-text (i18n/label :t/yes)
:cancel-button-text (i18n/label :t/no)
:on-accept #(re-frame/dispatch [::cancel-sheet-confirm])
:on-cancel #()}})
(rf/defn on-add-listener-to-hardware-back-button
"Adds listener to hardware back button on Android.
During keycard setup we show user a warning that setup will be cancelled
when back button pressed. This prevents user from going back during setup
flow as some of the actions changing keycard step could not be repeated."
{:events [:keycard/add-listener-to-hardware-back-button]}
[{:keys [db]} listener]
{:db (assoc-in db [:keycard :back-button-listener] listener)})
(rf/defn show-wrong-keycard-alert
[_]
(log/debug "show-wrong-keycard-alert")
{:effects.utils/show-popup {:title (i18n/label :t/wrong-card)
:content (i18n/label :t/wrong-card-text)}})
(rf/defn unauthorized-operation
[cofx]
(rf/merge cofx
{:effects.utils/show-popup {:title ""
:content (i18n/label :t/keycard-unauthorized-operation)}}
(clear-on-card-connected)
(navigation/set-stack-root :profile-stack [:my-profile :keycard-settings])))
(rf/defn navigate-to-enter-pin-screen
{:events [:keycard/navigate-to-enter-pin-screen]}
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db (assoc-in db [:keycard :pin :current] [])}
(navigation/navigate-to :enter-pin-settings nil)))
(defn- tag-lost-exception?
[code error]
(or
(= code "android.nfc.TagLostException")
(= error "Tag was lost.")
(= error "NFCError:100")))
(rf/defn process-error
[{:keys [db]} code error]
(when-not (tag-lost-exception? code error)
{:db (assoc-in db [:keycard :setup-step] :error)}))
(rf/defn get-keys-from-keycard
[{:keys [db]}]
(let [key-uid (get-in db [:profile/login :key-uid])
pin (string/join (get-in db [:keycard :pin :login]))]
(log/debug "[keycard] get-keys-from-keycard"
", not empty pin:"
(boolean (seq pin)))
(when (seq pin)
{:db (assoc-in db [:keycard :pin :status] :verifying)
:keycard/get-keys {:pin pin}})))
(rf/defn on-get-keys-success
{:events [:keycard.callback/on-get-keys-success]}
[{:keys [db] :as cofx} data]
(let [{:keys [key-uid encryption-public-key whisper-private-key]
:as account-data}
(js->clj data :keywordize-keys true)
{:keys [name]} (get-in db [:profile/profiles-overview key-uid])
key-uid (get-in db [:keycard :application-info :key-uid])
multiaccount-data (types/clj->json {:name name
:key-uid key-uid})
save-keys? (get-in db [:profile/login :save-password?])]
(rf/merge cofx
{:db
(-> db
(assoc-in [:keycard :pin :status] nil)
(assoc-in [:keycard :pin :login] [])
(update-in [:keycard :application-info]
assoc
:puk-retry-counter 5
:pin-retry-counter 3)
(assoc-in [:keycard :profile/profile]
(update account-data :whisper-public-key address/normalized-hex))
(assoc-in [:keycard :flow] nil)
(update :profile/login assoc
:password encryption-public-key
:key-uid key-uid
:name name))
:keycard/login-with-keycard {:multiaccount-data multiaccount-data
:password encryption-public-key
:chat-key whisper-private-key
:key-uid key-uid}}
(when save-keys?
(keychain/save-keycard-keys key-uid encryption-public-key whisper-private-key))
(clear-on-card-connected)
(clear-on-card-read)
(hide-connection-sheet))))
(rf/defn blocked-or-frozen-keycard-popup
[{:keys [db] :as cofx} card-status]
(rf/merge
cofx
{:db (assoc-in db [:keycard :pin :status] card-status)}
(hide-connection-sheet)
; do not try to display the popover if it is already open or
; we are in the login interface (which has a different handling)
(when-not (or (:profile/login db) (:popover/popover db))
(popover/show-popover {:view card-status}))))
(rf/defn blocked-keycard-popup
[cofx]
(blocked-or-frozen-keycard-popup cofx :blocked-card))
(rf/defn frozen-keycard-popup
[cofx]
(blocked-or-frozen-keycard-popup cofx :frozen-card))
(rf/defn on-get-keys-error
{:events [:keycard.callback/on-get-keys-error]}
[{:keys [db] :as cofx} error]
(log/debug "[keycard] get keys error: " error)
(let [tag-was-lost? (tag-lost? (:error error))
key-uid (get-in db [:keycard :application-info :key-uid])
flow (get-in db [:keycard :flow])
pin-retries-count (pin-retries (:error error))]
(if tag-was-lost?
{:db (assoc-in db [:keycard :pin :status] nil)}
(if-not (nil? pin-retries-count)
(rf/merge
cofx
{:db (-> db
(assoc-in [:keycard :application-info :pin-retry-counter] pin-retries-count)
(update-in [:keycard :pin]
assoc
:status :error
:login []
:import-multiaccount []
:error-label :t/pin-mismatch))}
(hide-connection-sheet)
(when (zero? pin-retries-count) (frozen-keycard-popup)))
(show-wrong-keycard-alert)))))
(rf/defn factory-reset
{:events [:keycard/factory-reset]}
[{:keys [db]} on-card-read]
(log/debug "[keycard] factory-reset")
{:db (update db :keycard dissoc :factory-reset-card?)
:keycard/factory-reset {:on-success on-card-read}})
;; Get application info
(rf/defn update-pairings
[{:keys [db]} instance-uid pairing]
(let [paired-on (datetime/timestamp)
pairings (-> (get-in db [:keycard :pairings])
(assoc instance-uid {:pairing pairing :paired-on paired-on}))]
{:keycard/persist-pairings pairings
:db (assoc-in db [:keycard :pairings] pairings)}))
(rf/defn get-application-info
{:events [:keycard/get-application-info]}
[{:keys [db]} on-card-read]
(log/debug "[keycard] get-application-info")
{:keycard/get-application-info {:on-success on-card-read}})
(rf/defn on-get-application-info-success
{:events [:keycard.callback/on-get-application-info-success]}
[{:keys [db] :as cofx} info on-success]
(let [{:keys [pin-retry-counter puk-retry-counter instance-uid new-pairing]} info
view-id (:view-id db)
{:keys [on-card-read]} (:keycard db)
on-success' (or on-success
on-card-read)
enter-step (get-in db
[:keycard :pin
:enter-step])]
(log/debug "[keycard] on-get-application-info-success"
"on-success" on-success'
"pin-retry-counter" pin-retry-counter
"puk-retry-counter" puk-retry-counter)
(rf/merge
cofx
{:db (-> db
(assoc-in [:keycard :pin :enter-step] enter-step)
(assoc-in [:keycard :application-info] info)
(assoc-in [:keycard :application-info :applet-installed?] true)
(assoc-in [:keycard :application-info-error] nil))}
(stash-on-card-read)
(when new-pairing
(update-pairings instance-uid new-pairing))
(when (and (zero? pin-retry-counter)
(pos? puk-retry-counter)
(not= enter-step :puk))
(frozen-keycard-popup))
(fn [{:keys [db] :as cofx}]
(if (zero? puk-retry-counter)
(blocked-keycard-popup cofx)
(when on-success'
(dispatch-event cofx on-success')))))))
(rf/defn on-get-application-info-error
{:events [:keycard.callback/on-get-application-info-error]}
[{:keys [db] :as cofx} error]
(let [on-card-read (get-in db [:keycard :on-card-read])
on-card-connected (get-in db [:keycard :on-card-connected])
last-on-card-connected (get-in db [:keycard :last-on-card-connected])
login? (= on-card-read :keycard/login-with-keycard)
tag-was-lost? (tag-lost? (:error error))]
(log/debug "[keycard] application info error"
error
on-card-connected
last-on-card-connected)
(when-not tag-was-lost?
(if login?
(rf/merge cofx
(clear-on-card-read)
(navigation/navigate-to :not-keycard nil))
(rf/merge cofx
{:db (assoc-in db [:keycard :application-info-error] error)}
(when (contains?
#{last-on-card-connected on-card-connected}
:keycard/prepare-to-sign)
(show-wrong-keycard-alert))
(when on-card-read
(dispatch-event on-card-read)))))))
(rf/defn on-card-connected
{:events [::on-card-connected]}
[{:keys [db] :as cofx} _]
(let [instance-uid (get-in db [:keycard :application-info :instance-uid])
key-uid (get-in db [:keycard :application-info :key-uid])
should-read-instance-uid? (nil? instance-uid)
on-card-connected (get-in db [:keycard :on-card-connected])
on-card-read (cond
should-read-instance-uid? :keycard/get-application-info
:else (get-in db [:keycard :on-card-read]))]
(log/debug "[keycard] on-card-connected" on-card-connected
"on-card-read" on-card-read)
(when on-card-connected
(rf/merge cofx
{:db (-> db
(assoc-in [:keycard :card-read-in-progress?] (boolean on-card-read)))}
(when on-card-connected
(dispatch-event on-card-connected))
(stash-on-card-connected)
(when (and on-card-read
(nil? on-card-connected))
(get-application-info on-card-read))))))
(rf/defn on-card-disconnected
{:events [::on-card-disconnected]}
[{:keys [db] :as cofx} _]
(log/debug "[keycard] card disconnected")
(rf/merge cofx
{:db (-> db
(assoc-in [:keycard :card-read-in-progress?] false))}
(restore-on-card-connected)
(restore-on-card-read)))
(defn keycard-multiaccount?
[db]
(boolean (get-in db [:profile/profile :keycard-pairing])))
(rf/defn verify-pin
{:events [:keycard/verify-pin]}
[{:keys [db] :as cofx} {:keys [pin-step on-card-connected on-failure on-success]}]
(let [on-success (or on-success
(get-in db [:keycard :pin :on-verified]))
on-failure (or on-failure
(get-in db [:keycard :pin :on-verified-failure]))
pin-step (or pin-step
(get-in db [:keycard :pin :step]))]
(rf/merge
cofx
{:db (update-in db
[:keycard :pin]
assoc
:step pin-step
:on-verified on-success
:on-verified-failure on-failure)}
(show-connection-sheet
{:on-card-connected (or on-card-connected :keycard/verify-pin)
:handler
(fn [{:keys [db] :as cofx}]
(let [pin (vector->string (get-in db [:keycard :pin pin-step]))]
(rf/merge
cofx
{:db (assoc-in db [:keycard :pin :status] :verifying)
:keycard/verify-pin {:pin pin}})))}))))
(rf/defn navigete-to-keycard-settings
{:events [::navigate-to-keycard-settings]}
[cofx]
(navigation/set-stack-root :profile-stack [:my-profile :keycard-settings]))
@@ -0,0 +1,43 @@
(ns legacy.status-im.keycard.common-test
(:require
[cljs.test :refer-macros [deftest is testing]]
[legacy.status-im.keycard.common :as common]))
(deftest test-show-connection-sheet
(testing "the card is not connected yet"
(let [db {:keycard {:card-connected? false}}
res (common/show-connection-sheet-component
{:db db}
{:on-card-connected :do-something
:handler (fn [{:keys [db]}]
{:db (assoc db :some-key :some-value)})})]
(is (= :do-something
(get-in res [:db :keycard :on-card-connected])))
(is (nil? (get-in res [:db :some-key])))
(is (true? (get-in res [:db :bottom-sheet/show?])))))
(testing "the card is connected before the interaction"
(let [db {:keycard {:card-connected? true}}
res (common/show-connection-sheet-component
{:db db}
{:on-card-connected :do-something
:handler (fn [{:keys [db]}]
{:db (assoc db :some-key :some-value)})})]
(is (nil? (get-in res [:db :keycard :on-card-connected])))
(is (= :do-something
(get-in res [:db :keycard :last-on-card-connected])))
(is (= :some-value (get-in res [:db :some-key])))
(is (true? (get-in res [:db :bottom-sheet/show?])))))
(testing "on-card-connected is not specified"
(is
(thrown?
js/Error
(common/show-connection-sheet-component
{:db {}}
{:handler (fn [_])}))))
(testing "handler is not specified"
(is
(thrown?
js/Error
(common/show-connection-sheet-component
{:db {}}
{:on-card-connected :do-something})))))
+688
View File
@@ -0,0 +1,688 @@
(ns legacy.status-im.keycard.core
(:require
legacy.status-im.keycard.backup-key
[legacy.status-im.keycard.card :as card]
[legacy.status-im.keycard.change-pin :as change-pin]
[legacy.status-im.keycard.common :as common]
legacy.status-im.keycard.delete-key
legacy.status-im.keycard.export-key
[legacy.status-im.keycard.login :as login]
[legacy.status-im.keycard.mnemonic :as mnemonic]
[legacy.status-im.keycard.onboarding :as onboarding]
[legacy.status-im.keycard.recovery :as recovery]
[legacy.status-im.keycard.sign :as sign]
legacy.status-im.keycard.unpair
[legacy.status-im.multiaccounts.update.core :as multiaccounts.update]
[re-frame.db]
[status-im.navigation.events :as navigation]
[taoensso.timbre :as log]
[utils.datetime :as datetime]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(rf/defn show-keycard-has-multiaccount-alert
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db (assoc-in db [:keycard :setup-step] nil)
:effects.utils/show-confirmation {:title nil
:content (i18n/label
:t/keycard-has-multiaccount-on-it)
:cancel-button-text ""
:confirm-button-text :t/okay}}))
(rf/defn load-pin-screen
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db (-> db
(assoc-in [:keycard :setup-step] :pin)
(assoc-in [:keycard :pin]
{:enter-step :original
:original []
:confirmation []}))}
(navigation/navigate-to :keycard-onboarding-pin nil)))
(rf/defn load-recovery-pin-screen
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db (-> db
(assoc-in [:keycard :pin]
{:enter-step :import-multiaccount
:import-multiaccount []
:current []}))}
(common/listen-to-hardware-back-button)
(navigation/navigate-replace :keycard-recovery-pin nil)))
(rf/defn load-pairing
[{:keys [db]}]
(let [instance-uid (get-in db [:keycard :application-info :instance-uid])
pairing-data (or (get-in db [:keycard :pairings (keyword instance-uid)])
(get-in db [:keycard :pairings instance-uid]))]
{:db (update-in db [:keycard :secrets] merge pairing-data)}))
(rf/defn proceed-setup-with-initialized-card
[{:keys [db] :as cofx} flow instance-uid paired?]
(log/debug "[keycard] proceed-setup-with-initialized-card"
"instance-uid"
instance-uid)
(if (= flow :import)
(navigation/navigate-to cofx :keycard-recovery-no-key nil)
(if paired?
(rf/merge cofx
(common/listen-to-hardware-back-button)
(when (= flow :create)
(mnemonic/set-mnemonic))
(when (= flow :recovery)
(onboarding/proceed-with-generating-key)))
(if (get-in db [:keycard :secrets :password])
(onboarding/load-pairing-screen cofx)
(recovery/load-pair-screen cofx)))))
(rf/defn navigate-to-keycard-settings
{:events [:profile.ui/keycard-settings-button-pressed]}
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db (-> db
(assoc-in [:keycard :pin :on-verified] nil)
(assoc-in [:keycard :setup-step] nil))}
(common/clear-on-card-connected)
(navigation/navigate-to :keycard-settings nil)))
(rf/defn password-option-pressed
{:eevents [:keycard.ui/password-option-pressed]}
[{:keys [db]}]
(when (= (get-in db [:keycard :flow]) :create)
#())) ;;TODO with v1 flow
(rf/defn settings-screen-did-load
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db (-> db
(assoc-in [:keycard :pin :on-verified] nil)
(assoc-in [:keycard :setup-step] nil))}
(common/clear-on-card-connected)))
(defn reset-card-screen-did-load
[{:keys [db]}]
{:db (assoc-in db [:keycard :reset-card :disabled?] false)})
(defn enter-pin-screen-did-load
[{:keys [db]}]
(let [enter-step (get-in db [:keycard :pin :enter-step])]
{:db (assoc-in db [:keycard :pin enter-step] [])}))
(defn login-pin-screen-did-load
[{:keys [db]}]
(let [enter-step (get-in db [:keycard :pin :enter-step])]
{:db (-> db
(assoc-in [:keycard :pin enter-step] [])
(dissoc :intro-wizard :recovered-account?))}))
(defn multiaccounts-screen-did-load
[{:keys [db]}]
{:db (assoc-in db [:keycard :setup-step] nil)})
(defn authentication-method-screen-did-load
[{:keys [db]}]
{:db (assoc-in db [:keycard :setup-step] nil)})
(rf/defn set-nfc-supported
{:events [:keycard.callback/check-nfc-support-success]}
[_ supported?]
{:keycard/set-nfc-supported supported?})
;; TODO: Should be listener and replace the view in bottom sheet to avoid this
(rf/defn on-check-nfc-enabled-success
{:events [:keycard.callback/check-nfc-enabled-success]}
[{:keys [db]} nfc-enabled?]
(log/debug "[keycard] check-nfc-enabled-success"
"nfc-enabled?"
nfc-enabled?)
{:db (assoc-in db [:keycard :nfc-enabled?] nfc-enabled?)})
(defn- proceed-to-pin-confirmation
[fx]
(assoc-in fx [:db :keycard :pin :enter-step] :confirmation))
(defn- proceed-to-change-puk-confirmation
[fx]
(assoc-in fx [:db :keycard :pin :enter-step] :puk-confirmation))
(defn- proceed-to-pin-reset-confirmation
[fx]
(-> fx
(update-in [:db :keycard :pin] dissoc :reset-confirmation)
(assoc-in [:db :keycard :pin :enter-step] :reset-confirmation)))
(defn- proceed-to-puk-confirmation
[fx]
(assoc-in fx [:db :keycard :pin :enter-step] :puk))
(rf/defn on-unblock-pin-success
{:events [:keycard.callback/on-unblock-pin-success]}
[{:keys [db] :as cofx}]
(let [reset-pin (get-in db [:keycard :pin :reset])]
(rf/merge cofx
{:db
(-> db
(update-in [:keycard :application-info]
assoc
:puk-retry-counter 5
:pin-retry-counter 3)
(update-in [:keycard :pin]
assoc
:status :after-unblocking
:enter-step :login
:login reset-pin
:confirmation []
:puk []
:puk-restore? true
:error-label nil))}
(common/hide-connection-sheet)
(common/clear-on-card-connected)
(common/clear-on-card-read))))
(rf/defn on-unblock-pin-error
{:events [:keycard.callback/on-unblock-pin-error]}
[{:keys [db] :as cofx} error]
(let [tag-was-lost? (common/tag-lost? (:error error))
puk-retries (common/pin-retries (:error error))]
(log/debug "[keycard] unblock pin error" error)
(when-not tag-was-lost?
(rf/merge cofx
{:db
(-> db
(assoc-in [:keycard :application-info :puk-retry-counter] puk-retries)
(update-in [:keycard :pin]
merge
{:status (if (zero? puk-retries) :blocked-card :error)
:error-label :t/puk-mismatch
:enter-step :puk
:puk []}))}
(common/hide-connection-sheet)))))
(rf/defn clear-on-verify-handlers
[{:keys [db]}]
{:db (update-in db
[:keycard :pin]
dissoc
:on-verified-failure
:on-verified)})
(rf/defn on-verify-pin-success
{:events [:keycard.callback/on-verify-pin-success]}
[{:keys [db] :as cofx}]
(let [on-verified (get-in db [:keycard :pin :on-verified])]
(log/debug "[hardwaller] success pin verification. on-verified" on-verified)
(rf/merge cofx
{:db (update-in db
[:keycard :pin]
merge
{:status nil
:error-label nil})}
(common/clear-on-card-connected)
(common/clear-on-card-read)
;; TODO(Ferossgp): Each pin input should handle this event on it's own,
;; now for simplicity do not hide bottom sheet when generating key
;; and exporting key but should be refactored.
(when-not (contains? #{:keycard/generate-and-load-key
:wallet-legacy.accounts/generate-new-keycard-account
:keycard/remove-key-with-unpair
:keycard/unpair-and-delete}
on-verified)
(common/hide-connection-sheet))
(when-not (contains? #{:keycard/unpair
:keycard/generate-and-load-key
:keycard/remove-key-with-unpair
:keycard/unpair-and-delete
:wallet-legacy.accounts/generate-new-keycard-account}
on-verified)
(common/get-application-info nil))
(when on-verified
(common/dispatch-event on-verified))
(clear-on-verify-handlers))))
(rf/defn on-verify-pin-error
{:events [:keycard.callback/on-verify-pin-error]}
[{:keys [db] :as cofx} error]
(let [tag-was-lost? (common/tag-lost? (:error error))
setup? (boolean (get-in db [:keycard :setup-step]))
on-verified-failure (get-in db [:keycard :pin :on-verified-failure])
exporting? (get-in db [:keycard :on-export-success])
pin-retries (common/pin-retries (:error error))]
(log/debug "[keycard] verify pin error" error)
(when-not tag-was-lost?
(if-not (nil? pin-retries)
(rf/merge cofx
{:db (-> db
(assoc-in [:keycard :application-info :pin-retry-counter] pin-retries)
(update-in [:keycard :pin]
assoc
:status :error
:enter-step :current
:puk []
:current []
:original []
:confirmation []
:sign []
:error-label :t/pin-mismatch))}
(common/hide-connection-sheet)
(when (and (not setup?)
(not on-verified-failure))
(when exporting?
(navigation/navigate-back)))
;(navigation/navigate-to :enter-pin-settings nil)))
(when (zero? pin-retries) (common/frozen-keycard-popup))
(when on-verified-failure
(fn [_]
{:utils/dispatch-later
[{:dispatch [on-verified-failure]
:ms 200}]}))
#_(clear-on-verify-handlers))
(rf/merge cofx
(common/hide-connection-sheet)
(common/show-wrong-keycard-alert)
(clear-on-verify-handlers))))))
(rf/defn unblock-pin
{:events [:keycard/unblock-pin]}
[cofx]
(common/show-connection-sheet
cofx
{:on-card-connected :keycard/unblock-pin
:handler
(fn [{:keys [db]}]
(let [puk (common/vector->string (get-in db [:keycard :pin :puk]))
pin (common/vector->string (get-in db [:keycard :pin :reset]))
key-uid (get-in db [:keycard :application-info :key-uid])]
{:db (assoc-in db [:keycard :pin :status] :verifying)
:keycard/unblock-pin
{:puk puk
:new-pin pin}}))}))
(def pin-code-length 6)
(def puk-code-length 12)
(rf/defn handle-pin-input
[{:keys [db]} enter-step]
(let [numbers-entered (count (get-in db [:keycard :pin enter-step]))]
(when (or (= numbers-entered pin-code-length)
(= numbers-entered puk-code-length))
{:dispatch [:keycard/process-pin-input]})))
(rf/defn update-pin
{:events [:keycard.ui/pin-numpad-button-pressed]}
[{:keys [db] :as cofx} number enter-step]
(log/debug "update-pin" enter-step)
(let [numbers-entered (count (get-in db [:keycard :pin enter-step]))
need-update? (if (or (= enter-step :puk)
(= enter-step :puk-original)
(= enter-step :puk-confirmation))
(< numbers-entered puk-code-length)
(< numbers-entered pin-code-length))]
(rf/merge cofx
{:db (cond-> (-> db
(assoc-in [:keycard :pin :enter-step] enter-step)
(assoc-in [:keycard :pin :status] nil))
need-update? (update-in [:keycard :pin enter-step] (fnil conj []) number))}
(when need-update?
(handle-pin-input enter-step)))))
(defn- pin-enter-error
[fx error-label]
(update-in fx
[:db :keycard :pin]
merge
{:status :error
:error-label error-label
:enter-step :original
:original []
:confirmation []}))
(defn- puk-enter-error
[fx error-label]
(update-in fx
[:db :keycard :pin]
merge
{:status :error
:error-label error-label
:enter-step :puk-original
:puk-original []
:puk-confirmation []}))
(defn- pin-reset-error
[fx error-label]
(update-in fx
[:db :keycard :pin]
merge
{:status :error
:error-label error-label
:enter-step :reset
:reset []
:reset-confirmation []}))
; PIN enter steps:
; login - PIN is used to login
; sign - PIN for transaction sign
; current - current PIN to perform actions which require PIN auth
; original - new PIN when user changes it or creates new one
; confirmation - confirmation for new PIN
(rf/defn process-pin-input
{:events [:keycard/process-pin-input]}
[{:keys [db]}]
(let [enter-step (get-in db [:keycard :pin :enter-step])
pin (get-in db [:keycard :pin enter-step])
numbers-entered (count pin)]
(log/debug "[keycard] process-pin-input"
"enter-step" enter-step
"numbers-entered" numbers-entered)
(cond-> {:db (assoc-in db [:keycard :pin :status] nil)}
(and (= enter-step :login)
(= 6 numbers-entered))
(login/proceed-to-login)
(and (= enter-step :original)
(= pin-code-length numbers-entered))
(proceed-to-pin-confirmation)
(and (= enter-step :original)
(= pin-code-length numbers-entered)
(= common/default-pin (common/vector->string pin)))
(pin-enter-error :t/cannot-use-default-pin)
(and (= enter-step :import-multiaccount)
(= pin-code-length numbers-entered))
(recovery/load-recovering-key-screen)
(and (= enter-step :current)
(= pin-code-length numbers-entered))
(common/verify-pin {:pin-step :current})
(and (= enter-step :sign)
(= pin-code-length numbers-entered))
(sign/prepare-to-sign)
(and (= enter-step :puk)
(= puk-code-length numbers-entered))
(unblock-pin)
(and (= enter-step :confirmation)
(= (get-in db [:keycard :pin :original])
(get-in db [:keycard :pin :confirmation])))
(change-pin/change-pin)
(and (= enter-step :confirmation)
(= pin-code-length numbers-entered)
(not= (get-in db [:keycard :pin :original])
(get-in db [:keycard :pin :confirmation])))
(pin-enter-error :t/pin-mismatch)
(and (= enter-step :puk-original)
(= puk-code-length numbers-entered))
(proceed-to-change-puk-confirmation)
(and (= enter-step :puk-confirmation)
(= (get-in db [:keycard :pin :puk-original])
(get-in db [:keycard :pin :puk-confirmation])))
(change-pin/change-puk)
(and (= enter-step :puk-confirmation)
(= puk-code-length numbers-entered)
(not= (get-in db [:keycard :pin :puk-original])
(get-in db [:keycard :pin :puk-confirmation])))
(puk-enter-error :t/puk-mismatch)
(= enter-step :reset)
(proceed-to-pin-reset-confirmation)
(and (= enter-step :reset-confirmation)
(= (get-in db [:keycard :pin :reset])
(get-in db [:keycard :pin :reset-confirmation])))
(proceed-to-puk-confirmation)
(and (= enter-step :reset-confirmation)
(= pin-code-length numbers-entered)
(not= (get-in db [:keycard :pin :reset])
(get-in db [:keycard :pin :reset-confirmation])))
(pin-reset-error :t/pin-mismatch))))
(rf/defn set-multiaccount-pairing
[cofx _ pairing paired-on]
(rf/merge cofx
(multiaccounts.update/multiaccount-update
:keycard-pairing
pairing
{})
(multiaccounts.update/multiaccount-update
:keycard-paired-on
paired-on
{})))
(rf/defn on-retrieve-pairings-success
{:events [:keycard.callback/on-retrieve-pairings-success]}
[{:keys [db]} pairings]
(card/set-pairings pairings)
{:db (assoc-in db [:keycard :pairings] pairings)})
;; When pairing to device has completed, we need to persist pairing data to
;; local storage. That's needed to ensure that during keycard setup
;; keycard won't run out of pairings slots, ie. we don't pair the same
;; card to the same device more than one time. Also, this allows the user to proceed
;; with setup and skip the pairing step if the pairing was already done during a previous
;; unfinished setup.
(rf/defn on-pair-success
{:events [:keycard.callback/on-pair-success]}
[{:keys [db] :as cofx} pairing]
(let [setup-step (get-in db [:keycard :setup-step])
flow (get-in db [:keycard :flow])
instance-uid (get-in db [:keycard :application-info :instance-uid])
multiaccount (common/find-multiaccount-by-keycard-instance-uid db instance-uid)
paired-on (datetime/timestamp)
pairings (-> (get-in db [:keycard :pairings])
(dissoc (keyword instance-uid))
(assoc instance-uid {:pairing pairing :paired-on paired-on}))
next-step (if (= setup-step :pair)
:begin
:card-ready)]
(rf/merge cofx
{:keycard/persist-pairings pairings
:db (-> db
(assoc-in [:keycard :pairings] pairings)
(assoc-in [:keycard :application-info :paired?] true)
(assoc-in [:keycard :setup-step] next-step)
(assoc-in [:keycard :secrets :pairing] pairing)
(assoc-in [:keycard :secrets :paired-on] paired-on))}
(when-not (and (= flow :recovery) (= next-step :card-ready))
(common/hide-connection-sheet))
(when multiaccount
(set-multiaccount-pairing multiaccount pairing paired-on))
(when (= flow :login)
(navigation/navigate-to :multiaccounts nil))
(when (= flow :recovery)
(onboarding/proceed-with-generating-key))
(when (= flow :import)
(load-recovery-pin-screen))
;; TODO: If card is already initialized need to confirm pin only then go to mnenmonic
;; https://github.com/status-im/status-mobile/issues/9451
(when (= flow :create)
(mnemonic/set-mnemonic)))))
(rf/defn on-pair-error
{:events [:keycard.callback/on-pair-error]}
[{:keys [db] :as cofx} {:keys [error code]}]
(log/debug "[keycard] pair error: " error)
(let [setup-step (get-in db [:keycard :setup-step])
tag-was-lost? (common/tag-lost? error)
flow (get-in db [:keycard :flow])]
(log/debug "[keycard] on-pair-error" setup-step "flow:" flow)
(when-not tag-was-lost?
(rf/merge cofx
{:db (assoc-in db [:keycard :setup-error] (i18n/label :t/invalid-pairing-password))}
(common/set-on-card-connected (if (= setup-step :pairing)
:keycard/load-pairing-screen
:keycard/pair))
(common/hide-connection-sheet)
(when (= flow :import)
(navigation/navigate-to :keycard-recovery-pair nil))
(when (not= setup-step :enter-pair-code)
(common/process-error code error))))))
(rf/defn set-setup-step
[{:keys [db]} card-state]
{:db (assoc-in db
[:keycard :setup-step]
(case card-state
:not-paired :pair
:no-pairing-slots :no-slots
:init :card-ready
:profile/profile :import-multiaccount
:begin))})
(rf/defn show-no-keycard-applet-alert
[_]
{:effects.utils/show-confirmation {:title (i18n/label :t/no-keycard-applet-on-card)
:content (i18n/label
:t/keycard-applet-install-instructions)
:cancel-button-text ""
:confirm-button-text :t/okay}})
;; NOTE: Maybe replaced by multiple events based on on flow to make it easier to maintain.
;; Because there are many execution paths it is harder to follow all possible states.
(rf/defn check-card-state
{:events [:keycard/check-card-state]}
[{:keys [db] :as cofx}]
(let [app-info (get-in db [:keycard :application-info])
flow (get-in db [:keycard :flow])
{:keys [instance-uid key-uid paired?]} app-info
card-state (common/get-card-state app-info)]
(log/debug "[keycard] check-card-state"
"card-state" card-state
"flow" flow)
(rf/merge cofx
{:db (assoc-in db [:keycard :card-state] card-state)}
(set-setup-step card-state)
(when paired?
(load-pairing))
(if (and flow
(= card-state :init))
(proceed-setup-with-initialized-card flow instance-uid paired?)
(common/hide-connection-sheet))
(when (= card-state :pre-init)
(if (= flow :import)
(navigation/navigate-to :keycard-recovery-no-key nil)
(fn [cofx]
(rf/merge
cofx
(common/clear-on-card-read)
(load-pin-screen)))))
(when (and (= card-state :profile/profile)
(= flow :import))
(if (common/find-multiaccount-by-key-uid db key-uid)
;; reimplement
;;(multiaccounts.recover/show-existing-multiaccount-alert key-uid)
(if paired?
(load-recovery-pin-screen)
(recovery/load-pair-screen))))
(when (= card-state :blank)
(if (= flow :import)
(navigation/navigate-to :keycard-recovery-no-key nil)
(show-no-keycard-applet-alert)))
(when (and (= card-state :profile/profile)
(#{:create :recovery} flow))
(show-keycard-has-multiaccount-alert)))))
(rf/defn on-card-connected
{:events [:keycard.callback/on-card-connected]}
[{:keys [db]} _]
(log/debug "[keycard] card globally connected")
{:db (assoc-in db [:keycard :card-connected?] true)})
(rf/defn on-card-disconnected
{:events [:keycard.callback/on-card-disconnected]}
[{:keys [db]} _]
(log/debug "[keycard] card disconnected")
{:db (assoc-in db [:keycard :card-connected?] false)})
(rf/defn on-nfc-user-cancelled
{:events [:keycard.callback/on-nfc-user-cancelled]}
[{:keys [db]} _]
(log/debug "[keycard] nfc user cancelled")
{:dispatch [:signing.ui/cancel-is-pressed]})
(rf/defn on-nfc-timeout
{:events [:keycard.callback/on-nfc-timeout]}
[{:keys [db]} _]
(log/debug "[keycard] nfc timeout")
{:db (-> db
(assoc-in [:keycard :nfc-running?] false)
(assoc-in [:keycard :card-connected?] false))
:dispatch-later [{:ms 500 :dispatch [:keycard.ui/start-nfc]}]})
(rf/defn on-register-card-events
{:events [:keycard.callback/on-register-card-events]}
[{:keys [db]} listeners]
{:db (update-in db [:keycard :listeners] merge listeners)})
(rf/defn ui-recovery-phrase-cancel-pressed
{:events [:keycard.ui/recovery-phrase-cancel-pressed]}
[{:keys [db]}]
{:db (assoc-in db [:keycard :setup-step] :recovery-phrase)})
(rf/defn ui-pin-numpad-delete-button-pressed
{:events [:keycard.ui/pin-numpad-delete-button-pressed]}
[{:keys [db]} step]
(when-not (empty? (get-in db [:keycard :pin step]))
{:db (update-in db [:keycard :pin step] pop)}))
(rf/defn start-nfc
{:events [:keycard.ui/start-nfc]}
[cofx]
{:keycard/start-nfc nil})
(rf/defn stop-nfc
{:events [:keycard.ui/stop-nfc]}
[cofx]
{:keycard/stop-nfc nil
:keycard.callback/on-card-disconnected nil})
(rf/defn start-nfc-success
{:events [:keycard.callback/start-nfc-success]}
[{:keys [db]} _]
(log/debug "[keycard] nfc started success")
{:db (assoc-in db [:keycard :nfc-running?] true)})
(rf/defn start-nfc-failure
{:events [:keycard.callback/start-nfc-failure]}
[{:keys [db]} _]
(log/debug "[keycard] nfc failed starting")) ;; leave current value on :nfc-running
(rf/defn stop-nfc-success
{:events [:keycard.callback/stop-nfc-success]}
[{:keys [db]} _]
(log/debug "[keycard] nfc stopped success")
(log/debug "[keycard] setting card-connected? and nfc-running? to false")
{:db (-> db
(assoc-in [:keycard :nfc-running?] false)
(assoc-in [:keycard :card-connected?] false))})
(rf/defn stop-nfc-failure
{:events [:keycard.callback/stop-nfc-failure]}
[{:keys [db]} _]
(log/debug "[keycard] nfc failed stopping")) ;; leave current value on :nfc-running
(rf/defn init
{:events [:keycard/init]}
[_]
{:keycard/register-card-events nil
:keycard/check-nfc-support nil
:keycard/check-nfc-enabled nil
:keycard/retrieve-pairings nil})
@@ -0,0 +1,77 @@
(ns legacy.status-im.keycard.core-test)
;;TODO re-enable after fixing keycard flow
#_(deftest process-pin-input
(testing "start entering PIN"
(is (= {:db {:keycard {:pin {:original [1]
:confirmation []
:status nil
:enter-step :original}}}}
(keycard/process-pin-input {:db {:keycard {:pin {:original [1]
:confirmation []
:enter-step :original}}}}))))
(testing "first 6 numbers entered"
(is (= {:db {:keycard {:pin {:original [1 2 3 4 5 6]
:confirmation []
:status nil
:enter-step :confirmation}}}}
(keycard/process-pin-input {:db {:keycard {:pin {:original [1 2 3 4 5 6]
:confirmation []
:enter-step :original}}}}))))
(testing "confirmation entered"
(is (= {:db {:keycard {:pin {:original [1 2 3 4 5 6]
:confirmation [1 2 3 4 5 6]
:current [1 1 1 1 1 1]
:enter-step :confirmation
:status :verifying}
:card-connected? true}}
:keycard/change-pin {:new-pin "123456"
:current-pin "111111"
:pairing nil}}
(keycard/process-pin-input {:db {:keycard {:pin {:original [1 2 3 4 5 6]
:confirmation [1 2 3 4 5 6]
:current [1 1 1 1 1 1]
:enter-step :confirmation}
:card-connected? true}}}))))
(testing "confirmation doesn't match"
(is (= {:db {:keycard {:pin {:original []
:confirmation []
:enter-step :original
:error-label :t/pin-mismatch
:status :error}}}}
(keycard/process-pin-input {:db {:keycard {:pin {:original [1 2 3 4 5 6]
:confirmation [1 2 3 4 5 7]
:enter-step :confirmation}}}})))))
#_(deftest on-generate-and-load-key-success
(is
(=
(select-keys
(get-in
(keycard/on-generate-and-load-key-success
{:random-guid-generator (constantly "")
:signing-phrase ""
:status ""
:db {}}
#js
{"whisper-private-key" "f342f3ef17ce86abfa92b912b3a108a4546cfc687fd8e0f02fedf87a60ee4c0f"
"whisper-public-key"
"04de2e21f1642ebee03b9aa4bf1936066124cc89967eaf269544c9b90c539fd5c980166a897d06dd4d3732b38116239f63c89395a8d73eac72881fab802010cb56"
"encryption-public-key"
"04f2a432677a1b7c4f1bb22078135821d1d10fce23422297b5c808a545f2b61cdba38ee7394762172fc6ff5e9e28db7535e555efe2812905ffd4e0c25e82a98ae8"
"whisper-address" "87df2285f90b71221fab6267b7cb37532fedbb1f"
"wallet-address" "7e92236392a850980d00d0cd2a4b92886bd7fe7b"})
[:db :keycard :profile/profile])
[:whisper-private-key
:whisper-public-key
:encryption-public-key
:wallet-address
:whisper-address]))
{:whisper-private-key "f342f3ef17ce86abfa92b912b3a108a4546cfc687fd8e0f02fedf87a60ee4c0f"
:whisper-public-key
"0x04de2e21f1642ebee03b9aa4bf1936066124cc89967eaf269544c9b90c539fd5c980166a897d06dd4d3732b38116239f63c89395a8d73eac72881fab802010cb56"
:encryption-public-key
"04f2a432677a1b7c4f1bb22078135821d1d10fce23422297b5c808a545f2b61cdba38ee7394762172fc6ff5e9e28db7535e555efe2812905ffd4e0c25e82a98ae8"
:whisper-address "87df2285f90b71221fab6267b7cb37532fedbb1f"
:wallet-address "7e92236392a850980d00d0cd2a4b92886bd7fe7b"}))
@@ -0,0 +1,47 @@
(ns legacy.status-im.keycard.delete-key
(:require
[legacy.status-im.keycard.common :as common]
[status-im.navigation.events :as navigation]
[utils.re-frame :as rf]))
(rf/defn reset-card-pressed
{:events [:keycard-settings.ui/reset-card-pressed]}
[cofx]
(navigation/navigate-to cofx :reset-card nil))
(rf/defn delete-card
[{:keys [db] :as cofx}]
(let [key-uid (get-in db [:keycard :application-info :key-uid])
multiaccount-key-uid (get-in db [:profile/profile :key-uid])]
(if (and key-uid
(= key-uid multiaccount-key-uid))
{:keycard/delete nil}
(common/unauthorized-operation cofx))))
(rf/defn navigate-to-reset-card-screen
{:events [:keycard/navigate-to-reset-card-screen]}
[cofx]
(navigation/navigate-to cofx :reset-card nil))
(rf/defn reset-card-next-button-pressed
{:events [:keycard-settings.ui/reset-card-next-button-pressed]}
[{:keys [db]}]
{:db (assoc-in db [:keycard :reset-card :disabled?] true)
:dispatch [:keycard/proceed-to-reset-card false]})
(rf/defn proceed-to-reset-card
{:events [:keycard/proceed-to-reset-card]}
[{:keys [db] :as cofx} keep-keys-on-keycard?]
(rf/merge cofx
{:db (assoc-in db
[:keycard :pin]
{:enter-step :current
:current []
:puk []
:status nil
:error-label nil
:on-verified (if keep-keys-on-keycard?
:keycard/unpair-and-delete
:keycard/remove-key-with-unpair)})}
(common/set-on-card-connected :keycard/navigate-to-enter-pin-screen)
(common/navigate-to-enter-pin-screen)))
@@ -0,0 +1,13 @@
(ns legacy.status-im.keycard.export-key
(:require
[legacy.status-im.keycard.common :as common]
[utils.re-frame :as rf]))
(rf/defn on-export-key-success
{:events [:keycard.callback/on-export-key-success]}
[{:keys [db] :as cofx} pubkey]
(let [callback-fn (get-in db [:keycard :on-export-success])]
(rf/merge cofx
{:dispatch (callback-fn pubkey)}
(common/clear-pin)
(common/hide-connection-sheet))))
+208
View File
@@ -0,0 +1,208 @@
(ns legacy.status-im.keycard.fx
(:require
["@react-native-async-storage/async-storage" :default AsyncStorage]
["react-native" :refer (BackHandler)]
[legacy.status-im.keycard.card :as card]
[legacy.status-im.utils.deprecated-types :as types]
[re-frame.core :as re-frame]
[taoensso.timbre :as log]))
(re-frame/reg-fx
:keycard/start-nfc
(fn []
(log/debug "fx start-nfc")
(card/start-nfc
{:on-success #(re-frame/dispatch [:keycard.callback/start-nfc-success])
:on-failure #(re-frame/dispatch [:keycard.callback/start-nfc-failure])})))
(re-frame/reg-fx
:keycard/stop-nfc
(fn []
(log/debug "fx stop-nfc")
(card/stop-nfc
{:on-success #(re-frame/dispatch [:keycard.callback/stop-nfc-success])
:on-failure #(re-frame/dispatch [:keycard.callback/stop-nfc-failure])})))
(re-frame/reg-fx
:keycard/set-nfc-message
card/set-nfc-message)
(re-frame/reg-fx
:keycard/start-nfc-and-show-connection-sheet
(fn [args]
(log/debug "fx start-nfc-and-show-connection-sheet")
(card/start-nfc
{:on-success
(fn []
(log/debug "nfc started successfully. next: show-connection-sheet")
(re-frame/dispatch [:keycard.callback/start-nfc-success])
(re-frame/dispatch [:keycard.callback/show-connection-sheet args]))
:on-failure
(fn []
(log/debug "nfc failed star starting. not calling show-connection-sheet")
(re-frame/dispatch [:keycard.callback/start-nfc-failure]))})))
(re-frame/reg-fx
:keycard/stop-nfc-and-hide-connection-sheet
(fn []
(log/debug "fx stop-nfc-and-hide-connection-sheet")
(card/stop-nfc
{:on-success
(fn []
(re-frame/dispatch [:keycard.callback/stop-nfc-success])
(re-frame/dispatch [:keycard.callback/hide-connection-sheet]))
:on-failure
(fn []
(re-frame/dispatch [:keycard.callback/stop-nfc-failure]))})))
(re-frame/reg-fx
:keycard/get-application-info
card/get-application-info)
(re-frame/reg-fx
:keycard/factory-reset
card/factory-reset)
(re-frame/reg-fx
:keycard/check-nfc-support
card/check-nfc-support)
(re-frame/reg-fx
:keycard/check-nfc-enabled
card/check-nfc-enabled)
(re-frame/reg-fx
:keycard/open-nfc-settings
card/open-nfc-settings)
(re-frame/reg-fx
:keycard/install-applet-and-init-card
card/install-applet-and-init-card)
(re-frame/reg-fx
:keycard/init-card
card/init-card)
(re-frame/reg-fx
:keycard/register-card-events
card/register-card-events)
(re-frame/reg-fx
:keycard/remove-event-listeners
card/remove-event-listeners)
(re-frame/reg-fx
:keycard/pair
card/pair)
(re-frame/reg-fx
:keycard/generate-and-load-key
card/generate-and-load-key)
(re-frame/reg-fx
:keycard/unblock-pin
card/unblock-pin)
(re-frame/reg-fx
:keycard/verify-pin
card/verify-pin)
(re-frame/reg-fx
:keycard/change-pin
card/change-pin)
(re-frame/reg-fx
:keycard/change-puk
card/change-puk)
(re-frame/reg-fx
:keycard/change-pairing
card/change-pairing)
(re-frame/reg-fx
:keycard/unpair
card/unpair)
(re-frame/reg-fx
:keycard/delete
card/delete)
(re-frame/reg-fx
:keycard/remove-key
card/remove-key)
(re-frame/reg-fx
:keycard/remove-key-with-unpair
card/remove-key-with-unpair)
(re-frame/reg-fx
:keycard/export-key
card/export-key)
(re-frame/reg-fx
:keycard/unpair-and-delete
card/unpair-and-delete)
(re-frame/reg-fx
:keycard/import-keys
card/import-keys)
(re-frame/reg-fx
:keycard/get-keys
card/get-keys)
(re-frame/reg-fx
:keycard/sign
card/sign)
(re-frame/reg-fx
:keycard/sign-typed-data
card/sign-typed-data)
(re-frame/reg-fx
:keycard/login-with-keycard
card/login)
(re-frame/reg-fx
:send-transaction-with-signature
card/send-transaction-with-signature)
(re-frame/reg-fx
:keycard/persist-pairings
(fn [pairings]
(.. AsyncStorage
(setItem "status-keycard-pairings" (types/serialize pairings)))))
(re-frame/reg-fx
:keycard/retrieve-pairings
(fn []
(.. AsyncStorage
(getItem "status-keycard-pairings")
(then #(re-frame/dispatch [:keycard.callback/on-retrieve-pairings-success
(types/deserialize %)])))))
;; TODO: Should act differently on different views
(re-frame/reg-fx
:keycard/listen-to-hardware-back-button
;;NOTE: not done in view because effect should happen under different conditions and is not
;;dependent on particular screen to be loaded. An fx is easier to re-use and test.
(fn []
(re-frame/dispatch [:keycard/add-listener-to-hardware-back-button
(.addEventListener BackHandler
"hardwareBackPress"
(fn []
(re-frame/dispatch [:keycard/back-button-pressed])
true))])))
(re-frame/reg-fx
:keycard/remove-listener-to-hardware-back-button
(fn [^js listener]
(.remove listener)))
(re-frame/reg-fx
:keycard/save-multiaccount-and-login
card/save-multiaccount-and-login)
(re-frame/reg-fx
:keycard/delete-multiaccount-before-migration
card/delete-multiaccount-before-migration)
+182
View File
@@ -0,0 +1,182 @@
(ns legacy.status-im.keycard.login
(:require
[legacy.status-im.bottom-sheet.events :as bottom-sheet]
[legacy.status-im.keycard.common :as common]
legacy.status-im.keycard.fx
[legacy.status-im.keycard.onboarding :as onboarding]
[legacy.status-im.keycard.recovery :as recovery]
[legacy.status-im.signing.core :as signing.core]
[legacy.status-im.utils.deprecated-types :as types]
[status-im.navigation.events :as navigation]
[taoensso.timbre :as log]
[utils.address :as address]
[utils.re-frame :as rf]))
(rf/defn login-got-it-pressed
{:events [:keycard.login.pin.ui/got-it-pressed
:keycard.login.pin.ui/cancel-pressed]}
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db db}
(navigation/pop-to-root :screen/profile.profiles)))
(rf/defn login-pin-more-icon-pressed
{:events [:keycard.login.pin.ui/more-icon-pressed]}
[cofx]
(bottom-sheet/hide-bottom-sheet-old cofx))
(rf/defn login-create-key-pressed
{:events [:keycard.login.ui/create-new-key-pressed]}
[cofx]
(rf/merge cofx
(bottom-sheet/hide-bottom-sheet-old)
(onboarding/start-onboarding-flow)))
(rf/defn login-add-key-pressed
{:events [:keycard.login.ui/add-key-pressed]}
[cofx]
(recovery/start-import-flow cofx))
(rf/defn login-remember-me-changed
{:events [:keycard.login.ui/remember-me-changed]}
[{:keys [db] :as cofx} value]
(rf/merge cofx
{:db (assoc-in db [:keycard :remember-me?] value)}))
(rf/defn login-pair-card-pressed
{:events [:keycard.login.ui/pair-card-pressed]}
[{:keys [db] :as cofx}]
(log/debug "[keycard] load-pair-card-pressed")
(rf/merge cofx
{:db (assoc-in db [:keycard :flow] :login)}
(navigation/navigate-to :keycard-recovery-pair nil)))
(rf/defn reset-pin
{:events [::reset-pin]}
[{:keys [db] :as cofx}]
(rf/merge
cofx
(signing.core/discard)
(fn [{:keys [db]}]
{:db (-> db
(dissoc :popover/popover)
(update-in [:keycard :pin]
dissoc
:reset
:puk)
(update-in [:keycard :pin]
assoc
:enter-step :reset
:error nil
:status nil))
:hide-popover nil})
(when (:profile/profile db)
(navigation/navigate-to :my-profile nil))
(when-not (:profile/login db)
(if (:popover/popover db)
(navigation/navigate-replace :keycard-pin nil)
(navigation/navigate-to :keycard-pin nil)))))
(rf/defn dismiss-frozen-keycard-popover
{:events [::frozen-keycard-popover-dismissed]}
[{:keys [db]}]
{:db (-> db
(dissoc :popover/popover)
(update :keycard dissoc :setup-step))
:hide-popover nil})
(rf/defn login-with-keycard
{:events [:keycard/login-with-keycard]}
[{:keys [db] :as cofx}]
(let [{:keys [:pin-retry-counter :puk-retry-counter]
:as application-info}
(get-in db [:keycard :application-info])
key-uid (get-in db [:keycard :application-info :key-uid])
paired? (get-in db [:keycard :application-info :paired?])
multiaccount (get-in db
[:profile/profiles-overview (get-in db [:profile/login :key-uid])])
multiaccount-key-uid (get multiaccount :key-uid)
multiaccount-mismatch? (or (nil? multiaccount)
(not= multiaccount-key-uid key-uid))]
(log/debug "[keycard] login-with-keycard"
"empty application info" (empty? application-info)
"no key-uid" (empty? key-uid)
"multiaccount-mismatch?" multiaccount-mismatch?
"no pairing" paired?)
(cond
(empty? application-info)
(rf/merge cofx
(common/hide-connection-sheet)
(navigation/navigate-to :not-keycard nil))
(empty? key-uid)
(rf/merge cofx
(common/hide-connection-sheet)
(navigation/navigate-to :keycard-blank nil))
multiaccount-mismatch?
(rf/merge cofx
(common/hide-connection-sheet)
(navigation/navigate-to :keycard-wrong nil))
(not paired?)
(rf/merge cofx
(common/hide-connection-sheet)
(navigation/navigate-to :keycard-unpaired nil))
(and (zero? pin-retry-counter)
(or (nil? puk-retry-counter)
(pos? puk-retry-counter)))
nil
:else
(common/get-keys-from-keycard cofx))))
(rf/defn proceed-to-login
{:events [::login-after-reset]}
[cofx]
(log/debug "[keycard] proceed-to-login")
(common/show-connection-sheet
cofx
{:sheet-options {:on-cancel [::common/cancel-sheet-confirm]}
:on-card-connected :keycard/get-application-info
:on-card-read :keycard/login-with-keycard
:handler (common/get-application-info :keycard/login-with-keycard)}))
(rf/defn on-keycard-keychain-keys
{:events [:multiaccounts.login.callback/get-keycard-keys-success]}
[{:keys [db] :as cofx} key-uid [encryption-public-key whisper-private-key :as creds]]
(if (nil? creds)
(navigation/set-stack-root cofx
:multiaccounts-stack
[:multiaccounts
:keycard-login-pin])
(let [{:keys [name]} (get-in db [:profile/profiles-overview key-uid])
multiaccount-data (types/clj->json {:name name
:key-uid key-uid})
account-data {:key-uid key-uid
:encryption-public-key encryption-public-key
:whisper-private-key whisper-private-key}]
{:db
(-> db
(assoc-in [:keycard :pin :status] nil)
(assoc-in [:keycard :pin :login] [])
(assoc-in [:keycard :profile/profile]
(update account-data :whisper-public-key address/normalized-hex))
(assoc-in [:keycard :flow] nil)
(update :profile/login assoc
:password encryption-public-key
:key-uid key-uid
:name name
:save-password? true))
:keycard/login-with-keycard
{:multiaccount-data multiaccount-data
:key-uid key-uid
:password encryption-public-key
:chat-key whisper-private-key}})))
(rf/defn on-login-success
{:events [:keycard.login.callback/login-success]}
[_ result]
(log/debug "loginWithKeycard success: " result))
@@ -0,0 +1,37 @@
(ns legacy.status-im.keycard.mnemonic
(:require
[legacy.status-im.keycard.common :as common]
legacy.status-im.keycard.fx
[status-im.navigation.events :as navigation]
[taoensso.timbre :as log]
[utils.re-frame :as rf]))
(rf/defn set-mnemonic
[{:keys [db] :as cofx}]
(log/debug "[keycard] set-mnemonic")
(let [selected-id (get-in db [:intro-wizard :selected-id])
mnemonic (reduce
(fn [_ {:keys [id mnemonic]}]
(when (= selected-id id)
(reduced mnemonic)))
nil
(get-in db [:intro-wizard :multiaccounts]))]
(rf/merge
cofx
{:db (-> db
(assoc-in [:keycard :setup-step] :recovery-phrase)
(assoc-in [:keycard :secrets :mnemonic] mnemonic))}
(common/clear-on-card-connected)
(common/hide-connection-sheet)
(navigation/navigate-replace :keycard-onboarding-recovery-phrase nil))))
(rf/defn load-loading-keys-screen
{:events [:keycard.ui/recovery-phrase-confirm-pressed
:keycard/load-loading-keys-screen]}
[{:keys [db] :as cofx}]
(rf/merge
cofx
{:db (assoc-in db [:keycard :setup-step] :loading-keys)}
(common/show-connection-sheet
{:on-card-connected :keycard/load-loading-keys-screen
:handler (common/dispatch-event :keycard/generate-and-load-key)})))
+11
View File
@@ -0,0 +1,11 @@
(ns legacy.status-im.keycard.nfc)
(def is-nfc-supported? (atom nil))
(defn set-nfc-supported?
[supported?]
(reset! is-nfc-supported? supported?))
(defn nfc-supported?
[]
@is-nfc-supported?)
@@ -0,0 +1,335 @@
(ns legacy.status-im.keycard.onboarding
(:require
[clojure.string :as string]
[legacy.status-im.keycard.common :as common]
legacy.status-im.keycard.fx
[legacy.status-im.keycard.mnemonic :as mnemonic]
[legacy.status-im.ui.components.react :as react]
[legacy.status-im.utils.utils :as utils]
[re-frame.core :as re-frame]
[status-im.constants :as constants]
[status-im.navigation.events :as navigation]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(rf/defn begin-setup-button-pressed
{:keys [:keycard.ui/begin-setup-button-pressed]}
[{:keys [db]}]
{:db (-> db
(assoc-in [:keycard :setup-step] :pin)
(assoc-in [:keycard :pin :enter-step] :original)
(assoc-in [:keycard :pin :original] [])
(assoc-in [:keycard :pin :confirmation] []))})
(rf/defn start-installation
[{:keys [db] :as cofx}]
(let [card-state (get-in db [:keycard :card-state])
pin (common/vector->string (get-in db [:keycard :pin :original]))]
(log/debug "start-installation: card-state" card-state)
(case card-state
:pre-init
{:keycard/init-card pin}
(do
(log/debug (str "Cannot start keycard installation from state: " card-state))
(rf/merge cofx
{:effects.utils/show-popup {:title (i18n/label :t/error)
:content (i18n/label :t/something-went-wrong)}}
(navigation/navigate-to :keycard-authentication-method nil))))))
(rf/defn load-preparing-screen
{:events [:keycard/load-preparing-screen]}
[cofx]
(common/show-connection-sheet
cofx
{:sheet-options {:on-cancel [::cancel-pressed]}
:on-card-connected :keycard/load-preparing-screen
:handler start-installation}))
(rf/defn load-pairing-screen
{:events [:keycard/load-pairing-screen
:keycard.onboarding.puk-code.ui/confirm-pressed]}
[{:keys [db] :as cofx}]
(rf/merge
cofx
{:db (assoc-in db [:keycard :setup-step] :pairing)}
(common/show-connection-sheet
{:sheet-options {:on-cancel [::cancel-pressed]}
:on-card-connected :keycard/load-pairing-screen
:handler (common/dispatch-event :keycard/pair)})))
(rf/defn puk-code-next-pressed
{:events [:keycard.onboarding.puk-code.ui/next-pressed]}
[_]
{:ui/show-confirmation {:title (i18n/label :t/secret-keys-confirmation-title)
:content (i18n/label :t/secret-keys-confirmation-text)
:confirm-button-text (i18n/label :t/yes)
:cancel-button-text (i18n/label :t/cancel)
:on-accept #(re-frame/dispatch
[:keycard.onboarding.puk-code.ui/confirm-pressed])
:on-cancel #()}})
(rf/defn load-finishing-screen
{:events [:keycard.onboarding.recovery-phrase-confirm-word2.ui/next-pressed
:keycard/load-finishing-screen]}
[{:keys [db] :as cofx}]
(rf/merge
cofx
{:db (assoc-in db [:keycard :setup-step] :loading-keys)}
(common/show-connection-sheet
{:sheet-options {:on-cancel [::cancel-pressed]}
:on-card-connected :keycard/load-finishing-screen
:handler (common/dispatch-event :keycard/generate-and-load-key)})))
(rf/defn recovery-phrase-learn-more-pressed
{:events [:keycard.onboarding.recovery-phrase.ui/learn-more-pressed]}
[_]
(.openURL ^js react/linking constants/keycard-integration-link))
(rf/defn recovery-phrase-next-pressed
{:events [:keycard.onboarding.recovery-phrase.ui/next-pressed
:keycard.ui/recovery-phrase-next-button-pressed]}
[_]
{:ui/show-confirmation {:title (i18n/label :t/keycard-recovery-phrase-confirmation-title)
:content (i18n/label :t/keycard-recovery-phrase-confirmation-text)
:confirm-button-text (i18n/label :t/yes)
:cancel-button-text (i18n/label :t/cancel)
:on-accept #(re-frame/dispatch
[:keycard.onboarding.recovery-phrase.ui/confirm-pressed])
:on-cancel #()}})
(rf/defn recovery-phrase-start-confirmation
[{:keys [db] :as cofx}]
(let [mnemonic (get-in db [:keycard :secrets :mnemonic])
[word1 word2] (shuffle (map-indexed vector (clojure.string/split mnemonic #" ")))
word1 (zipmap [:idx :word] word1)
word2 (zipmap [:idx :word] word2)]
(rf/merge cofx
{:db (-> db
(assoc-in [:keycard :setup-step] :recovery-phrase-confirm-word1)
(assoc-in [:keycard :recovery-phrase :step] :word1)
(assoc-in [:keycard :recovery-phrase :confirm-error] nil)
(assoc-in [:keycard :recovery-phrase :input-word] nil)
(assoc-in [:keycard :recovery-phrase :word1] word1)
(assoc-in [:keycard :recovery-phrase :word2] word2))}
(common/remove-listener-to-hardware-back-button))))
(rf/defn recovery-phrase-confirm-pressed
{:events [:keycard.onboarding.recovery-phrase.ui/confirm-pressed]}
[cofx]
(rf/merge cofx
(recovery-phrase-start-confirmation)
(navigation/navigate-to :keycard-onboarding-recovery-phrase-confirm-word1 nil)))
(rf/defn recovery-phrase-next-word
[{:keys [db]}]
{:db (-> db
(assoc-in [:keycard :recovery-phrase :step] :word2)
(assoc-in [:keycard :recovery-phrase :confirm-error] nil)
(assoc-in [:keycard :recovery-phrase :input-word] nil)
(assoc-in [:keycard :setup-step] :recovery-phrase-confirm-word2))})
(rf/defn proceed-with-generating-key
[{:keys [db] :as cofx}]
(let [pin (get-in db
[:keycard :secrets :pin]
(common/vector->string (get-in db [:keycard :pin :current])))]
(if (empty? pin)
(rf/merge cofx
{:db (-> db
(assoc-in [:keycard :pin]
{:enter-step :current
:on-verified :keycard/generate-and-load-key
:current []})
(assoc-in [:keycard :setup-step] :loading-keys))}
(navigation/navigate-to :keycard-onboarding-pin nil))
(load-finishing-screen cofx))))
(rf/defn recovery-phrase-confirm-word-next-pressed
{:events [:keycard.onboarding.recovery-phrase-confirm-word.ui/next-pressed
:keycard.onboarding.recovery-phrase-confirm-word.ui/input-submitted]}
[{:keys [db] :as cofx}]
(let [step (get-in db [:keycard :recovery-phrase :step])
input-word (get-in db [:keycard :recovery-phrase :input-word])
{:keys [word]} (get-in db [:keycard :recovery-phrase step])]
(if (= word input-word)
(if (= (:view-id db) :keycard-onboarding-recovery-phrase-confirm-word1)
(rf/merge cofx
(recovery-phrase-next-word)
(navigation/navigate-replace :keycard-onboarding-recovery-phrase-confirm-word2 nil))
(proceed-with-generating-key cofx))
{:db (assoc-in db [:keycard :recovery-phrase :confirm-error] (i18n/label :t/wrong-word))})))
(rf/defn recovery-phrase-confirm-word-input-changed
{:events [:keycard.onboarding.recovery-phrase-confirm-word.ui/input-changed]}
[{:keys [db]} input]
{:db (assoc-in db [:keycard :recovery-phrase :input-word] input)})
(rf/defn pair-code-input-changed
{:events [:keycard.onboarding.pair.ui/input-changed]}
[{:keys [db]} input]
{:db (assoc-in db [:keycard :secrets :password] input)})
(rf/defn keycard-option-pressed
{:events [:onboarding.ui/keycard-option-pressed]}
[{:keys [db] :as cofx}]
(let [flow (get-in db [:keycard :flow])]
(rf/merge cofx
{:keycard/check-nfc-enabled nil}
(if (= flow :import)
(navigation/navigate-to :keycard-recovery-intro nil)
(do
(common/listen-to-hardware-back-button)
(navigation/navigate-to :keycard-onboarding-intro nil))))))
(rf/defn start-onboarding-flow
{:events [:keycard/start-onboarding-flow]}
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db (assoc-in db [:keycard :flow] :create)
:keycard/check-nfc-enabled nil}
(common/listen-to-hardware-back-button)
(navigation/navigate-to :keycard-onboarding-intro nil)))
(rf/defn open-nfc-settings-pressed
{:events [:keycard.onboarding.nfc-on/open-nfc-settings-pressed]}
[_]
{:keycard/open-nfc-settings nil})
(defn- show-recover-confirmation
[]
{:ui/show-confirmation {:title (i18n/label :t/are-you-sure?)
:content (i18n/label :t/are-you-sure-description)
:confirm-button-text (clojure.string/upper-case (i18n/label :t/yes))
:cancel-button-text (i18n/label :t/see-it-again)
:on-accept #(re-frame/dispatch
[:keycard.ui/recovery-phrase-confirm-pressed])
:on-cancel #(re-frame/dispatch
[:keycard.ui/recovery-phrase-cancel-pressed])}})
(rf/defn recovery-phrase-confirm-word
{:events [:keycard.ui/recovery-phrase-confirm-word-next-button-pressed]}
[{:keys [db]}]
(let [step (get-in db [:keycard :recovery-phrase :step])
input-word (get-in db [:keycard :recovery-phrase :input-word])
{:keys [word]} (get-in db [:keycard :recovery-phrase step])]
(if (= word input-word)
(if (= step :word1)
(recovery-phrase-next-word db)
(show-recover-confirmation))
{:db (assoc-in db [:keycard :recovery-phrase :confirm-error] (i18n/label :t/wrong-word))})))
(rf/defn recovery-phrase-next-button-pressed
[{:keys [db] :as cofx}]
(if (= (get-in db [:keycard :flow]) :create)
(recovery-phrase-start-confirmation cofx)
(let [mnemonic (get-in db [:multiaccounts/recover :passphrase])]
(rf/merge cofx
{:db (assoc-in db [:keycard :secrets :mnemonic] mnemonic)}
(mnemonic/load-loading-keys-screen)))))
(rf/defn on-install-applet-and-init-card-success
{:events [:keycard.callback/on-install-applet-and-init-card-success
:keycard.callback/on-init-card-success]}
[{:keys [db] :as cofx} secrets]
(let [secrets' (js->clj secrets :keywordize-keys true)]
(rf/merge cofx
{:db (-> db
(assoc-in [:keycard :card-state] :init)
(assoc-in [:keycard :setup-step] :secret-keys)
(update-in [:keycard :secrets] merge secrets'))}
(common/show-connection-sheet
{:on-card-connected :keycard/get-application-info
:on-card-read :keycard/check-card-state
:handler (common/get-application-info :keycard/check-card-state)}))))
(rf/defn on-install-applet-and-init-card-error
{:events [:keycard.callback/on-install-applet-and-init-card-error
:keycard.callback/on-init-card-error]}
[{:keys [db] :as cofx} {:keys [code error]}]
(log/debug "[keycard] install applet and init card error: " error)
(rf/merge cofx
{:db (assoc-in db [:keycard :setup-error] error)}
(common/set-on-card-connected :keycard/load-preparing-screen)
(common/process-error code error)))
(rf/defn generate-and-load-key
{:events [:keycard/generate-and-load-key]}
[{:keys [db] :as cofx}]
(let [{:keys [pin]}
(get-in db [:keycard :secrets])
{:keys [selected-id multiaccounts]}
(:intro-wizard db)
multiaccount (or (->> multiaccounts
(filter #(= (:id %) selected-id))
first)
(assoc (get-in db [:intro-wizard :root-key])
:derived
(get-in db [:intro-wizard :derived])))
recovery-mnemonic (get-in db [:intro-wizard :passphrase])
mnemonic (or (:mnemonic multiaccount)
recovery-mnemonic)
pin' (or pin
(common/vector->string (get-in db
[:keycard :pin
:current])))]
{:keycard/generate-and-load-key
{:mnemonic mnemonic
:pin pin'
:key-uid (:key-uid multiaccount)
:delete-multiaccount? (get-in db [:keycard :delete-account?])}}))
(rf/defn factory-reset-card-toggle
{:events [:keycard.onboarding.intro.ui/factory-reset-card-toggle]}
[{:keys [db] :as cofx} checked?]
{:db (assoc-in db [:keycard :factory-reset-card?] checked?)})
(rf/defn begin-setup-pressed
{:events [:keycard.onboarding.intro.ui/begin-setup-pressed]}
[{:keys [db] :as cofx}]
(rf/merge
cofx
{:db (-> db
(update :keycard
dissoc
:secrets
:card-state :multiaccount-wallet-address
:multiaccount-whisper-public-key :application-info)
(assoc-in [:keycard :setup-step] :begin)
(assoc-in [:keycard :pin :on-verified] nil))}
(if (get-in db [:keycard :factory-reset-card?])
(utils/show-confirmation {:title (i18n/label :t/keycard-factory-reset-title)
:content (i18n/label :t/keycard-factory-reset-text)
:confirm-button-text (i18n/label :t/yes)
:cancel-button-text (i18n/label :t/no)
:on-accept #(re-frame/dispatch [::factory-reset])
:on-cancel #(re-frame/dispatch [::factory-reset-cancel])})
(common/show-connection-sheet
{:on-card-connected :keycard/get-application-info
:on-card-read :keycard/check-card-state
:handler (common/get-application-info :keycard/check-card-state)}))))
(rf/defn factory-reset
{:events [::factory-reset]}
[cofx]
(common/show-connection-sheet
cofx
{:on-card-connected :keycard/factory-reset
:on-card-read :keycard/check-card-state
:handler (common/factory-reset :keycard/check-card-state)}))
(rf/defn factory-reset-cancel
{:events [::factory-reset-cancel]}
[{:keys [db] :as cofx}]
{:db (update db :keycard dissoc :factory-reset-card?)})
(rf/defn cancel-pressed
{:events [::cancel-pressed]}
[cofx]
(rf/merge cofx
(navigation/navigate-back)
(common/cancel-sheet-confirm)))
+355
View File
@@ -0,0 +1,355 @@
(ns legacy.status-im.keycard.recovery
(:require
[clojure.string :as string]
[legacy.status-im.bottom-sheet.events :as bottom-sheet]
[legacy.status-im.keycard.common :as common]
legacy.status-im.keycard.fx
[legacy.status-im.multiaccounts.create.core :as multiaccounts.create]
[legacy.status-im.multiaccounts.model :as multiaccounts.model]
[legacy.status-im.popover.core :as popover]
[legacy.status-im.utils.deprecated-types :as types]
[native-module.core :as native-module]
[re-frame.core :as re-frame]
[react-native.platform :as platform]
[status-im.constants :as constants]
[status-im.navigation.events :as navigation]
[taoensso.timbre :as log]
[utils.address :as address]
[utils.datetime :as datetime]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[utils.security.core :as security]))
(rf/defn pair*
[_ password]
{:keycard/pair {:password password}})
(rf/defn pair
{:events [:keycard/pair]}
[cofx]
(let [{:keys [password]} (get-in cofx [:db :keycard :secrets])]
(common/show-connection-sheet
cofx
{:on-card-connected :keycard/pair
:handler (pair* password)})))
(rf/defn pair-code-next-button-pressed
{:events [:keycard.onboarding.pair.ui/input-submitted
:keycard.ui/pair-code-next-button-pressed
:keycard.onboarding.pair.ui/next-pressed]}
[{:keys [db] :as cofx}]
(let [pairing (get-in db [:keycard :secrets :pairing])
paired-on (get-in db [:keycard :secrets :paired-on] (datetime/timestamp))]
(rf/merge cofx
(if pairing
{:db (-> db
(assoc-in [:keycard :setup-step] :import-multiaccount)
(assoc-in [:keycard :secrets :paired-on] paired-on))}
(pair)))))
(rf/defn load-pair-screen
[{:keys [db] :as cofx}]
(log/debug "[keycard] load-pair-screen")
(rf/merge cofx
{:db (-> db
(assoc-in [:keycard :setup-step] :pair))
:dispatch [:bottom-sheet/hide-old]}
(common/listen-to-hardware-back-button)
(navigation/navigate-to :keycard-recovery-pair nil)))
(rf/defn keycard-storage-selected-for-recovery
{:events [:recovery.ui/keycard-storage-selected]}
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db (assoc-in db [:keycard :flow] :recovery)}
(navigation/navigate-to :keycard-recovery-enter-mnemonic nil)))
(rf/defn start-import-flow
{:events [::recover-with-keycard-pressed]}
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db
(-> db
(assoc-in [:keycard :flow] :import)
(assoc :recovered-account? true))
:keycard/check-nfc-enabled nil}
(bottom-sheet/hide-bottom-sheet-old)
(navigation/navigate-to-within-stack [:keycard-recovery-intro
:screen/onboarding.new-to-status])))
(rf/defn access-key-pressed
{:events [:multiaccounts.recover.ui/recover-multiaccount-button-pressed]}
[_]
{:dispatch [:bottom-sheet/show-sheet-old :recover-sheet]})
(rf/defn recovery-keycard-selected
{:events [:recovery.ui/keycard-option-pressed]}
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db (assoc-in db [:keycard :flow] :recovery)
:keycard/check-nfc-enabled nil}
(common/listen-to-hardware-back-button)
(navigation/navigate-to :keycard-onboarding-intro nil)))
(rf/defn cancel-pressed
{:events [::cancel-pressed]}
[cofx]
(rf/merge cofx
(common/cancel-sheet-confirm)
(navigation/navigate-back)))
(rf/defn begin-setup-pressed
{:events [:keycard.recovery.intro.ui/begin-recovery-pressed]}
[{:keys [db] :as cofx}]
(rf/merge
cofx
{:db (-> db
(update :keycard
dissoc
:secrets
:card-state :multiaccount-wallet-address
:multiaccount-whisper-public-key
:application-info)
(assoc-in [:keycard :setup-step] :begin)
(assoc-in [:keycard :pin :on-verified] nil))}
(common/show-connection-sheet
{:on-card-connected :keycard/get-application-info
:on-card-read :keycard/check-card-state
:sheet-options {:on-cancel [::cancel-pressed]}
:handler (common/get-application-info :keycard/check-card-state)})))
(rf/defn recovery-success-finish-pressed
{:events [:keycard.recovery.success/finish-pressed]}
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db (update db
:keycard dissoc
:multiaccount-wallet-address
:multiaccount-whisper-public-key)}
(navigation/navigate-to (if platform/android?
:notifications-settings
:screen/onboarding.welcome)
nil)))
(rf/defn intro-wizard
{:events [:multiaccounts.create.ui/intro-wizard]}
[{:keys [db] :as cofx}]
(let [accs (get db :profile/profiles-overview)]
(rf/merge cofx
{:db (-> db
(update :keycard dissoc :flow)
(dissoc :restored-account?))}
(if (pos? (count accs))
(navigation/navigate-to :get-your-keys nil)
(navigation/set-stack-root :onboarding [:get-your-keys])))))
(rf/defn recovery-no-key
{:events [:keycard.recovery.no-key.ui/generate-key-pressed]}
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db (assoc-in db [:keycard :flow] :create)
:keycard/check-nfc-enabled nil}
(intro-wizard)))
;; NOTE: currently non functional, keycard needs to be reimplemented
(rf/defn create-keycard-multiaccount
{:events [::create-keycard-multiaccount]
:interceptors [(re-frame/inject-cofx :random-guid-generator)
(re-frame/inject-cofx ::multiaccounts.create/get-signing-phrase)]}
[{:keys [db] :as cofx}]
(let [{{:keys [secrets flow] :profile/keys [profile]} :keycard} db
{:keys [address
name
public-key
whisper-public-key
wallet-public-key
wallet-root-public-key
whisper-address
wallet-address
wallet-root-address
whisper-private-key
encryption-public-key
instance-uid
key-uid
recovered]}
profile
{:keys [pairing paired-on]} secrets
{:keys [name]}
(if (nil? name)
;; name might have been generated during recovery via passphrase
(get-in db [:intro-wizard :derived constants/path-whisper-keyword])
{:name name})]
(rf/merge cofx
{:db (-> db
(assoc-in [:keycard :setup-step] nil)
(dissoc :intro-wizard))})))
(rf/defn return-to-keycard-login
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db (-> db
(update-in [:keycard :pin]
assoc
:enter-step :login
:status nil
:login [])
(update :keycard dissoc :application-info))}
(navigation/set-stack-root :multiaccounts-stack
[:multiaccounts
:keycard-login-pin])))
(rf/defn on-backup-success
[{:keys [db] :as cofx} backup-type]
(rf/merge cofx
{:effects.utils/show-popup {:title (i18n/label (if (= backup-type :recovery-card)
:t/keycard-access-reset
:t/keycard-backup-success-title))
:content (i18n/label (if (= backup-type :recovery-card)
:t/keycard-can-use-with-new-passcode
:t/keycard-backup-success-body))}}
(cond
(multiaccounts.model/logged-in? db)
(navigation/set-stack-root :profile-stack [:my-profile :keycard-settings])
(:profile/login db)
(return-to-keycard-login)
:else
(navigation/set-stack-root :onboarding [:get-your-keys]))))
(re-frame/reg-fx
::finish-migration
(fn [[account settings password encryption-pass login-params]]
(native-module/convert-to-keycard-account
account
settings
password
encryption-pass
#(let [{:keys [error]} (types/json->clj %)]
(if (string/blank? error)
(native-module/login-with-keycard
(assoc login-params :node-config {:ProcessBackedupMessages true}))
(throw
(js/Error.
"Please shake the phone to report this error and restart the app. Migration failed unexpectedly.")))))))
(rf/defn migrate-account
[{:keys [db] :as cofx}]
(let [pairing (get-in db [:keycard :secrets :pairing])
paired-on (get-in db [:keycard :secrets :paired-on])
instance-uid (get-in db [:keycard :profile/profile :instance-uid])
account (-> db
:profile/login
(assoc :keycard-pairing pairing)
(assoc :save-password? false))
key-uid (-> account :key-uid)
settings {:keycard-instance-uid instance-uid
:keycard-paired-on paired-on
:keycard-pairing pairing}
password (native-module/sha3 (security/safe-unmask-data (get-in db
[:keycard
:migration-password])))
encryption-pass (get-in db [:keycard :profile/profile :encryption-public-key])
login-params {:key-uid key-uid
:multiaccount-data (types/clj->json account)
:password encryption-pass
:chat-key (get-in db
[:keycard :profile/profile :whisper-private-key])}]
{:db (-> db
(assoc-in [:profile/profiles-overview key-uid :keycard-pairing] pairing)
(assoc :profile/login account)
(assoc :auth-method "none")
(update :keycard dissoc :flow :migration-password)
(dissoc :recovered-account?))
::finish-migration [account settings password encryption-pass login-params]}))
(rf/defn delete-multiaccount
[{:keys [db]}]
(let [key-uid (get-in db [:profile/login :key-uid])]
{:keycard/delete-multiaccount-before-migration
{:key-uid key-uid
:on-error #(re-frame/dispatch [::delete-multiaccount-error %])
:on-success #(re-frame/dispatch [::create-keycard-multiaccount])}}))
(rf/defn handle-delete-multiaccount-error
{:events [::delete-multiaccount-error]}
[cofx _]
(popover/show-popover cofx {:view :transfer-multiaccount-unknown-error}))
(rf/defn on-generate-and-load-key-success
{:events [:keycard.callback/on-generate-and-load-key-success]
:interceptors [(re-frame/inject-cofx :random-guid-generator)
(re-frame/inject-cofx ::multiaccounts.create/get-signing-phrase)]}
[{:keys [db random-guid-generator] :as cofx} data]
(let [account-data (js->clj data :keywordize-keys true)
backup? (get-in db [:keycard :creating-backup?])
migration? (get-in db [:keycard :converting-account?])]
(rf/merge
cofx
{:db (-> db
(assoc-in
[:keycard :profile/profile]
(-> account-data
(update :address address/normalized-hex)
(update :whisper-address address/normalized-hex)
(update :wallet-address address/normalized-hex)
(update :wallet-root-address address/normalized-hex)
(update :public-key address/normalized-hex)
(update :whisper-public-key address/normalized-hex)
(update :wallet-public-key address/normalized-hex)
(update :wallet-root-public-key address/normalized-hex)
(update :instance-uid #(get-in db [:keycard :profile/profile :instance-uid] %))))
(assoc-in [:keycard :multiaccount-wallet-address] (:wallet-address account-data))
(assoc-in [:keycard :multiaccount-whisper-public-key] (:whisper-public-key account-data))
(assoc-in [:keycard :pin :status] nil)
(assoc-in [:keycard :application-info :key-uid]
(address/normalized-hex (:key-uid account-data)))
(update :keycard dissoc :recovery-phrase :creating-backup? :converting-account?)
(update-in [:keycard :secrets] dissoc :pin :puk :password :mnemonic)
(assoc :multiaccounts/new-installation-id (random-guid-generator)))}
(common/remove-listener-to-hardware-back-button)
(common/hide-connection-sheet)
(cond backup? (on-backup-success backup?)
migration? (migrate-account)
(get-in db [:keycard :delete-account?])
(delete-multiaccount)
:else (create-keycard-multiaccount)))))
(rf/defn on-generate-and-load-key-error
{:events [:keycard.callback/on-generate-and-load-key-error]}
[{:keys [db] :as cofx} {:keys [error code]}]
(log/debug "[keycard] generate and load key error: " error)
(when-not (common/tag-lost? error)
(rf/merge cofx
{:db (assoc-in db [:keycard :setup-error] error)}
(common/set-on-card-connected :keycard/load-loading-keys-screen)
(common/process-error code error))))
(rf/defn import-multiaccount
{:events [:keycard/import-multiaccount]}
[{:keys [db] :as cofx}]
(let [{:keys [pairing]} (get-in db [:keycard :secrets])
instance-uid (get-in db [:keycard :application-info :instance-uid])
key-uid (get-in db [:keycard :application-info :key-uid])
pairing' (or pairing (common/get-pairing db key-uid))
pin (common/vector->string (get-in db [:keycard :pin :import-multiaccount]))]
(rf/merge cofx
{:db (-> db
(assoc-in [:keycard :profile/profile :instance-uid] instance-uid)
(assoc-in [:keycard :pin :status] :verifying)
(assoc-in [:keycard :secrets]
{:pairing pairing'
:paired-on (datetime/timestamp)}))
:keycard/import-keys
{:pin pin
:on-success :keycard.callback/on-generate-and-load-key-success}})))
(rf/defn load-recovering-key-screen
{:events [:keycard/load-recovering-key-screen]}
[cofx]
(common/show-connection-sheet
cofx
{:on-card-connected :keycard/load-recovering-key-screen
:handler (common/dispatch-event :keycard/import-multiaccount)}))
+196
View File
@@ -0,0 +1,196 @@
(ns legacy.status-im.keycard.sign
(:require
[clojure.string :as string]
[legacy.status-im.keycard.common :as common]
[legacy.status-im.utils.deprecated-types :as types]
[re-frame.core :as re-frame]
[taoensso.timbre :as log]
[utils.address :as address]
[utils.ethereum.chain :as chain]
[utils.money :as money]
[utils.re-frame :as rf]))
(rf/defn sign
{:events [:keycard/sign]}
[{:keys [db] :as cofx} hash on-success]
(let [card-connected? (get-in db [:keycard :card-connected?])
key-uid (get-in db [:profile/profile :key-uid])
keycard-key-uid (get-in db [:keycard :application-info :key-uid])
keycard-pin-retries (get-in db [:keycard :application-info :pin-retry-counter])
keycard-match? (= key-uid keycard-key-uid)
hash (or hash (get-in db [:keycard :hash]))
data (get-in db [:keycard :data])
typed? (get-in db [:keycard :typed?])
pin (common/vector->string (get-in db [:keycard :pin :sign]))
from (or (get-in db [:signing/tx :from :address])
(get-in db [:signing/tx :message :from]))
path (reduce
(fn [_ {:keys [address path]}]
(when (address/address= from address)
(reduced path)))
nil
(:profile/wallet-accounts db))]
(cond
(not keycard-match?)
(common/show-wrong-keycard-alert cofx)
(not card-connected?)
(rf/merge cofx
{:db (assoc-in db [:signing/sign :keycard-step] :signing)}
(common/set-on-card-connected :keycard/sign))
(pos? keycard-pin-retries) ; if 0, get-application-info will have already closed the
; connection sheet and opened the frozen card popup
{:db (-> db
(assoc-in [:keycard :card-read-in-progress?] true)
(assoc-in [:keycard :pin :status] :verifying))
:keycard/sign {:hash (address/naked-address hash)
:data data
:typed? typed? ; this parameter is for e2e
:on-success on-success
:pin pin
:path path}})))
(defn normalize-signature
[signature]
(-> signature
(string/replace-first #"00$" "1b")
(string/replace-first #"01$" "1c")
address/normalized-hex))
(rf/defn sign-message
{:events [:keycard/sign-message]}
[cofx params result]
(let [{:keys [result error]} (types/json->clj result)
on-success #(re-frame/dispatch [:keycard/on-sign-message-success params
(normalize-signature %)])
hash (address/naked-address result)]
(sign cofx hash on-success)))
(rf/defn on-sign-message-success
{:events [:keycard/on-sign-message-success]}
[{:keys [db] :as cofx} {:keys [tx-hash message-id chat-id value contract]} signature]
(rf/merge
cofx
{:dispatch
(if message-id
[:sign/send-accept-transaction-message message-id tx-hash signature]
[:sign/send-transaction-message chat-id value contract tx-hash signature])
:signing/show-transaction-result nil
:db (-> db
(assoc-in [:keycard :pin :sign] [])
(assoc-in [:keycard :pin :status] nil))}
(common/clear-on-card-connected)
(common/get-application-info nil)
(common/hide-connection-sheet)))
(rf/defn sign-typed-data
{:events [:keycard/sign-typed-data]}
[{:keys [db] :as cofx}]
(let [card-connected? (get-in db [:keycard :card-connected?])
hash (get-in db [:keycard :hash])]
(if card-connected?
{:db (-> db
(assoc-in [:keycard :card-read-in-progress?] true)
(assoc-in [:signing/sign :keycard-step] :signing))
:keycard/sign-typed-data {:hash (address/naked-address hash)}}
(rf/merge cofx
(common/set-on-card-connected :keycard/sign-typed-data)
{:db (assoc-in db [:signing/sign :keycard-step] :signing)}))))
(rf/defn fetch-currency-token-on-success
{:events [:keycard/fetch-currency-token-on-success]}
[{:keys [db]} {:keys [decimals symbol]}]
{:db (-> db
(assoc-in [:signing/sign :formatted-data :message :formatted-currency] symbol)
(update-in [:signing/sign :formatted-data :message]
#(assoc %
:formatted-amount
(.dividedBy ^js (money/bignumber (:amount %))
(money/bignumber (money/from-decimal decimals))))))})
(rf/defn store-hash-and-sign-typed
{:events [:keycard/store-hash-and-sign-typed]}
[{:keys [db] :as cofx} result]
(let [{:keys [result]} (types/json->clj result)
message (get-in db [:signing/sign :formatted-data :message])
currency-contract (:currency message)]
(when currency-contract
{:json-rpc/call [{:method "wallet_discoverToken"
:params [(chain/chain-id db) currency-contract]
:on-success #(re-frame/dispatch [:keycard/fetch-currency-token-on-success
%])}]})
(rf/merge cofx
{:db (assoc-in db [:keycard :hash] result)}
sign-typed-data)))
(rf/defn prepare-to-sign
{:events [:keycard/prepare-to-sign]}
[{:keys [db] :as cofx}]
(common/show-connection-sheet
cofx
{:on-card-connected :keycard/prepare-to-sign
:handler (common/get-application-info :keycard/sign)}))
(rf/defn sign-message-completed
[_ signature]
{:dispatch
[:signing/sign-message-completed signature]})
(rf/defn send-transaction-with-signature
[_ data]
{:send-transaction-with-signature data})
(rf/defn on-sign-success
{:events [:keycard.callback/on-sign-success]}
[{:keys [db] :as cofx} signature]
(log/debug "[keycard] sign success: " signature)
(let [signature-json (types/clj->json {:result (normalize-signature signature)})
transaction (get-in db [:keycard :transaction])
tx-obj (select-keys transaction
[:from :to :value :gas :gasPrice :command? :chat-id :message-id])
command? (:command? transaction)]
(rf/merge cofx
{:db (-> db
(assoc-in [:keycard :hash] nil)
(assoc-in [:keycard :transaction] nil))}
(when-not command?
(fn [{:keys [db] :as cofx}]
(rf/merge
cofx
{:db (-> db
(assoc-in [:keycard :pin :sign] [])
(assoc-in [:keycard :pin :status] nil))}
(common/clear-on-card-connected)
(common/get-application-info nil)
(common/hide-connection-sheet))))
(if transaction
(send-transaction-with-signature
{:transaction (types/clj->json transaction)
:signature signature
:on-completed #(re-frame/dispatch [:signing/transaction-completed % tx-obj])})
(sign-message-completed signature-json)))))
(rf/defn on-sign-error
{:events [:keycard.callback/on-sign-error]}
[{:keys [db] :as cofx} error]
(log/debug "[keycard] sign error: " error)
(let [tag-was-lost? (common/tag-lost? (:error error))
pin-retries (common/pin-retries (:error error))]
(when-not tag-was-lost?
(if (not (nil? pin-retries))
(rf/merge cofx
{:db (-> db
(assoc-in [:keycard :application-info :pin-retry-counter] pin-retries)
(update-in [:keycard :pin]
assoc
:status :error
:sign []
:error-label :t/pin-mismatch)
(assoc-in [:signing/sign :keycard-step] :pin))}
(common/hide-connection-sheet)
(when (zero? pin-retries) (common/frozen-keycard-popup)))
(rf/merge cofx
(common/hide-connection-sheet)
(common/show-wrong-keycard-alert))))))
@@ -0,0 +1,588 @@
(ns legacy.status-im.keycard.simulated-keycard
(:require
[clojure.string :as string]
[keycard.keycard :as keycard]
[legacy.status-im.multiaccounts.create.core :as multiaccounts.create]
[legacy.status-im.utils.deprecated-types :as types]
[native-module.core :as native-module]
re-frame.db
[status-im.constants :as constants]
[taoensso.timbre :as log]
[utils.address :as address]
[utils.i18n :as i18n]
[utils.re-frame :as re-frame]))
(def kk1-password "000000")
(def default-pin "111111")
(def default-puk "000000000000")
(def account-password (native-module/sha3 "no password"))
(def initial-state
{:card-connected? false
:application-info {:initialized? false}})
(defonce state (atom initial-state))
(defn connect-card
[]
(swap! state assoc :card-connected? true)
(doseq [callback (vals (get @state :on-card-connected))]
(callback)))
(defn connect-selected-card
[]
(swap! state assoc
:application-info
{:free-pairing-slots 5
:app-version "3.0"
:secure-channel-pub-key
"04c3071768912a515c00aeab7ceb8a5bfda91d036f4a4e60b7944cee3ca7fb67b6d118e8df1e2480b87fd636c6615253245bbbc93a6a407f155f2c58f76c96ef0e"
:instance-uid "1b360b10a9a68b7d494e8f059059f118"
:paired? true
:has-master-key? true
:initialized? true
:pin-retry-counter 3
:puk-retry-counter 5
:key-uid (get-in @re-frame.db/app-db [:profile/login :key-uid])})
(swap! state assoc :pin default-pin :puk default-puk :password kk1-password)
(connect-card))
(def initialization (atom false))
(defn connect-pairing-card
[]
(reset! initialization false)
(swap! state assoc
:application-info
{:free-pairing-slots 5
:app-version "3.0"
:secure-channel-pub-key
"04c3071768912a515c00aeab7ceb8a5bfda91d036f4a4e60b7944cee3ca7fb67b6d118e8df1e2480b87fd636c6615253245bbbc93a6a407f155f2c58f76c96ef0e"
:instance-uid "1b360b10a9a68b7d494e8f059059f118"
:paired? false
:has-master-key? true
:initialized? true
:pin-retry-counter 3
:puk-retry-counter 5
:key-uid "0x16839e8b1b8a395acb18100c7e2b161701c225adf31eefa02114099b4d81a30b"})
(swap! state assoc :pin default-pin :puk default-puk :password kk1-password)
(connect-card))
(defn disconnect-card
[]
(swap! state assoc :card-connected? false)
(doseq [callback (vals (get @state :on-card-disconnected))]
(callback)))
(defn reset-state
[]
(swap! state assoc :application-info {:initialized? false}))
(defn- later
[f]
(when f (js/setTimeout f 500)))
(defn pin-error
[]
#js
{:code "EUNSPECIFIED"
:message (str "Unexpected error SW, 0x63C" (get-in @state [:application-info :pin-retry-counter]))})
(defn puk-error
[]
#js
{:code "EUNSPECIFIED"
:message (str "Unexpected error SW, 0x63C" (get-in @state [:application-info :puk-retry-counter]))})
(defn with-pin
[pin on-failure on-valid]
(cond
(= (get-in @state [:application-info :pin-retry-counter]) 0)
(later #(on-failure (pin-error)))
(= pin (get @state :pin))
(do
(swap! state update
:application-info assoc
:pin-retry-counter 3
:puk-retry-counter 5)
(later on-valid))
:else
(do
(swap! state update-in
[:application-info :pin-retry-counter]
(fnil dec 3))
(later #(on-failure (pin-error))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn start-nfc
[{:keys [on-success]}]
(when (get @state :card-connected?) (connect-card))
(later #(on-success true)))
(defn stop-nfc
[{:keys [on-success]}]
(later #(on-success true)))
(defn set-nfc-message
[{:keys [on-success]}]
(later #(on-success true)))
(defn check-nfc-support
[{:keys [on-success]}]
(later #(on-success true)))
(defn check-nfc-enabled
[{:keys [on-success]}]
(later #(on-success true)))
(defn open-nfc-settings [])
(defn on-card-connected
[callback]
(let [id (random-uuid)]
(swap! state update :on-card-connected assoc id callback)
id))
(defn on-card-disconnected
[callback]
(let [id (random-uuid)]
(swap! state update :on-card-disconnected assoc id callback)
id))
(defn register-card-events
[args]
(log/debug "register-card-events")
(on-card-connected (:on-card-connected args))
(on-card-disconnected (:on-card-disconnected args)))
(defn set-pairings
[args]
(log/warn "set-pairings not implemented" args))
(defn remove-event-listener
[id]
(log/debug "remove-event-listener")
(swap! state update :on-card-connected dissoc id)
(swap! state update :on-card-disconnected dissoc id))
(defn remove-event-listeners
[]
(log/debug "remove-event-listeners")
(swap! state dissoc :on-card-connected)
(swap! state dissoc :on-card-disconnected))
(defn get-application-info
[{:keys [on-success]}]
(log/debug "get-application-info")
(later #(on-success (get @state :application-info))))
(defn factory-reset
[args]
(log/debug "factory-reset")
(reset-state)
(get-application-info args))
(defn install-applet [_])
(defn install-cash-applet [_])
(defn init-card
[{:keys [pin on-success]}]
(reset! initialization true)
(swap! state assoc
:application-info
{:free-pairing-slots 5
:app-version "3.0"
:secure-channel-pub-key
"04c3071768912a515c00aeab7ceb8a5bfda91d036f4a4e60b7944cee3ca7fb67b6d118e8df1e2480b87fd636c6615253245bbbc93a6a407f155f2c58f76c96ef0e"
:key-uid ""
:instance-uid "1b360b10a9a68b7d494e8f059059f118"
:paired? false
:has-master-key? false
:pin-retry-counter 3
:puk-retry-counter 5
:initialized? true})
(swap! state assoc :pin pin :puk default-puk :password kk1-password)
(later
#(on-success {:password kk1-password
:puk default-puk
:pin pin})))
(defn install-applet-and-init-card [_])
(def derived-acc (atom nil))
(defn multiaccount->keys
[data]
(let [{:keys [selected-id multiaccounts root-key derived]}
data
{:keys [id address public-key derived key-uid]}
(or (->> multiaccounts
(filter #(= (:id %) selected-id))
first)
(assoc root-key :derived derived))
whisper (get derived constants/path-whisper-keyword)
wallet (get derived constants/path-default-wallet-keyword)
wallet-root (get derived constants/path-wallet-root-keyword)
password account-password
response {:key-uid key-uid
:encryption-public-key password
:address address
:whisper-public-key (:public-key whisper)
:instance-uid "1b360b10a9a68b7d494e8f059059f118"
:wallet-root-public-key (:public-key wallet-root)
:wallet-root-address (:address wallet-root)
:whisper-address (:address whisper)
:public-key public-key
:whisper-private-key "34bc7d0c258c4f2ac1dac4fd6c55c9478bac1f4a9d8b9f1152c8551ab7187b43"
:wallet-address (:address wallet)
:wallet-public-key (:public-key wallet)}]
[id response]))
(defn pair
[{:keys [password on-success]}]
(when (and (not @initialization)
(not @derived-acc))
(native-module/multiaccount-import-mnemonic
"night fortune spider version version armed amused winner matter tonight cave flag"
nil
(fn [result]
(let [{:keys [id] :as root-data}
(multiaccounts.create/normalize-multiaccount-data-keys
(types/json->clj result))]
(native-module.core/multiaccount-derive-addresses
id
[constants/path-wallet-root
constants/path-eip1581
constants/path-whisper
constants/path-default-wallet]
(fn [result]
(let [derived-data (multiaccounts.create/normalize-derived-data-keys
(types/json->clj result))]
(reset! derived-acc
{:root-key root-data
:derived derived-data}))))))))
(when (= password (get @state :password))
(swap! state assoc-in [:application-info :paired?] true)
(later #(on-success (str (rand-int 10000000))))))
(defn generate-and-load-key
[{:keys [pin on-success key-uid delete-multiaccount?]}]
(when (= pin (get @state :pin))
(let [[id response] (multiaccount->keys
(:intro-wizard @re-frame.db/app-db))]
(log/debug "[simulated kk] generate-and-load-key response" response)
(swap! state assoc-in
[:application-info :key-uid]
(:key-uid response))
(let [deletion-wrapper
(if delete-multiaccount?
(fn [on-deletion-success]
(js/alert "OH SHEET")
(native-module/delete-multiaccount
key-uid
(fn [result]
(let [{:keys [error]} (types/json->clj result)]
(if-not (string/blank? error)
(log/error error)
(on-deletion-success))))))
(fn [cb] (cb)))]
(deletion-wrapper
(fn []
(native-module/multiaccount-store-derived
id
(:key-uid response)
[constants/path-wallet-root
constants/path-eip1581
constants/path-whisper
constants/path-default-wallet]
account-password
#(on-success response))))))))
(defn unblock-pin
[{:keys [puk new-pin on-success on-failure]}]
(cond
(= (get-in @state [:application-info :puk-retry-counter]) 0)
(later #(on-failure (puk-error)))
(= puk (get @state :puk))
(do
(swap! state update
:application-info assoc
:pin-retry-counter 3
:puk-retry-counter 5)
(swap! state assoc :pin new-pin)
(later #(on-success true)))
:else
(do
(swap! state update-in
[:application-info :puk-retry-counter]
(fnil dec 5))
(later
#(on-failure (puk-error))))))
(defn verify-pin
[{:keys [pin on-success on-failure]}]
(with-pin pin on-failure #(on-success 3)))
(defn change-pin
[{:keys [current-pin new-pin on-success on-failure]}]
(with-pin current-pin
on-failure
(fn []
(swap! state assoc :pin new-pin)
(on-success))))
(defn change-puk
[{:keys [pin puk on-success on-failure]}]
(with-pin pin
on-failure
(fn []
(swap! state assoc :puk puk)
(on-success))))
(defn change-pairing
[{:keys [pin pairing on-success on-failure]}]
(with-pin pin
on-failure
(fn []
(swap! state assoc :password pairing)
(on-success))))
(defn unpair
[args]
(log/warn "unpair not implemented" args))
(defn delete
[args]
(log/warn "delete not implemented" args))
(defn remove-key
[args]
(log/warn "remove-key not implemented" args))
(defn remove-key-with-unpair
[args]
(log/warn "remove-key-with-unpair not implemented" args))
(defn normalize-path
[path]
(if (string/starts-with? path "m/")
(str constants/path-wallet-root
"/"
(last (string/split path "/")))
path))
(defn export-key
[{:keys [pin on-success on-failure]}]
(with-pin
pin
on-failure
#(let [{:keys [key-uid wallet-root-address]}
(get @re-frame.db/app-db :profile/profile)
accounts (get @re-frame.db/app-db :profile/wallet-accounts)
hashed-password account-password
path-num (inc (get-in @re-frame.db/app-db
[:profile/profile :latest-derived-path]))
path (str "m/" path-num)]
(native-module/multiaccount-load-account
wallet-root-address
hashed-password
(fn [value]
(let [{:keys [id error]} (types/json->clj value)]
(if error
(re-frame/dispatch [::new-account-error :password-error error])
(native-module/multiaccount-derive-addresses
id
[path]
(fn [derived]
(let [derived-address (get-in (types/json->clj derived) [(keyword path) :address])]
(if (some (fn [a] (= derived-address (get a :address))) accounts)
(re-frame/dispatch [::new-account-error :account-error
(i18n/label :t/account-exists-title)])
(native-module/multiaccount-store-derived
id
key-uid
[path]
hashed-password
(fn [result]
(let [{:keys [error] :as result} (types/json->clj result)
{:keys [publicKey]} (get result (keyword path))]
(if error
(on-failure error)
(on-success publicKey))))))))))))))))
(defn unpair-and-delete [_])
(defn get-keys
[{:keys [on-success on-failure pin]}]
(with-pin pin
on-failure
(if @derived-acc
(let [[id account-keys] (multiaccount->keys @derived-acc)]
(swap! state assoc-in
[:application-info :key-uid]
(:key-uid account-keys))
(native-module/multiaccount-store-derived
id
(:key-uid account-keys)
[constants/path-wallet-root
constants/path-eip1581
constants/path-whisper
constants/path-default-wallet]
account-password
#(on-success account-keys)))
#(on-success
{:key-uid (get-in @state [:application-info :key-uid])
:instance-uid (get-in @state [:application-info :instance-uid])
:encryption-public-key account-password}))))
(def import-keys get-keys)
(defn sign
[{:keys [pin data path typed? on-success on-failure] :as card}]
(with-pin pin
on-failure
#(let [address
(if path
(reduce
(fn [_ {:keys [address] :as acc}]
(when (= path (:path acc))
(reduced address)))
nil
(:profile/wallet-accounts @re-frame.db/app-db))
(-> (:profile/wallet-accounts @re-frame.db/app-db)
first
:address))
password account-password]
(if-not typed?
(let [params (types/clj->json
{:account address
:password password
:data (or data (str "0x" (:hash card)))})]
(native-module/sign-message
params
(fn [res]
(let [signature (-> res
types/json->clj
:result
address/normalized-hex)]
(on-success signature)))))
(native-module/sign-typed-data
data
address
password
(fn [res]
(let [signature (-> res
types/json->clj
:result
address/normalized-hex)]
(on-success signature))))))))
(defn sign-typed-data
[args]
(log/warn "sign-typed-data not implemented" args))
(defrecord SimulatedKeycard []
keycard/Keycard
(keycard/start-nfc [_this args]
(log/debug "simulated card start-nfc")
(start-nfc args))
(keycard/stop-nfc [_this args]
(log/debug "simulated card stop-nfc")
(stop-nfc args))
(keycard/set-nfc-message [_this args]
(log/debug "simulated card set-nfc-message")
(set-nfc-message args))
(keycard/check-nfc-support [_this args]
(log/debug "simulated card check-nfc-support")
(check-nfc-support args))
(keycard/check-nfc-enabled [_this args]
(log/debug "simulated card check-nfc-enabled")
(check-nfc-enabled args))
(keycard/open-nfc-settings [_this]
(log/debug "simulated card open-nfc-setting")
(open-nfc-settings))
(keycard/register-card-events [_this args]
(log/debug "simulated card register-card-event")
(register-card-events args))
(keycard/set-pairings [_this args]
(log/debug "simulated card set-pairings")
(set-pairings args))
(keycard/on-card-connected [_this callback]
(log/debug "simulated card on-card-connected")
(on-card-connected callback))
(keycard/on-card-disconnected [_this callback]
(log/debug "simulated card on-card-disconnected")
(on-card-disconnected callback))
(keycard/remove-event-listener [_this event]
(log/debug "simulated card remove-event-listener")
(remove-event-listener event))
(keycard/remove-event-listeners [_this]
(log/debug "simulated card remove-event-listener")
(remove-event-listeners))
(keycard/get-application-info [_this args]
(log/debug "simulated card get-application-info")
(get-application-info args))
(keycard/factory-reset [_this args]
(log/debug "simulated card factory-reset")
(factory-reset args))
(keycard/install-applet [_this args]
(log/debug "simulated card install-applet")
(install-applet args))
(keycard/init-card [_this args]
(log/debug "simulated card init-card")
(init-card args))
(keycard/install-applet-and-init-card [_this args]
(log/debug "simulated card install-applet-and-init-card")
(install-applet-and-init-card args))
(keycard/pair [_this args]
(log/debug "simulated card pair")
(pair args))
(keycard/generate-and-load-key [_this args]
(log/debug "simulated card generate-and-load-key")
(generate-and-load-key args))
(keycard/unblock-pin [_this args]
(log/debug "simulated card unblock-pin")
(unblock-pin args))
(keycard/verify-pin [_this args]
(log/debug "simulated card verify-pin")
(verify-pin args))
(keycard/change-pin [_this args]
(log/debug "simulated card change-pin")
(change-pin args))
(keycard/change-puk [_this args]
(log/debug "simulated card change-puk")
(change-puk args))
(keycard/change-pairing [_this args]
(log/debug "simulated card change-pairing")
(change-pairing args))
(keycard/unpair [_this args]
(log/debug "simulated card unpair")
(unpair args))
(keycard/delete [_this args]
(log/debug "simulated card delete")
(delete args))
(keycard/remove-key [_this args]
(log/debug "simulated card remove-key")
(remove-key args))
(keycard/remove-key-with-unpair [_this args]
(log/debug "simulated card remove-key-with-unpair")
(remove-key-with-unpair args))
(keycard/export-key [_this args]
(log/debug "simulated card export-key")
(export-key args))
(keycard/unpair-and-delete [_this args]
(log/debug "simulated card unpair-and-delete")
(unpair-and-delete args))
(keycard/import-keys [_this args]
(log/debug "simulated card import-keys")
(import-keys args))
(keycard/get-keys [_this args]
(log/debug "simulated card get-keys")
(get-keys args))
(keycard/sign [_this args]
(log/debug "simulated card sign")
(sign args)))
@@ -0,0 +1,32 @@
(ns legacy.status-im.keycard.test-menu
(:require
[legacy.status-im.keycard.simulated-keycard :as simulated-keycard]
[legacy.status-im.ui.components.react :as react]))
(defn button
[label accessibility-label handler]
[react/view
{:style {:width 20
:height 30
:justify-content :center
:align-items :center}}
[react/text
{:on-press handler
:style {:font-size 8}
:accessibility-label accessibility-label}
label]])
(defn test-menu
[]
[react/view
{:style {:position :absolute
:top 70
:right 0
:width 50
:justify-content :center
:align-items :center}}
[button "conn" :connect-card simulated-keycard/connect-card]
[button "conn sell" :connect-selected-card simulated-keycard/connect-selected-card]
[button "pair" :connect-pairing-card simulated-keycard/connect-pairing-card]
[button "disc" :disconnect-card simulated-keycard/disconnect-card]
[button "res" :keycard-reset-state simulated-keycard/reset-state]])
+187
View File
@@ -0,0 +1,187 @@
(ns legacy.status-im.keycard.unpair
(:require
[legacy.status-im.keycard.common :as common]
[legacy.status-im.multiaccounts.update.core :as multiaccounts.update]
[re-frame.core :as re-frame]
[status-im.navigation.events :as navigation]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(rf/defn unpair-card-pressed
{:events [:keycard-settings.ui/unpair-card-pressed]}
[_]
{:ui/show-confirmation {:title (i18n/label :t/unpair-card)
:content (i18n/label :t/unpair-card-confirmation)
:confirm-button-text (i18n/label :t/yes)
:cancel-button-text (i18n/label :t/no)
:on-accept #(re-frame/dispatch
[:keycard-settings.ui/unpair-card-confirmed])
:on-cancel #()}})
(rf/defn unpair-card-confirmed
{:events [:keycard-settings.ui/unpair-card-confirmed]}
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db (assoc-in db
[:keycard :pin]
{:enter-step :current
:current []
:puk []
:status nil
:error-label nil
:on-verified :keycard/unpair})}
(common/navigate-to-enter-pin-screen)))
(rf/defn unpair
{:events [:keycard/unpair]}
[{:keys [db]}]
(let [pin (common/vector->string (get-in db [:keycard :pin :current]))]
{:keycard/unpair {:pin pin}}))
(rf/defn unpair-and-delete
{:events [:keycard/unpair-and-delete]}
[cofx]
(common/show-connection-sheet
cofx
{:on-card-connected :keycard/unpair-and-delete
:handler
(fn [{:keys [db]}]
(let [pin (common/vector->string (get-in db [:keycard :pin :current]))]
{:keycard/unpair-and-delete
{:pin pin}}))}))
(rf/defn remove-pairing-from-multiaccount
[cofx {:keys [remove-instance-uid?]}]
(rf/merge cofx
(multiaccounts.update/multiaccount-update
:keycard-pairing
nil
{})
(multiaccounts.update/multiaccount-update
:keycard-paired-on
nil
{})
(when remove-instance-uid?
(multiaccounts.update/multiaccount-update
:keycard-instance-uid
nil
{}))))
(rf/defn on-unpair-success
{:events [:keycard.callback/on-unpair-success]}
[{:keys [db] :as cofx}]
(let [instance-uid (get-in db [:keycard :application-info :instance-uid])
pairings (get-in db [:keycard :pairings])]
(rf/merge
cofx
{:db (-> db
(assoc-in [:keycard :secrets] nil)
(update-in [:keycard :pairings] dissoc (keyword instance-uid))
(assoc-in [:keycard :pin]
{:status nil
:error-label nil
:on-verified nil}))
:keycard/persist-pairings (dissoc pairings (keyword instance-uid))
:effects.utils/show-popup {:title ""
:content (i18n/label :t/card-unpaired)}}
(common/clear-on-card-connected)
(remove-pairing-from-multiaccount nil)
(navigation/navigate-to :keycard-settings nil))))
(rf/defn on-unpair-error
{:events [:keycard.callback/on-unpair-error]}
[{:keys [db] :as cofx} error]
(log/debug "[keycard] unpair error" error)
(rf/merge cofx
{:db (assoc-in db
[:keycard :pin]
{:status nil
:error-label nil
:on-verified nil})
:keycard/get-application-info nil
:effects.utils/show-popup {:title ""
:content (i18n/label :t/something-went-wrong)}}
(common/clear-on-card-connected)
(navigation/navigate-to :keycard-settings nil)))
(rf/defn remove-key-with-unpair
{:events [:keycard/remove-key-with-unpair]}
[cofx]
(common/show-connection-sheet
cofx
{:on-card-connected :keycard/remove-key-with-unpair
:handler
(fn [{:keys [db]}]
(let [pin (common/vector->string (get-in db [:keycard :pin :current]))]
{:keycard/remove-key-with-unpair
{:pin pin}}))}))
(defn handle-account-removal
[{:keys [db] :as cofx} keys-removed-from-card?]
(let [key-uid (get-in db [:profile/profile :key-uid])
instance-uid (get-in db [:keycard :application-info :instance-uid])
pairings (get-in db [:keycard :pairings])]
(rf/merge
cofx
{:db (-> db
(update :profile/profiles-overview dissoc key-uid)
(assoc-in [:keycard :secrets] nil)
(update-in [:keycard :pairings]
dissoc
(keyword instance-uid))
(update-in [:keycard :pairings] dissoc instance-uid)
(assoc-in [:keycard :whisper-public-key] nil)
(assoc-in [:keycard :wallet-address] nil)
(assoc-in [:keycard :application-info] nil)
(assoc-in [:keycard :pin]
{:status nil
:error-label nil
:on-verified nil}))
:keycard/persist-pairings (dissoc pairings (keyword instance-uid))
:effects.utils/show-popup {:title (i18n/label (if keys-removed-from-card?
:t/profile-deleted-title
:t/database-reset-title))
:content (i18n/label (if keys-removed-from-card?
:t/profile-deleted-keycard
:t/database-reset-content))
:on-dismiss #(re-frame/dispatch [:logout])}}
;;should be reimplemented :key-storage/delete-profile {:key-uid key-uid
;;:on-success #(log/debug "[keycard] remove account ok")
;; :on-error #(log/warn "[keycard] remove account: " %)}
(common/clear-on-card-connected)
(common/hide-connection-sheet))))
(rf/defn on-remove-key-success
{:events [:keycard.callback/on-remove-key-success]}
[cofx]
(handle-account-removal cofx true))
(rf/defn on-remove-key-error
{:events [:keycard.callback/on-remove-key-error]}
[{:keys [db] :as cofx} error]
(log/debug "[keycard] remove key error" error)
(let [tag-was-lost? (common/tag-lost? (:error error))]
(rf/merge cofx
(if tag-was-lost?
(rf/merge cofx
{:db (assoc-in db [:keycard :pin :status] nil)}
(common/set-on-card-connected :keycard/remove-key-with-unpair))
(common/show-wrong-keycard-alert)))))
(rf/defn on-unpair-and-delete-success
{:events [:keycard.callback/on-unpair-and-delete-success]}
[cofx]
(handle-account-removal cofx false))
(rf/defn on-unpair-and-delete-error
{:events [:keycard.callback/on-unpair-and-delete-error]}
[{:keys [db] :as cofx} error]
(log/debug "[keycard] unpair and delete error" error)
(let [tag-was-lost? (common/tag-lost? (:error error))]
(rf/merge cofx
(if tag-was-lost?
(rf/merge cofx
{:db (assoc-in db [:keycard :pin :status] nil)}
(common/set-on-card-connected :keycard/unpair-and-delete))
(common/show-wrong-keycard-alert)))))
+21 -2
View File
@@ -5,6 +5,16 @@
[react-native.platform :as platform]
[status-im.config :as config]))
(defn- add-custom-bootnodes
[config network all-bootnodes]
(let [bootnodes (as-> all-bootnodes $
(get $ network)
(vals $)
(map :address $))]
(if (seq bootnodes)
(assoc-in config [:ClusterConfig :BootNodes] bootnodes)
config)))
(defn- add-log-level
[config log-level]
(if (empty? log-level)
@@ -114,8 +124,10 @@
waku-nodes (get config/waku-nodes-config fleet-key)
rendezvous-nodes (pick-nodes 3 (vals (:rendezvous current-fleet)))
{:keys [installation-id log-level
waku-bloom-filter-mode]}
profile]
waku-bloom-filter-mode
custom-bootnodes custom-bootnodes-enabled?]}
profile
use-custom-bootnodes (get custom-bootnodes-enabled? current-network)]
(cond-> (get-in networks [current-network :config])
:always
(get-base-node-config)
@@ -132,6 +144,8 @@
(if wakuv2-enabled
waku-nodes
[])
:BootNodes
(if wakuv2-enabled [] (pick-nodes 4 (vals (:boot current-fleet))))
:TrustedMailServers
(if wakuv2-enabled [] (pick-nodes 6 (vals (:mail current-fleet))))
:StaticNodes
@@ -171,6 +185,11 @@
:RequireTopics (get-topics current-network)
:StatusAccountsConfig {:Enabled true})
(and
config/bootnodes-settings-enabled?
use-custom-bootnodes)
(add-custom-bootnodes current-network custom-bootnodes)
:always
(add-log-level log-level))))
+650
View File
@@ -0,0 +1,650 @@
(ns legacy.status-im.signing.core
(:require
[clojure.set :as set]
[clojure.string :as string]
[legacy.status-im.ethereum.tokens :as tokens]
[legacy.status-im.keycard.card :as keycard.card]
[legacy.status-im.keycard.common :as keycard.common]
[legacy.status-im.signing.eip1559 :as eip1559]
[legacy.status-im.signing.keycard :as signing.keycard]
[legacy.status-im.utils.deprecated-types :as types]
[legacy.status-im.utils.hex :as utils.hex]
[legacy.status-im.utils.utils :as utils]
[native-module.core :as native-module]
[re-frame.core :as re-frame]
[status-im.common.json-rpc.events :as json-rpc]
[status-im.constants :as constants]
[taoensso.timbre :as log]
[utils.address :as address]
[utils.ethereum.chain :as chain]
[utils.ethereum.eip.eip55 :as eip55]
[utils.i18n :as i18n]
[utils.money :as money]
[utils.re-frame :as rf]
[utils.security.core :as security]))
(re-frame/reg-fx
:signing/send-transaction-fx
(fn [{:keys [tx-obj hashed-password cb]}]
(native-module/send-transaction (types/clj->json tx-obj)
hashed-password
cb)))
(re-frame/reg-fx
:signing/show-transaction-error
(fn [message]
(utils/show-popup (i18n/label :t/transaction-failed) message)))
(re-frame/reg-fx
:signing/show-transaction-result
(fn []
(utils/show-popup (i18n/label :t/transaction-sent) (i18n/label :t/transaction-description))))
(re-frame/reg-fx
:signing.fx/sign-message
(fn [{:keys [params on-completed]}]
(native-module/sign-message (types/clj->json params)
on-completed)))
(re-frame/reg-fx
:signing.fx/recover-message
(fn [{:keys [params on-completed]}]
(native-module/recover-message (types/clj->json params)
on-completed)))
(re-frame/reg-fx
:signing.fx/sign-typed-data
(fn [{:keys [v4 data account on-completed hashed-password]}]
(if v4
(native-module/sign-typed-data-v4 data account hashed-password on-completed)
(native-module/sign-typed-data data account hashed-password on-completed))))
(defn get-contact
[db to]
(let [to (utils.hex/normalize-hex to)]
(or
(get-in db [:contacts/contacts to])
{:address (address/normalized-hex to)})))
(rf/defn change-password
{:events [:signing.ui/password-is-changed]}
[{db :db} password]
(let [unmasked-pass (security/safe-unmask-data password)]
{:db (update db
:signing/sign assoc
:password password
:error nil
:enabled? (and unmasked-pass (> (count unmasked-pass) 5)))}))
(rf/defn sign-message
[{{:signing/keys [sign tx] :as db} :db}]
(let [{{:keys [data typed? from v4]} :message} tx
{:keys [in-progress? password]} sign
from from
hashed-password (native-module/sha3 (security/safe-unmask-data
password))]
(when-not in-progress?
(merge
{:db (update db :signing/sign assoc :error nil :in-progress? true)}
(if typed?
{:signing.fx/sign-typed-data {:v4 v4
:data data
:account from
:hashed-password hashed-password
:on-completed #(re-frame/dispatch
[:signing/sign-message-completed %])}}
{:signing.fx/sign-message {:params {:data data
:password hashed-password
:account from}
:on-completed #(re-frame/dispatch [:signing/sign-message-completed
%])}})))))
(rf/defn send-transaction
{:events [:signing.ui/sign-is-pressed]}
[{{:signing/keys [sign tx] :ens/keys [registration] :as db} :db :as cofx}]
(let [{:keys [in-progress? password]} sign
{:keys [tx-obj gas gasPrice maxPriorityFeePerGas
maxFeePerGas message nonce]} tx
hashed-password (native-module/sha3 (security/safe-unmask-data
password))
{:keys [action username custom-domain?]} registration
{:keys [public-key]} (:profile/profile db)
chain-id (chain/chain-id db)]
(if message
(sign-message cofx)
(let [tx-obj-to-send (merge tx-obj
(when gas
{:gas (str "0x" (native-module/number-to-hex gas))})
(when gasPrice
{:gasPrice (str "0x" (native-module/number-to-hex gasPrice))})
(when nonce
{:nonce (str "0x" (native-module/number-to-hex nonce))})
(when maxPriorityFeePerGas
{:maxPriorityFeePerGas (str "0x"
(native-module/number-to-hex
(js/parseInt maxPriorityFeePerGas)))})
(when maxFeePerGas
{:maxFeePerGas (str "0x"
(native-module/number-to-hex
(js/parseInt maxFeePerGas)))}))
cb #(re-frame/dispatch
[:signing/transaction-completed %
tx-obj-to-send hashed-password])
watch-ens-tx-fn #(re-frame/dispatch
[:transactions/watch-transaction
%
{:trigger-fn
(fn [_ {:keys [hash type]}]
(and (= hash %)
(contains? #{:outbound :failed} type)))
:on-trigger
(fn [{:keys [type]}]
(case type
:outbound (do (rf/dispatch [:ens/clear-registration %])
(rf/dispatch [:ens/save-username custom-domain?
username false]))
:failed (rf/dispatch [:ens/update-ens-tx-state :failure username
custom-domain? %])
nil))}])]
(when-not in-progress?
(cond-> {:db (update db :signing/sign assoc :error nil :in-progress? true)}
(nil? action) (assoc :signing/send-transaction-fx
{:tx-obj tx-obj-to-send
:hashed-password hashed-password
:cb cb})
(= action constants/ens-action-type-register) (assoc :json-rpc/call
[{:method "ens_register"
:params [chain-id tx-obj-to-send
hashed-password username
public-key]
:on-success
#(do
(cb (types/clj->json
{:result %}))
(watch-ens-tx-fn %))
:on-error #(cb (types/clj->json
{:error %}))}])
(= action
constants/ens-action-type-set-pub-key) (assoc :json-rpc/call
[{:method "ens_setPubKey"
:params [chain-id tx-obj-to-send
hashed-password username
public-key]
:on-success #(do (cb (types/clj->json
{:result %}))
(watch-ens-tx-fn %))
:on-error #(cb (types/clj->json
{:error
%}))}])))))))
(rf/defn prepare-unconfirmed-transaction
[{:keys [db now]} new-tx-hash
{:keys [value gasPrice maxFeePerGas maxPriorityFeePerGas gas data to from hash]} symbol amount]
(let [token (tokens/symbol->token (:wallet-legacy/all-tokens db) symbol)
from (eip55/address->checksum from)
;;if there is a hash in the tx object that means we resending transaction
old-tx-hash hash
gas-price (money/to-fixed (money/bignumber gasPrice))
gas-limit (money/to-fixed (money/bignumber gas))
tx {:timestamp now
:to to
:from from
:type :pending
:hash new-tx-hash
:data data
:token token
:symbol symbol
:value (if token
(money/to-fixed (money/unit->token amount (:decimals token)))
(money/to-fixed (money/bignumber value)))
:gas-price gas-price
:fee-cap maxFeePerGas
:tip-cap maxPriorityFeePerGas
:gas-limit gas-limit}]
(log/info "[signing] prepare-unconfirmed-transaction" tx)
{:db (-> db
;;remove old transaction, because we replace it with the new one
(update-in [:wallet-legacy :accounts from :transactions] dissoc old-tx-hash)
(assoc-in [:wallet-legacy :accounts from :transactions new-tx-hash] tx))}))
(defn get-method-type
[data]
(cond
(string/starts-with? data constants/method-id-transfer)
:transfer
(string/starts-with? data constants/method-id-approve)
:approve
(string/starts-with? data constants/method-id-approve-and-call)
:approve-and-call))
(defn get-transfer-token
[db to data]
(let [{:keys [decimals] :as token} (tokens/address->token (:wallet-legacy/all-tokens db) to)]
(when (and token data (string? data))
(when-let [type (get-method-type data)]
(let [[address value _] (native-module/decode-parameters
(str "0x" (subs data 10))
(if (= type :approve-and-call)
["address" "uint256" "bytes"]
["address" "uint256"]))]
(when (and address value)
{:to address
:contact (get-contact db address)
:contract to
:approve? (not= type :transfer)
:value value
:amount (money/to-fixed (money/token->unit value decimals))
:token token
:symbol (:symbol token)}))))))
(defn parse-tx-obj
[db {:keys [from to value data cancel?] :as tx}]
(merge {:from {:address from}
:cancel? cancel?
:hash (:hash tx)}
(if (nil? to)
{:contact {:name (i18n/label :t/new-contract)}}
(let [eth-value (when value (money/bignumber value))
eth-amount (when eth-value (money/to-fixed (money/wei->ether eth-value)))
token (get-transfer-token db to data)]
(cond
(and eth-amount (or (not (.equals ^js (money/bignumber 0) ^js eth-amount)) (nil? data)))
{:to to
:contact (get-contact db to)
:symbol :ETH
:value value
:amount (str eth-amount)
:token (tokens/asset-for (:wallet-legacy/all-tokens db)
(chain/get-current-network db)
:ETH)}
(not (nil? token))
token
:else
{:to to
:contact {:address (address/normalized-hex to)}})))))
(defn prepare-tx
[db {{:keys [data gas gasPrice maxFeePerGas maxPriorityFeePerGas] :as tx-obj} :tx-obj :as tx}]
(merge
tx
(parse-tx-obj db tx-obj)
{:data data
:gas (when gas (money/bignumber gas))
:gasPrice (when gasPrice (money/bignumber gasPrice))
:maxFeePerGas (when maxFeePerGas
(money/bignumber maxFeePerGas))
:maxPriorityFeePerGas (when maxPriorityFeePerGas
(money/bignumber maxPriorityFeePerGas))}))
(defn hex-to-utf8
[s]
(let [utf8 (native-module/hex-to-utf8 s)]
(if (empty? utf8)
nil
utf8)))
(defn hex->text
"Converts a hexstring to UTF8 text."
[data]
(or (hex-to-utf8 data) data))
(rf/defn show-sign
[{:keys [db] :as cofx}]
(let [{:signing/keys [queue]} db
{{:keys [gas gasPrice maxFeePerGas] :as tx-obj} :tx-obj
{:keys [data typed? pinless?] :as message} :message
:as tx}
(last queue)
keycard-multiaccount? (boolean (get-in db [:profile/profile :keycard-pairing]))
wallet-set-up-passed? (get-in db [:profile/profile :wallet-set-up-passed?])]
(if message
(rf/merge
cofx
{:db (assoc db
:signing/queue (drop-last queue)
:signing/tx tx
:signing/sign {:type (cond pinless? :pinless
keycard-multiaccount? :keycard
:else :password)
:formatted-data (if typed?
(types/js->pretty-json
(types/json->js data))
(hex->text data))
:keycard-step (when pinless? :connect)})
:show-signing-sheet nil}
#(when-not wallet-set-up-passed?
{:dispatch-later [{:dispatch [:show-popover {:view :signing-phrase}] :ms 200}]})
(when pinless?
(keycard.card/start-nfc {:on-success #(re-frame/dispatch [:keycard.callback/start-nfc-success])
:on-failure #(re-frame/dispatch
[:keycard.callback/start-nfc-failure])})
(signing.keycard/hash-message
{:data data
:typed? true
:on-completed #(re-frame/dispatch [:keycard/store-hash-and-sign-typed %])})))
(rf/merge
cofx
{:db (assoc db
:signing/queue (drop-last queue)
:signing/tx (prepare-tx db tx))
:show-signing-sheet nil
:dismiss-keyboard nil}
#(when-not wallet-set-up-passed?
{:dispatch-later [{:dispatch [:show-popover {:view :signing-phrase}] :ms 200}]})
#(when-not gas
{:db (assoc-in (:db %) [:signing/edit-fee :gas-loading?] true)
:signing/update-estimated-gas {:obj (-> tx-obj
(dissoc :gasPrice)
(update :maxFeePerGas
(fn [fee]
(some-> fee
money/bignumber
(money/mul 2)
money/to-hex))))
:success-event :signing/update-estimated-gas-success
:error-event :signing/update-estimated-gas-error}})
(fn [cofx]
{:db (assoc-in (:db cofx) [:signing/edit-fee :gas-price-loading?] true)
:signing/update-gas-price
{:success-callback #(re-frame/dispatch
[:wallet-legacy.send/update-gas-price-success :signing/tx % tx-obj])
:error-callback #(re-frame/dispatch [:signing/update-gas-price-error %])
:network-id (get-in (chain/current-network db)
[:config :NetworkId])}})))))
(rf/defn check-queue
[{:keys [db] :as cofx}]
(let [{:signing/keys [tx queue]} db]
(when (and (not tx) (seq queue))
(show-sign cofx))))
(rf/defn send-transaction-message
{:events [:sign/send-transaction-message]}
[cofx chat-id value contract transaction-hash signature]
{:json-rpc/call [{:method "wakuext_sendTransaction"
;; We make sure `value` is serialized as string, and not as an integer or
;; big-int
:params [chat-id (str value) contract transaction-hash
(or (:result (types/json->clj signature))
(address/normalized-hex signature))]
:js-response true
:on-success
#(re-frame/dispatch [:transport/message-sent %])}]})
(rf/defn send-accept-request-transaction-message
{:events [:sign/send-accept-transaction-message]}
[cofx message-id transaction-hash signature]
{:json-rpc/call [{:method "wakuext_acceptRequestTransaction"
:params [transaction-hash message-id
(or (:result (types/json->clj signature))
(address/normalized-hex signature))]
:js-response true
:on-success
#(re-frame/dispatch [:transport/message-sent %])}]})
(rf/defn transaction-result
[{:keys [db] :as cofx} result tx-obj]
(let [{:keys [on-result symbol amount from]} (get db :signing/tx)]
(rf/merge cofx
{:db (dissoc db :signing/tx :signing/sign)
:signing/show-transaction-result nil}
(prepare-unconfirmed-transaction result tx-obj symbol amount)
(check-queue)
#(when on-result
{:dispatch (conj on-result result)}))))
(rf/defn command-transaction-result
[{:keys [db] :as cofx} transaction-hash hashed-password
{:keys [message-id chat-id from] :as tx-obj}]
(let [{:keys [on-result symbol amount contract value]} (get db :signing/tx)
data (str (get-in db [:profile/profile :public-key])
(subs transaction-hash 2))]
(rf/merge
cofx
{:db (dissoc db :signing/tx :signing/sign)}
(if (keycard.common/keycard-multiaccount? db)
(signing.keycard/hash-message
{:data data
:on-completed
(fn [hash]
(re-frame/dispatch
[:keycard/sign-message
{:tx-hash transaction-hash
:message-id message-id
:chat-id chat-id
:value value
:contract contract
:data data}
hash]))})
(fn [_]
{:signing.fx/sign-message
{:params {:data data
:password hashed-password
:account from}
:on-completed
(fn [res]
(re-frame/dispatch
(if message-id
[:sign/send-accept-transaction-message message-id transaction-hash res]
[:sign/send-transaction-message
chat-id value contract transaction-hash res])))}
:signing/show-transaction-result nil}))
(prepare-unconfirmed-transaction transaction-hash tx-obj symbol amount)
(check-queue)
#(when on-result
{:dispatch (conj on-result transaction-hash)}))))
(rf/defn transaction-error
[{:keys [db]} {:keys [code message]}]
(let [on-error (get-in db [:signing/tx :on-error])]
(if (= code constants/send-transaction-err-decrypt)
;;wrong password
{:db (assoc-in db [:signing/sign :error] (i18n/label :t/wrong-password))}
(merge {:db (dissoc db :signing/tx :signing/sign)
:signing/show-transaction-error message}
(when on-error
{:dispatch (conj on-error message)})))))
(rf/defn dissoc-signing-db-entries-and-check-queue
{:events [:signing/dissoc-entries-and-check-queue]}
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db (dissoc db :signing/tx :signing/sign)}
check-queue))
(rf/defn sign-message-completed
{:events [:signing/sign-message-completed]}
[{:keys [db] :as cofx} result]
(let [{:keys [result error]} (types/json->clj result)
on-result (get-in db [:signing/tx :on-result])]
(if error
{:db (update db
:signing/sign
assoc
:error (if (= 5 (:code error))
(i18n/label :t/wrong-password)
(:message error))
:in-progress? false)}
(rf/merge cofx
(when-not (= (-> db :signing/sign :type) :pinless)
(dissoc-signing-db-entries-and-check-queue))
#(when (= (-> db :signing/sign :type) :pinless)
{:dispatch-later [{:ms 3000
:dispatch [:signing/dissoc-entries-and-check-queue]}]})
#(when on-result
{:dispatch (conj on-result result)})))))
(rf/defn transaction-completed
{:events [:signing/transaction-completed]
:interceptors [(re-frame/inject-cofx :random-id-generator)]}
[cofx response tx-obj hashed-password]
(log/info "transaction-completed" "tx-obj" tx-obj "response" response)
(let [cofx-in-progress-false (assoc-in cofx [:db :signing/sign :in-progress?] false)
{:keys [result error]} (types/json->clj response)]
(if error
(transaction-error cofx-in-progress-false error)
(if (:command? tx-obj)
(command-transaction-result cofx-in-progress-false result hashed-password tx-obj)
(transaction-result cofx-in-progress-false result tx-obj)))))
(rf/defn discard
"Discrad transaction signing"
{:events [:signing.ui/cancel-is-pressed]}
[{:keys [db] :as cofx}]
(let [{:keys [on-error]} (get-in db [:signing/tx])]
(rf/merge cofx
{:db (-> db
(assoc-in [:keycard :pin :status] nil)
(dissoc :signing/tx :signing/sign))
:hide-signing-sheet nil}
(check-queue)
(keycard.common/hide-connection-sheet)
(keycard.common/clear-pin)
#(when on-error
{:dispatch (conj on-error "transaction was cancelled by user")}))))
(defn normalize-tx-obj
[_db tx]
(update-in tx [:tx-obj :from] #(eip55/address->checksum %)))
(rf/defn sign
"Signing transaction or message, shows signing sheet
tx
{:tx-obj - transaction object to send https://github.com/ethereum/wiki/wiki/JavaScript-API#parameters-25
:message {:address :data :typed? } - message data to sign
:on-result - re-frame event vector
:on-error - re-frame event vector}"
{:events [:signing.ui/sign]}
[{:keys [db] :as cofx} tx]
(rf/merge cofx
{:db (update db :signing/queue conj (normalize-tx-obj db tx))}
(check-queue)))
(rf/defn sign-transaction-button-clicked-from-chat
{:events [:wallet-legacy.ui/sign-transaction-button-clicked-from-chat]}
[{:keys [db] :as cofx} {:keys [to amount from token]}]
(let [{:keys [symbol address]} token
amount-hex (str "0x" (native-module/number-to-hex amount))
to-norm (address/normalized-hex (if (string? to) to (:address to)))
from-address (:address from)
identity (:current-chat-id db)
db (dissoc db :wallet-legacy/prepare-transaction :signing/edit-fee)]
(if to-norm
(rf/merge
cofx
{:db db}
(sign {:tx-obj (if (= symbol :ETH)
{:to to-norm
:from from-address
:chat-id identity
:command? true
:value amount-hex}
{:to (address/normalized-hex address)
:from from-address
:chat-id identity
:command? true
:data (native-module/encode-transfer to-norm amount-hex)})}))
{:db db
:json-rpc/call
[{:method "wakuext_requestAddressForTransaction"
:params [(:current-chat-id db)
from-address
amount
(when-not (= symbol :ETH)
address)]
:js-response true
:on-success #(re-frame/dispatch [:transport/message-sent %])}]})))
(rf/defn sign-transaction-button-clicked-from-request
{:events [:wallet-legacy.ui/sign-transaction-button-clicked-from-request]}
[{:keys [db] :as cofx} {:keys [amount from token]}]
(let [{:keys [request-parameters chat-id]} (:wallet-legacy/prepare-transaction db)
{:keys [symbol address]} token
amount-hex (str "0x" (native-module/number-to-hex amount))
to-norm (:address request-parameters)
from-address (:address from)]
(rf/merge cofx
{:db (dissoc db :wallet-legacy/prepare-transaction :signing/edit-fee)}
(fn [cofx]
(sign
cofx
{:tx-obj (if (= symbol :ETH)
{:to to-norm
:from from-address
:message-id (:id request-parameters)
:chat-id chat-id
:command? true
:value amount-hex}
{:to (address/normalized-hex address)
:from from-address
:command? true
:message-id (:id request-parameters)
:chat-id chat-id
:data (native-module/encode-transfer to-norm amount-hex)})})))))
(rf/defn sign-transaction-button-clicked
{:events [:wallet-legacy.ui/sign-transaction-button-clicked]}
[{:keys [db] :as cofx} {:keys [to amount from token gas gasPrice maxFeePerGas maxPriorityFeePerGas]}]
(let [{:keys [symbol address]} token
amount-hex (str "0x" (native-module/number-to-hex amount))
to-norm (address/normalized-hex (if (string? to) to (:address to)))
from-address (:address from)]
(rf/merge cofx
{:db (dissoc db :wallet-legacy/prepare-transaction :signing/edit-fee)}
(sign
{:tx-obj (merge (if (eip1559/sync-enabled?)
{:from from-address
:gas gas
;; per eip1559
:maxFeePerGas maxFeePerGas
:maxPriorityFeePerGas maxPriorityFeePerGas}
{:from from-address
;;gas and gasPrice from qr (eip681)
:gas gas
:gasPrice gasPrice})
(if (= symbol :ETH)
{:to to-norm
:value amount-hex}
{:to (address/normalized-hex address)
:data (native-module/encode-transfer to-norm amount-hex)}))}))))
(re-frame/reg-fx
:signing/get-transaction-by-hash-fx
(fn [[tx-hash handler]]
(json-rpc/call
{:method "eth_getTransactionByHash"
:params [tx-hash]
:on-success handler})))
(rf/defn cancel-transaction-pressed
{:events [:signing.ui/cancel-transaction-pressed]}
[_ hash]
{:signing/get-transaction-by-hash-fx [hash #(re-frame/dispatch [:signing/cancel-transaction %])]})
(rf/defn increase-gas-pressed
{:events [:signing.ui/increase-gas-pressed]}
[_ hash]
{:signing/get-transaction-by-hash-fx [hash #(re-frame/dispatch [:signing/increase-gas %])]})
(rf/defn cancel-transaction
{:events [:signing/cancel-transaction]}
[cofx {:keys [from nonce hash]}]
(when (and from nonce hash)
(sign cofx
{:tx-obj {:from from
:to from
:nonce nonce
:value "0x0"
:cancel? true
:hash hash}})))
(rf/defn increase-gas
{:events [:signing/increase-gas]}
[cofx {:keys [from nonce] :as tx}]
(when (and from nonce)
(sign cofx
{:tx-obj (-> tx
(select-keys [:from :to :value :input :gas :nonce :hash])
(set/rename-keys {:input :data}))})))
@@ -0,0 +1,64 @@
(ns legacy.status-im.signing.core-test
(:require
[cljs.test :refer-macros [deftest is testing]]
[legacy.status-im.signing.core :as signing]
[native-module.core :as native-module]))
(deftest signing-test
(testing "showing sheet"
(let [to "0x2f88d65f3cb52605a54a833ae118fb1363acccd2"
contract "0xc55cf4b03948d7ebc8b9e8bad92643703811d162"
amount1-hex (str "0x" (native-module/number-to-hex "10000000000000000000"))
amount2-hex (str "0x" (native-module/number-to-hex "100000000000000000000"))
data (native-module/encode-transfer to amount2-hex)
first-tx {:tx-obj {:to to
:from nil
:value amount1-hex}}
second-tx {:tx-obj {:to contract
:from nil
:data data}}
sign-first (signing/sign {:db {}} first-tx)
sign-second (signing/sign sign-first second-tx)]
(testing "after fist transaction"
(testing "signing in progress"
(is (get-in sign-first [:db :signing/tx])))
(testing "qieue is empty"
(is (= (get-in sign-first [:db :signing/queue]) '())))
(testing "first tx object is parsed"
(is (= (dissoc (get-in sign-first [:db :signing/tx]) :token :hash :cancel?)
(merge first-tx
{:from {:address nil}
:gas nil
:gasPrice nil
:maxFeePerGas nil
:maxPriorityFeePerGas nil
:data nil
:to to
:contact {:address to}
:symbol :ETH
:value "0x8ac7230489e80000"
:amount "10"})))))
(testing "after second transaction"
(testing "signing still in progress"
(is (get-in sign-second [:db :signing/tx])))
(testing "queue is not empty, second tx in queue"
(is (= (get-in sign-second [:db :signing/queue]) (list second-tx))))
(testing "check queue does nothing"
(is (not (signing/check-queue sign-second))))
(let [first-discarded (signing/check-queue (update sign-second :db dissoc :signing/tx))]
(testing "after first transaction discarded"
(testing "signing still in progress"
(is (get-in first-discarded [:db :signing/tx])))
(testing "qieue is empty"
(is (= (get-in first-discarded [:db :signing/queue]) '())))
(testing "second tx object is parsed"
(is (= (dissoc (get-in first-discarded [:db :signing/tx]) :token :hash :cancel?)
(merge second-tx
{:from {:address nil}
:gas nil
:gasPrice nil
:maxFeePerGas nil
:maxPriorityFeePerGas nil
:data data
:to contract
:contact {:address contract}}))))))))))
+55
View File
@@ -0,0 +1,55 @@
(ns legacy.status-im.signing.eip1559
(:require
[re-frame.core :as re-frame]
[status-im.common.json-rpc.events :as json-rpc]))
(defonce london-activated? (atom false))
(defn london-is-definitely-activated
[network-id]
(contains? #{"1" "3"} network-id))
(defn on-block
[callback header]
(let [activated? (contains? header :baseFeePerGas)]
(reset! london-activated? activated?)
(callback activated?)))
(defn check-activation
[callback]
(json-rpc/call
{:method "eth_getBlockByNumber"
:params ["latest" false]
:on-success (partial on-block callback)
:on-error #(callback nil)}))
(defn sync-enabled? [] @london-activated?)
(defn enabled?
([] @london-activated?)
([network-id enabled-callback disabled-callback]
(let [definitely-activated? (london-is-definitely-activated network-id)
enabled-callback (or enabled-callback #())
disabled-callback (or disabled-callback #())]
(cond
definitely-activated?
(do
(reset! london-activated? true)
(enabled-callback))
(not definitely-activated?)
(check-activation
(fn [activated?]
(if activated?
(enabled-callback)
(disabled-callback))))
:else
(do
(reset! london-activated? false)
(disabled-callback))))))
(re-frame/reg-fx
:check-eip1559-activation
(fn [network-id]
(enabled? network-id nil nil)))
+430
View File
@@ -0,0 +1,430 @@
(ns legacy.status-im.signing.gas
(:require
[clojure.string :as string]
[legacy.status-im.bottom-sheet.events :as bottom-sheet]
[legacy.status-im.popover.core :as popover]
[legacy.status-im.signing.eip1559 :as eip1559]
[re-frame.core :as re-frame]
[status-im.common.json-rpc.events :as json-rpc]
[taoensso.timbre :as log]
[utils.ethereum.chain :as chain]
[utils.i18n :as i18n]
[utils.money :as money]
[utils.re-frame :as rf]))
(def min-gas-price-wei ^js (money/bignumber 1))
(def min-gas-units ^js (money/bignumber 21000))
(defmulti get-error-label-key (fn [type _] type))
(defmethod get-error-label-key :gasPrice
[_ value]
(cond
(not value) :t/invalid-number
(.lt ^js (money/->wei :gwei value) min-gas-price-wei) :t/wallet-send-min-wei
(-> (money/->wei :gwei value) .decimalPlaces pos?) :t/invalid-number))
(defmethod get-error-label-key :gas
[_ ^js value]
(cond
(not value) :t/invalid-number
(.lt value min-gas-units) :t/wallet-send-min-units
(-> value .decimalPlaces pos?) :t/invalid-number))
(defmethod get-error-label-key :default
[_ value]
(when (or (not value)
(<= value 0))
:t/invalid-number))
(defn calculate-max-fee
[^js gas ^js gasPrice]
(if (and gas gasPrice)
(money/to-fixed (money/wei->ether (.times gas gasPrice)))
"0"))
(defn edit-max-fee
[edit]
(let [gasPrice (get-in edit [:gasPrice :value-number])
gas (get-in edit [:gas :value-number])]
(assoc edit :max-fee (calculate-max-fee gas gasPrice))))
(defn build-edit
"Takes the previous edit, either :gas or :gas-price and a value as string.
Wei for gas, and gwei for gas price.
Validates them and sets max fee"
[edit-value k value]
(let [^js bn-value (money/bignumber value)
error-label-key (get-error-label-key k bn-value)
data (if error-label-key
{:value value
:max-fee 0
:error (i18n/label error-label-key)}
{:value value
:value-number (if (= :gasPrice k)
(money/->wei :gwei bn-value)
bn-value)})]
(-> edit-value
(assoc k data)
edit-max-fee)))
;; TODO(rasom): this number is almost arbitrary, I was able to sent txs with
;; 0.2 gwei tip, so it should be revisited lately
(def minimum-priority-fee-gwei
(money/bignumber 0.3))
(defn get-suggested-tip
[latest-priority-fee]
(money/bignumber latest-priority-fee))
(defn get-minimum-priority-fee
[latest-priority-fee]
(let [latest-priority-fee-bn (money/bignumber latest-priority-fee)
suggested-tip-gwei (money/wei->gwei (get-suggested-tip latest-priority-fee-bn))]
(if (money/greater-than minimum-priority-fee-gwei suggested-tip-gwei)
(money/div suggested-tip-gwei 2)
minimum-priority-fee-gwei)))
(defn get-suggestions-range
[latest-priority-fee]
(let [current-minimum-fee (get-minimum-priority-fee latest-priority-fee)]
[(if (money/greater-than minimum-priority-fee-gwei current-minimum-fee)
current-minimum-fee
minimum-priority-fee-gwei)
(money/wei->gwei (money/bignumber latest-priority-fee))]))
(def average-priority-fee
(money/wei->gwei (money/->wei :gwei 1.5)))
(defn validate-max-fee
[db]
(let [{:keys [maxFeePerGas maxPriorityFeePerGas]} (get db :signing/edit-fee)
latest-base-fee (money/wei->gwei
(money/bignumber
(get db :wallet-legacy/current-base-fee)))
fee-error (cond
(or (:error maxFeePerGas)
(:error maxPriorityFeePerGas))
nil
(money/greater-than latest-base-fee
(:value-number maxFeePerGas))
{:label (i18n/label :t/below-base-fee)
:severity :error})]
(if fee-error
(assoc-in db [:signing/edit-fee :maxFeePerGas :fee-error] fee-error)
(update-in db [:signing/edit-fee :maxFeePerGas] dissoc :fee-error))))
(defn validate-max-priority-fee
[db]
(let [{:keys [maxPriorityFeePerGas]} (get db :signing/edit-fee)
latest-priority-fee (get db :wallet-legacy/current-priority-fee)
fee-error (cond
(:error maxPriorityFeePerGas)
nil
(money/greater-than (get-minimum-priority-fee
(money/div
(money/wei->gwei (money/bignumber
latest-priority-fee))
2))
(:value-number maxPriorityFeePerGas))
{:label (i18n/label :t/low-tip)
:severity :error}
#_(money/greater-than average-priority-fee
(:value-number maxPriorityFeePerGas))
#_{:label (i18n/label :t/lower-than-average-tip)
:severity :error})]
(if fee-error
(assoc-in db [:signing/edit-fee :maxPriorityFeePerGas :fee-error] fee-error)
(update-in db [:signing/edit-fee :maxPriorityFeePerGas] dissoc :fee-error))))
(defn validate-eip1559-fees
[db]
(if (eip1559/sync-enabled?)
(reduce
(fn [acc f]
(f acc))
db
[validate-max-fee
validate-max-priority-fee])
db))
(rf/defn edit-value
{:events [:signing.edit-fee.ui/edit-value]}
[{:keys [db]} key value]
{:db (-> db
(assoc-in [:signing/edit-fee :selected-fee-option] :custom)
(update :signing/edit-fee build-edit key value)
validate-eip1559-fees)})
(defn get-fee-options
[tip slow normal fast]
(let [tip-bn (money/bignumber tip)
normal-bn (money/bignumber normal)
slow-bn (money/bignumber slow)
fast-bn (money/bignumber fast)]
{:normal
{:base-fee normal-bn
:tip tip-bn
:fee (money/add normal-bn tip-bn)}
:slow
{:base-fee slow-bn
:tip tip-bn
:fee (money/add slow-bn tip-bn)}
:fast
{:base-fee fast-bn
:tip tip-bn
:fee (money/add fast-bn tip-bn)}}))
(rf/defn set-fee-option
{:events [:signing.edit-fee.ui/set-option]}
[{:keys [db] :as cofx} option]
(let [tip (get db :wallet-legacy/current-priority-fee)
slow (get db :wallet-legacy/slow-base-fee)
normal (get db :wallet-legacy/normal-base-fee)
fast (get db :wallet-legacy/fast-base-fee)
{:keys [fee tip]} (get (get-fee-options tip slow normal fast) option)]
{:db (-> db
(assoc-in [:signing/edit-fee :selected-fee-option] option)
(update :signing/edit-fee
build-edit
:maxFeePerGas (money/wei->gwei fee))
(update :signing/edit-fee
build-edit
:maxPriorityFeePerGas (money/wei->gwei tip)))}))
(rf/defn set-priority-fee
{:events [:signing.edit-fee.ui/set-priority-fee]}
[{:keys [db]} value]
(let [{:keys [maxFeePerGas maxPriorityFeePerGas]}
(get db :signing/edit-fee)
latest-base-fee (get db :wallet-legacy/current-base-fee)
max-fee-value (:value-number maxFeePerGas)
max-priority-fee-value (:value-number maxPriorityFeePerGas)
new-value (money/bignumber value)
fee-without-tip (money/sub max-fee-value max-priority-fee-value)
base-fee (money/wei->gwei (money/bignumber latest-base-fee))
new-max-fee-value
(money/to-fixed
(if (money/greater-than base-fee fee-without-tip)
(money/add new-value base-fee)
(money/add new-value fee-without-tip)))]
{:db (-> db
(update :signing/edit-fee build-edit :maxPriorityFeePerGas value)
(update :signing/edit-fee build-edit :maxFeePerGas new-max-fee-value)
validate-eip1559-fees)}))
(rf/defn update-estimated-gas-success
{:events [:signing/update-estimated-gas-success]}
[{db :db} gas]
(when (contains? db :signing/tx)
{:db (-> db
(assoc-in [:signing/tx :gas] gas)
(assoc-in [:signing/edit-fee :gas-loading?] false))}))
(rf/defn update-gas-price-success
{:events [:signing/update-gas-price-success]}
[{db :db} price]
(if (eip1559/sync-enabled?)
(let [{:keys [normal-base-fee max-priority-fee]} price
max-priority-fee-bn (money/with-precision (get-suggested-tip
max-priority-fee)
0)]
{:db (-> db
(assoc-in [:signing/tx :maxFeePerGas]
(money/to-hex (money/add max-priority-fee-bn
(money/bignumber normal-base-fee))))
(assoc-in [:signing/tx :maxPriorityFeePerGas]
(money/to-hex max-priority-fee-bn))
(assoc-in [:signing/edit-fee :gas-price-loading?] false))})
{:db (-> db
(assoc-in [:signing/tx :gasPrice] price)
(assoc-in [:signing/edit-fee :gas-price-loading?] false))}))
(rf/defn update-estimated-gas-error
{:events [:signing/update-estimated-gas-error]}
[{db :db} {:keys [message]}]
(log/warn "signing/update-estimated-gas-error" message)
{:db (-> db
(assoc-in [:signing/edit-fee :gas-loading?] false)
(assoc-in [:signing/tx :gas-error-message] message))})
(rf/defn update-gas-price-error
{:events [:signing/update-gas-price-error]}
[{db :db}]
{:db (assoc-in db [:signing/edit-fee :gas-price-loading?] false)})
(rf/defn open-fee-sheet
{:events [:signing.ui/open-fee-sheet]}
[{{:signing/keys [tx] :as db} :db :as cofx} sheet-opts]
(let [{:keys [gas gasPrice maxFeePerGas maxPriorityFeePerGas]} tx
max-fee (money/to-fixed (money/wei->gwei
maxFeePerGas))
max-priority-fee (money/to-fixed (money/wei->gwei
maxPriorityFeePerGas))
edit-fee (reduce
(partial apply build-edit)
{:selected-fee-option
(get-in db
[:signing/edit-fee
:selected-fee-option])}
{:gas (money/to-fixed gas)
:gasPrice (money/to-fixed
(money/wei->gwei gasPrice))
:maxFeePerGas max-fee
:maxPriorityFeePerGas
max-priority-fee})]
(rf/merge cofx
{:db (assoc db :signing/edit-fee edit-fee)}
(bottom-sheet/show-bottom-sheet-old {:view sheet-opts}))))
(rf/defn submit-fee
{:events [:signing.edit-fee.ui/submit]}
[{{:signing/keys [edit-fee] :as db} :db :as cofx} force?]
(let [{:keys [gas gasPrice maxFeePerGas maxPriorityFeePerGas]} edit-fee
errors?
(keep
(fn [[k {:keys [fee-error]}]]
(when (= :error (:severity fee-error))
[k fee-error]))
edit-fee)]
(if (and (seq errors?)
(not force?))
(popover/show-popover cofx {:view :fees-warning})
(rf/merge cofx
{:db (update db
:signing/tx assoc
:gas (:value-number gas)
:gasPrice (:value-number gasPrice)
:maxFeePerGas (money/->wei :gwei (:value-number maxFeePerGas))
:maxPriorityFeePerGas (money/->wei :gwei
(:value-number maxPriorityFeePerGas)))}
(bottom-sheet/hide-bottom-sheet-old)))))
(rf/defn submit-nonce
{:events [:signing.nonce/submit]}
[{db :db :as cofx} nonce]
(rf/merge cofx
{:db (assoc-in db [:signing/tx :nonce] (if (string/blank? nonce) nil nonce))}
(bottom-sheet/hide-bottom-sheet-old)))
(re-frame/reg-fx
:signing/update-gas-price
(fn [{:keys [success-callback error-callback network-id] :as params}]
(eip1559/enabled?
network-id
(fn []
(json-rpc/call
{:method "eth_feeHistory"
;; NOTE(rasom): We don't need `reward` atm but if the last parameter is
;; `nil` request fails on some chains (particularly on xDai).
:params [101 "latest" [100]]
:on-success #(re-frame/dispatch [::header-fetched
(assoc params :fee-history %)])}))
(fn []
(json-rpc/call
{:method "eth_gasPrice"
:on-success #(success-callback (money/bignumber %))
:on-error (or error-callback #(log/warn "eth_gasPrice error" %))})))))
(def london-block-gas-limit (money/bignumber 30000000))
(defn calc-percentiles
[v ps]
(let [sorted-v (sort-by
identity
(fn [a b]
(utils.money/greater-than b a))
v)]
(reduce
(fn [acc p]
(assoc acc p (nth sorted-v p)))
{}
ps)))
;; It is very unlikely to be that small on mainnet, but on testnets current base
;; fee might be very small and using this value might slow transaction
(def minimum-base-fee (money/->wei :gwei (money/bignumber 1)))
(defn recommended-base-fee
[current perc20]
(let [fee
(cond (money/greater-than-or-equals current perc20)
current
(money/greater-than perc20 current)
perc20)]
(if (money/greater-than minimum-base-fee fee)
minimum-base-fee
fee)))
(defn slow-base-fee [_ perc10] perc10)
(defn fast-base-fee
[current]
(let [fee (money/mul current 2)]
(if (money/greater-than minimum-base-fee fee)
(money/mul minimum-base-fee 2)
fee)))
(defn check-base-fee
[{:keys [baseFeePerGas testnet?]}]
(let [all-base-fees (mapv money/bignumber baseFeePerGas)
next-base-fee (peek all-base-fees)
previous-fees (subvec all-base-fees 0 101)
current-base-fee (peek previous-fees)
percentiles (calc-percentiles previous-fees [10 20 80])]
{:normal-base-fee (money/to-hex
(if testnet?
(fast-base-fee next-base-fee)
(recommended-base-fee
next-base-fee
(get percentiles 20))))
:slow-base-fee (money/to-hex
(slow-base-fee
next-base-fee
(get percentiles 10)))
:fast-base-fee (money/to-hex (fast-base-fee next-base-fee))
:current-base-fee (money/to-hex current-base-fee)}))
(defn max-priority-fee-hex
[gas-price base-fee]
(money/to-hex
(money/sub (money/bignumber gas-price)
(money/bignumber base-fee))))
(rf/defn header-fetched
{:events [::header-fetched]}
[{{:networks/keys [current-network networks]} :db}
{:keys [error-callback success-callback fee-history] :as params}]
{:json-rpc/call
[;; NOTE(rasom): eth_maxPriorityFeePerGas is not supported by some networks
;; so it is more reliable to calculate maxPriorityFeePerGas using the value
;; returned by eth_gasPrice and current base fee.
{:method "eth_gasPrice"
:on-success #(success-callback
(let [{:keys [current-base-fee] :as base-fees}
(check-base-fee
(assoc fee-history
:testnet?
(chain/testnet?
(get-in networks [current-network :config :NetworkId]))))]
(merge {:max-priority-fee
(max-priority-fee-hex (money/bignumber %) current-base-fee)}
base-fees)))
:on-error (if error-callback
#(error-callback %)
#(log/error "Can't fetch header" %))}]})
(re-frame/reg-fx
:signing/update-estimated-gas
(fn [{:keys [obj success-event error-event]}]
(json-rpc/call
{:method "eth_estimateGas"
:params [obj]
:on-success #(re-frame/dispatch [success-event
(money/bignumber (if (= (int %) 21000) % (int (* % 1.2))))])
:on-error #(re-frame/dispatch [error-event %])})))
@@ -0,0 +1,39 @@
(ns legacy.status-im.signing.gas-test
(:require
[cljs.test :refer-macros [deftest is testing]]
[legacy.status-im.signing.gas :as signing.gas]))
(deftest build-edit-test
(testing "an invalid edit"
(let [actual (-> {}
(signing.gas/build-edit :gas "invalid")
(signing.gas/build-edit :gasPrice "0.00000000001"))]
(testing "it marks gasPrice as invalid"
(is (get-in actual [:gasPrice :error])))
(testing "it does not change value"
(is (= "0.00000000001" (get-in actual [:gasPrice :value]))))
(testing "it marks gas as invalid"
(is (get-in actual [:gas :error])))
(testing "it does not change gas value"
(is (= "invalid" (get-in actual [:gas :value]))))
(testing "it sets max-fee to 0"
(is (= "0" (:max-fee actual))))))
(testing "gas price in wei should be round"
(let [actual (-> {}
(signing.gas/build-edit :gas "21000")
(signing.gas/build-edit :gasPrice "0.0000000023"))]
(is (get-in actual [:gasPrice :error]))))
(testing "an valid edit"
(let [actual (-> {}
(signing.gas/build-edit :gas "21000")
(signing.gas/build-edit :gasPrice "10"))]
(testing "it does not mark gas-price as invalid"
(is (not (get-in actual [:gasPrice :error]))))
(testing "it sets the value in number for gas-price, in gwei"
(is (= "10000000000" (str (get-in actual [:gasPrice :value-number])))))
(testing "it does not mark gas as invalid"
(is (not (get-in actual [:gas :error]))))
(testing "it sets the value in number for gasi"
(is (= "21000" (str (get-in actual [:gas :value-number])))))
(testing "it calculates max-fee"
(is (= "0.00021" (:max-fee actual)))))))
+120
View File
@@ -0,0 +1,120 @@
(ns legacy.status-im.signing.keycard
(:require
[legacy.status-im.utils.deprecated-types :as types]
[native-module.core :as native-module]
[re-frame.core :as re-frame]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(re-frame/reg-fx
::hash-transaction
(fn [{:keys [transaction on-completed]}]
(native-module/hash-transaction (types/clj->json transaction) on-completed)))
(re-frame/reg-fx
::hash-message
(fn [{:keys [message on-completed]}]
(native-module/hash-message message on-completed)))
(re-frame/reg-fx
::hash-typed-data
(fn [{:keys [v4 data on-completed]}]
(if v4
(native-module/hash-typed-data-v4 data on-completed)
(native-module/hash-typed-data data on-completed))))
(defn prepare-transaction
[{:keys [gas gasPrice data nonce tx-obj] :as params}]
(let [{:keys [from to value chat-id message-id command? maxPriorityFeePerGas maxFeePerGas]} tx-obj
maxPriorityFeePerGas (or maxPriorityFeePerGas (get params :maxPriorityFeePerGas))
maxFeePerGas (or maxFeePerGas (get params :maxFeePerGas))]
(cond-> {:from from
:to to
:value value
:chat-id chat-id
:message-id message-id
:command? command?}
maxPriorityFeePerGas
(assoc :maxPriorityFeePerGas
(str "0x"
(native-module/number-to-hex
(js/parseInt maxPriorityFeePerGas))))
maxFeePerGas
(assoc :maxFeePerGas
(str "0x"
(native-module/number-to-hex
(js/parseInt maxFeePerGas))))
gas
(assoc :gas (str "0x" (native-module/number-to-hex gas)))
gasPrice
(assoc :gasPrice (str "0x" (native-module/number-to-hex gasPrice)))
data
(assoc :data data)
nonce
(assoc :nonce nonce))))
(rf/defn hash-message
[_ {:keys [v4 data typed? on-completed]}]
(if typed?
{::hash-typed-data
{:data data
:v4 v4
:on-completed
(or on-completed
#(re-frame/dispatch
[:signing.keycard.callback/hash-message-completed
data typed? %]))}}
{::hash-message
{:message data
:on-completed
(or on-completed
#(re-frame/dispatch
[:signing.keycard.callback/hash-message-completed
data typed? %]))}}))
(rf/defn hash-message-completed
{:events [:signing.keycard.callback/hash-message-completed]}
[{:keys [db]} data typed? result]
(let [{:keys [result error]} (types/json->clj result)]
(if error
{:dispatch [:signing.ui/cancel-is-pressed]
:effects.utils/show-popup {:title (i18n/label :t/sign-request-failed)
:content (:message error)}}
{:db (update db
:keycard assoc
:hash result
:typed? typed?
:data data)})))
(rf/defn hash-transaction
[{:keys [db]}]
(let [tx (prepare-transaction (:signing/tx db))]
(log/debug "hash-transaction" tx)
{::hash-transaction
{:transaction tx
:on-completed #(re-frame/dispatch
[:signing.keycard.callback/hash-transaction-completed tx %])}}))
(rf/defn hash-transaction-completed
{:events [:signing.keycard.callback/hash-transaction-completed]}
[{:keys [db]} original-tx result]
(let [{:keys [transaction hash]} (:result (types/json->clj result))]
{:db (-> db
(assoc-in [:keycard :transaction]
(merge original-tx transaction))
(assoc-in [:keycard :hash] hash))}))
(rf/defn sign-with-keycard
{:events [:signing.ui/sign-with-keycard-pressed]}
[{:keys [db] :as cofx}]
(let [{:keys [message maxPriorityFeePerGas maxFeePerGas]} (get db :signing/tx)]
(rf/merge
cofx
{:db (-> db
(assoc-in [:keycard :pin :enter-step] :sign)
(assoc-in [:signing/sign :type] :keycard)
(assoc-in [:signing/sign :keycard-step] :pin))}
#(if message
(hash-message % message)
(hash-transaction %)))))
+16
View File
@@ -38,6 +38,22 @@
:params []
:on-success [:stickers/stickers-recent-success]})))
(rf/defn load-packs
{:events [:stickers/load-packs]}
[{:keys [db]}]
{:json-rpc/call [{:method "stickers_market"
:params [(chain/chain-id db)]
:on-success #(re-frame/dispatch [:stickers/stickers-market-success %])}
{:method "stickers_installed"
:params []
:on-success #(re-frame/dispatch [:stickers/stickers-installed-success %])}
{:method "stickers_pending"
:params []
:on-success #(re-frame/dispatch [:stickers/stickers-pending-success %])}
{:method "stickers_recent"
:params []
:on-success #(re-frame/dispatch [:stickers/stickers-recent-success %])}]})
(rf/defn pending-pack
{:events [:stickers/pending-pack]}
[{db :db} id]
+32
View File
@@ -0,0 +1,32 @@
(ns legacy.status-im.subs.bootnodes
(:require
[re-frame.core :as re-frame]))
(re-frame/reg-sub
:custom-bootnodes/enabled?
:<- [:profile/profile]
:<- [:networks/current-network]
(fn [[{:keys [custom-bootnodes-enabled?]} current-network]]
(get custom-bootnodes-enabled? current-network)))
(re-frame/reg-sub
:custom-bootnodes/network-bootnodes
:<- [:profile/profile]
:<- [:networks/current-network]
(fn [[multiaccount current-network]]
(get-in multiaccount [:custom-bootnodes current-network])))
(re-frame/reg-sub
:get-manage-bootnode
:<- [:bootnodes/manage]
(fn [manage]
manage))
(re-frame/reg-sub
:wakuv2-nodes/validation-errors
:<- [:wakuv2-nodes/manage]
(fn [manage]
(set (keep
(fn [[k {:keys [error]}]]
(when error k))
manage))))
+210
View File
@@ -0,0 +1,210 @@
(ns legacy.status-im.subs.keycard
(:require
[clojure.string :as string]
[legacy.status-im.keycard.common :as common]
[re-frame.core :as re-frame]
[utils.address :as address]
[utils.datetime :as datetime]))
(re-frame/reg-sub
:keycard/nfc-enabled?
(fn [db]
(get-in db [:keycard :nfc-enabled?])))
(re-frame/reg-sub
:keycard/card-read-in-progress?
(fn [db]
(get-in db [:keycard :card-read-in-progress?] false)))
(re-frame/reg-sub
:keycard-setup-step
(fn [db]
(get-in db [:keycard :setup-step])))
(re-frame/reg-sub
:keycard-card-state
(fn [db]
(get-in db [:keycard :card-state])))
(re-frame/reg-sub
:keycard-flow
(fn [db]
(get-in db [:keycard :flow])))
(re-frame/reg-sub
:keycard-flow-steps
(fn [db]
(case (get-in db [:keycard :flow])
:recovery "2"
"3")))
(re-frame/reg-sub
:keycard-pair-code
(fn [db]
(get-in db [:keycard :secrets :password])))
(re-frame/reg-sub
:keycard-recovery-phrase-word
(fn [db]
(get-in db
[:keycard
:recovery-phrase
(get-in db [:keycard :recovery-phrase :step])])))
(re-frame/reg-sub
:keycard-recovery-phrase-input-word
(fn [db]
(get-in db [:keycard :recovery-phrase :input-word])))
(re-frame/reg-sub
:keycard-recovery-phrase-confirm-error
(fn [db]
(get-in db [:keycard :recovery-phrase :confirm-error])))
(re-frame/reg-sub
:keycard-recovery-phrase-step
(fn [db]
(get-in db [:keycard :recovery-phrase :step])))
(re-frame/reg-sub
:keycard-secrets
(fn [db]
(get-in db [:keycard :secrets])))
(re-frame/reg-sub
:keycard-puk-code
(fn [db]
(->> (get-in db [:keycard :secrets :puk])
(partition 4)
(map string/join)
(string/join " "))))
(re-frame/reg-sub
:keycard-setup-error
(fn [db]
(get-in db [:keycard :setup-error])))
(re-frame/reg-sub
:keycard-mnemonic
(fn [db]
(map-indexed vector
(partition 3
(map-indexed vector
(string/split
(get-in db [:keycard :secrets :mnemonic])
#" "))))))
(re-frame/reg-sub
:keycard-application-info
(fn [db]
(get-in db [:keycard :application-info])))
(re-frame/reg-sub
:keycard-application-info-error
(fn [db]
(get-in db [:keycard :application-info-error])))
(re-frame/reg-sub
:keycard-multiaccount
(fn [db]
(get-in db [:keycard :profile/profile])))
(re-frame/reg-sub
:keycard-multiaccount-wallet-address
(fn [db]
(str "0x" (get-in db [:keycard :multiaccount-wallet-address]))))
(re-frame/reg-sub
:keycard-multiaccount-whisper-public-key
(fn [db]
(address/normalized-hex (get-in db [:keycard :multiaccount-whisper-public-key]))))
(re-frame/reg-sub
:keycard-paired-on
(fn [db]
(some-> (get-in db [:profile/profile :keycard-paired-on])
(datetime/timestamp->year-month-day-date))))
(re-frame/reg-sub
:keycard-multiaccount-pairing
(fn [db]
(get-in db [:profile/profile :keycard-pairing])))
(re-frame/reg-sub
:keycard/pin-retry-counter
(fn [db]
(get-in db [:keycard :application-info :pin-retry-counter])))
(re-frame/reg-sub
:keycard/puk-retry-counter
(fn [db]
(get-in db [:keycard :application-info :puk-retry-counter])))
(re-frame/reg-sub
:keycard/retry-counter
:<- [:keycard/pin-retry-counter]
:<- [:keycard/puk-retry-counter]
(fn [[pin puk]]
(if (zero? pin) puk pin)))
(re-frame/reg-sub
:keycard-reset-card-disabled?
(fn [db]
(get-in db [:keycard :reset-card :disabled?] false)))
(re-frame/reg-sub
:keycard-multiaccount?
common/keycard-multiaccount?)
(re-frame/reg-sub
:keycard/original-pin
(fn [db]
(get-in db [:keycard :pin :original])))
(re-frame/reg-sub
:keycard/login-pin
(fn [db]
(get-in db [:keycard :pin :login])))
(re-frame/reg-sub
:keycard/pin-confirmation
(fn [db]
(get-in db [:keycard :pin :confirmation])))
(re-frame/reg-sub
:keycard/pin-enter-step
(fn [db]
(get-in db [:keycard :pin :enter-step])))
(re-frame/reg-sub
:keycard/pin-operation
(fn [db]
(get-in db [:keycard :pin :operation])))
(re-frame/reg-sub
:keycard/pin-data
(fn [db]
(get-in db [:keycard :pin])))
(re-frame/reg-sub
:keycard/pin
:<- [:keycard/pin-data]
:<- [:keycard/pin-enter-step]
(fn [[pin-data step]]
(get pin-data step)))
(re-frame/reg-sub
:keycard/pin-status
(fn [db]
(get-in db [:keycard :pin :status])))
(re-frame/reg-sub
:keycard/pin-error-label
(fn [db]
(get-in db [:keycard :pin :error-label])))
(re-frame/reg-sub
:keycard/frozen-card?
(fn [db]
(and (common/keycard-multiaccount? db)
(zero? (get-in db [:keycard :application-info :pin-retry-counter])))))
@@ -1,6 +1,7 @@
(ns legacy.status-im.subs.mailservers
(:require
[legacy.status-im.fleet.core :as fleet]
[legacy.status-im.mailserver.core :as mailserver]
[re-frame.core :as re-frame]))
(re-frame/reg-sub
@@ -23,6 +24,12 @@
(fn [state]
(#{:error :disconnected} state)))
(re-frame/reg-sub
:chats/fetching-gap-in-progress?
:<- [:mailserver/fetching-gaps-in-progress]
(fn [gaps [_ ids _]]
(seq (select-keys gaps ids))))
(re-frame/reg-sub
:mailserver/fetching?
:<- [:mailserver/state]
@@ -51,6 +58,11 @@
(= (get-in mailserver [:id :value])
current-mailserver-id)))
(re-frame/reg-sub
:mailserver/use-status-nodes?
(fn [db _]
(boolean (mailserver/fetch-use-mailservers? db))))
(re-frame/reg-sub
:mailserver.edit/validation-errors
:<- [:mailserver.edit/mailserver]
@@ -60,6 +72,15 @@
(when error k))
mailserver))))
(re-frame/reg-sub
:mailserver/connected?
:<- [:mailserver/state]
:<- [:disconnected?]
(fn [[mail-state disconnected?]]
(let [mailserver-connected? (= :connected mail-state)]
(and mailserver-connected?
(not disconnected?)))))
(re-frame/reg-sub
:mailserver/preferred-id
:<- [:profile/profile]
+6
View File
@@ -1,7 +1,9 @@
(ns legacy.status-im.subs.root
(:require
legacy.status-im.subs.bootnodes
legacy.status-im.subs.browser
legacy.status-im.subs.ens
legacy.status-im.subs.keycard
legacy.status-im.subs.mailservers
legacy.status-im.subs.stickers
[re-frame.core :as re-frame]))
@@ -21,6 +23,10 @@
(reg-root-key-sub :peers-summary :peers-summary)
(reg-root-key-sub :web3-node-version :web3-node-version)
;;keycard
(reg-root-key-sub :keycard :keycard)
(reg-root-key-sub :keycard/banner-hidden :keycard/banner-hidden)
;;bottom sheet old
(reg-root-key-sub :bottom-sheet/show? :bottom-sheet/show?)
(reg-root-key-sub :bottom-sheet/view :bottom-sheet/view)
+6
View File
@@ -3,6 +3,12 @@
[re-frame.core :as re-frame]
[status-im.constants :as constants]))
(re-frame/reg-sub
:stickers/installed-packs
:<- [:stickers/packs]
(fn [packs]
(filter #(= (:status %) constants/sticker-pack-status-installed) (vals packs))))
(re-frame/reg-sub
:stickers/all-packs
:<- [:stickers/packs]
@@ -0,0 +1,30 @@
(ns legacy.status-im.ui.screens.bootnodes-settings.edit-bootnode.styles
(:require
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.utils.styles :as styles]))
(def edit-bootnode-view
{:flex 1
:margin-horizontal 16
:margin-vertical 15})
(def button-container
{:margin-top 8
:margin-bottom 16
:margin-horizontal 16})
(styles/def button
{:height 52
:align-items :center
:justify-content :center
:border-radius 8
:ios {:opacity 0.9}})
(def button-label
{:color colors/white-persist
:font-size 17})
(def delete-button
(assoc button
:background-color
colors/red))
@@ -0,0 +1,76 @@
(ns legacy.status-im.ui.screens.bootnodes-settings.edit-bootnode.views
(:require
[clojure.string :as string]
[legacy.status-im.qr-scanner.core :as qr-scanner]
[legacy.status-im.ui.components.core :as quo]
[legacy.status-im.ui.components.react :as react]
[legacy.status-im.ui.components.toolbar :as toolbar]
[legacy.status-im.ui.components.topbar :as topbar]
[legacy.status-im.ui.screens.bootnodes-settings.edit-bootnode.styles :as styles]
[re-frame.core :as re-frame]
[utils.i18n :as i18n])
(:require-macros [legacy.status-im.utils.views :as views]))
(defn delete-button
[id]
[react/touchable-highlight {:on-press #(re-frame/dispatch [:bootnodes.ui/delete-pressed id])}
[react/view styles/button-container
[react/view
{:style styles/delete-button
:accessibility-label :bootnode-delete-button}
[react/text {:style styles/button-label}
(i18n/label :t/delete)]]]])
(views/defview edit-bootnode
[]
(views/letsubs [manage-bootnode [:get-manage-bootnode]
validation-errors [:wakuv2-nodes/validation-errors]]
(let [url (get-in manage-bootnode [:url :value])
id (get-in manage-bootnode [:id :value])
name (get-in manage-bootnode [:name :value])
is-valid? (empty? validation-errors)
invalid-url? (contains? validation-errors :url)]
[react/keyboard-avoiding-view
{:style {:flex 1}
:ignore-offset true}
[topbar/topbar {:title (i18n/label (if id :t/bootnode-details :t/add-bootnode))}]
[react/scroll-view {:keyboard-should-persist-taps :handled}
[react/view styles/edit-bootnode-view
[react/view {:padding-vertical 8}
[quo/text-input
{:label (i18n/label :t/name)
:placeholder (i18n/label :t/specify-name)
:accessibility-label :bootnode-name
:default-value name
:on-change-text #(re-frame/dispatch [:bootnodes.ui/input-changed :name %])
:auto-focus true}]]
[react/view
{:flex 1
:padding-vertical 8}
[quo/text-input
(merge
{:label (i18n/label :t/bootnode-address)
:placeholder (i18n/label :t/bootnode-format)
:accessibility-label :bootnode-address
:default-value url
:show-cancel false
:on-change-text #(re-frame/dispatch [:bootnodes.ui/input-changed :url %])
:error (when (and (not (string/blank? url)) invalid-url?)
(i18n/label :t/invalid-format
{:format (i18n/label :t/bootnode-format)}))
:bottom-value 0
:after {:icon :main-icons/qr
:on-press #(re-frame/dispatch
[::qr-scanner/scan-code
{:title (i18n/label :t/add-bootnode)
:handler :bootnodes.callback/qr-code-scanned}])}})]]
(when id
[delete-button id])]]
[toolbar/toolbar
{:right
[quo/button
{:type :secondary
:after :main-icon/next
:disabled (not is-valid?)
:on-press #(re-frame/dispatch [:bootnodes.ui/save-pressed])}
(i18n/label :t/save)]}]])))
@@ -0,0 +1,23 @@
(ns legacy.status-im.ui.screens.bootnodes-settings.styles
(:require
[legacy.status-im.utils.styles :as styles]))
(def wrapper
{:flex 1})
(def bootnode-item-inner
{:padding-horizontal 16})
(styles/def bootnode-item
{:flex-direction :row
:align-items :center
:padding-horizontal 16
:ios {:height 64}
:android {:height 56}})
(def bootnode-item-name-text
{:font-size 17})
(def switch-container
{:height 50
:padding-left 15})
@@ -0,0 +1,47 @@
(ns legacy.status-im.ui.screens.bootnodes-settings.views
(:require-macros [legacy.status-im.utils.views :as views])
(:require
[legacy.status-im.ui.components.list.views :as list]
[legacy.status-im.ui.components.react :as react]
[legacy.status-im.ui.components.topbar :as topbar]
[legacy.status-im.ui.screens.bootnodes-settings.styles :as styles]
[legacy.status-im.ui.screens.profile.components.views :as profile.components]
[re-frame.core :as re-frame]
[utils.i18n :as i18n]))
(defn navigate-to-add-bootnode
[id]
(re-frame/dispatch [:bootnodes.ui/add-bootnode-pressed id]))
(defn render-row
[{:keys [name id]}]
[react/touchable-highlight
{:on-press #(navigate-to-add-bootnode id)
:accessibility-label :bootnode-item}
[react/view styles/bootnode-item
[react/view styles/bootnode-item-inner
[react/text {:style styles/bootnode-item-name-text}
name]]]])
(views/defview bootnodes-settings
[]
(views/letsubs [bootnodes-enabled [:custom-bootnodes/enabled?]
bootnodes [:custom-bootnodes/network-bootnodes]]
[:<>
[topbar/topbar
{:title (i18n/label :t/bootnodes-settings)
:right-accessories
[{:icon :main-icons/add
:accessibility-label :add-bootnode
:on-press #(navigate-to-add-bootnode nil)}]}]
[react/view styles/switch-container
[profile.components/settings-switch-item
{:label-kw :t/bootnodes-enabled
:value bootnodes-enabled
:action-fn #(re-frame/dispatch [:bootnodes.ui/custom-bootnodes-switch-toggled %])}]]
[react/view styles/wrapper
[list/flat-list
{:data (vals bootnodes)
:default-separator? false
:key-fn :id
:render-fn render-row}]]]))
@@ -2,8 +2,10 @@
(:require
[legacy.status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.copyable-text :as copyable-text]
[legacy.status-im.ui.components.core :as quo]
[legacy.status-im.ui.components.icons.icons :as icons]
[legacy.status-im.ui.components.list.item :as list.item]
[legacy.status-im.ui.components.react :as react]
[legacy.status-im.ui.screens.browser.styles :as styles]
[re-frame.core :as re-frame]
@@ -11,7 +13,7 @@
(:require-macros [legacy.status-im.utils.views :as views]))
(views/defview permissions-panel
[_dapp-name message-id params]
[dapp-name message-id params]
(views/letsubs [{:keys [dapp? dapp]} [:get-current-browser]]
[react/view {}
[react/view styles/permissions-panel-icons-container
@@ -35,6 +37,35 @@
[react/view styles/dot]]
[react/view styles/permissions-panel-wallet-icon-container
[icons/icon :main-icons/wallet {:color colors/white}]]]
[react/text {:style styles/permissions-panel-title-label :number-of-lines 2}
(str "\"" dapp-name "\" Allow this site to add a network?")]
[react/text {:style styles/permissions-panel-description-label :number-of-lines 4}
"This will allow this network to be used within Status.Status does not verify custom networks.Learn about scams and network security risks."]
[react/scroll-view
[copyable-text/copyable-text-view
{:copied-text (:name (:new-network params))}
[list.item/list-item
{:size :small
:accessibility-label :network-name
:title "Network Name"
:accessory :text
:accessory-text (:name (:new-network params))}]]
[copyable-text/copyable-text-view
{:copied-text (get-in params [:new-network :config :UpstreamConfig :URL])}
[list.item/list-item
{:size :small
:accessibility-label :network-url
:title "Network URL"
:accessory :text
:accessory-text (get-in params [:new-network :config :UpstreamConfig :URL])}]]
[copyable-text/copyable-text-view
{:copied-text (str (get-in params [:new-network :config :NetworkId]))}
[list.item/list-item
{:size :small
:accessibility-label :network-id
:title "Chain ID"
:accessory :text
:accessory-text (str (get-in params [:new-network :config :NetworkId]))}]]]
[react/view
{:style {:flex-direction :row
:justify-content :center
@@ -10,7 +10,9 @@
[legacy.status-im.ui.components.react :as react]
[legacy.status-im.utils.utils :as utils]
[re-frame.core :as re-frame]
[utils.i18n :as i18n]))
[status-im.constants :as constants]
[utils.i18n :as i18n]
[utils.url :as url]))
(defn hide-sheet-and-dispatch
[event]
@@ -38,10 +40,13 @@
:on-press #(hide-sheet-and-dispatch [:browser/revoke-dapp-permissions host])}]]))
(defn browser-options
[url _account empty-tab name]
[url account empty-tab name]
(fn []
(let [bookmarks @(re-frame/subscribe [:bookmarks/active])
fav? (get bookmarks url)]
(let [bookmarks @(re-frame/subscribe [:bookmarks/active])
permissions @(re-frame/subscribe [:dapps/permissions])
fav? (get bookmarks url)
connected? (some #{constants/dapp-permission-web3}
(get-in permissions [(url/url-host url) :permissions]))]
[react/view {:flex 1}
[quo/button
{:style {:align-self :flex-end
@@ -81,4 +86,21 @@
#(browser/share-link url)
200))}]
[react/view {:style {:height 1 :background-color (colors/alpha colors/black 0.1)}}]])
])))
(if connected?
[list.item/list-item
{:icon [chat-icon/custom-icon-view-list (:name account) (:color account)]
:title (:name account)
:subtitle (i18n/label :t/connected)
:accessibility-label :connected-account
:chevron true
:on-press #(hide-sheet-and-dispatch
[:bottom-sheet/show-sheet-old
{:content (wallet-connection (url/url-host url) account)}])}]
[list.item/list-item
{:theme :accent
:title (i18n/label :t/connect-wallet)
:accessibility-label :connect-account
:icon :main-icons/wallet
:on-press #(hide-sheet-and-dispatch
[:browser/bridge-message-received
"{\"type\":\"api-request\",\"permission\":\"web3\"}"])}])])))
@@ -2,6 +2,7 @@
(:require
[legacy.status-im.browser.core :as browser]
[legacy.status-im.browser.webview-ref :as webview-ref]
[legacy.status-im.qr-scanner.core :as qr-scanner]
[legacy.status-im.ui.components.chat-icon.screen :as chat-icon]
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.connectivity.view :as connectivity]
@@ -104,7 +105,9 @@
(if empty-tab
[react/touchable-highlight
{:accessibility-label :universal-qr-scanner
:on-press #(re-frame/dispatch [:open-modal :shell-qr-reader])}
:on-press #(re-frame/dispatch
[::qr-scanner/scan-code
{:handler ::qr-scanner/on-scan-success}])}
[icons/icon :main-icons/qr {:color colors/black}]]
[react/touchable-highlight
{:on-press #(re-frame/dispatch
@@ -1,15 +1,16 @@
(ns legacy.status-im.ui.screens.communities.invite
(:require
[clojure.string :as string]
[legacy.status-im.communities.core :as communities]
[legacy.status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[legacy.status-im.ui.components.core :as quo]
[legacy.status-im.ui.components.list.item :as list.item]
[legacy.status-im.ui.components.toolbar :as toolbar]
[legacy.status-im.ui.components.topbar :as topbar]
[quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im.constants :as constants]
[utils.debounce :as debounce]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@@ -48,35 +49,18 @@
contacts-selected (reagent/atom #{})
{:keys [invite?]} (rf/sub [:get-screen-params])]
(fn []
(let [theme (quo.theme/use-theme)
contacts-data (rf/sub [:contacts/active])
{community-id :id
:keys [permissions
can-manage-users?]}
(let [contacts-data (rf/sub [:contacts/active])
{:keys [permissions
can-manage-users?]}
(rf/sub [:communities/edited-community])
selected @contacts-selected
selected-contacts-count (count selected)
contacts (map (fn [{:keys [public-key] :as contact}]
(assoc contact :active (contains? selected public-key)))
contacts-data)
;; no-membership communities can only be shared
can-invite? (and can-manage-users?
invite?
(not= (:access permissions) constants/community-no-membership-access))
on-press-share-community (rn/use-callback
(fn []
(rf/dispatch [:communities/share-community-confirmation-pressed
selected community-id])
(rf/dispatch [:navigate-back])
(rf/dispatch [:toasts/upsert
{:type :positive
:theme theme
:text (if (= 1 selected-contacts-count)
(i18n/label :t/one-user-was-invited)
(i18n/label
:t/n-users-were-invited
{:count selected-contacts-count}))}]))
[community-id selected selected-contacts-count theme])]
(not= (:access permissions) constants/community-no-membership-access))]
[:<>
[topbar/topbar
{:title (i18n/label (if can-invite?
@@ -97,8 +81,11 @@
:center
[quo/button
{:disabled (and (string/blank? @user-pk)
(zero? selected-contacts-count))
(zero? (count selected)))
:accessibility-label :share-community-link
:type :secondary
:on-press on-press-share-community}
:on-press #(debounce/throttle-and-dispatch
[::communities/share-community-confirmation-pressed @user-pk
selected]
3000)}
(i18n/label (if can-invite? :t/invite :t/share))]}]]))))
@@ -7,6 +7,8 @@
[legacy.status-im.ui.components.react :as react]
[legacy.status-im.ui.screens.profile.user.views :as profile.user]
[legacy.status-im.ui.screens.reset-password.views :as reset-password.views]
[legacy.status-im.ui.screens.signing.sheets :as signing-sheets]
[legacy.status-im.ui.screens.signing.views :as signing]
[re-frame.core :as re-frame]
[react-native.platform :as platform]
[reagent.core :as reagent]))
@@ -130,9 +132,15 @@
(= :share-chat-key view)
[profile.user/share-chat-key]
(= :transaction-data view)
[signing/transaction-data]
(= :password-reset-popover view)
[reset-password.views/reset-password-popover]
(= :fees-warning view)
[signing-sheets/fees-warning]
:else
[view])]]]])))})))
@@ -1,5 +1,6 @@
(ns legacy.status-im.ui.screens.privacy-and-security-settings.delete-profile
(:require
legacy.status-im.keycard.delete-key
[legacy.status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[legacy.status-im.ui.components.core :as quo]
[legacy.status-im.ui.components.list.item :as list.item]
@@ -22,12 +23,18 @@
[::delete-profile/delete-profile @password])
(reset! password nil)))
(defn on-delete-keycard-profile
[keep-keys-on-keycard?]
#(re-frame/dispatch
[:keycard/proceed-to-reset-card keep-keys-on-keycard?]))
(defn delete-profile
[]
(let [password (reagent/atom nil)
text-input-ref (atom nil)]
(fn []
(let [profile @(re-frame/subscribe [:profile/profile])
keycard? @(re-frame/subscribe [:keycard-multiaccount?])
error @(re-frame/subscribe [:delete-profile/error])
keep-keys-on-keycard? @(re-frame/subscribe
[:delete-profile/keep-keys-on-keycard?])]
@@ -43,29 +50,55 @@
[list.item/list-item
{:title (profile.utils/displayed-name profile)
:icon [chat-icon.screen/contact-icon-contacts-tab profile]}]
[quo/text
{:style {:margin-horizontal 24}
:align :center
:color :negative}
(i18n/label :t/delete-profile-warning)]
[quo/text-input
{:style {:margin-horizontal 36
:margin-top 36}
:show-cancel false
:secure-text-entry true
:return-key-type :next
:on-submit-editing nil
:auto-focus true
:on-change-text #(reset! password (security/mask-data %))
:bottom-value 36
:get-ref #(reset! text-input-ref %)
:error (when (and error (not @password))
(if (= :wrong-password error)
(i18n/label :t/wrong-password)
(str error)))}]]
(when keycard?
[react/view
[quo/list-header (i18n/label :t/actions)]
[list.item/list-item
{:title (i18n/label :t/delete-keys-keycard)
:accessory :checkbox
:active (not keep-keys-on-keycard?)
:on-press #(re-frame/dispatch [::delete-profile/keep-keys-on-keycard
(not keep-keys-on-keycard?)])}]
[list.item/list-item
{:title (i18n/label :t/unpair-keycard)
:subtitle (i18n/label :t/unpair-keycard-warning)
:subtitle-max-lines 4
:disabled true
:active true
:accessory :checkbox}]
[list.item/list-item
{:title (i18n/label :t/reset-database)
:subtitle (i18n/label :t/reset-database-warning-keycard)
:subtitle-max-lines 4
:disabled true
:active true
:accessory :checkbox}]])
(when-not keycard?
[quo/text
{:style {:margin-horizontal 24}
:align :center
:color :negative}
(i18n/label :t/delete-profile-warning)])
(when-not keycard?
[quo/text-input
{:style {:margin-horizontal 36
:margin-top 36}
:show-cancel false
:secure-text-entry true
:return-key-type :next
:on-submit-editing nil
:auto-focus true
:on-change-text #(reset! password (security/mask-data %))
:bottom-value 36
:get-ref #(reset! text-input-ref %)
:error (when (and error (not @password))
(if (= :wrong-password error)
(i18n/label :t/wrong-password)
(str error)))}])]
[react/view {:style {:align-items :center}}
[quo/separator]
(when (not keep-keys-on-keycard?)
(when-not keycard?
[quo/separator])
(when (and keycard? (not keep-keys-on-keycard?))
[quo/text
{:style {:margin-horizontal 24 :margin-bottom 16}
:align :center
@@ -74,8 +107,10 @@
[react/view
{:style {:margin-vertical 8}}
[quo/button
{:on-press (on-delete-profile password)
{:on-press (if keycard?
(on-delete-keycard-profile keep-keys-on-keycard?)
(on-delete-profile password))
:theme :negative
:accessibility-label :delete-profile-confirm
:disabled ((complement valid-password?) @password)}
:disabled (and (not keycard?) ((complement valid-password?) @password))}
(i18n/label :t/delete-profile)]]]]))))
@@ -35,6 +35,7 @@
profile-pictures-visibility]}
[:profile/profile]
has-picture [:profile/has-picture]
keycard? [:keycard-multiaccount?]
profile-pictures-show-to [:multiaccount/profile-pictures-show-to]]
[react/scroll-view {:padding-vertical 8}
[quo/list-header (i18n/label :t/security)]
@@ -91,15 +92,16 @@
:t/anyone))
:on-press #(re-frame/dispatch [:navigate-to :messages-from-contacts-only])
:accessibility-label :accept-new-chats-from}]
[list.item/list-item
{:size :small
:title (i18n/label :t/reset-password)
:chevron true
:accessory :text
:on-press #(do
(re-frame/dispatch [::reset-password/clear-form-vals])
(re-frame/dispatch [:navigate-to :reset-password]))
:accessibility-label :reset-password}]
(when (not keycard?)
[list.item/list-item
{:size :small
:title (i18n/label :t/reset-password)
:chevron true
:accessory :text
:on-press #(do
(re-frame/dispatch [::reset-password/clear-form-vals])
(re-frame/dispatch [:navigate-to :reset-password]))
:accessibility-label :reset-password}])
(when platform/android?
[list.item/list-item
{:size :small
@@ -73,7 +73,8 @@
(defn content
[]
(let [{:keys [preferred-name
mnemonic]}
mnemonic
keycard-pairing]}
@(re-frame/subscribe [:profile/profile])
active-contacts-count @(re-frame/subscribe [:contacts/active-count])
chain @(re-frame/subscribe [:chain-keyword])
@@ -154,6 +155,13 @@
:accessibility-label :sync-settings-button
:chevron true
:on-press #(re-frame/dispatch [:open-modal :sync-settings])}]
(when keycard-pairing
[list.item/list-item
{:icon :main-icons/keycard
:title (i18n/label :t/keycard)
:accessibility-label :keycard-button
:chevron true
:on-press #(re-frame/dispatch [:open-modal :keycard-settings])}])
[list.item/list-item
{:icon :main-icons/settings-advanced
:title (i18n/label :t/advanced)
@@ -5,6 +5,8 @@
[legacy.status-im.ui.screens.advanced-settings.views :as advanced-settings]
[legacy.status-im.ui.screens.appearance.views :as appearance]
[legacy.status-im.ui.screens.backup-settings.view :as backup-settings]
[legacy.status-im.ui.screens.bootnodes-settings.edit-bootnode.views :as edit-bootnode]
[legacy.status-im.ui.screens.bootnodes-settings.views :as bootnodes-settings]
[legacy.status-im.ui.screens.browser.bookmarks.views :as bookmarks]
[legacy.status-im.ui.screens.bug-report :as bug-report]
[legacy.status-im.ui.screens.communities.invite :as communities.invite]
@@ -134,10 +136,18 @@
;;TODO dynamic title
:options {:insets {:top? true}}
:component edit-wakuv2-node/edit-node}
{:name :bootnodes-settings
;;TODO dynamic title
:options {:insets {:top? true}}
:component bootnodes-settings/bootnodes-settings}
{:name :installations
:options {:topBar (topbar-options (i18n/label :t/devices))
:insets {:top? true}}
:component pairing/installations}
{:name :edit-bootnode
;;TODO dynamic title
:options {:insets {:top? true}}
:component edit-bootnode/edit-bootnode}
{:name :offline-messaging-settings
;;TODO dynamic title
:options {:insets {:top? true}}
@@ -0,0 +1,437 @@
(ns legacy.status-im.ui.screens.signing.sheets
(:require-macros [legacy.status-im.utils.views :as views])
(:require
[clojure.string :as string]
[legacy.status-im.signing.gas :as gas]
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.core :as quo]
[legacy.status-im.ui.components.icons.icons :as icons]
[legacy.status-im.ui.components.react :as react]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[utils.i18n :as i18n]
[utils.money :as money]))
(views/defview fee-bottom-sheet
[fee-display-symbol]
(views/letsubs [{gas-edit :gas gas-price-edit :gasPrice max-fee :max-fee} [:signing/edit-fee]]
[react/view
[react/view {:style {:margin-horizontal 16 :margin-top 8}}
[react/text {:style {:typography :title-bold}} (i18n/label :t/network-fee)]
[react/view
{:style {:flex-direction :row
:margin-top 8
:align-items :flex-end}}
[react/view {:flex 1}
[quo/text-input
{:on-change-text #(re-frame/dispatch [:signing.edit-fee.ui/edit-value :gas %])
:label (i18n/label :t/gas-limit)
:bottom-value 0
:error (:error gas-edit)
:default-value (:value gas-edit)
:keyboard-type :numeric
:auto-capitalize :none
:placeholder "0"
:show-cancel false
:auto-focus false}]]
[react/view
{:flex 1
:padding-left 16}
[quo/text-input
{:label (i18n/label :t/gas-price)
:on-change-text #(re-frame/dispatch [:signing.edit-fee.ui/edit-value :gasPrice %])
:bottom-value 0
:error (:error gas-price-edit)
:default-value (:value gas-price-edit)
:keyboard-type :numeric
:auto-capitalize :none
:placeholder "0.000"
:show-cancel false
:auto-focus false}]]
[react/view
{:padding-left 8
:padding-bottom 12}
[react/text (i18n/label :t/gwei)]]]
[react/view {:margin-vertical 16 :align-items :center}
[react/text {:style {:color colors/gray}} (i18n/label :t/wallet-transaction-total-fee)]
[react/view {:height 8}]
[react/nested-text {:style {:font-size 17}}
max-fee " "
[{:style {:color colors/gray}} fee-display-symbol]]]]
[react/view {:height 1 :background-color colors/gray-lighter}]
[react/view
{:margin-horizontal 16
:align-items :center
:justify-content :space-between
:flex-direction :row
:margin-top 6}
[quo/button
{:type :secondary
:on-press #(re-frame/dispatch [:bottom-sheet/hide-old])}
(i18n/label :t/cancel)]
[quo/button
{:type :secondary
:on-press #(re-frame/dispatch [:signing.edit-fee.ui/submit])
:disabled (or (:error gas-edit) (:error gas-price-edit))}
(i18n/label :t/update)]]]))
(declare fee-bottom-sheet-eip1559)
(defn fee-bottom-sheet-eip1559-custom
[_ #_fee-display-symbol]
(let [{gas-edit :gas
max-fee-per-gas-edit :maxFeePerGas
max-priority-fee-per-gas-edit :maxPriorityFeePerGas}
@(re-frame/subscribe [:signing/edit-fee])
error? (some :error
[gas-edit
max-fee-per-gas-edit
max-priority-fee-per-gas-edit])
base-fee @(re-frame/subscribe [:wallet-legacy/current-base-fee])
[fee-currency fiat-currency price]
@(re-frame/subscribe [:signing/currencies])
fee-eth
(if (and (:value-number gas-edit)
(:value-number max-fee-per-gas-edit))
(money/mul
(money/wei->ether
(money/->wei :gwei (:value-number max-fee-per-gas-edit)))
(:value-number gas-edit))
(money/bignumber 0))]
[:<>
[react/view {:style {:margin-horizontal 16 :margin-top 8}}
[react/text {:style {:typography :title-bold}} (i18n/label :t/max-priority-fee)]
[react/text
{:style {:color (colors/get-color :text-02)
:margin-top 12}}
(i18n/label :t/miners-higher-fee)]
[react/view
{:style {:margin-top 12
:flex-direction :row
:align-items :center
:justify-content :space-between}}
[quo/text (i18n/label :t/current-base-fee)]
[quo/text
(money/to-fixed (money/wei-> :gwei base-fee))
" "
(i18n/label :t/gwei)]]]
[react/view
{:margin-vertical 12
:margin-horizontal 16
:height 1
:background-color colors/gray-lighter}]
[react/view
{:margin-horizontal 16
:margin-top 4
:margin-bottom 26}
[quo/text-input
{:label (i18n/label :t/gas-amount-limit)
:accessibility-label :gas-amount-limit
:error (:error gas-edit)
:default-value (:value gas-edit)
:on-change-text #(re-frame/dispatch [:signing.edit-fee.ui/edit-value :gas %])
:show-cancel false}]]
[react/view
{:margin-horizontal 16
:margin-top 4
:margin-bottom 26}
[quo/text-input
{:label (i18n/label :t/per-gas-tip-limit)
:accessibility-label :per-gas-tip-limit
:error (or (:error max-priority-fee-per-gas-edit)
(get-in max-priority-fee-per-gas-edit [:fee-error :label]))
:default-value (str (:value max-priority-fee-per-gas-edit))
:on-change-text #(re-frame/dispatch [:signing.edit-fee.ui/edit-value :maxPriorityFeePerGas
%])
:show-cancel false
:after {:component [quo/text
{:style {:color (colors/get-color :text-02)}}
(i18n/label :t/gwei)]}}]]
[react/view
{:margin-horizontal 16
:margin-top 4
:margin-bottom 12}
[quo/text-input
{:label (i18n/label :t/per-gas-price-limit)
:accessibility-label :per-gas-price-limit
:error (or (:error max-fee-per-gas-edit)
(get-in max-fee-per-gas-edit [:fee-error :label]))
:default-value (str (:value max-fee-per-gas-edit))
:on-change-text #(re-frame/dispatch [:signing.edit-fee.ui/edit-value :maxFeePerGas %])
:show-cancel false
:after {:component [quo/text
{:style {:color (colors/get-color :text-02)}}
(i18n/label :t/gwei)]}}]]
[react/view
{:margin-vertical 12
:height 1
:background-color colors/gray-lighter}]
[react/view
{:style {:margin-top 4
:margin-horizontal 16
:flex-direction :row
:align-items :center
:justify-content :space-between}}
[quo/text (i18n/label :t/maximum-fee) ":"]
[react/view
{:style {:flex-direction :row
:align-items :center
:justify-content :flex-end}}
[quo/text
{:style {:margin-right 6
:color (colors/get-color :text-02)}}
(str (money/to-fixed fee-eth 6) " " fee-currency)]
[quo/text
(money/to-fixed (money/mul fee-eth (money/bignumber (or price 0))) 2)
" "
fiat-currency]]]
[react/text
{:style {:color (colors/get-color :text-02)
:margin 16}}
(i18n/label :t/fee-explanation)]
[react/view
{:style {:margin-left 12
:margin-right 16
:margin-bottom 20
:flex-direction :row
:align-items :center
:justify-content :space-between}}
[quo/button
{:type :secondary
:accessibility-label :see-fee-suggestions
;;:on-press
#_(re-frame/dispatch
[:bottom-sheet/show-sheet-old
{:content (fn []
[fee-bottom-sheet-eip1559 fee-display-symbol])
:content-height 270}])}
"" #_(i18n/label :t/see-suggestions)]
[quo/button
{:type :primary
:accessibility-label :save-fees
:disabled error?
:on-press #(re-frame/dispatch [:signing.edit-fee.ui/submit])
:theme :accent}
(i18n/label :t/save)]]]))
(defn fee-bottom-sheet-eip1559
[fee-display-symbol]
(let [{priority-fee-edit :maxPriorityFeePerGas
option :selected-fee-option
fee-edit :maxFeePerGas}
@(re-frame/subscribe [:signing/edit-fee])
{:keys [normal fast slow]}
@(re-frame/subscribe [:signing/priority-fee-suggestions-range])]
[react/view
[react/view {:style {:margin-horizontal 16 :margin-top 8}}
[react/text {:style {:typography :title-bold}} (i18n/label :t/fee-options)]
[react/text
{:style {:margin-top 12}
:accessibility-label :slow-fee
:on-press #(re-frame/dispatch [:signing.edit-fee.ui/set-option :slow])}
(string/join
" "
[(str (i18n/label :t/slow) ":")
(str (:base-fee slow) " gwei")
(str (:tip slow) " gwei")
(when (= :slow option)
"<- selected")])]
[react/text
{:style {:margin-top 12}
:accessibility-label :normal-fee
:on-press #(re-frame/dispatch [:signing.edit-fee.ui/set-option :normal])}
(string/join
" "
[(str (i18n/label :t/normal) ":")
(str (:base-fee normal) " gwei")
(str (:tip normal) " gwei")
(when (or (nil? option)
(= :normal option))
"<- selected")])]
[react/text
{:style {:margin-top 12}
:accessibility-label :fast-fee
:on-press #(re-frame/dispatch [:signing.edit-fee.ui/set-option :fast])}
(string/join
" "
[(str (i18n/label :t/fast) ":")
(str (:base-fee fast) " gwei")
(str (:tip fast) " gwei")
(when (= :fast option)
"<- selected")])]
(when (= :custom option)
[react/text {:style {:margin-top 12}}
(string/join
" "
[(str (i18n/label :t/custom) ":")
(str (-> fee-edit
:value-number
(money/to-fixed 2))
" gwei")
(str (-> priority-fee-edit
:value-number
(money/to-fixed 2))
" gwei")
(when (= :custom option)
"<- selected")])])]
[react/view
{:style {:margin-left 12
:margin-right 16
:margin-top 38}
:flex-direction :row
:align-items :center
:justify-content :space-between}
[quo/button
{:type :secondary
:accessibility-label :set-custom-fee
:on-press #(re-frame/dispatch
[:bottom-sheet/show-sheet-old
{:content (fn []
[fee-bottom-sheet-eip1559-custom fee-display-symbol])
:content-height 270}])}
(i18n/label :t/set-custom-fee)]
[quo/button
{:type :primary
:accessibility-label :save-custom-fee
:theme :accent
:on-press #(re-frame/dispatch [:signing.edit-fee.ui/submit])}
(i18n/label :t/save)]]]))
(defn gwei
[amount]
(str (money/to-fixed amount 2)
" "
(i18n/label :t/gwei)))
(defn fees-warning
[]
(let [base-fee @(re-frame/subscribe [:wallet-legacy/current-base-fee])
base-fee-gwei (money/wei-> :gwei (money/bignumber base-fee))
priority-fee @(re-frame/subscribe [:wallet-legacy/current-priority-fee])
priority-fee-gwei (money/wei-> :gwei (money/bignumber priority-fee))
{priority-fee-edit :maxPriorityFeePerGas
fee-edit :maxFeePerGas}
@(re-frame/subscribe [:signing/edit-fee])]
[react/view
[react/view
{:margin-top 24
:margin-horizontal 24
:margin-bottom 32
:align-items :center}
[react/view
{:background-color colors/blue-light
:width 32
:height 32
:border-radius 16
:align-items :center
:justify-content :center}
[icons/icon :main-icons/warning {:color colors/black}]]
[react/text
{:style {:typography :title-bold
:margin-top 16
:margin-bottom 8}}
(i18n/label :t/are-you-sure)]
[react/text
{:style {:color colors/gray
:text-align :center
:margin-horizontal 24}}
(i18n/label :t/bad-fees-description)]]
[react/view
{:style {:flex-direction :row
:justify-content :space-between
:margin-horizontal 32}}
[react/text (i18n/label :t/current-base-fee)]
[react/text (gwei base-fee-gwei)]]
[react/view
{:style {:flex-direction :row
:justify-content :space-between
:margin-horizontal 32}}
[react/text (i18n/label :t/current-minimum-tip)]
[react/text (gwei (gas/get-minimum-priority-fee priority-fee))]]
;;TODO(rasom): we can uncomment it once it will be clear which value can be
;;used as "average" here
#_[react/view
{:style {:flex-direction :row
:justify-content :space-between
:margin-horizontal 32}}
[react/text (i18n/label :t/current-average-tip)]
[react/text (gwei (money/to-fixed priority-fee-gwei 2))]]
[react/view
{:margin-vertical 16
:height 1
:background-color colors/gray-lighter}]
[react/view
{:style {:flex-direction :row
:justify-content :space-between
:margin-horizontal 32
:color :red}}
[react/text {:style {:color (colors/get-color :negative-01)}}
(i18n/label :t/your-tip-limit)]
[react/text {:style {:color (colors/get-color :negative-01)}}
(gwei (:value-number priority-fee-edit))]]
[react/view
{:style {:flex-direction :row
:justify-content :space-between
:margin-horizontal 32}}
[react/text {:style {:color (colors/get-color :negative-01)}}
(i18n/label :t/your-price-limit)]
[react/text {:style {:color (colors/get-color :negative-01)}}
(gwei (:value-number fee-edit))]]
[react/view
{:style
{:background-color colors/gray-lighter
:padding-horizontal 32
:padding-vertical 16
:margin-vertical 16}}
[react/view
{:style {:flex-direction :row
:justify-content :space-between}}
[react/text (i18n/label :t/suggested-min-tip)]
[react/text (gwei priority-fee-gwei)]]
[react/view
{:style {:flex-direction :row
:justify-content :space-between}}
[react/text (i18n/label :t/suggested-price-limit)]
[react/text (gwei (money/add base-fee-gwei priority-fee-gwei))]]]
[react/view
{:style {:align-items :center
:justify-content :center
:margin-top 8}}
[quo/button
{:type :primary
:on-press #(re-frame/dispatch [:hide-popover])}
(i18n/label :t/change-tip)]]
[react/view
{:style {:align-items :center
:justify-content :center
:margin-top 8
:margin-bottom 16}}
[quo/button
{:type :secondary
:on-press #(do (re-frame/dispatch [:hide-popover])
(re-frame/dispatch [:signing.edit-fee.ui/submit true]))}
(i18n/label :t/continue-anyway)]]]))
(defn advanced
[]
(let [nonce (reagent/atom nil)
default-nonce (:nonce @(re-frame/subscribe [:signing/tx]))]
(fn []
[react/view {:padding 20}
[quo/text-input
{:label (i18n/label :t/nonce)
:accessibility-label :nonce
:keyboard-type :numeric
:default-value default-nonce
:on-change-text #(reset! nonce %)
:show-cancel false
:auto-focus true
:container-style {:margin-bottom 20}}]
[react/view {:align-items :flex-end}
[quo/button
{:type :primary
:accessibility-label :save-nonce
:theme :accent
:on-press #(re-frame/dispatch [:signing.nonce/submit @nonce])}
(i18n/label :t/save)]]])))
@@ -0,0 +1,65 @@
(ns legacy.status-im.ui.screens.signing.styles
(:require
[legacy.status-im.ui.components.colors :as colors]))
(def header
{:flex-direction :row
:align-items :center
:justify-content :space-between
:padding-top 16
:padding-left 16
:margin-bottom 11})
(def message-header
{:flex-direction :row
:align-items :center
:justify-content :space-between
:padding-top 20
:padding-left 16
:padding-right 24
:margin-bottom 19})
(defn message
[]
{:background-color colors/white
:border-top-right-radius 16
:border-top-left-radius 16
:padding-bottom 40})
(def message-border
{:margin-horizontal 24
:max-height 96
:min-height 35
:flex 1
:border-radius 8
:border-color colors/black-transparent
:border-width 1
:padding 8})
(defn sheet
[]
{:background-color colors/white
:border-top-right-radius 16
:border-top-left-radius 16
:padding-bottom 40})
(defn sign-with-keycard-button
[disabled?]
{:background-color (if (colors/dark?) "#27D8B9" colors/black-light)
:padding-top 2
:border-radius 8
:width 182
:height 44
:flex-direction :row
:justify-content :center
:align-items :center
:opacity (if disabled? 0.1 1)
:padding-horizontal 12})
(defn sign-with-keycard-button-text
[disabled?]
{:padding-right 2
:padding-left 16
:color (if disabled? colors/black colors/white-persist)
:padding-horizontal 16
:padding-vertical 10})
@@ -0,0 +1,386 @@
(ns legacy.status-im.ui.screens.signing.views
(:require-macros [legacy.status-im.utils.views :as views])
(:require
[clojure.string :as string]
[legacy.status-im.react-native.resources :as resources]
[legacy.status-im.signing.eip1559 :as eip1559]
[legacy.status-im.ui.components.bottom-panel.views :as bottom-panel]
[legacy.status-im.ui.components.chat-icon.screen :as chat-icon]
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.copyable-text :as copyable-text]
[legacy.status-im.ui.components.core :as quo]
[legacy.status-im.ui.components.icons.icons :as icons]
[legacy.status-im.ui.components.list.item :as list.item]
[legacy.status-im.ui.components.react :as react]
[legacy.status-im.ui.screens.signing.sheets :as sheets]
[legacy.status-im.ui.screens.signing.styles :as styles]
[legacy.status-im.utils.deprecated-types :as types]
[legacy.status-im.utils.utils :as utils]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.contexts.profile.utils :as profile.utils]
[utils.i18n :as i18n]
[utils.security.core :as security]))
(defn separator
[]
[react/view {:height 1 :background-color colors/gray-lighter}])
(defn contact-item
[title {:keys [address] :as profile}]
[copyable-text/copyable-text-view
{:copied-text address}
[list.item/list-item
{:title title
:title-prefix-width 45
:size :small
:accessory :text
:accessory-text [quo/text
{:ellipsize-mode :middle
:number-of-lines 1
:monospace true}
(profile.utils/displayed-name profile)]}]])
(defn token-item
[{:keys [icon color] :as token} display-symbol]
(when token
[react/view
[list.item/list-item
{:size :small
:title (i18n/label :t/wallet-asset)
:accessory [react/view {:flex-direction :row}
[quo/text
{:color :secondary
:style {:margin-right 8}}
display-symbol]
(if icon
[react/view]
[chat-icon/custom-icon-view-list (:name token) color 32])]}]
[separator]]))
(defn header
[{:keys [in-progress?] :as sign}
{:keys [contact amount approve? cancel?] :as tx}
display-symbol fee fee-display-symbol]
[react/view styles/header
(when sign
[react/touchable-highlight
(when-not in-progress? {:on-press #(re-frame/dispatch [:set :signing/sign nil])})
[react/view {:padding-right 16}
[icons/icon :main-icons/back]]])
[react/view {:flex 1}
(if amount
[react/text {:style {:typography :title-bold}}
(str (cond approve?
(i18n/label :t/authorize)
cancel?
(i18n/label :t/cancelling)
:else
(i18n/label :t/sending))
(if cancel?
(str " " (utils/get-shortened-address (:hash tx)))
(str " " amount " " display-symbol)))]
[react/text {:style {:typography :title-bold}} (i18n/label :t/contract-interaction)])
(if sign
[react/nested-text
{:style {:color colors/gray}
:ellipsize-mode :middle
:number-of-lines 1} (i18n/label :t/to-capitalized) " "
[{:style {:color colors/black}} (profile.utils/displayed-name contact)]]
[react/text {:style {:margin-top 6 :color colors/gray}}
(str fee " " fee-display-symbol " " (string/lower-case (i18n/label :t/network-fee)))])]
[react/view {:padding-horizontal 24}
[quo/button
(merge {:type :secondary}
(when-not in-progress? {:on-press #(re-frame/dispatch [:signing.ui/cancel-is-pressed])}))
(i18n/label :t/cancel)]]])
(defn sign-with-keycard-button
[amount-error gas-error]
(let [disabled? (or amount-error gas-error)]
[react/touchable-highlight
{:on-press #(when-not disabled?
(re-frame/dispatch [:signing.ui/sign-with-keycard-pressed]))}
[react/view (styles/sign-with-keycard-button disabled?)
[react/text {:style (styles/sign-with-keycard-button-text disabled?)}
(i18n/label :t/sign-with)]
[react/view {:padding-right 16}
[react/image
{:source (resources/get-image :keycard-logo)
:style (merge {:width 64
:margin-bottom 7
:height 26}
(when (colors/dark?)
{:tint-color colors/white-persist}))}]]]]))
(defn- signing-phrase-view
[phrase]
[react/view {:align-items :center}
[react/text {:style {:color colors/gray :padding-bottom 8}} (i18n/label :t/signing-phrase)]
[react/text phrase]])
(defn- keycard-view
[_ phrase]
[react/view
[signing-phrase-view phrase]
[react/view {:align-items :center :margin-top 16 :margin-bottom 40}
[sign-with-keycard-button nil nil]]])
(defn- transaction-data-item
[data]
(let [text (types/clj->pretty-json data 2)]
[react/view
[react/text
{:style {:font-size 17
:line-height 20
:margin-bottom 24}}
text]]))
(views/defview transaction-data
[]
(views/letsubs
[{:keys [formatted-data]} [:signing/sign]]
[react/view {:style {:flex 1}}
[react/view
{:style {:margin-horizontal 24
:margin-top 24}}
[react/text
{:style {:font-size 17
:font-weight "700"}}
(i18n/label :t/transaction-data)]]
[react/scroll-view
{:style {:flex 1
:margin-horizontal 8
:padding-horizontal 16
:padding-vertical 10
:margin-vertical 14}}
[transaction-data-item formatted-data]]
[separator]
[react/view
{:style {:margin-horizontal 8
:margin-vertical 16}}
[quo/button {:on-press #(re-frame/dispatch [:hide-popover])}
(i18n/label :t/close)]]]))
(views/defview password-view
[{:keys [type error in-progress? enabled?] :as sign}]
(views/letsubs [phrase [:signing/phrase]]
(case type
:password
[react/view {:padding-top 8 :padding-bottom 8}
[signing-phrase-view phrase]
[react/view
{:padding-horizontal 16
:padding-vertical 12}
[quo/text-input
{:secure-text-entry true
:placeholder (i18n/label :t/enter-password)
:on-change-text #(re-frame/dispatch [:signing.ui/password-is-changed
(security/mask-data %)])
:accessibility-label :enter-password-input
:auto-capitalize :none
:editable (not in-progress?)
:error error
:show-cancel false}]]
[react/view {:align-items :center :height 60}
(if in-progress?
[react/activity-indicator
{:animating true
:size :large}]
[quo/button
{:on-press #(re-frame/dispatch [:signing.ui/sign-is-pressed])
:disabled (not enabled?)}
(i18n/label :t/transactions-sign)])]]
:keycard
[keycard-view sign phrase]
[react/view])))
(views/defview message-sheet
[]
(views/letsubs [{:keys [formatted-data] :as sign} [:signing/sign-message]]
[react/view (styles/message)
[react/view styles/message-header
[react/text {:style {:typography :title-bold}} (i18n/label :t/signing-a-message)]
[react/touchable-highlight {:on-press #(re-frame/dispatch [:signing.ui/cancel-is-pressed])}
[react/view {:padding 6}
[react/text {:style {:color colors/blue}} (i18n/label :t/cancel)]]]]
[separator]
[react/view {:padding-top 16 :flex 1}
[react/view styles/message-border
[react/scroll-view
[react/text (or formatted-data "")]]]
[password-view sign]]]))
(defn error-item
[]
(fn [title show-error]
[react/touchable-highlight
{:on-press #(swap! show-error not)
:underlay-color (:interactive-02 @colors/theme)}
[react/view
{:style {:align-items :center
:flex-direction :row}}
[react/text {:style {:color colors/red :margin-right 8}}
(i18n/label title)]
[icons/icon :warning {:color colors/red}]]]))
(defn amount-item
[]
(let [show-error (reagent/atom false)]
(fn [prices wallet-currency amount amount-error display-symbol fee-display-symbol prices-loading?]
(let [converted-value (* amount
(get-in prices
[(keyword display-symbol) (keyword (:code wallet-currency))]))]
[list.item/list-item
{:size :small
:title (if amount-error
[error-item :t/send-request-amount show-error]
(i18n/label :t/send-request-amount))
:error (when (and amount-error @show-error) amount-error)
:animated false
:accessory [react/view {:style {:flex-direction :row}}
[react/nested-text {:style {:color colors/gray}}
[{:style {:color colors/black}} (utils/format-decimals amount 6)]
" "
(or display-symbol fee-display-symbol)
" • "]
(if prices-loading?
[react/small-loading-indicator]
[react/text {:style {:color colors/black}}
(i18n/format-currency converted-value (:code wallet-currency))])
[react/text {:style {:color colors/gray}} (str " " (:code wallet-currency))]]}]))))
(views/defview fee-item
[prices wallet-currency fee-display-symbol fee
insufficient-balance? gas-error gas-error-state prices-loading?]
(views/letsubs [{:keys [gas-price-loading? gas-loading?]} [:signing/edit-fee]
show-error (reagent/atom false)]
(let [converted-fee-value (* fee
(get-in prices
[(keyword fee-display-symbol)
(keyword (:code wallet-currency))]))]
[list.item/list-item
{:size :small
:title (if (and (not (or gas-price-loading? gas-loading?)) gas-error)
[error-item :t/network-fee show-error]
(i18n/label :t/network-fee))
:error (when (and (not (or gas-price-loading? gas-loading?)) gas-error @show-error)
gas-error)
:disabled (or insufficient-balance? gas-price-loading? gas-loading?)
:chevron (not insufficient-balance?)
:animated false
:accessory (when-not insufficient-balance?
(if (or gas-price-loading? gas-loading?)
[react/small-loading-indicator]
(if (= :gas-isnt-set gas-error-state)
[react/text
{:style {:color colors/blue}
:accessibility-label :custom-gas-fee}
(i18n/label :t/set-custom-fee)]
[react/view
{:style {:flex-direction :row}
:accessibility-label :custom-gas-fee}
[react/nested-text {:style {:color colors/gray}}
[{:style {:color colors/black}} (utils/format-decimals fee 6)]
" "
fee-display-symbol
" • "]
(if prices-loading?
[react/small-loading-indicator]
[react/text {:style {:color colors/black}}
(i18n/format-currency converted-fee-value (:code wallet-currency))])
[react/text {:style {:color colors/gray}}
(str " " (:code wallet-currency))]])))
:on-press #(re-frame/dispatch
[:signing.ui/open-fee-sheet
{:content (fn []
(if (eip1559/enabled?)
[sheets/fee-bottom-sheet-eip1559-custom fee-display-symbol]
[sheets/fee-bottom-sheet fee-display-symbol]))}])}])))
(views/defview network-item
[]
(views/letsubs [network-name [:network-name]]
[list.item/list-item
{:title (i18n/label :t/network)
:size :small
:accessory :text
:accessory-text network-name}]))
(defn advanced-item
[]
[:<>
[separator]
[list.item/list-item
{:size :small
:title (i18n/label :t/advanced)
:chevron true
:on-press #(re-frame/dispatch [:bottom-sheet/show-sheet-old {:content sheets/advanced}])}]])
(views/defview sheet
[{:keys [from contact amount token cancel?] :as tx}]
(views/letsubs [fee [:signing/fee]
sign [:signing/sign]
{:keys [amount-error gas-error gas-error-state insufficient-balalce?]}
[:signing/amount-errors (:address from)]
keycard-multiaccount? [:keycard-multiaccount?]
prices [:prices]
wallet-currency [:wallet-legacy/currency]
mainnet? [:mainnet?]
prices-loading? [:prices-loading?]
management-enabled? [:wallet-legacy/transactions-management-enabled?]]
(let [display-symbol nil
fee-display-symbol nil]
[react/view (styles/sheet)
[header sign tx display-symbol fee fee-display-symbol]
[separator]
(if sign
[react/view {:padding-top 20}
[password-view sign]]
[react/view
(when-not mainnet?
[react/view
[network-item]
[separator]])
[contact-item (i18n/label :t/from-capitalized) from]
[separator]
[contact-item (i18n/label :t/to-capitalized) contact]
(when-not cancel?
[separator])
(when-not cancel?
[token-item token display-symbol])
(when-not cancel?
[amount-item prices wallet-currency amount amount-error display-symbol fee-display-symbol
prices-loading?])
[separator]
[fee-item prices wallet-currency fee-display-symbol fee insufficient-balalce? gas-error
gas-error-state prices-loading?]
(when (and management-enabled? (not keycard-multiaccount?))
[advanced-item])
(when (= :gas-is-set gas-error-state)
[react/text {:style {:color colors/gray :margin-horizontal 32 :text-align :center}}
(i18n/label :t/tx-fail-description1)])
[react/view {:align-items :center :margin-top 16 :margin-bottom 40}
(if keycard-multiaccount?
[sign-with-keycard-button amount-error gas-error]
(if (= :gas-isnt-set gas-error-state)
[react/text {:style {:color colors/gray :margin-horizontal 32 :text-align :center}}
(i18n/label :t/tx-fail-description2)]
[quo/button
{:on-press #(re-frame/dispatch [:set :signing/sign {:type :password}])
:disabled (or amount-error gas-error)
:theme (if gas-error-state :negative :main)}
(i18n/label (if gas-error-state
:t/sign-anyway
:t/sign-with-password))]))]])])))
(views/defview signing
[]
(views/letsubs [tx [:signing/tx]]
[bottom-panel/animated-bottom-panel
;;we use select-keys here because we don't want to update view if other keys in map are changed
(when tx (select-keys tx [:from :contact :amount :token :approve? :message :cancel? :hash]))
#(if (:message %)
[message-sheet]
[sheet %])
#(re-frame/dispatch [:hide-signing-sheet])]))
@@ -13,11 +13,3 @@
:background-color (colors/theme-colors (colors/custom-color customization-color 50)
(colors/custom-color customization-color 60)
theme)})
(defn avatar-identifier
[theme]
{:text-align :center
:font-size 36
:color (colors/theme-colors colors/black
colors/white
theme)})
@@ -1,9 +1,7 @@
(ns quo.components.avatars.group-avatar.view
(:require
[clojure.string :as string]
[quo.components.avatars.group-avatar.style :as style]
[quo.components.icon :as icon]
[quo.components.markdown.text :as text]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
@@ -23,7 +21,7 @@
(defn- view-internal
[_]
(fn [{:keys [size theme customization-color picture icon-name emoji chat-name]
(fn [{:keys [size theme customization-color picture icon-name]
:or {size :size-20
customization-color :blue
picture nil
@@ -40,27 +38,8 @@
{:source picture
:style {:width container-size
:height container-size}}]
(cond
emoji
(if (= size :size-80)
[rn/text
{:style (style/avatar-identifier theme)}
emoji]
[text/text
{:size :paragraph-1
:style (dissoc (style/avatar-identifier theme) :font-size)}
emoji])
chat-name
(if (= size :size-80)
[rn/text
{:style (style/avatar-identifier theme)}
((comp first string/upper-case) chat-name)]
[text/text
{:size :paragraph-1}
((comp first string/upper-case) chat-name)])
:else
[icon/icon icon-name
{:size icon-size
:color colors/white-opa-70}]))])))
[icon/icon icon-name
{:size icon-size
:color colors/white-opa-70}])])))
(def view (quo.theme/with-theme view-internal))
@@ -1,71 +0,0 @@
(ns quo.components.community.community-detail-token-gating.component-spec
(:require
[quo.core :as quo]
[test-helpers.component :as h]))
(h/describe "Community Detail Token Gating Component"
(h/test "render with member permissions"
(h/render
[quo/community-detail-token-gating
{:permissions [{:role 2
:role-text "Member"
:satisfied? true
:tokens [[{:symbol "ETH"
:sufficient? true
:collectible? false
:amount 0.8
:img-src nil}]
[{:symbol "ETH"
:sufficient? false
:collectible? false
:amount 1
:img-src nil}
{:symbol "STT"
:sufficient? false
:collectible? false
:amount 10
:img-src nil}]]}]}])
(h/is-truthy (h/get-by-translation-text :t/you-eligible-to-join-as {:role "Member"}))
(h/is-truthy (h/get-by-text "0.8 ETH"))
(h/is-truthy (h/get-by-text "1 ETH"))
(h/is-truthy (h/get-by-text "10 STT")))
(h/test "render with admin permissions"
(h/render
[quo/community-detail-token-gating
{:permissions [{:role 1
:role-text "Admin"
:satisfied? true
:tokens [[{:symbol "ETH"
:sufficient? true
:collectible? false
:amount 2
:img-src nil}]]}]}])
(h/is-truthy (h/get-by-translation-text :t/you-eligible-to-join-as {:role "Admin"}))
(h/is-truthy (h/get-by-text "2 ETH")))
(h/test "render with token master permissions"
(h/render-with-theme-provider
[quo/community-detail-token-gating
{:permissions [{:role 5
:role-text "Token Master"
:satisfied? true
:tokens [[{:symbol "TMANI "
:sufficient? true
:collectible? true
:img-src {:uri "mock-image"}}]]}]}])
(h/is-truthy (h/get-by-translation-text :t/you-eligible-to-join-as {:role "Token Master"}))
(h/is-truthy (h/get-by-text "TMANI")))
(h/test "render with token owner permissions"
(h/render-with-theme-provider
[quo/community-detail-token-gating
{:permissions [{:role 6
:role-text "Token Owner"
:satisfied? true
:tokens [[{:symbol "TOANI"
:sufficient? true
:collectible? true
:img-src {:uri "mock-image"}}]]}]}])
(h/is-truthy (h/get-by-translation-text :t/you-eligible-to-join-as {:role "Token Owner"}))
(h/is-truthy (h/get-by-text "TOANI"))))
@@ -1,20 +0,0 @@
(ns quo.components.community.community-detail-token-gating.style)
(def container
{:padding-horizontal 20
:margin-vertical -4})
(defn token-row
[first?]
{:flex-direction :row
:margin-top (if first? 8 4)
:row-gap 10
:column-gap 8
:flex-wrap :wrap
:margin-bottom 16})
(def divider
{:padding-left 0
:height 28
:padding-top 0
:align-items :flex-start})
@@ -1,74 +0,0 @@
(ns quo.components.community.community-detail-token-gating.view
(:require [clojure.string :as string]
[quo.components.community.community-detail-token-gating.style :as style]
[quo.components.dividers.divider-label.view :as divider-label]
[quo.components.markdown.text :as text]
[quo.components.tags.collectible-tag.view :as collectible-tag]
[quo.components.tags.token-tag.view :as token-tag]
[quo.foundations.colors :as colors]
[react-native.core :as rn]
[utils.i18n :as i18n]))
(defn- token-view
[{:keys [collectible? img-src amount sufficient?] :as token}]
(let [token-symbol (:symbol token)]
(if collectible?
[collectible-tag/view
{:collectible-name token-symbol
:size :size-24
:collectible-img-src img-src
:options (when sufficient?
:hold)}]
[token-tag/view
{:token-symbol token-symbol
:size :size-24
:token-value amount
:token-img-src img-src
:options (when sufficient? :hold)}])))
(defn- tokens-row
[{:keys [tokens divider? first?]}]
[:<>
[rn/view
{:style (style/token-row first?)}
(map-indexed (fn [token-index token]
^{:key (str "token-" token-index)}
[token-view token])
tokens)]
(when-not divider?
[divider-label/view
{:container-style style/divider}
[text/text
{:size :label
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}}
(string/lower-case (i18n/label :t/or))]])])
(defn- role-view
[{:keys [role tokens satisfied? role-text]}]
(when (seq tokens)
^{:key (str "role-" role)}
[:<>
[text/text
{:size :paragraph-1
:weight :medium
:style {:margin-top 4}}
(if satisfied?
(i18n/label :t/you-eligible-to-join-as {:role role-text})
(i18n/label :t/you-not-eligible-to-join-as {:role role-text}))]
[text/text
{:size :paragraph-2
:style {:margin-bottom 8}}
(i18n/label (if satisfied? :t/you-hodl :t/you-must-hold))]
(map-indexed (fn [index tokens-item]
^{:key (str role "-tokens-" index)}
[tokens-row
{:tokens tokens-item
:first? (zero? index)
:divider? (= index (dec (count tokens)))}])
tokens)]))
(defn view
[{:keys [permissions]}]
[rn/view {:style style/container}
(map role-view permissions)])
+6 -7
View File
@@ -102,7 +102,7 @@
:right 8})
(def token-tag-spacing
{:padding-top 10
{:margin-top 10
:margin-right 8})
(defn token-row
@@ -117,12 +117,11 @@
(defn token-row-or-text
[padding? theme]
(merge
{:padding-top 4
:margin-bottom -2
:color (colors/theme-colors
colors/neutral-50
colors/neutral-40
theme)}
{:margin-top 4
:color (colors/theme-colors
colors/neutral-50
colors/neutral-40
theme)}
(when padding?
{:padding-left 12})))
@@ -7,14 +7,8 @@
:align-items :center})
(def image
{:width 72
:height 72})
(def image-container
{:width 80
:height 80
:align-items :center
:justify-content :center})
{:width 80
:height 80})
(def text-container
{:margin-top 12
@@ -15,10 +15,9 @@
[rn/view {:style (merge styles/container container-style)}
(if placeholder?
[rn/view {:style styles/image-placeholder}]
[rn/view {:style styles/image-container}
[fast-image/fast-image
{:style styles/image
:source image}]])
[fast-image/fast-image
{:style styles/image
:source image}])
[rn/view {:style styles/text-container}
[text/text
{:style (styles/title blur?)
+4 -8
View File
@@ -1,8 +1,7 @@
(ns quo.components.inputs.input.style
(:require
[quo.components.markdown.text :as text]
[quo.foundations.colors :as colors]
[react-native.platform :as platform]))
[quo.foundations.colors :as colors]))
(defn variants-colors
[blur? theme]
@@ -98,12 +97,9 @@
(assoc base-props
:text-align-vertical :top
:line-height 22)
(cond-> base-props
:always
(assoc :height (if small? 30 38)
:line-height nil)
platform/ios?
(assoc :padding-top (+ padding 2))))))
(assoc base-props
:height (if small? 30 38)
:line-height nil))))
(defn right-icon-touchable-area
[small?]
+4 -7
View File
@@ -41,24 +41,21 @@
(defn user
[{:keys [short-chat-key primary-name secondary-name photo-path online? contact? verified?
untrustworthy? on-press on-long-press accessory customization-color theme
allow-multiple-presses? disabled?]}]
allow-multiple-presses?]}]
[rn/touchable-highlight
{:style container-style
:underlay-color (colors/resolve-color customization-color theme 5)
:allow-multiple-presses? allow-multiple-presses?
:accessibility-label :user-list
:on-press (when on-press on-press)
:on-long-press (when on-long-press on-long-press)
:disabled disabled?}
:on-long-press (when on-long-press on-long-press)}
[:<>
[user-avatar/user-avatar
{:full-name primary-name
:profile-picture photo-path
:online? online?
:size :small}]
[rn/view
{:style {:margin-horizontal 8
:flex 1}}
[rn/view {:style {:margin-horizontal 8 :flex 1}}
[author/view
{:primary-name primary-name
:secondary-name secondary-name
@@ -72,4 +69,4 @@
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}}
short-chat-key])]
(when accessory
[action-icon accessory customization-color disabled? theme])]])
[action-icon accessory customization-color theme])]])
@@ -9,10 +9,11 @@
[quo.components.tags.status-tags :as status-tags]
[quo.foundations.colors :as colors]
[react-native.core :as rn]
[reagent.core :as reagent]
[utils.i18n :as i18n]))
(defn- activity-reply-text-input
[{:keys [on-update-reply max-reply-length valid-reply?]} reply-input set-reply-input]
[{:keys [on-update-reply max-reply-length valid-reply?]} reply-input]
[rn/view
[rn/view
{:style {:margin-top 16
@@ -25,12 +26,12 @@
(i18n/label :t/your-answer)]
[text/text
{:style {:flex-shrink 1
:color (if (valid-reply? reply-input)
:color (if (valid-reply? @reply-input)
colors/neutral-40
colors/danger-60)}}
(str (count reply-input) "/" max-reply-length)]]
(str (count @reply-input) "/" max-reply-length)]]
[input/input
{:on-change-text #(do (set-reply-input %)
{:on-change-text #(do (reset! reply-input %)
(when on-update-reply
(on-update-reply %)))
:auto-capitalize :none
@@ -135,7 +136,7 @@
(-> button
(assoc :size size)
(assoc :type subtype)
(assoc :disabled? (and replying? disable-when (disable-when reply-input)))
(assoc :disabled? (and replying? disable-when (disable-when @reply-input)))
(assoc :inner-style
{:justify-content :center
:padding-bottom 0
@@ -152,16 +153,17 @@
:blur? blur?}])
(defn- footer
[{:keys [replying? items] :as props}]
(let [[reply-input set-reply-input] (rn/use-state "")]
[:<>
(when replying?
[activity-reply-text-input props reply-input set-reply-input])
(when items
[rn/view style/footer-container
(for [item items]
^{:key (:key item)}
[footer-item-view item replying? reply-input])])]))
[_]
(let [reply-input (reagent/atom "")]
(fn [{:keys [replying? items] :as props}]
[:<>
(when replying?
[activity-reply-text-input props reply-input])
(when items
[rn/view style/footer-container
(for [item items]
^{:key (:key item)}
[footer-item-view item replying? reply-input])])])))
(defn view
[{:keys [icon
@@ -4,7 +4,8 @@
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.svg :as svg]))
[react-native.svg :as svg]
[reagent.core :as reagent]))
(defn- get-path-props
[size stroke-width rotation]
@@ -48,53 +49,60 @@
{:color {:dark colors/neutral-80-opa-40
:light colors/white-opa-40}})
(defn circle-timer
[{:keys [color duration size stroke-width trail-color rotation initial-remaining-time]}]
(let [theme (quo.theme/use-theme-value)
rotation (or rotation :clockwise)
duration (or duration 4)
stroke-width (or stroke-width 1)
size (or size 9)
max-stroke-width (max stroke-width 0)
{:keys [path path-length]} (get-path-props size max-stroke-width rotation)
start-at (get-start-at duration initial-remaining-time)
elapsed-time (rn/use-ref-atom 0)
prev-frame-time (rn/use-ref-atom nil)
frame-request (rn/use-ref-atom nil)
[display-time set-display-time] (rn/use-state start-at)
swap-elapsed-time-each-frame (fn swap-elapsed-time-each-frame [frame-time]
(if (nil? @prev-frame-time)
(do (reset! prev-frame-time frame-time)
(reset! frame-request (js/requestAnimationFrame
swap-elapsed-time-each-frame)))
(let [delta (- (/ frame-time 1000)
(/ @prev-frame-time 1000))
current-elapsed (swap! elapsed-time + delta)
current-display-time (+ start-at current-elapsed)
completed? (>= current-display-time
duration)]
(set-display-time
(if completed? duration current-display-time))
(when-not completed?
(reset! prev-frame-time frame-time)))))]
(rn/use-effect #(reset! frame-request (js/requestAnimationFrame swap-elapsed-time-each-frame)))
(rn/use-unmount #(js/cancelAnimationFrame @frame-request))
[rn/view
{:style {:position :relative
:width size
:height size}}
[svg/svg
{:view-box (str "0 0 " size " " size)
:width size
:height size}
[svg/path
{:d path :fill :none :stroke (or trail-color :transparent) :stroke-width stroke-width}]
(when-not (= display-time duration)
[svg/path
{:d path
:fill :none
:stroke (or color (get-in themes [:color theme]))
:stroke-linecap :square
:stroke-width stroke-width
:stroke-dasharray path-length
:stroke-dashoffset (linear-ease display-time 0 path-length duration)}])]]))
(defn- circle-timer-internal
[{:keys [color duration size stroke-width trail-color rotation initial-remaining-time theme]}]
(let [rotation (or rotation :clockwise)
duration (or duration 4)
stroke-width (or stroke-width 1)
size (or size 9)
max-stroke-width (max stroke-width 0)
{:keys [path path-length]} (get-path-props size max-stroke-width rotation)
start-at (get-start-at duration initial-remaining-time)
elapsed-time (reagent/atom 0)
prev-frame-time (reagent/atom nil)
frame-request (reagent/atom nil)
display-time (reagent/atom start-at)
;; get elapsed frame time
swap-elapsed-time-each-frame (fn swap-elapsed-time-each-frame [frame-time]
(if (nil? @prev-frame-time)
(do (reset! prev-frame-time frame-time)
(reset! frame-request (js/requestAnimationFrame
swap-elapsed-time-each-frame)))
(let [delta (- (/ frame-time 1000)
(/ @prev-frame-time 1000))
current-elapsed (swap! elapsed-time + delta)
current-display-time (+ start-at current-elapsed)
completed? (>= current-display-time duration)]
(reset! display-time (if completed?
duration
current-display-time))
(when-not completed?
(reset! prev-frame-time frame-time)
(reset! frame-request (js/requestAnimationFrame
swap-elapsed-time-each-frame))))))]
(reagent/create-class
{:component-will-unmount #(js/cancelAnimationFrame @frame-request)
:reagent-render
(fn []
(reset! frame-request (js/requestAnimationFrame swap-elapsed-time-each-frame))
[rn/view
{:style {:position :relative
:width size
:height size}}
[svg/svg
{:view-box (str "0 0 " size " " size)
:width size
:height size}
[svg/path
{:d path :fill :none :stroke (or trail-color :transparent) :stroke-width stroke-width}]
(when-not (= @display-time duration)
[svg/path
{:d path
:fill :none
:stroke (or color (get-in themes [:color theme]))
:stroke-linecap :square
:stroke-width stroke-width
:stroke-dasharray path-length
:stroke-dashoffset (linear-ease @display-time 0 path-length duration)}])]])})))
(def circle-timer (quo.theme/with-theme circle-timer-internal))
@@ -15,10 +15,10 @@
[rn/touchable-highlight
{:on-press on-press
:underlay-color :transparent}
(into
[into
[rn/view
{:style (merge (style/action-container theme) style)}]
children)])
children]])
(defn toast-undo-action-internal
[{:keys [undo-duration undo-on-press theme]}]
@@ -124,10 +124,9 @@
[remaining-tiles (- (count remaining-images) 3)]])]]))))
(defn collectible
[{:keys [images on-press on-long-press]}]
[{:keys [images on-press]}]
[rn/view {:style style/tile-outer-container}
[rn/pressable
{:on-press on-press
:on-long-press on-long-press
:style style/tile-inner-container}
{:on-press on-press
:style style/tile-inner-container}
[tile-container {:images images}]]])
+19 -16
View File
@@ -3,20 +3,23 @@
[quo.components.icon :as icon]
[quo.components.selectors.filter.style :as style]
[quo.theme :as quo.theme]
[react-native.core :as rn]))
[react-native.core :as rn]
[reagent.core :as reagent]))
(defn view
[{:keys [blur? customization-color on-press-out pressed?]}]
(let [theme (quo.theme/use-theme-value)
[pressed? set-pressed] (rn/use-state pressed?)
on-press-out (fn []
(set-pressed (not pressed?))
(when on-press-out (on-press-out pressed?)))]
[rn/touchable-without-feedback
{:accessibility-label :selector-filter
:on-press-out on-press-out}
[rn/view {:style (style/container-outer customization-color pressed? theme)}
[rn/view {:style (style/container-inner pressed? blur? theme)}
[icon/icon :i/unread
{:color (style/icon-color pressed? theme)
:size 20}]]]]))
(defn view-internal
[initial-props]
(let [pressed? (reagent/atom (:pressed? initial-props))]
(fn [{:keys [blur? customization-color theme on-press-out]}]
[rn/touchable-without-feedback
{:accessibility-label :selector-filter
:on-press-out (fn []
(swap! pressed? not)
(when on-press-out
(on-press-out @pressed?)))}
[rn/view {:style (style/container-outer customization-color @pressed? theme)}
[rn/view {:style (style/container-inner @pressed? blur? theme)}
[icon/icon :i/unread
{:color (style/icon-color @pressed? theme)
:size 20}]]]])))
(def view (quo.theme/with-theme view-internal))
@@ -2,20 +2,23 @@
(:require
[quo.components.selectors.reaction-resource :as reactions.resource]
[quo.components.selectors.reactions-selector.style :as style]
[react-native.core :as rn]))
[react-native.core :as rn]
[reagent.core :as reagent]))
(defn view
[{:keys [emoji container-style on-press
accessibility-label start-pressed?]
:or {accessibility-label :reaction}}]
(let [[pressed? set-pressed] (rn/use-state start-pressed?)
on-press (fn [e]
(set-pressed (not pressed?))
(when on-press (on-press e)))]
[rn/pressable
{:accessibility-label accessibility-label
:allow-multiple-presses? true
:style (merge (style/container pressed?)
container-style)
:on-press on-press}
[rn/text (reactions.resource/system-emojis emoji)]]))
[{:keys [start-pressed?]}]
(let [pressed? (reagent/atom start-pressed?)]
(fn [{:keys [emoji container-style on-press
accessibility-label]
:or {accessibility-label :reaction}}]
[rn/pressable
{:accessibility-label accessibility-label
:allow-multiple-presses? true
:style (merge (style/container @pressed?)
container-style)
:on-press (fn [e]
(swap! pressed? not)
(when on-press
(on-press e)))}
[rn/text
(reactions.resource/system-emojis emoji)]])))
@@ -3,47 +3,47 @@
[quo.components.icon :as icons]
[quo.components.selectors.selectors.style :as style]
[quo.theme :as quo.theme]
[react-native.core :as rn]))
[react-native.core :as rn]
[reagent.core :as reagent]))
(defn- handle-press
[on-change checked-atom checked?]
(when checked-atom (swap! checked-atom not))
(when on-change (on-change (not checked?))))
(defn- base-selector
[{:keys [default-checked? checked? disabled? blur? customization-color on-change container-style
label-prefix outer-style-fn inner-style-fn icon-style-fn theme]
:or {customization-color :blue}}]
(let [controlled-component? (some? checked?)
[internal-checked?
set-internal-checked?] (rn/use-state (when-not controlled-component?
(or default-checked? false)))
actual-checked? (if controlled-component? checked? internal-checked?)
accessibility-label (str label-prefix "-" (if actual-checked? "on" "off"))
test-id (str label-prefix "-component")
outer-styles (outer-style-fn {:checked? actual-checked?
[{:keys [default-checked? checked?]}]
(let [controlled-component? (some? checked?)
internal-checked? (when-not controlled-component?
(reagent/atom (or default-checked? false)))]
(fn [{:keys [checked? disabled? blur? customization-color on-change container-style
label-prefix outer-style-fn inner-style-fn icon-style-fn theme]
:or {customization-color :blue}}]
(let [actual-checked? (if controlled-component? checked? @internal-checked?)
accessibility-label (str label-prefix "-" (if actual-checked? "on" "off"))
test-id (str label-prefix "-component")
outer-styles (outer-style-fn {:checked? actual-checked?
:disabled? disabled?
:blur? blur?
:container-style container-style
:customization-color customization-color
:theme theme})
on-press (rn/use-callback
(fn []
(when-not (nil? internal-checked?)
(set-internal-checked? (not internal-checked?)))
(when on-change (on-change (not actual-checked?))))
[internal-checked? actual-checked? on-change])]
[rn/pressable
(when-not disabled?
{:on-press on-press
:allow-multiple-presses? true})
[rn/view
{:style outer-styles
:needs-offscreen-alpha-compositing true
:accessibility-label accessibility-label
:testID test-id}
[rn/view
{:style (inner-style-fn {:theme theme
:checked? actual-checked?
:blur? blur?
:customization-color customization-color})}
(when (and icon-style-fn actual-checked?)
[icons/icon :i/check-small (icon-style-fn actual-checked? blur? theme)])]]]))
:theme theme})]
[rn/pressable
(when-not disabled?
{:on-press #(handle-press on-change internal-checked? actual-checked?)
:allow-multiple-presses? true})
[rn/view
{:style outer-styles
:needs-offscreen-alpha-compositing true
:accessibility-label accessibility-label
:testID test-id}
[rn/view
{:style (inner-style-fn {:theme theme
:checked? actual-checked?
:blur? blur?
:customization-color customization-color})}
(when (and icon-style-fn actual-checked?)
[icons/icon :i/check-small (icon-style-fn actual-checked? blur? theme)])]]]))))
(defn- toggle
[props]
@@ -8,7 +8,8 @@
[:options {:optional true} [:maybe [:enum :add :hold]]]
[:size {:optional true} [:maybe [:enum :size-24 :size-32]]]
[:blur? {:optional true} [:maybe :boolean]]
[:theme :schema.common/theme]
[:collectible-img-src :schema.common/image-source]
[:collectible-name :string]
[:collectible-id {:optional true} [:maybe :string]]]]]
[:collectible-id :string]]]]
:any])
@@ -8,50 +8,52 @@
[quo.theme]
[react-native.core :as rn]
[react-native.hole-view :as hole-view]
[reagent.core :as reagent]
[schema.core :as schema]))
(defn- view-internal
[{:keys [options blur? collectible-img-src collectible-name collectible-id] :as props}]
(let [theme (quo.theme/use-theme-value)
[container-width
set-container-width] (rn/use-state 0)
on-layout (rn/use-callback
#(set-container-width (oops/oget % :nativeEvent :layout :width)))
size (or (:size props) :size-24)]
[rn/view {:on-layout on-layout}
[hole-view/hole-view
{:holes (if options
[{:x (- container-width
(case size
:size-24 10
:size-32 12
nil))
:y (case size
:size-24 -6
:size-32 -4
nil)
:width 16
:height 16
:borderRadius 8}]
[])}
[rn/view {:style (style/container size options blur? theme)}
[rn/image {:style (style/collectible-img size) :source collectible-img-src}]
[text/text
{:size :paragraph-2
:weight :medium
:style (style/label theme)}
collectible-name]
(when collectible-id
[text/text
{:size :paragraph-2
:weight :medium
:margin-left 5
:style (style/label theme)}
collectible-id])]]
(when options
[rn/view {:style (style/options-icon size)}
[icons/icon (if (= options :hold) :i/hold :i/add-token)
{:size 20
:no-color true}]])]))
[]
(let [container-width (reagent/atom 0)
on-layout #(->> (oops/oget % :nativeEvent :layout :width)
(reset! container-width))]
(fn [{:keys [options blur? theme collectible-img-src collectible-name collectible-id] :as props}]
(let [size (or (:size props) :size-24)]
[rn/view
{:on-layout on-layout}
[hole-view/hole-view
{:holes (if options
[{:x (- @container-width
(case size
:size-24 10
:size-32 12
nil))
:y (case size
:size-24 -6
:size-32 -4
nil)
:width 16
:height 16
:borderRadius 8}]
[])}
[rn/view {:style (style/container size options blur? theme)}
[rn/image {:style (style/collectible-img size) :source collectible-img-src}]
[text/text
{:size :paragraph-2
:weight :medium
:style (style/label theme)}
collectible-name]
[text/text
{:size :paragraph-2
:weight :medium
:margin-left 5
:style (style/label theme)}
collectible-id]]]
(when options
[rn/view {:style (style/options-icon size)}
[icons/icon (if (= options :hold) :i/hold :i/add-token)
{:size 20
:no-color true}]])]))))
(def view (schema/instrument #'view-internal component-schema/?schema))
(def view
(quo.theme/with-theme
(schema/instrument #'view-internal component-schema/?schema)))
+115 -118
View File
@@ -50,131 +50,128 @@
- `fade-end-percentage` Percentage where fading starts relative to the total
layout width of the `flat-list` data."
[{:keys [default-active data fade-end-percentage fade-end? on-change on-scroll scroll-event-throttle
scrollable?
scroll-on-press? size type labelled? disabled? blurred? icon-color]
:or {fade-end-percentage 0.8
fade-end? false
scroll-event-throttle 64
scrollable? false
scroll-on-press? false
size default-tab-size}
:as props}]
(let [[active-tab-id
set-active-tab-id] (rn/use-state default-active)
[fading set-fading] (rn/use-state {:fade-end-percentage fade-end-percentage})
flat-list-ref (rn/use-ref-atom nil)
maybe-mask-wrapper (rn/use-memo
(fn []
(if fade-end?
[{:keys [default-active fade-end-percentage]
:or {fade-end-percentage 0.8}}]
(let [active-tab-id (reagent/atom default-active)
fading (reagent/atom {:fade-end-percentage fade-end-percentage})
flat-list-ref (atom nil)]
(fn
[{:keys [data
fade-end-percentage
fade-end?
on-change
on-scroll
scroll-event-throttle
scrollable?
scroll-on-press?
size
type
labelled?
disabled?
blurred?
icon-color]
:or {fade-end-percentage fade-end-percentage
fade-end? false
scroll-event-throttle 64
scrollable? false
scroll-on-press? false
size default-tab-size}
:as props}]
(let [maybe-mask-wrapper (if fade-end?
[masked-view/masked-view
{:mask-element (reagent/as-element
[linear-gradient/linear-gradient
{:colors [:black :transparent]
:locations [(get fading :fade-end-percentage)
:locations [(get @fading :fade-end-percentage)
1]
:start {:x 0 :y 0}
:end {:x 1 :y 0}
:pointer-events :none
:style {:width "100%"
:height "100%"}}])}]
[:<>]))
[fade-end? fading])
on-scroll (rn/use-context
(fn [^js e]
(when fade-end?
(let [offset-x (oget e "nativeEvent.contentOffset.x")
content-width (oget e "nativeEvent.contentSize.width")
layout-width (oget e "nativeEvent.layoutMeasurement.width")
new-percentage (calculate-fade-end-percentage
{:offset-x offset-x
:content-width content-width
:layout-width layout-width
:max-fade-percentage fade-end-percentage})]
;; Avoid unnecessary re-rendering.
(when (not= new-percentage (get fading :fade-end-percentage))
(set-fading (assoc fading :fade-end-percentage new-percentage)))))
(when on-scroll
(on-scroll e)))
[fade-end? fading fade-end-percentage])
data-count (count data)
style-padding-left (get-in props [:style :padding-left])
render-fn (rn/use-callback
(fn [{:keys [id label resource]} index]
[rn/view
{:style {:margin-right (if (= size default-tab-size) 12 8)
:padding-right (when (= index (dec data-count))
style-padding-left)}}
[tag/tag
{:id id
:size size
:active (= id active-tab-id)
:resource resource
:blurred? blurred?
:icon-color icon-color
:disabled? disabled?
:label (if labelled?
label
(when (= type :label) label))
:type type
:labelled? labelled?
:on-press (fn [id]
(set-active-tab-id id)
(when scroll-on-press?
(.scrollToIndex ^js @flat-list-ref
#js
{:animated true
:index index
:viewPosition 0.5}))
(when on-change
(on-change id)))}]])
[size default-tab-size data-count style-padding-left active-tab-id blurred?
icon-color disabled? labelled? type scroll-on-press? on-change])
on-press (rn/use-callback #(do (set-active-tab-id %) (when on-change (on-change %)))
[on-change])
key-fn (rn/use-callback (comp str :id))
ref (rn/use-callback #(reset! flat-list-ref %))
clean-props (dissoc props
:default-active
:fade-end-percentage
:fade-end?
:on-change
:scroll-on-press?
:size)]
(if scrollable?
(conj
maybe-mask-wrapper
[rn/flat-list
(merge
clean-props
(when scroll-on-press?
{:initial-scroll-index (utils.collection/first-index #(= active-tab-id (:id %)) data)})
{:ref ref
:extra-data (str active-tab-id)
:horizontal true
:scroll-event-throttle scroll-event-throttle
:shows-horizontal-scroll-indicator false
:data data
:key-fn key-fn
:on-scroll on-scroll
:render-fn render-fn})])
[rn/view {:style {:flex-direction :row}}
(for [{:keys [id label resource]} data]
^{:key id}
[rn/view {:style {:margin-right 8}}
[tag/tag
{:id id
:size size
:type type
:label (if labelled?
label
(when (= type :label) label))
:active (= id active-tab-id)
:disabled? disabled?
:blurred? blurred?
:icon-color icon-color
:labelled? (if (= type :label) true labelled?)
:resource (if (= type :icon) :i/placeholder resource)
:on-press on-press}]])])))
[:<>])]
(if scrollable?
(conj
maybe-mask-wrapper
[rn/flat-list
(merge
(dissoc props
:default-active
:fade-end-percentage
:fade-end?
:on-change
:scroll-on-press?
:size)
(when scroll-on-press?
{:initial-scroll-index (utils.collection/first-index #(= @active-tab-id (:id %)) data)})
{:ref #(reset! flat-list-ref %)
:extra-data (str @active-tab-id)
:horizontal true
:scroll-event-throttle scroll-event-throttle
:shows-horizontal-scroll-indicator false
:data data
:key-fn (comp str :id)
:on-scroll (fn [^js e]
(when fade-end?
(let [offset-x (oget e "nativeEvent.contentOffset.x")
content-width (oget e "nativeEvent.contentSize.width")
layout-width (oget e "nativeEvent.layoutMeasurement.width")
new-percentage (calculate-fade-end-percentage
{:offset-x offset-x
:content-width content-width
:layout-width layout-width
:max-fade-percentage fade-end-percentage})]
;; Avoid unnecessary re-rendering.
(when (not= new-percentage (get @fading :fade-end-percentage))
(swap! fading assoc :fade-end-percentage new-percentage))))
(when on-scroll
(on-scroll e)))
:render-fn (fn [{:keys [id label resource]} index]
[rn/view
{:style {:margin-right (if (= size default-tab-size) 12 8)
:padding-right (when (= index (dec (count data)))
(get-in props [:style :padding-left]))}}
[tag/tag
{:id id
:size size
:active (= id @active-tab-id)
:resource resource
:blurred? blurred?
:icon-color icon-color
:disabled? disabled?
:label (if labelled?
label
(when (= type :label) label))
:type type
:labelled? labelled?
:on-press (fn [id]
(reset! active-tab-id id)
(when scroll-on-press?
(.scrollToIndex ^js @flat-list-ref
#js
{:animated true
:index index
:viewPosition 0.5}))
(when on-change
(on-change id)))}]])})])
[rn/view {:style {:flex-direction :row}}
(for [{:keys [id label resource]} data]
^{:key id}
[rn/view {:style {:margin-right 8}}
[tag/tag
(merge {:id id
:size size
:type type
:label (if labelled?
label
(when (= type :label) label))
:active (= id active-tab-id)
:disabled? disabled?
:blurred? blurred?
:icon-color icon-color
:labelled? (if (= type :label) true labelled?)
:resource (if (= type :icon)
:i/placeholder
resource)
:on-press #(do (reset! active-tab-id %)
(when on-change (on-change %)))})]])])))))
+45 -43
View File
@@ -7,9 +7,10 @@
[quo.components.utilities.token.view :as token]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.hole-view :as hole-view]))
[react-native.hole-view :as hole-view]
[reagent.core :as reagent]))
(defn view
(defn- view-internal
"Options:
- :options - false / :add / :hold (default false)
- :size - :size-24 / :size-32 (default :size-24)
@@ -17,44 +18,45 @@
- :theme - :light / :dark
- :token-value - string - token value
- :token-symbol - string"
[{:keys [options size blur? token-value token-img-src token-symbol]
:or {size :size-24}}]
(let [theme (quo.theme/use-theme-value)
[container-width
set-container-width] (rn/use-state 0)
on-layout (rn/use-callback #(set-container-width
(oget % :nativeEvent :layout :width)))]
[rn/view {:on-layout on-layout}
[hole-view/hole-view
{:holes (if options
[{:x (- container-width
(case size
:size-24 10
:size-32 12
nil))
:y (case size
:size-24 -6
:size-32 -4
nil)
:width 16
:height 16
:borderRadius 8}]
[])}
[rn/view {:style (style/container size options blur? theme)}
[token/view
{:style (style/token-img size)
:token token-symbol
:size (case size
:size-24 :size-20
:size-32 :size-28)
:image-source token-img-src}]
[text/text
{:size :paragraph-2
:weight :medium
:style (style/label theme)}
(str token-value " " token-symbol)]]]
(when options
[rn/view {:style (style/options-icon size)}
[icons/icon (if (= options :hold) :i/hold :i/add-token)
{:size 20
:no-color true}]])]))
[]
(let [container-width (reagent/atom 0)]
(fn [{:keys [options size blur? theme token-value token-img-src token-symbol]
:or {size :size-24}}]
[rn/view
{:on-layout #(reset! container-width
(oget % :nativeEvent :layout :width))}
[hole-view/hole-view
{:holes (if options
[{:x (- @container-width
(case size
:size-24 10
:size-32 12
nil))
:y (case size
:size-24 -6
:size-32 -4
nil)
:width 16
:height 16
:borderRadius 8}]
[])}
[rn/view {:style (style/container size options blur? theme)}
[token/view
{:style (style/token-img size)
:token token-symbol
:size (case size
:size-24 :size-20
:size-32 :size-28)
:image-source token-img-src}]
[text/text
{:size :paragraph-2
:weight :medium
:style (style/label theme)}
(str token-value " " token-symbol)]]]
(when options
[rn/view {:style (style/options-icon size)}
[icons/icon (if (= options :hold) :i/hold :i/add-token)
{:size 20
:no-color true}]])])))
(def view (quo.theme/with-theme view-internal))
@@ -4,6 +4,7 @@
[:map
[:type {:optional true} [:enum :default :watch-only :add-account :empty :missing-keypair]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
[:theme :schema.common/theme]
[:metrics? {:optional true} [:maybe :boolean]]
[:on-press {:optional true} [:maybe fn?]]])
@@ -11,6 +11,7 @@
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.linear-gradient :as linear-gradient]
[reagent.core :as reagent]
[schema.core :as schema]))
(defn- loading-view
@@ -95,87 +96,87 @@
:end {:x 1 :y 0}}])
(defn- user-account
[{:keys [name balance percentage-value loading? amount customization-color type emoji metrics?
on-press]}]
(let [theme (quo.theme/use-theme-value)
[pressed? set-pressed] (rn/use-state false)
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed false))
watch-only? (= :watch-only type)
missing-keypair? (= :missing-keypair type)]
(if loading?
[loading-view
{:customization-color customization-color
:type type
:theme theme
:metrics? metrics?}]
[rn/pressable
{:on-press-in on-press-in
:on-press-out on-press-out
:style (style/card {:customization-color customization-color
:type type
:theme theme
:pressed? pressed?
:metrics? metrics?})
:on-press on-press}
(when (and customization-color (and (not watch-only?) (not missing-keypair?)))
[customization-colors/overlay
{:customization-color customization-color
:border-radius 16
:theme theme
:pressed? pressed?}])
[rn/view {:style style/profile-container}
[rn/view {:style {:padding-bottom 2 :margin-right 2}}
[text/text {:style style/emoji} emoji]]
[rn/view {:style style/watch-only-container}
[text/text
{:size :paragraph-2
:weight :medium
:number-of-lines 1
:max-width 110
:margin-right 4
:ellipis-mode :tail
:style (style/account-name type theme)}
name]
(when watch-only? [icon/icon :i/reveal {:color colors/neutral-50 :size 12}])
(when missing-keypair?
[icon/icon :i/alert {:color (properties/alert-icon-color theme) :size 12}])]]
[text/text
{:size :heading-2
:weight :semi-bold
:style (style/account-value type theme)}
balance]
(when metrics?
[rn/view {:style style/metrics-container}
[metrics-percentage type theme percentage-value]
(when (not= :empty type)
[metrics-info type theme amount])])
(when watch-only?
[gradient-overview theme customization-color])])))
[_]
(let [pressed? (reagent/atom false)
on-press-in #(reset! pressed? true)
on-press-out #(reset! pressed? false)]
(fn [{:keys [name balance percentage-value loading? amount customization-color type emoji metrics?
theme on-press]}]
(let [watch-only? (= :watch-only type)
missing-keypair? (= :missing-keypair type)]
(if loading?
[loading-view
{:customization-color customization-color
:type type
:theme theme
:metrics? metrics?}]
[rn/pressable
{:on-press-in on-press-in
:on-press-out on-press-out
:style (style/card {:customization-color customization-color
:type type
:theme theme
:pressed? @pressed?
:metrics? metrics?})
:on-press on-press}
(when (and customization-color (and (not watch-only?) (not missing-keypair?)))
[customization-colors/overlay
{:customization-color customization-color
:border-radius 16
:theme theme
:pressed? @pressed?}])
[rn/view {:style style/profile-container}
[rn/view {:style {:padding-bottom 2 :margin-right 2}}
[text/text {:style style/emoji} emoji]]
[rn/view {:style style/watch-only-container}
[text/text
{:size :paragraph-2
:weight :medium
:number-of-lines 1
:max-width 110
:margin-right 4
:ellipis-mode :tail
:style (style/account-name type theme)}
name]
(when watch-only? [icon/icon :i/reveal {:color colors/neutral-50 :size 12}])
(when missing-keypair?
[icon/icon :i/alert {:color (properties/alert-icon-color theme) :size 12}])]]
[text/text
{:size :heading-2
:weight :semi-bold
:style (style/account-value type theme)}
balance]
(when metrics?
[rn/view {:style style/metrics-container}
[metrics-percentage type theme percentage-value]
(when (not= :empty type)
[metrics-info type theme amount])])
(when watch-only?
[gradient-overview theme customization-color])])))))
(defn- add-account-view
[{:keys [on-press customization-color metrics?]}]
(let [theme (quo.theme/use-theme-value)
[pressed? set-pressed] (rn/use-state false)
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed false))]
[rn/pressable
{:on-press on-press
:on-press-in on-press-in
:on-press-out on-press-out
:style (style/add-account-container {:theme theme
:metrics? metrics?
:pressed? pressed?})}
[button/button
{:on-press on-press
:type :primary
:size 24
:icon true
:accessibility-label :add-account
:pressed? pressed?
:icon-only? true
:customization-color customization-color}
:i/add]]))
[_]
(let [pressed? (reagent/atom false)]
(fn [{:keys [on-press customization-color theme metrics?]}]
[rn/pressable
{:on-press on-press
:on-press-in #(reset! pressed? true)
:on-press-out #(reset! pressed? false)
:style (style/add-account-container {:theme theme
:metrics? metrics?
:pressed? @pressed?})}
[button/button
{:type :primary
:size 24
:icon true
:accessibility-label :add-account
:on-press on-press
:pressed? @pressed?
:on-press-in #(reset! pressed? true)
:on-press-out #(reset! pressed? false)
:customization-color customization-color
:icon-only? true}
:i/add]])))
(defn- view-internal
[{:keys [type] :as props}]
@@ -184,4 +185,6 @@
:add-account [add-account-view props]
nil))
(def view (schema/instrument #'view-internal component-schema/?schema))
(def view
(quo.theme/with-theme
(schema/instrument #'view-internal component-schema/?schema)))
@@ -6,7 +6,9 @@
[:props
[:map {:closed true}
[:status {:optional true} [:maybe [:enum :default :error]]]
[:on-change-text {:optional true} [:maybe fn?]]
[:theme :schema.common/theme]
[:on-inc-press {:optional true} [:maybe fn?]]
[:on-dec-press {:optional true} [:maybe fn?]]
[:container-style {:optional true} [:maybe :map]]
[:min-value {:optional true} [:maybe :int]]
[:max-value {:optional true} [:maybe :int]]
@@ -4,7 +4,7 @@
[quo.components.markdown.text :as text]
[quo.components.wallet.amount-input.schema :as amount-input.schema]
[quo.components.wallet.amount-input.style :as style]
[quo.theme]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[schema.core :as schema]))
@@ -21,34 +21,35 @@
icon])
(defn- view-internal
[{:keys [on-inc-press on-dec-press status value min-value max-value
[{:keys [on-inc-press on-dec-press theme status value min-value max-value
container-style]
:or {value 0
min-value 0
max-value 999999999}}]
(let [theme (quo.theme/use-theme-value)]
[rn/view
{:style (merge style/container container-style)}
[amount-button
{:theme theme
:accessibility-label :amount-input-dec-button
:icon :i/remove
:on-press on-dec-press
:disabled? (>= min-value value)}]
[rn/view {:style style/input-container}
[text/text
{:number-of-lines 1
:accessibility-label :amount-input
:weight :semi-bold
:size :heading-1
:align-self :center
:style (style/input-text theme (or status :default))}
value]]
[amount-button
{:theme theme
:icon :i/add
:accessibility-label :amount-input-inc-button
:on-press on-inc-press
:disabled? (>= value max-value)}]]))
[rn/view
{:style (merge style/container container-style)}
[amount-button
{:theme theme
:accessibility-label :amount-input-dec-button
:icon :i/remove
:on-press on-dec-press
:disabled? (>= min-value value)}]
[rn/view {:style style/input-container}
[text/text
{:number-of-lines 1
:accessibility-label :amount-input
:weight :semi-bold
:size :heading-1
:align-self :center
:style (style/input-text theme (or status :default))}
value]]
[amount-button
{:theme theme
:icon :i/add
:accessibility-label :amount-input-inc-button
:on-press on-inc-press
:disabled? (>= value max-value)}]])
(def view (schema/instrument #'view-internal amount-input.schema/?schema))
(def view
(quo.theme/with-theme
(schema/instrument #'view-internal amount-input.schema/?schema)))
+5 -4
View File
@@ -37,9 +37,8 @@
:border? true}]))
(defn title-view
[{:keys [details action selected? type blur? customization-color on-options-press]}]
(let [theme (quo.theme/use-theme-value)
{:keys [full-name]} details]
[{:keys [details action selected? type blur? customization-color on-options-press theme]}]
(let [{:keys [full-name]} details]
[rn/view
{:style style/title-container
:accessibility-label :title}
@@ -90,7 +89,7 @@
[item & _rest]
[account-list-card/view item])
(defn view
(defn- view-internal
[{:keys [accounts action container-style selected? on-press] :as props}]
[rn/pressable
{:style (style/container (merge props
@@ -109,3 +108,5 @@
:render-fn acc-list-card
:separator [rn/view {:style {:height 8}}]
:style {:padding-horizontal 8}}]])
(def view (quo.theme/with-theme view-internal))
@@ -1,23 +0,0 @@
(ns quo.components.wallet.network-link.helpers)
(def ^:private central-figure-width 63)
(defn calculate-side-lines-path-1x
"Calculates the `d` attribute for the side lines based on the SVG width."
[width]
(let [side-offset (/ (- width central-figure-width) 2)]
{:left (str "M0 57 L" side-offset " 57")
:right (str "M" (+ side-offset central-figure-width) " 1 L" width " 1")}))
(defn calculate-transform
"Calculates the transform attribute for the central figure based on the SVG width."
[width]
(let [translate-x (/ (- width central-figure-width) 2)]
(str "translate(" translate-x " 0)")))
(defn calculate-side-lines-path-2x
"Calculates the `d` attribute for the side lines based on the SVG width."
[width]
(let [side-offset (/ (- width central-figure-width) 2)]
{:left (str "M0 113 L" side-offset " 113")
:right (str "M" (+ side-offset central-figure-width) " 1 L" width " 1")}))
@@ -1,34 +0,0 @@
(ns quo.components.wallet.network-link.style)
(def left-circle-container
{:position :absolute
:left -3})
(def right-circle-container
{:position :absolute
:right -3})
(def bottom-left-circle-container
{:position :absolute
:bottom -3
:left -3})
(def top-right-circle-container
{:position :absolute
:top -3
:right -3})
(def link-linear-container
{:flex-direction :row
:align-items :center
:height 10})
(def link-1x-container
{:flex 1
:height 58
:justify-content :center})
(def link-2x-container
{:flex 1
:height 114
:justify-content :center})
+62 -131
View File
@@ -1,151 +1,82 @@
(ns quo.components.wallet.network-link.view
(:require
[oops.core :refer [oget]]
[quo.components.wallet.network-link.helpers :as helpers]
[quo.components.wallet.network-link.schema :as component-schema]
[quo.components.wallet.network-link.style :as style]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.svg :as svg]
[schema.core :as schema]))
(defn- circle
[fill stroke]
[svg/svg
{:height "8"
:width "8"}
[svg/circle
{:cx "4"
:cy "4"
:r "3.5"
:fill fill
:stroke stroke
:strokeWidth "1"}]])
(defn- line
[stroke width]
[svg/svg
{:height "10"
:width "100%"
:view-box (str "0 0 " width " 10")}
[svg/path
{:d (str "M0,5 L" width ",5")
:stroke stroke
:stroke-width "1"}]])
(defn link-linear
[{:keys [source theme]}]
(let [[container-width
set-container-width] (rn/use-state 100)
stroke-color (colors/resolve-color source theme)
fill-color (colors/theme-colors colors/white colors/neutral-90 theme)
on-layout (rn/use-callback #(set-container-width
(oget % :nativeEvent :layout :width)))]
[rn/view
{:style style/link-linear-container
:on-layout on-layout}
[line stroke-color container-width]
[rn/view {:style style/left-circle-container}
[circle fill-color stroke-color]]
[rn/view {:style style/right-circle-container}
[circle fill-color stroke-color]]]))
[svg/svg {:xmlns "http://www.w3.org/2000/svg" :width "73" :height "10" :fill :none}
[svg/path {:stroke (colors/resolve-color source theme) :d "M68 5H5"}]
[svg/circle
{:cx "68"
:cy "5"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color source theme)}]
[svg/circle
{:cx "5"
:cy "5"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color source theme)}]])
(defn link-1x
[{:keys [source destination theme]}]
(let [[container-width
set-container-width] (rn/use-state 100)
stroke-color "url(#gradient)"
source-color (colors/resolve-color source theme)
destination-color (colors/resolve-color destination theme)
fill-color (colors/theme-colors colors/white colors/neutral-90 theme)
view-box (str "0 0 " container-width " 58")
side-lines-path (helpers/calculate-side-lines-path-1x container-width)
central-transform (helpers/calculate-transform container-width)
on-layout (rn/use-callback #(set-container-width
(oget % :nativeEvent :layout :width)))]
[rn/view
{:style style/link-1x-container
:on-layout on-layout}
[svg/svg
{:xmlns "http://www.w3.org/2000/svg"
:height "100%"
:width "100%"
:view-box view-box
:fill :none}
[svg/path
{:d (:left side-lines-path)
:stroke source-color}]
[svg/path
{:d
"M63 1L53.6356 1C42.5899 1 33.6356 9.9543 33.6356 21L33.6356 37C33.6356 48.0457 24.6813 57 13.6356 57L2.85889e-05 57"
:transform central-transform
:stroke stroke-color}]
[svg/path
{:d (:right side-lines-path)
:stroke destination-color}]
[svg/defs
[svg/linear-gradient
{:id "gradient"
:x1 "72.271"
:x2 "82.385"
:y1 "5"
:y2 "34.155"
:gradient-units "userSpaceOnUse"}
[svg/stop {:stop-color (colors/resolve-color destination theme)}]
[svg/stop {:offset "1" :stop-color (colors/resolve-color source theme)}]]]]
[rn/view {:style style/bottom-left-circle-container}
[circle fill-color source-color]]
[rn/view {:style style/top-right-circle-container}
[circle fill-color destination-color]]]))
[svg/svg {:xmlns "http://www.w3.org/2000/svg" :width "73" :height "66" :fill :none}
[svg/path
{:stroke "url(#gradient)" :d "M68 5h-9.364c-11.046 0-20 8.954-20 20v16c0 11.046-8.955 20-20 20H5"}]
[svg/circle
{:cx "68"
:cy "5"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color destination theme)}]
[svg/circle
{:cx "5"
:cy "61"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color source theme)}]
[svg/defs
[svg/linear-gradient
{:id "gradient" :x1 "72.271" :x2 "82.385" :y1 "5" :y2 "34.155" :gradientUnits "userSpaceOnUse"}
[svg/stop {:stopColor (colors/resolve-color destination theme)}]
[svg/stop {:offset "1" :stopColor (colors/resolve-color source theme)}]]]])
(defn link-2x
[{:keys [source destination theme]}]
(let [[container-width
set-container-width] (rn/use-state 100)
stroke-color "url(#gradient)"
source-color (colors/resolve-color source theme)
destination-color (colors/resolve-color destination theme)
fill-color (colors/theme-colors colors/white colors/neutral-90 theme)
view-box (str "0 0 " container-width " 114")
side-lines-path (helpers/calculate-side-lines-path-2x container-width)
central-transform (helpers/calculate-transform container-width)
on-layout (rn/use-callback #(set-container-width
(oget % :nativeEvent :layout :width)))]
[rn/view
{:style style/link-2x-container
:on-layout on-layout}
[svg/svg
{:xmlns "http://www.w3.org/2000/svg"
:height "100%"
:width "100%"
:view-box view-box
:fill :none}
[svg/path
{:d (:left side-lines-path)
:stroke source-color}]
[svg/path
{:d
"M62.9999 1L53.6356 1C42.5899 1 33.6356 9.9543 33.6356 21L33.6356 93C33.6356 104.046 24.6813 113 13.6356 113L5.71778e-05 113"
:transform central-transform
:stroke stroke-color}]
[svg/path
{:d (:right side-lines-path)
:stroke destination-color}]
[svg/defs
[svg/linear-gradient
{:id "gradient"
:x1 "72.2711"
:y1 "5.00001"
:x2 "102.867"
:y2 "49.0993"
:gradient-units "userSpaceOnUse"}
[svg/stop {:stop-color (colors/resolve-color destination theme)}]
[svg/stop {:offset "1" :stop-color (colors/resolve-color source theme)}]]]]
[rn/view {:style style/bottom-left-circle-container}
[circle fill-color source-color]]
[rn/view {:style style/top-right-circle-container}
[circle fill-color destination-color]]]))
[svg/svg
{:width "73" :height "122" :viewBox "0 0 73 122" :fill "none" :xmlns "http://www.w3.org/2000/svg"}
[svg/path
{:d
"M67.9999 5L58.6356 5C47.5899 5 38.6356 13.9543 38.6356 25L38.6356 97C38.6356 108.046 29.6813 117 18.6356 117L5.00006 117"
:stroke "url(#gradient)"}]
[svg/circle
{:cx "68"
:cy "5"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color destination theme)}]
[svg/circle
{:cx "5"
:cy "117"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color source theme)}]
[svg/defs
[svg/linear-gradient
{:id "gradient"
:x1 "72.2711"
:y1 "5.00001"
:x2 "102.867"
:y2 "49.0993"
:gradientUnits "userSpaceOnUse"}
[svg/stop {:stop-color (colors/resolve-color destination theme)}]
[svg/stop {:offset "1" :stop-color (colors/resolve-color source theme)}]]]])
(defn- view-internal
[{:keys [shape container-style] :as props}]
@@ -12,5 +12,6 @@
[:amount :int]
[:max-amount :int]
[:network-name [:or :string :keyword]]]]]]
[:container-style {:optional true} [:maybe :map]]]]]
[:container-style {:optional true} [:maybe :map]]
[:theme :schema.common/theme]]]]
:any])
@@ -29,13 +29,13 @@
:background-color (colors/resolve-color network-name nil 10)})
(defn network-bar
[{:keys [bar-max-width on-top? bar-division? theme]
[{:keys [max-width on-top? bar-division? theme]
{:keys [network-name translate-x-shared-value]} :bar}
width-shared-value]
(reanimated/apply-animations-to-style
{:width width-shared-value
:transform [{:translate-x translate-x-shared-value}]}
{:max-width bar-max-width
{:max-width max-width
:flex-direction :row
:justify-content :flex-end
:background-color (colors/resolve-color network-name nil)
@@ -8,6 +8,7 @@
[react-native.core :as rn]
[react-native.gesture :as gesture]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[schema.core :as schema]
[utils.number]))
@@ -21,73 +22,60 @@
(js/clearTimeout (k @timeouts))
(swap! timeouts assoc k (js/setTimeout exec-fn-and-remove-timeout ms))))
(def get-gesture
(memoize
(fn
[[detecting-gesture? slider-width-shared-value max-amount
slider-height-shared-value
amount-shared-value amount-on-gesture-start width->amount
slider-opacity-shared-value on-new-amount set-detecting-gesture]]
(-> (gesture/gesture-pan)
(gesture/enabled detecting-gesture?)
(gesture/on-begin
(fn [_]
(animation/increase-slider slider-width-shared-value slider-height-shared-value)
(reset! amount-on-gesture-start (reanimated/get-shared-value amount-shared-value))))
(gesture/on-update
(fn [event]
(let [new-amount (-> (oops/oget event "translationX")
(width->amount)
(+ @amount-on-gesture-start)
(utils.number/value-in-range 1 max-amount))]
(reanimated/set-shared-value amount-shared-value new-amount))))
(gesture/on-finalize
(fn [_]
(animation/decrease-slider slider-width-shared-value slider-height-shared-value)
(animation/hide-slider slider-opacity-shared-value)
(on-new-amount (reanimated/get-shared-value amount-shared-value))
(add-new-timeout :turn-off-gesture #(set-detecting-gesture false) 20)))))))
(defn- f-slider
[slider-shared-values]
[rn/view {:style style/slider-container}
[reanimated/view {:style (style/slider slider-shared-values)}]])
(defn network-bar
[{:keys [total-width total-amount on-press on-new-amount allow-press?
bar-idx bar-width bar-max-width]
{:keys [amount-shared-value
max-amount]
:as bar} :bar
:as props}]
(let [[detecting-gesture?
set-detecting-gesture] (rn/use-state false)
amount-on-gesture-start (rn/use-ref-atom 0)
slider-width-shared-value (reanimated/use-shared-value 4)
slider-height-shared-value (reanimated/use-shared-value 32)
slider-opacity-shared-value (reanimated/use-shared-value 0)
network-bar-shared-value (reanimated/interpolate amount-shared-value
[0 total-amount]
[0 total-width])
width->amount #(/ (* % total-amount) total-width)
gesture (get-gesture
[detecting-gesture? slider-width-shared-value max-amount
slider-height-shared-value
amount-shared-value amount-on-gesture-start width->amount
slider-opacity-shared-value on-new-amount set-detecting-gesture])
on-press (rn/use-callback
(fn []
(when (and (not detecting-gesture?) allow-press?)
(on-press bar bar-idx bar-width bar-max-width)
(set-detecting-gesture true)
(animation/show-slider slider-opacity-shared-value)))
[detecting-gesture? allow-press? on-press])]
[rn/pressable
{:on-press on-press}
[reanimated/view
{:style (style/network-bar props network-bar-shared-value)
:accessibility-label :network-routing-bar}
[gesture/gesture-detector {:gesture gesture}
[rn/view {:style style/slider-container}
[reanimated/view
{:style (style/slider {:width-shared-value slider-width-shared-value
:height-shared-value slider-height-shared-value
:opacity-shared-value slider-opacity-shared-value})}]]]]]))
(defn f-network-bar
[_]
(let [detecting-gesture? (reagent/atom false)
amount-on-gesture-start (atom 0)]
(fn [{:keys [total-width total-amount on-press on-new-amount allow-press?]
{:keys [amount-shared-value
max-amount]} :bar
:as props}]
(let [slider-width-shared-value (reanimated/use-shared-value 4)
slider-height-shared-value (reanimated/use-shared-value 32)
slider-opacity-shared-value (reanimated/use-shared-value 0)
network-bar-shared-value (reanimated/interpolate amount-shared-value
[0 total-amount]
[0 total-width])
width->amount #(/ (* % total-amount) total-width)]
[rn/pressable
{:on-press (fn []
(when (and (not @detecting-gesture?) allow-press?)
(on-press)
(reset! detecting-gesture? true)
(animation/show-slider slider-opacity-shared-value)))}
[reanimated/view
{:style (style/network-bar props network-bar-shared-value)
:accessibility-label :network-routing-bar}
[gesture/gesture-detector
{:gesture
(-> (gesture/gesture-pan)
(gesture/enabled @detecting-gesture?)
(gesture/on-begin
(fn [_]
(animation/increase-slider slider-width-shared-value slider-height-shared-value)
(reset! amount-on-gesture-start (reanimated/get-shared-value amount-shared-value))))
(gesture/on-update
(fn [event]
(let [new-amount (-> (oops/oget event "translationX")
(width->amount)
(+ @amount-on-gesture-start)
(utils.number/value-in-range 1 max-amount))]
(reanimated/set-shared-value amount-shared-value new-amount))))
(gesture/on-finalize
(fn [_]
(animation/decrease-slider slider-width-shared-value slider-height-shared-value)
(animation/hide-slider slider-opacity-shared-value)
(on-new-amount (reanimated/get-shared-value amount-shared-value))
(add-new-timeout :turn-off-gesture #(reset! detecting-gesture? false) 20))))}
[:f> f-slider
{:width-shared-value slider-width-shared-value
:height-shared-value slider-height-shared-value
:opacity-shared-value slider-opacity-shared-value}]]]]))))
(defn- add-bar-shared-values
[{:keys [amount] :as network}]
@@ -105,113 +93,101 @@
(interleave (repeat [rn/view {:style (style/dashed-line-line network-name)}])
(repeat [rn/view {:style style/dashed-line-space}])))))
(defn- network-routing-bars
[{:keys [networks total-width total-amount requesting-data? on-amount-selected]}]
(let [[selected-network-idx
set-selected-network-idx] (rn/use-state nil)
[press-locked?
set-press-locked] (rn/use-state false)
lock-press (rn/use-callback #(set-press-locked true))
unlock-press (rn/use-callback #(set-press-locked false))
reset-state-values (rn/use-callback #(set-selected-network-idx nil))
bar-opacity-shared-value (reanimated/use-shared-value 0)
network-bars (map add-bar-shared-values networks)
amount->width #(* % (/ total-width total-amount))
bars-widths-negative (map #(-> % get-negative-amount amount->width)
network-bars)
last-bar-idx (dec (count network-bars))
network-bar-on-press (rn/use-callback
(fn [bar bar-idx bar-width bar-max-width]
(when-not selected-network-idx
(let [[previous-bars
[_ & next-bars]] (split-at bar-idx network-bars)]
(animation/move-previous-bars
{:bars previous-bars
:bars-widths-negative bars-widths-negative})
(animation/move-pressed-bar
{:bar bar
:bars-widths-negative bars-widths-negative
:number-previous-bars bar-idx})
(animation/move-next-bars
{:bars next-bars
:bars-widths-negative bars-widths-negative
:number-previous-bars (inc bar-idx)
:extra-offset (max 0 (- bar-max-width bar-width))
:add-new-timeout add-new-timeout}))
(animation/show-max-limit-bar bar-opacity-shared-value)
(set-selected-network-idx bar-idx)))
[selected-network-idx bars-widths-negative network-bars])
on-new-amount (rn/use-callback
(fn [new-amount]
(animation/hide-max-limit-bar bar-opacity-shared-value)
(when on-amount-selected
(on-amount-selected new-amount selected-network-idx)))
[on-amount-selected selected-network-idx])]
(rn/use-effect
#(when (and (not requesting-data?) selected-network-idx)
(let [bar (nth network-bars selected-network-idx)]
(animation/hide-pressed-bar bar amount->width))
(animation/update-bar-values-and-reset-animations
{:new-network-values networks
:network-bars network-bars
:amount->width amount->width
:reset-values-fn reset-state-values
:lock-press-fn lock-press
:unlock-press-fn unlock-press
:add-new-timeout add-new-timeout}))
[requesting-data?])
[:<>
(doall
(for [[bar-idx bar] (map-indexed vector network-bars)
:let [bar-max-width (amount->width (:max-amount bar))
bar-width (-> (:amount-shared-value bar)
(reanimated/get-shared-value)
(amount->width))
hide-division? (or (= last-bar-idx bar-idx) selected-network-idx)
this-bar-selected? (= selected-network-idx bar-idx)]]
^{:key (str "network-bar-" bar-idx)}
[network-bar
{:bar bar
:bar-idx bar-idx
:bar-width bar-width
:bar-max-width bar-max-width
:total-width total-width
:total-amount total-amount
:bar-division? hide-division?
:on-top? this-bar-selected?
:allow-press? (and (or (not selected-network-idx) this-bar-selected?)
(not requesting-data?)
(not press-locked?))
:on-press network-bar-on-press
:on-new-amount on-new-amount}]))
(defn f-network-routing-bars
[_]
(let [selected-network-idx (reagent/atom nil)
press-locked? (reagent/atom false)
lock-press #(reset! press-locked? true)
unlock-press #(reset! press-locked? false)
reset-state-values #(reset! selected-network-idx nil)]
(fn [{:keys [networks total-width total-amount requesting-data? on-amount-selected]}]
(let [bar-opacity-shared-value (reanimated/use-shared-value 0)
network-bars (map add-bar-shared-values networks)
amount->width #(* % (/ total-width total-amount))
bars-widths-negative (map #(-> % get-negative-amount amount->width)
network-bars)
last-bar-idx (dec (count network-bars))]
(rn/use-effect
#(when (and (not requesting-data?) @selected-network-idx)
(let [bar (nth network-bars @selected-network-idx)]
(animation/hide-pressed-bar bar amount->width))
(animation/update-bar-values-and-reset-animations
{:new-network-values networks
:network-bars network-bars
:amount->width amount->width
:reset-values-fn reset-state-values
:lock-press-fn lock-press
:unlock-press-fn unlock-press
:add-new-timeout add-new-timeout}))
[requesting-data?])
[:<>
(doall
(for [[bar-idx bar] (map-indexed vector network-bars)
:let [bar-max-width (amount->width (:max-amount bar))
bar-width (-> (:amount-shared-value bar)
(reanimated/get-shared-value)
(amount->width))
hide-division? (or (= last-bar-idx bar-idx) @selected-network-idx)
this-bar-selected? (= @selected-network-idx bar-idx)]]
^{:key (str "network-bar-" bar-idx)}
[:f> f-network-bar
{:bar bar
:max-width bar-max-width
:total-width total-width
:total-amount total-amount
:bar-division? hide-division?
:on-top? this-bar-selected?
:allow-press? (and (or (not @selected-network-idx) this-bar-selected?)
(not requesting-data?)
(not @press-locked?))
:on-press (fn []
(when-not @selected-network-idx
(let [[previous-bars [_ & next-bars]] (split-at bar-idx network-bars)
number-previous-bars bar-idx]
(animation/move-previous-bars
{:bars previous-bars
:bars-widths-negative bars-widths-negative})
(animation/move-pressed-bar
{:bar bar
:bars-widths-negative bars-widths-negative
:number-previous-bars number-previous-bars})
(animation/move-next-bars
{:bars next-bars
:bars-widths-negative bars-widths-negative
:number-previous-bars (inc number-previous-bars)
:extra-offset (max 0 (- bar-max-width bar-width))
:add-new-timeout add-new-timeout}))
(animation/show-max-limit-bar bar-opacity-shared-value)
(reset! selected-network-idx bar-idx)))
:on-new-amount (fn [new-amount]
(animation/hide-max-limit-bar bar-opacity-shared-value)
(when on-amount-selected
(on-amount-selected new-amount @selected-network-idx)))}]))
(let [{:keys [max-amount network-name]} (some->> selected-network-idx
(nth network-bars))
limit-bar-width (amount->width max-amount)]
[reanimated/view
{:style (style/max-limit-bar
{:opacity-shared-value bar-opacity-shared-value
:width limit-bar-width})}
[rn/view {:style (style/max-limit-bar-background network-name)}]
[dashed-line network-name]])]))
(let [{:keys [max-amount network-name]} (some->> @selected-network-idx
(nth network-bars))
limit-bar-width (amount->width max-amount)]
[reanimated/view
{:style (style/max-limit-bar
{:opacity-shared-value bar-opacity-shared-value
:width limit-bar-width})}
[rn/view {:style (style/max-limit-bar-background network-name)}]
[dashed-line network-name]])]))))
(defn view-internal
[{:keys [networks container-style] :as params}]
(let [theme (quo.theme/use-theme-value)
[total-width
set-total-width] (rn/use-state nil)
on-layout (rn/use-callback #(let [width (oops/oget % "nativeEvent.layout.width")]
(when (not= width total-width)
(set-total-width width))))]
(rn/use-unmount (fn []
(doseq [[_ living-timeout] @timeouts]
(js/clearTimeout living-timeout))))
[{:keys [networks container-style theme] :as params}]
(reagent/with-let [total-width (reagent/atom nil)]
[rn/view
{:accessibility-label :network-routing
:style (style/container container-style theme)
:on-layout on-layout}
(when total-width
:on-layout #(reset! total-width (oops/oget % "nativeEvent.layout.width"))}
(when @total-width
^{:key (str "network-routing-" (count networks))}
[network-routing-bars (assoc params :total-width total-width)])]))
[:f> f-network-routing-bars (assoc params :total-width @total-width)])]
(finally
(doseq [[_ living-timeout] @timeouts]
(js/clearTimeout living-timeout)))))
(def view (schema/instrument #'view-internal network-routing-schema/?schema))
(def view
(quo.theme/with-theme
(schema/instrument #'view-internal network-routing-schema/?schema)))
@@ -14,5 +14,6 @@
[:networks {:optional true}
[:maybe [:sequential [:map [:source [:maybe :schema.common/image-source]]]]]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
[:value {:optional true} [:maybe :string]]]]]
[:value {:optional true} [:maybe :string]]
[:theme :schema.common/theme]]]]
:any])

Some files were not shown because too many files have changed in this diff Show More