Swaps: Get full list of supported tokens from backend (#21139)

* 20380 Fetch tokens

* Address updates

* Events update

* Events test
This commit is contained in:
Alexander 2024-09-12 08:38:03 +02:00 committed by GitHub
parent babcd96fb6
commit 2bf3e6ce13
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 570 additions and 61 deletions

View File

@ -2,6 +2,7 @@
(:require
[clojure.string :as string]
[native-module.core :as native-module]
[promesa.core :as promesa]
[re-frame.core :as re-frame]
[react-native.background-timer :as background-timer]
[taoensso.timbre :as log]
@ -89,6 +90,17 @@
(rf/dispatch (conj on-success result request-id))
(on-success result request-id)))))))))))
(defn call-async
"Helper to handle RPC calls to status-go as promises"
[method js-response? & args]
(promesa/create
(fn [p-resolve p-reject]
(call {:method method
:params args
:on-success p-resolve
:on-error p-reject
:js-response js-response?}))))
(re-frame/reg-fx
:json-rpc/call
(fn [params]

View File

@ -98,6 +98,8 @@
[:settings/get-currencies]
(when (ff/enabled? ::ff/wallet.wallet-connect)
[:dispatch [:wallet-connect/init]])
(when (ff/enabled? ::ff/wallet.swap)
[:dispatch [:wallet.tokens/get-token-list]])
(when notifications-enabled?
[:effects/push-notifications-enable])]})))

View File

@ -6,6 +6,7 @@
[s]
(and (not (string/blank? s))
(boolean (re-find constants/regx-ens s))))
(defn private-key?
[s]
(or (re-find constants/regx-private-key-hex s)

View File

@ -14,6 +14,7 @@
[status-im.contexts.wallet.data-store :as data-store]
[status-im.contexts.wallet.db :as db]
[status-im.contexts.wallet.item-types :as item-types]
[status-im.contexts.wallet.tokens.events]
[taoensso.timbre :as log]
[utils.collection]
[utils.ethereum.chain :as chain]

View File

@ -0,0 +1,42 @@
(ns status-im.contexts.wallet.tokens.data)
(defn- tokens-by-key
[{:keys [key-fn added-tokens source-name tokens chain-ids]}]
(reduce
(fn [acc {:keys [address chainId decimals name image verified] :as token}]
(if (some #{chainId} chain-ids)
(let [k (key-fn token)]
(assoc acc
k
{:key k
:name name
:symbol (:symbol token)
:sources (if-let [added-token (get added-tokens k)]
(conj (:sources added-token) source-name)
[source-name])
:chain-id chainId
:address address
:decimals decimals
:image image
:type (if (= name "native") :native :erc20)
:community-id (get-in token [:communityData :id])
:verified? verified}))
acc))
{}
tokens))
(defn tokens-by-address
[props]
(tokens-by-key (assoc props
:key-fn
(fn [{:keys [chainId address]}]
(str chainId "-" address)))))
(defn tokens-by-symbol
[props]
(tokens-by-key (assoc props
:key-fn
(fn [{:keys [address] :as token}]
(if (get-in token [:communityData :id])
address
(:symbol token))))))

View File

@ -0,0 +1,25 @@
(ns status-im.contexts.wallet.tokens.effects
(:require [promesa.core :as promesa]
[status-im.contexts.wallet.tokens.rpc :as rpc]
[utils.re-frame :as rf]))
(rf/reg-fx
:effects.wallet.tokens/fetch-market-values
(fn [{:keys [symbols currency on-success on-error]}]
(-> (rpc/fetch-market-values symbols currency)
(promesa/then (partial rf/call-continuation on-success))
(promesa/catch (partial rf/call-continuation on-error)))))
(rf/reg-fx
:effects.wallet.tokens/fetch-details
(fn [{:keys [symbols on-success on-error]}]
(-> (rpc/fetch-details symbols)
(promesa/then (partial rf/call-continuation on-success))
(promesa/catch (partial rf/call-continuation on-error)))))
(rf/reg-fx
:effects.wallet.tokens/fetch-prices
(fn [{:keys [symbols currencies on-success on-error]}]
(-> (rpc/fetch-prices symbols currencies)
(promesa/then (partial rf/call-continuation on-success))
(promesa/catch (partial rf/call-continuation on-error)))))

View File

@ -0,0 +1,144 @@
(ns status-im.contexts.wallet.tokens.events
(:require [re-frame.core :as rf]
[status-im.constants :as constants]
[status-im.contexts.wallet.tokens.data :as tokens-data]
[status-im.contexts.wallet.tokens.effects]
[taoensso.timbre :as log]
[utils.address]
[utils.ethereum.chain :as chain]))
(rf/reg-event-fx
:wallet.tokens/get-token-list
(fn [{:keys [db]}]
{:db (assoc-in db [:wallet :ui :loading :token-list] true)
:fx [[:json-rpc/call
[{:method "wallet_getTokenList"
:params []
:on-success [:wallet.tokens/store-token-list]
:on-error [:wallet.tokens/get-token-list-failed]}]]]}))
(defn store-token-list
[{:keys [db]} [{:keys [data]}]]
(let [chain-ids (chain/chain-ids db)
tokens (reduce (fn [{:keys [by-address by-symbol] :as data}
{:keys [name source version tokens]}]
(-> data
(update :sources
conj
{:name name
:source source
:version version
:tokens-count (count tokens)})
(update :by-address
merge
(tokens-data/tokens-by-address
{:added-tokens by-address
:source-name name
:tokens tokens
:chain-ids chain-ids}))
(update :by-symbol
merge
(tokens-data/tokens-by-symbol
{:added-tokens by-symbol
:source-name name
:tokens tokens
:chain-ids chain-ids}))))
{:sources []
:by-address {}
:by-symbol {}}
data)
symbols (->> tokens
:by-symbol
keys
(remove utils.address/address?))]
{:fx [[:effects.wallet.tokens/fetch-market-values
{:symbols symbols
:currency constants/profile-default-currency
:on-success [:wallet.tokens/store-market-values]
:on-error [:wallet.tokens/fetch-market-values-failed]}]
[:effects.wallet.tokens/fetch-details
{:symbols symbols
:on-success [:wallet.tokens/store-details]
:on-error [:wallet.tokens/fetch-details-failed]}]
[:effects.wallet.tokens/fetch-prices
{:symbols symbols
:currencies [constants/profile-default-currency]
:on-success [:wallet.tokens/store-prices]
:on-error [:wallet.tokens/fetch-prices-failed]}]]
:db (-> db
(assoc-in [:wallet :tokens]
{:sources (:sources tokens)
:by-address (-> tokens :by-address vals)
:by-symbol (-> tokens :by-symbol vals)})
(assoc-in [:wallet :ui :loading]
{:token-list false
:market-values true
:details true
:prices true}))}))
(rf/reg-event-fx :wallet.tokens/store-token-list store-token-list)
(rf/reg-event-fx
:wallet.tokens/get-token-list-failed
(fn [{:keys [db]} [error]]
(log/info "failed to get wallet tokens "
{:error error
:event :wallet.tokens/get-token-list})
{:db (assoc-in db [:wallet :ui :loading :token-list] false)}))
(rf/reg-event-fx
:wallet.tokens/store-market-values
(fn [{:keys [db]} [raw-data]]
(let [market-values (reduce (fn [acc [token data]]
(assoc acc
token
{:market-cap (:MKTCAP data)
:high-day (:HIGHDAY data)
:low-day (:LOWDAY data)
:change-pct-hour (:CHANGEPCTHOUR data)
:change-pct-day (:CHANGEPCTDAY data)
:change-pct-24h (:CHANGEPCT24HOUR data)
:change-24h (:CHANGE24HOUR data)}))
{}
raw-data)]
{:db (-> db
(assoc-in [:wallet :tokens :market-values-per-token] market-values)
(assoc-in [:wallet :ui :loading :market-values] false))})))
(rf/reg-event-fx
:wallet.tokens/fetch-market-values-failed
(fn [{:keys [db]} [error]]
(log/info "failed to get wallet market values "
{:error error
:event :wallet.tokens/fetch-market-values})
{:db (assoc-in db [:wallet :ui :loading :market-values] false)}))
(rf/reg-event-fx
:wallet.tokens/store-details
(fn [{:keys [db]} [details]]
{:db (-> db
(assoc-in [:wallet :tokens :details-per-token] details)
(assoc-in [:wallet :ui :loading :details] false))}))
(rf/reg-event-fx
:wallet.tokens/fetch-details-failed
(fn [{:keys [db]} [error]]
(log/info "failed to get wallet details "
{:error error
:event :wallet.tokens/fetch-details})
{:db (assoc-in db [:wallet :ui :loading :details] false)}))
(rf/reg-event-fx
:wallet.tokens/store-prices
(fn [{:keys [db]} [prices]]
{:db (-> db
(assoc-in [:wallet :tokens :prices-per-token] prices)
(assoc-in [:wallet :ui :loading :prices] false))}))
(rf/reg-event-fx
:wallet.tokens/fetch-prices-failed
(fn [{:keys [db]} [error]]
(log/info "failed to get wallet prices "
{:error error
:event :wallet.tokens/fetch-prices})
{:db (assoc-in db [:wallet :ui :loading :prices] false)}))

View File

@ -0,0 +1,275 @@
(ns status-im.contexts.wallet.tokens.events-test
(:require
[cljs.test :refer [deftest is testing]]
[status-im.contexts.wallet.tokens.events :as tokens.events]))
(def eth
{:address "0x0000000000000000000000000000000000000000"
:chainId 1
:decimals 18
:name "Ether"
:symbol "ETH"
:verified true})
(def snt
{:address "0x0000000000000000000000000000000000000001"
:chainId 1
:decimals 18
:name "Status Network Token"
:symbol "SNT"
:verified true})
(def another-token1
{:address "0x0000000000000000000000000000000000000002"
:chainId 1
:decimals 18
:name "Another Token 1"
:symbol "ANOTHER1"
:verified false})
(def another-token2
{:address "0x0000000000000000000000000000000000000000" ; uses the same address as `eth`
:chainId 1
:decimals 18
:name "Another Token 2"
:symbol "ANOTHER1" ;; uses the same symbol as `another-token1`
:verified false})
(deftest store-token-list-test
(testing "response contains one list, and `chain-id` is equal to the one that's in the database"
(let [cofx {:db {:wallet {:networks {:prod [{:chain-id 1}]}}}}
data [{:name "native"
:source "native"
:version "1.0.0"
:tokens [eth snt]}]]
(is
(match?
(:db (tokens.events/store-token-list cofx [{:data data}]))
{:wallet {:networks {:prod [{:chain-id 1}]}
:tokens {:sources [{:name "native"
:source "native"
:version "1.0.0"
:tokens-count 2}]
:by-address '({:address "0x0000000000000000000000000000000000000000"
:decimals 18
:key "1-0x0000000000000000000000000000000000000000"
:community-id nil
:symbol "ETH"
:sources ["native"]
:name "Ether"
:type :erc20
:verified? true
:chain-id 1
:image nil}
{:address "0x0000000000000000000000000000000000000001"
:decimals 18
:key "1-0x0000000000000000000000000000000000000001"
:community-id nil
:symbol "SNT"
:sources ["native"]
:name "Status Network Token"
:type :erc20
:verified? true
:chain-id 1
:image nil})
:by-symbol '({:address "0x0000000000000000000000000000000000000000"
:decimals 18
:key "ETH"
:community-id nil
:symbol "ETH"
:sources ["native"]
:name "Ether"
:type :erc20
:verified? true
:chain-id 1
:image nil}
{:address "0x0000000000000000000000000000000000000001"
:decimals 18
:key "SNT"
:community-id nil
:symbol "SNT"
:sources ["native"]
:name "Status Network Token"
:type :erc20
:verified? true
:chain-id 1
:image nil})}
:ui {:loading {:token-list false
:market-values true
:details true
:prices true}}}}))))
(testing "response contains one list, and `chain-id` is NOT equal to the one that's in the database"
(let [cofx {:db {:wallet {:networks {:prod [{:chain-id 2}]}}}}
data [{:name "native"
:source "native"
:version "1.0.0"
:tokens [eth snt]}]]
(is (match? (-> (tokens.events/store-token-list cofx [{:data data}])
:db
:wallet
:tokens)
{:sources [{:name "native"
:source "native"
:version "1.0.0"
:tokens-count 2}]
:by-address nil
:by-symbol nil}))))
(testing "response contains two lists"
(let [cofx {:db {:wallet {:networks {:prod [{:chain-id 1}]}}}}
data [{:name "native"
:source "native"
:version "1.0.0"
:tokens [eth snt]}
{:name "second"
:source "second"
:version "1.0.0"
:tokens [snt another-token1]}]]
(is
(match?
(-> (tokens.events/store-token-list cofx [{:data data}])
:db
:wallet
:tokens)
{:sources [{:name "native"
:source "native"
:version "1.0.0"
:tokens-count 2}
{:name "second"
:source "second"
:version "1.0.0"
:tokens-count 2}]
:by-address '({:address "0x0000000000000000000000000000000000000000"
:decimals 18
:key "1-0x0000000000000000000000000000000000000000"
:community-id nil
:symbol "ETH"
:sources ["native"]
:name "Ether"
:type :erc20
:verified? true
:chain-id 1
:image nil}
{:address "0x0000000000000000000000000000000000000001"
:decimals 18
:key "1-0x0000000000000000000000000000000000000001"
:community-id nil
:symbol "SNT"
:sources ["native" "second"]
:name "Status Network Token"
:type :erc20
:verified? true
:chain-id 1
:image nil}
{:address "0x0000000000000000000000000000000000000002"
:decimals 18
:key "1-0x0000000000000000000000000000000000000002"
:community-id nil
:symbol "ANOTHER1"
:sources ["second"]
:name "Another Token 1"
:type :erc20
:verified? false
:chain-id 1
:image nil})
:by-symbol '({:address "0x0000000000000000000000000000000000000000"
:decimals 18
:key "ETH"
:community-id nil
:symbol "ETH"
:sources ["native"]
:name "Ether"
:type :erc20
:verified? true
:chain-id 1
:image nil}
{:address "0x0000000000000000000000000000000000000001"
:decimals 18
:key "SNT"
:community-id nil
:symbol "SNT"
:sources ["native" "second"]
:name "Status Network Token"
:type :erc20
:verified? true
:chain-id 1
:image nil}
{:address "0x0000000000000000000000000000000000000002"
:decimals 18
:key "ANOTHER1"
:community-id nil
:symbol "ANOTHER1"
:sources ["second"]
:name "Another Token 1"
:type :erc20
:verified? false
:chain-id 1
:image nil})}))))
(testing "second list contains a token that replaces the one that was added before"
(let [cofx {:db {:wallet {:networks {:prod [{:chain-id 1}]}}}}
data [{:name "native"
:source "native"
:version "1.0.0"
:tokens [eth another-token1]}
{:name "second"
:source "second"
:version "1.0.0"
:tokens [another-token2]}]]
(is
(match?
(-> (tokens.events/store-token-list cofx [{:data data}])
:db
:wallet
:tokens)
{:sources [{:name "native"
:source "native"
:version "1.0.0"
:tokens-count 2}
{:name "second"
:source "second"
:version "1.0.0"
:tokens-count 1}]
:by-address '({:address "0x0000000000000000000000000000000000000000"
:decimals 18
:key "1-0x0000000000000000000000000000000000000000"
:community-id nil
:symbol "ANOTHER1"
:sources ["native" "second"]
:name "Another Token 2"
:type :erc20
:verified? false
:chain-id 1
:image nil}
{:address "0x0000000000000000000000000000000000000002"
:decimals 18
:key "1-0x0000000000000000000000000000000000000002"
:community-id nil
:symbol "ANOTHER1"
:sources ["native"]
:name "Another Token 1"
:type :erc20
:verified? false
:chain-id 1
:image nil})
:by-symbol '({:address "0x0000000000000000000000000000000000000000"
:decimals 18
:key "ETH"
:community-id nil
:symbol "ETH"
:sources ["native"]
:name "Ether"
:type :erc20
:verified? true
:chain-id 1
:image nil}
{:address "0x0000000000000000000000000000000000000000"
:decimals 18
:key "ANOTHER1"
:community-id nil
:symbol "ANOTHER1"
:sources ["native" "second"]
:name "Another Token 2"
:type :erc20
:verified? false
:chain-id 1
:image nil})})))))

View File

@ -0,0 +1,19 @@
(ns status-im.contexts.wallet.tokens.rpc
(:require [promesa.core :as promesa]
[status-im.common.json-rpc.events :as rpc-events]
[utils.transforms :as transforms]))
(defn fetch-market-values
[symbols currency]
(-> (rpc-events/call-async "wallet_fetchMarketValues" true symbols currency)
(promesa/then transforms/js->clj)))
(defn fetch-details
[symbols]
(-> (rpc-events/call-async "wallet_fetchTokenDetails" true symbols)
(promesa/then transforms/js->clj)))
(defn fetch-prices
[symbols currencies]
(-> (rpc-events/call-async "wallet_fetchPrices" true symbols currencies)
(promesa/then transforms/js->clj)))

View File

@ -6,61 +6,54 @@
[utils.hex :as hex]
[utils.transforms :as transforms]))
(defn- call-rpc
"Helper to handle RPC calls to status-go as promises"
[method & args]
(promesa/create
(fn [p-resolve p-reject]
(rpc-events/call {:method method
:params args
:on-success p-resolve
:on-error p-reject
:js-response true}))))
(defn wallet-build-transaction
[chain-id tx]
(promesa/let [res (call-rpc :wallet_buildTransaction chain-id tx)]
(promesa/let [res (rpc-events/call-async :wallet_buildTransaction true chain-id tx)]
{:message-to-sign (oops/oget res :messageToSign)
:tx-args (oops/oget res :txArgs)}))
(defn wallet-build-raw-transaction
[chain-id tx-args signature]
(-> (call-rpc "wallet_buildRawTransaction"
chain-id
(transforms/js-stringify tx-args 0)
signature)
(-> (rpc-events/call-async "wallet_buildRawTransaction"
true
chain-id
(transforms/js-stringify tx-args 0)
signature)
(promesa/then #(oops/oget % "rawTx"))))
(defn wallet-send-transaction-with-signature
[chain-id tx-args signature]
(call-rpc "wallet_sendTransactionWithSignature"
chain-id
constants/transaction-pending-type-wallet-connect-transfer
(transforms/js-stringify tx-args 0)
signature))
(rpc-events/call-async "wallet_sendTransactionWithSignature"
true
chain-id
constants/transaction-pending-type-wallet-connect-transfer
(transforms/js-stringify tx-args 0)
signature))
(defn wallet-sign-message
[message address password]
(-> (call-rpc "wallet_signMessage"
message
address
password)
(-> (rpc-events/call-async "wallet_signMessage"
true
message
address
password)
(promesa/then hex/normalize-hex)))
(defn wallet-hash-message-eip-191
[message]
(call-rpc "wallet_hashMessageEIP191" message))
(rpc-events/call-async "wallet_hashMessageEIP191" true message))
(defn wallet-safe-sign-typed-data
[data address password chain-id legacy?]
(call-rpc "wallet_safeSignTypedDataForDApps"
data
address
password
chain-id
legacy?))
(rpc-events/call-async "wallet_safeSignTypedDataForDApps"
true
data
address
password
chain-id
legacy?))
(defn wallet-get-suggested-fees
[chain-id]
(-> (call-rpc "wallet_getSuggestedFees" chain-id)
(-> (rpc-events/call-async "wallet_getSuggestedFees" true chain-id)
(promesa/then transforms/js->clj)))

View File

@ -4,16 +4,16 @@
legacy.status-im.events
legacy.status-im.subs.root
[promesa.core :as promesa]
[status-im.common.json-rpc.events :as rpc-events]
status-im.events
status-im.navigation.core
status-im.subs.root
[test-helpers.integration :as h]
[tests.contract-test.utils :as contract-utils]))
[test-helpers.integration :as h]))
(use-fixtures :each (h/fixture-session))
(deftest profile-set-bio-contract-test
(h/test-async :contract/wakuext_setBio
(fn []
(-> (contract-utils/call-rpc "wakuext_setBio" "new bio")
(-> (rpc-events/call-async "wakuext_setBio" false "new bio")
(promesa/catch #(is (nil? %) "Set bio RPC call should have succeeded"))))))

View File

@ -1,18 +1,7 @@
(ns tests.contract-test.utils
(:require
[promesa.core :as promesa]
[status-im.common.json-rpc.events :as rpc-events]
[utils.number]))
(defn call-rpc
[method & args]
(promesa/create
(fn [p-resolve p-reject]
(rpc-events/call {:method method
:params args
:on-success p-resolve
:on-error p-reject}))))
(defn get-default-account
[accounts]
(first (filter :wallet accounts)))

View File

@ -5,6 +5,7 @@
legacy.status-im.subs.root
[native-module.core :as native-module]
[promesa.core :as promesa]
[status-im.common.json-rpc.events :as rpc-events]
status-im.events
status-im.navigation.core
status-im.subs.root
@ -26,10 +27,11 @@
(fn []
(promesa/let [sha3-pwd-hash (native-module/sha3 integration-constants/password)
derivation-path [integration-constants/derivation-path]
accounts (contract-utils/call-rpc "accounts_getAccounts")
accounts (rpc-events/call-async "accounts_getAccounts" false)
default-address (contract-utils/get-default-address accounts)
response (contract-utils/call-rpc
response (rpc-events/call-async
"wallet_getDerivedAddresses"
false
sha3-pwd-hash
default-address
derivation-path)]

View File

@ -5,6 +5,7 @@
legacy.status-im.subs.root
[promesa.core :as promesa]
[status-im.common.emoji-picker.utils :as emoji-picker.utils]
[status-im.common.json-rpc.events :as rpc-events]
[status-im.constants :as constants]
[status-im.contexts.wallet.data-store :as data-store]
status-im.events
@ -26,7 +27,7 @@
(deftest accounts-get-accounts-contract-test
(h/test-async :contract/accounts-get-accounts
(fn []
(promesa/let [result (contract-utils/call-rpc "accounts_getAccounts")]
(promesa/let [result (rpc-events/call-async "accounts_getAccounts" false)]
(assert-accounts-get-accounts result)))))
(defn check-emoji-is-updated
@ -38,12 +39,13 @@
(h/test-async :contract/accounts-save-account
(fn []
(promesa/let [test-emoji (emoji-picker.utils/random-emoji)
account (contract-utils/call-rpc "accounts_getAccounts")
account (rpc-events/call-async "accounts_getAccounts" false)
default-account (contract-utils/get-default-account account)
_ (contract-utils/call-rpc
_ (rpc-events/call-async
"accounts_saveAccount"
false
(data-store/<-account (merge default-account {:emoji test-emoji})))
accounts (contract-utils/call-rpc "accounts_getAccounts")]
accounts (rpc-events/call-async "accounts_getAccounts" false)]
(check-emoji-is-updated test-emoji accounts)))))
(defn assert-ethereum-chains
@ -59,7 +61,7 @@
(deftest accounts-get-chains-contract-test
(h/test-async :contract/wallet_get-ethereum-chains
(fn []
(promesa/let [response (contract-utils/call-rpc "wallet_getEthereumChains")]
(promesa/let [response (rpc-events/call-async "wallet_getEthereumChains" false)]
(assert-ethereum-chains response)))))
(defn assert-wallet-tokens
@ -81,10 +83,11 @@
(deftest wallet-get-walet-token-test
(h/test-async :wallet/get-wallet-token
(fn []
(promesa/let [accounts (contract-utils/call-rpc "accounts_getAccounts")
(promesa/let [accounts (rpc-events/call-async "accounts_getAccounts" false)
default-address (contract-utils/get-default-address accounts)
response (contract-utils/call-rpc
response (rpc-events/call-async
"wallet_getWalletToken"
false
[default-address])]
(assert-wallet-tokens response)))))
@ -100,10 +103,11 @@
(fn []
(promesa/let [input "test.eth"
chain-id constants/ethereum-mainnet-chain-id
ens-address (contract-utils/call-rpc "ens_addressOf" chain-id input)
response (contract-utils/call-rpc "wallet_getAddressDetails"
chain-id
ens-address)]
ens-address (rpc-events/call-async "ens_addressOf" false chain-id input)
response (rpc-events/call-async "wallet_getAddressDetails"
false
chain-id
ens-address)]
(assert-address-details response)))))
(defn assert-search-ens
@ -129,6 +133,6 @@
(promesa/all
(map (fn [{:keys [ens-name chain-id expected-result]}]
(promesa/let [ens-address
(contract-utils/call-rpc "ens_addressOf" chain-id ens-name)]
(rpc-events/call-async "ens_addressOf" false chain-id ens-name)]
(assert-search-ens expected-result ens-address)))
test-cases)))))))