wallet favourites

This commit is contained in:
andrey 2020-09-11 10:14:15 +02:00
parent 6454df4734
commit ff790d4152
No known key found for this signature in database
GPG Key ID: 89B67245FD2F0272
19 changed files with 763 additions and 286 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -115,6 +115,8 @@
"wallet_getTransfersByAddress" {}
"wallet_getCustomTokens" {}
"wallet_addCustomToken" {}
"wallet_addFavourite" {}
"wallet_getFavourites" {}
"wallet_deleteCustomToken" {}
"browsers_getBrowsers" {}
"browsers_addBrowser" {}

View File

@ -55,12 +55,13 @@
(fx/defn initialize-wallet
{:events [::initialize-wallet]}
[{:keys [db] :as cofx} accounts custom-tokens]
[{:keys [db] :as cofx} accounts custom-tokens favourites]
(fx/merge
cofx
{:db (assoc db :multiaccount/accounts
(rpc->accounts accounts))}
(wallet/initialize-tokens custom-tokens)
(wallet/initialize-favourites favourites)
(wallet/update-balances nil)
(prices/update-prices)))
@ -154,11 +155,17 @@
(js/Promise.
(fn [resolve reject]
(json-rpc/call {:method "wallet_getCustomTokens"
:on-success resolve
:on-error reject})))
(js/Promise.
(fn [resolve reject]
(json-rpc/call {:method "wallet_getFavourites"
:on-success resolve
:on-error reject})))]))
(.then (fn [[accounts custom-tokens]]
(.then (fn [[accounts custom-tokens favourites]]
(callback accounts
(mapv #(update % :symbol keyword) custom-tokens))))
(mapv #(update % :symbol keyword) custom-tokens)
favourites)))
(.catch (fn [_]
(log/error "Failed to initialize wallet"))))))
@ -186,9 +193,9 @@
:networks/networks networks
:multiaccount multiaccount))
::initialize-wallet
(fn [accounts custom-tokens]
(fn [accounts custom-tokens favourites]
(re-frame/dispatch [::initialize-wallet
accounts custom-tokens]))}
accounts custom-tokens favourites]))}
notifications-enabled?
(assoc ::notifications/enable nil))
(acquisition/login)
@ -269,7 +276,7 @@
:default-mailserver true})
(multiaccounts/switch-preview-privacy-mode-flag)
(logging/set-log-level (:log-level multiaccount))
(initialize-wallet accounts nil))))
(initialize-wallet accounts nil nil))))
(defn- keycard-setup? [cofx]
(boolean (get-in cofx [:db :keycard :flow])))

View File

@ -15,3 +15,8 @@
{:events [:search/token-filter-changed]}
[cofx search-filter]
{:db (assoc-in (:db cofx) [:ui/search :token-filter] search-filter)})
(fx/defn recipient-filter-changed
{:events [:search/recipient-filter-changed]}
[cofx search-filter]
{:db (assoc-in (:db cofx) [:ui/search :recipient-filter] search-filter)})

View File

@ -163,6 +163,8 @@
(reg-root-key-sub :wallet/prepare-transaction :wallet/prepare-transaction)
(reg-root-key-sub :wallet-service/manual-setting :wallet-service/manual-setting)
(reg-root-key-sub :wallet-service/state :wallet-service/state)
(reg-root-key-sub :wallet/recipient :wallet/recipient)
(reg-root-key-sub :wallet/favourites :wallet/favourites)
;;commands
(reg-root-key-sub :commands/select-account :commands/select-account)
@ -367,6 +369,12 @@
(fn [search]
(get search :home-filter)))
(re-frame/reg-sub
:search/recipient-filter
:<- [:ui/search]
(fn [search]
(get search :recipient-filter)))
(re-frame/reg-sub
:search/currency-filter
:<- [:ui/search]
@ -531,6 +539,23 @@
(fn [accounts]
(filter #(not= (:type %) :watch) accounts)))
(defn filter-recipient-accounts
[search-filter {:keys [name]}]
(string/includes? (string/lower-case (str name)) search-filter))
(re-frame/reg-sub
:accounts-for-recipient
:<- [:multiaccount/accounts]
:<- [:wallet/prepare-transaction]
:<- [:search/recipient-filter]
(fn [[accounts {:keys [from]} search-filter]]
(let [accounts (remove #(= (:address %) (:address from)) accounts)]
(if (string/blank? search-filter)
accounts
(filter (partial filter-recipient-accounts
(string/lower-case search-filter))
accounts)))))
(re-frame/reg-sub
:add-account-disabled?
:<- [:multiaccount/accounts]
@ -1285,6 +1310,21 @@
(fn [currency-id]
(get constants/currencies currency-id)))
(defn filter-recipient-favs
[search-filter {:keys [name]}]
(string/includes? (string/lower-case (str name)) search-filter))
(re-frame/reg-sub
:wallet/favourites-filtered
:<- [:wallet/favourites]
:<- [:search/recipient-filter]
(fn [[favs search-filter]]
(let [favs (vals favs)]
(if (string/blank? search-filter)
favs
(filter (partial filter-recipient-favs
(string/lower-case search-filter))
favs)))))
;;WALLET TRANSACTIONS ==================================================================================================
(re-frame/reg-sub
@ -1434,6 +1474,17 @@
(keep #(enrich-transaction-for-list filters % address))
(group-transactions-by-date))}))
(re-frame/reg-sub
:wallet/recipient-recent-txs
(fn [[_ address] _]
[(re-frame/subscribe [:wallet.transactions/transactions address])])
(fn [[transactions] _]
(->> transactions
vals
(sort-by :timestamp >)
(remove #(= (:type %) :pending))
(take 3))))
(re-frame/reg-sub
:wallet.transactions.details/current-transaction
(fn [[_ _ address] _]
@ -1620,6 +1671,28 @@
(fn [blocked-contacts]
(count blocked-contacts)))
(defn filter-recipient-contacts
[search-filter {:keys [names]}]
(let [{:keys [nickname three-words-name ens-name]} names]
(or
(when ens-name
(string/includes? (string/lower-case (str ens-name)) search-filter))
(string/includes? (string/lower-case three-words-name) search-filter)
(when nickname
(string/includes? (string/lower-case nickname) search-filter)))))
(re-frame/reg-sub
:contacts/active-with-ens-names
:<- [:contacts/active]
:<- [:search/recipient-filter]
(fn [[contacts search-filter]]
(let [contacts (filter :ens-verified contacts)]
(if (string/blank? search-filter)
contacts
(filter (partial filter-recipient-contacts
(string/lower-case search-filter))
contacts)))))
(re-frame/reg-sub
:contacts/current-contact
:<- [:contacts/contacts]

View File

@ -43,6 +43,7 @@
is-ens? (and (not is-public-key?)
(ens/valid-eth-name-prefix? new-identity))
error (db/validate-pub-key db new-identity)]
(reset! resolve-last-id nil)
(merge {:db (assoc db
:contacts/new-identity
{:public-key new-identity

View File

@ -2,7 +2,7 @@
(:require-macros [status-im.utils.views :as views])
(:require [status-im.ui.screens.profile.tribute-to-talk.views :as tr-to-talk]
[status-im.ui.screens.add-new.new-public-chat.view :as new-public-chat]
[status-im.ui.screens.wallet.components.views :as wallet.components]
[status-im.ui.screens.wallet.recipient.views :as recipient]
[status-im.ui.screens.qr-scanner.views :as qr-scanner]
[status-im.ui.screens.stickers.views :as stickers]
[status-im.ui.screens.home.views :as home]
@ -24,7 +24,8 @@
[status-im.utils.config :as config]
[status-im.ui.screens.chat.image.preview.views :as image-preview]
[status-im.ui.screens.profile.contact.views :as contact]
[status-im.ui.screens.notifications-settings.views :as notifications-settings]))
[status-im.ui.screens.notifications-settings.views :as notifications-settings]
[status-im.ui.screens.wallet.send.views :as wallet]))
(defonce main-stack (navigation/create-stack))
(defonce bottom-tabs (navigation/create-bottom-tabs))
@ -103,8 +104,14 @@
:transition :presentation-ios
:insets {:bottom true}
:component group-chat/add-participants-toggle-list}
{:name :contact-code
:component wallet.components/contact-code}
{:name :recipient
:transition :presentation-ios
:insets {:bottom true}
:component recipient/recipient}
{:name :new-favourite
:transition :presentation-ios
:insets {:bottom true}
:component recipient/new-favourite}
{:name :qr-scanner
:insets {:top false :bottom false}
:component qr-scanner/qr-scanner}
@ -122,7 +129,15 @@
{:name :notifications-onboarding
:back-handler :noop
:insets {:bottom true}
:component notifications-settings/notifications-onboarding}]
:component notifications-settings/notifications-onboarding}
{:name :prepare-send-transaction
:transition :presentation-ios
:insets {:bottom true}
:component wallet/prepare-send-transaction}
{:name :request-transaction
:transition :presentation-ios
:insets {:bottom true}
:component wallet/request-transaction}]
(when config/quo-preview-enabled?
[{:name :quo-preview

View File

@ -104,8 +104,6 @@
{:enableURLHandling true
:initialState @state}))
[main-app-navigator]]
[wallet/prepare-transaction]
[wallet/request-transaction]
[wallet/select-account]
[signing/signing]
[bottom-sheet]

View File

@ -1,43 +1,6 @@
(ns status-im.ui.screens.wallet.components.views
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.i18n :as i18n]
[status-im.ui.components.react :as react]
[status-im.ui.components.topbar :as topbar]
[status-im.ui.screens.wallet.components.styles :as styles]
[quo.core :as quo]
[status-im.ui.components.colors :as colors]
[status-im.utils.debounce :as debounce])
(:require-macros [status-im.utils.views :as views]))
(:require [status-im.ui.components.react :as react]
[status-im.ui.screens.wallet.components.styles :as styles]))
(defn separator []
[react/view (styles/separator)])
(defn- recipient-topbar [content]
[topbar/topbar {:navigation {:label (i18n/label :t/cancel)
:on-press #(do
(re-frame/dispatch [:set-in [:wallet/prepare-transaction :modal-opened?] false])
(re-frame/dispatch [:navigate-back]))}
:title (i18n/label :t/recipient)
:right-accessories [{:on-press #(debounce/dispatch-and-chill [:wallet.send/set-recipient content] 3000)
:label (i18n/label :t/done)
:disabled (string/blank? content)}]}])
(views/defview contact-code []
(views/letsubs [content (reagent/atom nil)]
[react/view {:flex 1}
[recipient-topbar @content]
[react/view {:padding-horizontal 16
:padding-vertical 24
:flex 1}
[quo/text-input
{:multiline true
:height 98
:placeholder (i18n/label :t/recipient-code-placeholder)
:text-align-vertical :top
:on-change-text #(reset! content %)
:accessibility-label :recipient-address-input}]
[react/text {:style {:color colors/gray
:margin-vertical 16}}
(i18n/label :t/enter-recipient-address-or-username)]]]))

View File

@ -0,0 +1,315 @@
(ns status-im.ui.screens.wallet.recipient.views
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.i18n :as i18n]
[status-im.ui.components.react :as react]
[status-im.ui.components.topbar :as topbar]
[quo.core :as quo]
[status-im.ui.components.colors :as colors]
[status-im.utils.debounce :as debounce]
[status-im.ui.components.search-input.view :as search-input]
[status-im.ui.screens.wallet.components.views :as components]
[status-im.ui.components.icons.vector-icons :as icons]
[status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.utils.utils :as utils]
[status-im.ui.components.keyboard-avoid-presentation :as kb-presentation]
[status-im.ui.components.toolbar :as toolbar]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.stateofus :as stateofus])
(:require-macros [status-im.utils.views :as views]))
(defn- recipient-topbar []
[topbar/topbar
{:navigation {:on-press
#(do
(re-frame/dispatch [:wallet/recipient-modal-closed])
(re-frame/dispatch [:search/recipient-filter-changed nil])
(re-frame/dispatch [:navigate-back]))}
:modal? true
:border-bottom false
:title (i18n/label :t/recipient)
:right-accessories
[{:icon :qr
:accessibility-label :scan-contact-code-button
:on-press #(re-frame/dispatch [:wallet.send/qr-scanner
{:handler :wallet.send/qr-scanner-result}])}]}])
(defonce search-active? (reagent/atom false))
(defn search-input-wrapper []
(let [search-filter @(re-frame/subscribe [:search/recipient-filter])]
[react/view {:padding-horizontal 16
:padding-vertical 10}
[search-input/search-input
{:search-active? search-active?
:search-filter search-filter
:on-cancel #(re-frame/dispatch [:search/recipient-filter-changed nil])
:on-change (fn [text]
(re-frame/dispatch [:search/recipient-filter-changed text])
(re-frame/dispatch [:set-in [:contacts/new-identity :state] :searching])
(debounce/debounce-and-dispatch [:new-chat/set-new-identity text] 300))}]]))
(defn section [_ _ _]
(let [opened? (reagent/atom false)]
(fn [title cnt content]
[react/view {:padding-vertical 8}
[quo/list-item
{:title title
:on-press #(swap! opened? not)
:accessory
[react/view {:flex-direction :row :align-items :center}
(when (pos? cnt)
[react/text {:style {:color colors/gray}} cnt])
[icons/icon (if @opened? :main-icons/dropdown :main-icons/next)
{:container-style {:align-items :center
:margin-left 8
:justify-content :center}
:resize-mode :center
:color colors/black}]]}]
(when @opened?
content)])))
(defn render-account [account]
[quo/list-item
{:icon [chat-icon/custom-icon-view-list (:name account) (:color account)]
:title (:name account)
:on-press #(re-frame/dispatch [:wallet.send/set-recipient (:address account)])
:subtitle [quo/text {:monospace true
:color :secondary}
(utils/get-shortened-checksum-address (:address account))]}])
(def scroll-view-ref (atom nil))
(defn contacts-list-item [{:keys [name] :as contact}]
(let [[first-name second-name] (multiaccounts/contact-two-names contact true)]
[quo/list-item
{:title first-name
:subtitle second-name
:on-press #(do
(some-> ^js @scroll-view-ref (.scrollTo #js {:x 0 :animated true}))
(re-frame/dispatch [:wallet.recipient/focus-input])
(re-frame/dispatch [:wallet.recipient/address-changed name]))
:icon [chat-icon/contact-icon-contacts-tab
(multiaccounts/displayed-photo contact)]}]))
(defn empty-items [icon title]
[react/view {:height 94 :align-items :center :justify-content :center}
[icons/icon icon
{:color colors/gray}]
[react/text {:style {:color colors/gray :margin-top 8}}
title]])
(views/defview accounts-section []
(views/letsubs [accounts [:accounts-for-recipient]]
(let [cnt (count accounts)]
[section
(i18n/label :t/my-accounts)
cnt
(if (> cnt 0)
[react/view
(for [account accounts]
[render-account account])]
[empty-items :main-icons/address (i18n/label :t/my-accounts-empty)])])))
(defn render-recent [{:keys [from to type amount-text currency-text]}]
(let [inbound? (= type :inbound)
address (if inbound? from to)]
[quo/list-item
{:title [quo/text {:monospace true}
(utils/get-shortened-checksum-address address)]
:on-press #(do
(re-frame/dispatch [:wallet.recipient/focus-input])
(re-frame/dispatch [:wallet.recipient/address-changed address]))
:size :small
:accessory [react/text {:style {:flex-shrink 1
:color colors/gray}}
(str (if inbound? "↓ " "↑ ") amount-text " " currency-text)]}]))
(defn recent-section []
(let [{:keys [from]} @(re-frame/subscribe [:wallet/prepare-transaction])
txs @(re-frame/subscribe [:wallet/recipient-recent-txs (:address from)])
cnt (count txs)]
[section
(i18n/label :t/recent)
cnt
(if (> cnt 0)
[react/view
(for [tx txs]
[render-recent tx])]
[empty-items :main-icons/history (i18n/label :t/recent-empty)])]))
(defn render-fav [{:keys [address name]}]
(let [noname? (string/blank? name)
short-address (utils/get-shortened-checksum-address address)]
[quo/list-item
{:icon [chat-icon/custom-icon-view-list
(if noname? " 2" name)
(rand-nth colors/chat-colors)]
:title (if noname?
[quo/text {:monospace true}
short-address]
name)
:subtitle (when-not noname? [quo/text {:monospace true
:color :secondary}
short-address])
:on-press #(re-frame/dispatch [:wallet.send/set-recipient address])
:size (when noname? :small)}]))
(views/defview fav-section []
(views/letsubs [favourites [:wallet/favourites-filtered]]
(let [cnt (count favourites)]
[section
(i18n/label :t/favourites)
cnt
[react/view
;;TODO implement later
#_[quo/list-item
{:title "Add favourite"
:icon :main-icons/add
:theme :accent}]
(if (> cnt 0)
[react/view
(for [data favourites]
[render-fav data])]
[empty-items :main-icons/favourite (i18n/label :t/favourites-empty)])]])))
(views/defview contacts-section []
(views/letsubs [contacts [:contacts/active-with-ens-names]]
(let [cnt (count contacts)]
[section
(i18n/label :t/contacts)
cnt
(if (> cnt 0)
[react/view
(for [contact contacts]
[contacts-list-item contact])]
[empty-items :main-icons/username (i18n/label :t/contacts-empty)])])))
(views/defview search-results []
(views/letsubs [contacts [:contacts/active-with-ens-names]
favourites [:wallet/favourites-filtered]
accounts [:accounts-for-recipient]]
[react/view
(for [account accounts]
[render-account account])
(for [data favourites]
[render-fav data])
(for [contact contacts]
[contacts-list-item contact])]))
(defn accordion [search-filter]
(if (not (string/blank? search-filter))
[search-results]
[react/view
[components/separator]
[accounts-section]
[components/separator]
[recent-section]
[components/separator]
[fav-section]
[components/separator]
[contacts-section]
[components/separator]]))
(views/defview new-favourite []
(views/letsubs [{:keys [resolved-address]} [:wallet/recipient]
fav-name (atom "")]
[kb-presentation/keyboard-avoiding-view {:style {:flex 1}}
[react/view {:flex 1}
[topbar/topbar
{:modal? true
:title (i18n/label :t/new-favourite)}]
[react/scroll-view {:style {:flex 1}}
[react/view {:padding-horizontal 16}
[react/view {:flex-direction :row :justify-content :space-between
:align-items :center :height 40 :margin-vertical 8}
[quo/text (i18n/label :t/address-or-ens-name)]]
[quo/text-input
{:multiline true
:default-value resolved-address
:height 70
:editable false}]]
[react/view {:height 16}]
[quo/list-header (i18n/label :t/name-optional)]
[react/view {:padding-horizontal 16}
[quo/text-input {:show-cancel false
:on-change-text #(reset! fav-name %)}]]]
[toolbar/toolbar
{:show-border? true
:center
[quo/button
{:accessibility-label :add-fav
:type :secondary
:on-press #(re-frame/dispatch [:wallet/add-favourite resolved-address @fav-name])}
(i18n/label :t/add)]}]]]))
(views/defview recipient []
(views/letsubs [{:keys [address resolved-address searching] :as recip} [:wallet/recipient]
search-filter [:search/recipient-filter]
input-focused? (reagent/atom false)]
(let [disabled? (or searching (not resolved-address))]
[kb-presentation/keyboard-avoiding-view {:style {:flex 1}}
[react/view {:flex 1}
[recipient-topbar]
[search-input-wrapper]
[react/scroll-view {:style {:flex 1}
:keyboard-should-persist-taps :handled
:ref #(reset! scroll-view-ref %)}
[react/view
[components/separator]
[react/view {:padding-horizontal 16 :margin-bottom 16}
[react/view {:flex-direction :row :justify-content :space-between
:align-items :center :height 40 :margin-vertical 8}
[quo/text (i18n/label :t/address-or-ens-name)]
[quo/button {:type :secondary
:on-press #(re-frame/dispatch [:wallet.recipient/address-paste-pressed])}
(i18n/label :t/paste)]]
[quo/text-input
{:multiline true
:get-ref #(when (and recip %)
(re-frame/dispatch [:set-in [:wallet/recipient :inp-ref] %]))
:on-focus #(reset! input-focused? true)
:on-blur #(reset! input-focused? false)
:default-value address
:height 70
:placeholder (i18n/label :t/recipient-code-placeholder)
:text-align-vertical :top
:on-change-text #(do
(re-frame/dispatch [:set-in [:wallet/recipient :searching] :searching])
(debounce/debounce-and-dispatch [:wallet.recipient/address-changed %] 600))
:accessibility-label :recipient-address-input}]]
[react/view {:align-items :center :height 30 :padding-bottom 8}
(if searching
[react/small-loading-indicator]
(when resolved-address
[quo/text {:style {:margin-horizontal 16}
:size :small
:align :center
:color :secondary}
(when-not (ethereum/address? address)
(str (stateofus/username-with-domain address) " • "))
[quo/text {:monospace true
:size :inherit
:color :inherit}
(utils/get-shortened-address resolved-address)]]))]
[accordion search-filter]]]
(when @input-focused?
[toolbar/toolbar
{:show-border? true
:left
[quo/button
{:accessibility-label :participant-add-to-favs
:type :secondary
:disabled disabled?
:on-press #(re-frame/dispatch [:navigate-to :new-favourite])}
(i18n/label :t/add-to-favourites)]
:right
[quo/button
{:accessibility-label :participant-done
:type :secondary
:after :main-icons/next
:disabled disabled?
:on-press #(re-frame/dispatch [:wallet.send/set-recipient resolved-address])}
(i18n/label :t/done)]}])]])))

View File

@ -50,9 +50,7 @@
:icon :main-icons/qr
:theme :accent
:accessibility-label :chose-recipient-scan-qr
:on-press #(re-frame/dispatch [:wallet.send/qr-scanner {:handler :wallet.send/qr-scanner-result
:cancel-handler :wallet.send/qr-scanner-cancel
:modal-opened? true}])}
:on-press #(re-frame/dispatch [:wallet.send/qr-scanner {:handler :wallet.send/qr-scanner-result}])}
{:title (i18n/label :t/recipient-code)
:icon :main-icons/address
:theme :accent

View File

@ -1,11 +1,14 @@
(ns status-im.ui.screens.wallet.send.styles
(:require [status-im.ui.components.colors :as colors]))
(defn sheet [small-screen?]
(defn sheet []
{:flex 1})
(defn acc-sheet []
{:background-color colors/white
:border-top-right-radius 16
:border-top-left-radius 16
:padding-bottom (if small-screen? 40 60)})
:padding-bottom 60})
(defn header [small-screen?]
{:flex-direction :row

View File

@ -19,7 +19,9 @@
[status-im.utils.utils :as utils]
[status-im.utils.money :as money]
[quo.core :as quo]
[status-im.ethereum.core :as ethereum]))
[status-im.ethereum.core :as ethereum]
[status-im.ui.components.keyboard-avoid-presentation :as kb-presentation]
[status-im.ui.components.topbar :as topbar]))
(defn header [{:keys [label small-screen?]}]
[react/view (styles/header small-screen?)
@ -69,23 +71,26 @@
(defn render-contact [contact from-chat?]
(if from-chat?
[quo/list-item {:title (multiaccounts/displayed-name contact)
:subtitle (:address contact)
:subtitle [quo/text {:monospace true
:color :secondary}
(utils/get-shortened-checksum-address (:address contact))]
:icon [chat-icon/contact-icon-contacts-tab
(multiaccounts/displayed-photo contact)]}]
[quo/list-item
{:title (if-not contact
(i18n/label :t/wallet-choose-recipient)
[quo/text {:size :large
:monospace true}
(utils/get-shortened-checksum-address
(if (string? contact) contact (:address contact)))])
:accessibility-label :choose-recipient-button
:on-press #(do
(re-frame/dispatch [:dismiss-keyboard])
(re-frame/dispatch [:bottom-sheet/show-sheet
{:content sheets/choose-recipient
:content-height 200}]))
:chevron true}]))
(merge {:title (if-not contact
(i18n/label :t/wallet-choose-recipient)
[quo/text {:size :large
:monospace true}
(utils/get-shortened-checksum-address
(if (string? contact) contact (:address contact)))])
:accessibility-label :choose-recipient-button
:on-press #(do
(re-frame/dispatch [:dismiss-keyboard])
(re-frame/dispatch [:wallet.send/navigate-to-recipient-code]))
:chevron true}
(when-not contact
{:icon :main-icons/add
:theme :accent}))]))
(defn set-max [token]
[react/touchable-highlight
@ -102,140 +107,12 @@
[react/text {:style {:color colors/gray :margin-left 16 :margin-bottom 8
:font-size 15 :line-height 22}}
(str (i18n/format-currency (* amount price) (:code wallet-currency)) " " (:code wallet-currency))])))
(views/defview sheet [_]
(views/letsubs [{:keys [amount-error amount-text
request?
from token to sign-enabled? from-chat?]
:as tx}
[:wallet.send/prepare-transaction-with-balance]
prices [:prices]
wallet-currency [:wallet/currency]
window-height [:dimensions/window-height]
window-width [:dimensions/window-width]
keyboard-height [:keyboard-height]]
(let [small-screen? (< (- window-height keyboard-height) 450)
to-norm (ethereum/normalized-hex (if (string? to) to (:address to)))]
[react/view {:style (styles/sheet small-screen?)}
[react/view {:flex-direction :row :padding-horizontal 16 :align-items :center
:margin-top 12 :margin-bottom 4}
[react/text-input
{:style {:font-size (if small-screen? 24 38)
:max-width (- (* (/ window-width 4) 3) 106)
:color (if amount-error colors/red colors/black)}
:keyboard-type :decimal-pad
:auto-capitalize :words
:accessibility-label :amount-input
:default-value amount-text
:editable (not request?)
:auto-focus true
:on-change-text #(re-frame/dispatch [:wallet.send/set-amount-text %])
:placeholder "0.0 "}]
[asset-selector tx window-width]
(when amount-error
[tooltip/tooltip (if from
amount-error
(i18n/label :t/select-account-first))
{:bottom-value 2
:font-size 12}])]
[fiat-value amount-text token prices wallet-currency]
(when-not (or request? from-chat?)
[set-max token])
[components/separator]
(when-not small-screen?
[quo/list-header (i18n/label :t/from)])
[react/view {:flex-direction :row :flex 1 :align-items :center}
(when small-screen?
[react/i18n-text {:style {:width 50 :text-align :right :color colors/gray} :key :t/from}])
[react/view {:flex 1}
[render-account from token :wallet.send/set-field]]]
(when-not small-screen?
[quo/list-header
(i18n/label :t/to)])
[react/view {:flex-direction :row :flex 1 :align-items :center}
(when small-screen?
[react/i18n-text {:style {:width 50 :text-align :right :color colors/gray} :key :t/to}])
[react/view {:flex 1}
[render-contact to from-chat?]]]
[toolbar/toolbar
{:left
[quo/button
{:type :secondary
:on-press #(re-frame/dispatch [:wallet/cancel-transaction-command])}
(i18n/label :t/cancel)]
:right
[quo/button
{:accessibility-label :send-transaction-bottom-sheet
:disabled (not sign-enabled?)
:on-press #(re-frame/dispatch
[(cond
request?
:wallet.ui/sign-transaction-button-clicked-from-request
from-chat?
:wallet.ui/sign-transaction-button-clicked-from-chat
:else
:wallet.ui/sign-transaction-button-clicked) tx])}
(if (and (not request?) from-chat? (not to-norm))
(i18n/label :t/wallet-send)
(i18n/label :t/next))]}]])))
(views/defview request-sheet [_]
(views/letsubs [{:keys [amount-error amount-text from token sign-enabled?] :as tx}
[:wallet.request/prepare-transaction-with-balance]
window-height [:dimensions/window-height]
window-width [:dimensions/window-width]
prices [:prices]
wallet-currency [:wallet/currency]
keyboard-height [:keyboard-height]]
(let [small-screen? (< (- window-height keyboard-height) 450)]
[react/view {:style (styles/sheet small-screen?)}
[header {:small-screen? small-screen?
:label :t/request-transaction}]
[react/view {:flex-direction :row :padding-horizontal 24 :align-items :center
:margin-vertical (if small-screen? 8 16)}
[react/text-input
{:style {:font-size (if small-screen? 24 38)
:color (when amount-error colors/red)
:flex-shrink 1}
:keyboard-type :decimal-pad
:auto-capitalize :words
:accessibility-label :amount-input
:default-value amount-text
:auto-focus true
:on-change-text #(re-frame/dispatch [:wallet.request/set-amount-text %])
:placeholder "0.0 "}]
[asset-selector tx window-width]
(when amount-error
[tooltip/tooltip amount-error {:bottom-value 2
:font-size 12}])]
[fiat-value amount-text token prices wallet-currency]
[components/separator]
(when-not small-screen?
[quo/list-header
(i18n/label :t/to)])
[react/view {:flex-direction :row :flex 1 :align-items :center}
(when small-screen?
[react/i18n-text {:style {:width 50 :text-align :right :color colors/gray} :key :t/to}])
[react/view {:flex 1}
[render-account from token :wallet.request/set-field]]]
[toolbar/toolbar
{:left
[react/view {:padding-horizontal 8}
[quo/button
{:type :secondary
:on-press #(re-frame/dispatch [:wallet/cancel-transaction-command])}
(i18n/label :t/cancel)]]
:right
[quo/button
{:accessibility-label :request-transaction-bottom-sheet
:disabled (not sign-enabled?)
:on-press #(re-frame/dispatch
[:wallet.ui/request-transaction-button-clicked tx])}
(i18n/label :t/wallet-request)]}]])))
(views/defview select-account-sheet [{:keys [from message]}]
(views/letsubs [window-height [:dimensions/window-height]
keyboard-height [:keyboard-height]]
(let [small-screen? (< (- window-height keyboard-height) 450)]
[react/view {:style (styles/sheet small-screen?)}
[react/view {:style (styles/acc-sheet)}
[header {:small-screen? small-screen?
:label :t/select-account}]
[react/view {:flex-direction :row :padding-horizontal 24 :align-items :center
@ -265,32 +142,144 @@
(:address from)])}
(i18n/label :t/share)]}]])))
(defview prepare-transaction []
(letsubs [tx [:wallet/prepare-transaction]]
[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
;; and because modal screen (qr code scanner) can't be opened over bottom sheet we have to use :modal-opened?
;; to hide our transaction panel
(when (and tx
(not (:modal-opened? tx))
(not (:request-command? tx)))
(select-keys tx [:from-chat?]))
sheet]))
(defview request-transaction []
(letsubs [tx [:wallet/prepare-transaction]]
[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
;; and because modal screen (qr code scanner) can't be opened over bottom sheet we have to use :modal-opened?
;; to hide our transaction panel
(when (and tx
(not (:modal-opened? tx))
(:request-command? tx))
(select-keys tx [:from-chat? :request?]))
request-sheet]))
(defview select-account []
(letsubs [data [:commands/select-account]]
[bottom-panel/animated-bottom-panel
data
select-account-sheet]))
(views/defview request-transaction [_]
(views/letsubs [{:keys [amount-error amount-text from token sign-enabled?] :as tx}
[:wallet.request/prepare-transaction-with-balance]
window-width [:dimensions/window-width]
prices [:prices]
wallet-currency [:wallet/currency]]
[kb-presentation/keyboard-avoiding-view {:style {:flex 1}}
[react/view {:flex 1}
[topbar/topbar
{:navigation {:on-press
#(do
(re-frame/dispatch [:wallet/cancel-transaction-command])
(re-frame/dispatch [:navigate-back]))}
:modal? true
:border-bottom true
:title (i18n/label :t/request-transaction)}]
[react/scroll-view {:style {:flex 1}
:keyboard-should-persist-taps :handled}
[react/view {:style (styles/sheet)}
[react/view {:flex-direction :row :padding-horizontal 24 :align-items :center
:margin-vertical 16}
[react/text-input
{:style {:font-size 38
:color (when amount-error colors/red)
:flex-shrink 1}
:keyboard-type :decimal-pad
:auto-capitalize :words
:accessibility-label :amount-input
:default-value amount-text
:auto-focus true
:on-change-text #(re-frame/dispatch [:wallet.request/set-amount-text %])
:placeholder "0.0 "}]
[asset-selector tx window-width]
(when amount-error
[tooltip/tooltip amount-error {:bottom-value 2
:font-size 12}])]
[fiat-value amount-text token prices wallet-currency]
[components/separator]
[quo/list-header
(i18n/label :t/to)]
[react/view {:flex-direction :row :flex 1 :align-items :center}
[react/view {:flex 1}
[render-account from token :wallet.request/set-field]]]]]
[toolbar/toolbar
{:show-border? true
:right
[quo/button
{:type :secondary
:after :main-icon/next
:accessibility-label :request-transaction-bottom-sheet
:disabled (not sign-enabled?)
:on-press #(do
(re-frame/dispatch
[:wallet.ui/request-transaction-button-clicked tx])
(re-frame/dispatch [:navigate-back]))}
(i18n/label :t/wallet-request)]}]]]))
(views/defview prepare-send-transaction [_]
(views/letsubs [{:keys [amount-error amount-text
request?
from token to sign-enabled? from-chat?]
:as tx}
[:wallet.send/prepare-transaction-with-balance]
prices [:prices]
wallet-currency [:wallet/currency]
window-width [:dimensions/window-width]]
(let [to-norm (ethereum/normalized-hex (if (string? to) to (:address to)))]
[kb-presentation/keyboard-avoiding-view {:style {:flex 1}}
[react/view {:flex 1}
[topbar/topbar
{:navigation {:on-press
#(do
(re-frame/dispatch [:wallet/cancel-transaction-command])
(re-frame/dispatch [:navigate-back]))}
:modal? true
:border-bottom true
:title (i18n/label :t/send-transaction)}]
[react/scroll-view {:style {:flex 1}
:keyboard-should-persist-taps :handled}
[react/view {:style (styles/sheet)}
[react/view {:flex-direction :row :padding-horizontal 16 :align-items :center
:margin-top 12 :margin-bottom 4}
[react/text-input
{:style {:font-size 38
:max-width (- (* (/ window-width 4) 3) 106)
:color (if amount-error colors/red colors/black)}
:keyboard-type :decimal-pad
:auto-capitalize :words
:accessibility-label :amount-input
:default-value amount-text
:editable (not request?)
:auto-focus true
:on-change-text #(re-frame/dispatch [:wallet.send/set-amount-text %])
:placeholder "0.0 "}]
[asset-selector tx window-width]
(when amount-error
[tooltip/tooltip (if from
amount-error
(i18n/label :t/select-account-first))
{:bottom-value 2
:font-size 12}])]
[fiat-value amount-text token prices wallet-currency]
(when-not (or request? from-chat?)
[set-max token])
[components/separator]
[quo/list-header (i18n/label :t/from)]
[react/view {:flex-direction :row :flex 1 :align-items :center}
[react/view {:flex 1}
[render-account from token :wallet.send/set-field]]]
[quo/list-header
(i18n/label :t/to)]
[react/view {:flex-direction :row :flex 1 :align-items :center}
[react/view {:flex 1}
[render-contact to from-chat?]]]]]
[toolbar/toolbar
{:show-border? true
:right
[quo/button
{:accessibility-label :send-transaction-bottom-sheet
:type :secondary
:after :main-icon/next
:disabled (not sign-enabled?)
:on-press #(do
(re-frame/dispatch
[(cond
request?
:wallet.ui/sign-transaction-button-clicked-from-request
from-chat?
:wallet.ui/sign-transaction-button-clicked-from-chat
:else
:wallet.ui/sign-transaction-button-clicked) tx])
(re-frame/dispatch [:navigate-back]))}
(if (and (not request?) from-chat? (not to-norm))
(i18n/label :t/wallet-send)
(i18n/label :t/next))]}]]])))

View File

@ -2,7 +2,6 @@
(:require [re-frame.core :as re-frame]
[status-im.contact.db :as contact.db]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.eip55 :as eip55]
[status-im.ethereum.eip681 :as eip681]
[status-im.ethereum.ens :as ens]
[status-im.i18n :as i18n]
@ -11,9 +10,7 @@
[status-im.router.core :as router]
[status-im.qr-scanner.core :as qr-scaner]
[status-im.ui.components.bottom-sheet.core :as bottom-sheet]
[status-im.navigation :as navigation]
[clojure.string :as string]
[status-im.ethereum.stateofus :as stateofus]))
[status-im.navigation :as navigation]))
;; FIXME(Ferossgp): Should be part of QR scanner not wallet
(fx/defn toggle-flashlight
@ -30,10 +27,14 @@
(assoc-in fx [:db :wallet :send-transaction :gas]
ethereum/default-transaction-gas))
(re-frame/reg-fx
::resolve-address
(fn [{:keys [registry ens-name cb]}]
(ens/get-addr registry ens-name cb)))
(fx/defn set-recipient
{:events [:wallet.send/set-recipient]}
[{:keys [db]} address]
{:db (-> db
(dissoc :wallet/recipient)
(assoc-in [:ui/search :recipient-filter] nil)
(assoc-in [:wallet/prepare-transaction :to] address))
:dispatch [:navigate-back]})
(re-frame/reg-fx
::resolve-addresses
@ -50,29 +51,6 @@
(.catch (fn [error]
(js/console.log error))))))
(fx/defn set-recipient
{:events [:wallet.send/set-recipient ::recipient-address-resolved]}
[{:keys [db]} raw-recipient]
(let [chain (ethereum/chain-keyword db)
recipient (when raw-recipient (string/trim raw-recipient))]
(cond
(ethereum/address? recipient)
(let [checksum (eip55/address->checksum recipient)]
(if (eip55/valid-address-checksum? checksum)
{:db (-> db
(assoc-in [:wallet/prepare-transaction :to] checksum)
(assoc-in [:wallet/prepare-transaction :modal-opened?] false))
:dispatch [:navigate-back]}
{:ui/show-error (i18n/label :t/wallet-invalid-address-checksum {:data recipient})}))
(not (string/blank? recipient))
{::resolve-address {:registry (get ens/ens-registries chain)
:ens-name (if (= (.indexOf ^js recipient ".") -1)
(stateofus/subdomain recipient)
recipient)
:cb #(re-frame/dispatch [::recipient-address-resolved %])}}
:else
{:ui/show-error (i18n/label :t/wallet-invalid-address {:data recipient})})))
(defn- fill-prepare-transaction-details
[db
{:keys [address name value symbol gas gasPrice gasLimit]
@ -101,30 +79,29 @@
(let [{:keys [address] :as details}
(eip681/extract-request-details data all-tokens)]
(if address
(if (:wallet/prepare-transaction db)
{:db (update db :wallet/prepare-transaction assoc
:to address :to-name (find-address-name db address))}
(let [current-chain-id (get-in networks [current-network :config :NetworkId])]
(merge {:db (fill-prepare-transaction-details db details all-tokens)}
(when (and chain-id (not= current-chain-id chain-id))
{:ui/show-error (i18n/label :t/wallet-invalid-chain-id
{:data uri :chain current-chain-id})}))))
(if (:wallet/recipient db)
{:db (update db :wallet/recipient assoc :resolved-address address
:address address)
;;android
:dispatch-later [{:ms 1000 :dispatch [:wallet.recipient/focus-input]}]}
(if (:wallet/prepare-transaction db)
{:db (update db :wallet/prepare-transaction assoc
:to address :to-name (find-address-name db address))}
(let [current-chain-id (get-in networks [current-network :config :NetworkId])]
(merge {:db (fill-prepare-transaction-details db details all-tokens)
:dispatch [:navigate-to :prepare-send-transaction]}
(when (and chain-id (not= current-chain-id chain-id))
{:ui/show-error (i18n/label :t/wallet-invalid-chain-id
{:data uri :chain current-chain-id})})))))
{:ui/show-error (i18n/label :t/wallet-invalid-address {:data uri})})))
(fx/defn qr-scanner-allowed
{:events [:wallet.send/qr-scanner]}
[{:keys [db] :as cofx} options]
(fx/merge cofx
(when (:modal-opened? options)
{:db (assoc-in db [:wallet/prepare-transaction :modal-opened?] true)})
(bottom-sheet/hide-bottom-sheet)
(qr-scaner/scan-qr-code options)))
(fx/defn qr-scanner-cancel
{:events [:wallet.send/qr-scanner-cancel]}
[{db :db} _]
{:db (assoc-in db [:wallet/prepare-transaction :modal-opened?] false)})
(fx/defn parse-eip681-uri-and-resolve-ens
{:events [:wallet/parse-eip681-uri-and-resolve-ens]}
[{db :db :as cofx} {:keys [message uri paths ens-names error]}]
@ -154,7 +131,4 @@
[cofx data _]
(fx/merge cofx
(navigation/navigate-back)
(parse-eip681-uri-and-resolve-ens (router/match-eip681 data))
(fn [{:keys [db]}]
(when (get-in db [:wallet/prepare-transaction :modal-opened?])
{:db (assoc-in db [:wallet/prepare-transaction :modal-opened?] false)}))))
(parse-eip681-uri-and-resolve-ens (router/match-eip681 data))))

View File

@ -26,7 +26,8 @@
[status-im.wallet.prices :as prices]
[status-im.wallet.utils :as wallet.utils]
[status-im.native-module.core :as status]
[status-im.ui.screens.mobile-network-settings.utils :as mobile-network-utils]))
[status-im.ui.screens.mobile-network-settings.utils :as mobile-network-utils]
status-im.wallet.recipient.core))
(defn get-balance
[{:keys [address on-success on-error]}]
@ -208,6 +209,13 @@
(when config/erc20-contract-warnings-enabled?
{:wallet/validate-tokens [default-tokens all-default-tokens]}))))
(fx/defn initialize-favourites
[{:keys [db]} favourites]
{:db (assoc db :wallet/favourites (reduce (fn [acc {:keys [address] :as favourit}]
(assoc acc address favourit))
{}
favourites))})
(fx/defn update-balances
[{{:keys [network-status :wallet/all-tokens
multiaccount :multiaccount/accounts] :as db} :db
@ -419,7 +427,8 @@
:symbol symbol
:amount-text amount-text
:request? true
:from-chat? true})}))
:from-chat? true})
:dispatch [:navigate-to :prepare-send-transaction]}))
(fx/defn sign-transaction-button-clicked-from-request
{:events [:wallet.ui/sign-transaction-button-clicked-from-request]}
@ -513,7 +522,8 @@
(:multiaccount/accounts db))
:to contact
:symbol :ETH
:from-chat? true})}
:from-chat? true})
:dispatch [:navigate-to :prepare-send-transaction]}
ens-verified
(assoc ::resolve-address
{:registry (get ens/ens-registries chain)
@ -535,7 +545,8 @@
contact.db/enrich-contact))
:symbol :ETH
:from-chat? true
:request-command? true})}))
:request-command? true})
:dispatch [:navigate-to :request-transaction]}))
(fx/defn prepare-transaction-from-wallet
{:events [:wallet/prepare-transaction-from-wallet]}
@ -545,6 +556,7 @@
:to nil
:symbol :ETH
:from-chat? false})
:dispatch [:navigate-to :prepare-send-transaction]
:signing/update-gas-price {:success-event :wallet.send/update-gas-price-success}})
(fx/defn cancel-transaction-command
@ -595,9 +607,10 @@
{:events [:wallet.send/navigate-to-recipient-code]}
[{:keys [db] :as cofx}]
(fx/merge cofx
{:db (assoc-in db [:wallet/prepare-transaction :modal-opened?] true)}
{:db (-> db
(assoc :wallet/recipient {}))}
(bottom-sheet/hide-bottom-sheet)
(navigation/navigate-to-cofx :contact-code nil)))
(navigation/navigate-to-cofx :recipient nil)))
(fx/defn show-delete-account-confirmation
{:events [:wallet.settings/show-delete-account-confirmation]}

View File

@ -0,0 +1,111 @@
(ns status-im.wallet.recipient.core
(:require [re-frame.core :as re-frame]
[status-im.ui.components.react :as react]
[clojure.string :as string]
[status-im.utils.fx :as fx]
[status-im.utils.utils :as utils]
[status-im.ethereum.ens :as ens]
[status-im.ethereum.core :as ethereum]
[status-im.utils.random :as random]
[status-im.ethereum.eip55 :as eip55]
[status-im.i18n :as i18n]
[status-im.ethereum.stateofus :as stateofus]
[status-im.navigation :as navigation]
[status-im.ethereum.json-rpc :as json-rpc]))
;;NOTE we want to handle only last resolve
(def resolve-last-id (atom nil))
(re-frame/reg-fx
::resolve-address
(fn [{:keys [registry ens-name cb]}]
(ens/get-addr registry ens-name cb)))
(re-frame/reg-fx
:wallet.recipient/address-paste
(fn [inp-ref]
(react/get-from-clipboard
#(do
(when inp-ref (.focus inp-ref))
(re-frame/dispatch [:wallet.recipient/address-changed (string/trim %)])))))
(re-frame/reg-fx
:wallet.recipient/address-paste
(fn [inp-ref]
(react/get-from-clipboard
#(do
(when inp-ref (.focus inp-ref))
(re-frame/dispatch [:wallet.recipient/address-changed (string/trim %)])))))
(re-frame/reg-fx
::focus-input
(fn [inp-ref]
(when inp-ref
(.focus inp-ref))))
(fx/defn focus-input
{:events [:wallet.recipient/focus-input]}
[{:keys [db]}]
{::focus-input (get-in db [:wallet/recipient :inp-ref])})
(fx/defn address-paste-pressed
{:events [:wallet.recipient/address-paste-pressed]}
[{:keys [db]}]
{:wallet.recipient/address-paste (get-in db [:wallet/recipient :inp-ref])})
(fx/defn set-recipient
{:events [::recipient-address-resolved]}
[{:keys [db]} raw-recipient id]
(when (or (not id) (= id @resolve-last-id))
(reset! resolve-last-id nil)
(let [chain (ethereum/chain-keyword db)
recipient (utils/safe-trim raw-recipient)]
(cond
(ethereum/address? recipient)
(let [checksum (eip55/address->checksum recipient)]
(if (eip55/valid-address-checksum? checksum)
{:db (-> db
(assoc-in [:wallet/recipient :searching] false)
(assoc-in [:wallet/recipient :resolved-address] checksum))}
{:ui/show-error (i18n/label :t/wallet-invalid-address-checksum {:data recipient})
:db (assoc-in db [:wallet/recipient :searching] false)}))
(and (not (string/blank? recipient)) (ens/valid-eth-name-prefix? recipient))
(let [ens-name (if (= (.indexOf ^js recipient ".") -1)
(stateofus/subdomain recipient)
recipient)]
(if (ens/is-valid-eth-name? ens-name)
(do
(reset! resolve-last-id (random/id))
{::resolve-address
{:registry (get ens/ens-registries chain)
:ens-name ens-name
:cb #(re-frame/dispatch [::recipient-address-resolved % @resolve-last-id])}})
{:db (assoc-in db [:wallet/recipient :searching] false)}))
:else
{:db (assoc-in db [:wallet/recipient :searching] false)}))))
(fx/defn address-changed
{:events [:wallet.recipient/address-changed]}
[{:keys [db] :as cofx} new-identity]
(fx/merge cofx
{:db (update db :wallet/recipient assoc :address new-identity :resolved-address nil
:searching true)}
(set-recipient new-identity nil)))
(fx/defn recipient-modal-closed
{:events [:wallet/recipient-modal-closed]}
[{:keys [db]}]
{:db (dissoc db :wallet/recipient)})
(fx/defn add-favourite
{:events [:wallet/add-favourite]}
[{:keys [db] :as cofx} address name]
(let [new-favourite {:address address
:name (or name "")}]
(fx/merge cofx
{:db (assoc-in db [:wallet/favourites address] new-favourite)
::focus-input (get-in db [:wallet/recipient :inp-ref])
::json-rpc/call [{:method "wallet_addFavourite"
:params [new-favourite]
:on-success #()}]}
(navigation/navigate-back))))

View File

@ -2,7 +2,7 @@
"_comment": "DO NOT EDIT THIS FILE BY HAND. USE 'scripts/update-status-go.sh <tag>' instead",
"owner": "status-im",
"repo": "status-go",
"version": "v0.62.1",
"commit-sha1": "36e742cb70fb7372e0d57ba391523613f6ed87d5",
"src-sha256": "06639pfr782zpp7ysaz4ljlsp87pswdnwlq0j15v7z75xfg4yz6p"
"version": "v0.62.2",
"commit-sha1": "682722b9734cef6f3c6832575d78c6996988a379",
"src-sha256": "0ry6l6f154j7d8zp4dw5gs5pwygmjr61hxnpj0yr32h7i6zdna88"
}

View File

@ -1263,5 +1263,15 @@
"membership-description": "Group membership requires you to be accepted by the group admin",
"group-membership-request": "Group membership request",
"members-limit-reached": "Members limit reached",
"favourites": "Favourites",
"new-favourite": "New favourite",
"add-to-favourites": "Add to favourites",
"favourites-empty": "Addresses added to favourites will appear here",
"contacts-empty": "Contacts with ENS names will appear here",
"my-accounts": "My accounts",
"my-accounts-empty": "Your available accounts will appear here",
"recent-empty": "Recently used addresses will appear here",
"address-or-ens-name": "Address or ENS name",
"name-optional": "Name (optional)",
"mute": "Mute"
}