feat: add coingecko fallback

This commit is contained in:
Anthony Laibe 2023-02-21 10:05:16 +01:00 committed by Anthony Laibe
parent 999d8c0ee0
commit e543fda4b5
19 changed files with 759 additions and 412 deletions

View File

@ -18,6 +18,7 @@ import (
"github.com/status-im/status-go/services/wallet/currency"
"github.com/status-im/status-go/services/wallet/history"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/services/wallet/thirdparty/opensea"
"github.com/status-im/status-go/services/wallet/token"
"github.com/status-im/status-go/services/wallet/transfer"
)
@ -295,24 +296,24 @@ func (api *API) GetCryptoOnRamps(ctx context.Context) ([]CryptoOnRamp, error) {
return api.s.cryptoOnRampManager.Get()
}
func (api *API) GetOpenseaCollectionsByOwner(ctx context.Context, chainID uint64, owner common.Address) ([]OpenseaCollection, error) {
func (api *API) GetOpenseaCollectionsByOwner(ctx context.Context, chainID uint64, owner common.Address) ([]opensea.Collection, error) {
log.Debug("call to get opensea collections")
client, err := newOpenseaClient(chainID, api.s.openseaAPIKey)
client, err := opensea.NewOpenseaClient(chainID, api.s.openseaAPIKey)
if err != nil {
return nil, err
}
return client.fetchAllCollectionsByOwner(owner)
return client.FetchAllCollectionsByOwner(owner)
}
func (api *API) GetOpenseaAssetsByOwnerAndCollection(ctx context.Context, chainID uint64, owner common.Address, collectionSlug string, limit int) ([]OpenseaAsset, error) {
func (api *API) GetOpenseaAssetsByOwnerAndCollection(ctx context.Context, chainID uint64, owner common.Address, collectionSlug string, limit int) ([]opensea.Asset, error) {
log.Debug("call to get opensea assets")
client, err := newOpenseaClient(chainID, api.s.openseaAPIKey)
client, err := opensea.NewOpenseaClient(chainID, api.s.openseaAPIKey)
if err != nil {
return nil, err
}
return client.fetchAllAssetsByOwnerAndCollection(owner, collectionSlug, limit)
return client.FetchAllAssetsByOwnerAndCollection(owner, collectionSlug, limit)
}
func (api *API) AddEthereumChain(ctx context.Context, network params.Network) error {
@ -332,27 +333,27 @@ func (api *API) GetEthereumChains(ctx context.Context, onlyEnabled bool) ([]*par
func (api *API) FetchPrices(ctx context.Context, symbols []string, currencies []string) (map[string]map[string]float64, error) {
log.Debug("call to FetchPrices")
return api.s.priceManager.FetchPrices(symbols, currencies)
return api.s.marketManager.FetchPrices(symbols, currencies)
}
func (api *API) FetchMarketValues(ctx context.Context, symbols []string, currencies []string) (map[string]map[string]thirdparty.MarketCoinValues, error) {
func (api *API) FetchMarketValues(ctx context.Context, symbols []string, currency string) (map[string]thirdparty.TokenMarketValues, error) {
log.Debug("call to FetchMarketValues")
return api.s.cryptoCompare.FetchTokenMarketValues(symbols, currencies)
return api.s.marketManager.FetchTokenMarketValues(symbols, currency)
}
func (api *API) GetHourlyMarketValues(ctx context.Context, symbol string, currency string, limit int, aggregate int) ([]thirdparty.TokenHistoricalPairs, error) {
func (api *API) GetHourlyMarketValues(ctx context.Context, symbol string, currency string, limit int, aggregate int) ([]thirdparty.HistoricalPrice, error) {
log.Debug("call to GetHourlyMarketValues")
return api.s.cryptoCompare.FetchHourlyMarketValues(symbol, currency, limit, aggregate)
return api.s.marketManager.FetchHistoricalHourlyPrices(symbol, currency, limit, aggregate)
}
func (api *API) GetDailyMarketValues(ctx context.Context, symbol string, currency string, limit int, allData bool, aggregate int) ([]thirdparty.TokenHistoricalPairs, error) {
func (api *API) GetDailyMarketValues(ctx context.Context, symbol string, currency string, limit int, allData bool, aggregate int) ([]thirdparty.HistoricalPrice, error) {
log.Debug("call to GetDailyMarketValues")
return api.s.cryptoCompare.FetchDailyMarketValues(symbol, currency, limit, allData, aggregate)
return api.s.marketManager.FetchHistoricalDailyPrices(symbol, currency, limit, allData, aggregate)
}
func (api *API) FetchTokenDetails(ctx context.Context, symbols []string) (map[string]thirdparty.Coin, error) {
func (api *API) FetchTokenDetails(ctx context.Context, symbols []string) (map[string]thirdparty.TokenDetails, error) {
log.Debug("call to FetchTokenDetails")
return api.s.cryptoCompare.FetchTokenDetails(symbols)
return api.s.marketManager.FetchTokenDetails(symbols)
}
func (api *API) GetSuggestedFees(ctx context.Context, chainID uint64) (*SuggestedFees, error) {

View File

@ -7,7 +7,7 @@ import (
iso4217 "github.com/ladydascalie/currency"
"github.com/status-im/status-go/services/wallet/price"
"github.com/status-im/status-go/services/wallet/market"
"github.com/status-im/status-go/services/wallet/token"
)
@ -25,12 +25,12 @@ type Format struct {
type FormatPerSymbol = map[string]Format
type Currency struct {
priceManager *price.Manager
marketManager *market.Manager
}
func NewCurrency(priceManager *price.Manager) *Currency {
func NewCurrency(marketManager *market.Manager) *Currency {
return &Currency{
priceManager: priceManager,
marketManager: marketManager,
}
}
@ -126,7 +126,7 @@ func (cm *Currency) FetchTokenCurrencyFormats(symbols []string) (FormatPerSymbol
formats := make(FormatPerSymbol)
// Get latest cached price, fetch only if not available
prices, err := cm.priceManager.GetOrFetchPrices(symbols, []string{decimalsCalculationCurrency}, math.MaxInt64)
prices, err := cm.marketManager.GetOrFetchPrices(symbols, []string{decimalsCalculationCurrency}, math.MaxInt64)
if err != nil {
return nil, err
}

View File

@ -6,7 +6,7 @@ import (
"time"
"github.com/ethereum/go-ethereum/event"
"github.com/status-im/status-go/services/wallet/price"
"github.com/status-im/status-go/services/wallet/market"
"github.com/status-im/status-go/services/wallet/token"
"github.com/status-im/status-go/services/wallet/walletevent"
)
@ -26,9 +26,9 @@ type Service struct {
cancelFn context.CancelFunc
}
func NewService(db *sql.DB, walletFeed *event.Feed, tokenManager *token.Manager, priceManager *price.Manager) *Service {
func NewService(db *sql.DB, walletFeed *event.Feed, tokenManager *token.Manager, marketManager *market.Manager) *Service {
return &Service{
currency: NewCurrency(priceManager),
currency: NewCurrency(marketManager),
db: NewCurrencyDB(db),
tokenManager: tokenManager,
walletFeed: walletFeed,

View File

@ -5,7 +5,7 @@ import (
"sync"
"time"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/services/wallet/market"
)
type tokenType = string
@ -26,13 +26,13 @@ type Exchange struct {
allTimeCache map[tokenType]map[currencyType][]allTimeEntry
fetchMutex sync.Mutex
cryptoCompare *thirdparty.CryptoCompare
marketManager *market.Manager
}
func NewExchange(cryptoCompare *thirdparty.CryptoCompare) *Exchange {
func NewExchange(marketManager *market.Manager) *Exchange {
return &Exchange{
cache: make(map[tokenType]map[currencyType]map[yearType][]float32),
cryptoCompare: cryptoCompare,
marketManager: marketManager,
}
}
@ -110,7 +110,7 @@ func (e *Exchange) FetchAndCacheMissingRates(token tokenType, currency currencyT
return nil
}
res, err := e.cryptoCompare.FetchDailyMarketValues(token, currency, daysToFetch, false, 1)
res, err := e.marketManager.FetchHistoricalDailyPrices(token, currency, daysToFetch, false, 1)
if err != nil {
return err
}
@ -125,7 +125,7 @@ func (e *Exchange) FetchAndCacheMissingRates(token tokenType, currency currencyT
}
// Fetch all time
allTime, err := e.cryptoCompare.FetchDailyMarketValues(token, currency, 1, true, 30)
allTime, err := e.marketManager.FetchHistoricalDailyPrices(token, currency, 1, true, 30)
if err != nil {
return err
}

View File

@ -22,7 +22,7 @@ import (
"github.com/status-im/status-go/rpc/network"
"github.com/status-im/status-go/services/wallet/chain"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/services/wallet/market"
"github.com/status-im/status-go/services/wallet/token"
"github.com/status-im/status-go/services/wallet/walletevent"
)
@ -55,7 +55,7 @@ type Service struct {
type chainIdentity uint64
func NewService(db *sql.DB, eventFeed *event.Feed, rpcClient *statusrpc.Client, tokenManager *token.Manager, cryptoCompare *thirdparty.CryptoCompare) *Service {
func NewService(db *sql.DB, eventFeed *event.Feed, rpcClient *statusrpc.Client, tokenManager *token.Manager, marketManager *market.Manager) *Service {
return &Service{
balance: NewBalance(NewBalanceDB(db)),
db: db,
@ -63,7 +63,7 @@ func NewService(db *sql.DB, eventFeed *event.Feed, rpcClient *statusrpc.Client,
rpcClient: rpcClient,
networkManager: rpcClient.NetworkManager,
tokenManager: tokenManager,
exchange: NewExchange(cryptoCompare),
exchange: NewExchange(marketManager),
}
}

View File

@ -16,7 +16,8 @@ import (
"github.com/status-im/status-go/appdatabase"
"github.com/status-im/status-go/params"
statusRPC "github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/services/wallet/market"
"github.com/status-im/status-go/services/wallet/thirdparty/cryptocompare"
"github.com/status-im/status-go/transactions/fake"
"github.com/stretchr/testify/require"
@ -25,7 +26,7 @@ import (
func setupDummyServiceNoDependencies(t *testing.T) (service *Service, closeFn func()) {
db, err := appdatabase.InitializeDB(":memory:", "wallet-history-service-tests", 1)
require.NoError(t, err)
cryptoCompare := thirdparty.NewCryptoCompare()
cryptoCompare := cryptocompare.NewClient()
// Creating a dummy status node to simulate what it's done in get_status_node.go
upstreamConfig := params.UpstreamRPCConfig{
@ -40,7 +41,7 @@ func setupDummyServiceNoDependencies(t *testing.T) (service *Service, closeFn fu
rpcClient, err := statusRPC.NewClient(client, 1, upstreamConfig, nil, db)
require.NoError(t, err)
return NewService(db, nil, rpcClient, nil, cryptoCompare), func() {
return NewService(db, nil, rpcClient, nil, market.NewManager(cryptoCompare)), func() {
require.NoError(t, db.Close())
}
}

View File

@ -1,7 +1,10 @@
package price
package market
import (
"sync"
"time"
"github.com/status-im/status-go/services/wallet/thirdparty"
)
type DataPoint struct {
@ -12,17 +15,38 @@ type DataPoint struct {
type DataPerTokenAndCurrency = map[string]map[string]DataPoint
type Manager struct {
priceProvider Provider
priceCache DataPerTokenAndCurrency
provider thirdparty.MarketDataProvider
priceCache DataPerTokenAndCurrency
IsConnected bool
LastCheckedAt int64
IsConnectedLock sync.RWMutex
}
func NewManager(priceProvider Provider) *Manager {
func NewManager(provider thirdparty.MarketDataProvider) *Manager {
return &Manager{
priceProvider: priceProvider,
provider: provider,
priceCache: make(DataPerTokenAndCurrency),
IsConnected: true,
LastCheckedAt: time.Now().Unix(),
}
}
func (pm *Manager) FetchHistoricalDailyPrices(symbol string, currency string, limit int, allData bool, aggregate int) ([]thirdparty.HistoricalPrice, error) {
return pm.provider.FetchHistoricalDailyPrices(symbol, currency, limit, allData, aggregate)
}
func (pm *Manager) FetchHistoricalHourlyPrices(symbol string, currency string, limit int, aggregate int) ([]thirdparty.HistoricalPrice, error) {
return pm.provider.FetchHistoricalHourlyPrices(symbol, currency, limit, aggregate)
}
func (pm *Manager) FetchTokenMarketValues(symbols []string, currency string) (map[string]thirdparty.TokenMarketValues, error) {
return pm.provider.FetchTokenMarketValues(symbols, currency)
}
func (pm *Manager) FetchTokenDetails(symbols []string) (map[string]thirdparty.TokenDetails, error) {
return pm.provider.FetchTokenDetails(symbols)
}
func (pm *Manager) FetchPrice(symbol string, currency string) (float64, error) {
symbols := [1]string{symbol}
currencies := [1]string{currency}
@ -37,7 +61,7 @@ func (pm *Manager) FetchPrice(symbol string, currency string) (float64, error) {
}
func (pm *Manager) FetchPrices(symbols []string, currencies []string) (map[string]map[string]float64, error) {
result, err := pm.priceProvider.FetchPrices(symbols, currencies)
result, err := pm.provider.FetchPrices(symbols, currencies)
if err != nil {
return nil, err
}

View File

@ -1,9 +1,12 @@
package price
package market
import (
"errors"
"testing"
"github.com/stretchr/testify/require"
"github.com/status-im/status-go/services/wallet/thirdparty"
)
type MockPriceProvider struct {
@ -18,6 +21,19 @@ func (mpp *MockPriceProvider) setMockPrices(prices map[string]map[string]float64
mpp.mockPrices = prices
}
func (mpp *MockPriceProvider) FetchHistoricalDailyPrices(symbol string, currency string, limit int, allData bool, aggregate int) ([]thirdparty.HistoricalPrice, error) {
return nil, errors.New("not implmented")
}
func (mpp *MockPriceProvider) FetchHistoricalHourlyPrices(symbol string, currency string, limit int, aggregate int) ([]thirdparty.HistoricalPrice, error) {
return nil, errors.New("not implmented")
}
func (mpp *MockPriceProvider) FetchTokenMarketValues(symbols []string, currency string) (map[string]thirdparty.TokenMarketValues, error) {
return nil, errors.New("not implmented")
}
func (mpp *MockPriceProvider) FetchTokenDetails(symbols []string) (map[string]thirdparty.TokenDetails, error) {
return nil, errors.New("not implmented")
}
func (mpp *MockPriceProvider) FetchPrices(symbols []string, currencies []string) (map[string]map[string]float64, error) {
res := make(map[string]map[string]float64)
for _, symbol := range symbols {
@ -29,8 +45,8 @@ func (mpp *MockPriceProvider) FetchPrices(symbols []string, currencies []string)
return res, nil
}
func setupTestPrice(t *testing.T, priceProvider Provider) *Manager {
return NewManager(priceProvider)
func setupTestPrice(t *testing.T, provider thirdparty.MarketDataProvider) *Manager {
return NewManager(provider)
}
func TestPrice(t *testing.T) {

View File

@ -1,5 +0,0 @@
package price
type Provider interface {
FetchPrices(symbols []string, currencies []string) (map[string]map[string]float64, error)
}

View File

@ -13,9 +13,9 @@ import (
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/wallet/async"
"github.com/status-im/status-go/services/wallet/chain"
"github.com/status-im/status-go/services/wallet/price"
"github.com/status-im/status-go/services/wallet/market"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/services/wallet/token"
"github.com/status-im/status-go/services/wallet/walletevent"
)
@ -28,15 +28,14 @@ func getFixedCurrencies() []string {
return []string{"USD"}
}
func NewReader(rpcClient *rpc.Client, tokenManager *token.Manager, priceManager *price.Manager, cryptoCompare *thirdparty.CryptoCompare, accountsDB *accounts.Database, walletFeed *event.Feed) *Reader {
return &Reader{rpcClient, tokenManager, priceManager, cryptoCompare, accountsDB, walletFeed, nil}
func NewReader(rpcClient *rpc.Client, tokenManager *token.Manager, marketManager *market.Manager, accountsDB *accounts.Database, walletFeed *event.Feed) *Reader {
return &Reader{rpcClient, tokenManager, marketManager, accountsDB, walletFeed, nil}
}
type Reader struct {
rpcClient *rpc.Client
tokenManager *token.Manager
priceManager *price.Manager
cryptoCompare *thirdparty.CryptoCompare
marketManager *market.Manager
accountsDB *accounts.Database
walletFeed *event.Feed
cancel context.CancelFunc
@ -182,13 +181,13 @@ func (r *Reader) GetWalletToken(ctx context.Context, addresses []common.Address)
var (
group = async.NewAtomicGroup(ctx)
prices = map[string]map[string]float64{}
tokenDetails = map[string]thirdparty.Coin{}
tokenMarketValues = map[string]map[string]thirdparty.MarketCoinValues{}
tokenDetails = map[string]thirdparty.TokenDetails{}
tokenMarketValues = map[string]thirdparty.TokenMarketValues{}
balances = map[uint64]map[common.Address]map[common.Address]*hexutil.Big{}
)
group.Add(func(parent context.Context) error {
prices, err = r.priceManager.FetchPrices(tokenSymbols, currencies)
prices, err = r.marketManager.FetchPrices(tokenSymbols, currencies)
if err != nil {
return err
}
@ -196,7 +195,7 @@ func (r *Reader) GetWalletToken(ctx context.Context, addresses []common.Address)
})
group.Add(func(parent context.Context) error {
tokenDetails, err = r.cryptoCompare.FetchTokenDetails(tokenSymbols)
tokenDetails, err = r.marketManager.FetchTokenDetails(tokenSymbols)
if err != nil {
return err
}
@ -204,7 +203,7 @@ func (r *Reader) GetWalletToken(ctx context.Context, addresses []common.Address)
})
group.Add(func(parent context.Context) error {
tokenMarketValues, err = r.cryptoCompare.FetchTokenMarketValues(tokenSymbols, currencies)
tokenMarketValues, err = r.marketManager.FetchTokenMarketValues(tokenSymbols, currency)
if err != nil {
return err
}
@ -257,13 +256,13 @@ func (r *Reader) GetWalletToken(ctx context.Context, addresses []common.Address)
marketValuesPerCurrency := make(map[string]TokenMarketValues)
for _, currency := range currencies {
marketValuesPerCurrency[currency] = TokenMarketValues{
MarketCap: tokenMarketValues[symbol][currency].MKTCAP,
HighDay: tokenMarketValues[symbol][currency].HIGHDAY,
LowDay: tokenMarketValues[symbol][currency].LOWDAY,
ChangePctHour: tokenMarketValues[symbol][currency].CHANGEPCTHOUR,
ChangePctDay: tokenMarketValues[symbol][currency].CHANGEPCTDAY,
ChangePct24hour: tokenMarketValues[symbol][currency].CHANGEPCT24HOUR,
Change24hour: tokenMarketValues[symbol][currency].CHANGE24HOUR,
MarketCap: tokenMarketValues[symbol].MKTCAP,
HighDay: tokenMarketValues[symbol].HIGHDAY,
LowDay: tokenMarketValues[symbol].LOWDAY,
ChangePctHour: tokenMarketValues[symbol].CHANGEPCTHOUR,
ChangePctDay: tokenMarketValues[symbol].CHANGEPCTDAY,
ChangePct24hour: tokenMarketValues[symbol].CHANGEPCT24HOUR,
Change24hour: tokenMarketValues[symbol].CHANGE24HOUR,
Price: prices[symbol][currency],
}
}

View File

@ -458,7 +458,7 @@ func (r *Router) suggestedRoutes(
return nil, err
}
pricesMap, err := r.s.priceManager.FetchPrices([]string{"ETH", tokenSymbol}, []string{"USD"})
pricesMap, err := r.s.marketManager.FetchPrices([]string{"ETH", tokenSymbol}, []string{"USD"})
if err != nil {
return nil, err
}

View File

@ -18,8 +18,9 @@ import (
"github.com/status-im/status-go/services/wallet/chain"
"github.com/status-im/status-go/services/wallet/currency"
"github.com/status-im/status-go/services/wallet/history"
"github.com/status-im/status-go/services/wallet/price"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/services/wallet/market"
"github.com/status-im/status-go/services/wallet/thirdparty/cryptocompare"
"github.com/status-im/status-go/services/wallet/thirdparty/opensea"
"github.com/status-im/status-go/services/wallet/token"
"github.com/status-im/status-go/services/wallet/transfer"
"github.com/status-im/status-go/services/wallet/walletevent"
@ -32,9 +33,9 @@ type Connection struct {
}
type ConnectedResult struct {
Infura map[uint64]Connection `json:"infura"`
CryptoCompare Connection `json:"cryptoCompare"`
Opensea map[uint64]Connection `json:"opensea"`
Blockchain map[uint64]Connection `json:"blockchain"`
Market Connection `json:"market"`
Collectibles map[uint64]Connection `json:"collectibles"`
}
// NewService initializes service instance.
@ -61,11 +62,11 @@ func NewService(
savedAddressesManager := &SavedAddressesManager{db: db}
transactionManager := &TransactionManager{db: db, transactor: transactor, gethManager: gethManager, config: config, accountsDB: accountsDB}
transferController := transfer.NewTransferController(db, rpcClient, accountFeed, walletFeed)
cryptoCompare := thirdparty.NewCryptoCompare()
priceManager := price.NewManager(cryptoCompare)
reader := NewReader(rpcClient, tokenManager, priceManager, cryptoCompare, accountsDB, walletFeed)
history := history.NewService(db, walletFeed, rpcClient, tokenManager, cryptoCompare)
currency := currency.NewService(db, walletFeed, tokenManager, priceManager)
cryptoCompare := cryptocompare.NewClient()
marketManager := market.NewManager(cryptoCompare)
reader := NewReader(rpcClient, tokenManager, marketManager, accountsDB, walletFeed)
history := history.NewService(db, walletFeed, rpcClient, tokenManager, marketManager)
currency := currency.NewService(db, walletFeed, tokenManager, marketManager)
return &Service{
db: db,
accountsDB: accountsDB,
@ -78,14 +79,13 @@ func NewService(
openseaAPIKey: openseaAPIKey,
feesManager: &FeeManager{rpcClient},
gethManager: gethManager,
priceManager: priceManager,
marketManager: marketManager,
transactor: transactor,
ens: ens,
stickers: stickers,
feed: accountFeed,
signals: signals,
reader: reader,
cryptoCompare: cryptoCompare,
history: history,
currency: currency,
}
@ -102,7 +102,7 @@ type Service struct {
cryptoOnRampManager *CryptoOnRampManager
transferController *transfer.Controller
feesManager *FeeManager
priceManager *price.Manager
marketManager *market.Manager
started bool
openseaAPIKey string
gethManager *account.GethManager
@ -112,7 +112,6 @@ type Service struct {
feed *event.Feed
signals *walletevent.SignalsTransmitter
reader *Reader
cryptoCompare *thirdparty.CryptoCompare
history *history.Service
currency *currency.Service
}
@ -167,27 +166,27 @@ func (s *Service) IsStarted() bool {
}
func (s *Service) CheckConnected(ctx context.Context) *ConnectedResult {
infura := make(map[uint64]Connection)
blockchain := make(map[uint64]Connection)
for chainID, client := range chain.ChainClientInstances {
infura[chainID] = Connection{
blockchain[chainID] = Connection{
Up: client.IsConnected,
LastCheckedAt: client.LastCheckedAt,
}
}
opensea := make(map[uint64]Connection)
for chainID, client := range OpenseaClientInstances {
opensea[chainID] = Connection{
collectibles := make(map[uint64]Connection)
for chainID, client := range opensea.OpenseaClientInstances {
collectibles[chainID] = Connection{
Up: client.IsConnected,
LastCheckedAt: client.LastCheckedAt,
}
}
return &ConnectedResult{
Infura: infura,
Opensea: opensea,
CryptoCompare: Connection{
Up: s.cryptoCompare.IsConnected,
LastCheckedAt: s.cryptoCompare.LastCheckedAt,
Blockchain: blockchain,
Collectibles: collectibles,
Market: Connection{
Up: s.marketManager.IsConnected,
LastCheckedAt: s.marketManager.LastCheckedAt,
},
}
}

View File

@ -0,0 +1,308 @@
package coingecko
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/services/wallet/thirdparty/utils"
)
var coinGeckoMapping = map[string]string{
"STT": "status",
"SNT": "status",
"ETH": "ethereum",
"AST": "airswap",
"AMB": "",
"ABT": "arcblock",
"ATM": "",
"BNB": "binancecoin",
"BLT": "bloom",
"CDT": "",
"COMP": "compound-coin",
"EDG": "edgeless",
"ELF": "",
"ENG": "enigma",
"EOS": "eos",
"GEN": "daostack",
"MANA": "decentraland-wormhole",
"LEND": "ethlend",
"LRC": "loopring",
"MET": "metronome",
"POLY": "polymath",
"PPT": "populous",
"SAN": "santiment-network-token",
"DNT": "district0x",
"SPN": "sapien",
"USDS": "stableusd",
"STX": "stox",
"SUB": "substratum",
"PAY": "tenx",
"GRT": "the-graph",
"TNT": "tierion",
"TRX": "tron",
"TGT": "",
"RARE": "superrare",
"UNI": "uniswap",
"USDC": "usd-coin",
"USDP": "paxos-standard",
"VRS": "",
"TIME": "",
}
const baseURL = "https://api.coingecko.com/api/v3/"
type HistoricalPriceContainer struct {
Prices [][]float64 `json:"prices"`
}
type GeckoMarketValues struct {
ID string `json:"id"`
Symbol string `json:"symbol"`
Name string `json:"name"`
MarketCap float64 `json:"market_cap"`
High24h float64 `json:"high_24h"`
Low24h float64 `json:"low_24h"`
PriceChange24h float64 `json:"price_change_24h"`
PriceChangePercentage24h float64 `json:"price_change_percentage_24h"`
PriceChangePercentage1hInCurrency float64 `json:"price_change_percentage_1h_in_currency"`
}
type GeckoToken struct {
ID string `json:"id"`
Symbol string `json:"symbol"`
Name string `json:"name"`
}
type Client struct {
client *http.Client
tokens map[string]GeckoToken
}
func NewClient() *Client {
return &Client{client: &http.Client{Timeout: time.Minute}, tokens: make(map[string]GeckoToken)}
}
func (c *Client) DoQuery(url string) (*http.Response, error) {
resp, err := c.client.Get(url)
if err != nil {
return nil, err
}
return resp, nil
}
func (c *Client) getTokens() (map[string]GeckoToken, error) {
if len(c.tokens) > 0 {
return c.tokens, nil
}
url := fmt.Sprintf("%scoins/list", baseURL)
resp, err := c.DoQuery(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var tokens []GeckoToken
err = json.Unmarshal(body, &tokens)
if err != nil {
return nil, err
}
for _, token := range tokens {
if id, ok := coinGeckoMapping[strings.ToUpper(token.Symbol)]; ok {
if id != token.ID {
continue
}
}
c.tokens[strings.ToUpper(token.Symbol)] = token
}
return c.tokens, nil
}
func (c *Client) mapSymbolsToIds(symbols []string) ([]string, error) {
tokens, err := c.getTokens()
if err != nil {
return nil, err
}
ids := make([]string, 0)
for _, symbol := range utils.RenameSymbols(symbols) {
if token, ok := tokens[symbol]; ok {
ids = append(ids, token.ID)
}
}
return ids, nil
}
func (c *Client) getIDFromSymbol(symbol string) (string, error) {
tokens, err := c.getTokens()
if err != nil {
return "", err
}
return tokens[strings.ToUpper(symbol)].ID, nil
}
func (c *Client) FetchPrices(symbols []string, currencies []string) (map[string]map[string]float64, error) {
ids, err := c.mapSymbolsToIds(symbols)
if err != nil {
return nil, err
}
url := fmt.Sprintf("%ssimple/price?ids=%s&vs_currencies=%s", baseURL, strings.Join(ids, ","), strings.Join(currencies, ","))
resp, err := c.DoQuery(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
prices := make(map[string]map[string]float64)
err = json.Unmarshal(body, &prices)
if err != nil {
return nil, err
}
result := make(map[string]map[string]float64)
for _, symbol := range symbols {
result[symbol] = map[string]float64{}
id, err := c.getIDFromSymbol(utils.GetRealSymbol(symbol))
if err != nil {
return nil, err
}
for _, currency := range currencies {
result[symbol][currency] = prices[id][strings.ToLower(currency)]
}
}
return result, nil
}
func (c *Client) FetchTokenDetails(symbols []string) (map[string]thirdparty.TokenDetails, error) {
tokens, err := c.getTokens()
if err != nil {
return nil, err
}
result := make(map[string]thirdparty.TokenDetails)
for _, symbol := range symbols {
if value, ok := tokens[utils.GetRealSymbol(symbol)]; ok {
result[symbol] = thirdparty.TokenDetails{
ID: value.ID,
Name: value.Name,
Symbol: symbol,
}
}
}
return result, nil
}
func (c *Client) FetchTokenMarketValues(symbols []string, currency string) (map[string]thirdparty.TokenMarketValues, error) {
ids, err := c.mapSymbolsToIds(symbols)
if err != nil {
return nil, err
}
queryParams := url.Values{
"ids": {strings.Join(ids, ",")},
"vs_currency": {currency},
"order": {"market_cap_desc"},
"per_page": {"250"},
"page": {"1"},
"sparkline": {"false"},
"price_change_percentage": {"1h,24h"},
}
url := baseURL + "coins/markets" + queryParams.Encode()
resp, err := c.DoQuery(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var marketValues []GeckoMarketValues
err = json.Unmarshal(body, &marketValues)
if err != nil {
return nil, err
}
result := make(map[string]thirdparty.TokenMarketValues)
for _, symbol := range symbols {
id, err := c.getIDFromSymbol(utils.GetRealSymbol(symbol))
if err != nil {
return nil, err
}
for _, marketValue := range marketValues {
if id != marketValue.ID {
continue
}
result[symbol] = thirdparty.TokenMarketValues{
MKTCAP: marketValue.MarketCap,
HIGHDAY: marketValue.High24h,
LOWDAY: marketValue.Low24h,
CHANGEPCTHOUR: marketValue.PriceChangePercentage1hInCurrency,
CHANGEPCTDAY: marketValue.PriceChangePercentage24h,
CHANGEPCT24HOUR: marketValue.PriceChangePercentage24h,
CHANGE24HOUR: marketValue.PriceChange24h,
}
}
}
return result, nil
}
func (c *Client) FetchHistoricalHourlyPrices(symbol string, currency string, limit int, aggregate int) ([]thirdparty.HistoricalPrice, error) {
return []thirdparty.HistoricalPrice{}, nil
}
func (c *Client) FetchHistoricalDailyPrices(symbol string, currency string, limit int, allData bool, aggregate int) ([]thirdparty.HistoricalPrice, error) {
id, err := c.getIDFromSymbol(utils.GetRealSymbol(symbol))
if err != nil {
return nil, err
}
url := fmt.Sprintf("%scoins/%s/market_chart?vs_currency=%s&days=30", baseURL, id, currency)
resp, err := c.DoQuery(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var container HistoricalPriceContainer
err = json.Unmarshal(body, &container)
if err != nil {
return nil, err
}
result := make([]thirdparty.HistoricalPrice, 0)
for _, price := range container.Prices {
result = append(result, thirdparty.HistoricalPrice{
Timestamp: int64(price[0]),
Value: price[1],
})
}
return result, nil
}

View File

@ -1,269 +0,0 @@
package thirdparty
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"sync"
"time"
)
const cryptocompareURL = "https://min-api.cryptocompare.com"
var renameMapping = map[string]string{
"STT": "SNT",
}
type Coin struct {
ID string `json:"Id"`
Name string `json:"Name"`
Symbol string `json:"Symbol"`
Description string `json:"Description"`
TotalCoinsMined float64 `json:"TotalCoinsMined"`
AssetLaunchDate string `json:"AssetLaunchDate"`
AssetWhitepaperURL string `json:"AssetWhitepaperUrl"`
AssetWebsiteURL string `json:"AssetWebsiteUrl"`
BuiltOn string `json:"BuiltOn"`
SmartContractAddress string `json:"SmartContractAddress"`
}
type MarketCoinValues struct {
MKTCAP float64 `json:"MKTCAP"`
HIGHDAY float64 `json:"HIGHDAY"`
LOWDAY float64 `json:"LOWDAY"`
CHANGEPCTHOUR float64 `json:"CHANGEPCTHOUR"`
CHANGEPCTDAY float64 `json:"CHANGEPCTDAY"`
CHANGEPCT24HOUR float64 `json:"CHANGEPCT24HOUR"`
CHANGE24HOUR float64 `json:"CHANGE24HOUR"`
}
type TokenHistoricalPairs struct {
Timestamp int64 `json:"time"`
Value float64 `json:"close"`
Volumefrom float64 `json:"volumefrom"`
Volumeto float64 `json:"volumeto"`
}
type HistoricalValuesContainer struct {
Aggregated bool `json:"Aggregated"`
TimeFrom int64 `json:"TimeFrom"`
TimeTo int64 `json:"TimeTo"`
HistoricalData []TokenHistoricalPairs `json:"Data"`
}
type HistoricalValuesData struct {
Data HistoricalValuesContainer `json:"Data"`
}
type CoinsContainer struct {
Data map[string]Coin `json:"Data"`
}
type MarketValuesContainer struct {
Raw map[string]map[string]MarketCoinValues `json:"Raw"`
}
type CryptoCompare struct {
client *http.Client
IsConnected bool
LastCheckedAt int64
IsConnectedLock sync.RWMutex
}
func NewCryptoCompare() *CryptoCompare {
return &CryptoCompare{client: &http.Client{Timeout: time.Minute}, IsConnected: true, LastCheckedAt: time.Now().Unix()}
}
func renameSymbols(symbols []string) (renames []string) {
for _, symbol := range symbols {
renames = append(renames, getRealSymbol(symbol))
}
return
}
func getRealSymbol(symbol string) string {
if val, ok := renameMapping[strings.ToUpper(symbol)]; ok {
return val
}
return strings.ToUpper(symbol)
}
func chunkSymbols(symbols []string) [][]string {
var chunks [][]string
chunkSize := 20
for i := 0; i < len(symbols); i += chunkSize {
end := i + chunkSize
if end > len(symbols) {
end = len(symbols)
}
chunks = append(chunks, symbols[i:end])
}
return chunks
}
func (c *CryptoCompare) DoQuery(url string) (*http.Response, error) {
resp, err := c.client.Get(url)
c.IsConnectedLock.Lock()
defer c.IsConnectedLock.Unlock()
c.LastCheckedAt = time.Now().Unix()
if err != nil {
c.IsConnected = false
return nil, err
}
c.IsConnected = true
return resp, nil
}
func (c *CryptoCompare) FetchPrices(symbols []string, currencies []string) (map[string]map[string]float64, error) {
chunks := chunkSymbols(symbols)
result := make(map[string]map[string]float64)
realCurrencies := renameSymbols(currencies)
for _, smbls := range chunks {
realSymbols := renameSymbols(smbls)
url := fmt.Sprintf("%s/data/pricemulti?fsyms=%s&tsyms=%s&extraParams=Status.im", cryptocompareURL, strings.Join(realSymbols, ","), strings.Join(realCurrencies, ","))
resp, err := c.DoQuery(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
prices := make(map[string]map[string]float64)
err = json.Unmarshal(body, &prices)
if err != nil {
return nil, err
}
for _, symbol := range smbls {
result[symbol] = map[string]float64{}
for _, currency := range currencies {
result[symbol][currency] = prices[getRealSymbol(symbol)][getRealSymbol(currency)]
}
}
}
return result, nil
}
func (c *CryptoCompare) FetchTokenDetails(symbols []string) (map[string]Coin, error) {
url := fmt.Sprintf("%s/data/all/coinlist", cryptocompareURL)
resp, err := c.DoQuery(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
container := CoinsContainer{}
err = json.Unmarshal(body, &container)
if err != nil {
return nil, err
}
coins := make(map[string]Coin)
for _, symbol := range symbols {
coins[symbol] = container.Data[getRealSymbol(symbol)]
}
return coins, nil
}
func (c *CryptoCompare) FetchTokenMarketValues(symbols []string, currencies []string) (map[string]map[string]MarketCoinValues, error) {
realCurrencies := renameSymbols(currencies)
realSymbols := renameSymbols(symbols)
item := map[string]map[string]MarketCoinValues{}
url := fmt.Sprintf("%s/data/pricemultifull?fsyms=%s&tsyms=%s&extraParams=Status.im", cryptocompareURL, strings.Join(realSymbols, ","), strings.Join(realCurrencies, ","))
resp, err := c.DoQuery(url)
if err != nil {
return item, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return item, err
}
container := MarketValuesContainer{}
err = json.Unmarshal(body, &container)
if err != nil {
return item, err
}
for _, symbol := range symbols {
item[symbol] = map[string]MarketCoinValues{}
for _, currency := range currencies {
item[symbol][currency] = container.Raw[getRealSymbol(symbol)][getRealSymbol(currency)]
}
}
return item, nil
}
func (c *CryptoCompare) FetchHourlyMarketValues(symbol string, currency string, limit int, aggregate int) ([]TokenHistoricalPairs, error) {
item := []TokenHistoricalPairs{}
url := fmt.Sprintf("%s/data/v2/histohour?fsym=%s&tsym=%s&aggregate=%d&limit=%d&extraParams=Status.im", cryptocompareURL, getRealSymbol(symbol), currency, aggregate, limit)
resp, err := c.DoQuery(url)
if err != nil {
return item, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return item, err
}
container := HistoricalValuesData{}
err = json.Unmarshal(body, &container)
if err != nil {
return item, err
}
item = container.Data.HistoricalData
return item, nil
}
func (c *CryptoCompare) FetchDailyMarketValues(symbol string, currency string, limit int, allData bool, aggregate int) ([]TokenHistoricalPairs, error) {
item := []TokenHistoricalPairs{}
url := fmt.Sprintf("%s/data/v2/histoday?fsym=%s&tsym=%s&aggregate=%d&limit=%d&allData=%v&extraParams=Status.im", cryptocompareURL, getRealSymbol(symbol), currency, aggregate, limit, allData)
resp, err := c.DoQuery(url)
if err != nil {
return item, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return item, err
}
container := HistoricalValuesData{}
err = json.Unmarshal(body, &container)
if err != nil {
return item, err
}
item = container.Data.HistoricalData
return item, nil
}

View File

@ -0,0 +1,195 @@
package cryptocompare
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"time"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/services/wallet/thirdparty/utils"
)
const baseURL = "https://min-api.cryptocompare.com"
type HistoricalPricesContainer struct {
Aggregated bool `json:"Aggregated"`
TimeFrom int64 `json:"TimeFrom"`
TimeTo int64 `json:"TimeTo"`
HistoricalData []thirdparty.HistoricalPrice `json:"Data"`
}
type HistoricalPricesData struct {
Data HistoricalPricesContainer `json:"Data"`
}
type TokenDetailsContainer struct {
Data map[string]thirdparty.TokenDetails `json:"Data"`
}
type MarketValuesContainer struct {
Raw map[string]map[string]thirdparty.TokenMarketValues `json:"Raw"`
}
type Client struct {
client *http.Client
}
func NewClient() *Client {
return &Client{client: &http.Client{Timeout: time.Minute}}
}
func (c *Client) DoQuery(url string) (*http.Response, error) {
resp, err := c.client.Get(url)
if err != nil {
return nil, err
}
return resp, nil
}
func (c *Client) FetchPrices(symbols []string, currencies []string) (map[string]map[string]float64, error) {
chunks := utils.ChunkSymbols(symbols)
result := make(map[string]map[string]float64)
realCurrencies := utils.RenameSymbols(currencies)
for _, smbls := range chunks {
realSymbols := utils.RenameSymbols(smbls)
url := fmt.Sprintf("%s/data/pricemulti?fsyms=%s&tsyms=%s&extraParams=Status.im", baseURL, strings.Join(realSymbols, ","), strings.Join(realCurrencies, ","))
resp, err := c.DoQuery(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
prices := make(map[string]map[string]float64)
err = json.Unmarshal(body, &prices)
if err != nil {
return nil, err
}
for _, symbol := range smbls {
result[symbol] = map[string]float64{}
for _, currency := range currencies {
result[symbol][currency] = prices[utils.GetRealSymbol(symbol)][utils.GetRealSymbol(currency)]
}
}
}
return result, nil
}
func (c *Client) FetchTokenDetails(symbols []string) (map[string]thirdparty.TokenDetails, error) {
url := fmt.Sprintf("%s/data/all/coinlist", baseURL)
resp, err := c.DoQuery(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
container := TokenDetailsContainer{}
err = json.Unmarshal(body, &container)
if err != nil {
return nil, err
}
tokenDetails := make(map[string]thirdparty.TokenDetails)
for _, symbol := range symbols {
tokenDetails[symbol] = container.Data[utils.GetRealSymbol(symbol)]
}
return tokenDetails, nil
}
func (c *Client) FetchTokenMarketValues(symbols []string, currency string) (map[string]thirdparty.TokenMarketValues, error) {
realCurrency := utils.GetRealSymbol(currency)
realSymbols := utils.RenameSymbols(symbols)
item := map[string]thirdparty.TokenMarketValues{}
url := fmt.Sprintf("%s/data/pricemultifull?fsyms=%s&tsyms=%s&extraParams=Status.im", baseURL, strings.Join(realSymbols, ","), realCurrency)
resp, err := c.DoQuery(url)
if err != nil {
return item, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return item, err
}
container := MarketValuesContainer{}
err = json.Unmarshal(body, &container)
if err != nil {
return item, err
}
for _, symbol := range symbols {
item[symbol] = container.Raw[utils.GetRealSymbol(symbol)][utils.GetRealSymbol(currency)]
}
return item, nil
}
func (c *Client) FetchHistoricalHourlyPrices(symbol string, currency string, limit int, aggregate int) ([]thirdparty.HistoricalPrice, error) {
item := []thirdparty.HistoricalPrice{}
url := fmt.Sprintf("%s/data/v2/histohour?fsym=%s&tsym=%s&aggregate=%d&limit=%d&extraParams=Status.im", baseURL, utils.GetRealSymbol(symbol), currency, aggregate, limit)
resp, err := c.DoQuery(url)
if err != nil {
return item, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return item, err
}
container := HistoricalPricesData{}
err = json.Unmarshal(body, &container)
if err != nil {
return item, err
}
item = container.Data.HistoricalData
return item, nil
}
func (c *Client) FetchHistoricalDailyPrices(symbol string, currency string, limit int, allData bool, aggregate int) ([]thirdparty.HistoricalPrice, error) {
item := []thirdparty.HistoricalPrice{}
url := fmt.Sprintf("%s/data/v2/histoday?fsym=%s&tsym=%s&aggregate=%d&limit=%d&allData=%v&extraParams=Status.im", baseURL, utils.GetRealSymbol(symbol), currency, aggregate, limit, allData)
resp, err := c.DoQuery(url)
if err != nil {
return item, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return item, err
}
container := HistoricalPricesData{}
err = json.Unmarshal(body, &container)
if err != nil {
return item, err
}
item = container.Data.HistoricalData
return item, nil
}

View File

@ -1,4 +1,4 @@
package wallet
package opensea
import (
"encoding/json"
@ -18,7 +18,7 @@ import (
const AssetLimit = 50
const CollectionLimit = 300
var OpenseaClientInstances = make(map[uint64]*OpenseaClient)
var OpenseaClientInstances = make(map[uint64]*Client)
var BaseURLs = map[uint64]string{
1: "https://api.opensea.io/api/v1",
@ -46,26 +46,26 @@ func (st *TraitValue) UnmarshalJSON(b []byte) error {
return nil
}
type OpenseaAssetContainer struct {
Assets []OpenseaAsset `json:"assets"`
type AssetContainer struct {
Assets []Asset `json:"assets"`
}
type OpenseaAssetCollection struct {
type AssetCollection struct {
Name string `json:"name"`
}
type OpenseaContract struct {
type Contract struct {
Address string `json:"address"`
}
type OpenseaTrait struct {
type Trait struct {
TraitType string `json:"trait_type"`
Value TraitValue `json:"value"`
DisplayType string `json:"display_type"`
MaxValue string `json:"max_value"`
}
type OpenseaPaymentToken struct {
type PaymentToken struct {
ID int `json:"id"`
Symbol string `json:"symbol"`
Address string `json:"address"`
@ -76,42 +76,42 @@ type OpenseaPaymentToken struct {
UsdPrice string `json:"usd_price"`
}
type OpenseaLastSale struct {
PaymentToken OpenseaPaymentToken `json:"payment_token"`
type LastSale struct {
PaymentToken PaymentToken `json:"payment_token"`
}
type OpenseaSellOrder struct {
type SellOrder struct {
CurrentPrice string `json:"current_price"`
}
type OpenseaAsset struct {
ID int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Permalink string `json:"permalink"`
ImageThumbnailURL string `json:"image_thumbnail_url"`
ImageURL string `json:"image_url"`
Contract OpenseaContract `json:"asset_contract"`
Collection OpenseaAssetCollection `json:"collection"`
Traits []OpenseaTrait `json:"traits"`
LastSale OpenseaLastSale `json:"last_sale"`
SellOrders []OpenseaSellOrder `json:"sell_orders"`
BackgroundColor string `json:"background_color"`
type Asset struct {
ID int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Permalink string `json:"permalink"`
ImageThumbnailURL string `json:"image_thumbnail_url"`
ImageURL string `json:"image_url"`
Contract Contract `json:"asset_contract"`
Collection AssetCollection `json:"collection"`
Traits []Trait `json:"traits"`
LastSale LastSale `json:"last_sale"`
SellOrders []SellOrder `json:"sell_orders"`
BackgroundColor string `json:"background_color"`
}
type OpenseaCollectionTrait struct {
type CollectionTrait struct {
Min float64 `json:"min"`
Max float64 `json:"max"`
}
type OpenseaCollection struct {
Name string `json:"name"`
Slug string `json:"slug"`
ImageURL string `json:"image_url"`
OwnedAssetCount int `json:"owned_asset_count"`
Traits map[string]OpenseaCollectionTrait `json:"traits"`
type Collection struct {
Name string `json:"name"`
Slug string `json:"slug"`
ImageURL string `json:"image_url"`
OwnedAssetCount int `json:"owned_asset_count"`
Traits map[string]CollectionTrait `json:"traits"`
}
type OpenseaClient struct {
type Client struct {
client *http.Client
url string
apiKey string
@ -121,7 +121,7 @@ type OpenseaClient struct {
}
// new opensea client.
func newOpenseaClient(chainID uint64, apiKey string) (*OpenseaClient, error) {
func NewOpenseaClient(chainID uint64, apiKey string) (*Client, error) {
if client, ok := OpenseaClientInstances[chainID]; ok {
if client.apiKey == apiKey {
return client, nil
@ -132,7 +132,7 @@ func newOpenseaClient(chainID uint64, apiKey string) (*OpenseaClient, error) {
Timeout: time.Second * 5,
}
if url, ok := BaseURLs[chainID]; ok {
openseaClient := &OpenseaClient{client: client, url: url, apiKey: apiKey, IsConnected: true, LastCheckedAt: time.Now().Unix()}
openseaClient := &Client{client: client, url: url, apiKey: apiKey, IsConnected: true, LastCheckedAt: time.Now().Unix()}
OpenseaClientInstances[chainID] = openseaClient
return openseaClient, nil
}
@ -140,9 +140,9 @@ func newOpenseaClient(chainID uint64, apiKey string) (*OpenseaClient, error) {
return nil, errors.New("ChainID not supported")
}
func (o *OpenseaClient) fetchAllCollectionsByOwner(owner common.Address) ([]OpenseaCollection, error) {
func (o *Client) FetchAllCollectionsByOwner(owner common.Address) ([]Collection, error) {
offset := 0
var collections []OpenseaCollection
var collections []Collection
o.IsConnectedLock.Lock()
defer o.IsConnectedLock.Unlock()
o.LastCheckedAt = time.Now().Unix()
@ -154,7 +154,7 @@ func (o *OpenseaClient) fetchAllCollectionsByOwner(owner common.Address) ([]Open
return nil, err
}
var tmp []OpenseaCollection
var tmp []Collection
err = json.Unmarshal(body, &tmp)
if err != nil {
o.IsConnected = false
@ -171,9 +171,9 @@ func (o *OpenseaClient) fetchAllCollectionsByOwner(owner common.Address) ([]Open
return collections, nil
}
func (o *OpenseaClient) fetchAllAssetsByOwnerAndCollection(owner common.Address, collectionSlug string, limit int) ([]OpenseaAsset, error) {
func (o *Client) FetchAllAssetsByOwnerAndCollection(owner common.Address, collectionSlug string, limit int) ([]Asset, error) {
offset := 0
var assets []OpenseaAsset
var assets []Asset
o.IsConnectedLock.Lock()
defer o.IsConnectedLock.Unlock()
o.LastCheckedAt = time.Now().Unix()
@ -185,7 +185,7 @@ func (o *OpenseaClient) fetchAllAssetsByOwnerAndCollection(owner common.Address,
return nil, err
}
container := OpenseaAssetContainer{}
container := AssetContainer{}
err = json.Unmarshal(body, &container)
if err != nil {
o.IsConnected = false
@ -213,7 +213,7 @@ func (o *OpenseaClient) fetchAllAssetsByOwnerAndCollection(owner common.Address,
return assets, nil
}
func (o *OpenseaClient) doOpenseaRequest(url string) ([]byte, error) {
func (o *Client) doOpenseaRequest(url string) ([]byte, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err

View File

@ -1,4 +1,4 @@
package wallet
package opensea
import (
"encoding/json"
@ -12,7 +12,7 @@ import (
)
func TestFetchAllCollectionsByOwner(t *testing.T) {
expected := []OpenseaCollection{OpenseaCollection{Name: "Rocky", Slug: "rocky", ImageURL: "ImageUrl", OwnedAssetCount: 1}}
expected := []Collection{Collection{Name: "Rocky", Slug: "rocky", ImageURL: "ImageUrl", OwnedAssetCount: 1}}
response, _ := json.Marshal(expected)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
@ -23,27 +23,27 @@ func TestFetchAllCollectionsByOwner(t *testing.T) {
}))
defer srv.Close()
opensea := &OpenseaClient{
opensea := &Client{
client: srv.Client(),
url: srv.URL,
}
res, err := opensea.fetchAllCollectionsByOwner(common.Address{1})
res, err := opensea.FetchAllCollectionsByOwner(common.Address{1})
assert.Equal(t, expected, res)
assert.Nil(t, err)
}
func TestFetchAllAssetsByOwnerAndCollection(t *testing.T) {
expected := []OpenseaAsset{OpenseaAsset{
expected := []Asset{Asset{
ID: 1,
Name: "Rocky",
Description: "Rocky Balboa",
Permalink: "permalink",
ImageThumbnailURL: "ImageThumbnailURL",
ImageURL: "ImageUrl",
Contract: OpenseaContract{Address: "1"},
Collection: OpenseaAssetCollection{Name: "Rocky"},
Contract: Contract{Address: "1"},
Collection: AssetCollection{Name: "Rocky"},
}}
response, _ := json.Marshal(OpenseaAssetContainer{Assets: expected})
response, _ := json.Marshal(AssetContainer{Assets: expected})
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
_, err := w.Write(response)
@ -53,11 +53,11 @@ func TestFetchAllAssetsByOwnerAndCollection(t *testing.T) {
}))
defer srv.Close()
opensea := &OpenseaClient{
opensea := &Client{
client: srv.Client(),
url: srv.URL,
}
res, err := opensea.fetchAllAssetsByOwnerAndCollection(common.Address{1}, "rocky", 200)
res, err := opensea.FetchAllAssetsByOwnerAndCollection(common.Address{1}, "rocky", 200)
assert.Equal(t, expected, res)
assert.Nil(t, err)
}

37
services/wallet/thirdparty/types.go vendored Normal file
View File

@ -0,0 +1,37 @@
package thirdparty
type HistoricalPrice struct {
Timestamp int64 `json:"time"`
Value float64 `json:"close"`
}
type TokenMarketValues struct {
MKTCAP float64 `json:"MKTCAP"`
HIGHDAY float64 `json:"HIGHDAY"`
LOWDAY float64 `json:"LOWDAY"`
CHANGEPCTHOUR float64 `json:"CHANGEPCTHOUR"`
CHANGEPCTDAY float64 `json:"CHANGEPCTDAY"`
CHANGEPCT24HOUR float64 `json:"CHANGEPCT24HOUR"`
CHANGE24HOUR float64 `json:"CHANGE24HOUR"`
}
type TokenDetails struct {
ID string `json:"Id"`
Name string `json:"Name"`
Symbol string `json:"Symbol"`
Description string `json:"Description"`
TotalCoinsMined float64 `json:"TotalCoinsMined"`
AssetLaunchDate string `json:"AssetLaunchDate"`
AssetWhitepaperURL string `json:"AssetWhitepaperUrl"`
AssetWebsiteURL string `json:"AssetWebsiteUrl"`
BuiltOn string `json:"BuiltOn"`
SmartContractAddress string `json:"SmartContractAddress"`
}
type MarketDataProvider interface {
FetchPrices(symbols []string, currencies []string) (map[string]map[string]float64, error)
FetchHistoricalDailyPrices(symbol string, currency string, limit int, allData bool, aggregate int) ([]HistoricalPrice, error)
FetchHistoricalHourlyPrices(symbol string, currency string, limit int, aggregate int) ([]HistoricalPrice, error)
FetchTokenMarketValues(symbols []string, currency string) (map[string]TokenMarketValues, error)
FetchTokenDetails(symbols []string) (map[string]TokenDetails, error)
}

View File

@ -0,0 +1,41 @@
package utils
import "strings"
var renameMapping = map[string]string{
"STT": "SNT",
}
func RenameSymbols(symbols []string) (renames []string) {
for _, symbol := range symbols {
renames = append(renames, GetRealSymbol(symbol))
}
return
}
func GetRealSymbol(symbol string) string {
if val, ok := renameMapping[strings.ToUpper(symbol)]; ok {
return val
}
return strings.ToUpper(symbol)
}
func ChunkSymbols(symbols []string, chunkSizeOptional ...int) [][]string {
var chunks [][]string
chunkSize := 20
if len(chunkSizeOptional) > 0 {
chunkSize = chunkSizeOptional[0]
}
for i := 0; i < len(symbols); i += chunkSize {
end := i + chunkSize
if end > len(symbols) {
end = len(symbols)
}
chunks = append(chunks, symbols[i:end])
}
return chunks
}