[#6703] Stickers UI and contract wedding

Signed-off-by: Andrey Shovkoplyas <motor4ik@gmail.com>
This commit is contained in:
Andrey Shovkoplyas 2019-02-15 10:08:42 +01:00
parent 2fa27d4ef9
commit a1aeff70e5
No known key found for this signature in database
GPG Key ID: EAAB7C8622D860A4
11 changed files with 269 additions and 70 deletions

View File

@ -83,13 +83,10 @@
(let [history-host (http/url-host (try (nth history history-index) (catch js/Error _)))]
(cond-> browser history-host (assoc :unsafe? (dependencies/phishing-detect history-host)))))
(def ipfs-proto-code "e3")
(def swarm-proto-code "e4")
(defn resolve-ens-content-callback [hex]
(let [hash (when hex (multihash/base58 (multihash/create :sha2-256 (subs hex 2))))]
(if (and hash (not= hash resolver/default-hash))
(re-frame/dispatch [:browser.callback/resolve-ens-multihash-success ipfs-proto-code hash])
(re-frame/dispatch [:browser.callback/resolve-ens-multihash-success constants/ipfs-proto-code hash])
(re-frame/dispatch [:browser.callback/resolve-ens-contenthash]))))
(defn resolve-ens-contenthash-callback [hex]
@ -97,7 +94,7 @@
hash (when hex (multihash/base58 (multihash/create :sha2-256 (subs hex 12))))]
;; We only support IPFS and SWARM
;; TODO Once more implementations / providers are published this will have to be improved
(if (and ((#{swarm-proto-code ipfs-proto-code} proto-code) hash (not= hash resolver/default-hash)))
(if (and ((#{constants/swarm-proto-code constants/ipfs-proto-code} proto-code) hash (not= hash resolver/default-hash)))
(re-frame/dispatch [:browser.callback/resolve-ens-multihash-success proto-code hash])
(re-frame/dispatch [:browser.callback/resolve-ens-multihash-error]))))
@ -179,7 +176,7 @@
(let [current-url (get-current-url (get-current-browser db))
host (http/url-host current-url)
path (subs current-url (+ (.indexOf current-url host) (count host)))
gateway (if (= ipfs-proto-code proto-code)
gateway (if (= constants/ipfs-proto-code proto-code)
(let [base32hash (-> (.encode js-dependencies/hi-base32 (alphabase.base58/decode hash))
(string/replace #"=" "")
(string/lower-case))]

View File

@ -261,3 +261,6 @@
(def ^:const ipfs-add-url "https://ipfs.infura.io:5001/api/v0/add")
(def ^:const ipfs-add-param-name "extension.event.edn")
(def ^:const ipfs-cat-url "https://ipfs.infura.io/ipfs/")
(def ^:const ipfs-proto-code "e3")
(def ^:const swarm-proto-code "e4")

View File

@ -17,7 +17,6 @@
[status-im.chat.models.message :as chat.message]
[status-im.contact.core :as contact]
[status-im.contact-recovery.core :as contact-recovery]
[status-im.data-store.core :as data-store]
[status-im.extensions.core :as extensions]
[status-im.extensions.registry :as extensions.registry]
[status-im.fleet.core :as fleet]
@ -39,7 +38,6 @@
[status-im.signals.core :as signals]
[status-im.transport.message.core :as transport.message]
[status-im.ui.screens.currency-settings.models :as currency-settings.models]
[status-im.chat.models.message :as models.message]
[status-im.node.core :as node]
[status-im.web3.core :as web3]
[status-im.ui.screens.navigation :as navigation]
@ -47,13 +45,13 @@
[status-im.utils.handlers :as handlers]
[status-im.utils.utils :as utils]
[taoensso.timbre :as log]
[status-im.utils.datetime :as time]
[status-im.chat.commands.core :as commands]
[status-im.chat.models.loading :as chat-loading]
[status-im.node.core :as node]
[cljs.reader :as edn]
[status-im.stickers.core :as stickers]
[status-im.utils.config :as config]))
[status-im.utils.config :as config]
[status-im.constants :as constants]
[status-im.utils.ethereum.core :as ethereum]))
;; init module
@ -1670,8 +1668,8 @@
(handlers/register-handler-fx
:stickers/load-sticker-pack-success
(fn [cofx [_ edn-string uri open?]]
(stickers/load-sticker-pack-success cofx edn-string uri open?)))
(fn [cofx [_ edn-string id price open?]]
(stickers/load-sticker-pack-success cofx edn-string id price open?)))
(handlers/register-handler-fx
:stickers/install-pack
@ -1680,14 +1678,13 @@
(handlers/register-handler-fx
:stickers/load-packs
(fn [_ _]
{;;TODO request list of packs from contract
:http-get-n (mapv (fn [uri] {:url uri
:success-event-creator (fn [o]
[:stickers/load-sticker-pack-success o uri])
:failure-event-creator (fn [o] nil)})
;;TODO for testing ONLY
["https://ipfs.infura.io/ipfs/QmRKmQjXyqpfznQ9Y9dTnKePJnQxoJATivPbGcCAKRsZJq/"])}))
(fn [cofx _]
(stickers/load-packs cofx)))
(handlers/register-handler-fx
:stickers/load-pack
(fn [cofx [_ proto-code hash id price open?]]
(stickers/load-pack cofx proto-code hash id price open?)))
(handlers/register-handler-fx
:stickers/select-pack
@ -1696,5 +1693,35 @@
(handlers/register-handler-fx
:stickers/open-sticker-pack
(fn [cofx [_ uri]]
(stickers/open-sticker-pack cofx uri)))
(fn [cofx [_ id]]
(stickers/open-sticker-pack cofx id)))
(handlers/register-handler-fx
:stickers/buy-pack
(fn [cofx [_ id price]]
(stickers/approve-pack cofx id price)))
(handlers/register-handler-fx
:stickers/buy-token
(fn [cofx [_ id]]
(stickers/buy-token cofx id)))
(handlers/register-handler-fx
:stickers/get-owned-packs
(fn [cofx _]
(stickers/get-owned-pack cofx)))
(handlers/register-handler-fx
:stickers/pack-owned
(fn [cofx [_ id]]
(stickers/pack-owned cofx id)))
(handlers/register-handler-fx
:stickers/pending-pack
(fn [cofx [_ id]]
(stickers/pending-pack cofx id)))
(handlers/register-handler-fx
:stickers/pending-timout
(fn [cofx _]
(stickers/pending-timeout cofx)))

View File

@ -2,11 +2,22 @@
(:require [status-im.utils.fx :as fx]
[cljs.reader :as edn]
[status-im.accounts.core :as accounts]
[status-im.ui.screens.navigation :as navigation]))
[status-im.ui.screens.navigation :as navigation]
[re-frame.core :as re-frame]
[status-im.utils.multihash :as multihash]
[status-im.constants :as constants]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.stickers :as ethereum.stickers]
[status-im.models.wallet :as models.wallet]
[status-im.utils.money :as money]
[status-im.utils.ethereum.abi-spec :as abi-spec]
[status-im.utils.ethereum.erc20 :as erc20]))
(fx/defn init-stickers-packs [{:keys [db]}]
(let [sticker-packs (map edn/read-string (get-in db [:account/account :stickers]))]
{:db (assoc db :stickers/packs-installed (into {} (map #(vector (:id %) %) sticker-packs)))}))
(let [sticker-packs (into {} (map #(let [pack (edn/read-string %)]
(vector (:id pack) pack))
(get-in db [:account/account :stickers])))]
{: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]
(let [pack (get-in db [:stickers/packs id])]
@ -17,24 +28,124 @@
(assoc :stickers/selected-pack id))}
(accounts/update-stickers (conj (:stickers account) (pr-str pack))))))
(fx/defn load-sticker-pack-success [{:keys [db] :as cofx} edn-string uri open?]
(let [{{:keys [id] :as pack} 'meta} (edn/read-string edn-string)
pack' (assoc pack :uri uri)]
(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)
:id id :price price)]
(fx/merge cofx
{:db (-> db (assoc-in [:stickers/packs id] pack'))}
#(when open? (navigation/navigate-to-cofx % :stickers-pack-modal pack')))))
{:db (-> db (assoc-in [:stickers/packs id] pack))}
#(when open? (navigation/navigate-to-cofx % :stickers-pack-modal pack)))))
(defn find-pack [pack-uri]
(fn [{:keys [uri] :as pack}]
(when (= pack-uri uri)
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 [{{:stickers/keys [packs packs-installed]} :db :as cofx} uri]
(when uri
(let [pack (some (find-pack uri) (concat (vals packs-installed) (vals packs)))]
(fx/defn open-sticker-pack [{{:keys [web3 network] :stickers/keys [packs packs-installed] :as db} :db :as cofx} id]
(when id
(let [pack (or (get packs-installed id) (get packs id))
network (get-in db [:account/account :networks network])]
(if pack
(navigation/navigate-to-cofx cofx :stickers-pack-modal pack)
{:http-get {:url uri
:success-event-creator (fn [o]
[:stickers/load-sticker-pack-success o uri true])
:failure-event-creator (fn [o] nil)}}))))
{:stickers/pack-data-fx [web3 network id true]}))))
(fx/defn load-pack [cofx proto-code hash id price open?]
{:http-get {:url (str (if (= constants/swarm-proto-code proto-code)
"https://swarm-gateways.net/bzz:/"
"https://ipfs.infura.io/ipfs/")
hash)
:success-event-creator (fn [o]
[:stickers/load-sticker-pack-success o id price open?])
:failure-event-creator (constantly nil)}})
(fx/defn load-packs [{{:keys [web3 network] :as db} :db}]
(let [network (get-in db [:account/account :networks network])
address (ethereum/normalized-address (get-in db [:account/account :address]))]
{:stickers/owned-packs-fx [web3 network address]
:stickers/load-packs-fx [web3 network]}))
(defn prepare-transaction [id tx on-result]
(merge {:id id
:symbol :ETH
:method constants/web3-send-transaction
:amount (money/bignumber 0)}
(when on-result {:on-result on-result})
tx))
(fx/defn buy-token [{{:keys [network] :as db} :db} pack-id]
(let [network (get-in db [:account/account :networks network])
address (ethereum/normalized-address (get-in db [:account/account :address]))
tx-object {:to (get ethereum.stickers/contracts (ethereum/network->chain-keyword network))
:data (abi-spec/encode "buyToken(uint256,address)" [pack-id address])}]
(models.wallet/open-modal-wallet-for-transaction
db
(prepare-transaction "buy" tx-object [:stickers/pending-pack pack-id])
tx-object)))
(fx/defn approve-pack [{db :db} pack-id price]
(let [network (get-in db [:account/account :networks (:network db)])
chain (ethereum/network->chain-keyword network)
stickers-contract (get ethereum.stickers/contracts chain)
tx-object {:to (get erc20/snt-contracts chain)
:data (abi-spec/encode "approve(address,uint256)" [stickers-contract price])}]
(models.wallet/open-modal-wallet-for-transaction
db
(prepare-transaction "approve" tx-object [:stickers/buy-token pack-id])
tx-object)))
(fx/defn pending-pack [{{:keys [web3 network] :as db} :db :as cofx} id]
(let [network (get-in db [:account/account :networks network])
address (ethereum/normalized-address (get-in db [:account/account :address]))]
(fx/merge cofx
{:db (update db :stickers/packs-pendning conj id)
:stickers/owned-packs-fx [web3 network address]}
(navigation/navigate-to-clean :wallet-transaction-sent-modal {})
#(when (zero? (count (:stickers/packs-pendning db)))
{:stickers/set-pending-timout-fx nil}))))
(fx/defn pending-timeout [{{:keys [web3 network] :stickers/keys [packs-pendning packs-owned] :as db} :db}]
(let [packs-diff (clojure.set/difference packs-pendning packs-owned)
network (get-in db [:account/account :networks network])
address (ethereum/normalized-address (get-in db [:account/account :address]))]
(merge {:db (assoc db :stickers/packs-pendning packs-diff)}
(when-not (zero? (count packs-diff))
{:stickers/owned-packs-fx [web3 network address]
:stickers/set-pending-timout-fx nil}))))
(fx/defn pack-owned [{db :db} id]
{:db (update db :stickers/packs-owned conj id)})
(fx/defn get-owned-pack [{{:keys [web3 network] :as db} :db}]
(let [address (ethereum/normalized-address (get-in db [:account/account :address]))]
{:stickers/owned-packs-fx [web3 network address]}))
(re-frame/reg-fx
:stickers/pack-data-fx
(fn [[web3 network id open?]]
(when-let [contract (get ethereum.stickers/contracts (ethereum/network->chain-keyword network))]
(ethereum.stickers/pack-data web3 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 [[web3 network]]
(when-let [contract (get ethereum.stickers/contracts (ethereum/network->chain-keyword network))]
(ethereum.stickers/pack-count web3 contract
(fn [count]
(dotimes [n count]
(ethereum.stickers/pack-data web3 contract n (pack-data-callback n false))))))))
(re-frame/reg-fx
:stickers/owned-packs-fx
(fn [[web3 network address]]
(when-let [contract (get ethereum.stickers/contracts (ethereum/network->chain-keyword network))]
(ethereum.stickers/owned-tokens web3 contract address
(fn [tokens]
(doseq [n tokens]
(ethereum.stickers/token-pack-id web3 contract n
#(re-frame/dispatch [:stickers/pack-owned %]))))))))

View File

@ -55,9 +55,9 @@
[react/view {:style {:margin-bottom 5 :height 2 :width 16 :border-radius 1
:background-color (if selected? colors/blue colors/white)}}]]])
(defn pack-stickers [packs id]
(let [{:keys [stickers uri]} (some #(when (= id (:id %)) %) packs)]
(map #(assoc % :pack uri) stickers)))
(defn pack-stickers [packs pack-id]
(let [{:keys [stickers id]} (some #(when (= pack-id (:id %)) %) packs)]
(map #(assoc % :pack id) stickers)))
(defn show-panel-anim
[bottom-anim-value alpha-value]

View File

@ -63,6 +63,8 @@
:dimensions/window (dimensions/window)
:push-notifications/stored {}
:registry {}
:stickers/packs-owned #{}
:stickers/packs-pendning #{}
:hardwallet {:nfc-supported? false
:nfc-enabled? false
:pin {:original []
@ -195,6 +197,8 @@
(spec/def ::hardwallet (spec/nilable map?))
(spec/def :stickers/packs (spec/nilable map?))
(spec/def :stickers/packs-owned (spec/nilable set?))
(spec/def :stickers/packs-pendning (spec/nilable set?))
(spec/def :stickers/packs-installed (spec/nilable map?))
(spec/def :stickers/selected-pack (spec/nilable any?))
(spec/def :stickers/recent (spec/nilable vector?))
@ -270,6 +274,8 @@
:stickers/packs-installed
:stickers/selected-pack
:stickers/recent
:stickers/packs-owned
:stickers/packs-pendning
:bottom-sheet/show?
:bottom-sheet/view]
:opt-un [::modal

View File

@ -21,8 +21,15 @@
:stickers/all-packs
:<- [:stickers/packs]
:<- [:stickers/installed-packs]
(fn [[packs installed]]
(map #(if (get installed (:id %)) (assoc % :installed true) %) (vals packs))))
:<- [:get :stickers/packs-owned]
:<- [:get :stickers/packs-pendning]
(fn [[packs installed owned pending]]
(map (fn [{:keys [id] :as pack}]
(cond-> pack
(get installed id) (assoc :installed true)
(get owned id) (assoc :owned true)
(get pending id) (assoc :pending true)))
(vals packs))))
(re-frame/reg-sub
:stickers/get-current-pack
@ -32,9 +39,9 @@
(first (filter #(= (:id %) id) packs))))
(defn find-pack-id-for-uri [sticker-uri packs]
(some (fn [{:keys [stickers uri]}]
(some (fn [{:keys [stickers id]}]
(when (some #(= sticker-uri (:uri %)) stickers)
uri))
id))
packs))
(re-frame/reg-sub

View File

@ -9,7 +9,9 @@
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.icons.vector-icons :as icons]
[status-im.utils.money :as money]))
[status-im.utils.money :as money]
[status-im.utils.ethereum.core :as ethereum]
[status-im.ui.components.react :as components]))
(defn- thumbnail-icon [uri size]
[react/image {:style {:width size :height size :border-radius (/ size 2)}
@ -19,24 +21,32 @@
[react/view styles/installed-icon
[icons/icon :main-icons/check {:color colors/white :height 20 :width 20}]])
(defview price-badge [price id]
(letsubs [balance [:balance]]
(let [snt (money/wei-> :eth (:SNT balance))
(defview price-badge [price id owned pending]
(letsubs [network [:network]
balance [:balance]]
(let [chain (ethereum/network->chain-keyword network)
snt (money/to-number (if (= :mainnet chain) (:SNT balance) (:STT balance)))
not-enough-snt? (> price snt)
no-snt? (nil? snt)]
[react/touchable-highlight {:on-press #(when (zero? price) (re-frame/dispatch [:stickers/install-pack id]))}
[react/view (styles/price-badge not-enough-snt?)
(when (and (not (zero? price)) (not no-snt?))
no-snt? (or (nil? snt) (zero? snt))]
[react/touchable-highlight {:on-press #(cond pending nil
(or owned (zero? price))
(re-frame/dispatch [:stickers/install-pack id])
(or no-snt? not-enough-snt?) nil
:else (re-frame/dispatch [:stickers/buy-pack id price]))}
[react/view (styles/price-badge (and (not (or owned (zero? price))) (or no-snt? not-enough-snt?)))
(when (and (not (zero? price))) ;(not no-snt?))
[icons/icon :icons/logo {:color colors/white :width 12 :height 12 :container-style {:margin-right 8}}])
[react/text {:style {:font-size 15 :color colors/white}}
(cond (zero? price)
(i18n/label :t/install)
no-snt?
(i18n/label :t/buy-with-snt)
:else
(str price))]]])))
(if pending
[components/activity-indicator {:animating true}]
[react/text {:style {:font-size 15 :color colors/white}}
(cond (or owned (zero? price))
(i18n/label :t/install)
;no-snt?
;(i18n/label :t/buy-with-snt)
:else
(str (money/wei-> :eth price)))])]])))
(defn pack-badge [{:keys [name author price thumbnail preview id installed] :as pack}]
(defn pack-badge [{:keys [name author price thumbnail preview id installed owned pending] :as pack}]
[react/touchable-highlight {:on-press #(re-frame/dispatch [:navigate-to :stickers-pack pack])}
[react/view {:margin-bottom 27}
[react/image {:style {:height 200 :border-radius 20} :source {:uri preview}}]
@ -47,7 +57,7 @@
[react/text {:style {:font-size 15 :color colors/gray :margin-top 6}} author]]
(if installed
[installed-icon]
[price-badge price id])]]])
[price-badge price id owned pending])]]])
(defview packs []
(letsubs [packs [:stickers/all-packs]]
@ -64,7 +74,7 @@
(def sticker-icon-size 60)
(defview pack-main [modal?]
(letsubs [{:keys [id name author price thumbnail stickers installed]} [:stickers/get-current-pack]]
(letsubs [{:keys [id name author price thumbnail stickers installed owned pending]} [:stickers/get-current-pack]]
[react/view styles/screen
[status-bar/status-bar]
[react/keyboard-avoiding-view components.styles/flex
@ -76,7 +86,7 @@
[react/text {:style {:font-size 15 :color colors/gray :margin-top 6}} author]]
(if installed
[installed-icon]
[price-badge price id])]
[price-badge price id owned pending])]
[react/view {:style {:padding-top 8 :flex 1}}
[react/scroll-view {:keyboard-should-persist-taps :handled :style {:flex 1}}
[react/view {:flex-direction :row :flex-wrap :wrap}

View File

@ -2,7 +2,6 @@
(:require [cljs.spec.alpha :as spec]
[clojure.string :as string]
[status-im.js-dependencies :as dependencies]))
;; Utility functions for encoding
(def utils dependencies/web3-utils)
@ -330,7 +329,7 @@
(conj (butlast (:coll (reduce offset-reducer {:cnt 0 :coll []} lengths))) 0)))
(defn hex-to-bytes [hex]
(let [len (* (.toNumber (.toBN utils (subs hex 0 64))) 2)]
(let [len (* (hex-to-number (subs hex 0 64)) 2)]
(substr hex 64 len)))
(defn dyn-hex-to-value [hex type]

View File

@ -24,6 +24,11 @@
(def utils dependencies/web3-utils)
(def snt-contracts
{:mainnet "0x744d70fdbe2ba4cf95131626614a1763df805b9e"
:testnet "0xc55cF4B03948D7EBc8b9E8BAD92643703811d162"
:rinkeby nil})
(def abi
(clj->js
[{:constant true

View File

@ -0,0 +1,34 @@
(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 "0x82694E3DeabE4D6f4e6C180Fe6ad646aB8EF53ae"
:rinkeby nil})
(defn pack-count [web3 contract cb]
"Returns number of packs rigestered in the contract"
(ethereum/call web3
(ethereum/call-params contract "packCount()")
(fn [_ count] (cb (ethereum/hex->int count)))))
(defn pack-data [web3 contract pack-id cb]
"Returns vector of pack data parameters by pack id: [category owner mintable timestamp price contenthash]"
(ethereum/call web3
(ethereum/call-params contract "getPackData(uint256)" (ethereum/int->hex pack-id))
(fn [_ data]
(cb (abi-spec/decode (subs data 2) ["bytes4[]" "address" "bool" "uint256" "uint256" "bytes"])))))
(defn owned-tokens [web3 contract address cb]
"Returns vector of owned tokens ids in the contract by address"
(ethereum/call web3
(ethereum/call-params contract "tokensOwnedBy(address)" (ethereum/normalized-address address))
(fn [_ data]
(cb (first (abi-spec/decode (subs data 2) ["uint256[]"]))))))
(defn token-pack-id [web3 contract token cb]
"Returns pack id in the contract by token id"
(ethereum/call web3
(ethereum/call-params contract "tokenPackId(uint256)" (ethereum/int->hex token))
(fn [_ data] (cb (ethereum/hex->int data)))))