From 3df4a7f1e45f0c1d7ae736ccca16b122194b18b1 Mon Sep 17 00:00:00 2001 From: Julien Eluard Date: Wed, 22 Nov 2017 11:37:20 +0100 Subject: [PATCH] Added ERC20 listing support --- resources/images/tokens/default.png | Bin 0 -> 2278 bytes .../views/input/animations/expandable.cljs | 2 +- src/status_im/chat/views/input/emoji.cljs | 6 +- src/status_im/constants.cljs | 23 ++---- .../ui/components/common/common.cljs | 10 +-- src/status_im/ui/components/list/styles.cljs | 3 +- .../ui/components/sync_state/gradient.cljs | 8 +- .../ui/screens/network_settings/subs.cljs | 17 +--- .../ui/screens/profile/qr_code/views.cljs | 2 +- .../ui/screens/qr_scanner/events.cljs | 2 +- .../wallet/choose_recipient/events.cljs | 2 +- src/status_im/ui/screens/wallet/events.cljs | 51 +++++++++++- .../ui/screens/wallet/main/styles.cljs | 9 ++ .../ui/screens/wallet/main/views.cljs | 77 +++++++----------- .../ui/screens/wallet/navigation.cljs | 5 +- .../ui/screens/wallet/request/views.cljs | 2 +- src/status_im/ui/screens/wallet/subs.cljs | 4 +- src/status_im/utils/erc20.cljs | 51 ------------ src/status_im/utils/ethereum/core.cljs | 65 +++++++++++++++ .../utils/{eip => ethereum}/eip681.cljs | 8 +- src/status_im/utils/ethereum/erc20.cljs | 58 +++++++++++++ src/status_im/utils/ethereum/macros.clj | 25 ++++++ src/status_im/utils/ethereum/tokens.cljs | 27 ++++++ src/status_im/utils/money.cljs | 31 ++++--- test/cljs/status_im/test/runner.cljs | 8 +- .../utils/{erc20.cljs => ethereum/core.cljs} | 8 +- .../test/utils/{eip => ethereum}/eip681.cljs | 4 +- .../cljs/status_im/utils/ethereum/tokens.cljs | 5 ++ 28 files changed, 334 insertions(+), 179 deletions(-) create mode 100644 resources/images/tokens/default.png delete mode 100644 src/status_im/utils/erc20.cljs create mode 100644 src/status_im/utils/ethereum/core.cljs rename src/status_im/utils/{eip => ethereum}/eip681.cljs (92%) create mode 100644 src/status_im/utils/ethereum/erc20.cljs create mode 100644 src/status_im/utils/ethereum/macros.clj create mode 100644 src/status_im/utils/ethereum/tokens.cljs rename test/cljs/status_im/test/utils/{erc20.cljs => ethereum/core.cljs} (67%) rename test/cljs/status_im/test/utils/{eip => ethereum}/eip681.cljs (96%) create mode 100644 test/cljs/status_im/utils/ethereum/tokens.cljs diff --git a/resources/images/tokens/default.png b/resources/images/tokens/default.png new file mode 100644 index 0000000000000000000000000000000000000000..40d1d3eff172ef023f3b3f6eada1b33aa3816e64 GIT binary patch literal 2278 zcmd5;X)qg#8VyM-iNum>f~d8E+8;q(T1!Q^Vrl5Llvu94mQvKd)LOT^wyKS2?X)z4 zqV`a#t$pilE7n>DSDIQARIfMh&AgxQ@B8t6XU>^#&YVAI=F74r;9wAO2mkT{vT9$)}~uLy5uOpNB*x_)Fs#GNrxl;+L`*HJyaMVRe=9jcw(ZROH#*l zjz95CdFyV2m^%Ul?9`WKtS@I#dq4iw%ipm;W>m?2)y874NJw~K%|A|0+eJlTikJrG z!&JPw2t+tX*>{2vd-HqTK#$IX!@{*sI{g7^I4&J+W@NK?jIxw=+ z7tray`vQr)bYri1?d1jfX)`xSQ=?Ef`xKq+=4MNpV?u@+K3YmJ`A zoW@9u-$6a$;>~3t9V|9Zt!{aEdmCa+=&Gv!u0#$?Nrb_ho5u17*QO(TiI88`0;aaw z-?&d37{$Cw9eK7c!7}bJL#;2#oyv z=^@*Q*Rj(lC+7kTK9M<{&X*`ZG;o6Fa@rK)u#-enQ^V`GWTkHEI3tMsIpm{1=To07 zVcR#?`=RkupKmMrp{pwACKtx|R9|m4gEEi53L+h*y>+<1l4Ey1{uuHRldKvn7_}>d zdJCwuJFd)i;tD0T){p(DA+Fu!tK{0!ql`JNi$o_m`%c68iKZIhcwbsE+oOkp$CnS%P807}d+SWkdH zU38tZrF=OqS@qYhs`%#Erk(2}Nj|3u8NG>uFOY9R-$_2W=5!{xUc{6<+0XK_li|Rr z3uvHS`W@O(I#2#jWQza+Inn42~FnQ(-N{HOA+2x}xm8gE^KY`ZYf! zlzC}>aD3nSYzL25GG)ru!?D`_GQ&5R-^Oie>6ivCq+M5%hc5^)ON3oXwF%2f97aIp zv2A#KiTKO{Kn*R=WJK*KL)A1MHQ%Ir$1CT(wqV3>!<9A2O8&E2j-EuI>xK9d=NS=X zXn~qdmmWwIWdPSB!5o?fgT{m}t~_ci^?HaTL9rg~;z>}wuslzCyl!qoai?DpW>Ffp z7dAiZlR3?9 zQS2otA}I*;HR%uc=f+F&{dDBdVs5VjDc)Ka0!##>#M8yj5dH13M8D(6lyq2c%26KQ z1=i1_@FBR=4SesJI52x);QO z8yVC-d>ghvs&MUy#@$Xqp=pEarxEgy^71K=oI4n_NiH+gX!*KA*nQ8u1CG`HsTzbu z(=bZ^4eB4kTUd8BwGV&lePRqYKc@^wJseowBm&eJW?s=W!|HB%dIWIb#FPpn~6j{>qv#>2;4;HAxxdOdg9#!#q=yVhVo3K zRuB@k&Q;+9(bvp|X94vFEdD^blK3)rlhCxMD1FUso)${Rg_^w%R*?$=)-JNS*=JVp1fh&^*Rno=yqKgf}6_ww%*=x#{vZdzKvdrmr~90 ze<+^gj%jF?t;g_|E1O^?{8PoFey!DF^SM{-S}vgeVR}P5S%&Z52LgLAxZ#?Sy#tR_ zg$MpWy|dqz4Xn9SP!^(vcx5q=ri?2UWh-(qJgX${DQj@2KJ0MHa!gNuR&z#dsaKbO zp+An)?-Du8UzYns^DV6DzbMt z=(bEY21`_^RwUjkeI!-kNYe8cTv04|(RT0xtY+6Ru<=mVjx%q db - (assoc-in [:wallet :balance] balance) + (assoc-in [:wallet :balance :ETH] balance) (assoc-in [:wallet :balance-loading?] false)))) (handlers/register-handler-db @@ -116,6 +148,21 @@ (assoc-error-message :balance-update err) (assoc-in [:wallet :balance-loading?] false)))) +(handlers/register-handler-db + :update-token-balance-success + (fn [db [_ symbol balance]] + (-> db + (assoc-in [:wallet :balance symbol] balance) + (assoc-in [:wallet :balance-loading?] false)))) + +(handlers/register-handler-db + :update-token-balance-fail + (fn [db [_ err]] + (log/debug "Unable to get token balance: " err) + (-> db + (assoc-error-message :balance-update err) + (assoc-in [:wallet :balance-loading?] false)))) + (handlers/register-handler-db :update-prices-success (fn [db [_ prices]] diff --git a/src/status_im/ui/screens/wallet/main/styles.cljs b/src/status_im/ui/screens/wallet/main/styles.cljs index 47af26613a..d6f8a04dbd 100644 --- a/src/status_im/ui/screens/wallet/main/styles.cljs +++ b/src/status_im/ui/screens/wallet/main/styles.cljs @@ -109,5 +109,14 @@ :color styles/color-gray4 :margin-left 6}) +(def corner-dot + {:position :absolute + :top 12 + :right 6 + :width 4 + :height 4 + :border-radius 2 + :background-color styles/color-cyan}) + (defn asset-border [color] {:border-color color :border-width 1 :border-radius 32}) diff --git a/src/status_im/ui/screens/wallet/main/views.cljs b/src/status_im/ui/screens/wallet/main/views.cljs index a1d5ec38a8..e2e32ed8ce 100644 --- a/src/status_im/ui/screens/wallet/main/views.cljs +++ b/src/status_im/ui/screens/wallet/main/views.cljs @@ -12,6 +12,8 @@ [status-im.i18n :as i18n] [status-im.react-native.resources :as resources] [status-im.utils.config :as config] + [status-im.utils.ethereum.core :as ethereum] + [status-im.utils.ethereum.tokens :as tokens] [status-im.utils.money :as money] [status-im.utils.platform :as platform] [status-im.utils.utils :as utils] @@ -77,13 +79,6 @@ [btn/button {:disabled? true :style (button.styles/button-bar :last) :text-style styles/main-button-text} (i18n/label :t/wallet-exchange)]]]]) -;; TODO(goranjovic): snt is temporarily given the same logo that eth uses to minimize the changes -;; while ERC20 is mocked and hidden behind a flag. -(defn- token->image [id] - (case id - "eth" {:source (:ethereum resources/assets) :style (styles/asset-border components.styles/color-gray-transparent-light)} - "snt" {:source (:ethereum resources/assets) :style (styles/asset-border components.styles/color-blue)})) - (defn add-asset [] [list/touchable-item show-not-implemented! [react/view @@ -93,54 +88,42 @@ [react/text {:style styles/add-asset-text} (i18n/label :t/wallet-add-asset)]]]]]) -(defn render-asset [{:keys [id currency amount]}] - ;; TODO(jeluard) Navigate to asset details screen - #_ - [list/touchable-item show-not-implemented! - [react/view - [list/item - [list/item-image {:uri :launch_logo}] - [react/view {:style styles/asset-item-value-container} - [react/text {:style styles/asset-item-value} (str amount)] - [react/text {:style styles/asset-item-currency - :uppercase? true} - id]] - [list/item-icon {:icon :icons/forward}]]]] - (if id - [react/view - [list/item - (let [{:keys [source style]} (token->image id)] - [list/item-image source style]) - [react/view {:style styles/asset-item-value-container} - [react/text {:style styles/asset-item-value - :number-of-lines 1 - :ellipsize-mode :tail} - (money/to-fixed (money/wei-> :eth amount))] - [react/text {:style styles/asset-item-currency - :uppercase? true - :number-of-lines 1} - id]]]] +(defn render-asset [{:keys [name symbol icon decimals amount]}] + (if name ;; If no 'name' then this the dummy value used to render `add-asset` + [list/touchable-item #(utils/show-popup "TODO" (str "Details about " symbol " here")) + [react/view + [list/item + (let [{:keys [source style]} icon] + [list/item-image source style]) + [react/view {:style styles/asset-item-value-container} + [react/text {:style styles/asset-item-value + :number-of-lines 1 + :ellipsize-mode :tail} + (money/to-fixed (money/token->unit (or amount 0) decimals))] + [react/text {:style styles/asset-item-currency + :uppercase? true + :number-of-lines 1} + symbol]] + [list/item-icon {:icon :icons/forward}]]]] [add-asset])) -(defn asset-section [balance prices-loading? balance-loading?] - (let [assets (concat [{:id "eth" :currency :eth :amount balance}] - (if config/erc20-enabled? - [{:id "snt" :currency :snt :amount 5000000000000000000000}]))] +(defn tokens-for [network] + (get tokens/all (ethereum/network network))) + +(defn asset-section [network balance prices-loading? balance-loading?] + (let [tokens (tokens-for network) + assets (map #(assoc % :amount (get balance (:symbol %))) (concat [tokens/ethereum] (when config/erc20-enabled? tokens)))] [react/view {:style styles/asset-section} [react/text {:style styles/asset-section-title} (i18n/label :t/wallet-assets)] [list/flat-list - {:data (concat assets [{}]) ;; Extra map triggers rendering for add-asset - ;; TODO(goranjovic): Refactor - ;; the order where new element is inserted - ;; with `conj` depends on the underlying collection type. - ;; whereas `concat` like here guarantees that empty element - ;; will be inserted in the end. + {:data assets ;; TODO(jeluard) Reenable once we `add-an-asset` story is flecthed out ;; (concat assets [{}]) ;; Extra map triggers rendering for add-asset :render-fn render-asset - :on-refresh #(rf/dispatch [:update-wallet]) + :on-refresh #(rf/dispatch [:update-wallet (when config/erc20-enabled? (map :symbol tokens))]) :refreshing (boolean (or prices-loading? balance-loading?))}]])) (defview wallet [] - (letsubs [balance [:balance] + (letsubs [network [:network] + balance [:balance] portfolio-value [:portfolio-value] portfolio-change [:portfolio-change] prices-loading? [:prices-loading?] @@ -151,4 +134,4 @@ [toolbar-view] [react/view components.styles/flex [main-section portfolio-value portfolio-change syncing? error-message] - [asset-section balance prices-loading? balance-loading?]]])) + [asset-section network balance prices-loading? balance-loading?]]])) diff --git a/src/status_im/ui/screens/wallet/navigation.cljs b/src/status_im/ui/screens/wallet/navigation.cljs index 14c65b359d..513e746c57 100644 --- a/src/status_im/ui/screens/wallet/navigation.cljs +++ b/src/status_im/ui/screens/wallet/navigation.cljs @@ -1,10 +1,11 @@ (ns status-im.ui.screens.wallet.navigation (:require [re-frame.core :as re-frame] - [status-im.ui.screens.navigation :as navigation])) + [status-im.ui.screens.navigation :as navigation] + [status-im.ui.screens.wallet.main.views :as main])) (defmethod navigation/preload-data! :wallet [db _] - (re-frame/dispatch [:update-wallet]) + (re-frame/dispatch [:update-wallet (map :symbol (main/tokens-for (:network db)))]) (assoc-in db [:wallet :current-tab] 0)) (defmethod navigation/preload-data! :transactions-history diff --git a/src/status_im/ui/screens/wallet/request/views.cljs b/src/status_im/ui/screens/wallet/request/views.cljs index 2c6fc5abac..d789d3c4ba 100644 --- a/src/status_im/ui/screens/wallet/request/views.cljs +++ b/src/status_im/ui/screens/wallet/request/views.cljs @@ -15,7 +15,7 @@ [status-im.ui.components.styles :as components.styles] [status-im.i18n :as i18n] [status-im.utils.platform :as platform] - [status-im.utils.eip.eip681 :as eip681] + [status-im.utils.ethereum.eip681 :as eip681] [status-im.utils.money :as money])) (defn toolbar-view [] diff --git a/src/status_im/ui/screens/wallet/subs.cljs b/src/status_im/ui/screens/wallet/subs.cljs index 556e9f6a32..07b5ffb5d3 100644 --- a/src/status_im/ui/screens/wallet/subs.cljs +++ b/src/status_im/ui/screens/wallet/subs.cljs @@ -30,7 +30,7 @@ :<- [:price] (fn [[balance price]] (if (and balance price) - (-> (money/wei->ether balance) + (-> (money/wei->ether (get balance :ETH)) ;; TODO(jeluard) Modify to consider tokens (money/eth->usd price) (money/with-precision 2) str) @@ -42,7 +42,7 @@ :<- [:balance] (fn [[price last-day balance]] (when (and price last-day) - (if (> balance 0) + (if (> (get balance :ETH) 0) ;; TODO(jeluard) Modify to consider tokens (-> (money/percent-change price last-day) (money/with-precision 2) .toNumber) diff --git a/src/status_im/utils/erc20.cljs b/src/status_im/utils/erc20.cljs deleted file mode 100644 index 1682fcf526..0000000000 --- a/src/status_im/utils/erc20.cljs +++ /dev/null @@ -1,51 +0,0 @@ -(ns status-im.utils.erc20 - (:require [clojure.string :as string] - [status-im.js-dependencies :as dependencies])) - -;; Example -;; -;; Contract: https://ropsten.etherscan.io/address/0x29b5f6efad2ad701952dfde9f29c960b5d6199c5#readContract -;; Owner: https://ropsten.etherscan.io/token/0x29b5f6efad2ad701952dfde9f29c960b5d6199c5?a=0xa7cfd581060ec66414790691681732db249502bd -;; -;; With a running node on Ropsten: -;; (let [web3 (:web3 @re-frame.db/app-db) -;; contract "0x29b5f6efad2ad701952dfde9f29c960b5d6199c5" -;; address "0xa7cfd581060ec66414790691681732db249502bd"] -;; (erc20/balance-of web3 contract address println)) -;; -;; => 0x0000000000000000000000000000000000000000000000000000000001bd0c4a -;; (hex->int "0x0000000000000000000000000000000000000000000000000000000001bd0c4a") ;; => 29166666 (note token decimals) - -(defn sha3 [s] - (.sha3 dependencies/Web3.prototype (str s))) - -(defn hex->int [s] - (js/parseInt s 16)) - -(defn zero-pad-64 [s] - (str (apply str (drop (count s) (repeat 64 "0"))) s)) - -(defn format-param [param] - (if (number? param) - (zero-pad-64 (hex->int param)) - (zero-pad-64 (subs param 2)))) - -(defn format-call-params [method-id & params] - (let [params (string/join (map format-param params))] - (str method-id params))) - -(defn get-call-params [contract method-id & params] - (let [data (apply format-call-params method-id params)] - {:to contract :data data})) - -(defn sig->method-id [signature] - (apply str (take 10 (sha3 signature)))) - -(defn balance-of-params [token of] - (let [method-id (sig->method-id "balanceOf(address)")] - (get-call-params token method-id of))) - -(defn balance-of [web3 token of cb] - (.call (.-eth web3) - (clj->js (balance-of-params token of)) - cb)) diff --git a/src/status_im/utils/ethereum/core.cljs b/src/status_im/utils/ethereum/core.cljs new file mode 100644 index 0000000000..f988dae255 --- /dev/null +++ b/src/status_im/utils/ethereum/core.cljs @@ -0,0 +1,65 @@ +(ns status-im.utils.ethereum.core + (:require [clojure.string :as string] + [status-im.js-dependencies :as dependencies])) + +;; IDs standardized in https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md#list-of-chain-ids + +(def chain-ids + {:mainnet {:id 1 :name "Mainnet"} + :ropsten {:id 3 :name "Ropsten"} + :rinkeby {:id 4 :name "Rinkeby"}}) + +(defn chain-id [k] + (get-in chain-ids [k :id])) + +(defn testnet? [id] + (contains? #{(chain-id :ropsten) (chain-id :rinkeby)} id)) + +(def hex-prefix "0x") + +(defn normalized-address [address] + (when address + (if (string/starts-with? address hex-prefix) + address + (str hex-prefix address)))) + +(defn network [network] + (when network + (keyword (string/replace network "_rpc" "")))) + +(defn sha3 [s] + (.sha3 dependencies/Web3.prototype (str s))) + +(defn hex->boolean [s] + (= s "0x0")) + +(defn boolean->hex [b] + (if b "0x0" "0x1")) + +(defn hex->int [s] + (js/parseInt s 16)) + +(defn int->hex [i] + (.toHex dependencies/Web3.prototype i)) + +(defn zero-pad-64 [s] + (str (apply str (drop (count s) (repeat 64 "0"))) s)) + +(defn format-param [param] + (if (number? param) + (zero-pad-64 (hex->int param)) + (zero-pad-64 (subs param 2)))) + +(defn format-call-params [method-id & params] + (let [params (string/join (map format-param params))] + (str method-id params))) + +(defn- sig->method-id [signature] + (apply str (take 10 (sha3 signature)))) + +(defn call [web3 params cb] + (.call (.-eth web3) (clj->js params) cb)) + +(defn call-params [contract method-sig & params] + (let [data (apply format-call-params (sig->method-id method-sig) params)] + {:to contract :data data})) \ No newline at end of file diff --git a/src/status_im/utils/eip/eip681.cljs b/src/status_im/utils/ethereum/eip681.cljs similarity index 92% rename from src/status_im/utils/eip/eip681.cljs rename to src/status_im/utils/ethereum/eip681.cljs index 12c65fa0f3..d3af2b2552 100644 --- a/src/status_im/utils/eip/eip681.cljs +++ b/src/status_im/utils/ethereum/eip681.cljs @@ -1,11 +1,11 @@ -(ns status-im.utils.eip.eip681 +(ns status-im.utils.ethereum.eip681 "Utility function related to [EIP681](https://github.com/ethereum/EIPs/issues/681) This EIP standardize how ethereum payment request can be represented as URI (say to embed them in a QR code). e.g. ethereum:0x1234@1/transfer?to=0x5678&value=1e18&gas=5000" (:require [clojure.string :as string] - [status-im.constants :as constants] + [status-im.utils.ethereum.core :as ethereum] [status-im.utils.money :as money])) (def scheme "ethereum") @@ -43,7 +43,7 @@ (when authority-path (let [[_ address chain-id function-name] (re-find authority-path-pattern authority-path)] (when-not (or (string/blank? address) function-name) ;; Native token support only TODO(jeluard) Add ERC20 support - (merge {:address address :chain-id (if chain-id (js/parseInt chain-id) constants/mainnet-id)} + (merge {:address address :chain-id (if chain-id (js/parseInt chain-id) (ethereum/chain-id :mainnet))} (parse-query query)))))))) @@ -59,7 +59,7 @@ (when (and address (not function-name)) ;; Native token support only TODO(jeluard) Add ERC20 support (let [parameters (dissoc (into {} (filter second m)) :chain-id)] ;; filter nil values (str scheme scheme-separator address - (when (and chain-id (not= chain-id constants/mainnet-id)) + (when (and chain-id (not= chain-id (ethereum/chain-id :mainnet))) ;; Add chain-id if specified and is not main-net (str chain-id-separator chain-id)) (when-not (empty? parameters) diff --git a/src/status_im/utils/ethereum/erc20.cljs b/src/status_im/utils/ethereum/erc20.cljs new file mode 100644 index 0000000000..eef214c75e --- /dev/null +++ b/src/status_im/utils/ethereum/erc20.cljs @@ -0,0 +1,58 @@ +(ns status-im.utils.ethereum.erc20 + " + Helper functions to interact with [ERC20](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md) smart contract + + Example + + Contract: https://ropsten.etherscan.io/address/0x29b5f6efad2ad701952dfde9f29c960b5d6199c5#readContract + Owner: https://ropsten.etherscan.io/token/0x29b5f6efad2ad701952dfde9f29c960b5d6199c5?a=0xa7cfd581060ec66414790691681732db249502bd + + With a running node on Ropsten: + (let [web3 (:web3 @re-frame.db/app-db) + contract \"0x29b5f6efad2ad701952dfde9f29c960b5d6199c5\" + address \"0xa7cfd581060ec66414790691681732db249502bd\"] + (erc20/balance-of web3 contract address println)) + + => 29166666 + " + (:require [status-im.utils.ethereum.core :as ethereum]) + (:refer-clojure :exclude [name symbol])) + +(defn name [web3 contract cb] + (ethereum/call web3 (ethereum/call-params contract "name()") cb)) + +(defn symbol [web3 contract cb] + (ethereum/call web3 (ethereum/call-params contract "symbol()") cb)) + +(defn decimals [web3 contract cb] + (ethereum/call web3 (ethereum/call-params contract "decimals()") cb)) + +(defn total-supply [web3 contract cb] + (ethereum/call web3 + (ethereum/call-params contract "totalSupply()") + #(cb %1 (ethereum/hex->int %2)))) + +(defn balance-of [web3 contract address cb] + (ethereum/call web3 + (ethereum/call-params contract "balanceOf(address)" address) + #(cb %1 (ethereum/hex->int %2)))) + +(defn transfer [web3 contract address value cb] + (ethereum/call web3 + (ethereum/call-params contract "transfer(address, uint256)" address (ethereum/int->hex value)) + #(cb %1 (ethereum/hex->boolean %2)))) + +(defn transfer-from [web3 contract from-address to-address value cb] + (ethereum/call web3 + (ethereum/call-params contract "transferFrom(address, address, uint256)" from-address to-address (ethereum/int->hex value)) + #(cb %1 (ethereum/hex->boolean %2)))) + +(defn approve [web3 contract address value cb] + (ethereum/call web3 + (ethereum/call-params contract "approve(address, uint256)" address (ethereum/int->hex value)) + #(cb %1 (ethereum/hex->boolean %2)))) + +(defn allowance [web3 contract owner-address spender-address cb] + (ethereum/call web3 + (ethereum/call-params contract "allowance(address, address)" owner-address spender-address) + #(cb %1 (ethereum/hex->int %2)))) \ No newline at end of file diff --git a/src/status_im/utils/ethereum/macros.clj b/src/status_im/utils/ethereum/macros.clj new file mode 100644 index 0000000000..31bcdfca37 --- /dev/null +++ b/src/status_im/utils/ethereum/macros.clj @@ -0,0 +1,25 @@ +(ns status-im.utils.ethereum.macros + (:require [clojure.string :as string] + [clojure.java.io :as io])) + +(def tokens-folder "./resources/images/tokens/") + +(def default-icon-path (str tokens-folder "default.png")) + +(defn icon-path + [symbol] + (let [s (str "./resources/images/tokens/" (string/lower-case (name symbol)) ".png")] + (if (.exists (io/file s)) + `(js/require ~s) + `(js/require "./resources/images/tokens/default.png")))) + +(defn- token->icon [{:keys [icon symbol]}] + ;; Tokens can define their own icons. + ;; If not try to make one using a local image as resource, if it does not exist fallback to default. + (or icon (icon-path symbol))) + +(defmacro resolve-icons + "In react-native arguments to require must be static strings. + Resolve all icons at compilation time so no variable is used." + [tokens] + (mapv #(assoc-in % [:icon :source] (token->icon %)) tokens)) \ No newline at end of file diff --git a/src/status_im/utils/ethereum/tokens.cljs b/src/status_im/utils/ethereum/tokens.cljs new file mode 100644 index 0000000000..0557a25783 --- /dev/null +++ b/src/status_im/utils/ethereum/tokens.cljs @@ -0,0 +1,27 @@ +(ns status-im.utils.ethereum.tokens + (:require [status-im.ui.components.styles :as styles]) + (:require-macros [status-im.utils.ethereum.macros :refer [resolve-icons]])) + +(defn- asset-border [color] + {:border-color color :border-width 1 :border-radius 32}) + +(def ethereum {:name "Ethereum" :symbol :ETH :decimals 18 + :icon {:source (js/require "./resources/images/assets/ethereum.png") + :style (asset-border styles/color-light-blue-transparent)}}) + +(def all + {:mainnet + (resolve-icons + [{:name "Status Network Token" + :symbol :SNT + :decimals 18 + :address "0x744d70FDBE2Ba4CF95131626614a1763DF805B9E"}]) + :testnet + (resolve-icons + [{:name "Status Test Token" + :symbol :STT + :decimals 18 + :address "0xc55cf4b03948d7ebc8b9e8bad92643703811d162"}])}) + +(defn token-for [chain-id symbol] + (some #(if (= symbol (:symbol %)) %) (get all chain-id))) \ No newline at end of file diff --git a/src/status_im/utils/money.cljs b/src/status_im/utils/money.cljs index c404a26804..1fe551dc6e 100644 --- a/src/status_im/utils/money.cljs +++ b/src/status_im/utils/money.cljs @@ -49,18 +49,20 @@ (dependencies/Web3.prototype.toDecimal (normalize s)) (catch :default err nil)))) +(defn from-decimal [n] (str "1" (string/join (repeat n "0")))) + (def eth-units {:wei (bignumber "1") - :kwei (bignumber "1000") - :mwei (bignumber "1000000") - :gwei (bignumber "1000000000") - :szabo (bignumber "1000000000000") - :finney (bignumber "1000000000000000") - :eth (bignumber "1000000000000000000") - :keth (bignumber "1000000000000000000000") - :meth (bignumber "1000000000000000000000000") - :geth (bignumber "1000000000000000000000000000") - :teth (bignumber "1000000000000000000000000000000")}) + :kwei (bignumber (from-decimal 3)) + :mwei (bignumber (from-decimal 6)) + :gwei (bignumber (from-decimal 9)) + :szabo (bignumber (from-decimal 12)) + :finney (bignumber (from-decimal 15)) + :eth (bignumber (from-decimal 18)) + :keth (bignumber (from-decimal 21)) + :meth (bignumber (from-decimal 24)) + :geth (bignumber (from-decimal 27)) + :teth (bignumber (from-decimal 30))}) (defn wei-> [unit n] (when-let [bn (bignumber n)] @@ -80,11 +82,16 @@ (when bn (.times bn (bignumber 1e18)))) +(defn token->unit [n decimals] + (when-let [bn (bignumber n)] + (.dividedBy bn (bignumber (from-decimal decimals))))) + (defn fee-value [gas gas-price] (.times (bignumber gas) (bignumber gas-price))) (defn eth->usd [eth usd-price] - (.times (bignumber eth) (bignumber usd-price))) + (when-let [bn (bignumber eth)] + (.times bn (bignumber usd-price)))) (defn percent-change [from to] (let [bnf (bignumber from) @@ -99,5 +106,5 @@ (.round bn decimals))) (defn sufficient-funds? [amount balance] - (when amount + (when balance (.greaterThanOrEqualTo balance amount))) diff --git a/test/cljs/status_im/test/runner.cljs b/test/cljs/status_im/test/runner.cljs index d282666208..8c108b8a5b 100644 --- a/test/cljs/status_im/test/runner.cljs +++ b/test/cljs/status_im/test/runner.cljs @@ -13,8 +13,8 @@ [status-im.test.utils.utils] [status-im.test.utils.money] [status-im.test.utils.clocks] - [status-im.test.utils.eip.eip681] - [status-im.test.utils.erc20] + [status-im.test.utils.ethereum.eip681] + [status-im.test.utils.ethereum.core] [status-im.test.utils.random] [status-im.test.utils.gfycat.core] [status-im.test.utils.signing-phrase.core] @@ -44,8 +44,8 @@ 'status-im.test.utils.utils 'status-im.test.utils.money 'status-im.test.utils.clocks - 'status-im.test.utils.eip.eip681 - 'status-im.test.utils.erc20 + 'status-im.test.utils.ethereum.eip681 + 'status-im.test.utils.ethereum.core 'status-im.test.utils.random 'status-im.test.utils.gfycat.core 'status-im.test.utils.signing-phrase.core diff --git a/test/cljs/status_im/test/utils/erc20.cljs b/test/cljs/status_im/test/utils/ethereum/core.cljs similarity index 67% rename from test/cljs/status_im/test/utils/erc20.cljs rename to test/cljs/status_im/test/utils/ethereum/core.cljs index 7259092c65..0228287a17 100644 --- a/test/cljs/status_im/test/utils/erc20.cljs +++ b/test/cljs/status_im/test/utils/ethereum/core.cljs @@ -1,11 +1,11 @@ -(ns status-im.test.utils.erc20 +(ns status-im.test.utils.ethereum.core (:require [cljs.test :refer-macros [deftest is testing]] - [status-im.utils.erc20 :as erc20])) + [status-im.utils.ethereum.core :as core])) -(deftest erc20 +(deftest call-params (testing "ERC20 balance-of params" (let [contract "0x29b5f6efad2ad701952dfde9f29c960b5d6199c5" address "0xa7cfd581060ec66414790691681732db249502bd"] - (is (= (erc20/balance-of-params contract address) + (is (= (core/call-params contract "balanceOf(address)" address) {:to "0x29b5f6efad2ad701952dfde9f29c960b5d6199c5" :data "0x70a08231000000000000000000000000a7cfd581060ec66414790691681732db249502bd"}))))) diff --git a/test/cljs/status_im/test/utils/eip/eip681.cljs b/test/cljs/status_im/test/utils/ethereum/eip681.cljs similarity index 96% rename from test/cljs/status_im/test/utils/eip/eip681.cljs rename to test/cljs/status_im/test/utils/ethereum/eip681.cljs index a1b186b3a0..f5591aaa79 100644 --- a/test/cljs/status_im/test/utils/eip/eip681.cljs +++ b/test/cljs/status_im/test/utils/ethereum/eip681.cljs @@ -1,6 +1,6 @@ -(ns status-im.test.utils.eip.eip681 +(ns status-im.test.utils.ethereum.eip681 (:require [cljs.test :refer-macros [deftest is testing]] - [status-im.utils.eip.eip681 :as eip681] + [status-im.utils.ethereum.eip681 :as eip681] [status-im.utils.money :as money])) (deftest parse-uri diff --git a/test/cljs/status_im/utils/ethereum/tokens.cljs b/test/cljs/status_im/utils/ethereum/tokens.cljs new file mode 100644 index 0000000000..adf37ccf50 --- /dev/null +++ b/test/cljs/status_im/utils/ethereum/tokens.cljs @@ -0,0 +1,5 @@ +(ns status-im.utils.ethereum.tokens) + +(def ethereum {}) + +(def all {}) \ No newline at end of file