feat: add coingecko fallback
This commit is contained in:
parent
999d8c0ee0
commit
e543fda4b5
|
@ -18,6 +18,7 @@ import (
|
||||||
"github.com/status-im/status-go/services/wallet/currency"
|
"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/history"
|
||||||
"github.com/status-im/status-go/services/wallet/thirdparty"
|
"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/token"
|
||||||
"github.com/status-im/status-go/services/wallet/transfer"
|
"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()
|
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")
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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")
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
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) {
|
func (api *API) FetchPrices(ctx context.Context, symbols []string, currencies []string) (map[string]map[string]float64, error) {
|
||||||
log.Debug("call to FetchPrices")
|
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")
|
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")
|
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")
|
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")
|
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) {
|
func (api *API) GetSuggestedFees(ctx context.Context, chainID uint64) (*SuggestedFees, error) {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
|
|
||||||
iso4217 "github.com/ladydascalie/currency"
|
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"
|
"github.com/status-im/status-go/services/wallet/token"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,12 +25,12 @@ type Format struct {
|
||||||
type FormatPerSymbol = map[string]Format
|
type FormatPerSymbol = map[string]Format
|
||||||
|
|
||||||
type Currency struct {
|
type Currency struct {
|
||||||
priceManager *price.Manager
|
marketManager *market.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCurrency(priceManager *price.Manager) *Currency {
|
func NewCurrency(marketManager *market.Manager) *Currency {
|
||||||
return &Currency{
|
return &Currency{
|
||||||
priceManager: priceManager,
|
marketManager: marketManager,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +126,7 @@ func (cm *Currency) FetchTokenCurrencyFormats(symbols []string) (FormatPerSymbol
|
||||||
formats := make(FormatPerSymbol)
|
formats := make(FormatPerSymbol)
|
||||||
|
|
||||||
// Get latest cached price, fetch only if not available
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"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/token"
|
||||||
"github.com/status-im/status-go/services/wallet/walletevent"
|
"github.com/status-im/status-go/services/wallet/walletevent"
|
||||||
)
|
)
|
||||||
|
@ -26,9 +26,9 @@ type Service struct {
|
||||||
cancelFn context.CancelFunc
|
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{
|
return &Service{
|
||||||
currency: NewCurrency(priceManager),
|
currency: NewCurrency(marketManager),
|
||||||
db: NewCurrencyDB(db),
|
db: NewCurrencyDB(db),
|
||||||
tokenManager: tokenManager,
|
tokenManager: tokenManager,
|
||||||
walletFeed: walletFeed,
|
walletFeed: walletFeed,
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/status-im/status-go/services/wallet/thirdparty"
|
"github.com/status-im/status-go/services/wallet/market"
|
||||||
)
|
)
|
||||||
|
|
||||||
type tokenType = string
|
type tokenType = string
|
||||||
|
@ -26,13 +26,13 @@ type Exchange struct {
|
||||||
allTimeCache map[tokenType]map[currencyType][]allTimeEntry
|
allTimeCache map[tokenType]map[currencyType][]allTimeEntry
|
||||||
fetchMutex sync.Mutex
|
fetchMutex sync.Mutex
|
||||||
|
|
||||||
cryptoCompare *thirdparty.CryptoCompare
|
marketManager *market.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewExchange(cryptoCompare *thirdparty.CryptoCompare) *Exchange {
|
func NewExchange(marketManager *market.Manager) *Exchange {
|
||||||
return &Exchange{
|
return &Exchange{
|
||||||
cache: make(map[tokenType]map[currencyType]map[yearType][]float32),
|
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
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -125,7 +125,7 @@ func (e *Exchange) FetchAndCacheMissingRates(token tokenType, currency currencyT
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch all time
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ import (
|
||||||
"github.com/status-im/status-go/rpc/network"
|
"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/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/token"
|
||||||
"github.com/status-im/status-go/services/wallet/walletevent"
|
"github.com/status-im/status-go/services/wallet/walletevent"
|
||||||
)
|
)
|
||||||
|
@ -55,7 +55,7 @@ type Service struct {
|
||||||
|
|
||||||
type chainIdentity uint64
|
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{
|
return &Service{
|
||||||
balance: NewBalance(NewBalanceDB(db)),
|
balance: NewBalance(NewBalanceDB(db)),
|
||||||
db: db,
|
db: db,
|
||||||
|
@ -63,7 +63,7 @@ func NewService(db *sql.DB, eventFeed *event.Feed, rpcClient *statusrpc.Client,
|
||||||
rpcClient: rpcClient,
|
rpcClient: rpcClient,
|
||||||
networkManager: rpcClient.NetworkManager,
|
networkManager: rpcClient.NetworkManager,
|
||||||
tokenManager: tokenManager,
|
tokenManager: tokenManager,
|
||||||
exchange: NewExchange(cryptoCompare),
|
exchange: NewExchange(marketManager),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,8 @@ import (
|
||||||
"github.com/status-im/status-go/appdatabase"
|
"github.com/status-im/status-go/appdatabase"
|
||||||
"github.com/status-im/status-go/params"
|
"github.com/status-im/status-go/params"
|
||||||
statusRPC "github.com/status-im/status-go/rpc"
|
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/status-im/status-go/transactions/fake"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -25,7 +26,7 @@ import (
|
||||||
func setupDummyServiceNoDependencies(t *testing.T) (service *Service, closeFn func()) {
|
func setupDummyServiceNoDependencies(t *testing.T) (service *Service, closeFn func()) {
|
||||||
db, err := appdatabase.InitializeDB(":memory:", "wallet-history-service-tests", 1)
|
db, err := appdatabase.InitializeDB(":memory:", "wallet-history-service-tests", 1)
|
||||||
require.NoError(t, err)
|
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
|
// Creating a dummy status node to simulate what it's done in get_status_node.go
|
||||||
upstreamConfig := params.UpstreamRPCConfig{
|
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)
|
rpcClient, err := statusRPC.NewClient(client, 1, upstreamConfig, nil, db)
|
||||||
require.NoError(t, err)
|
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())
|
require.NoError(t, db.Close())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
package price
|
package market
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/services/wallet/thirdparty"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DataPoint struct {
|
type DataPoint struct {
|
||||||
|
@ -12,17 +15,38 @@ type DataPoint struct {
|
||||||
type DataPerTokenAndCurrency = map[string]map[string]DataPoint
|
type DataPerTokenAndCurrency = map[string]map[string]DataPoint
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
priceProvider Provider
|
provider thirdparty.MarketDataProvider
|
||||||
priceCache DataPerTokenAndCurrency
|
priceCache DataPerTokenAndCurrency
|
||||||
|
IsConnected bool
|
||||||
|
LastCheckedAt int64
|
||||||
|
IsConnectedLock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager(priceProvider Provider) *Manager {
|
func NewManager(provider thirdparty.MarketDataProvider) *Manager {
|
||||||
return &Manager{
|
return &Manager{
|
||||||
priceProvider: priceProvider,
|
provider: provider,
|
||||||
priceCache: make(DataPerTokenAndCurrency),
|
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) {
|
func (pm *Manager) FetchPrice(symbol string, currency string) (float64, error) {
|
||||||
symbols := [1]string{symbol}
|
symbols := [1]string{symbol}
|
||||||
currencies := [1]string{currency}
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
|
@ -1,9 +1,12 @@
|
||||||
package price
|
package market
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/services/wallet/thirdparty"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MockPriceProvider struct {
|
type MockPriceProvider struct {
|
||||||
|
@ -18,6 +21,19 @@ func (mpp *MockPriceProvider) setMockPrices(prices map[string]map[string]float64
|
||||||
mpp.mockPrices = prices
|
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) {
|
func (mpp *MockPriceProvider) FetchPrices(symbols []string, currencies []string) (map[string]map[string]float64, error) {
|
||||||
res := make(map[string]map[string]float64)
|
res := make(map[string]map[string]float64)
|
||||||
for _, symbol := range symbols {
|
for _, symbol := range symbols {
|
||||||
|
@ -29,8 +45,8 @@ func (mpp *MockPriceProvider) FetchPrices(symbols []string, currencies []string)
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupTestPrice(t *testing.T, priceProvider Provider) *Manager {
|
func setupTestPrice(t *testing.T, provider thirdparty.MarketDataProvider) *Manager {
|
||||||
return NewManager(priceProvider)
|
return NewManager(provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrice(t *testing.T) {
|
func TestPrice(t *testing.T) {
|
|
@ -1,5 +0,0 @@
|
||||||
package price
|
|
||||||
|
|
||||||
type Provider interface {
|
|
||||||
FetchPrices(symbols []string, currencies []string) (map[string]map[string]float64, error)
|
|
||||||
}
|
|
|
@ -13,9 +13,9 @@ import (
|
||||||
"github.com/status-im/status-go/rpc"
|
"github.com/status-im/status-go/rpc"
|
||||||
"github.com/status-im/status-go/services/wallet/async"
|
"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/chain"
|
||||||
|
"github.com/status-im/status-go/services/wallet/market"
|
||||||
"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/thirdparty"
|
||||||
|
|
||||||
"github.com/status-im/status-go/services/wallet/token"
|
"github.com/status-im/status-go/services/wallet/token"
|
||||||
"github.com/status-im/status-go/services/wallet/walletevent"
|
"github.com/status-im/status-go/services/wallet/walletevent"
|
||||||
)
|
)
|
||||||
|
@ -28,15 +28,14 @@ func getFixedCurrencies() []string {
|
||||||
return []string{"USD"}
|
return []string{"USD"}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReader(rpcClient *rpc.Client, tokenManager *token.Manager, priceManager *price.Manager, cryptoCompare *thirdparty.CryptoCompare, accountsDB *accounts.Database, walletFeed *event.Feed) *Reader {
|
func NewReader(rpcClient *rpc.Client, tokenManager *token.Manager, marketManager *market.Manager, accountsDB *accounts.Database, walletFeed *event.Feed) *Reader {
|
||||||
return &Reader{rpcClient, tokenManager, priceManager, cryptoCompare, accountsDB, walletFeed, nil}
|
return &Reader{rpcClient, tokenManager, marketManager, accountsDB, walletFeed, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Reader struct {
|
type Reader struct {
|
||||||
rpcClient *rpc.Client
|
rpcClient *rpc.Client
|
||||||
tokenManager *token.Manager
|
tokenManager *token.Manager
|
||||||
priceManager *price.Manager
|
marketManager *market.Manager
|
||||||
cryptoCompare *thirdparty.CryptoCompare
|
|
||||||
accountsDB *accounts.Database
|
accountsDB *accounts.Database
|
||||||
walletFeed *event.Feed
|
walletFeed *event.Feed
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
@ -182,13 +181,13 @@ func (r *Reader) GetWalletToken(ctx context.Context, addresses []common.Address)
|
||||||
var (
|
var (
|
||||||
group = async.NewAtomicGroup(ctx)
|
group = async.NewAtomicGroup(ctx)
|
||||||
prices = map[string]map[string]float64{}
|
prices = map[string]map[string]float64{}
|
||||||
tokenDetails = map[string]thirdparty.Coin{}
|
tokenDetails = map[string]thirdparty.TokenDetails{}
|
||||||
tokenMarketValues = map[string]map[string]thirdparty.MarketCoinValues{}
|
tokenMarketValues = map[string]thirdparty.TokenMarketValues{}
|
||||||
balances = map[uint64]map[common.Address]map[common.Address]*hexutil.Big{}
|
balances = map[uint64]map[common.Address]map[common.Address]*hexutil.Big{}
|
||||||
)
|
)
|
||||||
|
|
||||||
group.Add(func(parent context.Context) error {
|
group.Add(func(parent context.Context) error {
|
||||||
prices, err = r.priceManager.FetchPrices(tokenSymbols, currencies)
|
prices, err = r.marketManager.FetchPrices(tokenSymbols, currencies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -196,7 +195,7 @@ func (r *Reader) GetWalletToken(ctx context.Context, addresses []common.Address)
|
||||||
})
|
})
|
||||||
|
|
||||||
group.Add(func(parent context.Context) error {
|
group.Add(func(parent context.Context) error {
|
||||||
tokenDetails, err = r.cryptoCompare.FetchTokenDetails(tokenSymbols)
|
tokenDetails, err = r.marketManager.FetchTokenDetails(tokenSymbols)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -204,7 +203,7 @@ func (r *Reader) GetWalletToken(ctx context.Context, addresses []common.Address)
|
||||||
})
|
})
|
||||||
|
|
||||||
group.Add(func(parent context.Context) error {
|
group.Add(func(parent context.Context) error {
|
||||||
tokenMarketValues, err = r.cryptoCompare.FetchTokenMarketValues(tokenSymbols, currencies)
|
tokenMarketValues, err = r.marketManager.FetchTokenMarketValues(tokenSymbols, currency)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -257,13 +256,13 @@ func (r *Reader) GetWalletToken(ctx context.Context, addresses []common.Address)
|
||||||
marketValuesPerCurrency := make(map[string]TokenMarketValues)
|
marketValuesPerCurrency := make(map[string]TokenMarketValues)
|
||||||
for _, currency := range currencies {
|
for _, currency := range currencies {
|
||||||
marketValuesPerCurrency[currency] = TokenMarketValues{
|
marketValuesPerCurrency[currency] = TokenMarketValues{
|
||||||
MarketCap: tokenMarketValues[symbol][currency].MKTCAP,
|
MarketCap: tokenMarketValues[symbol].MKTCAP,
|
||||||
HighDay: tokenMarketValues[symbol][currency].HIGHDAY,
|
HighDay: tokenMarketValues[symbol].HIGHDAY,
|
||||||
LowDay: tokenMarketValues[symbol][currency].LOWDAY,
|
LowDay: tokenMarketValues[symbol].LOWDAY,
|
||||||
ChangePctHour: tokenMarketValues[symbol][currency].CHANGEPCTHOUR,
|
ChangePctHour: tokenMarketValues[symbol].CHANGEPCTHOUR,
|
||||||
ChangePctDay: tokenMarketValues[symbol][currency].CHANGEPCTDAY,
|
ChangePctDay: tokenMarketValues[symbol].CHANGEPCTDAY,
|
||||||
ChangePct24hour: tokenMarketValues[symbol][currency].CHANGEPCT24HOUR,
|
ChangePct24hour: tokenMarketValues[symbol].CHANGEPCT24HOUR,
|
||||||
Change24hour: tokenMarketValues[symbol][currency].CHANGE24HOUR,
|
Change24hour: tokenMarketValues[symbol].CHANGE24HOUR,
|
||||||
Price: prices[symbol][currency],
|
Price: prices[symbol][currency],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -458,7 +458,7 @@ func (r *Router) suggestedRoutes(
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,9 @@ import (
|
||||||
"github.com/status-im/status-go/services/wallet/chain"
|
"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/currency"
|
||||||
"github.com/status-im/status-go/services/wallet/history"
|
"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/market"
|
||||||
"github.com/status-im/status-go/services/wallet/thirdparty"
|
"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/token"
|
||||||
"github.com/status-im/status-go/services/wallet/transfer"
|
"github.com/status-im/status-go/services/wallet/transfer"
|
||||||
"github.com/status-im/status-go/services/wallet/walletevent"
|
"github.com/status-im/status-go/services/wallet/walletevent"
|
||||||
|
@ -32,9 +33,9 @@ type Connection struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConnectedResult struct {
|
type ConnectedResult struct {
|
||||||
Infura map[uint64]Connection `json:"infura"`
|
Blockchain map[uint64]Connection `json:"blockchain"`
|
||||||
CryptoCompare Connection `json:"cryptoCompare"`
|
Market Connection `json:"market"`
|
||||||
Opensea map[uint64]Connection `json:"opensea"`
|
Collectibles map[uint64]Connection `json:"collectibles"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService initializes service instance.
|
// NewService initializes service instance.
|
||||||
|
@ -61,11 +62,11 @@ func NewService(
|
||||||
savedAddressesManager := &SavedAddressesManager{db: db}
|
savedAddressesManager := &SavedAddressesManager{db: db}
|
||||||
transactionManager := &TransactionManager{db: db, transactor: transactor, gethManager: gethManager, config: config, accountsDB: accountsDB}
|
transactionManager := &TransactionManager{db: db, transactor: transactor, gethManager: gethManager, config: config, accountsDB: accountsDB}
|
||||||
transferController := transfer.NewTransferController(db, rpcClient, accountFeed, walletFeed)
|
transferController := transfer.NewTransferController(db, rpcClient, accountFeed, walletFeed)
|
||||||
cryptoCompare := thirdparty.NewCryptoCompare()
|
cryptoCompare := cryptocompare.NewClient()
|
||||||
priceManager := price.NewManager(cryptoCompare)
|
marketManager := market.NewManager(cryptoCompare)
|
||||||
reader := NewReader(rpcClient, tokenManager, priceManager, cryptoCompare, accountsDB, walletFeed)
|
reader := NewReader(rpcClient, tokenManager, marketManager, accountsDB, walletFeed)
|
||||||
history := history.NewService(db, walletFeed, rpcClient, tokenManager, cryptoCompare)
|
history := history.NewService(db, walletFeed, rpcClient, tokenManager, marketManager)
|
||||||
currency := currency.NewService(db, walletFeed, tokenManager, priceManager)
|
currency := currency.NewService(db, walletFeed, tokenManager, marketManager)
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
db: db,
|
||||||
accountsDB: accountsDB,
|
accountsDB: accountsDB,
|
||||||
|
@ -78,14 +79,13 @@ func NewService(
|
||||||
openseaAPIKey: openseaAPIKey,
|
openseaAPIKey: openseaAPIKey,
|
||||||
feesManager: &FeeManager{rpcClient},
|
feesManager: &FeeManager{rpcClient},
|
||||||
gethManager: gethManager,
|
gethManager: gethManager,
|
||||||
priceManager: priceManager,
|
marketManager: marketManager,
|
||||||
transactor: transactor,
|
transactor: transactor,
|
||||||
ens: ens,
|
ens: ens,
|
||||||
stickers: stickers,
|
stickers: stickers,
|
||||||
feed: accountFeed,
|
feed: accountFeed,
|
||||||
signals: signals,
|
signals: signals,
|
||||||
reader: reader,
|
reader: reader,
|
||||||
cryptoCompare: cryptoCompare,
|
|
||||||
history: history,
|
history: history,
|
||||||
currency: currency,
|
currency: currency,
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ type Service struct {
|
||||||
cryptoOnRampManager *CryptoOnRampManager
|
cryptoOnRampManager *CryptoOnRampManager
|
||||||
transferController *transfer.Controller
|
transferController *transfer.Controller
|
||||||
feesManager *FeeManager
|
feesManager *FeeManager
|
||||||
priceManager *price.Manager
|
marketManager *market.Manager
|
||||||
started bool
|
started bool
|
||||||
openseaAPIKey string
|
openseaAPIKey string
|
||||||
gethManager *account.GethManager
|
gethManager *account.GethManager
|
||||||
|
@ -112,7 +112,6 @@ type Service struct {
|
||||||
feed *event.Feed
|
feed *event.Feed
|
||||||
signals *walletevent.SignalsTransmitter
|
signals *walletevent.SignalsTransmitter
|
||||||
reader *Reader
|
reader *Reader
|
||||||
cryptoCompare *thirdparty.CryptoCompare
|
|
||||||
history *history.Service
|
history *history.Service
|
||||||
currency *currency.Service
|
currency *currency.Service
|
||||||
}
|
}
|
||||||
|
@ -167,27 +166,27 @@ func (s *Service) IsStarted() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) CheckConnected(ctx context.Context) *ConnectedResult {
|
func (s *Service) CheckConnected(ctx context.Context) *ConnectedResult {
|
||||||
infura := make(map[uint64]Connection)
|
blockchain := make(map[uint64]Connection)
|
||||||
for chainID, client := range chain.ChainClientInstances {
|
for chainID, client := range chain.ChainClientInstances {
|
||||||
infura[chainID] = Connection{
|
blockchain[chainID] = Connection{
|
||||||
Up: client.IsConnected,
|
Up: client.IsConnected,
|
||||||
LastCheckedAt: client.LastCheckedAt,
|
LastCheckedAt: client.LastCheckedAt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
opensea := make(map[uint64]Connection)
|
collectibles := make(map[uint64]Connection)
|
||||||
for chainID, client := range OpenseaClientInstances {
|
for chainID, client := range opensea.OpenseaClientInstances {
|
||||||
opensea[chainID] = Connection{
|
collectibles[chainID] = Connection{
|
||||||
Up: client.IsConnected,
|
Up: client.IsConnected,
|
||||||
LastCheckedAt: client.LastCheckedAt,
|
LastCheckedAt: client.LastCheckedAt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &ConnectedResult{
|
return &ConnectedResult{
|
||||||
Infura: infura,
|
Blockchain: blockchain,
|
||||||
Opensea: opensea,
|
Collectibles: collectibles,
|
||||||
CryptoCompare: Connection{
|
Market: Connection{
|
||||||
Up: s.cryptoCompare.IsConnected,
|
Up: s.marketManager.IsConnected,
|
||||||
LastCheckedAt: s.cryptoCompare.LastCheckedAt,
|
LastCheckedAt: s.marketManager.LastCheckedAt,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package wallet
|
package opensea
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -18,7 +18,7 @@ import (
|
||||||
const AssetLimit = 50
|
const AssetLimit = 50
|
||||||
const CollectionLimit = 300
|
const CollectionLimit = 300
|
||||||
|
|
||||||
var OpenseaClientInstances = make(map[uint64]*OpenseaClient)
|
var OpenseaClientInstances = make(map[uint64]*Client)
|
||||||
|
|
||||||
var BaseURLs = map[uint64]string{
|
var BaseURLs = map[uint64]string{
|
||||||
1: "https://api.opensea.io/api/v1",
|
1: "https://api.opensea.io/api/v1",
|
||||||
|
@ -46,26 +46,26 @@ func (st *TraitValue) UnmarshalJSON(b []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type OpenseaAssetContainer struct {
|
type AssetContainer struct {
|
||||||
Assets []OpenseaAsset `json:"assets"`
|
Assets []Asset `json:"assets"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OpenseaAssetCollection struct {
|
type AssetCollection struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OpenseaContract struct {
|
type Contract struct {
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OpenseaTrait struct {
|
type Trait struct {
|
||||||
TraitType string `json:"trait_type"`
|
TraitType string `json:"trait_type"`
|
||||||
Value TraitValue `json:"value"`
|
Value TraitValue `json:"value"`
|
||||||
DisplayType string `json:"display_type"`
|
DisplayType string `json:"display_type"`
|
||||||
MaxValue string `json:"max_value"`
|
MaxValue string `json:"max_value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OpenseaPaymentToken struct {
|
type PaymentToken struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Symbol string `json:"symbol"`
|
Symbol string `json:"symbol"`
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
|
@ -76,42 +76,42 @@ type OpenseaPaymentToken struct {
|
||||||
UsdPrice string `json:"usd_price"`
|
UsdPrice string `json:"usd_price"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OpenseaLastSale struct {
|
type LastSale struct {
|
||||||
PaymentToken OpenseaPaymentToken `json:"payment_token"`
|
PaymentToken PaymentToken `json:"payment_token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OpenseaSellOrder struct {
|
type SellOrder struct {
|
||||||
CurrentPrice string `json:"current_price"`
|
CurrentPrice string `json:"current_price"`
|
||||||
}
|
}
|
||||||
type OpenseaAsset struct {
|
type Asset struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Permalink string `json:"permalink"`
|
Permalink string `json:"permalink"`
|
||||||
ImageThumbnailURL string `json:"image_thumbnail_url"`
|
ImageThumbnailURL string `json:"image_thumbnail_url"`
|
||||||
ImageURL string `json:"image_url"`
|
ImageURL string `json:"image_url"`
|
||||||
Contract OpenseaContract `json:"asset_contract"`
|
Contract Contract `json:"asset_contract"`
|
||||||
Collection OpenseaAssetCollection `json:"collection"`
|
Collection AssetCollection `json:"collection"`
|
||||||
Traits []OpenseaTrait `json:"traits"`
|
Traits []Trait `json:"traits"`
|
||||||
LastSale OpenseaLastSale `json:"last_sale"`
|
LastSale LastSale `json:"last_sale"`
|
||||||
SellOrders []OpenseaSellOrder `json:"sell_orders"`
|
SellOrders []SellOrder `json:"sell_orders"`
|
||||||
BackgroundColor string `json:"background_color"`
|
BackgroundColor string `json:"background_color"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OpenseaCollectionTrait struct {
|
type CollectionTrait struct {
|
||||||
Min float64 `json:"min"`
|
Min float64 `json:"min"`
|
||||||
Max float64 `json:"max"`
|
Max float64 `json:"max"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OpenseaCollection struct {
|
type Collection struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Slug string `json:"slug"`
|
Slug string `json:"slug"`
|
||||||
ImageURL string `json:"image_url"`
|
ImageURL string `json:"image_url"`
|
||||||
OwnedAssetCount int `json:"owned_asset_count"`
|
OwnedAssetCount int `json:"owned_asset_count"`
|
||||||
Traits map[string]OpenseaCollectionTrait `json:"traits"`
|
Traits map[string]CollectionTrait `json:"traits"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OpenseaClient struct {
|
type Client struct {
|
||||||
client *http.Client
|
client *http.Client
|
||||||
url string
|
url string
|
||||||
apiKey string
|
apiKey string
|
||||||
|
@ -121,7 +121,7 @@ type OpenseaClient struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// new opensea client.
|
// 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, ok := OpenseaClientInstances[chainID]; ok {
|
||||||
if client.apiKey == apiKey {
|
if client.apiKey == apiKey {
|
||||||
return client, nil
|
return client, nil
|
||||||
|
@ -132,7 +132,7 @@ func newOpenseaClient(chainID uint64, apiKey string) (*OpenseaClient, error) {
|
||||||
Timeout: time.Second * 5,
|
Timeout: time.Second * 5,
|
||||||
}
|
}
|
||||||
if url, ok := BaseURLs[chainID]; ok {
|
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
|
OpenseaClientInstances[chainID] = openseaClient
|
||||||
return openseaClient, nil
|
return openseaClient, nil
|
||||||
}
|
}
|
||||||
|
@ -140,9 +140,9 @@ func newOpenseaClient(chainID uint64, apiKey string) (*OpenseaClient, error) {
|
||||||
return nil, errors.New("ChainID not supported")
|
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
|
offset := 0
|
||||||
var collections []OpenseaCollection
|
var collections []Collection
|
||||||
o.IsConnectedLock.Lock()
|
o.IsConnectedLock.Lock()
|
||||||
defer o.IsConnectedLock.Unlock()
|
defer o.IsConnectedLock.Unlock()
|
||||||
o.LastCheckedAt = time.Now().Unix()
|
o.LastCheckedAt = time.Now().Unix()
|
||||||
|
@ -154,7 +154,7 @@ func (o *OpenseaClient) fetchAllCollectionsByOwner(owner common.Address) ([]Open
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var tmp []OpenseaCollection
|
var tmp []Collection
|
||||||
err = json.Unmarshal(body, &tmp)
|
err = json.Unmarshal(body, &tmp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
o.IsConnected = false
|
o.IsConnected = false
|
||||||
|
@ -171,9 +171,9 @@ func (o *OpenseaClient) fetchAllCollectionsByOwner(owner common.Address) ([]Open
|
||||||
return collections, nil
|
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
|
offset := 0
|
||||||
var assets []OpenseaAsset
|
var assets []Asset
|
||||||
o.IsConnectedLock.Lock()
|
o.IsConnectedLock.Lock()
|
||||||
defer o.IsConnectedLock.Unlock()
|
defer o.IsConnectedLock.Unlock()
|
||||||
o.LastCheckedAt = time.Now().Unix()
|
o.LastCheckedAt = time.Now().Unix()
|
||||||
|
@ -185,7 +185,7 @@ func (o *OpenseaClient) fetchAllAssetsByOwnerAndCollection(owner common.Address,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
container := OpenseaAssetContainer{}
|
container := AssetContainer{}
|
||||||
err = json.Unmarshal(body, &container)
|
err = json.Unmarshal(body, &container)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
o.IsConnected = false
|
o.IsConnected = false
|
||||||
|
@ -213,7 +213,7 @@ func (o *OpenseaClient) fetchAllAssetsByOwnerAndCollection(owner common.Address,
|
||||||
return assets, nil
|
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)
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
|
@ -1,4 +1,4 @@
|
||||||
package wallet
|
package opensea
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFetchAllCollectionsByOwner(t *testing.T) {
|
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)
|
response, _ := json.Marshal(expected)
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(200)
|
w.WriteHeader(200)
|
||||||
|
@ -23,27 +23,27 @@ func TestFetchAllCollectionsByOwner(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
opensea := &OpenseaClient{
|
opensea := &Client{
|
||||||
client: srv.Client(),
|
client: srv.Client(),
|
||||||
url: srv.URL,
|
url: srv.URL,
|
||||||
}
|
}
|
||||||
res, err := opensea.fetchAllCollectionsByOwner(common.Address{1})
|
res, err := opensea.FetchAllCollectionsByOwner(common.Address{1})
|
||||||
assert.Equal(t, expected, res)
|
assert.Equal(t, expected, res)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetchAllAssetsByOwnerAndCollection(t *testing.T) {
|
func TestFetchAllAssetsByOwnerAndCollection(t *testing.T) {
|
||||||
expected := []OpenseaAsset{OpenseaAsset{
|
expected := []Asset{Asset{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Name: "Rocky",
|
Name: "Rocky",
|
||||||
Description: "Rocky Balboa",
|
Description: "Rocky Balboa",
|
||||||
Permalink: "permalink",
|
Permalink: "permalink",
|
||||||
ImageThumbnailURL: "ImageThumbnailURL",
|
ImageThumbnailURL: "ImageThumbnailURL",
|
||||||
ImageURL: "ImageUrl",
|
ImageURL: "ImageUrl",
|
||||||
Contract: OpenseaContract{Address: "1"},
|
Contract: Contract{Address: "1"},
|
||||||
Collection: OpenseaAssetCollection{Name: "Rocky"},
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(200)
|
w.WriteHeader(200)
|
||||||
_, err := w.Write(response)
|
_, err := w.Write(response)
|
||||||
|
@ -53,11 +53,11 @@ func TestFetchAllAssetsByOwnerAndCollection(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
opensea := &OpenseaClient{
|
opensea := &Client{
|
||||||
client: srv.Client(),
|
client: srv.Client(),
|
||||||
url: srv.URL,
|
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.Equal(t, expected, res)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue