-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
aab3ed1
commit a09899b
Showing
2 changed files
with
268 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |