feat: implement getSuggestedRoutes in the wallet Send Flow (#18104)

Signed-off-by: Brian Sztamfater <brian@status.im>
This commit is contained in:
Brian Sztamfater 2023-12-19 07:59:58 -03:00 committed by GitHub
parent d40f70dca4
commit a4f99de8d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 316 additions and 139 deletions

View File

@ -173,8 +173,7 @@
:use-mailservers? true
:recovered recovered}
config/default-multiaccount)
;; The address from which we derive any chat
;; account/encryption keys
;; The address from which we derive any chat account/encryption keys
eip1581-address
(assoc :eip1581-address eip1581-address)
save-mnemonic?

View File

@ -394,3 +394,15 @@
(def ^:const status-address-domain ".stateofus.eth")
(def ^:const eth-address-domain ".eth")
(def ^:const gas-rate-low 0)
(def ^:const gas-rate-medium 1)
(def ^:const gas-rate-high 2)
(def ^:const send-type-transfer 0)
(def ^:const send-type-ens-register 1)
(def ^:const send-type-ens-release 2)
(def ^:const send-type-ens-set-pub-key 3)
(def ^:const send-type-stickers-buy 4)
(def ^:const send-type-bridge 5)
(def ^:const send-type-erc-721-transfer 6)

View File

@ -74,6 +74,18 @@
(map #(total-token-fiat-value currency %))
(reduce money/add)))
(defn calculate-balance-for-token
[token]
(money/bignumber
(money/mul (total-token-units-in-all-chains token)
(-> token :market-values-per-currency :usd :price))))
(defn calculate-balance
[tokens-in-account]
(->> tokens-in-account
(map #(calculate-balance-for-token %))
(reduce +)))
(defn network-list
[{:keys [balances-per-chain]} networks]
(into #{}

View File

@ -152,7 +152,9 @@
(rf/defn clean-scanned-address
{:events [:wallet/clean-scanned-address]}
[{:keys [db]}]
{:db (dissoc db :wallet/scanned-address :wallet/send-address)})
{:db (-> db
(dissoc :wallet/scanned-address :wallet/send-address)
(update-in [:wallet :ui :send] dissoc :to-address))})
(rf/reg-event-fx :wallet/create-derived-addresses
(fn [{:keys [db]} [{:keys [sha3-pwd path]} on-success]]
@ -437,10 +439,6 @@
(background-timer/clear-timeout current-timeout)
{:db (assoc db :wallet/local-suggestions [] :wallet/valid-ens-or-address? false)})))
(rf/reg-event-fx :wallet/select-send-address
(fn [{:keys [db]} [address]]
{:db (assoc db :wallet/send-address address)}))
(rf/reg-event-fx :wallet/get-address-details-success
(fn [{:keys [db]} [{:keys [hasActivity]}]]
{:db (assoc-in db

View File

@ -17,7 +17,7 @@
(deftest clean-scanned-address
(let [db {:wallet/scanned-address address}]
(testing "clean-scanned-address"
(let [expected-db {}
(let [expected-db {:wallet {:ui {:send nil}}}
effects (events/clean-scanned-address {:db db})
result-db (:db effects)]
(is (match? result-db expected-db))))))

View File

@ -1,5 +1,8 @@
(ns status-im2.contexts.wallet.send.events
(:require
[status-im2.constants :as constants]
[taoensso.timbre :as log]
[utils.money :as money]
[utils.number]
[utils.re-frame :as rf]))
@ -10,3 +13,79 @@
(rf/reg-event-fx :wallet/select-send-account-address
(fn [{:keys [db]} [address]]
{:db (assoc db [:wallet :ui :send :send-account-address] address)}))
(rf/reg-event-fx :wallet/suggested-routes-success
(fn [{:keys [db]} [suggested-routes timestamp]]
(when (= (get-in db [:wallet :ui :send :suggested-routes-call-timestamp]) timestamp)
{:db (-> db
(assoc-in [:wallet :ui :send :suggested-routes] suggested-routes)
(assoc-in [:wallet :ui :send :route] (first (:Best suggested-routes)))
(assoc-in [:wallet :ui :send :loading-suggested-routes?] false))})))
(rf/reg-event-fx :wallet/suggested-routes-error
(fn [{:keys [db]} [_error]]
{:db (-> db
(update-in [:wallet :ui :send] dissoc :suggested-routes)
(update-in [:wallet :ui :send] dissoc :route)
(assoc-in [:wallet :ui :send :loading-suggested-routes?] false))}))
(rf/reg-event-fx :wallet/clean-suggested-routes
(fn [{:keys [db]}]
{:db (-> db
(update-in [:wallet :ui :send] dissoc :suggested-routes)
(update-in [:wallet :ui :send] dissoc :route)
(update-in [:wallet :ui :send] dissoc :loading-suggested-routes?))}))
(rf/reg-event-fx :wallet/select-send-address
(fn [{:keys [db]} [{:keys [address stack-id]}]]
{:db (assoc-in db [:wallet :ui :send :to-address] address)
:fx [[:navigate-to-within-stack [:wallet-select-asset stack-id]]]}))
(rf/reg-event-fx :wallet/send-select-token
(fn [{:keys [db]} [{:keys [token stack-id]}]]
{:db (assoc-in db [:wallet :ui :send :token] token)
:fx [[:navigate-to-within-stack [:wallet-send-input-amount stack-id]]]}))
(rf/reg-event-fx :wallet/send-select-amount
(fn [{:keys [db]} [{:keys [amount]}]]
(js/alert "Not implemented yet")
{:db (assoc-in db [:wallet :ui :send :amount] amount)}))
(rf/reg-event-fx :wallet/get-suggested-routes
(fn [{:keys [db now]} [amount]]
(let [wallet-address (get-in db [:wallet :current-viewing-account-address])
token (get-in db [:wallet :ui :send :token])
to-address (get-in db [:wallet :ui :send :to-address])
token-decimal (:decimals token)
token-id (:symbol token)
network-preferences []
gas-rates constants/gas-rate-medium
amount-in (money/amount-in-hex amount token-decimal)
from-address wallet-address
disabled-from-chain-ids []
disabled-to-chain-ids []
from-locked-amount {}
request-params [constants/send-type-transfer
from-address
to-address
amount-in
token-id
disabled-from-chain-ids
disabled-to-chain-ids
network-preferences
gas-rates
from-locked-amount]]
{:db (-> db
(assoc-in [:wallet :ui :send :loading-suggested-routes?] true)
(assoc-in [:wallet :ui :send :suggested-routes-call-timestamp] now))
:json-rpc/call [{:method "wallet_getSuggestedRoutes"
:params request-params
:on-success (fn [suggested-routes]
(rf/dispatch [:wallet/suggested-routes-success suggested-routes
now]))
:on-error (fn [error]
(rf/dispatch [:wallet/suggested-routes-error error])
(log/error "failed to get suggested routes"
{:event :wallet/get-suggested-routes
:error error
:params request-params}))}]})))

View File

@ -12,98 +12,107 @@
(fn [_] (val keyval)))))
(def sub-mocks
{:profile/profile {:currency :usd}
:wallet/network-details [{:source 525
:short-name "eth"
:network-name :ethereum
:chain-id 1
:related-chain-id 5}]
:wallet/current-viewing-account {:path "m/44'/60'/0'/0/1"
:emoji "💎"
:key-uid "0x2f5ea39"
:address "0x1"
:wallet false
:name "Account One"
:type :generated
:watch-only? false
:chat false
:test-preferred-chain-ids #{5 420 421613}
:color :purple
:hidden false
:prod-preferred-chain-ids #{1 10 42161}
:network-preferences-names #{:ethereum :arbitrum :optimism}
:position 1
:clock 1698945829328
:created-at 1698928839000
:operable "fully"
:mixedcase-address "0x7bcDfc75c431"
:public-key "0x04371e2d9d66b82f056bc128064"
:removed false}})
{:profile/profile {:currency :usd}
:wallet/network-details [{:source 525
:short-name "eth"
:network-name :ethereum
:chain-id 1
:related-chain-id 5}]
:wallet/current-viewing-account {:path "m/44'/60'/0'/0/1"
:emoji "💎"
:key-uid "0x2f5ea39"
:address "0x1"
:wallet false
:name "Account One"
:type :generated
:watch-only? false
:chat false
:test-preferred-chain-ids #{5 420 421613}
:color :purple
:hidden false
:prod-preferred-chain-ids #{1 10 42161}
:network-preferences-names #{:ethereum :arbitrum
:optimism}
:position 1
:clock 1698945829328
:created-at 1698928839000
:operable "fully"
:mixedcase-address "0x7bcDfc75c431"
:public-key "0x04371e2d9d66b82f056bc128064"
:removed false}
:wallet/wallet-send-token {:symbol :eth}
:wallet/wallet-send-loading-suggested-routes? false
:wallet/wallet-send-route {:route []}})
(h/describe "Send > input amount screen"
(h/test "Default render"
(setup-subs sub-mocks)
(h/render [input-amount/view {}])
(h/is-truthy (h/get-by-text "0"))
(h/is-truthy (h/get-by-text "ETH"))
(h/is-truthy (h/get-by-text "$0.00"))
(h/is-disabled (h/get-by-label-text :button-one)))
(with-redefs [re-frame/dispatch #()]
(setup-subs sub-mocks)
(h/render [input-amount/view {}])
(h/is-truthy (h/get-by-text "0"))
(h/is-truthy (h/get-by-text "ETH"))
(h/is-truthy (h/get-by-text "$0.00"))
(h/is-disabled (h/get-by-label-text :button-one))))
(h/test "Fill token input and confirm"
(setup-subs sub-mocks)
(let [on-confirm (h/mock-fn)]
(h/render [input-amount/view
{:on-confirm on-confirm
:rate 10}])
(with-redefs [re-frame/dispatch #()]
(setup-subs sub-mocks)
(let [on-confirm (h/mock-fn)]
(h/render [input-amount/view
{:on-confirm on-confirm
:rate 10
:limit 1000}])
(h/fire-event :press (h/query-by-label-text :keyboard-key-1))
(h/fire-event :press (h/query-by-label-text :keyboard-key-2))
(h/fire-event :press (h/query-by-label-text :keyboard-key-3))
(h/fire-event :press (h/query-by-label-text :keyboard-key-.))
(h/fire-event :press (h/query-by-label-text :keyboard-key-4))
(h/fire-event :press (h/query-by-label-text :keyboard-key-5))
(h/fire-event :press (h/query-by-label-text :keyboard-key-1))
(h/fire-event :press (h/query-by-label-text :keyboard-key-2))
(h/fire-event :press (h/query-by-label-text :keyboard-key-3))
(h/fire-event :press (h/query-by-label-text :keyboard-key-.))
(h/fire-event :press (h/query-by-label-text :keyboard-key-4))
(h/fire-event :press (h/query-by-label-text :keyboard-key-5))
(h/wait-for #(h/is-truthy (h/get-by-text "$1234.50")))
(h/wait-for #(h/is-truthy (h/get-by-text "$1234.50")))
(h/is-truthy (h/get-by-label-text :button-one))
(h/is-truthy (h/get-by-label-text :button-one))
(h/fire-event :press (h/get-by-label-text :button-one))
(h/was-called on-confirm)))
(h/fire-event :press (h/get-by-label-text :button-one))
(h/was-called on-confirm))))
(h/test "Try to fill more than limit"
(setup-subs sub-mocks)
(h/render [input-amount/view
{:rate 10
:limit 286}])
(with-redefs [re-frame/dispatch #()]
(setup-subs sub-mocks)
(h/render [input-amount/view
{:rate 10
:limit 286}])
(h/fire-event :press (h/query-by-label-text :keyboard-key-2))
(h/fire-event :press (h/query-by-label-text :keyboard-key-9))
(h/fire-event :press (h/query-by-label-text :keyboard-key-5))
(h/fire-event :press (h/query-by-label-text :keyboard-key-2))
(h/fire-event :press (h/query-by-label-text :keyboard-key-9))
(h/fire-event :press (h/query-by-label-text :keyboard-key-5))
(h/wait-for #(h/is-truthy (h/get-by-text "$290.00")))
(h/wait-for #(h/is-truthy (h/get-by-text "$290.00")))
(h/fire-event :press (h/query-by-label-text :keyboard-key-backspace))
(h/fire-event :press (h/query-by-label-text :keyboard-key-8))
(h/fire-event :press (h/query-by-label-text :keyboard-key-5))
(h/wait-for #(h/is-truthy (h/get-by-text "$2850.00"))))
(h/fire-event :press (h/query-by-label-text :keyboard-key-backspace))
(h/fire-event :press (h/query-by-label-text :keyboard-key-8))
(h/fire-event :press (h/query-by-label-text :keyboard-key-5))
(h/wait-for #(h/is-truthy (h/get-by-text "$2850.00")))))
(h/test "Switch from crypto to fiat and check limit"
(setup-subs sub-mocks)
(h/render [input-amount/view
{:rate 10
:limit 250}])
(with-redefs [re-frame/dispatch #()]
(setup-subs sub-mocks)
(h/render [input-amount/view
{:rate 10
:limit 250}])
(h/fire-event :press (h/query-by-label-text :keyboard-key-2))
(h/fire-event :press (h/query-by-label-text :keyboard-key-0))
(h/wait-for #(h/is-truthy (h/get-by-text "$200.00")))
(h/fire-event :press (h/query-by-label-text :keyboard-key-2))
(h/fire-event :press (h/query-by-label-text :keyboard-key-0))
(h/wait-for #(h/is-truthy (h/get-by-text "$200.00")))
(h/fire-event :press (h/query-by-label-text :reorder))
(h/fire-event :press (h/query-by-label-text :reorder))
(h/wait-for #(h/is-truthy (h/get-by-text "2.00 ETH")))
(h/wait-for #(h/is-truthy (h/get-by-text "2.00 ETH")))
(h/fire-event :press (h/query-by-label-text :keyboard-key-5))
(h/fire-event :press (h/query-by-label-text :keyboard-key-5))
(h/fire-event :press (h/query-by-label-text :keyboard-key-5))
(h/fire-event :press (h/query-by-label-text :keyboard-key-5))
(h/wait-for #(h/is-truthy (h/get-by-text "205.50 ETH")))
(h/fire-event :press (h/query-by-label-text :keyboard-key-5))
(h/wait-for #(h/is-truthy (h/get-by-text "205.50 ETH")))))
(h/wait-for #(h/is-truthy (h/get-by-text "205.50 ETH")))
(h/fire-event :press (h/query-by-label-text :keyboard-key-5))
(h/wait-for #(h/is-truthy (h/get-by-text "205.50 ETH"))))))

View File

@ -8,6 +8,7 @@
[reagent.core :as reagent]
[status-im2.contexts.wallet.common.account-switcher.view :as account-switcher]
[status-im2.contexts.wallet.send.input-amount.style :as style]
[utils.debounce :as debounce]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@ -48,67 +49,84 @@
current))
(defn- f-view-internal
[{:keys [token limit rate]}]
(let [bottom (safe-area/get-bottom)
{:keys [currency]} (rf/sub [:profile/profile])
networks (rf/sub [:wallet/network-details])
;; Temporary values
token (or token :eth)
conversion-rate (or rate 10)
limit-crypto (or limit 2860000.32)
limit-fiat (* limit-crypto conversion-rate)
input-value (reagent/atom "")
current-limit (reagent/atom {:amount limit-crypto
:currency token})
handle-swap (fn [crypto?]
(let [num-value (parse-double @input-value)]
(reset! current-limit (if crypto?
{:amount limit-crypto
:currency token}
{:amount limit-fiat
:currency currency}))
(when (> num-value (:amount @current-limit))
(reset! input-value ""))))
handle-keyboard-press (fn [v]
(let [current-value @input-value
new-value (make-new-input current-value v)
num-value (or (parse-double new-value) 0)]
(when (<= num-value (:amount @current-limit))
(reset! input-value new-value)
(reagent/flush))))
handle-delete (fn [_]
(swap! input-value #(subs % 0 (dec (count %))))
(reagent/flush))
handle-on-change (fn [v]
(when (valid-input? @input-value v)
(let [num-value (or (parse-double v) 0)
current-limit-amount (:amount @current-limit)]
(if (> num-value current-limit-amount)
(reset! input-value (str current-limit-amount))
(reset! input-value v))
(reagent/flush))))]
[{:keys [rate limit]}]
(let [bottom (safe-area/get-bottom)
{:keys [currency]} (rf/sub [:profile/profile])
networks (rf/sub [:wallet/network-details])
token (rf/sub [:wallet/wallet-send-token])
loading-suggested-routes? (rf/sub [:wallet/wallet-send-loading-suggested-routes?])
token-symbol (:symbol token)
limit-crypto (or (:total-balance token) limit)
conversion-rate (or rate 10)
limit-fiat (* limit-crypto conversion-rate)
input-value (reagent/atom "")
current-limit (reagent/atom {:amount limit-crypto
:currency token-symbol})
handle-swap (fn [crypto?]
(let [num-value (parse-double @input-value)]
(reset! current-limit (if crypto?
{:amount limit-crypto
:currency token-symbol}
{:amount limit-fiat
:currency currency}))
(when (> num-value (:amount @current-limit))
(reset! input-value ""))))
handle-keyboard-press (fn [v]
(let [current-value @input-value
new-value (make-new-input current-value v)
num-value (or (parse-double new-value) 0)]
(when (and (not loading-suggested-routes?)
(<= num-value (:amount @current-limit)))
(reset! input-value new-value)
(reagent/flush))))
handle-delete (fn [_]
(when-not loading-suggested-routes?
(swap! input-value #(subs % 0 (dec (count %))))
(reagent/flush)))
handle-on-change (fn [v]
(when (valid-input? @input-value v)
(let [num-value (or (parse-double v) 0)
current-limit-amount (:amount @current-limit)]
(if (> num-value current-limit-amount)
(reset! input-value (str current-limit-amount))
(reset! input-value v))
(reagent/flush))))]
(fn [{:keys [on-confirm]
:or {on-confirm #(js/alert "Confirmed")}}]
(let [limit-label (make-limit-label @current-limit)
input-num-value (parse-double @input-value)
confirm-disabled? (or
(empty? @input-value)
(<= input-num-value 0)
(> input-num-value (:amount @current-limit)))]
:or {on-confirm #(rf/dispatch [:wallet/send-select-amount
{:amount @input-value
:stack-id :wallet-send-input-amount}])}}]
(let [limit-label (make-limit-label @current-limit)
input-num-value (parse-double @input-value)
route (rf/sub [:wallet/wallet-send-route])
loading-suggested-routes? (rf/sub [:wallet/wallet-send-loading-suggested-routes?])
confirm-disabled? (or
(nil? route)
(empty? @input-value)
(<= input-num-value 0)
(> input-num-value (:amount @current-limit)))]
(rn/use-effect
(fn []
(let [dismiss-keyboard-fn #(when (= % "active") (rn/dismiss-keyboard!))
app-keyboard-listener (.addEventListener rn/app-state "change" dismiss-keyboard-fn)]
#(.remove app-keyboard-listener))))
(rn/use-effect (fn []
(rf/dispatch [:wallet/clean-suggested-routes])
(when-not (or
(empty? @input-value)
(<= input-num-value 0)
(> input-num-value (:amount @current-limit)))
(debounce/debounce-and-dispatch [:wallet/get-suggested-routes @input-value]
100)))
[@input-value])
[rn/view
{:style style/screen}
[account-switcher/view
{:icon-name :i/arrow-left
:on-press #(rf/dispatch [:navigate-back])
:on-press #(rf/dispatch [:navigate-back-within-stack :wallet-send-input-amount])
:switcher-type :select-account}]
[quo/token-input
{:container-style style/input-container
:token token
:token token-symbol
:currency currency
:networks networks
:title (i18n/label :t/send-limit {:limit limit-label})
@ -119,7 +137,16 @@
:on-change-text (fn [text]
(handle-on-change text))}]
;; Network routing content to be added
[rn/scroll-view]
[rn/scroll-view
{:content-container-style {:flex-grow 1
:align-items :center
:justify-content :center}}
(cond loading-suggested-routes?
[quo/text "Loading routes"]
(and (not loading-suggested-routes?) route)
[quo/text "Route found"]
(and (not loading-suggested-routes?) (nil? route))
[quo/text "Route not found"])]
[quo/bottom-actions
{:actions :1-action
:button-one-label (i18n/label :t/confirm)

View File

@ -23,7 +23,7 @@
[input-value input-focused?]
(fn []
(let [scanned-address (rf/sub [:wallet/scanned-address])
send-address (rf/sub [:wallet/send-address])
send-address (rf/sub [:wallet/wallet-send-to-address])
valid-ens-or-address? (rf/sub [:wallet/valid-ens-or-address?])
chain-id (rf/sub [:chain-id])
contacts (rf/sub [:contacts/active])]
@ -76,7 +76,10 @@
_ _]
(let [props {:on-press (fn []
(let [address (if accounts (:address (first accounts)) address)]
(when-not ens (rf/dispatch [:wallet/select-send-address address]))))
(when-not ens
(rf/dispatch [:wallet/select-send-address
{:address address
:stack-id :wallet-select-address}]))))
:active-state? false}]
(cond
(= type types/saved-address)
@ -151,7 +154,9 @@
:type :primary
:disabled? (not valid-ens-or-address?)
:container-style style/button
:on-press #(js/alert "Not implemented yet")}
:on-press #(rf/dispatch [:wallet/select-send-address
{:address @input-value
:stack-id :wallet-select-address}])}
(i18n/label :t/continue)])]
[:<>
[quo/tabs

View File

@ -17,10 +17,13 @@
(defn- asset-component
[]
(fn [token _ _ _]
(let [on-press #(js/alert "Not implemented yet")
(let [on-press
#(rf/dispatch [:wallet/send-select-token
{:token token
:stack-id :wallet-select-asset}])
total-balance-formatted (.toFixed (:total-balance token) 2)
balance-fiat-formatted (.toFixed (:total-balance-fiat token) 2)
currency-symbol (rf/sub [:profile/currency-symbol])]
balance-fiat-formatted (.toFixed (:total-balance-fiat token) 2)
currency-symbol (rf/sub [:profile/currency-symbol])]
[quo/token-network
{:token (:symbol token)
:label (:name token)

View File

@ -156,7 +156,6 @@
(reg-root-key-sub :wallet/networks :wallet/networks)
(reg-root-key-sub :wallet/local-suggestions :wallet/local-suggestions)
(reg-root-key-sub :wallet/valid-ens-or-address? :wallet/valid-ens-or-address?)
(reg-root-key-sub :wallet/send-address :wallet/send-address)
;;debug
(when js/goog.DEBUG

View File

@ -29,6 +29,11 @@
:<- [:wallet]
:-> :ui)
(rf/reg-sub
:wallet/wallet-send
:<- [:wallet/ui]
:-> :send)
(rf/reg-sub
:wallet/tokens-loading?
:<- [:wallet/ui]
@ -40,6 +45,31 @@
:<- [:wallet]
:-> :current-viewing-account-address)
(rf/reg-sub
:wallet/wallet-send-to-address
:<- [:wallet/wallet-send]
:-> :to-address)
(rf/reg-sub
:wallet/wallet-send-route
:<- [:wallet/wallet-send]
:-> :route)
(rf/reg-sub
:wallet/wallet-send-token
:<- [:wallet/wallet-send]
:-> :token)
(rf/reg-sub
:wallet/wallet-send-amount
:<- [:wallet/wallet-send]
:-> :amount)
(rf/reg-sub
:wallet/wallet-send-loading-suggested-routes?
:<- [:wallet/wallet-send]
:-> :loading-suggested-routes?)
(rf/reg-sub
:wallet/watch-address-activity-state
:<- [:wallet/ui]
@ -110,7 +140,7 @@
(assoc token
:networks (utils/network-list token networks)
:total-balance (utils/total-token-units-in-all-chains token)
:total-balance-fiat 0))
:total-balance-fiat (utils/calculate-balance-for-token token)))
(:tokens account))
sorted-tokens (sort-by :name compare tokens)
filtered-tokens (filter #(or (string/starts-with? (string/lower-case (:name %))

View File

@ -246,3 +246,7 @@
(schema/=> format-amount
[:=> [:cat [:maybe :int]]
[:maybe :string]])
(defn amount-in-hex
[amount token-decimal]
(to-hex (mul (bignumber amount) (from-decimal token-decimal))))