[refactor] remove ethereum `call` and `call-params`

- use `json-rpc/eth-call` and `json-rpc/eth-transaction-call`
everywhere
- move all conversions to abi-spec
This commit is contained in:
yenda 2019-05-20 02:21:38 +02:00
parent fc4c772c0b
commit b6fecd4e1c
No known key found for this signature in database
GPG Key ID: 0095623C0069DCE6
18 changed files with 502 additions and 476 deletions

View File

@ -1,61 +1,17 @@
(ns status-im.ethereum.contracts (ns status-im.ethereum.contracts
(:require [re-frame.core :as re-frame] (:require [status-im.utils.ethereum.core :as ethereum]))
[status-im.utils.ethereum.abi-spec :as abi-spec]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.fx :as fx]
[status-im.utils.money :as money]
[status-im.wallet.core :as wallet]))
(def contracts (def contracts
{:status/tribute-to-talk {:status/snt
{:address {:mainnet "0x744d70fdbe2ba4cf95131626614a1763df805b9e"
{:mainnet nil :testnet "0xc55cf4b03948d7ebc8b9e8bad92643703811d162"}
:testnet "0x3da3fc53e24707f36c5b4433b442e896c4955f0e" :status/tribute-to-talk
:rinkeby nil} {:testnet "0x3da3fc53e24707f36c5b4433b442e896c4955f0e"}
:methods :status/stickers
{:get-manifest {:testnet "0x39d16CdB56b5a6a89e1A397A13Fe48034694316E"}})
{:signature "getManifest(address)"
:outputs ["bytes"]}
:set-manifest
{:signature "setManifest(bytes)"
:write? true}}}})
(re-frame/reg-fx (defn get-address
::call
(fn [{:keys [address data callback]}]
(ethereum/call {:to address
:data data}
callback)))
(defn get-contract-address
[db contract] [db contract]
(let [chain-keyword (-> (get-in db [:account/account :networks (:network db)]) (let [chain-keyword (-> (get-in db [:account/account :networks (:network db)])
ethereum/network->chain-keyword)] ethereum/network->chain-keyword)]
(get-in contracts [contract :address chain-keyword]))) (get-in contracts [contract chain-keyword])))
(fx/defn call
[{:keys [db] :as cofx}
{:keys [contract contract-address method params
callback on-result on-error details]}]
(when-let [contract-address (or contract-address
(get-contract-address db contract))]
(let [{:keys [signature outputs write?]}
(get-in contracts [contract :methods method])
data (abi-spec/encode signature params)]
(if write?
(wallet/open-sign-transaction-flow
cofx
(merge {:to contract-address
:data data
:id "approve"
:symbol :ETH
:method "eth_sendTransaction"
:amount (money/bignumber 0)
:on-result on-result
:on-error on-error}
details))
{::call {:address contract-address
:data data
:callback #(callback (if (empty? outputs)
%
(abi-spec/decode % outputs)))}}))))

View File

@ -3,22 +3,94 @@
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[status-im.accounts.core :as accounts] [status-im.accounts.core :as accounts]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.ethereum.contracts :as contracts]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.ui.screens.navigation :as navigation] [status-im.ui.screens.navigation :as navigation]
[status-im.utils.ethereum.abi-spec :as abi-spec] [status-im.utils.ethereum.abi-spec :as abi-spec]
[status-im.utils.ethereum.core :as ethereum] [status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.stickers :as ethereum.stickers]
[status-im.utils.fx :as fx] [status-im.utils.fx :as fx]
[status-im.utils.money :as money]
[status-im.utils.multihash :as multihash] [status-im.utils.multihash :as multihash]
[status-im.utils.utils :as utils]
[status-im.wallet.core :as wallet])) [status-im.wallet.core :as wallet]))
(fx/defn init-stickers-packs [{:keys [db]}] (defn pack-data-callback
[id open?]
(fn [[category owner mintable timestamp price contenthash]]
(let [proto-code (subs contenthash 2 4)
hash (when contenthash
(multihash/base58 (multihash/create :sha2-256 (subs contenthash 12))))]
(when (and (#{constants/swarm-proto-code constants/ipfs-proto-code}
proto-code) hash)
(re-frame/dispatch [:stickers/load-pack proto-code hash id price open?])))))
(re-frame/reg-fx
:stickers/set-pending-timout-fx
(fn []
(utils/set-timeout #(re-frame/dispatch [:stickers/pending-timout])
10000)))
(defn eth-call-pack-data
[contract id open?]
(json-rpc/eth-call
{:contract contract
;; Returns vector of pack data parameters by pack id:
;; [category owner mintable timestamp price contenthash]
:method "getPackData(uint256)"
:params [id]
:outputs ["bytes4[]" "address" "bool" "uint256" "uint256" "bytes"]
:on-success (pack-data-callback id open?)}))
(re-frame/reg-fx
:stickers/pack-data-fx
(fn [[contract id]]
(eth-call-pack-data contract id true)))
(re-frame/reg-fx
:stickers/load-packs-fx
(fn [[contract]]
(json-rpc/eth-call
{:contract contract
;; Returns number of packs registered in the contract
:method "packCount()"
:outputs ["uint256"]
:on-success
(fn [[count]]
(dotimes [id count]
(eth-call-pack-data contract id false)))})))
(re-frame/reg-fx
:stickers/owned-packs-fx
(fn [[contract address]]
(json-rpc/eth-call
{:contract contract
;; Returns vector of owned tokens ids in the contract by address
:method "tokensOwnedBy(address)"
:params [address]
:outputs ["uint256[]"]
:on-success
(fn [[tokens]]
(doseq [id tokens]
(json-rpc/eth-call
{:contract contract
;; Returns pack id in the contract by token id
:method "tokenPackId(uint256)"
:params [id]
:outputs ["uint256"]
:on-success
(fn [[pack-id]]
(re-frame/dispatch [:stickers/pack-owned pack-id]))})))})))
(fx/defn init-stickers-packs
[{:keys [db]}]
(let [sticker-packs (into {} (map #(let [pack (edn/read-string %)] (let [sticker-packs (into {} (map #(let [pack (edn/read-string %)]
(vector (:id pack) pack)) (vector (:id pack) pack))
(get-in db [:account/account :stickers])))] (get-in db [:account/account :stickers])))]
{:db (assoc db :stickers/packs-installed sticker-packs :stickers/packs sticker-packs)})) {:db (assoc db
:stickers/packs-installed sticker-packs
:stickers/packs sticker-packs)}))
(fx/defn install-stickers-pack [{{:account/keys [account] :as db} :db :as cofx} id] (fx/defn install-stickers-pack
[{{:account/keys [account] :as db} :db :as cofx} id]
(let [pack (get-in db [:stickers/packs id])] (let [pack (get-in db [:stickers/packs id])]
(fx/merge (fx/merge
cofx cofx
@ -27,125 +99,91 @@
(assoc :stickers/selected-pack id))} (assoc :stickers/selected-pack id))}
(accounts/update-stickers (conj (:stickers account) (pr-str pack)))))) (accounts/update-stickers (conj (:stickers account) (pr-str pack))))))
(fx/defn load-sticker-pack-success [{:keys [db] :as cofx} edn-string id price open?] (fx/defn load-sticker-pack-success
[{:keys [db] :as cofx} edn-string id price open?]
(let [pack (assoc (get (edn/read-string edn-string) 'meta) (let [pack (assoc (get (edn/read-string edn-string) 'meta)
:id id :price price)] :id id :price price)]
(fx/merge cofx (fx/merge cofx
{:db (-> db (assoc-in [:stickers/packs id] pack))} {:db (assoc-in db [:stickers/packs id] pack)}
#(when open? (navigation/navigate-to-cofx % :stickers-pack-modal pack))))) #(when open?
(navigation/navigate-to-cofx % :stickers-pack-modal pack)))))
(defn pack-data-callback [id open?]
(fn [[category owner mintable timestamp price contenthash]]
(let [proto-code (subs contenthash 2 4)
hash (when contenthash (multihash/base58 (multihash/create :sha2-256 (subs contenthash 12))))]
(when (and (#{constants/swarm-proto-code constants/ipfs-proto-code} proto-code) hash)
(re-frame/dispatch [:stickers/load-pack proto-code hash id price open?])))))
(fx/defn open-sticker-pack (fx/defn open-sticker-pack
[{{:keys [network] :stickers/keys [packs packs-installed] :as db} :db :as cofx} id] [{{:stickers/keys [packs packs-installed] :as db} :db :as cofx} id]
(when id (when id
(let [pack (or (get packs-installed id) (get packs id)) (let [pack (or (get packs-installed id)
network (get-in db [:account/account :networks network])] (get packs id))
contract-address (contracts/get-address db :status/stickers)]
(if pack (if pack
(navigation/navigate-to-cofx cofx :stickers-pack-modal pack) (navigation/navigate-to-cofx cofx :stickers-pack-modal pack)
{:stickers/pack-data-fx [network id true]})))) (when contract-address
{:stickers/pack-data-fx [contract-address id]})))))
(fx/defn load-pack [cofx proto-code hash id price open?] (fx/defn load-pack
{:http-get {:url (str (if (= constants/swarm-proto-code proto-code) [cofx proto-code hash id price open?]
"https://swarm-gateways.net/bzz:/" {:http-get {:url (str (if (= constants/swarm-proto-code proto-code)
"https://ipfs.infura.io/ipfs/") "https://swarm-gateways.net/bzz:/"
hash) "https://ipfs.infura.io/ipfs/")
:success-event-creator (fn [o] hash)
[:stickers/load-sticker-pack-success o id price open?]) :success-event-creator
:failure-event-creator (constantly nil)}}) (fn [o]
[:stickers/load-sticker-pack-success o id price open?])
:failure-event-creator
(constantly nil)}})
(fx/defn load-packs [{{:keys [network] :as db} :db}] (fx/defn load-packs
(let [network (get-in db [:account/account :networks network]) [{:keys [db]}]
address (ethereum/normalized-address (get-in db [:account/account :address]))] (let [contract (contracts/get-address db :status/stickers)
{:stickers/owned-packs-fx [network address] address (ethereum/current-address db)]
:stickers/load-packs-fx [network]})) (when contract
{:stickers/owned-packs-fx [contract address]
:stickers/load-packs-fx [contract]})))
(defn prepare-transaction [id tx on-result] (fx/defn approve-pack
(merge {:id id [{db :db :as cofx} pack-id price]
:symbol :ETH (let [address (ethereum/current-address db)
:method constants/web3-send-transaction chain (ethereum/chain-keyword db)
:amount (money/bignumber 0)} stickers-contract (contracts/get-address db :status/stickers)
(when on-result {:on-result on-result}) snt-contract (contracts/get-address db :status/snt)]
tx)) (wallet/eth-transaction-call
cofx
(def snt-contracts {:contract snt-contract
{:mainnet "0x744d70fdbe2ba4cf95131626614a1763df805b9e" :method "approveAndCall(address,uint256,bytes)"
:testnet "0xc55cf4b03948d7ebc8b9e8bad92643703811d162" :params [stickers-contract
:rinkeby nil}) price
(abi-spec/encode "buyToken(uint256,address)"
(fx/defn approve-pack [{db :db} pack-id price] [pack-id address])]
(let [network (get-in db [:account/account :networks (:network db)]) :on-result [:stickers/pending-pack pack-id]})))
address (ethereum/normalized-address (get-in db [:account/account :address]))
chain (ethereum/network->chain-keyword network)
stickers-contract (get ethereum.stickers/contracts chain)
data (abi-spec/encode "buyToken(uint256,address)" [pack-id address])
tx-object {:to (get snt-contracts chain)
:data (abi-spec/encode "approveAndCall(address,uint256,bytes)" [stickers-contract price data])}]
(wallet/open-modal-wallet-for-transaction
db
(prepare-transaction "approve" tx-object [:stickers/pending-pack pack-id])
tx-object)))
(fx/defn pending-pack (fx/defn pending-pack
[{{:keys [network] :as db} :db :as cofx} id] [{:keys [db] :as cofx} id]
(let [network (get-in db [:account/account :networks network]) (let [contract (contracts/get-address db :status/stickers)
address (ethereum/normalized-address (get-in db [:account/account :address]))] address (ethereum/current-address db)]
(fx/merge cofx (when contract
{:db (update db :stickers/packs-pendning conj id) (fx/merge cofx
:stickers/owned-packs-fx [network address]} {:db (update db :stickers/packs-pending conj id)
(navigation/navigate-to-clean :wallet-transaction-sent-modal {}) :stickers/owned-packs-fx [contract address]}
#(when (zero? (count (:stickers/packs-pendning db))) (navigation/navigate-to-clean :wallet-transaction-sent-modal {})
{:stickers/set-pending-timout-fx nil})))) #(when (zero? (count (:stickers/packs-pending db)))
{:stickers/set-pending-timout-fx nil})))))
(fx/defn pending-timeout (fx/defn pending-timeout
[{{:keys [network] :stickers/keys [packs-pendning packs-owned] :as db} :db}] [{{:stickers/keys [packs-pending packs-owned] :as db} :db}]
(let [packs-diff (clojure.set/difference packs-pendning packs-owned) (let [packs-diff (clojure.set/difference packs-pending packs-owned)
network (get-in db [:account/account :networks network]) contract (contracts/get-address db :status/stickers)
address (ethereum/normalized-address (get-in db [:account/account :address]))] address (ethereum/current-address db)]
(merge {:db (assoc db :stickers/packs-pendning packs-diff)} (when contract
(when-not (zero? (count packs-diff)) (merge {:db (assoc db :stickers/packs-pending packs-diff)}
{:stickers/owned-packs-fx [network address] (when-not (zero? (count packs-diff))
:stickers/set-pending-timout-fx nil})))) {:stickers/owned-packs-fx [contract address]
:stickers/set-pending-timout-fx nil})))))
(fx/defn pack-owned [{db :db} id] (fx/defn pack-owned [{db :db} id]
{:db (update db :stickers/packs-owned conj id)}) {:db (update db :stickers/packs-owned conj id)})
(fx/defn get-owned-pack (fx/defn get-owned-pack
[{{:keys [network] :as db} :db}] [{:keys [db]}]
(let [address (ethereum/normalized-address (get-in db [:account/account :address]))] (let [contract (contracts/get-address db :status/stickers)
{:stickers/owned-packs-fx [network address]})) address (ethereum/current-address db)]
(when contract
(re-frame/reg-fx {:stickers/owned-packs-fx [contract address]})))
:stickers/pack-data-fx
(fn [[network id open?]]
(when-let [contract (get ethereum.stickers/contracts (ethereum/network->chain-keyword network))]
(ethereum.stickers/pack-data contract id (pack-data-callback id open?)))))
(re-frame/reg-fx
:stickers/set-pending-timout-fx
(fn []
(js/setTimeout #(re-frame/dispatch [:stickers/pending-timout]) 10000)))
(re-frame/reg-fx
:stickers/load-packs-fx
(fn [[network]]
(when-let [contract (get ethereum.stickers/contracts (ethereum/network->chain-keyword network))]
(ethereum.stickers/pack-count contract
(fn [count]
(dotimes [n count]
(ethereum.stickers/pack-data contract n (pack-data-callback n false))))))))
(re-frame/reg-fx
:stickers/owned-packs-fx
(fn [[network address]]
(when-let [contract (get ethereum.stickers/contracts (ethereum/network->chain-keyword network))]
(ethereum.stickers/owned-tokens contract address
(fn [tokens]
(doseq [n tokens]
(ethereum.stickers/token-pack-id contract n
#(re-frame/dispatch [:stickers/pack-owned %]))))))))

View File

@ -126,7 +126,7 @@
(reg-root-key-sub :stickers/packs :stickers/packs) (reg-root-key-sub :stickers/packs :stickers/packs)
(reg-root-key-sub :stickers/installed-packs :stickers/packs-installed) (reg-root-key-sub :stickers/installed-packs :stickers/packs-installed)
(reg-root-key-sub :stickers/packs-owned :stickers/packs-owned) (reg-root-key-sub :stickers/packs-owned :stickers/packs-owned)
(reg-root-key-sub :stickers/packs-pendning :stickers/packs-pendning) (reg-root-key-sub :stickers/packs-pending :stickers/packs-pending)
;;mailserver ;;mailserver
(reg-root-key-sub :mailserver/current-id :mailserver/current-id) (reg-root-key-sub :mailserver/current-id :mailserver/current-id)
@ -768,7 +768,7 @@
:<- [:stickers/packs] :<- [:stickers/packs]
:<- [:stickers/installed-packs] :<- [:stickers/installed-packs]
:<- [:stickers/packs-owned] :<- [:stickers/packs-owned]
:<- [:stickers/packs-pendning] :<- [:stickers/packs-pending]
(fn [[packs installed owned pending]] (fn [[packs installed owned pending]]
(map (fn [{:keys [id] :as pack}] (map (fn [{:keys [id] :as pack}]
(cond-> pack (cond-> pack
@ -1167,9 +1167,8 @@
(let [{:keys [gas-used gas-price hash timestamp type token value] (let [{:keys [gas-used gas-price hash timestamp type token value]
:as transaction} :as transaction}
(get transactions current-transaction) (get transactions current-transaction)
native-currency-text (-> native-currency native-currency-text (name (or (:symbol-display native-currency)
:symbol-display (:symbol native-currency)))]
name)]
(when transaction (when transaction
(merge transaction (merge transaction
{:gas-price-eth (if gas-price {:gas-price-eth (if gas-price

View File

@ -5,12 +5,14 @@
[status-im.accounts.update.core :as accounts.update] [status-im.accounts.update.core :as accounts.update]
[status-im.contact.db :as contact.db] [status-im.contact.db :as contact.db]
[status-im.ethereum.contracts :as contracts] [status-im.ethereum.contracts :as contracts]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.ipfs.core :as ipfs] [status-im.ipfs.core :as ipfs]
[status-im.tribute-to-talk.db :as tribute-to-talk.db] [status-im.tribute-to-talk.db :as tribute-to-talk.db]
[status-im.ui.screens.navigation :as navigation] [status-im.ui.screens.navigation :as navigation]
[status-im.utils.contenthash :as contenthash] [status-im.utils.contenthash :as contenthash]
[status-im.utils.ethereum.core :as ethereum] [status-im.utils.ethereum.core :as ethereum]
[status-im.utils.fx :as fx] [status-im.utils.fx :as fx]
[status-im.wallet.core :as wallet]
[taoensso.timbre :as log])) [taoensso.timbre :as log]))
(fx/defn update-settings (fx/defn update-settings
@ -195,25 +197,35 @@
[:tribute-to-talk.callback/fetch-manifest-success [:tribute-to-talk.callback/fetch-manifest-success
identity manifest])))})) identity manifest])))}))
(re-frame/reg-fx
:tribute-to-talk/get-manifest
(fn [{:keys [contract address on-success]}]
(json-rpc/eth-call
{:contract contract
:method "getManifest(address)"
:params [address]
:outputs ["bytes"]
:on-success on-success})))
(fx/defn check-manifest (fx/defn check-manifest
[{:keys [db] :as cofx} identity] [{:keys [db] :as cofx} public-key]
(or (contracts/call cofx (if-let [contract (contracts/get-address db :status/tribute-to-talk)]
{:contract :status/tribute-to-talk (let [address (contact.db/public-key->address public-key)]
:method :get-manifest {:tribute-to-talk/get-manifest
:params [(contact.db/public-key->address identity)] {:contract contract
:return-params ["bytes"] :address address
:callback :on-success
#(re-frame/dispatch (fn [[contenthash]]
(if-let [contenthash (first %)] (re-frame/dispatch
[:tribute-to-talk.callback/check-manifest-success (if contenthash
identity [:tribute-to-talk.callback/check-manifest-success
contenthash] public-key
[:tribute-to-talk.callback/no-manifest-found identity]))}) contenthash]
;; `contracts/call` returns nil if there is no contract for the current network [:tribute-to-talk.callback/no-manifest-found public-key])))}})
;; update settings if checking own manifest or do nothing otherwise ;; update settings if checking own manifest or do nothing otherwise
(when-let [me? (= identity (when-let [me? (= identity
(get-in cofx [:db :account/account :public-key]))] (get-in cofx [:db :account/account :public-key]))]
(update-settings cofx nil)))) (update-settings cofx nil))))
(fx/defn check-own-manifest (fx/defn check-own-manifest
[cofx] [cofx]
@ -224,13 +236,17 @@
(let [contenthash (when hash (let [contenthash (when hash
(contenthash/encode {:hash hash (contenthash/encode {:hash hash
:namespace :ipfs}))] :namespace :ipfs}))]
(or (contracts/call cofx (if-let [contract (contracts/get-address db :status/tribute-to-talk)]
{:contract :status/tribute-to-talk (wallet/eth-transaction-call
:method :set-manifest cofx
:params [contenthash] {:contract contract
:on-result [:tribute-to-talk.callback/set-manifest-transaction-completed] :method "setManifest(bytes)"
:on-error [:tribute-to-talk.callback/set-manifest-transaction-failed]}) :params [contenthash]
{:db (assoc-in db [:navigation/screen-params :tribute-to-talk :state] :transaction-failed)}))) :on-result [:tribute-to-talk.callback/set-manifest-transaction-completed]
:on-error [:tribute-to-talk.callback/set-manifest-transaction-failed]})
{:db (assoc-in db
[:navigation/screen-params :tribute-to-talk :state]
:transaction-failed)})))
(defn remove (defn remove
[{:keys [db] :as cofx}] [{:keys [db] :as cofx}]

View File

@ -14,15 +14,14 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:new-chat/set-new-identity :new-chat/set-new-identity
(fn [{{:keys [network network-status] :as db} :db} [_ new-identity]] (fn [{{:keys [network-status] :as db} :db} [_ new-identity]]
(let [is-public-key? (and (string? new-identity) (let [is-public-key? (and (string? new-identity)
(string/starts-with? new-identity "0x"))] (string/starts-with? new-identity "0x"))]
(merge {:db (assoc db (merge {:db (assoc db
:contacts/new-identity new-identity :contacts/new-identity new-identity
:contacts/new-identity-error (db/validate-pub-key db new-identity))} :contacts/new-identity-error (db/validate-pub-key db new-identity))}
(when-not is-public-key? (when-not is-public-key?
(let [network (get-in db [:account/account :networks network]) (let [chain (ethereum/chain-keyword db)]
chain (ethereum/network->chain-keyword network)]
{:resolve-public-key {:registry (get ens/ens-registries chain) {:resolve-public-key {:registry (get ens/ens-registries chain)
:ens-name (if (ens/is-valid-eth-name? new-identity) :ens-name (if (ens/is-valid-eth-name? new-identity)
new-identity new-identity

View File

@ -93,7 +93,8 @@
[stickers-panel (map #(assoc % :pack id) stickers) window-width])])) [stickers-panel (map #(assoc % :pack id) stickers) window-width])]))
(defn pack-icon [{:keys [id on-press background-color] (defn pack-icon [{:keys [id on-press background-color]
:or {on-press #(re-frame/dispatch [:stickers/select-pack id])}} icon] :or {on-press #(re-frame/dispatch [:stickers/select-pack id])}}
icon]
[react/touchable-highlight {:on-press on-press} [react/touchable-highlight {:on-press on-press}
[react/view {:style {:align-items :center}} [react/view {:style {:align-items :center}}
[react/view {:style (styles/pack-icon background-color icon-size icon-horizontal-margin)} [react/view {:style (styles/pack-icon background-color icon-size icon-horizontal-margin)}
@ -148,7 +149,8 @@
[vector-icons/icon :stickers-icons/recent {:color colors/gray}]] [vector-icons/icon :stickers-icons/recent {:color colors/gray}]]
(for [{:keys [id thumbnail]} installed-packs] (for [{:keys [id thumbnail]} installed-packs]
^{:key id} ^{:key id}
[pack-icon {:id id} [pack-icon {:id id
:background-color colors/white}
[react/image {:style {:width icon-size :height icon-size :border-radius (/ icon-size 2)} [react/image {:style {:width icon-size :height icon-size :border-radius (/ icon-size 2)}
:source {:uri thumbnail}}]])] :source {:uri thumbnail}}]])]
[scroll-indicator]]]]])) [scroll-indicator]]]]]))

View File

@ -61,7 +61,7 @@
:push-notifications/stored {} :push-notifications/stored {}
:registry {} :registry {}
:stickers/packs-owned #{} :stickers/packs-owned #{}
:stickers/packs-pendning #{} :stickers/packs-pending #{}
:hardwallet {:nfc-supported? false :hardwallet {:nfc-supported? false
:nfc-enabled? false :nfc-enabled? false
:pin {:original [] :pin {:original []
@ -192,7 +192,7 @@
(spec/def :stickers/packs (spec/nilable map?)) (spec/def :stickers/packs (spec/nilable map?))
(spec/def :stickers/packs-owned (spec/nilable set?)) (spec/def :stickers/packs-owned (spec/nilable set?))
(spec/def :stickers/packs-pendning (spec/nilable set?)) (spec/def :stickers/packs-pending (spec/nilable set?))
(spec/def :stickers/packs-installed (spec/nilable map?)) (spec/def :stickers/packs-installed (spec/nilable map?))
(spec/def :stickers/selected-pack (spec/nilable any?)) (spec/def :stickers/selected-pack (spec/nilable any?))
(spec/def :stickers/recent (spec/nilable vector?)) (spec/def :stickers/recent (spec/nilable vector?))
@ -269,7 +269,7 @@
:stickers/selected-pack :stickers/selected-pack
:stickers/recent :stickers/recent
:stickers/packs-owned :stickers/packs-owned
:stickers/packs-pendning :stickers/packs-pending
:bottom-sheet/show? :bottom-sheet/show?
:bottom-sheet/view :bottom-sheet/view
:bottom-sheet/options :bottom-sheet/options

View File

@ -6,6 +6,7 @@
[status-im.native-module.core :as status] [status-im.native-module.core :as status]
[status-im.transport.utils :as transport.utils] [status-im.transport.utils :as transport.utils]
[status-im.ui.screens.navigation :as navigation] [status-im.ui.screens.navigation :as navigation]
[status-im.utils.ethereum.abi-spec :as abi-spec]
[status-im.utils.ethereum.core :as ethereum] [status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.tokens :as tokens] [status-im.utils.ethereum.tokens :as tokens]
[status-im.utils.fx :as fx] [status-im.utils.fx :as fx]
@ -29,10 +30,13 @@
{:keys [from to value gas gasPrice]} on-completed masked-password] {:keys [from to value gas gasPrice]} on-completed masked-password]
(let [contract (:address (tokens/symbol->token all-tokens (keyword chain) symbol))] (let [contract (:address (tokens/symbol->token all-tokens (keyword chain) symbol))]
(status/send-transaction (types/clj->json (status/send-transaction (types/clj->json
(merge (ethereum/call-params contract "transfer(address,uint256)" to value) {:to contract
{:from from :from from
:gas gas :data (abi-spec/encode
:gasPrice gasPrice})) "transfer(address,uint256)"
[to value])
:gas gas
:gasPrice gasPrice})
(security/safe-unmask-data masked-password) (security/safe-unmask-data masked-password)
on-completed))) on-completed)))
@ -66,9 +70,10 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:wallet/send-transaction :wallet/send-transaction
(fn [{{:keys [chain] :as db} :db} _] (fn [{{:keys [chain] :as db} :db} _]
(let [{:keys [password symbol in-progress?] :as transaction} (get-in db [:wallet :send-transaction]) (let [{:keys [password symbol in-progress?] :as transaction}
(get-in db [:wallet :send-transaction])
all-tokens (:wallet/all-tokens db) all-tokens (:wallet/all-tokens db)
from (get-in db [:account/account :address])] from (ethereum/current-address db)]
(when-not in-progress? (when-not in-progress?
{:db (-> db {:db (-> db
(assoc-in [:wallet :send-transaction :wrong-password?] false) (assoc-in [:wallet :send-transaction :wrong-password?] false)
@ -374,8 +379,9 @@
(wallet/prepare-send-transaction from transaction) (wallet/prepare-send-transaction from transaction)
(let [contract (:address (tokens/symbol->token all-tokens (keyword chain) symbol)) (let [contract (:address (tokens/symbol->token all-tokens (keyword chain) symbol))
{:keys [gas gasPrice to from value]} (wallet/prepare-send-transaction from transaction)] {:keys [gas gasPrice to from value]} (wallet/prepare-send-transaction from transaction)]
(merge (ethereum/call-params contract "transfer(address,uint256)" to value) (merge (abi-spec/encode "transfer(address,uint256)" [to value])
{:from from {:to contract
:from from
:gas gas :gas gas
:gasPrice gasPrice})))) :gasPrice gasPrice}))))

View File

@ -28,6 +28,9 @@
(when x (when x
(subs (.fromUtf8 utils x) 2))) (subs (.fromUtf8 utils x) 2)))
(defn hex-to-boolean [x]
(= x "0x0"))
(defn bytes-to-hex [x] (defn bytes-to-hex [x]
(when x (when x
(subs (.bytesToHex utils x) 2))) (subs (.bytesToHex utils x) 2)))

View File

@ -1,6 +1,5 @@
(ns status-im.utils.ethereum.core (ns status-im.utils.ethereum.core
(:require [clojure.string :as string] (:require [clojure.string :as string]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.js-dependencies :as dependencies] [status-im.js-dependencies :as dependencies]
[status-im.utils.ethereum.tokens :as tokens] [status-im.utils.ethereum.tokens :as tokens]
[status-im.utils.money :as money])) [status-im.utils.money :as money]))
@ -40,6 +39,10 @@
address address
(str hex-prefix address)))) (str hex-prefix address))))
(defn current-address [db]
(-> (get-in db [:account/account :address])
normalized-address))
(defn naked-address [s] (defn naked-address [s]
(when s (when s
(string/replace s hex-prefix ""))) (string/replace s hex-prefix "")))
@ -74,71 +77,6 @@
([s opts] ([s opts]
(.sha3 dependencies/Web3.prototype (str s) (clj->js opts)))) (.sha3 dependencies/Web3.prototype (str s) (clj->js opts))))
(defn hex->string [s]
(when s
(let [hex (.toString s)]
(loop [res "" i (if (string/starts-with? hex hex-prefix) 2 0)]
(if (and (< i (.-length hex)))
(recur
(if (= (.substr hex i 2) "00")
res
(str res (.fromCharCode js/String (js/parseInt (.substr hex i 2) 16))))
(+ i 2))
res)))))
(defn hex->boolean [s]
(= s "0x0"))
(defn boolean->hex [b]
(if b "0x0" "0x1"))
(defn hex->int [s]
(if (= s hex-prefix)
0
(js/parseInt s 16)))
(defn int->hex [i]
(.toHex dependencies/Web3.prototype i))
(defn hex->bignumber [s]
(money/bignumber (if (= s hex-prefix) 0 s)))
(defn hex->address
"When hex value is 66 char in length (2 for 0x, 64 for
the 32 bytes used by abi-spec for an address), only keep
the part that constitute the address and normalize it,"
[s]
(when (= 66 (count s))
(normalized-address (subs s 26))))
(defn zero-pad-64 [s]
(str (apply str (drop (count s) (repeat 64 "0"))) s))
(defn string->hex [i]
(.fromAscii dependencies/Web3.prototype i))
(defn format-param [param]
(if (number? param)
(zero-pad-64 (str (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 [params callback]
(json-rpc/call
{:method "eth_call"
:params [params "latest"]
:on-success callback}))
(defn call-params [contract method-sig & params]
(let [data (apply format-call-params (sig->method-id method-sig) params)]
{:to contract :data data}))
(def default-transaction-gas (money/bignumber 21000)) (def default-transaction-gas (money/bignumber 21000))
(defn estimate-gas [symbol] (defn estimate-gas [symbol]

View File

@ -1,13 +1,18 @@
(ns status-im.utils.ethereum.eip165 (ns status-im.utils.ethereum.eip165
"Utility function related to [EIP165](https://eips.ethereum.org/EIPS/eip-165)" "Utility function related to [EIP165](https://eips.ethereum.org/EIPS/eip-165)"
(:require [status-im.utils.ethereum.core :as ethereum])) (:require [status-im.ethereum.json-rpc :as json-rpc]
[status-im.utils.ethereum.abi-spec :as abi-spec]))
(def supports-interface-hash "0x01ffc9a7") (def supports-interface-hash "0x01ffc9a7")
(def marker-hash "0xffffffff") (def marker-hash "0xffffffff")
(defn supports-interface? [contract hash cb] (defn supports-interface?
(ethereum/call (ethereum/call-params contract "supportsInterface(bytes4)" hash) [contract hash cb]
#(cb %))) (json-rpc/eth-call
{:contract contract
:method "supportsInterface(bytes4)"
:params [hash]
:on-success cb}))
(defn supports? (defn supports?
"Calls cb with true if `supportsInterface` is supported by this contract. "Calls cb with true if `supportsInterface` is supported by this contract.
@ -16,9 +21,9 @@
(supports-interface? (supports-interface?
contract contract
supports-interface-hash supports-interface-hash
#(if (true? (ethereum/hex->boolean %)) #(if (true? (abi-spec/hex-to-boolean %))
(supports-interface? contract (supports-interface? contract
marker-hash marker-hash
(fn [response] (fn [response]
(cb (false? (ethereum/hex->boolean response))))) (cb (false? (abi-spec/hex-to-boolean response)))))
(cb false)))) (cb false))))

View File

@ -6,8 +6,8 @@
" "
(:refer-clojure :exclude [name]) (:refer-clojure :exclude [name])
(:require [clojure.string :as string] (:require [clojure.string :as string]
[status-im.utils.ethereum.core :as ethereum] [status-im.ethereum.json-rpc :as json-rpc]
[status-im.utils.ethereum.abi-spec :as abi-spec])) [status-im.utils.ethereum.core :as ethereum]))
;; this is the addresses of ens registries for the different networks ;; this is the addresses of ens registries for the different networks
(def ens-registries (def ens-registries
@ -17,7 +17,7 @@
(def default-namehash "0000000000000000000000000000000000000000000000000000000000000000") (def default-namehash "0000000000000000000000000000000000000000000000000000000000000000")
(def default-address "0x0000000000000000000000000000000000000000") (def default-address "0x0000000000000000000000000000000000000000")
(def default-key "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") (def default-key "0x0400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
(defn namehash (defn namehash
[s] [s]
@ -35,28 +35,37 @@
(defn resolver (defn resolver
[registry ens-name cb] [registry ens-name cb]
(ethereum/call (ethereum/call-params registry (json-rpc/eth-call
"resolver(bytes32)" {:contract registry
(namehash ens-name)) :method "resolver(bytes32)"
(fn [address] :params [(namehash ens-name)]
(let [address (ethereum/hex->address address)] :outputs ["address"]
(cb (if (and address (not= address default-address)) address "")))))) :on-success
(fn [[address]]
(when-not (= address default-address)
(cb address)))}))
(defn owner (defn owner
[registry ens-name cb] [registry ens-name cb]
(ethereum/call (ethereum/call-params registry (json-rpc/eth-call
"owner(bytes32)" {:contract registry
(namehash ens-name)) :method "owner(bytes32)"
(fn [address] :params [(namehash ens-name)]
(cb address)))) :outputs ["address"]
:on-success
(fn [[address]]
(cb address))}))
(defn ttl (defn ttl
[registry ens-name cb] [registry ens-name cb]
(ethereum/call (ethereum/call-params registry (json-rpc/eth-call
"ttl(bytes32)" {:contract registry
(namehash ens-name)) :method "ttl(bytes32)"
(fn [ttl] :params [(namehash ens-name)]
(cb (ethereum/hex->int ttl))))) :outputs ["uint256"]
:on-success
(fn [[ttl]]
(cb ttl))}))
;; Resolver contract ;; Resolver contract
;; Resolver must implement EIP65 (supportsInterface). When interacting with an unknown resolver it's safer to rely on it. ;; Resolver must implement EIP65 (supportsInterface). When interacting with an unknown resolver it's safer to rely on it.
@ -65,9 +74,14 @@
(defn addr (defn addr
[resolver ens-name cb] [resolver ens-name cb]
(ethereum/call (ethereum/call-params resolver "addr(bytes32)" (namehash ens-name)) (json-rpc/eth-call
(fn [address] {:contract resolver
(cb (ethereum/hex->address address))))) :method "addr(bytes32)"
:params [(namehash ens-name)]
:outputs ["address"]
:on-success
(fn [[address]]
(cb address))}))
(def name-hash "0x691f3431") (def name-hash "0x691f3431")
@ -75,37 +89,41 @@
(defn name (defn name
[resolver ens-name cb] [resolver ens-name cb]
(ethereum/call (ethereum/call-params resolver (json-rpc/eth-call
"name(bytes32)" {:contract resolver
(namehash ens-name)) :method "name(bytes32)"
(fn [address] :params [(namehash ens-name)]
(cb (ethereum/hex->address address))))) :outputs ["string"]
:on-success
(fn [[name]]
(cb name))}))
(defn contenthash (defn contenthash
[resolver ens-name cb] [resolver ens-name cb]
(ethereum/call (ethereum/call-params resolver (json-rpc/eth-call
"contenthash(bytes32)" {:contract resolver
(namehash ens-name)) :method "contenthash(bytes32)"
(fn [hash] :params [(namehash ens-name)]
(cb (first (abi-spec/decode hash ["bytes"])))))) :on-success
(fn [hash]
(cb hash))}))
(defn content (defn content
[resolver ens-name cb] [resolver ens-name cb]
(ethereum/call (ethereum/call-params resolver (json-rpc/eth-call
"content(bytes32)" {:contract resolver
(namehash ens-name)) :method "content(bytes32)"
(fn [hash] :params [(namehash ens-name)]
(cb hash)))) :on-success
(fn [hash]
(cb hash))}))
(def ABI-hash "0x2203ab56") (def ABI-hash "0x2203ab56")
(def pubkey-hash "0xc8690233") (def pubkey-hash "0xc8690233")
(defn add-uncompressed-public-key-prefix (defn uncompressed-public-key
[key] [x y]
(when (and key (str "0x04" x y))
(not= "0x" key)
(not= default-key key))
(str "0x04" (subs key 2))))
(defn is-valid-eth-name? (defn is-valid-eth-name?
[ens-name] [ens-name]
@ -115,12 +133,16 @@
(defn pubkey (defn pubkey
[resolver ens-name cb] [resolver ens-name cb]
(ethereum/call (ethereum/call-params resolver (json-rpc/eth-call
"pubkey(bytes32)" {:contract resolver
(namehash ens-name)) :method "pubkey(bytes32)"
(fn [key] :params [(namehash ens-name)]
(when-let [public-key (add-uncompressed-public-key-prefix key)] :outputs ["bytes32" "bytes32"]
(cb public-key))))) :on-success
(fn [[x y]]
(when-let [public-key (uncompressed-public-key x y)]
(when-not (= public-key default-key)
(cb public-key))))}))
(defn get-addr (defn get-addr
[registry ens-name cb] [registry ens-name cb]

View File

@ -2,19 +2,22 @@
" "
Helper functions to interact with [ERC721](https://eips.ethereum.org/EIPS/eip-721) smart contract Helper functions to interact with [ERC721](https://eips.ethereum.org/EIPS/eip-721) smart contract
" "
(:require [status-im.utils.ethereum.core :as ethereum])) (:require [status-im.ethereum.json-rpc :as json-rpc]))
(defn token-of-owner-by-index [contract address index cb] (defn token-of-owner-by-index
(ethereum/call (ethereum/call-params [contract address index cb]
contract (json-rpc/eth-call
"tokenOfOwnerByIndex(address,uint256)" {:contract contract
(ethereum/normalized-address address) :method "tokenOfOwnerByIndex(address,uint256)"
(ethereum/int->hex index)) :params [address index]
#(cb (ethereum/hex->bignumber %)))) :outputs ["uint256"]
:on-success (fn [[token]] (cb token))}))
(defn token-uri [contract tokenId cb] (defn token-uri
(ethereum/call (ethereum/call-params [contract tokenId cb]
contract (json-rpc/eth-call
"tokenURI(uint256)" {:contract contract
(ethereum/int->hex tokenId)) :method "tokenURI(uint256)"
#(cb (ethereum/hex->string %)))) :params [tokenId]
:outputs ["string"]
:on-success (fn [[uri]] (cb uri))}))

View File

@ -3,7 +3,6 @@
(:refer-clojure :exclude [name])) (:refer-clojure :exclude [name]))
(def default-hash "0x0000000000000000000000000000000000000000000000000000000000000000") (def default-hash "0x0000000000000000000000000000000000000000000000000000000000000000")
(defn contenthash [registry ens-name cb] (defn contenthash [registry ens-name cb]
(ens/resolver registry (ens/resolver registry
ens-name ens-name

View File

@ -1,34 +0,0 @@
(ns status-im.utils.ethereum.stickers
(:require [status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.abi-spec :as abi-spec]))
(def contracts
{:mainnet nil
:testnet "0x39d16CdB56b5a6a89e1A397A13Fe48034694316E"
:rinkeby nil})
(defn pack-count
"Returns number of packs rigestered in the contract"
[contract cb]
(ethereum/call (ethereum/call-params contract "packCount()")
(fn [count] (cb (ethereum/hex->int count)))))
(defn pack-data
"Returns vector of pack data parameters by pack id: [category owner mintable timestamp price contenthash]"
[contract pack-id cb]
(ethereum/call (ethereum/call-params contract "getPackData(uint256)" (ethereum/int->hex pack-id))
(fn [data]
(cb (abi-spec/decode data ["bytes4[]" "address" "bool" "uint256" "uint256" "bytes"])))))
(defn owned-tokens
"Returns vector of owned tokens ids in the contract by address"
[contract address cb]
(ethereum/call (ethereum/call-params contract "tokensOwnedBy(address)" (ethereum/normalized-address address))
(fn [data]
(cb (first (abi-spec/decode data ["uint256[]"]))))))
(defn token-pack-id
"Returns pack id in the contract by token id"
[contract token cb]
(ethereum/call (ethereum/call-params contract "tokenPackId(uint256)" (ethereum/int->hex token))
(fn [data] (cb (ethereum/hex->int data)))))

View File

@ -9,6 +9,7 @@
[status-im.ui.screens.wallet.utils :as wallet.utils] [status-im.ui.screens.wallet.utils :as wallet.utils]
[status-im.utils.config :as config] [status-im.utils.config :as config]
[status-im.utils.core :as utils.core] [status-im.utils.core :as utils.core]
[status-im.utils.ethereum.abi-spec :as abi-spec]
[status-im.utils.ethereum.core :as ethereum] [status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.tokens :as tokens] [status-im.utils.ethereum.tokens :as tokens]
[status-im.utils.fx :as fx] [status-im.utils.fx :as fx]
@ -256,9 +257,9 @@
[from {:keys [amount to gas gas-price data nonce]}] [from {:keys [amount to gas gas-price data nonce]}]
(cond-> {:from (ethereum/normalized-address from) (cond-> {:from (ethereum/normalized-address from)
:to (ethereum/normalized-address to) :to (ethereum/normalized-address to)
:value (ethereum/int->hex amount) :value (str "0x" (abi-spec/number-to-hex amount))
:gas (ethereum/int->hex gas) :gas (str "0x" (abi-spec/number-to-hex gas))
:gasPrice (ethereum/int->hex gas-price)} :gasPrice (str "0x" (abi-spec/number-to-hex gas-price))}
data data
(assoc :data data) (assoc :data data)
nonce nonce
@ -314,15 +315,15 @@
(when on-error (when on-error
{:dispatch (conj on-error "transaction was cancelled by user")})))) {:dispatch (conj on-error "transaction was cancelled by user")}))))
(defn prepare-unconfirmed-transaction [db now hash] (defn prepare-unconfirmed-transaction
[db now hash]
(let [transaction (get-in db [:wallet :send-transaction]) (let [transaction (get-in db [:wallet :send-transaction])
all-tokens (:wallet/all-tokens db)] all-tokens (:wallet/all-tokens db)]
(let [chain (:chain db) (let [chain (:chain db)
token (tokens/symbol->token all-tokens (keyword chain) (:symbol transaction))] token (tokens/symbol->token all-tokens (keyword chain) (:symbol transaction))]
(-> transaction (-> transaction
(assoc :confirmations "0" (assoc :timestamp (str now)
:timestamp (str now) :type :pending
:type :outbound
:hash hash :hash hash
:value (:amount transaction) :value (:amount transaction)
:token token :token token
@ -466,30 +467,6 @@
:wallet-send-modal-stack :wallet-send-modal-stack
:wallet-send-modal-stack-with-onboarding)]]})) :wallet-send-modal-stack-with-onboarding)]]}))
(fx/defn open-sign-transaction-flow
[{:keys [db] :as cofx}
{:keys [gas gas-price] :as transaction}]
(let [go-to-view-id (if (get-in db [:account/account :wallet-set-up-passed?])
:wallet-send-modal-stack
:wallet-send-modal-stack-with-onboarding)]
(fx/merge cofx
(cond-> {:db (-> db
(assoc-in [:navigation/screen-params :wallet-send-modal-stack :modal?] true)
(assoc-in [:wallet :send-transaction]
transaction)
(assoc-in [:wallet :send-transaction :original-gas]
gas))}
(not gas)
(assoc :wallet/update-estimated-gas
{:obj (select-keys transaction [:to :data])
:success-event :wallet/update-estimated-gas-success})
(not gas-price)
(assoc :wallet/update-gas-price
{:success-event :wallet/update-gas-price-success
:edit? false}))
(navigation/navigate-to-cofx go-to-view-id {}))))
(defn send-transaction-screen-did-load (defn send-transaction-screen-did-load
[{:keys [db]}] [{:keys [db]}]
{:db (assoc-in db {:db (assoc-in db
@ -575,3 +552,34 @@
(toggle-visible-token symbol true) (toggle-visible-token symbol true)
;;TODO(goranjovic): move `update-token-balance-success` function to wallet models ;;TODO(goranjovic): move `update-token-balance-success` function to wallet models
(update-token-balance symbol balance))) (update-token-balance symbol balance)))
(fx/defn eth-transaction-call
[{:keys [db] :as cofx}
{:keys [contract method params on-success on-error details] :as transaction}]
(let [current-address (ethereum/current-address db)
transaction (merge {:to contract
:from current-address
:data (abi-spec/encode method params)
:id "approve"
:symbol :ETH
:method "eth_sendTransaction"
:amount (money/bignumber 0)
:on-success on-success
:on-error on-error}
details)
go-to-view-id (if (get-in db [:account/account :wallet-set-up-passed?])
:wallet-send-modal-stack
:wallet-send-modal-stack-with-onboarding)]
(fx/merge cofx
{:db (-> db
(assoc-in [:navigation/screen-params :wallet-send-modal-stack :modal?] true)
(assoc-in [:wallet :send-transaction]
transaction))
:wallet/update-estimated-gas
{:obj (select-keys transaction [:to :from :data])
:success-event :wallet/update-estimated-gas-success}
:wallet/update-gas-price
{:success-event :wallet/update-gas-price-success
:edit? false}}
(navigation/navigate-to-cofx go-to-view-id {}))))

View File

@ -1,136 +1,210 @@
(ns status-im.wallet.custom-tokens.core (ns status-im.wallet.custom-tokens.core
(:require-macros [status-im.utils.views :refer [defview letsubs]]) (:require [clojure.string :as string]
(:require [re-frame.core :as re-frame] [re-frame.core :as re-frame]
[status-im.ethereum.decode :as decode]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.ui.components.colors :as colors] [status-im.ui.components.colors :as colors]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.utils.ethereum.core :as ethereum] [status-im.utils.ethereum.core :as ethereum]
[clojure.string :as string] [status-im.utils.money :as money]
[status-im.ethereum.decode :as decode]
[status-im.utils.fx :as fx] [status-im.utils.fx :as fx]
[status-im.wallet.core :as wallet])) [status-im.wallet.core :as wallet]))
(re-frame/reg-fx (re-frame/reg-fx
:wallet.custom-token/get-decimals :wallet.custom-token/get-decimals
(fn [contract] (fn [contract]
(ethereum/call (json-rpc/eth-call
(ethereum/call-params contract "decimals()") {:contract contract
#(re-frame/dispatch [:wallet.custom-token/decimals-result %])))) :method "decimals()"
:outputs ["uint256"]
:on-success
(fn [[contract-decimals]]
(re-frame/dispatch [:wallet.custom-token/decimals-result
contract-decimals]))})))
(re-frame/reg-fx (re-frame/reg-fx
:wallet.custom-token/get-symbol :wallet.custom-token/get-symbol
(fn [contract] (fn [contract]
(ethereum/call (json-rpc/eth-call
(ethereum/call-params contract "symbol()") {:contract contract
#(re-frame/dispatch [:wallet.custom-token/symbol-result contract %])))) :method "symbol()"
:outputs ["string"]
:on-success
(fn [[contract-symbol]]
(re-frame/dispatch [:wallet.custom-token/symbol-result
contract
contract-symbol]))})))
(re-frame/reg-fx (re-frame/reg-fx
:wallet.custom-token/get-balance :wallet.custom-token/get-balance
(fn [[contract address]] (fn [[contract wallet-address]]
(ethereum/call (json-rpc/eth-call
(ethereum/call-params contract "balanceOf(address)" (ethereum/normalized-address address)) {:contract contract
#(re-frame/dispatch [:wallet.custom-token/balance-result contract %])))) :method "balanceOf(address)"
:params [wallet-address]
:outputs ["uint256"]
:on-success
(fn [[balance]]
(re-frame/dispatch [:wallet.custom-token/balance-result
contract
(money/bignumber balance)]))})))
(re-frame/reg-fx (re-frame/reg-fx
:wallet.custom-token/get-name :wallet.custom-token/get-name
(fn [contract] (fn [contract]
(ethereum/call (json-rpc/eth-call
(ethereum/call-params contract "name()") {:contract contract
#(re-frame/dispatch [:wallet.custom-token/name-result contract %])))) :method "name()"
:outputs ["string"]
:on-success
(fn [[contract-name]]
(re-frame/dispatch [:wallet.custom-token/name-result
contract
contract-name]))})))
(re-frame/reg-fx (re-frame/reg-fx
:wallet.custom-token/get-total-supply :wallet.custom-token/get-total-supply
(fn [contract] (fn [contract]
(ethereum/call (json-rpc/eth-call
(ethereum/call-params contract "totalSupply()") {:contract contract
#(re-frame/dispatch [:wallet.custom-token/total-supply-result contract %])))) :method "totalSupply()"
:outputs ["uint256"]
:on-success
(fn [[contract-total-supply]]
(re-frame/dispatch [:wallet.custom-token/total-supply-result
contract
(money/bignumber contract-total-supply)]))})))
(re-frame/reg-fx (re-frame/reg-fx
:wallet.custom-token/contract-address-paste :wallet.custom-token/contract-address-paste
(fn [] (fn []
(react/get-from-clipboard #(re-frame/dispatch [:wallet.custom-token/contract-address-is-pasted (string/trim %)])))) (react/get-from-clipboard
#(re-frame/dispatch [:wallet.custom-token/contract-address-is-pasted
(string/trim %)]))))
(defn field-exists? [{:wallet/keys [all-tokens] :as db} field-key field-value] (defn field-exists?
[{:wallet/keys [all-tokens] :as db} field-key field-value]
(let [chain-key (ethereum/get-chain-keyword db)] (let [chain-key (ethereum/get-chain-keyword db)]
(some #(= field-value (get % field-key)) (vals (get all-tokens chain-key))))) (some #(= field-value (get % field-key))
(vals (get all-tokens chain-key)))))
(fx/defn total-supply-result [{:keys [db]} contract result] (fx/defn total-supply-result
(if (and (string? result) (string/starts-with? result "0x") (> (count result) 2)) [{:keys [db]} contract total-supply]
{:wallet.custom-token/get-balance [contract (get-in db [:account/account :address])]} (if (money/valid? total-supply)
{:db (update db :wallet/custom-token-screen merge {:in-progress? nil :error (i18n/label :t/wrong-contract)})})) {:wallet.custom-token/get-balance
[contract (ethereum/current-address db)]}
{:db (update db
:wallet/custom-token-screen
merge {:in-progress? nil
:error (i18n/label :t/wrong-contract)})}))
(defn token-in-list? [{:wallet/keys [all-tokens] :as db} contract] (defn token-in-list?
[{:wallet/keys [all-tokens] :as db} contract]
(let [chain-key (ethereum/get-chain-keyword db) (let [chain-key (ethereum/get-chain-keyword db)
addresses (set (map string/lower-case (keys (get all-tokens chain-key))))] addresses (set (map string/lower-case (keys (get all-tokens chain-key))))]
(not (nil? (get addresses (string/lower-case contract)))))) (not (nil? (get addresses (string/lower-case contract))))))
(fx/defn contract-address-is-changed [{:keys [db]} contract] (fx/defn contract-address-is-changed
[{:keys [db]} contract]
(if (ethereum/address? contract) (if (ethereum/address? contract)
(if (token-in-list? db contract) (if (token-in-list? db contract)
{:db (assoc db :wallet/custom-token-screen {:contract contract :error (i18n/label :t/already-have-asset)})} {:db (assoc db
{:db (assoc db :wallet/custom-token-screen {:contract contract :in-progress? true}) :wallet/custom-token-screen
{:contract contract :error (i18n/label :t/already-have-asset)})}
{:db (assoc db
:wallet/custom-token-screen
{:contract contract :in-progress? true})
:wallet.custom-token/get-total-supply contract}) :wallet.custom-token/get-total-supply contract})
{:db (assoc db :wallet/custom-token-screen {:contract contract :error (i18n/label :t/wrong-address)})})) {:db (assoc db
:wallet/custom-token-screen
{:contract contract
:error (i18n/label :t/wrong-address)})}))
(fx/defn decimals-result [{:keys [db]} result] (fx/defn decimals-result
{:db (update db :wallet/custom-token-screen merge {:decimals (str (decode/uint result)) [{:keys [db]} result]
:in-progress? nil})}) {:db (update db
:wallet/custom-token-screen
merge
{:decimals (str (decode/uint result))
:in-progress? nil})})
(fx/defn symbol-result [{:keys [db]} contract result] (fx/defn symbol-result
(let [token-symbol (decode/string result) [{:keys [db]} contract token-symbol]
symbol-exists? (field-exists? db :symbol (keyword token-symbol))] (let [symbol-exists? (field-exists? db :symbol (keyword token-symbol))]
{:db {:db
(update db :wallet/custom-token-screen merge (update db
:wallet/custom-token-screen merge
{:symbol token-symbol {:symbol token-symbol
:error-symbol (when symbol-exists? :error-symbol (when symbol-exists?
(i18n/label :t/you-already-have-an-asset {:value token-symbol}))}) (i18n/label :t/you-already-have-an-asset {:value token-symbol}))})
:wallet.custom-token/get-decimals :wallet.custom-token/get-decimals
contract})) contract}))
(fx/defn name-result [{:keys [db]} contract result] (fx/defn name-result
(let [token-name (decode/string result) [{:keys [db]} contract token-name]
name-exists? (field-exists? db :name token-name)] (let [name-exists? (field-exists? db :name token-name)]
{:db {:db
(update db :wallet/custom-token-screen merge (update db :wallet/custom-token-screen merge
{:name token-name {:name token-name
:error-name (when name-exists? :error-name (when name-exists?
(i18n/label :t/you-already-have-an-asset {:value token-name}))}) (i18n/label :t/you-already-have-an-asset
:wallet.custom-token/get-symbol {:value token-name}))})
contract})) :wallet.custom-token/get-symbol contract}))
(fx/defn balance-result [{:keys [db]} contract result] (fx/defn balance-result
(if (and (string? result) (string/starts-with? result "0x") (> (count result) 2)) [{:keys [db]} contract balance]
{:db (assoc-in db [:wallet/custom-token-screen :balance] (str (decode/uint result))) (if (money/valid? balance)
{:db (assoc-in db
[:wallet/custom-token-screen :balance]
(str balance))
:wallet.custom-token/get-name contract} :wallet.custom-token/get-name contract}
{:db (update db :wallet/custom-token-screen merge {:in-progress? nil :error (i18n/label :t/wrong-contract)})})) {:db (update db
:wallet/custom-token-screen
merge
{:in-progress? nil
:error (i18n/label :t/wrong-contract)})}))
(fx/defn add-custom-token [{:keys [db] :as cofx}] (fx/defn add-custom-token
[{:keys [db] :as cofx}]
(let [{:keys [contract name symbol decimals]} (get db :wallet/custom-token-screen) (let [{:keys [contract name symbol decimals]} (get db :wallet/custom-token-screen)
chain-key (ethereum/get-chain-keyword db) chain-key (ethereum/get-chain-keyword db)
symbol (keyword symbol) symbol (keyword symbol)
new-token {:address contract :name name :symbol symbol :custom? true new-token {:address contract
:decimals (int decimals) :color (rand-nth colors/chat-colors)}] :name name
(fx/merge (assoc-in cofx [:db :wallet/all-tokens chain-key contract] new-token) :symbol symbol
:custom? true
:decimals (int decimals)
:color (rand-nth colors/chat-colors)}]
(fx/merge (assoc-in cofx
[:db :wallet/all-tokens chain-key contract]
new-token)
(wallet/add-custom-token new-token)))) (wallet/add-custom-token new-token))))
(fx/defn remove-custom-token [{:keys [db] :as cofx} {:keys [address] :as token}] (fx/defn remove-custom-token
[{:keys [db] :as cofx} {:keys [address] :as token}]
(let [chain-key (ethereum/get-chain-keyword db)] (let [chain-key (ethereum/get-chain-keyword db)]
(fx/merge (update-in cofx [:db :wallet/all-tokens chain-key] dissoc address) (fx/merge (update-in cofx [:db :wallet/all-tokens chain-key] dissoc address)
(wallet/remove-custom-token token)))) (wallet/remove-custom-token token))))
(fx/defn field-is-edited [{:keys [db] :as cofx} field-key value] (fx/defn field-is-edited
[{:keys [db] :as cofx} field-key value]
(case field-key (case field-key
:contract (contract-address-is-changed cofx value) :contract (contract-address-is-changed cofx value)
:name {:db (update db :wallet/custom-token-screen merge :name {:db (update db
:wallet/custom-token-screen merge
{field-key {field-key
value value
:error-name :error-name
(when (field-exists? db field-key value) (when (field-exists? db field-key value)
(i18n/label :t/you-already-have-an-asset {:value value}))})} (i18n/label :t/you-already-have-an-asset
{:value value}))})}
:symbol {:db (update db :wallet/custom-token-screen merge :symbol {:db (update db :wallet/custom-token-screen merge
{field-key {field-key
value value
:error-symbol :error-symbol
(when (field-exists? db field-key (keyword value)) (when (field-exists? db field-key (keyword value))
(i18n/label :t/you-already-have-an-asset {:value value}))})} (i18n/label :t/you-already-have-an-asset {:value value}))})}
:decimals {:db (assoc-in db [:wallet/custom-token-screen :decimals] value)})) :decimals {:db (assoc-in db
[:wallet/custom-token-screen :decimals]
value)}))

View File

@ -1,17 +1,9 @@
(ns status-im.test.utils.ethereum.core (ns status-im.test.utils.ethereum.core
(:require [cljs.test :refer-macros [deftest is testing]] (:require [cljs.test :refer-macros [deftest is]]
[status-im.utils.ethereum.core :as ethereum])) [status-im.utils.ethereum.core :as ethereum]))
(deftest call-params
(testing "ERC20 balance-of params"
(let [contract "0x29b5f6efad2ad701952dfde9f29c960b5d6199c5"
address "0xa7cfd581060ec66414790691681732db249502bd"]
(is (= (ethereum/call-params contract "balanceOf(address)" address)
{:to "0x29b5f6efad2ad701952dfde9f29c960b5d6199c5"
:data "0x70a08231000000000000000000000000a7cfd581060ec66414790691681732db249502bd"})))))
(deftest chain-id->chain-keyword (deftest chain-id->chain-keyword
(is (= (ethereum/chain-id->chain-keyword 1) :mainnet)) (is (= (ethereum/chain-id->chain-keyword 1) :mainnet))
(is (= (ethereum/chain-id->chain-keyword 3) :testnet)) (is (= (ethereum/chain-id->chain-keyword 3) :testnet))
(is (= (ethereum/chain-id->chain-keyword 4) :rinkeby)) (is (= (ethereum/chain-id->chain-keyword 4) :rinkeby))
(is (= (ethereum/chain-id->chain-keyword 5777) :custom))) (is (= (ethereum/chain-id->chain-keyword 5777) :custom)))