feat(wallet)!: Activity - a more reliable update (#21596)

This commit:

- Removes the usage of deprecated "wallet_filterActivityAsync RPC" and updates it to "wallet_startActivityFilterSessionV2" with other supporting activity filter session RPCs
- Removes deprecated "wallet_checkRecentHistoryForChainIDs" RPC as we no longer support past transaction history
- Added support for Bridge, Swap and Approval activities
- Added context for the sender and receiver if the address is known (own accounts or saved addresses)
- Added support for viewing, copying and sharing links to TX details on block explorer

Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com>
This commit is contained in:
Mohamed Javid 2024-12-02 21:35:05 +05:30 committed by GitHub
parent f08ac94596
commit f6dfc64567
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 618 additions and 450 deletions

View File

@ -6,7 +6,8 @@
[:catn
[:props
[:map
[:transaction {:optional true} [:maybe [:enum :receive :send :swap :bridge :buy :destroy :mint]]]
[:transaction {:optional true}
[:maybe [:enum :receive :send :swap :bridge :buy :destroy :mint :approval]]]
[:status {:optional true} [:maybe [:enum :pending :confirmed :finalised :failed]]]
[:counter {:optional true} [:maybe :int]]
[:timestamp {:optional true} [:maybe :string]]

View File

@ -41,7 +41,8 @@
colors/white-opa-10)})
(def timestamp-container
{:margin-left 4})
{:margin-left 4
:flex 1})
(defn timestamp
[theme blur?]

View File

@ -12,22 +12,24 @@
[utils.i18n :as i18n]))
(def transaction-translation
{:receive [i18n/label :t/receive]
:send [i18n/label :t/send]
:swap [i18n/label :t/swap]
:bridge [i18n/label :t/bridge]
:buy [i18n/label :t/buy]
:destroy [i18n/label :t/destroy]
:mint [i18n/label :t/mint]})
{:receive [i18n/label :t/receive]
:send [i18n/label :t/send]
:swap [i18n/label :t/swap]
:bridge [i18n/label :t/bridge]
:buy [i18n/label :t/buy]
:destroy [i18n/label :t/destroy]
:mint [i18n/label :t/mint]
:approval [i18n/label :t/set-spending-cap]})
(def transaction-icon
{:receive :i/receive
:send :i/send
:swap :i/transaction
:bridge :i/bridge
:buy :i/buy
:destroy :i/destroy
:mint :i/mint})
{:receive :i/receive
:send :i/send
:swap :i/transaction
:bridge :i/bridge
:buy :i/buy
:destroy :i/destroy
:mint :i/mint
:approval :i/token-sales})
(def status-icon
{:pending :i/pending-state
@ -39,7 +41,8 @@
[{:keys [transaction
timestamp
counter
blur?]
blur?
on-press-options]
:or {transaction :receive
counter 1}}
theme]
@ -64,7 +67,13 @@
:size :label
:style (style/timestamp theme blur?)
:accessibility-label :transaction-timestamp}
timestamp]]])
timestamp]]
(when on-press-options
[rn/pressable
{:on-press on-press-options
:hit-slop {:top 10 :bottom 10 :left 10 :right 10}}
[icon/icon :i/options
{:size 20}]])])
(defn transaction-icon-view
[{:keys [blur? transaction status]
@ -120,8 +129,8 @@
:accessibility-label :wallet-activity
:disabled (= state :disabled)
:on-press on-press
:on-press-in on-press-in
:on-press-out on-press-out}
:on-press-in (when on-press on-press-in)
:on-press-out (when on-press on-press-out)}
[rn/view {:style style/container}
[transaction-icon-view props theme]
[rn/view {:style {:flex 1}}

View File

@ -35,6 +35,12 @@
(def sepolia-chain-explorer-link "https://sepolia.etherscan.io/address/")
(def optimism-sepolia-chain-explorer-link "https://sepolia-optimistic.etherscan.io/address/")
(def arbitrum-sepolia-chain-explorer-link "https://sepolia.arbiscan.io/address/")
(def mainnet-tx-details-base-link "https://etherscan.io/tx")
(def optimism-mainnet-tx-details-base-link "https://optimistic.etherscan.io/tx")
(def arbitrum-mainnet-tx-details-base-link "https://arbiscan.io/tx")
(def mainnet-sepolia-tx-details-base-link "https://sepolia.etherscan.io/tx")
(def optimism-sepolia-tx-details-base-link "https://sepolia-optimistic.etherscan.io/tx")
(def arbitrum-sepolia-tx-details-base-link "https://sepolia.arbiscan.io/tx")
(def opensea-link "https://opensea.io")
(def opensea-tesnet-link "https://testnets.opensea.io")

View File

@ -152,7 +152,8 @@
{:key :bridge}
{:key :buy}
{:key :destroy}
{:key :mint}]}
{:key :mint}
{:key :approval}]}
{:key :status
:type :select
:options [{:key :pending}
@ -219,7 +220,8 @@
:descriptor descriptor
:blur? (:blur? @component-state)
:show-blur-background? true}
[rn/view {:style {:align-self :center}}
[rn/view {:style {:flex 1}}
[quo/wallet-activity
(merge {:on-press #(js/alert "Dropdown pressed")}
(merge {:on-press #(js/alert "Activity pressed")
:on-press-options #(js/alert "Options pressed")}
@component-state)]]])))

View File

@ -13,6 +13,8 @@
(def first-tab-id :assets)
;; NOTE: If the id of the tabs are changed, please check 'wallet/select-account-tab' event as
;; a activity event depends on it
(def tabs-data
[{:id :assets :label (i18n/label :t/assets) :accessibility-label :assets-tab}
{:id :collectibles :label (i18n/label :t/collectibles) :accessibility-label :collectibles-tab}
@ -30,8 +32,6 @@
(hot-reload/use-safe-unmount (fn []
(rf/dispatch [:wallet/close-account-page])
(rf/dispatch [:wallet/clean-current-viewing-account])))
(rn/use-mount
#(rf/dispatch [:wallet/fetch-activities-for-current-account]))
[rn/view {:style {:flex 1}}
[account-switcher/view
{:type :wallet-networks

View File

@ -0,0 +1,104 @@
(ns status-im.contexts.wallet.common.activity-tab.activity-types.view
(:require [quo.core :as quo]
[status-im.contexts.wallet.common.activity-tab.options.view :as activity-options]
[utils.re-frame :as rf]))
;; Common helpers
(defn- open-options
[transactions]
(rf/dispatch
[:show-bottom-sheet
{:content #(activity-options/view (first transactions))}]))
(defn- common-activity-props
[{:keys [tx-type relative-date status transactions]}]
{:transaction tx-type
:timestamp relative-date
:status status
:blur? false
:on-press-options #(open-options transactions)})
(defn- network-tag
[{:keys [network-name network-logo]}]
{:type :network
:network-name network-name
:network-logo network-logo})
(defn- collectible-tag
[{:keys [nft-url nft-name amount token-id]}]
{:type :collectible
:collectible nft-url
:collectible-name (if (> amount 1)
(str amount " " nft-name)
nft-name)
:collectible-number (when (not= token-id "0")
token-id)})
(defn- token-tag
[{:keys [token amount]}]
{:type :token
:token token
:amount amount})
;; Activity type views
(defn send-activity
[{:keys [symbol-out amount-out token-id sender-tag recipient-tag network-name-out network-logo-out]
:as activity-data}]
(let [base-props (common-activity-props activity-data)
first-tag (if token-id
(collectible-tag activity-data)
(token-tag {:token symbol-out :amount amount-out}))]
[quo/wallet-activity
(assoc base-props
:first-tag first-tag
:second-tag-prefix :t/from
:second-tag sender-tag
:third-tag-prefix :t/to
:third-tag recipient-tag
:fourth-tag-prefix :t/on
:fourth-tag (network-tag {:network-name network-name-out
:network-logo network-logo-out}))]))
(defn bridge-activity
[{:keys [symbol-out amount-out network-name-out network-logo-out network-name-in network-logo-in
sender-tag]
:as activity-data}]
[quo/wallet-activity
(assoc (common-activity-props activity-data)
:first-tag (token-tag {:token symbol-out :amount amount-out})
:second-tag-prefix :t/from
:second-tag (network-tag {:network-name network-name-out
:network-logo network-logo-out})
:third-tag-prefix :t/to
:third-tag (network-tag {:network-name network-name-in
:network-logo network-logo-in})
:fourth-tag-prefix :t/in
:fourth-tag sender-tag)])
(defn swap-activity
[{:keys [symbol-out amount-out symbol-in amount-in sender-tag network-name-out network-logo-out]
:as activity-data}]
[quo/wallet-activity
(assoc (common-activity-props activity-data)
:first-tag (token-tag {:token symbol-out :amount amount-out})
:second-tag-prefix :t/to
:second-tag (token-tag {:token symbol-in :amount amount-in})
:third-tag-prefix :t/in
:third-tag sender-tag
:fourth-tag-prefix :t/on
:fourth-tag (network-tag {:network-name network-name-out
:network-logo network-logo-out}))])
(defn approval-activity
[{:keys [symbol-out amount-out sender-tag spender-tag network-name-out network-logo-out]
:as activity-data}]
[quo/wallet-activity
(assoc (common-activity-props activity-data)
:first-tag (token-tag {:token symbol-out :amount amount-out})
:second-tag-prefix :t/in
:second-tag sender-tag
:third-tag-prefix :t/for
:third-tag spender-tag
:fourth-tag-prefix :t/on
:fourth-tag (network-tag {:network-name network-name-out
:network-logo network-logo-out}))])

View File

@ -1,6 +1,5 @@
(ns status-im.contexts.wallet.common.activity-tab.constants)
(def ^:const wallet-activity-error-code-success 1)
(def ^:const wallet-activity-error-code-task-canceled 2)
(def ^:const wallet-activity-error-code-failed 3)
@ -12,6 +11,7 @@
(def ^:const wallet-activity-type-bridge 4)
(def ^:const wallet-activity-type-contract-deployment 5)
(def ^:const wallet-activity-type-mint 6)
(def ^:const wallet-activity-type-approval 7)
(def ^:const wallet-activity-status-failed 0)
(def ^:const wallet-activity-status-pending 1)
@ -62,4 +62,3 @@
wallet-activity-type-buy :t/via
wallet-activity-type-swap :t/via
wallet-activity-type-bridge :t/in})

View File

@ -1,70 +1,126 @@
(ns status-im.contexts.wallet.common.activity-tab.events
(:require [camel-snake-kebab.extras :as cske]
[utils.collection :as collection]
[utils.ethereum.chain :as chain]
[utils.re-frame :as rf]
[utils.transforms :as transforms]))
(defonce ^:private request-id-atom (atom 0))
(def ^:const limit-per-request 20)
(defn- get-unique-request-id
(defn- nested-merge
[& maps]
(apply merge-with merge maps))
(defn- create-default-filters
[]
(swap! request-id-atom inc)
@request-id-atom)
{:period {:startTimestamp 0
:endTimestamp 0}
:types []
:statuses []
:counterpartyAddresses []
:assets []
:collectibles []
:filterOutAssets false
:filterOutCollectibles false})
(rf/reg-event-fx
:wallet/store-session-id-for-activity-filter-session
(fn [{:keys [db]} [request-id]]
{:db (assoc-in db [:wallet :ui :activity-tab :request :request-id] request-id)}))
(rf/reg-event-fx
:wallet/fetch-activities-for-current-account
(fn [{:keys [db]}]
(let [address (-> db :wallet :current-viewing-account-address)
chain-ids (chain/chain-ids db)
request-id (get-unique-request-id)
filters {:period {:startTimestamp 0
:endTimestamp 0}
:types []
:statuses []
:counterpartyAddresses []
:assets []
:collectibles []
:filterOutAssets false
:filterOutCollectibles false}
offset 0
limit 50
request-params [request-id [address] chain-ids filters offset limit]]
{:db (assoc-in db [:wallet :ui :activity-tab :request request-id] address)
(let [address (get-in db [:wallet :current-viewing-account-address])
chain-ids (chain/chain-ids db)
params [[address] chain-ids (create-default-filters) limit-per-request]]
{:db (-> db
(update-in [:wallet :activities] dissoc address)
(update-in [:wallet :ui :activity-tab :request] dissoc :request-id)
(update-in [:wallet :ui :activity-tab :request]
assoc
:address address
:loading? true
:initial-request? true))
:fx [[:json-rpc/call
[{;; This method is deprecated and will be replaced by
;; "wallet_startActivityFilterSession"
;; https://github.com/status-im/status-mobile/issues/19864
:method "wallet_filterActivityAsync"
:params request-params
:on-error [:wallet/log-rpc-error
{:event :wallet/fetch-activities-for-current-account
:params request-params}]}]]]})))
[{:method "wallet_startActivityFilterSessionV2"
:params params
:on-success [:wallet/store-session-id-for-activity-filter-session]
:on-error [:wallet/log-rpc-error
{:event :wallet/fetch-activities-for-current-account
:params params}]}]]]})))
(def ^:private activity-transaction-id (comp hash :transaction))
(rf/reg-event-fx
:wallet/stop-activity-filter-session
(fn [{:keys [db]}]
(when-let [session-id (get-in db [:wallet :ui :activity-tab :request :request-id])]
{:db (update-in db [:wallet :ui :activity-tab] dissoc :request)
:fx [[:json-rpc/call
[{:method "wallet_stopActivityFilterSession"
:params [session-id]
:on-error [:wallet/log-rpc-error
{:event :wallet/stop-activity-filter-session
:params [session-id]}]}]]]})))
(rf/reg-event-fx
:wallet/get-more-for-activities-filter-session
(fn [{:keys [db]}]
(let [session-id (get-in db [:wallet :ui :activity-tab :request :request-id])
has-more? (get-in db [:wallet :ui :activity-tab :request :has-more?])
params [session-id limit-per-request]]
(when (and session-id has-more?)
{:fx [[:json-rpc/call
[{:method "wallet_getMoreForActivityFilterSession"
:params params
:on-error [:wallet/log-rpc-error
{:event :wallet/get-more-for-activities-filter-session
:params params}]}]]]}))))
(rf/reg-event-fx
:wallet/reset-activities-filter-session
(fn [{:keys [db]}]
(when-let [session-id (get-in db [:wallet :ui :activity-tab :request :request-id])]
{:db (assoc-in db [:wallet :ui :activity-tab :request :initial-request?] true)
:fx [[:json-rpc/call
[{:method "wallet_resetActivityFilterSession"
:params [session-id limit-per-request]
:on-error [:wallet/log-rpc-error
{:event :wallet/reset-activities-filter-session
:params [session-id limit-per-request]}]}]]]})))
(rf/reg-event-fx
:wallet/activity-filtering-for-current-account-done
(fn [{:keys [db]} [{:keys [message requestId]}]]
(let [address (get-in db [:wallet :ui :activity-tab :request requestId])
activities (->> message
(transforms/json->clj)
(:activities)
(cske/transform-keys transforms/->kebab-case-keyword))
activities-indexed (zipmap (map activity-transaction-id activities)
activities)]
{:db (assoc-in db [:wallet :activities address] activities-indexed)})))
(def ^:private nested-merge (partial merge-with merge))
(fn [{:keys [db]} [{:keys [message]}]]
(let [{:keys [address initial-request?]} (get-in db [:wallet :ui :activity-tab :request])
{:keys [activities offset hasMore]} (transforms/json->clj message)
new-activities (->> activities
(cske/transform-keys transforms/->kebab-case-keyword)
(collection/index-by :key))
existing-activities (get-in db [:wallet :activities address])
updated-activities (if initial-request?
new-activities
(nested-merge existing-activities new-activities))]
{:db (-> db
(assoc-in [:wallet :activities address] updated-activities)
(assoc-in [:wallet :ui :activity-tab :request :offset] offset)
(assoc-in [:wallet :ui :activity-tab :request :has-more?] hasMore)
(assoc-in [:wallet :ui :activity-tab :request :loading?] false)
(assoc-in [:wallet :ui :activity-tab :request :initial-request?] false))})))
(rf/reg-event-fx
:wallet/activities-filtering-entries-updated
(fn [{:keys [db]} [{:keys [message requestId]}]]
(let [address (get-in db [:wallet :ui :activity-tab :request requestId])
activities (->> message
(transforms/json->clj)
(cske/transform-keys transforms/->kebab-case-keyword))
activities-indexed (zipmap (map activity-transaction-id activities)
activities)]
{:db (-> db
(update-in [:wallet :ui :activity-tab :request] dissoc requestId)
(update-in [:wallet :activities address] nested-merge activities-indexed))})))
(when (= requestId (get-in db [:wallet :ui :activity-tab :request :request-id]))
(let [address (get-in db [:wallet :ui :activity-tab :request :address])
updated-activities (->> message
transforms/json->clj
(cske/transform-keys transforms/->kebab-case-keyword)
(collection/index-by :key))]
{:db (update-in db [:wallet :activities address] nested-merge updated-activities)}))))
(rf/reg-event-fx
:wallet/activities-session-updated
(fn [_ [{:keys [message]}]]
(let [{:keys [hasNewOnTop]} (transforms/json->clj message)]
(when hasNewOnTop
{:fx [[:dispatch [:wallet/reset-activities-filter-session]]]}))))

View File

@ -0,0 +1,49 @@
(ns status-im.contexts.wallet.common.activity-tab.options.view
(:require [quo.core :as quo]
[react-native.clipboard :as clipboard]
[react-native.core :as rn]
[status-im.contexts.wallet.common.utils.external-links :as external-links]
[status-im.contexts.wallet.common.utils.networks :as network-utils]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
[{:keys [chain-id]
tx-hash :hash}]
(let [{:keys [view-on-block-explorer-label
link-to-block-explorer-label]} (network-utils/get-network-details chain-id)
tx-details-link-on-block-explorer (external-links/get-link-to-tx-details chain-id tx-hash)
open-tx-on-block-explorer (rn/use-callback
#(rf/dispatch [:browser.ui/open-url
tx-details-link-on-block-explorer])
[tx-details-link-on-block-explorer])
copy-tx-hash-to-clipboard (rn/use-callback
(fn []
(clipboard/set-string tx-hash)
(rf/dispatch
[:toasts/upsert
{:type :positive
:text
(i18n/label
:t/transaction-hash-copied-to-clipboard)}]))
[tx-hash])
share-link-to-block-explorer (rn/use-callback
#(rf/dispatch [:open-share
{:options
{:message
tx-details-link-on-block-explorer}}])
[tx-details-link-on-block-explorer])]
[quo/action-drawer
[[{:icon :i/link
:accessibility-label :view-on-block-explorer
:on-press open-tx-on-block-explorer
:label (i18n/label view-on-block-explorer-label)
:right-icon :i/external}]
[{:icon :i/copy
:accessibility-label :copy-transaction-hash
:label (i18n/label :t/copy-transaction-hash)
:on-press copy-tx-hash-to-clipboard}]
[{:icon :i/share
:accessibility-label :share-link-to-block-explorer
:label (i18n/label link-to-block-explorer-label)
:on-press share-link-to-block-explorer}]]]))

View File

@ -1,157 +1,49 @@
(ns status-im.contexts.wallet.common.activity-tab.view
(:require
[clojure.string :as string]
[quo.core :as quo]
[quo.theme]
[react-native.core :as rn]
[status-im.common.resources :as resources]
[status-im.constants :as constants]
[status-im.contexts.shell.constants :as shell.constants]
[status-im.contexts.wallet.common.activity-tab.activity-types.view :as activity-type]
[status-im.contexts.wallet.common.empty-tab.view :as empty-tab]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn send-and-receive-activity
[{:keys [transaction relative-date status sender recipient token amount network-name
network-logo token-id nft-url nft-name]}]
(if token-id
[quo/wallet-activity
{:transaction transaction
:timestamp relative-date
:status status
:counter 1
:first-tag {:size 24
:type :collectible
:collectible nft-url
:collectible-name (if (> amount 1)
(str amount " " nft-name)
nft-name)
:collectible-number (when (not= token-id "0")
token-id)}
:second-tag-prefix :t/from
:second-tag {:type :address :address sender}
:third-tag-prefix :t/to
:third-tag {:type :address :address recipient}
:fourth-tag-prefix :t/via
:fourth-tag {:size 24
:type :network
:network-name network-name
:network-logo network-logo}
:blur? false}]
[quo/wallet-activity
{:transaction transaction
:timestamp relative-date
:status status
:counter 1
:first-tag {:size 24
:type :token
:token token
:amount amount}
:second-tag-prefix :t/from
:second-tag {:type :address :address sender}
:third-tag-prefix :t/to
:third-tag {:type :address :address recipient}
:fourth-tag-prefix :t/via
:fourth-tag {:size 24
:type :network
:network-name network-name
:network-logo network-logo}
:blur? false}]))
;; WIP to add the mint activity.
;(defn mint-activity
; [{:keys [transaction relative-date status recipient network-name
; network-logo nft-name nft-url token-id]}]
; [quo/wallet-activity
; {:transaction transaction
; :timestamp relative-date
; :status status
; :counter 1
; :first-tag {:size 24
; :type :collectible
; :collectible nft-url
; :collectible-name nft-name
; :collectible-number token-id}
; :second-tag-prefix :t/at
; :second-tag {:type :address :address recipient}
; :third-tag-prefix :t/to
; :third-tag {:type :address :address recipient}
; :fourth-tag-prefix :t/via
; :fourth-tag {:size 24
; :type :network
; :network-name network-name
; :network-logo network-logo}
; :blur? false}])
(defn- section-header
[{:keys [title]}]
[quo/divider-date title])
(defn activity-item
[{:keys [transaction] :as activity}]
(case transaction
(:send :receive) [send-and-receive-activity activity]
;; WIP to add the mint activity.
;; :mint [mint-activity activity]
(defn- activity-item
[{:keys [tx-type] :as activity}]
(case tx-type
:send [activity-type/send-activity activity]
:bridge [activity-type/bridge-activity activity]
:swap [activity-type/swap-activity activity]
:approval [activity-type/approval-activity activity]
nil))
(defn- pressable-text
[{:keys [on-press text]}]
[rn/text
{:style {:text-decoration-line :underline}
:on-press on-press}
text])
(defn view
[]
(let [theme (quo.theme/use-theme)
address (rf/sub [:wallet/current-viewing-account-address])
activity-list (rf/sub [:wallet/activities-for-current-viewing-account])
open-eth-chain-explorer (rn/use-callback
#(rf/dispatch [:wallet/navigate-to-chain-explorer
{:address address
:network constants/mainnet-network-name}])
[address])
open-oeth-chain-explorer (rn/use-callback
#(rf/dispatch [:wallet/navigate-to-chain-explorer
{:address address
:network constants/optimism-network-name}])
[address])
open-arb-chain-explorer (rn/use-callback
#(rf/dispatch [:wallet/navigate-to-chain-explorer
{:address address
:network constants/arbitrum-network-name}])
[address])]
[:<>
[quo/information-box
{:type :informative
:icon :i/info
:closable? false
:style {:margin-horizontal 20 :margin-vertical 8}}
[:<>
(str (i18n/label :t/wallet-activity-beta-message) " ")
[pressable-text
{:on-press open-eth-chain-explorer
:text (i18n/label :t/etherscan)}]
", "
[pressable-text
{:on-press open-oeth-chain-explorer
:text (i18n/label :t/op-explorer)}]
(str ", " (string/lower-case (i18n/label :t/or)) " ")
[pressable-text
{:on-press open-arb-chain-explorer
:text (i18n/label :t/arbiscan)}]
"."]]
(if (empty? activity-list)
[empty-tab/view
{:title (i18n/label :t/no-activity)
:description (i18n/label :t/empty-tab-description)
:image (resources/get-themed-image :no-activity theme)}]
[rn/section-list
{:sections activity-list
:sticky-section-headers-enabled false
:style {:flex 1
(let [theme (quo.theme/use-theme)
loading? (rf/sub [:wallet/activity-tab-loading?])
activity-list (rf/sub [:wallet/activities-for-current-viewing-account])
on-end-reached (rn/use-callback
#(rf/dispatch
[:wallet/get-more-for-activities-filter-session]))]
(if (and (and (some? loading?) (false? loading?)) (empty? activity-list))
[empty-tab/view
{:title (i18n/label :t/no-activity)
:description (i18n/label :t/empty-tab-description)
:image (resources/get-themed-image :no-activity theme)}]
[rn/section-list
{:sections activity-list
:sticky-section-headers-enabled false
:style {:flex 1
:padding-horizontal 8}
:content-container-style {:padding-bottom shell.constants/floating-shell-button-height}
:render-fn activity-item
:render-section-header-fn section-header}])]))
:content-container-style {:padding-bottom shell.constants/floating-shell-button-height}
:shows-vertical-scroll-indicator false
:render-fn activity-item
:render-section-header-fn section-header
:on-end-reached on-end-reached
:on-end-reached-threshold 2}])))

View File

@ -24,3 +24,30 @@
config/optimism-sepolia-chain-explorer-link
:else config/mainnet-chain-explorer-link))
(defn get-base-url-for-tx-details-by-chain-id
[chain-id]
(condp = chain-id
constants/ethereum-mainnet-chain-id
config/mainnet-tx-details-base-link
constants/arbitrum-mainnet-chain-id
config/arbitrum-mainnet-tx-details-base-link
constants/optimism-mainnet-chain-id
config/optimism-mainnet-tx-details-base-link
constants/ethereum-sepolia-chain-id
config/mainnet-sepolia-tx-details-base-link
constants/arbitrum-sepolia-chain-id
config/arbitrum-sepolia-tx-details-base-link
constants/optimism-sepolia-chain-id
config/optimism-sepolia-tx-details-base-link
config/mainnet-tx-details-base-link))
(defn get-link-to-tx-details
[chain-id tx-hash]
(str (get-base-url-for-tx-details-by-chain-id chain-id) "/" tx-hash))

View File

@ -139,25 +139,31 @@
[(butlast $) (last $)]))
(def mainnet-network-details
{:source (resources/get-network constants/mainnet-network-name)
:short-name constants/mainnet-short-name
:full-name constants/mainnet-full-name
:network-name constants/mainnet-network-name
:abbreviated-name constants/mainnet-abbreviated-name})
{:source (resources/get-network constants/mainnet-network-name)
:short-name constants/mainnet-short-name
:full-name constants/mainnet-full-name
:network-name constants/mainnet-network-name
:abbreviated-name constants/mainnet-abbreviated-name
:view-on-block-explorer-label :t/view-on-eth
:link-to-block-explorer-label :t/share-link-to-eth})
(def arbitrum-network-details
{:source (resources/get-network constants/arbitrum-network-name)
:short-name constants/arbitrum-short-name
:full-name constants/arbitrum-full-name
:network-name constants/arbitrum-network-name
:abbreviated-name constants/arbitrum-abbreviated-name})
{:source (resources/get-network constants/arbitrum-network-name)
:short-name constants/arbitrum-short-name
:full-name constants/arbitrum-full-name
:network-name constants/arbitrum-network-name
:abbreviated-name constants/arbitrum-abbreviated-name
:view-on-block-explorer-label :t/view-on-arb
:link-to-block-explorer-label :t/share-link-to-arb})
(def optimism-network-details
{:source (resources/get-network constants/optimism-network-name)
:short-name constants/optimism-short-name
:full-name constants/optimism-full-name
:network-name constants/optimism-network-name
:abbreviated-name constants/optimism-abbreviated-name})
{:source (resources/get-network constants/optimism-network-name)
:short-name constants/optimism-short-name
:full-name constants/optimism-full-name
:network-name constants/optimism-network-name
:abbreviated-name constants/optimism-abbreviated-name
:view-on-block-explorer-label :t/view-on-oeth
:link-to-block-explorer-label :t/share-link-to-oeth})
(defn get-network-details
[chain-id]

View File

@ -19,7 +19,6 @@
[status-im.feature-flags :as ff]
[taoensso.timbre :as log]
[utils.collection]
[utils.ethereum.chain :as chain]
[utils.ethereum.eip.eip55 :as eip55]
[utils.i18n :as i18n]
[utils.number]
@ -40,14 +39,12 @@
(rf/reg-event-fx :wallet/navigate-to-account
(fn [{:keys [db]} [address]]
{:db (assoc-in db [:wallet :current-viewing-account-address] address)
:fx [[:dispatch [:navigate-to :screen/wallet.accounts address]]
[:dispatch [:wallet/fetch-activities-for-current-account]]]}))
:fx [[:dispatch [:navigate-to :screen/wallet.accounts address]]]}))
(rf/reg-event-fx :wallet/navigate-to-account-within-stack
(fn [{:keys [db]} [address]]
{:db (assoc-in db [:wallet :current-viewing-account-address] address)
:fx [[:dispatch [:navigate-to-within-stack [:screen/wallet.accounts :shell-stack] address]]
[:dispatch [:wallet/fetch-activities-for-current-account]]]}))
:fx [[:dispatch [:navigate-to-within-stack [:screen/wallet.accounts :shell-stack] address]]]}))
(rf/reg-event-fx :wallet/navigate-to-new-account
(fn [{:keys [db]} [address]]
@ -58,7 +55,11 @@
(rf/reg-event-fx :wallet/select-account-tab
(fn [{:keys [db]} [tab]]
{:db (assoc-in db [:wallet :ui :account-page :active-tab] tab)}))
(let [activity-tab-selected? (= tab :activity)]
{:db (assoc-in db [:wallet :ui :account-page :active-tab] tab)
:fx [(if activity-tab-selected?
[:dispatch [:wallet/fetch-activities-for-current-account]]
[:dispatch [:wallet/stop-activity-filter-session]])]})))
(rf/reg-event-fx :wallet/select-home-tab
(fn [{:keys [db]} [tab]]
@ -66,7 +67,8 @@
(rf/reg-event-fx :wallet/clear-account-tab
(fn [{:keys [db]}]
{:db (assoc-in db [:wallet :ui :account-page :active-tab] nil)}))
{:db (assoc-in db [:wallet :ui :account-page :active-tab] nil)
:fx [[:dispatch [:wallet/stop-activity-filter-session]]]}))
(rf/reg-event-fx :wallet/switch-current-viewing-account
(fn [{:keys [db]} [address]]
@ -94,7 +96,8 @@
(let [just-completed-transaction? (get-in db [:wallet :ui :send :just-completed-transaction?])]
{:db (update db :wallet dissoc :current-viewing-account-address)
:fx [(when-not just-completed-transaction?
[:dispatch [:wallet/clear-account-tab]])]})))
[:dispatch [:wallet/clear-account-tab]])
[:dispatch [:wallet/stop-activity-filter-session]]]})))
(defn log-rpc-error
[_ [{:keys [event params]} error]]
@ -106,15 +109,13 @@
(def refresh-accounts-fx-dispatches
[[:dispatch [:wallet/get-wallet-token-for-all-accounts]]
[:dispatch [:wallet/request-collectibles-for-all-accounts {:new-request? true}]]
[:dispatch [:wallet/check-recent-history-for-all-accounts]]])
[:dispatch [:wallet/request-collectibles-for-all-accounts {:new-request? true}]]])
(rf/reg-event-fx
:wallet/fetch-assets-for-address
(fn [_ [address]]
{:fx [[:dispatch [:wallet/get-wallet-token-for-account address]]
[:dispatch [:wallet/request-collectibles-for-account address]]
[:dispatch [:wallet/check-recent-history-for-account address]]]}))
[:dispatch [:wallet/request-collectibles-for-account address]]]}))
(defn- reconcile-accounts
[db-accounts-by-address new-accounts]
@ -491,25 +492,6 @@
[{:method "wallet_startWallet"
:on-error [:wallet/log-rpc-error {:event :wallet/start-wallet}]}]]]}))
(rf/reg-event-fx :wallet/check-recent-history-for-all-accounts
(fn [{:keys [db]}]
{:fx (->> (get-in db [:wallet :accounts])
vals
(mapv (fn [{:keys [address]}]
[:dispatch [:wallet/check-recent-history-for-account address]])))}))
(rf/reg-event-fx
:wallet/check-recent-history-for-account
(fn [{:keys [db]} [address]]
(let [chain-ids (chain/chain-ids db)
params [chain-ids [address]]]
{:fx [[:json-rpc/call
[{:method "wallet_checkRecentHistoryForChainIDs"
:params params
:on-error [:wallet/log-rpc-error
{:event :wallet/check-recent-history-for-account
:params params}]}]]]})))
(rf/reg-event-fx :wallet/initialize
(fn []
{:fx [[:dispatch [:wallet/start-wallet]]

View File

@ -131,18 +131,6 @@
address]}]]]}]
(is (match? expected-effects (dispatch [event-id address])))))
(h/deftest-event :wallet/check-recent-history-for-all-accounts
[event-id dispatch]
(testing "check recent history for all accounts"
(let [address-1 "0x1"
address-2 "0x2"
expected-fx [[:dispatch [:wallet/check-recent-history-for-account address-1]]
[:dispatch [:wallet/check-recent-history-for-account address-2]]]]
(reset! rf-db/app-db
{:wallet {:accounts {address-1 {:address address-1}
address-2 {:address address-2}}}})
(is (match? expected-fx (:fx (dispatch [event-id])))))))
(h/deftest-event :wallet/reconcile-keypairs
[event-id dispatch]
(let [keypair-key-uid (:key-uid raw-account)]

View File

@ -623,7 +623,6 @@
(fn [{:keys [db]}]
(let [address (get-in db [:wallet :current-viewing-account-address])]
{:fx [[:dispatch [:wallet/navigate-to-account-within-stack address]]
[:dispatch [:wallet/fetch-activities-for-current-account]]
[:dispatch [:wallet/select-account-tab :activity]]
[:dispatch-later
[{:ms 20

View File

@ -107,4 +107,9 @@
[:wallet/activities-filtering-entries-updated
(transforms/js->clj event-js)]]]}
"wallet-activity-session-updated"
{:fx [[:dispatch
[:wallet/activities-session-updated
(transforms/js->clj event-js)]]]}
(log/debug ::unknown-wallet-event :type event-type)))))

View File

@ -561,7 +561,6 @@
(fn [{:keys [db]}]
(let [address (get-in db [:wallet :current-viewing-account-address])]
{:fx [[:dispatch [:wallet/navigate-to-account-within-stack address]]
[:dispatch [:wallet/fetch-activities-for-current-account]]
[:dispatch [:wallet/select-account-tab :activity]]
[:dispatch-later
[{:ms 20

View File

@ -1,119 +1,165 @@
(ns status-im.subs.wallet.activities
(:require
[native-module.core :as native-module]
[quo.foundations.resources :as quo.resources]
[quo.foundations.resources]
[quo.foundations.resources :as resources]
[re-frame.core :as rf]
[status-im.contexts.wallet.common.activity-tab.constants :as constants]
[status-im.constants :as constants]
[status-im.contexts.wallet.common.activity-tab.constants :as activity-constants]
[utils.collection :as collection]
[utils.datetime :as datetime]
[utils.hex :as utils.hex]
[utils.hex :as hex]
[utils.money :as money]))
(def precision 6)
(def ^:private precision 6)
(defn- hex->number
[hex-str]
(-> hex-str
hex/normalize-hex
money/from-hex
str))
(defn- hex->amount
[hex-str]
(-> hex-str
hex->number
money/wei->ether
(money/with-precision precision)
str))
(defn- get-token-amount
[{:keys [token-type]} amount]
(if (#{activity-constants/wallet-activity-token-type-erc-721
activity-constants/wallet-activity-token-type-erc-1155}
token-type)
(hex->number amount)
(hex->amount amount)))
(defn- get-address-context-tag
[accounts-and-saved-addresses address]
(let [{:keys [context-type name emoji color customization-color]}
(get accounts-and-saved-addresses address)]
(case context-type
:account
{:type :account
:account-name name
:emoji emoji
:customization-color color}
:saved-address
{:type :default
:full-name name
:customization-color customization-color}
{:type :address :address address})))
(defn- get-spender-context-tag
[address]
(let [swap-providers (->> constants/swap-providers
vals
(collection/index-by :contract-address))
{:keys [full-name name] :as spender} (get swap-providers address)]
(if spender
{:type :network
:network-name full-name
:network-logo (resources/get-network name)}
{:type :address :address address})))
(defn- process-base-activity
[{:keys [timestamp sender recipient token-in token-out chain-id-in chain-id-out activity-status]
:as activity}
{:keys [chain-id->network-details accounts-and-saved-addresses]}]
(let [token-id (some-> (or token-in token-out)
:token-id
hex->number)
network-in (chain-id->network-details chain-id-in)
network-out (chain-id->network-details chain-id-out)]
(assoc activity
:relative-date (datetime/timestamp->relative (* timestamp 1000))
:sender-tag (get-address-context-tag accounts-and-saved-addresses sender)
:recipient-tag (get-address-context-tag accounts-and-saved-addresses recipient)
:network-name-in (:full-name network-in)
:network-logo-in (resources/get-network (:network-name network-in))
:network-name-out (:full-name network-out)
:network-logo-out (resources/get-network (:network-name network-out))
:status (activity-constants/wallet-activity-status->name activity-status)
:token-id token-id)))
(defn- process-activity
[{:keys [activity-type token-out amount-out token-in amount-in approval-spender] :as activity} context]
(let [base-activity (process-base-activity activity context)]
(condp = activity-type
activity-constants/wallet-activity-type-send
(assoc base-activity
:tx-type :send
:amount-out (get-token-amount token-out amount-out))
activity-constants/wallet-activity-type-bridge
(assoc base-activity
:tx-type :bridge
:amount-out (get-token-amount token-out amount-out))
activity-constants/wallet-activity-type-swap
(assoc base-activity
:tx-type :swap
:amount-in (get-token-amount token-in amount-in)
:amount-out (get-token-amount token-out amount-out))
activity-constants/wallet-activity-type-approval
(assoc base-activity
:tx-type :approval
:amount-out (get-token-amount token-out amount-out)
:spender-tag (get-spender-context-tag approval-spender))
nil)))
(rf/reg-sub
:wallet/all-activities
:<- [:wallet]
:-> :activities)
(defn- hex-wei->amount
[hex-str-amount]
(-> hex-str-amount
(utils.hex/normalize-hex)
(native-module/hex-to-number)
(money/wei->ether)
(money/with-precision precision)
(str)))
(rf/reg-sub
:wallet/activity-tab
:<- [:wallet/ui]
:-> :activity-tab)
(defn- hex-string->number
[hex-str-amount]
(-> hex-str-amount
(utils.hex/normalize-hex)
(native-module/hex-to-number)
(str)))
(rf/reg-sub
:wallet/activity-tab-request
:<- [:wallet/activity-tab]
:-> :request)
(defn- get-token-amount
[token amount]
(let [token-type (:token-type token)]
(if (#{constants/wallet-activity-token-type-erc-721
constants/wallet-activity-token-type-erc-1155}
token-type)
(hex-string->number amount)
(hex-wei->amount amount))))
(rf/reg-sub
:wallet/activity-tab-loading?
:<- [:wallet/activity-tab-request]
:-> :loading?)
(defn- process-send-activity
[{:keys [symbol-out amount-out token-out]
:as data}]
(assoc data
:transaction :send
:token symbol-out
:amount (get-token-amount token-out amount-out)))
(defn- process-receive-activity
[{:keys [symbol-in amount-in token-in] :as data}]
(assoc data
:transaction :receive
:token symbol-in
:amount (get-token-amount token-in amount-in)))
;; WIP to add the mint activity.
;(defn- process-mint-activity
; [{:keys [token-in symbol-in amount-in chain-id-in nft-name] :as data}
; chain-id->network-name]
; (-> data
; (merge activity)
; (assoc :transaction :mint
; ;:token symbol-in
; ;:amount (activity-amount amount-in)
; :nft-name (normalize-nft-name token-id nft-name))))
(defn- process-activity-by-type
[chain-id->network-name
{:keys [activity-type activity-status timestamp sender recipient token-in token-out
chain-id-in chain-id-out]
:as data}]
(let [network-name (chain-id->network-name (or chain-id-in chain-id-out))
token-id (some-> (or token-in token-out)
:token-id
hex-string->number)
activity (assoc data
:relative-date (datetime/timestamp->relative (* timestamp 1000))
:sender sender
:recipient recipient
:timestamp timestamp
:network-name network-name
:token-id token-id
:status (constants/wallet-activity-status->name activity-status)
:network-logo (quo.resources/get-network network-name))]
(condp = activity-type
constants/wallet-activity-type-send
(process-send-activity activity)
constants/wallet-activity-type-receive
(process-receive-activity activity)
;; WIP to add the mint activity. Constants/wallet-activity-type-mint
;; (process-mint-activity activity chain-id->network-name)
nil)))
(rf/reg-sub
:wallet/accounts-and-saved-addresses
:<- [:wallet/accounts-without-assets]
:<- [:wallet/saved-addresses-by-network-mode]
(fn [[accounts saved-addresses]]
(merge
(collection/index-by :address (map #(assoc % :context-type :account) accounts))
(collection/index-by :address
(map #(assoc % :context-type :saved-address) (vals saved-addresses))))))
(rf/reg-sub
:wallet/activities-for-current-viewing-account
:<- [:wallet/all-activities]
:<- [:wallet/current-viewing-account-address]
:<- [:wallet/network-details]
(fn [[activities current-viewing-account-address network-details]]
(let [chain-id->network-name (update-vals (group-by :chain-id network-details)
(comp :network-name first))
address-activities (->> (get activities current-viewing-account-address)
(vals)
(sort-by :timestamp))]
(->> address-activities
(keep #(process-activity-by-type chain-id->network-name %))
(group-by (fn [{:keys [timestamp]}]
(datetime/timestamp->relative-short-date (* timestamp 1000))))
:<- [:wallet/accounts-and-saved-addresses]
(fn [[activities current-address network-details accounts-and-saved-addresses]]
(let [context {:chain-id->network-details (collection/index-by :chain-id network-details)
:accounts-and-saved-addresses accounts-and-saved-addresses}
activities (->> (get activities current-address)
vals
(keep #(process-activity % context))
(sort-by :timestamp >)
(group-by #(datetime/timestamp->relative-short-date
(* (:timestamp %) 1000))))]
(->> activities
(map (fn [[date activities]]
{:title date
:data (reverse activities)
:data activities
:timestamp (:timestamp (first activities))}))
(sort-by (fn [{:keys [timestamp]}] (- timestamp)))))))
(sort-by :timestamp >)))))

View File

@ -29,18 +29,14 @@
:sender "acc1"
:recipient "acc2"
:timestamp 1588291200}
2 {:activity-type constants/wallet-activity-type-receive
:amount-in "0x1"
:sender "acc2"
:recipient "acc1"
:timestamp 1588377600}
3 {:activity-type constants/wallet-activity-type-send
3 {:activity-type constants/wallet-activity-type-bridge
:amount-out "0x1"
:sender "acc1"
:recipient "acc4"
:timestamp 1588464000}
4 {:activity-type constants/wallet-activity-type-send
4 {:activity-type constants/wallet-activity-type-swap
:amount-out "0x1"
:amount-in "0x1"
:sender "acc1"
:recipient "acc4"
:timestamp 1588464100}
@ -58,59 +54,43 @@
(is
(match?
[{:title "May 3, 2020"
:timestamp 1588464000
:data [{:relative-date "May 3, 2020"
:amount "0"
:network-logo nil
:recipient "acc4"
:transaction :send
:token nil
:network-name nil
:status nil
:sender "acc1"
:timestamp 1588464100}
{:relative-date "May 3, 2020"
:amount "0"
:network-logo nil
:recipient "acc4"
:transaction :send
:token nil
:network-name nil
:status nil
:sender "acc1"
:timestamp 1588464050}
{:relative-date "May 3, 2020"
:amount "0"
:network-logo nil
:recipient "acc4"
:transaction :send
:token nil
:network-name nil
:status nil
:sender "acc1"
:timestamp 1588464000}]}
{:title "May 2, 2020"
:timestamp 1588377600
:data [{:relative-date "May 2, 2020"
:amount "0"
:network-logo nil
:recipient "acc1"
:transaction :receive
:token nil
:network-name nil
:status nil
:sender "acc2"
:timestamp 1588377600}]}
:timestamp 1588464100
:data [{:relative-date "May 3, 2020"
:amount-out "0"
:network-logo-out nil
:recipient "acc4"
:tx-type :swap
:network-name-out nil
:status nil
:sender "acc1"
:timestamp 1588464100}
{:relative-date "May 3, 2020"
:amount-out "0"
:network-logo-out nil
:recipient "acc4"
:tx-type :send
:network-name-out nil
:status nil
:sender "acc1"
:timestamp 1588464050}
{:relative-date "May 3, 2020"
:amount-out "0"
:network-logo-out nil
:recipient "acc4"
:tx-type :bridge
:network-name-out nil
:status nil
:sender "acc1"
:timestamp 1588464000}]}
{:title "May 1, 2020"
:timestamp 1588291200
:data [{:relative-date "May 1, 2020"
:amount "0"
:network-logo nil
:recipient "acc2"
:transaction :send
:token nil
:network-name nil
:status nil
:sender "acc1"
:timestamp 1588291200}]}]
:data [{:relative-date "May 1, 2020"
:amount-out "0"
:network-logo-out nil
:recipient "acc2"
:tx-type :send
:network-name-out nil
:status nil
:sender "acc1"
:timestamp 1588291200}]}]
(rf/sub [sub-name])))))

View File

@ -44,24 +44,30 @@
(testing "returns data with prod"
(swap! rf-db/app-db assoc-in [:wallet :networks] network-data)
(is
(= [{:network-name :mainnet
:short-name "eth"
:chain-id 1
:abbreviated-name "Eth."
:full-name "Mainnet"
:layer 1}
{:network-name :arbitrum
:short-name "arb1"
:abbreviated-name "Arb1."
:full-name "Arbitrum"
:chain-id 42161
:layer 2}
{:network-name :optimism
:short-name "oeth"
:abbreviated-name "Oeth."
:full-name "Optimism"
:chain-id 10
:layer 2}]
(= [{:network-name :mainnet
:short-name "eth"
:chain-id 1
:abbreviated-name "Eth."
:full-name "Mainnet"
:layer 1
:view-on-block-explorer-label :t/view-on-eth
:link-to-block-explorer-label :t/share-link-to-eth}
{:network-name :arbitrum
:short-name "arb1"
:abbreviated-name "Arb1."
:full-name "Arbitrum"
:chain-id 42161
:layer 2
:view-on-block-explorer-label :t/view-on-arb
:link-to-block-explorer-label :t/share-link-to-arb}
{:network-name :optimism
:short-name "oeth"
:abbreviated-name "Oeth."
:full-name "Optimism"
:chain-id 10
:layer 2
:view-on-block-explorer-label :t/view-on-oeth
:link-to-block-explorer-label :t/share-link-to-oeth}]
(map #(dissoc % :source :related-chain-id) (rf/sub [sub-name]))))))
(h/deftest-sub :wallet/network-details-by-network-name

View File

@ -373,6 +373,12 @@
(map #(assoc-network-preferences-names network-details % test-networks-enabled?))
(sort-by :position))))
(rf/reg-sub
:wallet/accounts-without-assets
:<- [:wallet/accounts]
(fn [accounts]
(map #(dissoc % :tokens :collectibles) accounts)))
(rf/reg-sub
:wallet/watch-only-accounts
:<- [:wallet/accounts]

View File

@ -3,7 +3,7 @@
"_comment": "Instead use: scripts/update-status-go.sh <rev>",
"owner": "status-im",
"repo": "status-go",
"version": "v6.1.0",
"commit-sha1": "c014fbfc1ec739aad2db951fdcff6182d4dbd919",
"src-sha256": "16im0nc8zri5z1wvzbh83331g02iy305m2lca4ljjgdmlaq6l2zx"
"version": "v6.2.0",
"commit-sha1": "896eb635029e025cc446d73768038fb6c3afe2fb",
"src-sha256": "08vpcxdhjh2szsarbnhqlgdnmn8i7zp11nk0j02gj3xjlzdn9yjy"
}

View File

@ -531,7 +531,7 @@
"copy-qr": "Copy code",
"copy-text": "Copy text",
"copy-to-clipboard": "Copy",
"copy-transaction-hash": "Copy transaction ID",
"copy-transaction-hash": "Copy transaction hash",
"correct-private-key": "Correct private key",
"cost-fee": "Cost/Fee",
"counter-9-plus": "9+",
@ -2326,6 +2326,7 @@
"set-dapp-access-permissions": "Set DApp access permissions",
"set-max": "Set max",
"set-nickname-toast": "You have renamed {{primary-name}} as {{nickname}}",
"set-spending-cap": "Set spending cap",
"set-spending-cap-of": "Set spending cap of",
"set-up-sync": "Set up sync",
"settings": "Settings",
@ -2347,6 +2348,9 @@
"share-image": "Share image",
"share-invite-link": "Share an invite link",
"share-link": "Share link",
"share-link-to-arb": "Share link to Arbiscan",
"share-link-to-eth": "Share link to Etherscan",
"share-link-to-oeth": "Share link to Optimism Explorer",
"share-logs": "Share logs",
"share-my-profile": "Share my profile",
"share-opensea-link": "Share OpenSea link",
@ -2594,6 +2598,7 @@
"transaction-details": "Transaction details",
"transaction-failed": "Transaction failed",
"transaction-fee": "Transaction fee",
"transaction-hash-copied-to-clipboard": "Transaction hash copied to clipboard",
"transaction-history": "Transaction history",
"transaction-request": "Transaction Request",
"transaction-sent": "Transaction sent",