Skip to content

Commit

Permalink
(feat) Added logic in the markets assistant to load all the tokens fr…
Browse files Browse the repository at this point in the history
…om the official tokens lists for each environment
  • Loading branch information
aarmoa committed Jun 6, 2024
1 parent 68f05f6 commit 2218318
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 29 deletions.
2 changes: 2 additions & 0 deletions client/chain/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ func createClient(senderAddress cosmtypes.AccAddress, cosmosKeyring keyring.Keyr
}

clientCtx = clientCtx.WithNodeURI(network.TmEndpoint).WithClient(tmClient)
// configure Keyring as nil to avoid the account initialization request when running unit tests
clientCtx.Keyring = nil

chainClient, err := NewChainClient(
clientCtx,
Expand Down
51 changes: 24 additions & 27 deletions client/chain/markets_assistant.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ import (
var legacyMarketAssistantLazyInitialization sync.Once
var legacyMarketAssistant MarketsAssistant

type TokenMetadata interface {
GetName() string
GetAddress() string
GetSymbol() string
GetLogo() string
GetDecimals() int32
GetUpdatedAt() int64
}

type MarketsAssistant struct {
tokensBySymbol map[string]core.Token
tokensByDenom map[string]core.Token
Expand Down Expand Up @@ -140,6 +149,17 @@ func NewMarketsAssistant(networkName string) (MarketsAssistant, error) {

func NewMarketsAssistantInitializedFromChain(ctx context.Context, exchangeClient exchange.ExchangeClient) (MarketsAssistant, error) {
assistant := newMarketsAssistant()

officialTokens, err := core.LoadTokens(exchangeClient.GetNetwork().OfficialTokensListUrl)
if err == nil {
for _, tokenMetadata := range officialTokens {
if tokenMetadata.Denom != "" {
// add tokens to the assistant ensuring all of them get assigned a unique symbol
tokenRepresentation(tokenMetadata.GetSymbol(), tokenMetadata, tokenMetadata.Denom, &assistant)
}
}
}

spotMarketsRequest := spotExchangePB.MarketsRequest{
MarketStatus: "active",
}
Expand All @@ -160,8 +180,8 @@ func NewMarketsAssistantInitializedFromChain(ctx context.Context, exchangeClient
quoteTokenSymbol = marketInfo.GetQuoteTokenMeta().GetSymbol()
}

baseToken := spotTokenRepresentation(baseTokenSymbol, marketInfo.GetBaseTokenMeta(), marketInfo.GetBaseDenom(), &assistant)
quoteToken := spotTokenRepresentation(quoteTokenSymbol, marketInfo.GetQuoteTokenMeta(), marketInfo.GetQuoteDenom(), &assistant)
baseToken := tokenRepresentation(baseTokenSymbol, marketInfo.GetBaseTokenMeta(), marketInfo.GetBaseDenom(), &assistant)
quoteToken := tokenRepresentation(quoteTokenSymbol, marketInfo.GetQuoteTokenMeta(), marketInfo.GetQuoteDenom(), &assistant)

makerFeeRate := decimal.RequireFromString(marketInfo.GetMakerFeeRate())
takerFeeRate := decimal.RequireFromString(marketInfo.GetTakerFeeRate())
Expand Down Expand Up @@ -199,7 +219,7 @@ func NewMarketsAssistantInitializedFromChain(ctx context.Context, exchangeClient
if len(marketInfo.GetQuoteTokenMeta().GetSymbol()) > 0 {
quoteTokenSymbol := marketInfo.GetQuoteTokenMeta().GetSymbol()

quoteToken := derivativeTokenRepresentation(quoteTokenSymbol, marketInfo.GetQuoteTokenMeta(), marketInfo.GetQuoteDenom(), &assistant)
quoteToken := tokenRepresentation(quoteTokenSymbol, marketInfo.GetQuoteTokenMeta(), marketInfo.GetQuoteDenom(), &assistant)

initialMarginRatio := decimal.RequireFromString(marketInfo.GetInitialMarginRatio())
maintenanceMarginRatio := decimal.RequireFromString(marketInfo.GetMaintenanceMarginRatio())
Expand Down Expand Up @@ -265,30 +285,7 @@ func uniqueSymbol(symbol string, denom string, tokenMetaSymbol string, tokenMeta
return uniqueSymbol
}

func spotTokenRepresentation(symbol string, tokenMeta *spotExchangePB.TokenMeta, denom string, assistant *MarketsAssistant) core.Token {
_, isPresent := assistant.tokensByDenom[denom]

if !isPresent {
uniqueSymbol := uniqueSymbol(symbol, denom, tokenMeta.GetSymbol(), tokenMeta.GetName(), *assistant)

newToken := core.Token{
Name: tokenMeta.GetName(),
Symbol: symbol,
Denom: denom,
Address: tokenMeta.GetAddress(),
Decimals: tokenMeta.GetDecimals(),
Logo: tokenMeta.GetLogo(),
Updated: tokenMeta.GetUpdatedAt(),
}

assistant.tokensByDenom[denom] = newToken
assistant.tokensBySymbol[uniqueSymbol] = newToken
}

return assistant.tokensByDenom[denom]
}

func derivativeTokenRepresentation(symbol string, tokenMeta *derivativeExchangePB.TokenMeta, denom string, assistant *MarketsAssistant) core.Token {
func tokenRepresentation(symbol string, tokenMeta TokenMetadata, denom string, assistant *MarketsAssistant) core.Token {
_, isPresent := assistant.tokensByDenom[denom]

if !isPresent {
Expand Down
23 changes: 23 additions & 0 deletions client/chain/markets_assistant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package chain

import (
"context"
"github.com/InjectiveLabs/sdk-go/client/common"
"net/http"
"net/http/httptest"
"strings"
"testing"

Expand All @@ -13,7 +16,17 @@ import (
)

func TestMarketAssistantCreationUsingMarketsFromExchange(t *testing.T) {
httpServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("[]"))
}))
defer httpServer.Close()

network := common.NewNetwork()
network.OfficialTokensListUrl = httpServer.URL

mockExchange := exchange.MockExchangeClient{}
mockExchange.Network = network
var spotMarketInfos []*spotExchangePB.SpotMarketInfo
var derivativeMarketInfos []*derivativeExchangePB.DerivativeMarketInfo
injUsdtSpotMarketInfo := createINJUSDTSpotMarketInfo()
Expand Down Expand Up @@ -74,7 +87,17 @@ func TestMarketAssistantCreationUsingMarketsFromExchange(t *testing.T) {
}

func TestMarketAssistantCreationWithAllTokens(t *testing.T) {
httpServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("[]"))
}))
defer httpServer.Close()

network := common.NewNetwork()
network.OfficialTokensListUrl = httpServer.URL

mockExchange := exchange.MockExchangeClient{}
mockExchange.Network = network
mockChain := MockChainClient{}
smartDenomMetadata := createSmartDenomMetadata()

Expand Down
11 changes: 10 additions & 1 deletion client/common/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import (
)

const (
SessionRenewalOffset = 2 * time.Minute
MainnetTokensListUrl = "https://github.com/InjectiveLabs/injective-lists/raw/master/tokens/mainnet.json"
TestnetTokensListUrl = "https://github.com/InjectiveLabs/injective-lists/raw/master/tokens/testnet.json"
DevnetTokensListUrl = "https://github.com/InjectiveLabs/injective-lists/raw/master/tokens/devnet.json"
)

func cookieByName(cookies []*http.Cookie, key string) *http.Cookie {
Expand Down Expand Up @@ -199,6 +201,7 @@ type Network struct {
ChainCookieAssistant CookieAssistant
ExchangeCookieAssistant CookieAssistant
ExplorerCookieAssistant CookieAssistant
OfficialTokensListUrl string
}

func LoadNetwork(name string, node string) Network {
Expand All @@ -218,6 +221,7 @@ func LoadNetwork(name string, node string) Network {
ChainCookieAssistant: &DisabledCookieAssistant{},
ExchangeCookieAssistant: &DisabledCookieAssistant{},
ExplorerCookieAssistant: &DisabledCookieAssistant{},
OfficialTokensListUrl: MainnetTokensListUrl,
}

case "devnet-1":
Expand All @@ -234,6 +238,7 @@ func LoadNetwork(name string, node string) Network {
ChainCookieAssistant: &DisabledCookieAssistant{},
ExchangeCookieAssistant: &DisabledCookieAssistant{},
ExplorerCookieAssistant: &DisabledCookieAssistant{},
OfficialTokensListUrl: DevnetTokensListUrl,
}
case "devnet":
return Network{
Expand All @@ -249,6 +254,7 @@ func LoadNetwork(name string, node string) Network {
ChainCookieAssistant: &DisabledCookieAssistant{},
ExchangeCookieAssistant: &DisabledCookieAssistant{},
ExplorerCookieAssistant: &DisabledCookieAssistant{},
OfficialTokensListUrl: DevnetTokensListUrl,
}
case "testnet":
validNodes := []string{"lb", "sentry"}
Expand Down Expand Up @@ -303,6 +309,7 @@ func LoadNetwork(name string, node string) Network {
ChainCookieAssistant: chainCookieAssistant,
ExchangeCookieAssistant: exchangeCookieAssistant,
ExplorerCookieAssistant: explorerCookieAssistant,
OfficialTokensListUrl: TestnetTokensListUrl,
}
case "mainnet":
validNodes := []string{"lb"}
Expand Down Expand Up @@ -342,6 +349,7 @@ func LoadNetwork(name string, node string) Network {
ChainCookieAssistant: chainCookieAssistant,
ExchangeCookieAssistant: exchangeCookieAssistant,
ExplorerCookieAssistant: explorerCookieAssistant,
OfficialTokensListUrl: MainnetTokensListUrl,
}
}

Expand All @@ -355,6 +363,7 @@ func NewNetwork() Network {
ChainCookieAssistant: &DisabledCookieAssistant{},
ExchangeCookieAssistant: &DisabledCookieAssistant{},
ExplorerCookieAssistant: &DisabledCookieAssistant{},
OfficialTokensListUrl: MainnetTokensListUrl,
}
}

Expand Down
67 changes: 67 additions & 0 deletions client/core/tokens_file_loader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package core

import (
"encoding/json"
"fmt"
"net/http"
)

type TokenMetadata struct {
Address string `json:"address"`
IsNative bool `json:"isNative"`
TokenVerification string `json:"tokenVerification"`
Decimals int32 `json:"decimals"`
CoinGeckoId string `json:"coinGeckoId"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Logo string `json:"logo"`
Creator string `json:"creator"`
Denom string `json:"denom"`
TokenType string `json:"tokenType"`
ExternalLogo string `json:"externalLogo"`
}

func (tm TokenMetadata) GetName() string {
return tm.Name
}

func (tm TokenMetadata) GetAddress() string {
return tm.Address
}

func (tm TokenMetadata) GetSymbol() string {
return tm.Symbol
}

func (tm TokenMetadata) GetLogo() string {
return tm.Logo
}

func (tm TokenMetadata) GetDecimals() int32 {
return tm.Decimals
}

func (tm TokenMetadata) GetUpdatedAt() int64 {
return -1
}

// LoadTokens loads tokens from the given file URL
func LoadTokens(tokensFileUrl string) ([]TokenMetadata, error) {
var tokensMetadata []TokenMetadata
response, err := http.Get(tokensFileUrl)
if err != nil {
return tokensMetadata, err
}
if 400 <= response.StatusCode {
return tokensMetadata, fmt.Errorf("failed to load tokens from %s: %s", tokensFileUrl, response.Status)
}
defer response.Body.Close()

decoder := json.NewDecoder(response.Body)
err = decoder.Decode(&tokensMetadata)
if err != nil {
return make([]TokenMetadata, 0), err
}

return tokensMetadata, nil
}
73 changes: 73 additions & 0 deletions client/core/tokens_file_loader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package core

import (
"encoding/json"
"fmt"
"gotest.tools/v3/assert"
"net/http"
"net/http/httptest"
"testing"
)

func TestLoadTokensFromUrl(t *testing.T) {
tokensMetadata := make([]TokenMetadata, 2)
tokensMetadata = append(tokensMetadata, TokenMetadata{
Address: "",
IsNative: true,
Decimals: 9,
Symbol: "SOL",
Name: "Solana",
Logo: "https://imagedelivery.net/DYKOWp0iCc0sIkF-2e4dNw/2aa4deed-fa31-4d1a-ba0a-d698b84f3800/public",
Creator: "inj15jeczm4mqwtc9lk4c0cyynndud32mqd4m9xnmu",
CoinGeckoId: "solana",
Denom: "",
TokenType: "spl",
TokenVerification: "verified",
ExternalLogo: "solana.png",
},
)
tokensMetadata = append(tokensMetadata, TokenMetadata{
Address: "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270",
IsNative: false,
Decimals: 18,
Symbol: "WMATIC",
Name: "Wrapped Matic",
Logo: "https://imagedelivery.net/DYKOWp0iCc0sIkF-2e4dNw/0d061e1e-a746-4b19-1399-8187b8bb1700/public",
Creator: "inj169ed97mcnf8ay6rgvskn95n6tyt46uwvy5qgs0",
CoinGeckoId: "wmatic",
Denom: "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270",
TokenType: "evm",
TokenVerification: "verified",
ExternalLogo: "polygon.png",
},
)

metadataString, err := json.Marshal(tokensMetadata)
assert.NilError(t, err)

httpServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write(metadataString)
}))
defer httpServer.Close()

loadedTokens, err := LoadTokens(httpServer.URL)
assert.NilError(t, err)

assert.Equal(t, len(loadedTokens), len(tokensMetadata))

for i, metadata := range tokensMetadata {
assert.Equal(t, loadedTokens[i], metadata)
}
}

func TestLoadTokensFromUrlReturnsNoTokensWhenRequestErrorHappens(t *testing.T) {
httpServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
}))
defer httpServer.Close()

loadedTokens, err := LoadTokens(httpServer.URL)
assert.Error(t, err, fmt.Sprintf("failed to load tokens from %s: %v %s", httpServer.URL, http.StatusNotFound, http.StatusText(http.StatusNotFound)))
assert.Equal(t, len(loadedTokens), 0)
}
5 changes: 5 additions & 0 deletions client/exchange/exchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ type ExchangeClient interface {
GetInfo(ctx context.Context, req *metaPB.InfoRequest) (*metaPB.InfoResponse, error)
GetVersion(ctx context.Context, req *metaPB.VersionRequest) (*metaPB.VersionResponse, error)
Ping(ctx context.Context, req *metaPB.PingRequest) (*metaPB.PingResponse, error)
GetNetwork() common.Network
Close()
}

Expand Down Expand Up @@ -961,6 +962,10 @@ func (c *exchangeClient) StreamAccountPortfolio(ctx context.Context, accountAddr
return stream, nil
}

func (c *exchangeClient) GetNetwork() common.Network {
return c.network
}

func (c *exchangeClient) Close() {
c.conn.Close()
}
6 changes: 6 additions & 0 deletions client/exchange/exchange_test_support.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package exchange
import (
"context"
"errors"
"github.com/InjectiveLabs/sdk-go/client/common"

accountPB "github.com/InjectiveLabs/sdk-go/exchange/accounts_rpc/pb"
auctionPB "github.com/InjectiveLabs/sdk-go/exchange/auction_rpc/pb"
Expand All @@ -16,6 +17,7 @@ import (
)

type MockExchangeClient struct {
Network common.Network
SpotMarketsResponses []*spotExchangePB.MarketsResponse
DerivativeMarketsResponses []*derivativeExchangePB.MarketsResponse
}
Expand Down Expand Up @@ -303,6 +305,10 @@ func (e *MockExchangeClient) Ping(ctx context.Context, req *metaPB.PingRequest)
return &metaPB.PingResponse{}, nil
}

func (e *MockExchangeClient) GetNetwork() common.Network {
return e.Network
}

func (e *MockExchangeClient) Close() {

}

0 comments on commit 2218318

Please sign in to comment.