Add web3.keycard.signTypedData

Signed-off-by: Vitaliy Vlasov <siphiuel@gmail.com>
This commit is contained in:
Vitaliy Vlasov 2019-11-04 11:51:56 +02:00
parent 1b43e4c16b
commit e11385e350
No known key found for this signature in database
GPG Key ID: A7D57C347F2B2964
26 changed files with 369 additions and 44 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 940 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -218,6 +218,7 @@ var TopLevel = {
"injectJavaScript" : function () {},
"installApplet" : function () {},
"installAppletAndInitCard" : function () {},
"installCashApplet" : function () {},
"Int8Array" : function () {},
"integer" : function () {},
"interpolate" : function () {},
@ -491,6 +492,7 @@ var TopLevel = {
"sign" : function () {},
"signGroupMembership" : function () {},
"signMessage" : function () {},
"signPinless" : function () {},
"signTypedData" : function () {},
"slice" : function () {},
"SplashScreen" : function () {},

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "nfc.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "nfc-1.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "nfc-2.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 940 B

View File

@ -325,9 +325,12 @@
(when (or (not message?) (and address data))
(signing/sign cofx (merge
(if message?
{:message {:address address :data data :typed? (not= constants/web3-personal-sign method)
:from dapps-address}}
{:tx-obj (update (first params) :from #(or % dapps-address))})
{:message {:address address
:data data
:typed? (not= constants/web3-personal-sign method)
:pinless? (= method constants/web3-keycard-sign-typed-data)
:from dapps-address}}
{:tx-obj (update (first params) :from #(or % dapps-address))})
{:on-result [:browser.dapp/transaction-on-result message-id id]
:on-error [:browser.dapp/transaction-on-error message-id]}))))
(if (#{"eth_accounts" "eth_coinbase"} method)
@ -347,6 +350,7 @@
[{:keys [db] :as cofx} dapp-name {:keys [method] :as payload} message-id]
(let [{:dapps/keys [permissions]} db]
(if (and (#{"eth_accounts" "eth_coinbase" "eth_sendTransaction" "eth_sign"
"keycard_signTypedData"
"eth_signTypedData" "personal_sign" "personal_ecRecover"} method)
(not (some #{constants/dapp-permission-web3} (get-in permissions [dapp-name :permissions]))))
(send-to-bridge cofx

View File

@ -225,8 +225,12 @@
(def ^:const web3-shh-get-filter-changes "shh_getFilterChanges")
(def ^:const web3-shh-get-messages "shh_getMessages")
;; Keycard ns
(def ^:const web3-keycard-sign-typed-data "keycard_signTypedData")
(defn web3-sign-message? [method]
(#{web3-sign-typed-data web3-sign-typed-data-v3 web3-personal-sign} method))
(#{web3-sign-typed-data web3-sign-typed-data-v3 web3-personal-sign web3-keycard-sign-typed-data} method))
(def ^:const status-create-address "status_createaddress")

View File

@ -423,3 +423,40 @@
(re-frame/dispatch
[:hardwallet.callback/on-sign-error
(error-object->map response)])))})))
(defn install-cash-applet []
(log/info "[keycard] install-cash-applet")
(keycard/install-cash-applet
card
{:on-success
(fn [response]
(log/info "[keycard response succ] install-cash-applet"
(js->clj response :keywordize-keys true))
(re-frame/dispatch
[:hardwallet.callback/on-install-applet-success response]))
:on-failure
(fn [response]
(log/info "[keycard response fail] install-cash-applet"
(error-object->map response))
(re-frame/dispatch
[:hardwallet.callback/on-install-applet-error
(error-object->map response)]))}))
(defn sign-typed-data
[{:keys [hash] :as args}]
(log/info "[keycard] sign-typed-data" args)
(keycard/sign-typed-data
card
{:hash hash
:on-success
(fn [response]
(log/info "[keycard response succ] sign-typed-data" (js->clj response :keywordize-keys true))
(re-frame/dispatch [:hardwallet.callback/on-sign-success
response]))
:on-failure
(fn [response]
(log/info "[keycard response fail] sign-typed-data"
(error-object->map response))
(re-frame/dispatch
[:hardwallet.callback/on-sign-error
(error-object->map response)]))}))

View File

@ -411,7 +411,7 @@
[{:keys [db] :as cofx} error]
(log/debug "[hardwallet] application info error " error)
(let [on-card-read (get-in db [:hardwallet :on-card-read])
on-card-connected (get-in db [:hardwallet :on-card-conncted])
on-card-connected (get-in db [:hardwallet :on-card-connected])
login? (= on-card-read :hardwallet/login-with-keycard)
tag-was-lost? (tag-lost? (:error error))]
(when-not tag-was-lost?

View File

@ -94,6 +94,10 @@
:hardwallet/sign
card/sign)
(re-frame/reg-fx
:hardwallet/sign-typed-data
card/sign-typed-data)
(re-frame/reg-fx
:hardwallet/login-with-keycard
status/login-with-keycard)

View File

@ -11,6 +11,7 @@
(remove-event-listeners [this])
(get-application-info [this args])
(install-applet [this args])
(install-cash-applet [this args])
(init-card [this args])
(install-applet-and-init-card [this args])
(pair [this args])
@ -26,4 +27,5 @@
(export-key [this args])
(unpair-and-delete [this args])
(get-keys [this args])
(sign [this args]))
(sign [this args])
(sign-typed-data [this args]))

View File

@ -59,6 +59,12 @@
(then on-success)
(catch on-failure)))
(defn install-cash-applet [{:keys [on-success on-failure]}]
(.. status-keycard
installCashApplet
(then on-success)
(catch on-failure)))
(defn init-card [{:keys [pin on-success on-failure]}]
(.. status-keycard
(init pin)
@ -178,6 +184,14 @@
(then on-success)
(catch on-failure))))
(defn sign-typed-data
[{:keys [hash on-success on-failure]}]
(when hash
(.. status-keycard
(signPinless hash)
(then on-success)
(catch on-failure))))
(defrecord RealKeycard []
keycard/Keycard
(keycard/check-nfc-support [this args]
@ -200,6 +214,8 @@
(get-application-info args))
(keycard/install-applet [this args]
(install-applet args))
(keycard/install-cash-applet [this args]
(install-cash-applet args))
(keycard/init-card [this args]
(init-card args))
(keycard/install-applet-and-init-card [this args]
@ -231,4 +247,6 @@
(keycard/get-keys [this args]
(get-keys args))
(keycard/sign [this args]
(sign args)))
(sign args))
(keycard/sign-typed-data [this args]
(sign-typed-data args)))

View File

@ -2,7 +2,10 @@
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.hardwallet.card :as card]
[status-im.utils.fx :as fx]
[status-im.utils.money :as money]
[status-im.utils.types :as types]
[taoensso.timbre :as log]
[status-im.hardwallet.common :as common]))
@ -74,6 +77,63 @@
(common/get-application-info (common/get-pairing db) nil)
(common/hide-connection-sheet)))
(def sign-typed-data-listener (atom nil))
(fx/defn sign-typed-data
{:events [:hardwallet/sign-typed-data]}
[{:keys [db] :as cofx}]
(let [card-connected? (get-in db [:hardwallet :card-connected?])
hash (get-in db [:hardwallet :hash])]
(if card-connected?
(do
(when @sign-typed-data-listener
(card/remove-event-listener @sign-typed-data-listener))
{:db (-> db
(assoc-in [:hardwallet :card-read-in-progress?] true)
(assoc-in [:signing/sign :keycard-step] :signing))
:hardwallet/sign-typed-data {:hash (ethereum/naked-address hash)}})
(do
(reset! sign-typed-data-listener
(card/on-card-connected #(re-frame/dispatch [:hardwallet/sign-typed-data])))
(fx/merge cofx
(common/set-on-card-connected :hardwallet/sign-typed-data)
{:db (assoc-in db [:signing/sign :keycard-step] :signing)})))))
(fx/defn fetch-currency-symbol-on-success
{:events [:hardwallet/fetch-currency-symbol-on-success]}
[{:keys [db] :as cofx} currency]
{:db (assoc-in db [:signing/sign :formatted-data :message :formatted-currency] currency)})
(fx/defn fetch-currency-decimals-on-success
{:events [:hardwallet/fetch-currency-decimals-on-success]}
[{:keys [db] :as cofx} decimals]
{:db (update-in db [:signing/sign :formatted-data :message]
#(assoc % :formatted-amount (.dividedBy (money/bignumber (:amount %))
(money/bignumber (money/from-decimal decimals)))))})
(fx/defn store-hash-and-sign-typed
{:events [:hardwallet/store-hash-and-sign-typed]}
[{:keys [db] :as cofx} result]
(let [{:keys [result error]} (types/json->clj result)
message (get-in db [:signing/sign :formatted-data :message])
currency-contract (:currency message)]
(when-not (or (:receiver message) (:code message))
(json-rpc/eth-call {:contract currency-contract
:method "decimals()"
:outputs ["uint8"]
:on-success (fn [[decimals]]
(re-frame/dispatch [:hardwallet/fetch-currency-decimals-on-success decimals]))})
(json-rpc/eth-call {:contract currency-contract
:method "symbol()"
:outputs ["string"]
:on-success (fn [[currency]]
(re-frame/dispatch [:hardwallet/fetch-currency-symbol-on-success currency]))}))
(fx/merge cofx
{:db (assoc-in db [:hardwallet :hash] result)}
sign-typed-data)))
(fx/defn prepare-to-sign
{:events [:hardwallet/prepare-to-sign]}
[{:keys [db] :as cofx}]

View File

@ -60,6 +60,7 @@
(later #(on-success (get @state :application-info))))
(defn install-applet [args])
(defn install-cash-applet [args])
(def kk1-password "6d9ZHjn94kFP4bPm")
@ -121,6 +122,7 @@
(defn unpair-and-delete [args])
(defn get-keys [args])
(defn sign [args])
(defn sign-typed-data [args])
(defrecord SimulatedKeycard []
keycard/Keycard

View File

@ -10,6 +10,7 @@
[status-im.ethereum.eip55 :as eip55]
[status-im.ethereum.tokens :as tokens]
[status-im.i18n :as i18n]
[status-im.signing.keycard :as signing.keycard]
[status-im.native-module.core :as status]
[status-im.utils.fx :as fx]
[status-im.hardwallet.common :as hardwallet.common]
@ -179,17 +180,26 @@
(fx/defn show-sign [{:keys [db] :as cofx}]
(let [{:signing/keys [queue]} db
{{:keys [gas gasPrice] :as tx-obj} :tx-obj {:keys [data typed?] :as message} :message :as tx} (last queue)
{{:keys [gas gasPrice] :as tx-obj} :tx-obj {:keys [data typed? pinless?] :as message} :message :as tx} (last queue)
keycard-multiaccount? (boolean (get-in db [:multiaccount :keycard-pairing]))
wallet-set-up-passed? (get-in db [:multiaccount :wallet-set-up-passed?])
updated-db (if wallet-set-up-passed? db (assoc db :popover/popover {:view :signing-phrase}))]
(if message
{:db (assoc updated-db
:signing/in-progress? true
:signing/queue (drop-last queue)
:signing/tx tx
:signing/sign {:type (if keycard-multiaccount? :keycard :password)
:formatted-data (if typed? (types/json->clj data) (ethereum/hex-to-utf8 data))})}
(fx/merge
cofx
{:db (assoc updated-db
:signing/in-progress? true
:signing/queue (drop-last queue)
:signing/tx tx
:signing/sign {:type (cond pinless? :pinless
keycard-multiaccount? :keycard
:else :password)
:formatted-data (if typed? (types/json->clj data) (ethereum/hex-to-utf8 data))
:keycard-step (when pinless? :connect)})}
(when pinless?
(signing.keycard/hash-message {:data data
:typed? true
:on-completed #(re-frame/dispatch [:hardwallet/store-hash-and-sign-typed %])})))
(fx/merge
cofx
{:db (assoc updated-db
@ -295,18 +305,28 @@
(when on-error
{:dispatch (conj on-error message)})))))
(fx/defn dissoc-signing-db-entries-and-check-queue
{:events [:signing/dissoc-entries-and-check-queue]}
[{:keys [db] :as cofx}]
(fx/merge cofx
{:db (dissoc db :signing/tx :signing/in-progress? :signing/sign)}
check-queue))
(fx/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)}
{:db (-> db
(assoc-in [:signing/sign :error] (if (= 5 (:code error)) (i18n/label :t/wrong-password) (:message error)))
(assoc :signing/in-progress? false))}
(fx/merge cofx
{:db (dissoc db :signing/tx :signing/in-progress? :signing/sign)}
(check-queue)
(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)})))))
@ -314,7 +334,7 @@
{:events [:signing/transaction-completed]
:interceptors [(re-frame/inject-cofx :random-id-generator)]}
[cofx response tx-obj hashed-password]
(let [cofx-in-progress-false (assoc-in cofx [:db :signing/sign :in-progress?] false)
(let [cofx-in-progress-false (assoc-in cofx [:db :signing/in-progress?] false)
{:keys [result error]} (types/json->clj response)]
(log/debug "transaction-completed" error tx-obj)
(if error

View File

@ -79,11 +79,12 @@
{:events [:signing.ui/sign-with-keycard-pressed]}
[{:keys [db] :as cofx}]
(let [message (get-in db [:signing/tx :message])]
(fx/merge cofx
{:db (-> db
(assoc-in [:hardwallet :pin :enter-step] :sign)
(assoc-in [:signing/sign :keycard-step] :pin)
(assoc-in [:signing/sign :type] :keycard))}
(if message
(hash-message message)
(hash-transaction)))))
(fx/merge
cofx
{:db (-> db
(assoc-in [:hardwallet :pin :enter-step] :sign)
(assoc-in [:signing/sign :type] :keycard)
(assoc-in [:signing/sign :keycard-step] :pin))}
(if message
(hash-message message nil)
(hash-transaction)))))

View File

@ -177,7 +177,6 @@
;;signing
(reg-root-key-sub :signing/tx :signing/tx)
(reg-root-key-sub :signing/sign :signing/sign)
(reg-root-key-sub :signing/edit-fee :signing/edit-fee)
;;intro-wizard
@ -2069,6 +2068,21 @@
(fn [{:keys [signing-phrase]}]
signing-phrase))
(re-frame/reg-sub
:signing/sign
(fn [db]
(let [sign (:signing/sign db)]
(if (= :pinless (:type sign))
(let [message (get-in sign [:formatted-data :message])]
(if (and (:amount message) (:currency message))
(assoc sign :fiat-amount
(money/fiat-amount-value (:amount message)
(:currency message)
:USD (:prices db))
:fiat-currency "USD")
sign))
sign))))
(defn- too-precise-amount?
"Checks if number has any extra digit beyond the allowed number of decimals.
It does so by checking the number against its rounded value."

View File

@ -10,8 +10,9 @@
[status-im.ui.screens.profile.user.views :as profile.user]
[status-im.ui.screens.multiaccounts.recover.views :as multiaccounts.recover]
[status-im.react-native.js-dependencies :as js-dependencies]
[status-im.ui.screens.biometric.views :as biometric]
[status-im.ui.components.colors :as colors]))
[status-im.ui.components.colors :as colors]
[status-im.ui.screens.signing.views :as signing]
[status-im.ui.screens.biometric.views :as biometric]))
(defn hide-panel-anim
[bottom-anim-value alpha-value window-height]
@ -133,6 +134,9 @@
(= :disable-password-saving view)
[biometric/disable-password-saving-popover]
(= :transaction-data view)
[signing/transaction-data]
:else
[view])]]]]])))})))

View File

@ -58,3 +58,22 @@
:color (if disabled? colors/black colors/white)
:padding-horizontal 16
:padding-vertical 10})
(defn sheet-title [small-screen?]
{:font-weight "500"
:font-size (if small-screen? 16 19)
:margin-top 16})
(defn sheet-subtitle [small-screen?]
{:font-size (if small-screen? 16 19)
:text-align :center
:margin-bottom 12
:color colors/gray})
(defn sheet-icon [bg-color]
{:height 64
:width 64
:border-radius 32
:justify-content :center
:align-items :center
:background-color bg-color})

View File

@ -133,6 +133,105 @@
[react/view {:align-items :center :margin-top 16 :margin-bottom 40}
[sign-with-keycard-button nil nil]])])
(defn signature-request [{:keys [error formatted-data
fiat-amount fiat-currency
keycard-step
in-progress? enabled?] :as sign} small-screen?]
(let [message (:message formatted-data)
title (case keycard-step
:connect :t/looking-for-cards
:signing :t/processing
:error :t/lost-connection
:success :t/success)
subtitle (case keycard-step
:connect :t/hold-card
:signing :t/try-keeping-the-card-still
:error :t/tap-card-again
:success :t/transaction-signed)]
[react/view (assoc styles/message :padding-vertical 16 :align-items :center)
[react/view {:style {:align-self :flex-start :padding-left 16 :margin-bottom 24}}
[react/text {:style {:font-size (if small-screen? 15 17) :font-weight "700"}}
(i18n/label :t/confirmation-request)]]
(when (and (:formatted-amount message) (:formatted-currency message))
[react/view {:style {:margin-bottom 24 :align-self :stretch}}
[react/nested-text {:style {:font-weight "500" :font-size (if small-screen? 34 44)
:text-align :center}}
(str (:formatted-amount message) " ")
[{:style {:color colors/gray}} (:formatted-currency message)]]
[react/text {:style {:font-size 19 :text-align :center
:margin-bottom 16}}
(str fiat-amount " " fiat-currency)]
[separator]])
[react/view {:style (styles/sheet-icon (case keycard-step
(:connect :signing) colors/blue-transparent-10
:error colors/red-transparent-10
:success colors/green-transparent-10))}
(case keycard-step
:connect
[icons/icon :main-icons/nfc {:color colors/blue :width 27 :height 21}]
:signing
[react/activity-indicator {:animating true :color colors/blue}]
:error
[icons/icon :main-icons/close {:color colors/red}]
:success
[icons/icon :main-icons/check {:color colors/green}])]
[react/text {:style (styles/sheet-title small-screen?)} (i18n/label title)]
[react/text {:style (styles/sheet-subtitle small-screen?)} (i18n/label subtitle)]
[button/button {:type :main
:disabled? (= keycard-step :success)
:text-style {:font-size (if small-screen? 18 20)}
:style {:align-self :stretch}
:container-style {:height (if small-screen? 52 64)}
:label (i18n/label :t/show-transaction-data)
:on-press #(re-frame/dispatch [:show-popover {:view :transaction-data}])}]
[button/button {:type :main
:theme :red
:disabled? (= keycard-step :success)
:container-style {:margin-top 8
:height 64 :margin-bottom 16}
:style {:align-self :stretch}
:text-style {:font-size 20}
:label (i18n/label :t/decline)
:on-press #(re-frame/dispatch [:signing.ui/cancel-is-pressed])}]]))
(defn- transaction-data-item [{:keys [label data]}]
[react/view
[react/text {:style {:font-size 17
:line-height 20
:margin-bottom 8
:color colors/gray}}
label]
[react/text {:style {:font-size 17
:line-height 20
:margin-bottom 24}}
data]])
(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 {:label "Label"
:data formatted-data}]]
[separator]
[react/view {:style {:margin-horizontal 8
:margin-vertical 16}}
[button/button {:type :main
:text-style {:font-size 20}
:style {:margin-horizontal 0}
:container-style {:height 64}
:label (i18n/label :t/close)
:on-press #(re-frame/dispatch [:hide-popover])}]]]))
(views/defview password-view [{:keys [type error in-progress? enabled?] :as sign}]
(views/letsubs [phrase [:signing/phrase]]
(case type
@ -160,19 +259,22 @@
[react/view])))
(views/defview message-sheet []
(views/letsubs [{:keys [formatted-data type] :as sign} [:signing/sign]]
[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]]]))
(views/letsubs [{:keys [formatted-data type] :as sign} [:signing/sign]
small-screen? [:dimensions/small-screen?]]
(if (= type :pinless)
[signature-request sign small-screen?]
[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 amount-item [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)) :price]))]

View File

@ -135,6 +135,7 @@
"clear-history-confirmation": "Clear history?",
"clear-history-confirmation-content": "Are you sure you want to clear this chat history?",
"clear-history-title": "Clear history?",
"close": "Close",
"close-app-button": "Confirm",
"close-app-content": "The app will stop and close. When you reopen it, the selected network will be used",
"close-app-title": "Warning!",
@ -142,6 +143,7 @@
"complete-hardwallet-setup": "This card is now an essential part your multiaccount security. Transactions can't be sent without it.",
"completed": "Completed",
"confirm": "Confirm",
"confirmation-request": "Confirmation request",
"confirmations": "Confirmations",
"confirmations-helper-text": "When the transaction has 12 confirmations you can consider it settled.",
"connect": "Connect",
@ -593,6 +595,8 @@
"logout-app-content": "The account will be logged out. When you unlock it again, the selected network will be used",
"logout-are-you-sure": "Are you sure you want\nto log out?",
"logout-title": "Log out?",
"looking-for-cards": "Looking for cards...",
"lost-connection": "Lost connection",
"mailserver-address": "Mailserver address",
"mailserver-automatic": "Automatic selection",
"mailserver-connection-error": "Could not connect to mailserver",
@ -875,6 +879,7 @@
"show-less": "Show less",
"show-more": "Show more",
"show-qr": "Show QR code",
"show-transaction-data": "Show transaction data",
"sign-and-send": "Sign and send",
"sign-in": "Unlock",
"sign-message": "Sign Message",
@ -916,6 +921,7 @@
"sync-synced": "In sync",
"syncing-devices": "Syncing...",
"tag-was-lost": "Tag was lost",
"tap-card-again": "Tap the card to the back of your phone again",
"test-networks": "Test networks",
"text-input-disabled": "Please wait a moment...",
"this-device": "This device",
@ -934,6 +940,7 @@
"token-details": "Token details",
"topic-name-error": "Use only lowercase letters (a to z), numbers & dashes (-). Do not use chat keys",
"transaction": "Transaction",
"transaction-data": "Transaction data",
"transaction-declined": "Transaction declined",
"transaction-description": "Consider it complete after 12 confirmations on the network.",
"transaction-details": "Transaction details",
@ -941,6 +948,7 @@
"transaction-history": "Transaction history",
"transaction-request": "Transaction Request",
"transaction-sent": "Transaction sent",
"transaction-signed": "The transaction has been successfully signed",
"transactions": "Transactions",
"transactions-filter-select-all": "Select all",
"transactions-filter-title": "Filter history",
@ -978,6 +986,7 @@
"tribute-to-talk-tribute-received2": " are now contacts and can securely chat with each other.",
"tribute-to-talk-you-require-snt": "You require SNT for new people to start a chat.",
"try-again": "Try again",
"try-keeping-the-card-still": "Try keeping the card still",
"turn-nfc-on": "Turn NFC on to continue",
"turn-nfc-description": "NFC is disabled on yor device. You can enable it in settings",
"keycard-init-title": "Looking for cards...",