Skip to content

Commit

Permalink
add gecko and coinranking
Browse files Browse the repository at this point in the history
  • Loading branch information
vkuznecovas committed May 26, 2021
1 parent aab3ed1 commit a09899b
Show file tree
Hide file tree
Showing 2 changed files with 268 additions and 0 deletions.
156 changes: 156 additions & 0 deletions fees/price/coinranking.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/* Mysterium network payment library.
*
* Copyright (C) 2021 BlockDev AG
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package price

import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"strconv"
"strings"
"time"
)

// CoinRanking represents a CoinRanking REST API client.
type CoinRanking struct {
baseURI string
client *http.Client
accessToken *string
}

// DefaultCoinRankingURI points to default CoinRanking api.
const DefaultCoinRankingURI = "https://api.coinranking.com/v2/"

// NewCoinRanking returns a new CoinRanking API client.
func NewCoinRanking(baseURI string, coinRankingAccessToken *string) *CoinRanking {
if !strings.HasSuffix(baseURI, "/") {
baseURI += "/"
}
return &CoinRanking{
baseURI: baseURI,
accessToken: coinRankingAccessToken,

client: &http.Client{
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 10 * time.Second,
KeepAlive: 10 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 5 * time.Second,
ExpectContinueTimeout: 4 * time.Second,
ResponseHeaderTimeout: 3 * time.Second,
},
Timeout: 10 * time.Second,
},
}
}

type coinsPricesResponse struct {
Status string `json:"status"`
Data struct {
Coins []struct {
UUID string `json:"uuid"`
Price string `json:"price"`
} `json:"coins"`
} `json:"data"`
}

var currencyToUuid = map[currency]string{
ETH: "razxDUgYGNAdQ",
MYST: "C5Hx25DA3vp8h",
MATIC: "uW2tk-ILY0ii",
USD: "yhjMzLPhuIDl",
EUR: "5k-_VTxqtCEI",
}
var uuidToCurrency = map[string]currency{}

func init() {
for k, v := range currencyToUuid {
uuidToCurrency[v] = k
}
}

func coinToUUID(coin currency) (string, bool) {
u, ok := currencyToUuid[coin]
return u, ok
}

func uuidToCoin(uuid string) (currency, bool) {
c, ok := uuidToCurrency[uuid]
return c, ok
}

// GetCoinPrice returns the coin price in the given currencies for given coins.
func (g *CoinRanking) GetCoinPrice(coins []string, vsCurrencies []string) (PriceResponse, error) {
r := make(PriceResponse)
for _, cur := range vsCurrencies {
r[cur] = make(Prices)

url := g.baseURI + "coins"
req, _ := http.NewRequest(http.MethodGet, url, nil)
if g.accessToken != nil && *g.accessToken != "" {
req.Header.Add("x-access-token", *g.accessToken)
}
q := req.URL.Query()
for _, co := range coins {
u, ok := coinToUUID(currency(co))
if !ok {
return PriceResponse{}, errors.New("unknown coin: " + co)
}
q.Add("uuids[]", u)
}

curUuid, ok := coinToUUID(currency(cur))
if !ok {
return PriceResponse{}, errors.New("unknown currency")
}
q.Add("referenceCurrencyUuid", curUuid)
req.URL.RawQuery = q.Encode()

resp, err := g.client.Do(req)
if err != nil {
return PriceResponse{}, err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
body, _ := ioutil.ReadAll(resp.Body)
return PriceResponse{}, fmt.Errorf("got an unexpected error code %v, body: %v", resp.StatusCode, string(body))
}
var res coinsPricesResponse
if err = json.NewDecoder(resp.Body).Decode(&res); err != nil {
return PriceResponse{}, err
}

for _, v := range res.Data.Coins {
price, err := strconv.ParseFloat(v.Price, 64)
if err != nil {
return PriceResponse{}, err
}

coin, ok := uuidToCoin(v.UUID)
if !ok {
return PriceResponse{}, errors.New("unknown coin: " + v.UUID)
}
r[cur][string(coin)] = price
}
}
return r, nil
}
112 changes: 112 additions & 0 deletions fees/price/gecko.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/* Mysterium network payment library.
*
* Copyright (C) 2021 BlockDev AG
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package price

import (
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"strings"
"time"
)

type currency string

const (
// ETH - the ethereum currency.
ETH currency = "ethereum"
// MYST - the mysterium coin.
MYST currency = "mysterium"
// MATIC - the matic coin.
MATIC currency = "matic-network"
// USD - the good old dollar.
USD currency = "usd"
// EUR - the official currency of the European Union
EUR currency = "eur"
)

// Gecko represents a gecko REST API client.
type Gecko struct {
baseURI string
client *http.Client
}

// DefaultGeckoURI points to default coingecko api.
const DefaultGeckoURI = "https://api.coingecko.com/api/v3"

// NewGecko returns a new gecko API client.
func NewGecko(baseURI string) *Gecko {
if !strings.HasSuffix(baseURI, "/") {
baseURI += "/"
}
return &Gecko{
baseURI: baseURI,
client: &http.Client{
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 10 * time.Second,
KeepAlive: 10 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 4 * time.Second,
ResponseHeaderTimeout: 3 * time.Second,
},
Timeout: 30 * time.Second,
},
}
}

// Prices represents a single coin pricing information.
type Prices map[string]float64

// PriceResponse represents the gecko price response.
type PriceResponse map[string]Prices

// GetPriceInUSD returns the price in USD.
func (pr PriceResponse) GetPriceInUSD(c currency) (float64, bool) {
v, ok := pr[string(c)]
if !ok {
return 0, false
}
p, ok := v[string(USD)]
return p, ok
}

// GetCoinPrice returns the coin price in the given currencies for given coins.
func (g *Gecko) GetCoinPrice(coins []string, vsCurrencies []string) (PriceResponse, error) {
path := fmt.Sprintf("simple/price?ids=%v&vs_currencies=%v", strings.Join(coins, ","), strings.Join(vsCurrencies, ","))
url := fmt.Sprintf("%v%v", g.baseURI, path)
resp, err := g.client.Get(url)
if err != nil {
return PriceResponse{}, err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
body, _ := ioutil.ReadAll(resp.Body)
return PriceResponse{}, fmt.Errorf("got an unexpected error code %v, body: %v", resp.StatusCode, string(body))
}

var res PriceResponse
if err = json.NewDecoder(resp.Body).Decode(&res); err != nil {
return PriceResponse{}, err
}

return res, nil
}

0 comments on commit a09899b

Please sign in to comment.