Skip to content

Commit 16a4c65

Browse files
vuong177catShaarkThanhNhann
authored
Add JunoSwap as price provider to price-feeder tool (CosmosContracts#353)
* add wasm binding * update juno provider with getAvailablePairs func * done GetTickerPrices function of Juno (dont have tests) * fix error test fot price-feeder of juno * update more test case for GetTickerPrices func of Juno * nits Co-authored-by: catShaark <[email protected]> Co-authored-by: ThanhNhann <[email protected]>
1 parent 2de60e2 commit 16a4c65

File tree

7 files changed

+492
-3
lines changed

7 files changed

+492
-3
lines changed

price-feeder/config.toml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ providers = [
5757
]
5858
quote = "USD"
5959

60+
[[currency_pairs]]
61+
base = "JUNO"
62+
providers = [
63+
"juno",
64+
]
65+
quote = "USD"
66+
6067
[[currency_pairs]]
6168
base = "JUNO"
6269
providers = [
@@ -82,9 +89,9 @@ providers = [
8289
quote = "USDT"
8390

8491
[account]
85-
address = "juno1244e8dl62sfa75d0zft7vvx59je3rdhxcn5kr9"
92+
address = "juno1pry5cux508t55pf00pryl6hp09cfahml5k6w8m"
8693
chain_id = "test-1"
87-
validator = "junovaloper1244e8dl62sfa75d0zft7vvx59je3rdhx8wzecu"
94+
validator = "junovaloper1pry5cux508t55pf00pryl6hp09cfahmlttvpuz"
8895
prefix = "juno"
8996

9097
[keyring]

price-feeder/config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ var (
3636
provider.ProviderKraken: {},
3737
provider.ProviderBinance: {},
3838
provider.ProviderOsmosis: {},
39+
provider.ProviderJuno: {},
3940
provider.ProviderOkx: {},
4041
provider.ProviderHuobi: {},
4142
provider.ProviderGate: {},

price-feeder/oracle/oracle.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,9 @@ func NewProvider(
470470
case provider.ProviderOsmosis:
471471
return provider.NewOsmosisProvider(endpoint), nil
472472

473+
case provider.ProviderJuno:
474+
return provider.NewJunoProvider(endpoint), nil
475+
473476
case provider.ProviderHuobi:
474477
return provider.NewHuobiProvider(ctx, logger, endpoint, providerPairs...)
475478

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
package provider
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
_ "io"
8+
"io/ioutil"
9+
"net/http"
10+
"reflect"
11+
"strings"
12+
13+
"github.com/CosmosContracts/juno/price-feeder/oracle/types"
14+
"github.com/CosmosContracts/juno/price-feeder/oracle/util"
15+
)
16+
17+
const (
18+
junoRestURL = "https://api-junoswap.enigma-validator.com"
19+
junoPriceTokenEndpoint = "/prices/tokens"
20+
junoVolumeTokenEndpoint = "/volumes/tokens"
21+
junoCandleEndpoint = "/prices/tokens/historical"
22+
junoPairsEndpoint = "/summary/pairs"
23+
)
24+
25+
var _ Provider = (*JunoProvider)(nil)
26+
27+
type (
28+
// JunoProvider defines an Oracle provider implemented by the Juno public
29+
// API.
30+
//
31+
// REF: https://api-junoswap.enigma-validator.com/swagger/#/
32+
JunoProvider struct {
33+
baseURL string
34+
client *http.Client
35+
}
36+
37+
// JunoTokenPriceResponse defines the response structure of price for an Juno token
38+
// request.
39+
JunoTokenPriceResponse struct {
40+
Price float64 `json:"price"`
41+
}
42+
43+
// JunoTokenVolumnResponse defines the response structure of volume for an Juno token
44+
// request.
45+
JunoTokenVolumnResponse struct {
46+
Volumne float64 `json:"volumes"`
47+
}
48+
49+
// JunoTokenInfo defines the response structure of information of an Juno token
50+
// request.
51+
JunoTokenInfo struct {
52+
JunoTokenPriceResponse
53+
Symbol string
54+
Volume float64
55+
}
56+
57+
// JunoPairData defines the data response structure for an Juno pair.
58+
JunoPairData struct {
59+
Base string `json:"base"`
60+
Quote string `json:"target"`
61+
}
62+
)
63+
64+
func NewJunoProvider(endpoint Endpoint) *JunoProvider {
65+
if endpoint.Name == ProviderJuno {
66+
return &JunoProvider{
67+
baseURL: endpoint.Rest,
68+
client: newDefaultHTTPClient(),
69+
}
70+
}
71+
return &JunoProvider{
72+
baseURL: junoRestURL,
73+
client: newDefaultHTTPClient(),
74+
}
75+
}
76+
77+
func (p JunoProvider) GetTickerPrices(pairs ...types.CurrencyPair) (map[string]types.TickerPrice, error) {
78+
79+
var junoTokenInfo []JunoTokenInfo
80+
// Get price and symbol of tokens
81+
pathPriceToken := fmt.Sprintf("%s%s/current", p.baseURL, junoPriceTokenEndpoint)
82+
resp, err := p.client.Get(pathPriceToken)
83+
if err != nil {
84+
return nil, fmt.Errorf("failed to make Juno request: %w", err)
85+
}
86+
err = checkHTTPStatus(resp)
87+
if err != nil {
88+
return nil, err
89+
}
90+
91+
defer resp.Body.Close()
92+
93+
bz, err := io.ReadAll(resp.Body)
94+
if err != nil {
95+
return nil, fmt.Errorf("failed to read Juno response body: %w", err)
96+
}
97+
var tokensResps map[string]interface{}
98+
if err := json.Unmarshal(bz, &tokensResps); err != nil {
99+
return nil, fmt.Errorf("failed to unmarshal Juno response body: %w", err)
100+
}
101+
102+
listSymbol := reflect.ValueOf(tokensResps).MapKeys()
103+
104+
// Get symbol and price of Tokens
105+
for _, symbol := range listSymbol {
106+
tokenInfo, err := getSymbolAndPriceToken(symbol, tokensResps)
107+
if err != nil {
108+
return nil, fmt.Errorf("failed to unmarshal Juno response body: %w", err)
109+
}
110+
junoTokenInfo = append(junoTokenInfo, tokenInfo)
111+
}
112+
113+
// Get volume of tokens
114+
for id, symbol := range listSymbol {
115+
path := fmt.Sprintf("%s%s/%s/current", p.baseURL, junoVolumeTokenEndpoint, symbol)
116+
resp, err = p.client.Get(path)
117+
if err != nil {
118+
return nil, fmt.Errorf("failed to make Juno request: %w", err)
119+
}
120+
err = checkHTTPStatus(resp)
121+
if err != nil {
122+
return nil, err
123+
}
124+
125+
defer resp.Body.Close()
126+
127+
bz, err = io.ReadAll(resp.Body)
128+
if err != nil {
129+
return nil, fmt.Errorf("failed to read Juno response body: %w", err)
130+
}
131+
var tokensResp JunoTokenVolumnResponse
132+
if err := json.Unmarshal(bz, &tokensResp); err != nil {
133+
return nil, fmt.Errorf("failed to unmarshal Juno response body: %w", err)
134+
}
135+
junoTokenInfo[id].Volume = tokensResp.Volumne
136+
}
137+
138+
baseDenomIdx := make(map[string]types.CurrencyPair)
139+
for _, cp := range pairs {
140+
baseDenomIdx[strings.ToUpper(cp.Base)] = cp
141+
}
142+
143+
tickerPrices := make(map[string]types.TickerPrice, len(pairs))
144+
for _, tr := range junoTokenInfo {
145+
symbol := strings.ToUpper(tr.Symbol) // symbol == base in a currency pair
146+
147+
cp, ok := baseDenomIdx[symbol]
148+
if !ok {
149+
// skip tokens that are not requested
150+
continue
151+
}
152+
153+
if _, ok := tickerPrices[symbol]; ok {
154+
return nil, fmt.Errorf("duplicate token found in Juno response: %s", symbol)
155+
}
156+
157+
price, err := util.NewDecFromFloat(tr.Price)
158+
if err != nil {
159+
return nil, fmt.Errorf("failed to read Juno price (%f) for %s", tr.Price, symbol)
160+
}
161+
162+
volume, err := util.NewDecFromFloat(tr.Volume)
163+
if err != nil {
164+
return nil, fmt.Errorf("failed to read Juno volume (%f) for %s", tr.Volume, symbol)
165+
}
166+
tickerPrices[cp.String()] = types.TickerPrice{Price: price, Volume: volume}
167+
}
168+
169+
for _, cp := range pairs {
170+
if _, ok := tickerPrices[cp.String()]; !ok {
171+
return nil, fmt.Errorf(types.ErrMissingExchangeRate.Error(), cp.String())
172+
}
173+
}
174+
175+
return tickerPrices, nil
176+
177+
}
178+
179+
func (p JunoProvider) GetCandlePrices(pairs ...types.CurrencyPair) (map[string][]types.CandlePrice, error) {
180+
return nil, nil
181+
}
182+
183+
func (p JunoProvider) GetAvailablePairs() (map[string]struct{}, error) {
184+
path := fmt.Sprintf("%s%s", p.baseURL, junoPairsEndpoint)
185+
resp, err := p.client.Get(path)
186+
if err != nil {
187+
return nil, err
188+
}
189+
190+
err = checkHTTPStatus(resp)
191+
if err != nil {
192+
return nil, err
193+
}
194+
defer resp.Body.Close()
195+
196+
body, _ := ioutil.ReadAll(resp.Body)
197+
198+
var pairsSummary []JunoPairData
199+
if err := json.Unmarshal(body, &pairsSummary); err != nil {
200+
return nil, err
201+
}
202+
203+
availablePairs := make(map[string]struct{}, len(pairsSummary))
204+
for _, pair := range pairsSummary {
205+
cp := types.CurrencyPair{
206+
Base: strings.ToUpper(pair.Base),
207+
Quote: strings.ToUpper(pair.Quote),
208+
}
209+
availablePairs[cp.String()] = struct{}{}
210+
}
211+
212+
return availablePairs, nil
213+
}
214+
215+
// SubscribeCurrencyPairs performs a no-op since juno does not use websockets
216+
func (p JunoProvider) SubscribeCurrencyPairs(pairs ...types.CurrencyPair) error {
217+
return nil
218+
}
219+
220+
// Get symbol and price of token
221+
func getSymbolAndPriceToken(symbol reflect.Value, tokensResps map[string]interface{}) (tokenInfo JunoTokenInfo, err error) {
222+
223+
var tokenPrice JunoTokenPriceResponse
224+
tokenInfo.Symbol = symbol.String()
225+
dataOfToken := tokensResps[tokenInfo.Symbol]
226+
body, _ := json.Marshal(dataOfToken)
227+
if err := json.Unmarshal(body, &tokenPrice); err != nil {
228+
return tokenInfo, fmt.Errorf("failed to unmarshal Juno response body: %w", err)
229+
}
230+
tokenInfo.Price = tokenPrice.Price
231+
232+
return tokenInfo, nil
233+
}

0 commit comments

Comments
 (0)