chore: make opensea client return common types

This commit is contained in:
Dario Gabriel Lipicar 2023-07-18 12:01:53 -03:00 committed by dlipicar
parent 383de2a7df
commit b1cf54974e
12 changed files with 505 additions and 192 deletions

View File

@ -143,7 +143,7 @@ func (m *DefaultTokenManager) GetAllChainIDs() ([]uint64, error) {
}
type CollectiblesManager interface {
FetchBalancesByOwnerAndContractAddress(chainID uint64, ownerAddress gethcommon.Address, contractAddresses []gethcommon.Address) (thirdparty.TokenBalancesPerContractAddress, error)
FetchBalancesByOwnerAndContractAddress(chainID walletcommon.ChainID, ownerAddress gethcommon.Address, contractAddresses []gethcommon.Address) (thirdparty.TokenBalancesPerContractAddress, error)
}
func (m *DefaultTokenManager) GetBalancesByChain(ctx context.Context, accounts, tokenAddresses []gethcommon.Address, chainIDs []uint64) (BalancesByChain, error) {
@ -2028,7 +2028,8 @@ func (m *Manager) checkPermissions(permissions []*protobuf.CommunityTokenPermiss
}
chainIDLoopERC721:
for chainID, address := range tokenRequirement.ContractAddresses {
for chainID, addressStr := range tokenRequirement.ContractAddresses {
contractAddress := gethcommon.HexToAddress(addressStr)
if _, exists := ownedERC721Tokens[chainID]; !exists || len(ownedERC721Tokens[chainID]) == 0 {
continue chainIDLoopERC721
}
@ -2038,7 +2039,7 @@ func (m *Manager) checkPermissions(permissions []*protobuf.CommunityTokenPermiss
continue
}
tokenBalances := ownedERC721Tokens[chainID][account][gethcommon.HexToAddress(address)]
tokenBalances := ownedERC721Tokens[chainID][account][contractAddress]
if len(tokenBalances) > 0 {
// 'account' owns some TokenID owned from contract 'address'
if _, exists := accountsChainIDsCombinations[account]; !exists {
@ -2189,8 +2190,6 @@ func (m *Manager) GetOwnedERC721Tokens(walletAddresses []gethcommon.Address, tok
ownedERC721Tokens := make(CollectiblesByChain)
client := m.openseaClientBuilder.NewOpenseaClient(m.walletConfig.OpenseaAPIKey, nil)
for chainID, erc721Tokens := range tokenRequirements {
skipChain := true
@ -2214,7 +2213,7 @@ func (m *Manager) GetOwnedERC721Tokens(walletAddresses []gethcommon.Address, tok
}
for _, owner := range walletAddresses {
balances, err := m.collectiblesManager.FetchBalancesByOwnerAndContractAddress(chainID, owner, contractAddresses)
balances, err := m.collectiblesManager.FetchBalancesByOwnerAndContractAddress(walletcommon.ChainID(chainID), owner, contractAddresses)
if err != nil {
m.logger.Info("couldn't fetch owner assets", zap.Error(err))
return nil, err

View File

@ -21,6 +21,7 @@ import (
"github.com/status-im/status-go/protocol/requests"
"github.com/status-im/status-go/protocol/transport"
"github.com/status-im/status-go/services/wallet/bigint"
walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/golang/protobuf/proto"
@ -102,8 +103,8 @@ func (m *testCollectiblesManager) setResponse(chainID uint64, walletAddress geth
m.response[chainID][walletAddress][contractAddress] = balances
}
func (m *testCollectiblesManager) FetchBalancesByOwnerAndContractAddress(chainID uint64, ownerAddress gethcommon.Address, contractAddresses []gethcommon.Address) (thirdparty.TokenBalancesPerContractAddress, error) {
return m.response[chainID][ownerAddress], nil
func (m *testCollectiblesManager) FetchBalancesByOwnerAndContractAddress(chainID walletCommon.ChainID, ownerAddress gethcommon.Address, contractAddresses []gethcommon.Address) (thirdparty.TokenBalancesPerContractAddress, error) {
return m.response[uint64(chainID)][ownerAddress], nil
}
type testTokenManager struct {

View File

@ -545,12 +545,12 @@ func tokenURIToCommunityID(tokenURI string) string {
return communityID
}
func (s *Service) CanProvideCollectibleMetadata(chainID uint64, id thirdparty.CollectibleUniqueID, tokenURI string) (bool, error) {
func (s *Service) CanProvideCollectibleMetadata(id thirdparty.CollectibleUniqueID, tokenURI string) (bool, error) {
ret := tokenURI != "" && tokenURIToCommunityID(tokenURI) != ""
return ret, nil
}
func (s *Service) FetchCollectibleMetadata(chainID uint64, id thirdparty.CollectibleUniqueID, tokenURI string) (*thirdparty.CollectibleMetadata, error) {
func (s *Service) FetchCollectibleMetadata(id thirdparty.CollectibleUniqueID, tokenURI string) (*thirdparty.CollectibleData, error) {
if s.messenger == nil {
return nil, fmt.Errorf("messenger not ready")
}
@ -573,12 +573,16 @@ func (s *Service) FetchCollectibleMetadata(chainID uint64, id thirdparty.Collect
for _, tokenMetadata := range tokensMetadata {
contractAddresses := tokenMetadata.GetContractAddresses()
if contractAddresses[chainID] == id.ContractAddress.Hex() {
return &thirdparty.CollectibleMetadata{
Name: tokenMetadata.GetName(),
Description: tokenMetadata.GetDescription(),
CollectionImageURL: tokenMetadata.GetImage(),
ImageURL: tokenMetadata.GetImage(),
if contractAddresses[uint64(id.ChainID)] == id.ContractAddress.Hex() {
return &thirdparty.CollectibleData{
ID: id,
Name: tokenMetadata.GetName(),
Description: tokenMetadata.GetDescription(),
ImageURL: tokenMetadata.GetImage(),
CollectionData: thirdparty.CollectionData{
Name: tokenMetadata.GetName(),
ImageURL: tokenMetadata.GetImage(),
},
}, nil
}
}

View File

@ -299,13 +299,27 @@ 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) ([]opensea.OwnedCollection, error) {
log.Debug("call to get opensea collections")
/*
Collectibles API Start
*/
func (api *API) FetchBalancesByOwnerAndContractAddress(chainID wcommon.ChainID, ownerAddress common.Address, contractAddresses []common.Address) (thirdparty.TokenBalancesPerContractAddress, error) {
log.Debug("call to FetchBalancesByOwnerAndContractAddress")
return api.s.collectiblesManager.FetchBalancesByOwnerAndContractAddress(chainID, ownerAddress, contractAddresses)
}
// Old Collectibles API - To be deprecated
func (api *API) GetOpenseaCollectionsByOwner(ctx context.Context, chainID wcommon.ChainID, owner common.Address) ([]opensea.OwnedCollection, error) {
log.Debug("call to GetOpenseaCollectionsByOwner")
return api.s.collectiblesManager.FetchAllCollectionsByOwner(chainID, owner)
}
// Kept for compatibility with mobile app
func (api *API) GetOpenseaAssetsByOwnerAndCollection(ctx context.Context, chainID uint64, owner common.Address, collectionSlug string, limit int) ([]opensea.Asset, error) {
func (api *API) GetOpenseaAssetsByOwnerAndCollectionWithCursor(ctx context.Context, chainID wcommon.ChainID, owner common.Address, collectionSlug string, cursor string, limit int) (*opensea.AssetContainer, error) {
log.Debug("call to GetOpenseaAssetsByOwnerAndCollectionWithCursor")
return api.s.collectiblesManager.FetchAllOpenseaAssetsByOwnerAndCollection(chainID, owner, collectionSlug, cursor, limit)
}
func (api *API) GetOpenseaAssetsByOwnerAndCollection(ctx context.Context, chainID wcommon.ChainID, owner common.Address, collectionSlug string, limit int) ([]opensea.Asset, error) {
container, err := api.GetOpenseaAssetsByOwnerAndCollectionWithCursor(ctx, chainID, owner, collectionSlug, "", limit)
if err != nil {
return nil, err
@ -313,35 +327,34 @@ func (api *API) GetOpenseaAssetsByOwnerAndCollection(ctx context.Context, chainI
return container.Assets, nil
}
func (api *API) GetOpenseaAssetsByOwnerAndCollectionWithCursor(ctx context.Context, chainID uint64, owner common.Address, collectionSlug string, cursor string, limit int) (*opensea.AssetContainer, error) {
log.Debug("call to get opensea assets")
func (api *API) GetCollectiblesByOwnerAndCollectionWithCursor(ctx context.Context, chainID wcommon.ChainID, owner common.Address, collectionSlug string, cursor string, limit int) (*thirdparty.CollectibleDataContainer, error) {
log.Debug("call to GetCollectiblesByOwnerAndCollectionWithCursor")
return api.s.collectiblesManager.FetchAllAssetsByOwnerAndCollection(chainID, owner, collectionSlug, cursor, limit)
}
func (api *API) GetOpenseaAssetsByOwnerWithCursor(ctx context.Context, chainID uint64, owner common.Address, cursor string, limit int) (*opensea.AssetContainer, error) {
log.Debug("call to FetchAllAssetsByOwner")
func (api *API) GetCollectiblesByOwnerWithCursor(ctx context.Context, chainID wcommon.ChainID, owner common.Address, cursor string, limit int) (*thirdparty.CollectibleDataContainer, error) {
log.Debug("call to GetCollectiblesByOwnerWithCursor")
return api.s.collectiblesManager.FetchAllAssetsByOwner(chainID, owner, cursor, limit)
}
func (api *API) GetOpenseaAssetsByOwnerAndContractAddressWithCursor(ctx context.Context, chainID uint64, owner common.Address, contractAddresses []common.Address, cursor string, limit int) (*opensea.AssetContainer, error) {
log.Debug("call to GetOpenseaAssetsByOwnerAndContractAddressWithCursor")
func (api *API) GetCollectiblesByOwnerAndContractAddressWithCursor(ctx context.Context, chainID wcommon.ChainID, owner common.Address, contractAddresses []common.Address, cursor string, limit int) (*thirdparty.CollectibleDataContainer, error) {
log.Debug("call to GetCollectiblesByOwnerAndContractAddressWithCursor")
return api.s.collectiblesManager.FetchAllAssetsByOwnerAndContractAddress(chainID, owner, contractAddresses, cursor, limit)
}
func (api *API) GetOpenseaAssetsByNFTUniqueID(ctx context.Context, chainID uint64, uniqueIDs []thirdparty.CollectibleUniqueID, limit int) (*opensea.AssetContainer, error) {
log.Debug("call to GetOpenseaAssetsByNFTUniqueID")
return api.s.collectiblesManager.FetchAssetsByNFTUniqueID(chainID, uniqueIDs, limit)
func (api *API) GetCollectiblesByUniqueID(ctx context.Context, uniqueIDs []thirdparty.CollectibleUniqueID) ([]thirdparty.CollectibleData, error) {
log.Debug("call to GetCollectiblesByUniqueID")
return api.s.collectiblesManager.FetchAssetsByCollectibleUniqueID(uniqueIDs)
}
func (api *API) GetCollectibleOwnersByContractAddress(chainID uint64, contractAddress common.Address) (*thirdparty.CollectibleContractOwnership, error) {
func (api *API) GetCollectibleOwnersByContractAddress(chainID wcommon.ChainID, contractAddress common.Address) (*thirdparty.CollectibleContractOwnership, error) {
log.Debug("call to GetCollectibleOwnersByContractAddress")
return api.s.collectiblesManager.FetchCollectibleOwnersByContractAddress(chainID, contractAddress)
}
func (api *API) FetchBalancesByOwnerAndContractAddress(chainID uint64, ownerAddress common.Address, contractAddresses []common.Address) (thirdparty.TokenBalancesPerContractAddress, error) {
log.Debug("call to FetchBalancesByOwnerAndContractAddress")
return api.s.collectiblesManager.FetchBalancesByOwnerAndContractAddress(chainID, ownerAddress, contractAddresses)
}
/*
Collectibles API End
*/
func (api *API) AddEthereumChain(ctx context.Context, network params.Network) error {
log.Debug("call to AddEthereumChain")

View File

@ -12,10 +12,10 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
"github.com/status-im/status-go/contracts/collectibles"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/wallet/bigint"
walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/services/wallet/thirdparty/opensea"
)
@ -24,8 +24,6 @@ const requestTimeout = 5 * time.Second
const hystrixContractOwnershipClientName = "contractOwnershipClient"
const maxNFTDescriptionLength = 1024
// ERC721 does not support function "TokenURI" if call
// returns error starting with one of these strings
var noTokenURIErrorPrefixes = []string{
@ -39,11 +37,11 @@ type Manager struct {
fallbackContractOwnershipProvider thirdparty.CollectibleContractOwnershipProvider
metadataProvider thirdparty.CollectibleMetadataProvider
opensea *opensea.Client
nftCache map[uint64]map[string]opensea.Asset
nftCache map[walletCommon.ChainID]map[string]thirdparty.CollectibleData
nftCacheLock sync.RWMutex
}
func NewManager(rpcClient *rpc.Client, mainContractOwnershipProvider thirdparty.CollectibleContractOwnershipProvider, fallbackContractOwnershipProvider thirdparty.CollectibleContractOwnershipProvider, openseaAPIKey string, walletFeed *event.Feed) *Manager {
func NewManager(rpcClient *rpc.Client, mainContractOwnershipProvider thirdparty.CollectibleContractOwnershipProvider, fallbackContractOwnershipProvider thirdparty.CollectibleContractOwnershipProvider, opensea *opensea.Client) *Manager {
hystrix.ConfigureCommand(hystrixContractOwnershipClientName, hystrix.CommandConfig{
Timeout: 10000,
MaxConcurrentRequests: 100,
@ -55,8 +53,7 @@ func NewManager(rpcClient *rpc.Client, mainContractOwnershipProvider thirdparty.
rpcClient: rpcClient,
mainContractOwnershipProvider: mainContractOwnershipProvider,
fallbackContractOwnershipProvider: fallbackContractOwnershipProvider,
opensea: opensea.NewClient(openseaAPIKey, walletFeed),
nftCache: make(map[uint64]map[string]opensea.Asset),
opensea: opensea,
}
}
@ -94,17 +91,21 @@ func (o *Manager) SetMetadataProvider(metadataProvider thirdparty.CollectibleMet
o.metadataProvider = metadataProvider
}
func (o *Manager) FetchAllCollectionsByOwner(chainID uint64, owner common.Address) ([]opensea.OwnedCollection, error) {
func (o *Manager) FetchAllCollectionsByOwner(chainID walletCommon.ChainID, owner common.Address) ([]opensea.OwnedCollection, error) {
return o.opensea.FetchAllCollectionsByOwner(chainID, owner)
}
func (o *Manager) FetchAllAssetsByOwnerAndCollection(chainID uint64, owner common.Address, collectionSlug string, cursor string, limit int) (*opensea.AssetContainer, error) {
func (o *Manager) FetchAllOpenseaAssetsByOwnerAndCollection(chainID walletCommon.ChainID, owner common.Address, collectionSlug string, cursor string, limit int) (*opensea.AssetContainer, error) {
return o.opensea.FetchAllOpenseaAssetsByOwnerAndCollection(chainID, owner, collectionSlug, cursor, limit)
}
func (o *Manager) FetchAllAssetsByOwnerAndCollection(chainID walletCommon.ChainID, owner common.Address, collectionSlug string, cursor string, limit int) (*thirdparty.CollectibleDataContainer, error) {
assetContainer, err := o.opensea.FetchAllAssetsByOwnerAndCollection(chainID, owner, collectionSlug, cursor, limit)
if err != nil {
return nil, err
}
err = o.processAssets(chainID, assetContainer.Assets)
err = o.processAssets(assetContainer.Collectibles)
if err != nil {
return nil, err
}
@ -113,7 +114,7 @@ func (o *Manager) FetchAllAssetsByOwnerAndCollection(chainID uint64, owner commo
}
// Need to combine different providers to support all needed ChainIDs
func (o *Manager) FetchBalancesByOwnerAndContractAddress(chainID uint64, ownerAddress common.Address, contractAddresses []common.Address) (thirdparty.TokenBalancesPerContractAddress, error) {
func (o *Manager) FetchBalancesByOwnerAndContractAddress(chainID walletCommon.ChainID, ownerAddress common.Address, contractAddresses []common.Address) (thirdparty.TokenBalancesPerContractAddress, error) {
ret := make(thirdparty.TokenBalancesPerContractAddress)
for _, contractAddress := range contractAddresses {
@ -138,10 +139,10 @@ func (o *Manager) FetchBalancesByOwnerAndContractAddress(chainID uint64, ownerAd
}
} else if err == nil {
// OpenSea could provide
for _, asset := range assetsContainer.Assets {
contractAddress := common.HexToAddress(asset.Contract.Address)
for _, collectible := range assetsContainer.Collectibles {
contractAddress := collectible.ID.ContractAddress
balance := thirdparty.TokenBalance{
TokenID: asset.TokenID,
TokenID: collectible.ID.TokenID,
Balance: &bigint.BigInt{Int: big.NewInt(1)},
}
ret[contractAddress] = append(ret[contractAddress], balance)
@ -154,13 +155,13 @@ func (o *Manager) FetchBalancesByOwnerAndContractAddress(chainID uint64, ownerAd
return ret, nil
}
func (o *Manager) FetchAllAssetsByOwnerAndContractAddress(chainID uint64, owner common.Address, contractAddresses []common.Address, cursor string, limit int) (*opensea.AssetContainer, error) {
func (o *Manager) FetchAllAssetsByOwnerAndContractAddress(chainID walletCommon.ChainID, owner common.Address, contractAddresses []common.Address, cursor string, limit int) (*thirdparty.CollectibleDataContainer, error) {
assetContainer, err := o.opensea.FetchAllAssetsByOwnerAndContractAddress(chainID, owner, contractAddresses, cursor, limit)
if err != nil {
return nil, err
}
err = o.processAssets(chainID, assetContainer.Assets)
err = o.processAssets(assetContainer.Collectibles)
if err != nil {
return nil, err
}
@ -168,13 +169,13 @@ func (o *Manager) FetchAllAssetsByOwnerAndContractAddress(chainID uint64, owner
return assetContainer, nil
}
func (o *Manager) FetchAllAssetsByOwner(chainID uint64, owner common.Address, cursor string, limit int) (*opensea.AssetContainer, error) {
func (o *Manager) FetchAllAssetsByOwner(chainID walletCommon.ChainID, owner common.Address, cursor string, limit int) (*thirdparty.CollectibleDataContainer, error) {
assetContainer, err := o.opensea.FetchAllAssetsByOwner(chainID, owner, cursor, limit)
if err != nil {
return nil, err
}
err = o.processAssets(chainID, assetContainer.Assets)
err = o.processAssets(assetContainer.Collectibles)
if err != nil {
return nil, err
}
@ -182,31 +183,25 @@ func (o *Manager) FetchAllAssetsByOwner(chainID uint64, owner common.Address, cu
return assetContainer, nil
}
func (o *Manager) FetchAssetsByNFTUniqueID(chainID uint64, uniqueIDs []thirdparty.CollectibleUniqueID, limit int) (*opensea.AssetContainer, error) {
assetContainer := new(opensea.AssetContainer)
idsToFetch := o.getIDsNotInCache(chainID, uniqueIDs)
func (o *Manager) FetchAssetsByCollectibleUniqueID(uniqueIDs []thirdparty.CollectibleUniqueID) ([]thirdparty.CollectibleData, error) {
idsToFetch := o.getIDsNotInCollectiblesDataCache(uniqueIDs)
if len(idsToFetch) > 0 {
fetchedAssetContainer, err := o.opensea.FetchAssetsByNFTUniqueID(chainID, idsToFetch, limit)
fetchedAssets, err := o.opensea.FetchAssetsByCollectibleUniqueID(idsToFetch)
if err != nil {
return nil, err
}
err = o.processAssets(chainID, fetchedAssetContainer.Assets)
err = o.processAssets(fetchedAssets)
if err != nil {
return nil, err
}
assetContainer.NextCursor = fetchedAssetContainer.NextCursor
assetContainer.PreviousCursor = fetchedAssetContainer.PreviousCursor
}
assetContainer.Assets = o.getCachedAssets(chainID, uniqueIDs)
return assetContainer, nil
return o.getCacheCollectiblesData(uniqueIDs), nil
}
func (o *Manager) FetchCollectibleOwnersByContractAddress(chainID uint64, contractAddress common.Address) (*thirdparty.CollectibleContractOwnership, error) {
func (o *Manager) FetchCollectibleOwnersByContractAddress(chainID walletCommon.ChainID, contractAddress common.Address) (*thirdparty.CollectibleContractOwnership, error) {
mainFunc := func() (any, error) {
return o.mainContractOwnershipProvider.FetchCollectibleOwnersByContractAddress(chainID, contractAddress)
}
@ -224,15 +219,15 @@ func (o *Manager) FetchCollectibleOwnersByContractAddress(chainID uint64, contra
return owners.(*thirdparty.CollectibleContractOwnership), nil
}
func isMetadataEmpty(asset opensea.Asset) bool {
func isMetadataEmpty(asset thirdparty.CollectibleData) bool {
return asset.Name == "" &&
asset.Description == "" &&
asset.ImageURL == "" &&
asset.TokenURI == ""
}
func (o *Manager) fetchTokenURI(chainID uint64, id thirdparty.CollectibleUniqueID) (string, error) {
backend, err := o.rpcClient.EthClient(chainID)
func (o *Manager) fetchTokenURI(id thirdparty.CollectibleUniqueID) (string, error) {
backend, err := o.rpcClient.EthClient(uint64(id.ChainID))
if err != nil {
return "", err
}
@ -262,25 +257,15 @@ func (o *Manager) fetchTokenURI(chainID uint64, id thirdparty.CollectibleUniqueI
return tokenURI, err
}
func (o *Manager) processAssets(chainID uint64, assets []opensea.Asset) error {
o.nftCacheLock.Lock()
defer o.nftCacheLock.Unlock()
if _, ok := o.nftCache[chainID]; !ok {
o.nftCache[chainID] = make(map[string]opensea.Asset)
}
func (o *Manager) processAssets(assets []thirdparty.CollectibleData) error {
for idx, asset := range assets {
id := thirdparty.CollectibleUniqueID{
ContractAddress: common.HexToAddress(asset.Contract.Address),
TokenID: asset.TokenID,
}
id := asset.ID
if isMetadataEmpty(asset) {
if o.metadataProvider == nil {
return fmt.Errorf("CollectibleMetadataProvider not available")
}
tokenURI, err := o.fetchTokenURI(chainID, id)
tokenURI, err := o.fetchTokenURI(id)
if err != nil {
return err
@ -288,70 +273,82 @@ func (o *Manager) processAssets(chainID uint64, assets []opensea.Asset) error {
assets[idx].TokenURI = tokenURI
canProvide, err := o.metadataProvider.CanProvideCollectibleMetadata(chainID, id, tokenURI)
canProvide, err := o.metadataProvider.CanProvideCollectibleMetadata(id, tokenURI)
if err != nil {
return err
}
if canProvide {
metadata, err := o.metadataProvider.FetchCollectibleMetadata(chainID, id, tokenURI)
metadata, err := o.metadataProvider.FetchCollectibleMetadata(id, tokenURI)
if err != nil {
return err
}
if metadata != nil {
assets[idx].Name = metadata.Name
assets[idx].Description = metadata.Description
assets[idx].Collection.ImageURL = metadata.CollectionImageURL
assets[idx].ImageURL = metadata.ImageURL
assets[idx] = *metadata
}
}
}
// The NFT description field could be arbitrarily large, causing memory management issues upstream.
// Trim it to a reasonable length here.
if len(assets[idx].Description) > maxNFTDescriptionLength {
assets[idx].Description = assets[idx].Description[:maxNFTDescriptionLength]
}
o.nftCache[chainID][id.HashKey()] = assets[idx]
o.setCacheCollectibleData(assets[idx])
}
return nil
}
func (o *Manager) getIDsNotInCache(chainID uint64, uniqueIDs []thirdparty.CollectibleUniqueID) []thirdparty.CollectibleUniqueID {
func (o *Manager) isIDInCollectiblesDataCache(id thirdparty.CollectibleUniqueID) bool {
o.nftCacheLock.RLock()
defer o.nftCacheLock.RUnlock()
idsToFetch := make([]thirdparty.CollectibleUniqueID, 0, len(uniqueIDs))
if _, ok := o.nftCache[chainID]; !ok {
idsToFetch = uniqueIDs
} else {
for _, id := range uniqueIDs {
if _, ok := o.nftCache[chainID][id.HashKey()]; !ok {
idsToFetch = append(idsToFetch, id)
}
if _, ok := o.nftCache[id.ChainID]; ok {
if _, ok := o.nftCache[id.ChainID][id.HashKey()]; ok {
return true
}
}
return false
}
func (o *Manager) getIDsNotInCollectiblesDataCache(uniqueIDs []thirdparty.CollectibleUniqueID) []thirdparty.CollectibleUniqueID {
idsToFetch := make([]thirdparty.CollectibleUniqueID, 0, len(uniqueIDs))
for _, id := range uniqueIDs {
if o.isIDInCollectiblesDataCache(id) {
continue
}
idsToFetch = append(idsToFetch, id)
}
return idsToFetch
}
func (o *Manager) getCachedAssets(chainID uint64, uniqueIDs []thirdparty.CollectibleUniqueID) []opensea.Asset {
func (o *Manager) getCacheCollectiblesData(uniqueIDs []thirdparty.CollectibleUniqueID) []thirdparty.CollectibleData {
o.nftCacheLock.RLock()
defer o.nftCacheLock.RUnlock()
assets := make([]opensea.Asset, 0, len(uniqueIDs))
if _, ok := o.nftCache[chainID]; ok {
for _, id := range uniqueIDs {
if asset, ok := o.nftCache[chainID][id.HashKey()]; ok {
assets := make([]thirdparty.CollectibleData, 0, len(uniqueIDs))
for _, id := range uniqueIDs {
if _, ok := o.nftCache[id.ChainID]; ok {
if asset, ok := o.nftCache[id.ChainID][id.HashKey()]; ok {
assets = append(assets, asset)
continue
}
}
emptyAsset := thirdparty.CollectibleData{
ID: id,
}
assets = append(assets, emptyAsset)
}
return assets
}
func (o *Manager) setCacheCollectibleData(data thirdparty.CollectibleData) {
o.nftCacheLock.Lock()
defer o.nftCacheLock.Unlock()
id := data.ID
if _, ok := o.nftCache[id.ChainID]; !ok {
o.nftCache[id.ChainID] = make(map[string]thirdparty.CollectibleData)
}
o.nftCache[id.ChainID][id.HashKey()] = data
}

View File

@ -3,6 +3,7 @@ package common
type ChainID uint64
const (
UnknownChainID uint64 = 0
EthereumMainnet uint64 = 1
EthereumGoerli uint64 = 5
EthereumSepolia uint64 = 11155111

View File

@ -29,6 +29,7 @@ import (
"github.com/status-im/status-go/services/wallet/thirdparty/coingecko"
"github.com/status-im/status-go/services/wallet/thirdparty/cryptocompare"
"github.com/status-im/status-go/services/wallet/thirdparty/infura"
"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"
@ -106,7 +107,8 @@ func NewService(
alchemyClient := alchemy.NewClient(config.WalletConfig.AlchemyAPIKeys)
infuraClient := infura.NewClient(config.WalletConfig.InfuraAPIKey, config.WalletConfig.InfuraAPIKeySecret)
collectiblesManager := collectibles.NewManager(rpcClient, alchemyClient, infuraClient, config.WalletConfig.OpenseaAPIKey, walletFeed)
openseaClient := opensea.NewClient(config.WalletConfig.OpenseaAPIKey, walletFeed)
collectiblesManager := collectibles.NewManager(rpcClient, alchemyClient, infuraClient, openseaClient)
return &Service{
db: db,
accountsDB: accountsDB,

View File

@ -15,8 +15,8 @@ import (
"github.com/status-im/status-go/services/wallet/thirdparty"
)
func getBaseURL(chainID uint64) (string, error) {
switch chainID {
func getBaseURL(chainID walletCommon.ChainID) (string, error) {
switch uint64(chainID) {
case walletCommon.EthereumMainnet:
return "https://eth-mainnet.g.alchemy.com", nil
case walletCommon.EthereumGoerli:
@ -43,7 +43,7 @@ func getAPIKeySubpath(apiKey string) string {
return apiKey
}
func getNFTBaseURL(chainID uint64, apiKey string) (string, error) {
func getNFTBaseURL(chainID walletCommon.ChainID, apiKey string) (string, error) {
baseURL, err := getBaseURL(chainID)
if err != nil {
@ -93,7 +93,7 @@ func (o *Client) doQuery(url string) (*http.Response, error) {
return resp, nil
}
func (o *Client) IsChainSupported(chainID uint64) bool {
func (o *Client) IsChainSupported(chainID walletCommon.ChainID) bool {
_, err := getBaseURL(chainID)
return err == nil
}
@ -125,13 +125,13 @@ func alchemyOwnershipToCommon(contractAddress common.Address, alchemyOwnership C
return &ownership, nil
}
func (o *Client) FetchCollectibleOwnersByContractAddress(chainID uint64, contractAddress common.Address) (*thirdparty.CollectibleContractOwnership, error) {
func (o *Client) FetchCollectibleOwnersByContractAddress(chainID walletCommon.ChainID, contractAddress common.Address) (*thirdparty.CollectibleContractOwnership, error) {
queryParams := url.Values{
"contractAddress": {contractAddress.String()},
"withTokenBalances": {"true"},
}
url, err := getNFTBaseURL(chainID, o.apiKeys[chainID])
url, err := getNFTBaseURL(chainID, o.apiKeys[uint64(chainID)])
if err != nil {
return nil, err

View File

@ -1,29 +1,115 @@
package thirdparty
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/services/wallet/bigint"
w_common "github.com/status-im/status-go/services/wallet/common"
)
type CollectibleUniqueID struct {
ContractAddress common.Address `json:"contractAddress"`
TokenID *bigint.BigInt `json:"tokenID"`
ChainID w_common.ChainID `json:"chainID"`
ContractAddress common.Address `json:"contractAddress"`
TokenID *bigint.BigInt `json:"tokenID"`
}
func (k *CollectibleUniqueID) HashKey() string {
return k.ContractAddress.String() + "+" + k.TokenID.String()
return fmt.Sprintf("%d+%s+%s", k.ChainID, k.ContractAddress.String(), k.TokenID.String())
}
type CollectibleMetadata struct {
Name string `json:"name"`
Description string `json:"description"`
CollectionImageURL string `json:"collection_image"`
ImageURL string `json:"image"`
func GroupCollectibleUIDsByChainID(uids []CollectibleUniqueID) map[w_common.ChainID][]CollectibleUniqueID {
ret := make(map[w_common.ChainID][]CollectibleUniqueID)
for _, uid := range uids {
if _, ok := ret[uid.ChainID]; !ok {
ret[uid.ChainID] = make([]CollectibleUniqueID, 0, len(uids))
}
ret[uid.ChainID] = append(ret[uid.ChainID], uid)
}
return ret
}
type CollectionTrait struct {
Min float64 `json:"min"`
Max float64 `json:"max"`
}
type CollectionData struct {
Name string `json:"name"`
Slug string `json:"slug"`
ImageURL string `json:"image_url"`
Traits map[string]CollectionTrait `json:"traits"`
}
type CollectibleTrait struct {
TraitType string `json:"trait_type"`
Value string `json:"value"`
DisplayType string `json:"display_type"`
MaxValue string `json:"max_value"`
}
type CollectibleData struct {
ID CollectibleUniqueID `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Permalink string `json:"permalink"`
ImageURL string `json:"image_url"`
AnimationURL string `json:"animation_url"`
AnimationMediaType string `json:"animation_media_type"`
Traits []CollectibleTrait `json:"traits"`
BackgroundColor string `json:"background_color"`
TokenURI string `json:"token_uri"`
CollectionData CollectionData `json:"collection_data"`
}
type CollectibleHeader struct {
ID CollectibleUniqueID `json:"id"`
Name string `json:"name"`
ImageURL string `json:"image_url"`
AnimationURL string `json:"animation_url"`
AnimationMediaType string `json:"animation_media_type"`
BackgroundColor string `json:"background_color"`
CollectionName string `json:"collection_name"`
}
type CollectibleDataContainer struct {
Collectibles []CollectibleData
NextCursor string
PreviousCursor string
}
func (c *CollectibleData) toHeader() CollectibleHeader {
return CollectibleHeader{
ID: c.ID,
Name: c.Name,
ImageURL: c.ImageURL,
AnimationURL: c.AnimationURL,
AnimationMediaType: c.AnimationMediaType,
BackgroundColor: c.BackgroundColor,
CollectionName: c.CollectionData.Name,
}
}
func CollectiblesToHeaders(collectibles []CollectibleData) []CollectibleHeader {
res := make([]CollectibleHeader, 0, len(collectibles))
for _, c := range collectibles {
res = append(res, c.toHeader())
}
return res
}
type CollectibleOwnershipProvider interface {
CanProvideAccountOwnership(chainID uint64) (bool, error)
FetchAccountOwnership(chainID uint64, address common.Address) (*CollectibleData, error)
}
type CollectibleMetadataProvider interface {
CanProvideCollectibleMetadata(chainID uint64, id CollectibleUniqueID, tokenURI string) (bool, error)
FetchCollectibleMetadata(chainID uint64, id CollectibleUniqueID, tokenURI string) (*CollectibleMetadata, error)
CanProvideCollectibleMetadata(id CollectibleUniqueID, tokenURI string) (bool, error)
FetchCollectibleMetadata(id CollectibleUniqueID, tokenURI string) (*CollectibleData, error)
}
type TokenBalance struct {
@ -44,6 +130,6 @@ type CollectibleContractOwnership struct {
}
type CollectibleContractOwnershipProvider interface {
FetchCollectibleOwnersByContractAddress(chainID uint64, contractAddress common.Address) (*CollectibleContractOwnership, error)
IsChainSupported(chainID uint64) bool
FetchCollectibleOwnersByContractAddress(chainID w_common.ChainID, contractAddress common.Address) (*CollectibleContractOwnership, error)
IsChainSupported(chainID w_common.ChainID) bool
}

View File

@ -63,8 +63,8 @@ func (o *Client) doQuery(url string) (*http.Response, error) {
return resp, nil
}
func (o *Client) IsChainSupported(chainID uint64) bool {
switch chainID {
func (o *Client) IsChainSupported(chainID walletCommon.ChainID) bool {
switch uint64(chainID) {
case walletCommon.EthereumMainnet, walletCommon.EthereumGoerli, walletCommon.EthereumSepolia, walletCommon.ArbitrumMainnet:
return true
}
@ -98,7 +98,7 @@ func infuraOwnershipToCommon(contractAddress common.Address, ownersMap map[commo
return &ownership, nil
}
func (o *Client) FetchCollectibleOwnersByContractAddress(chainID uint64, contractAddress common.Address) (*thirdparty.CollectibleContractOwnership, error) {
func (o *Client) FetchCollectibleOwnersByContractAddress(chainID walletCommon.ChainID, contractAddress common.Address) (*thirdparty.CollectibleContractOwnership, error) {
cursor := ""
ownersMap := make(map[common.Address][]CollectibleOwner)

View File

@ -16,6 +16,9 @@ import (
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"github.com/status-im/status-go/services/wallet/bigint"
walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/connection"
@ -36,12 +39,17 @@ const GetRequestWaitTime = 300 * time.Millisecond
const ChainIDRequiringAPIKey = walletCommon.EthereumMainnet
const FetchNoLimit = 0
var (
ErrChainIDNotSupported = errors.New("chainID not supported by opensea API")
)
func getbaseURL(chainID uint64) (string, error) {
switch chainID {
type urlGetter func(walletCommon.ChainID, string) (string, error)
func getbaseURL(chainID walletCommon.ChainID) (string, error) {
// v1 Endpoints only support L1 chain
switch uint64(chainID) {
case walletCommon.EthereumMainnet:
return "https://api.opensea.io/api/v1", nil
case walletCommon.EthereumGoerli:
@ -51,6 +59,34 @@ func getbaseURL(chainID uint64) (string, error) {
return "", ErrChainIDNotSupported
}
func getURL(chainID walletCommon.ChainID, path string) (string, error) {
baseURL, err := getbaseURL(chainID)
if err != nil {
return "", err
}
return fmt.Sprintf("%s/%s", baseURL, path), nil
}
func chainStringToChainID(chainString string) walletCommon.ChainID {
chainID := walletCommon.UnknownChainID
switch chainString {
case "ethereum":
chainID = walletCommon.EthereumMainnet
case "arbitrum":
chainID = walletCommon.ArbitrumMainnet
case "optimism":
chainID = walletCommon.OptimismMainnet
case "goerli":
chainID = walletCommon.EthereumGoerli
case "arbitrum_goerli":
chainID = walletCommon.ArbitrumGoerli
case "optimism_goerli":
chainID = walletCommon.OptimismGoerli
}
return walletCommon.ChainID(chainID)
}
type TraitValue string
func (st *TraitValue) UnmarshalJSON(b []byte) error {
@ -78,7 +114,8 @@ type AssetContainer struct {
}
type Contract struct {
Address string `json:"address"`
Address string `json:"address"`
ChainIdentifier string `json:"chain_identifier"`
}
type Trait struct {
@ -143,6 +180,62 @@ type OwnedCollection struct {
OwnedAssetCount *bigint.BigInt `json:"owned_asset_count"`
}
func (c *Asset) id() thirdparty.CollectibleUniqueID {
return thirdparty.CollectibleUniqueID{
ChainID: chainStringToChainID(c.Contract.ChainIdentifier),
ContractAddress: common.HexToAddress(c.Contract.Address),
TokenID: c.TokenID,
}
}
func openseaToCollectibleTraits(traits []Trait) []thirdparty.CollectibleTrait {
ret := make([]thirdparty.CollectibleTrait, 0, len(traits))
caser := cases.Title(language.Und, cases.NoLower)
for _, orig := range traits {
dest := thirdparty.CollectibleTrait{
TraitType: strings.Replace(orig.TraitType, "_", " ", 1),
Value: caser.String(string(orig.Value)),
DisplayType: orig.DisplayType,
MaxValue: orig.MaxValue,
}
ret = append(ret, dest)
}
return ret
}
func (c *Collection) toCommon() thirdparty.CollectionData {
ret := thirdparty.CollectionData{
Name: c.Name,
Slug: c.Slug,
ImageURL: c.ImageURL,
Traits: make(map[string]thirdparty.CollectionTrait),
}
for traitType, trait := range c.Traits {
ret.Traits[traitType] = thirdparty.CollectionTrait{
Min: trait.Min,
Max: trait.Max,
}
}
return ret
}
func (c *Asset) toCommon() thirdparty.CollectibleData {
return thirdparty.CollectibleData{
ID: c.id(),
Name: c.Name,
Description: c.Description,
Permalink: c.Permalink,
ImageURL: c.ImageURL,
AnimationURL: c.AnimationURL,
AnimationMediaType: c.AnimationMediaType,
Traits: openseaToCollectibleTraits(c.Traits),
BackgroundColor: c.BackgroundColor,
TokenURI: c.TokenURI,
CollectionData: c.Collection.toCommon(),
}
}
type HTTPClient struct {
client *http.Client
getRequestLock sync.RWMutex
@ -242,6 +335,7 @@ type Client struct {
client *HTTPClient
apiKey string
connectionStatus *connection.Status
urlGetter urlGetter
}
// new opensea client.
@ -250,20 +344,21 @@ func NewClient(apiKey string, feed *event.Feed) *Client {
client: newHTTPClient(),
apiKey: apiKey,
connectionStatus: connection.NewStatus(EventCollectibleStatusChanged, feed),
urlGetter: getURL,
}
}
func (o *Client) FetchAllCollectionsByOwner(chainID uint64, owner common.Address) ([]OwnedCollection, error) {
func (o *Client) FetchAllCollectionsByOwner(chainID walletCommon.ChainID, owner common.Address) ([]OwnedCollection, error) {
offset := 0
var collections []OwnedCollection
baseURL, err := getbaseURL(chainID)
if err != nil {
return nil, err
}
for {
url := fmt.Sprintf("%s/collections?asset_owner=%s&offset=%d&limit=%d", baseURL, owner, offset, CollectionLimit)
path := fmt.Sprintf("collections?asset_owner=%s&offset=%d&limit=%d", owner, offset, CollectionLimit)
url, err := o.urlGetter(chainID, path)
if err != nil {
return nil, err
}
body, err := o.client.doGetRequest(url, o.apiKey)
if err != nil {
o.connectionStatus.SetIsConnected(false)
@ -291,7 +386,7 @@ func (o *Client) FetchAllCollectionsByOwner(chainID uint64, owner common.Address
return collections, nil
}
func (o *Client) FetchAllAssetsByOwnerAndCollection(chainID uint64, owner common.Address, collectionSlug string, cursor string, limit int) (*AssetContainer, error) {
func (o *Client) FetchAllAssetsByOwnerAndCollection(chainID walletCommon.ChainID, owner common.Address, collectionSlug string, cursor string, limit int) (*thirdparty.CollectibleDataContainer, error) {
queryParams := url.Values{
"owner": {owner.String()},
"collection": {collectionSlug},
@ -304,7 +399,7 @@ func (o *Client) FetchAllAssetsByOwnerAndCollection(chainID uint64, owner common
return o.fetchAssets(chainID, queryParams, limit)
}
func (o *Client) FetchAllAssetsByOwnerAndContractAddress(chainID uint64, owner common.Address, contractAddresses []common.Address, cursor string, limit int) (*AssetContainer, error) {
func (o *Client) FetchAllAssetsByOwnerAndContractAddress(chainID walletCommon.ChainID, owner common.Address, contractAddresses []common.Address, cursor string, limit int) (*thirdparty.CollectibleDataContainer, error) {
queryParams := url.Values{
"owner": {owner.String()},
}
@ -320,7 +415,7 @@ func (o *Client) FetchAllAssetsByOwnerAndContractAddress(chainID uint64, owner c
return o.fetchAssets(chainID, queryParams, limit)
}
func (o *Client) FetchAllAssetsByOwner(chainID uint64, owner common.Address, cursor string, limit int) (*AssetContainer, error) {
func (o *Client) FetchAllAssetsByOwner(chainID walletCommon.ChainID, owner common.Address, cursor string, limit int) (*thirdparty.CollectibleDataContainer, error) {
queryParams := url.Values{
"owner": {owner.String()},
}
@ -332,18 +427,107 @@ func (o *Client) FetchAllAssetsByOwner(chainID uint64, owner common.Address, cur
return o.fetchAssets(chainID, queryParams, limit)
}
func (o *Client) FetchAssetsByNFTUniqueID(chainID uint64, uniqueIDs []thirdparty.CollectibleUniqueID, limit int) (*AssetContainer, error) {
func (o *Client) FetchAssetsByCollectibleUniqueID(uniqueIDs []thirdparty.CollectibleUniqueID) ([]thirdparty.CollectibleData, error) {
queryParams := url.Values{}
for _, uniqueID := range uniqueIDs {
queryParams.Add("token_ids", uniqueID.TokenID.String())
queryParams.Add("asset_contract_addresses", uniqueID.ContractAddress.String())
ret := make([]thirdparty.CollectibleData, 0, len(uniqueIDs))
idsPerChainID := thirdparty.GroupCollectibleUIDsByChainID(uniqueIDs)
for chainID, ids := range idsPerChainID {
for _, id := range ids {
queryParams.Add("token_ids", id.TokenID.String())
queryParams.Add("asset_contract_addresses", id.ContractAddress.String())
}
data, err := o.fetchAssets(chainID, queryParams, FetchNoLimit)
if err != nil {
return nil, err
}
ret = append(ret, data.Collectibles...)
}
return o.fetchAssets(chainID, queryParams, limit)
return ret, nil
}
func (o *Client) fetchAssets(chainID uint64, queryParams url.Values, limit int) (*AssetContainer, error) {
func (o *Client) fetchAssets(chainID walletCommon.ChainID, queryParams url.Values, limit int) (*thirdparty.CollectibleDataContainer, error) {
assets := new(thirdparty.CollectibleDataContainer)
if len(queryParams["cursor"]) > 0 {
assets.PreviousCursor = queryParams["cursor"][0]
}
tmpLimit := AssetLimit
if limit > FetchNoLimit && limit < tmpLimit {
tmpLimit = limit
}
queryParams["limit"] = []string{strconv.Itoa(tmpLimit)}
for {
path := "assets?" + queryParams.Encode()
url, err := o.urlGetter(chainID, path)
if err != nil {
return nil, err
}
body, err := o.client.doGetRequest(url, o.apiKey)
if err != nil {
o.connectionStatus.SetIsConnected(false)
return nil, err
}
o.connectionStatus.SetIsConnected(true)
// if Json is not returned there must be an error
if !json.Valid(body) {
return nil, fmt.Errorf("invalid json: %s", string(body))
}
container := AssetContainer{}
err = json.Unmarshal(body, &container)
if err != nil {
return nil, err
}
for _, asset := range container.Assets {
if len(asset.AnimationURL) > 0 {
asset.AnimationMediaType, err = o.client.doContentTypeRequest(asset.AnimationURL)
if err != nil {
asset.AnimationURL = ""
}
}
assets.Collectibles = append(assets.Collectibles, asset.toCommon())
}
assets.NextCursor = container.NextCursor
if len(assets.NextCursor) == 0 {
break
}
queryParams["cursor"] = []string{assets.NextCursor}
if limit > FetchNoLimit && len(assets.Collectibles) >= limit {
break
}
}
return assets, nil
}
// Only here for compatibility with mobile app, to be removed
func (o *Client) FetchAllOpenseaAssetsByOwnerAndCollection(chainID walletCommon.ChainID, owner common.Address, collectionSlug string, cursor string, limit int) (*AssetContainer, error) {
queryParams := url.Values{
"owner": {owner.String()},
"collection": {collectionSlug},
}
if len(cursor) > 0 {
queryParams["cursor"] = []string{cursor}
}
return o.fetchOpenseaAssets(chainID, queryParams, limit)
}
func (o *Client) fetchOpenseaAssets(chainID walletCommon.ChainID, queryParams url.Values, limit int) (*AssetContainer, error) {
assets := new(AssetContainer)
if len(queryParams["cursor"]) > 0 {

View File

@ -9,6 +9,9 @@ import (
"testing"
"github.com/status-im/status-go/services/wallet/bigint"
walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/connection"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/stretchr/testify/assert"
@ -20,8 +23,27 @@ const (
ExpectedExpiredKeyError = "invalid json: Expired API key"
)
func initTestClient(srv *httptest.Server) *Client {
urlGetter := func(chainID walletCommon.ChainID, path string) (string, error) {
return srv.URL, nil
}
status := connection.NewStatus("", nil)
client := &HTTPClient{
client: srv.Client(),
}
opensea := &Client{
client: client,
connectionStatus: status,
urlGetter: urlGetter,
}
return opensea
}
func TestFetchAllCollectionsByOwner(t *testing.T) {
expected := []OwnedCollection{{
expectedOS := []OwnedCollection{{
Collection: Collection{
Name: "Rocky",
Slug: "rocky",
@ -29,7 +51,7 @@ func TestFetchAllCollectionsByOwner(t *testing.T) {
},
OwnedAssetCount: &bigint.BigInt{Int: big.NewInt(1)},
}}
response, _ := json.Marshal(expected)
response, _ := json.Marshal(expectedOS)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
_, err := w.Write(response)
@ -39,15 +61,9 @@ func TestFetchAllCollectionsByOwner(t *testing.T) {
}))
defer srv.Close()
client := &HTTPClient{
client: srv.Client(),
}
opensea := &Client{
client: client,
url: srv.URL,
}
res, err := opensea.FetchAllCollectionsByOwner(common.Address{1})
assert.Equal(t, expected, res)
opensea := initTestClient(srv)
res, err := opensea.FetchAllCollectionsByOwner(walletCommon.ChainID(1), common.Address{1})
assert.Equal(t, expectedOS, res)
assert.Nil(t, err)
}
@ -61,34 +77,56 @@ func TestFetchAllCollectionsByOwnerWithInValidJson(t *testing.T) {
}))
defer srv.Close()
client := &HTTPClient{
client: srv.Client(),
}
opensea := &Client{
client: client,
url: srv.URL,
}
res, err := opensea.FetchAllCollectionsByOwner(common.Address{1})
opensea := initTestClient(srv)
res, err := opensea.FetchAllCollectionsByOwner(walletCommon.ChainID(1), common.Address{1})
assert.Nil(t, res)
assert.Equal(t, err, fmt.Errorf(ExpectedExpiredKeyError))
}
func TestFetchAllAssetsByOwnerAndCollection(t *testing.T) {
expected := AssetContainer{
expectedOS := AssetContainer{
Assets: []Asset{{
ID: 1,
TokenID: &bigint.BigInt{Int: big.NewInt(1)},
Name: "Rocky",
Description: "Rocky Balboa",
Permalink: "permalink",
ImageThumbnailURL: "ImageThumbnailURL",
ImageURL: "ImageUrl",
Contract: Contract{Address: "1"},
Collection: Collection{Name: "Rocky"},
Contract: Contract{
Address: "1",
ChainIdentifier: "ethereum",
},
Collection: Collection{
Name: "Rocky",
Traits: map[string]CollectionTrait{},
},
Traits: []Trait{},
}},
NextCursor: "",
PreviousCursor: "",
}
response, _ := json.Marshal(expected)
expectedCommon := thirdparty.CollectibleDataContainer{
Collectibles: []thirdparty.CollectibleData{{
ID: thirdparty.CollectibleUniqueID{
ChainID: 1,
ContractAddress: common.HexToAddress("0x1"),
TokenID: &bigint.BigInt{Int: big.NewInt(1)},
},
Name: "Rocky",
Description: "Rocky Balboa",
Permalink: "permalink",
ImageURL: "ImageUrl",
Traits: []thirdparty.CollectibleTrait{},
CollectionData: thirdparty.CollectionData{
Name: "Rocky",
Traits: map[string]thirdparty.CollectionTrait{},
},
}},
NextCursor: "",
PreviousCursor: "",
}
response, _ := json.Marshal(expectedOS)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
_, err := w.Write(response)
@ -98,16 +136,10 @@ func TestFetchAllAssetsByOwnerAndCollection(t *testing.T) {
}))
defer srv.Close()
client := &HTTPClient{
client: srv.Client(),
}
opensea := &Client{
client: client,
url: srv.URL,
}
res, err := opensea.FetchAllAssetsByOwnerAndCollection(common.Address{1}, "rocky", "", 200)
opensea := initTestClient(srv)
res, err := opensea.FetchAllAssetsByOwnerAndCollection(walletCommon.ChainID(1), common.Address{1}, "rocky", "", 200)
assert.Nil(t, err)
assert.Equal(t, expected, *res)
assert.Equal(t, expectedCommon, *res)
}
func TestFetchAllAssetsByOwnerAndCollectionInvalidJson(t *testing.T) {
@ -120,14 +152,8 @@ func TestFetchAllAssetsByOwnerAndCollectionInvalidJson(t *testing.T) {
}))
defer srv.Close()
client := &HTTPClient{
client: srv.Client(),
}
opensea := &Client{
client: client,
url: srv.URL,
}
res, err := opensea.FetchAllAssetsByOwnerAndCollection(common.Address{1}, "rocky", "", 200)
opensea := initTestClient(srv)
res, err := opensea.FetchAllAssetsByOwnerAndCollection(walletCommon.ChainID(1), common.Address{1}, "rocky", "", 200)
assert.Nil(t, res)
assert.Equal(t, fmt.Errorf(ExpectedExpiredKeyError), err)
}