diff --git a/src/clj/commiteth/util/crypto_fiat_value.clj b/src/clj/commiteth/util/crypto_fiat_value.clj index 07bea89..b0654a4 100644 --- a/src/clj/commiteth/util/crypto_fiat_value.clj +++ b/src/clj/commiteth/util/crypto_fiat_value.clj @@ -1,66 +1,100 @@ (ns commiteth.util.crypto-fiat-value (:require [mount.core :as mount] + [clj-time.core :as t] + [clojure.edn :as edn] [clojure.tools.logging :as log] [commiteth.config :refer [env]] [commiteth.util.util :refer [json-api-request]])) -(defn fiat-api-provider [] +(defn- fiat-api-provider [] (env :fiat-api-provider :coinmarketcap)) - -(defn get-token-usd-price-cryptonator +(defn- get-token-usd-price-cryptonator "Get current USD value for a token using cryptonator API" [tla] + (log/infof "%s: Getting price-data from Cryptonator" tla) (let [token (subs (str tla) 1) - url (str "https://api.cryptonator.com/api/ticker/" - token - "-usd") + url (format "https://api.cryptonator.com/api/ticker/%s-usd" token) m (json-api-request url)] (-> (get-in m ["ticker" "price"]) - (read-string)))) + (edn/read-string)))) - -(def tla-to-id-mapping (atom {})) - -(defn make-tla-to-id-mapping +(defn- make-tla-to-id-mapping "Coinmarketcap API uses it's own IDs for tokens instead of TLAs" [] - (let [data (json-api-request "https://api.coinmarketcap.com/v1/ticker/?limit=0")] - (into {} (map - (fn [x] [(keyword (get x "symbol")) (get x "id")]) - data)))) + (->> (json-api-request "https://api.coinmarketcap.com/v1/ticker/?limit=0") + (map (fn [x] [(keyword (get x "symbol")) (get x "id")])) + (into {}))) -(defn get-token-usd-price-coinmarketcap +(defn- get-token-usd-price-coinmarketcap "Get current USD value for a token using coinmarketcap API" - [tla] - (let [token-id (get @tla-to-id-mapping tla) - url (format "https://api.coinmarketcap.com/v1/ticker/%s" token-id) - data (json-api-request url)] - (-> (first data) - (get "price_usd") - (read-string)))) + [tla token-id] + {:pre [(some? token-id)]} + (log/infof "%s: Getting price-data from CoinMarketCap (token-id %s)" tla token-id) + (-> (json-api-request (format "https://api.coinmarketcap.com/v1/ticker/%s" token-id)) + (first) + (get "price_usd") + (edn/read-string))) -(defn- get-price-fn [] - (let [fns {:cryptonator get-token-usd-price-cryptonator - :coinmarketcap get-token-usd-price-coinmarketcap}] - (get fns (fiat-api-provider)))) +(defrecord PriceInfo [tla usd date]) -(defn bounty-usd-value - "Get current USD value of a bounty. bounty is a map of token-tla (keyword) to value" - [bounty] - (let [get-token-usd-price (get-price-fn)] - (reduce + (map (fn [[tla value]] - (let [usd-price (get-token-usd-price tla)] - (* usd-price value))) - bounty)))) +(defn recent? [price-info] + (t/after? (:date price-info) (t/minus (t/now) (t/minutes 5)))) +(defprotocol IFiatCryptoConverter + (start [_]) + (convert-usd [_ tla amount])) + +(defrecord FiatCryptoConverter [provider state] + IFiatCryptoConverter + (start [_] + (when (= :coinmarketcap provider) + (swap! state assoc :tla->id (make-tla-to-id-mapping)))) + (convert-usd [_ tla amount] + (if-let [recent-price-info (and (some-> (get-in @state [:price-info tla]) recent?) + ;; return actual price info + (get-in @state [:price-info tla]))] + (* (:usd recent-price-info) amount) + ;; if we don't have price-info we need to fetch & store it + (let [price (case provider + :coinmarketcap (get-token-usd-price-coinmarketcap + tla + (get-in @state [:tla->id tla])) + :cryptonator (get-token-usd-price-cryptonator tla))] + (swap! state assoc-in [:price-info tla] (->PriceInfo tla price (t/now))) + (* price amount))))) (mount/defstate - crypto-fiat-util - :start - (do - (reset! tla-to-id-mapping (make-tla-to-id-mapping)) - (log/info "crypto-fiat-util started")) - :stop - (log/info "crypto-fiat-util stopped")) + fiat-converter + :start (let [provider (fiat-api-provider)] + (log/infof "Starting FiatCryptoConverter %s" provider) + (doto (->FiatCryptoConverter provider (atom {})) + (start))) + :stop (log/info "Stopping FiatCryptoConverter")) + +;; public api ------------------------------------------------------------------ + +(defn bounty-usd-value + "Get current USD value for the crypto-amounts passed as argument. + Example: {:ETH 123, :SNT 456}" + [crypto-amounts] + (->> crypto-amounts + (map (fn [[tla value]] + (convert-usd fiat-converter tla value))) + (reduce + 0))) + + +(comment + (fiat-api-provider) + + (def fc (->FiatCryptoConverter (fiat-api-provider) (atom {}))) + (start fc) + + (convert-usd fc :SNT 400) + + (bounty-usd-value {:ETH 2 :ANT 2 :SNT 5}) + + (mount/start) + + )