Compare commits

...
Author SHA1 Message Date
Brian Sztamfater b5a48dc06d feat: integrate send transaction endpoint
Signed-off-by: Brian Sztamfater <brian@status.im>
2023-12-08 00:35:47 -03:00
Brian Sztamfater 4c604abe5b feat: implement getSuggestedRoutes in the wallet Send Flow
Signed-off-by: Brian Sztamfater <brian@status.im>
2023-12-08 00:32:52 -03:00
15 changed files with 317 additions and 124 deletions
@@ -6,14 +6,14 @@
(h/describe "Wallet: Token Input"
(h/test "Token label renders"
(h/render [token-input/view
{:token :snt
{:token {:symbol "snt"}
:currency :eur
:conversion 1}])
(h/is-truthy (h/get-by-text "SNT")))
(h/test "Amount renders"
(h/render [token-input/view
{:token :snt
{:token {:symbol "snt"}
:currency :eur
:conversion 1}])
(h/is-truthy (h/get-by-text "€0.00"))))
@@ -43,7 +43,7 @@
:align-items :flex-end}}
[rn/image
{:style style/token
:source (resources/get-token token)}]
:source (resources/get-token (keyword (string/lower-case token)))}]
[rn/text-input
(cond-> {:auto-focus true
:ref #(reset! input-ref %)
+1 -2
View File
@@ -172,8 +172,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?
+12
View File
@@ -393,3 +393,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)
@@ -44,12 +44,15 @@
(when balance-in-chain
(calculate-raw-balance (:raw-balance balance-in-chain) decimals))))
(defn calculate-balance-for-token
[token]
(* (total-token-value-in-all-chains token)
(-> token :market-values-per-currency :usd :price)))
(defn calculate-balance
[tokens-in-account]
(->> tokens-in-account
(map (fn [token]
(* (total-token-value-in-all-chains token)
(-> token :market-values-per-currency :usd :price))))
(map #(calculate-balance-for-token %))
(reduce +)))
(defn network-list
@@ -0,0 +1,133 @@
(ns status-im2.contexts.wallet.send.events
(:require
[native-module.core :as native-module]
[status-im2.constants :as constants]
[taoensso.timbre :as log]
[utils.datetime :as datetime]
[utils.money :as money]
[utils.number]
[utils.re-frame :as rf]
[utils.security.core :as security]))
(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]} [address]]
{:db (assoc-in db [:wallet :ui :send :to-address] address)}))
(rf/reg-event-fx :wallet/send-select-token
(fn [{:keys [db]} [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]} [amount stack-id]]
{:db (assoc-in db [:wallet :ui :send :amount] amount)
:fx [[:navigate-to-within-stack [:wallet-transaction-confirmation stack-id]]]}))
(rf/reg-event-fx :wallet/get-suggested-routes
(fn [{:keys [db]} [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 [constants/mainnet-chain-id]
gas-rates constants/gas-rate-low
amount-in (money/mul (money/bignumber amount)
(money/from-decimal token-decimal))
from-address wallet-address
request-params [constants/send-type-transfer
from-address
to-address
(money/to-hex amount-in)
token-id
[]
[]
network-preferences
gas-rates
{}]
timestamp (datetime/timestamp)]
{:db (-> db
(assoc-in [:wallet :ui :send :loading-suggested-routes?] true)
(assoc-in [:wallet :ui :send :suggested-routes-call-timestamp]
timestamp))
:json-rpc/call [{:method "wallet_getSuggestedRoutes"
:params request-params
:on-success (fn [suggested-routes]
(rf/dispatch [:wallet/suggested-routes-success suggested-routes
timestamp]))
: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}))}]})))
(rf/reg-event-fx :wallet/send-transaction
(fn [{:keys [db]} [password]]
(let [route (get-in db [:wallet :ui :send :route])
from-address (get-in db [:wallet :current-viewing-account-address])
to-address (get-in db [:wallet :ui :send :to-address])
from (:From route)
token (get-in db [:wallet :ui :send :token])
token-id (:symbol token)
from-asset token-id
to-asset token-id
bridge-name (:BridgeName route)
chain-id (:chainId from)
multi-transaction-command {:fromAddress from-address
:toAddress to-address
:fromAsset from-asset
:toAsset to-asset
:fromAmount (:AmountOut route)
:type 0}
transaction-bridge
[{:BridgeName bridge-name
:ChainID chain-id
:TransferTx {:From from-address
:To to-address
:Gas (money/to-hex (:GasAmount route))
:GasPrice (money/to-hex (money/->wei :gwei
(:gasPrice (:GasFees route))))
:Value (:AmountOut route)
:Nonce nil
:MaxFeePerGas (money/to-hex
(money/->wei :gwei
(:maxFeePerGasMedium (:GasFees route))))
:MaxPriorityFeePerGas (money/to-hex (money/->wei :gwei
(:maxPriorityFeePerGas
(:GasFees
route))))
:Input ""
:Data "0x"}}]
sha3-pwd (native-module/sha3 (str (security/safe-unmask-data password)))
request-params [multi-transaction-command transaction-bridge sha3-pwd]]
{:json-rpc/call [{:method "wallet_createMultiTransaction"
:params request-params
:on-success #(rf/dispatch [:dismiss-modal :wallet-select-address])
:on-error (fn [error]
(log/error "failed to send transaction"
{:event :wallet/send-transaction
:error error
:params request-params}))}]})))
@@ -17,7 +17,13 @@
:short-name "eth"
:network-name :ethereum
:chain-id 1
:related-chain-id 5}]})
:related-chain-id 5}]
:wallet {:ui {:send {:token {:symbol "ETH"
:decimals 18
:total-balance 1
:total-balance-fiat 10
:loading-suggested-routes? false
:route {}}}}}})
(h/describe "Send > input amount screen"
(h/test "Default render"
@@ -7,6 +7,7 @@
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im2.contexts.wallet.send.input-amount.style :as style]
[utils.debounce :as debounce]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@@ -47,71 +48,88 @@
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]}]
(let [bottom (safe-area/get-bottom)
{:keys [currency]} (rf/sub [:profile/profile])
networks (rf/sub [:wallet/network-details])
wallet-send (get-in (rf/sub [:wallet]) [:ui :send])
token (:token wallet-send)
token-symbol (:symbol token)
limit-crypto (:total-balance token)
limit-fiat (:total-balance-fiat token)
conversion-rate (or rate 10)
input-value (reagent/atom "")
current-limit (reagent/atom {:amount limit-crypto
:currency token-symbol})
loading-suggested-routes? (:loading-suggested-routes? wallet-send)
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 @input-value
:wallet-send-input-amount])}}]
(let [limit-label (make-limit-label @current-limit)
input-num-value (parse-double @input-value)
route (get-in (rf/sub [:wallet]) [:ui :send :route])
loading-suggested-routes? (get-in (rf/sub [:wallet]) [:ui :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}
[quo/page-nav
{:background :blur
:icon-name :i/arrow-left
:on-press #(rf/dispatch [:navigate-back])
:on-press #(rf/dispatch [:navigate-back-within-stack :wallet-send-input-amount])
:right-side :account-switcher
:account-switcher {:customization-color :yellow
:emoji "🎮"
:on-press #(js/alert "Switch 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})
@@ -122,7 +140,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)
@@ -47,7 +47,7 @@
[input-value input-focused?]
(fn []
(let [scanned-address (rf/sub [:wallet/scanned-address])
send-address (rf/sub [:wallet/send-address])
send-address (get-in (rf/sub [:wallet]) [:ui :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])]
@@ -19,10 +19,13 @@
(defn- asset-component
[]
(fn [token _ _ _]
(let [on-press #(js/alert "Not implemented yet")
(let [on-press
#(if (= (:symbol token) "ETH")
(rf/dispatch [:wallet/send-select-token token :wallet-select-asset])
(js/alert "Only ETH transfers are allowed."))
total-balance-formatted (.toFixed (:total-balance token) 2)
balance-fiat-formatted (.toFixed (:total-balance-fiat token) 2)
currency-symbol "$"]
balance-fiat-formatted (.toFixed (:total-balance-fiat token) 2)
currency-symbol "$"]
[quo/token-network
{:token (quo.resources/get-token (keyword (string/lower-case (:symbol token))))
:label (:name token)
@@ -5,14 +5,15 @@
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im.utils.utils :as utils]
[status-im2.common.resources :as resources]
[status-im2.common.standard-authentication.core :as standard-auth]
[status-im2.contexts.wallet.send.transaction-confirmation.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- transaction-title
[]
[token amount account to-address]
[rn/view {:style style/content-container}
[rn/view {:style {:flex-direction :row}}
[quo/text
@@ -22,7 +23,7 @@
:accessibility-label :send-label}
(i18n/label :t/send)]
[quo/summary-tag
{:label "150 ETH"
{:label (str amount " " (:symbol token))
:type :token
:image-source (quo.resources/get-token :eth)}]]
[rn/view
@@ -35,10 +36,10 @@
:accessibility-label :send-label}
(i18n/label :t/from)]
[quo/summary-tag
{:label "Collectibles vault"
{:label (:name account)
:type :account
:emoji "🍑"
:customization-color :purple}]]
:emoji (:emoji account)
:customization-color (:color account)}]]
[rn/view
{:style {:flex-direction :row
:margin-top 4}}
@@ -49,13 +50,11 @@
:accessibility-label :send-label}
(i18n/label :t/to)]
[quo/summary-tag
{:label "Mark Libot"
:type :user
:image-source (resources/get-mock-image :user-picture-male4)
:customization-color :magenta}]]])
{:type :address
:label (utils/get-shortened-address to-address)}]]])
(defn- transaction-from
[status-account-props theme]
[amount account-props theme]
[rn/view
{:style {:padding-horizontal 20
:padding-bottom 16}}
@@ -68,13 +67,13 @@
[quo/summary-info
{:type :status-account
:networks? true
:values {:ethereum 150
:optimism 50
:arbitrum 25}
:account-props status-account-props}]])
:values {:ethereum amount
:optimism 0
:arbitrum 0}
:account-props account-props}]])
(defn- transaction-to
[user-props theme]
[amount user-props theme]
[rn/view
{:style {:padding-horizontal 20
:padding-bottom 16}}
@@ -87,13 +86,13 @@
[quo/summary-info
{:type :user
:networks? true
:values {:ethereum 150
:optimism 50
:arbitrum 25}
:values {:ethereum amount
:optimism 0
:arbitrum 0}
:account-props user-props}]])
(defn- transaction-details
[theme]
[estimated-time-min max-fees-eth token amount to-address theme]
[rn/view
{:style {:padding-horizontal 20
:padding-bottom 16}}
@@ -116,7 +115,7 @@
:status :default
:size :small
:title (i18n/label :t/est-time)
:subtitle "3-5 min"}]
:subtitle (str estimated-time-min " min")}]
[quo/data-item
{:container-style {:flex 1
:height 36}
@@ -128,7 +127,7 @@
:status :default
:size :small
:title (i18n/label :t/max-fees)
:subtitle "€188,70"}]
:subtitle (str "$" max-fees-eth)}]
[quo/data-item
{:container-style {:flex 1
:height 36}
@@ -139,35 +138,42 @@
:label :none
:status :default
:size :small
:title (i18n/label :t/user-gets {:name "Mark"})
:subtitle "149.99 ETH"}]]])
:title (i18n/label :t/user-gets {:name (utils/get-shortened-address to-address)})
:subtitle (str amount " " (:symbol token))}]]])
(defn- f-view-internal
[_]
(let [reset-slider? (reagent/atom false)
margin-top (safe-area/get-top)
biometric-auth? true
on-close #(rf/dispatch [:navigate-back-within-stack :wallet-select-asset])
status-account-props {:customization-color :purple
:size 32
:emoji "🍑"
:type :default
:name "Collectibles vault"
:address "0x0ah...78b"}
user-props {:full-name "M L"
:status-indicator? false
:size :small
:ring-background (resources/get-mock-image :ring)
:customization-color :blue
:name "Mark Libot"
:address "0x0ah...78b"
:status-account (merge status-account-props
{:size 16
:name "New house"
:emoji "🍔"})}]
(let [margin-top (safe-area/get-top)
biometric-auth? false
on-close #(rf/dispatch [:navigate-back-within-stack :wallet-select-asset])
send-transaction-data (get-in (rf/sub [:wallet]) [:ui :send])
token (:token send-transaction-data)
amount (:amount send-transaction-data)
route (:route send-transaction-data)
estimated-time-min (:EstimatedTime route)
max-fees-eth 5.28 ; calculate gas price in fiat
to-address (:to-address send-transaction-data)
account (rf/sub [:wallet/current-viewing-account])
account-props {:customization-color (:color account)
:size 32
:emoji (:emoji account)
:type :default
:name (:name account)
:address (utils/get-shortened-address (:address account))}
user-props {:full-name "M L"
:status-indicator? false
:size :small
:ring-background (resources/get-mock-image :ring)
:customization-color :blue
:name "Mark Libot"
:address "0x0ah...78b"
:status-account (merge account-props
{:size 16
:name "New house"
:emoji "🍔"})}]
(fn [{:keys [theme]}]
[rn/view {:style {:flex 1}}
[quo/gradient-cover {:customization-color :purple}]
[quo/gradient-cover {:customization-color (:color account)}]
[rn/view {:style (style/container margin-top)}
[quo/page-nav
{:icon-name :i/arrow-left
@@ -176,18 +182,18 @@
:right-side [{:icon-name :i/advanced
:on-press (fn callback [] nil)
:accessibility-label "Advanced"}]}]
[transaction-title]
[transaction-from status-account-props theme]
[transaction-to user-props theme]
[transaction-details theme]
[transaction-title token amount account to-address]
[transaction-from amount account-props theme]
[transaction-to amount user-props theme]
[transaction-details estimated-time-min max-fees-eth token amount to-address theme]
[rn/view {:style style/slide-button-container}
[quo/slide-button
{:size :size/s-48
:customization-color :purple
:on-reset (when @reset-slider? #(reset! reset-slider? false))
:on-complete #(js/alert "Not implemented yet")
:track-icon (if biometric-auth? :i/face-id :password)
:track-text (i18n/label :t/slide-to-send)}]]]])))
[standard-auth/slide-button
{:size :size-40
:track-text (i18n/label :t/slide-to-send)
:customization-color (:color account)
:on-enter-password #(rf/dispatch [:wallet/send-transaction %])
:biometric-auth? biometric-auth?
:auth-button-label (i18n/label :t/confirm)}]]]])))
(defn view-internal
[props]
+1
View File
@@ -22,6 +22,7 @@
status-im2.contexts.shell.share.events
status-im2.contexts.syncing.events
status-im2.contexts.wallet.events
status-im2.contexts.wallet.send.events
[status-im2.db :as db]
[utils.re-frame :as rf]))
-1
View File
@@ -156,7 +156,6 @@
(reg-root-key-sub :wallet/scanned-address :wallet/scanned-address)
(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
+2 -2
View File
@@ -48,7 +48,7 @@
:<- [:wallet/balances]
:<- [:wallet/tokens-loading?]
(fn [[accounts balances tokens-loading?]]
(mapv (fn [{:keys [color address type] :as account}]
(mapv (fn [{:keys [color address] :as account}]
(assoc account
:customization-color color
:type (if (= type :watch) :watch-only :empty)
@@ -75,7 +75,7 @@
(assoc token
:networks (utils/network-list token networks)
:total-balance (utils/total-token-value-in-all-chains token)
:total-balance-fiat (utils/calculate-balance token)))
:total-balance-fiat (utils/calculate-balance-for-token token)))
(:tokens account))
sorted-tokens
+6 -2
View File
@@ -152,7 +152,7 @@
;; E.g. for Ether, it's smallest part is wei or 10^(-18) of 1 ether
;; for arbitrary ERC20 token the smallest part is 10^(-decimals) of 1 token
;;
;; Different tokens can have different number of allowed decimals, so it's neccessary to include the
;; Different tokens can have different number of allowed decimals, so it's necessary to include the
;; decimals parameter
;; to get the amount scale right.
@@ -205,10 +205,14 @@
(with-precision 2)
str))
(defn add
(defn- add*
[bn1 n2]
(.add ^js bn1 n2))
(def add
"Add with defaults, this version is able to receive `nil` and takes them as 0."
(fnil add* (bignumber 0) (bignumber 0)))
(defn mul
[bn1 bn2]
(.mul ^js bn1 bn2))