Added ERC20 listing support
This commit is contained in:
parent
f7e63e9bea
commit
3df4a7f1e4
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
|
@ -6,7 +6,7 @@
|
|||
[status-im.ui.components.animation :as anim]
|
||||
[status-im.ui.components.drag-drop :as drag]
|
||||
[status-im.ui.components.react :refer [view
|
||||
animated-view]]
|
||||
animated-view]]
|
||||
[status-im.chat.views.input.animations.responder :as resp]
|
||||
[status-im.chat.views.input.utils :as input-utils]
|
||||
[status-im.chat.styles.animations :as style]
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
(:require-macros [status-im.utils.views :refer [defview]])
|
||||
(:require [re-frame.core :refer [subscribe dispatch]]
|
||||
[status-im.ui.components.react :refer [view
|
||||
text
|
||||
icon
|
||||
emoji-picker]]
|
||||
text
|
||||
icon
|
||||
emoji-picker]]
|
||||
[status-im.chat.styles.input.emoji :as style]
|
||||
[status-im.i18n :refer [label]]))
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
(ns status-im.constants
|
||||
(:require [status-im.i18n :as i18n]
|
||||
[status-im.utils.ethereum.core :as ethereum]
|
||||
[status-im.utils.types :as types]
|
||||
[status-im.utils.config :as config]))
|
||||
|
||||
|
@ -43,24 +44,14 @@
|
|||
:raw-config config)]))
|
||||
(into {})))
|
||||
|
||||
(def mainnet-id 1)
|
||||
(def ropsten-id 3)
|
||||
(def rinkeby-id 4)
|
||||
|
||||
(defn get-testnet-name [testnet-id]
|
||||
(cond
|
||||
(= testnet-id ropsten-id) "Ropsten"
|
||||
(= testnet-id rinkeby-id) "Rinkeby"
|
||||
:else "Unknown"))
|
||||
|
||||
(def mainnet-networks
|
||||
{"mainnet" {:id "mainnet",
|
||||
:name "Mainnet",
|
||||
:config {:NetworkId mainnet-id
|
||||
:config {:NetworkId (ethereum/chain-id :mainnet)
|
||||
:DataDir "/ethereum/mainnet"}}
|
||||
"mainnet_rpc" {:id "mainnet_rpc",
|
||||
:name "Mainnet with upstream RPC",
|
||||
:config {:NetworkId mainnet-id
|
||||
:config {:NetworkId (ethereum/chain-id :mainnet)
|
||||
:DataDir "/ethereum/mainnet_rpc"
|
||||
:UpstreamConfig {:Enabled true
|
||||
:URL "https://mainnet.infura.io/z6GCTmjdP3FETEJmMBI4"}}}})
|
||||
|
@ -68,21 +59,21 @@
|
|||
(def testnet-networks
|
||||
{"testnet" {:id "testnet",
|
||||
:name "Ropsten",
|
||||
:config {:NetworkId ropsten-id
|
||||
:config {:NetworkId (ethereum/chain-id :ropsten)
|
||||
:DataDir "/ethereum/testnet"}}
|
||||
"testnet_rpc" {:id "testnet_rpc",
|
||||
:name "Ropsten with upstream RPC",
|
||||
:config {:NetworkId ropsten-id
|
||||
:config {:NetworkId (ethereum/chain-id :ropsten)
|
||||
:DataDir "/ethereum/testnet_rpc"
|
||||
:UpstreamConfig {:Enabled true
|
||||
:URL "https://ropsten.infura.io/z6GCTmjdP3FETEJmMBI4"}}}
|
||||
"rinkeby" {:id "rinkeby",
|
||||
:name "Rinkeby",
|
||||
:config {:NetworkId rinkeby-id
|
||||
:config {:NetworkId (ethereum/chain-id :rinkeby)
|
||||
:DataDir "/ethereum/rinkeby"}}
|
||||
"rinkeby_rpc" {:id "rinkeby_rpc",
|
||||
:name "Rinkeby with upstream RPC",
|
||||
:config {:NetworkId rinkeby-id
|
||||
:config {:NetworkId (ethereum/chain-id :rinkeby)
|
||||
:DataDir "/ethereum/rinkeby_rpc"
|
||||
:UpstreamConfig {:Enabled true
|
||||
:URL "https://rinkeby.infura.io/z6GCTmjdP3FETEJmMBI4"}}}})
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
(:require [status-im.ui.components.react :as react]
|
||||
[status-im.ui.components.icons.vector-icons :as vector-icons]
|
||||
[status-im.ui.components.context-menu :refer [context-menu]]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.ui.components.common.styles :as styles]
|
||||
[status-im.utils.ethereum.core :as ethereum]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.i18n :as i18n]))
|
||||
|
||||
(defn top-shadow []
|
||||
|
@ -62,13 +63,12 @@
|
|||
[top-shadow]])
|
||||
|
||||
(defview network-info [{:keys [text-color]}]
|
||||
(letsubs [testnet? [:testnet?]
|
||||
testnet-name [:testnet-name]]
|
||||
(letsubs [network-id [:get-network-id]]
|
||||
[react/view
|
||||
[react/view styles/network-container
|
||||
[react/view styles/network-icon
|
||||
[vector-icons/icon :icons/network {:color :white}]]
|
||||
[react/text {:style (styles/network-text text-color)}
|
||||
(if testnet?
|
||||
(i18n/label :t/testnet-text {:testnet testnet-name})
|
||||
(if (ethereum/testnet? network-id)
|
||||
(i18n/label :t/testnet-text {:testnet (get-in ethereum/chain-ids [network-id :name] "Unknown")})
|
||||
(i18n/label :t/mainnet-text))]]]))
|
||||
|
|
|
@ -49,7 +49,8 @@
|
|||
:margin-right 16})
|
||||
|
||||
(def right-item-wrapper
|
||||
{:margin-right 16})
|
||||
{:margin-right 16
|
||||
:justify-content :center})
|
||||
|
||||
(def base-separator
|
||||
{:height 1
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
(:require [re-frame.core :refer [subscribe dispatch]]
|
||||
[reagent.core :as r]
|
||||
[status-im.ui.components.react :refer [view
|
||||
text
|
||||
animated-view
|
||||
linear-gradient
|
||||
get-dimensions]]
|
||||
text
|
||||
animated-view
|
||||
linear-gradient
|
||||
get-dimensions]]
|
||||
[status-im.ui.components.sync-state.styles :as st]
|
||||
[status-im.ui.components.animation :as anim]
|
||||
[taoensso.timbre :as log]))
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
(ns status-im.ui.screens.network-settings.subs
|
||||
(:require [re-frame.core :refer [reg-sub subscribe]]
|
||||
[status-im.constants :as constants]))
|
||||
(:require [re-frame.core :refer [reg-sub subscribe]]))
|
||||
|
||||
(reg-sub
|
||||
:get-current-account-network
|
||||
|
@ -14,16 +13,4 @@
|
|||
:<- [:get :networks/networks]
|
||||
:<- [:get :network]
|
||||
(fn [[networks network]]
|
||||
(get-in networks [network :raw-config :NetworkId])))
|
||||
|
||||
(reg-sub
|
||||
:testnet?
|
||||
:<- [:get-network-id]
|
||||
(fn [network-id]
|
||||
(contains? #{constants/rinkeby-id constants/ropsten-id} network-id)))
|
||||
|
||||
(reg-sub
|
||||
:testnet-name
|
||||
:<- [:get-network-id]
|
||||
(fn [network-id]
|
||||
(constants/get-testnet-name network-id)))
|
||||
(get-in networks [network :raw-config :NetworkId])))
|
|
@ -8,7 +8,7 @@
|
|||
[status-im.i18n :refer [label]]
|
||||
[status-im.ui.screens.profile.qr-code.styles :as styles]
|
||||
[status-im.utils.money :as money]
|
||||
[status-im.utils.eip.eip681 :as eip681])
|
||||
[status-im.utils.ethereum.eip681 :as eip681])
|
||||
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
|
||||
|
||||
(defview qr-code-view []
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
[status-im.utils.handlers :as u :refer [register-handler]]
|
||||
[status-im.utils.utils :as utils]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.eip.eip681 :as eip681]))
|
||||
[status-im.utils.ethereum.eip681 :as eip681]))
|
||||
|
||||
(defmethod nav/preload-data! :qr-scanner
|
||||
[db [_ _ identifier]]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
(ns status-im.ui.screens.wallet.choose-recipient.events
|
||||
(:require [status-im.constants :as constants]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.eip.eip681 :as eip681]
|
||||
[status-im.utils.ethereum.eip681 :as eip681]
|
||||
[status-im.utils.handlers :as handlers]))
|
||||
|
||||
(handlers/register-handler-db
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
[status-im.native-module.core :as status]
|
||||
[status-im.ui.screens.wallet.db :as wallet.db]
|
||||
[status-im.ui.screens.wallet.navigation]
|
||||
[status-im.utils.ethereum.core :as ethereum]
|
||||
[status-im.utils.ethereum.erc20 :as erc20]
|
||||
[status-im.utils.ethereum.tokens :as tokens]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.utils.prices :as prices]
|
||||
[status-im.utils.transactions :as transactions]
|
||||
|
@ -22,6 +25,18 @@
|
|||
(on-error err))))
|
||||
(on-error "web3 or account-id not available")))
|
||||
|
||||
(defn get-token-balance [{:keys [web3 contract account-id on-success on-error]}]
|
||||
(if (and web3 contract account-id)
|
||||
(erc20/balance-of
|
||||
web3
|
||||
contract
|
||||
(ethereum/normalized-address account-id)
|
||||
(fn [err resp]
|
||||
(if-not err
|
||||
(on-success resp)
|
||||
(on-error err))))
|
||||
(on-error "web3, contract or account-id not available")))
|
||||
|
||||
(defn assoc-error-message [db error-type err]
|
||||
(assoc-in db [:wallet :errors error-type] (or (when err (str err))
|
||||
:unknown-error)))
|
||||
|
@ -39,6 +54,17 @@
|
|||
:on-success #(dispatch [success-event %])
|
||||
:on-error #(dispatch [error-event %])})))
|
||||
|
||||
(reg-fx
|
||||
:get-tokens-balance
|
||||
(fn [{:keys [web3 symbols network account-id success-event error-event]}]
|
||||
(doseq [symbol symbols]
|
||||
(let [contract (:address (tokens/token-for (ethereum/network network) symbol))]
|
||||
(get-token-balance {:web3 web3
|
||||
:contract contract
|
||||
:account-id account-id
|
||||
:on-success #(dispatch [success-event symbol %])
|
||||
:on-error #(dispatch [error-event %])})))))
|
||||
|
||||
(reg-fx
|
||||
:get-transactions
|
||||
(fn [{:keys [network account-id success-event error-event]}]
|
||||
|
@ -60,11 +86,17 @@
|
|||
|
||||
(handlers/register-handler-fx
|
||||
:update-wallet
|
||||
(fn [{{:keys [web3 accounts/current-account-id network] :as db} :db} [_ a]]
|
||||
(fn [{{:keys [web3 accounts/current-account-id network] :as db} :db} [_ symbols]]
|
||||
{:get-balance {:web3 web3
|
||||
:account-id current-account-id
|
||||
:success-event :update-balance-success
|
||||
:error-event :update-balance-fail}
|
||||
:get-tokens-balance {:web3 web3
|
||||
:account-id current-account-id
|
||||
:symbols symbols
|
||||
:network network
|
||||
:success-event :update-token-balance-success
|
||||
:error-event :update-token-balance-fail}
|
||||
:get-prices {:from "ETH"
|
||||
:to "USD"
|
||||
:success-event :update-prices-success
|
||||
|
@ -105,7 +137,7 @@
|
|||
:update-balance-success
|
||||
(fn [db [_ balance]]
|
||||
(-> 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]]
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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?]]]))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 []
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
|
@ -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}))
|
|
@ -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)
|
|
@ -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))))
|
|
@ -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))
|
|
@ -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)))
|
|
@ -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)))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"})))))
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
(ns status-im.utils.ethereum.tokens)
|
||||
|
||||
(def ethereum {})
|
||||
|
||||
(def all {})
|
Loading…
Reference in New Issue