chore: make opensea client return common types
This commit is contained in:
parent
383de2a7df
commit
b1cf54974e
|
@ -143,7 +143,7 @@ func (m *DefaultTokenManager) GetAllChainIDs() ([]uint64, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type CollectiblesManager interface {
|
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) {
|
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:
|
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 {
|
if _, exists := ownedERC721Tokens[chainID]; !exists || len(ownedERC721Tokens[chainID]) == 0 {
|
||||||
continue chainIDLoopERC721
|
continue chainIDLoopERC721
|
||||||
}
|
}
|
||||||
|
@ -2038,7 +2039,7 @@ func (m *Manager) checkPermissions(permissions []*protobuf.CommunityTokenPermiss
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenBalances := ownedERC721Tokens[chainID][account][gethcommon.HexToAddress(address)]
|
tokenBalances := ownedERC721Tokens[chainID][account][contractAddress]
|
||||||
if len(tokenBalances) > 0 {
|
if len(tokenBalances) > 0 {
|
||||||
// 'account' owns some TokenID owned from contract 'address'
|
// 'account' owns some TokenID owned from contract 'address'
|
||||||
if _, exists := accountsChainIDsCombinations[account]; !exists {
|
if _, exists := accountsChainIDsCombinations[account]; !exists {
|
||||||
|
@ -2189,8 +2190,6 @@ func (m *Manager) GetOwnedERC721Tokens(walletAddresses []gethcommon.Address, tok
|
||||||
|
|
||||||
ownedERC721Tokens := make(CollectiblesByChain)
|
ownedERC721Tokens := make(CollectiblesByChain)
|
||||||
|
|
||||||
client := m.openseaClientBuilder.NewOpenseaClient(m.walletConfig.OpenseaAPIKey, nil)
|
|
||||||
|
|
||||||
for chainID, erc721Tokens := range tokenRequirements {
|
for chainID, erc721Tokens := range tokenRequirements {
|
||||||
|
|
||||||
skipChain := true
|
skipChain := true
|
||||||
|
@ -2214,7 +2213,7 @@ func (m *Manager) GetOwnedERC721Tokens(walletAddresses []gethcommon.Address, tok
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, owner := range walletAddresses {
|
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 {
|
if err != nil {
|
||||||
m.logger.Info("couldn't fetch owner assets", zap.Error(err))
|
m.logger.Info("couldn't fetch owner assets", zap.Error(err))
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/status-im/status-go/protocol/requests"
|
"github.com/status-im/status-go/protocol/requests"
|
||||||
"github.com/status-im/status-go/protocol/transport"
|
"github.com/status-im/status-go/protocol/transport"
|
||||||
"github.com/status-im/status-go/services/wallet/bigint"
|
"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"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
@ -102,8 +103,8 @@ func (m *testCollectiblesManager) setResponse(chainID uint64, walletAddress geth
|
||||||
m.response[chainID][walletAddress][contractAddress] = balances
|
m.response[chainID][walletAddress][contractAddress] = balances
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *testCollectiblesManager) FetchBalancesByOwnerAndContractAddress(chainID uint64, ownerAddress gethcommon.Address, contractAddresses []gethcommon.Address) (thirdparty.TokenBalancesPerContractAddress, error) {
|
func (m *testCollectiblesManager) FetchBalancesByOwnerAndContractAddress(chainID walletCommon.ChainID, ownerAddress gethcommon.Address, contractAddresses []gethcommon.Address) (thirdparty.TokenBalancesPerContractAddress, error) {
|
||||||
return m.response[chainID][ownerAddress], nil
|
return m.response[uint64(chainID)][ownerAddress], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type testTokenManager struct {
|
type testTokenManager struct {
|
||||||
|
|
|
@ -545,12 +545,12 @@ func tokenURIToCommunityID(tokenURI string) string {
|
||||||
return communityID
|
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) != ""
|
ret := tokenURI != "" && tokenURIToCommunityID(tokenURI) != ""
|
||||||
return ret, nil
|
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 {
|
if s.messenger == nil {
|
||||||
return nil, fmt.Errorf("messenger not ready")
|
return nil, fmt.Errorf("messenger not ready")
|
||||||
}
|
}
|
||||||
|
@ -573,12 +573,16 @@ func (s *Service) FetchCollectibleMetadata(chainID uint64, id thirdparty.Collect
|
||||||
|
|
||||||
for _, tokenMetadata := range tokensMetadata {
|
for _, tokenMetadata := range tokensMetadata {
|
||||||
contractAddresses := tokenMetadata.GetContractAddresses()
|
contractAddresses := tokenMetadata.GetContractAddresses()
|
||||||
if contractAddresses[chainID] == id.ContractAddress.Hex() {
|
if contractAddresses[uint64(id.ChainID)] == id.ContractAddress.Hex() {
|
||||||
return &thirdparty.CollectibleMetadata{
|
return &thirdparty.CollectibleData{
|
||||||
Name: tokenMetadata.GetName(),
|
ID: id,
|
||||||
Description: tokenMetadata.GetDescription(),
|
Name: tokenMetadata.GetName(),
|
||||||
CollectionImageURL: tokenMetadata.GetImage(),
|
Description: tokenMetadata.GetDescription(),
|
||||||
ImageURL: tokenMetadata.GetImage(),
|
ImageURL: tokenMetadata.GetImage(),
|
||||||
|
CollectionData: thirdparty.CollectionData{
|
||||||
|
Name: tokenMetadata.GetName(),
|
||||||
|
ImageURL: tokenMetadata.GetImage(),
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -299,13 +299,27 @@ 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) ([]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)
|
return api.s.collectiblesManager.FetchAllCollectionsByOwner(chainID, owner)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kept for compatibility with mobile app
|
func (api *API) GetOpenseaAssetsByOwnerAndCollectionWithCursor(ctx context.Context, chainID wcommon.ChainID, owner common.Address, collectionSlug string, cursor string, limit int) (*opensea.AssetContainer, error) {
|
||||||
func (api *API) GetOpenseaAssetsByOwnerAndCollection(ctx context.Context, chainID uint64, owner common.Address, collectionSlug string, limit int) ([]opensea.Asset, 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)
|
container, err := api.GetOpenseaAssetsByOwnerAndCollectionWithCursor(ctx, chainID, owner, collectionSlug, "", limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -313,35 +327,34 @@ func (api *API) GetOpenseaAssetsByOwnerAndCollection(ctx context.Context, chainI
|
||||||
return container.Assets, nil
|
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) {
|
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 get opensea assets")
|
log.Debug("call to GetCollectiblesByOwnerAndCollectionWithCursor")
|
||||||
return api.s.collectiblesManager.FetchAllAssetsByOwnerAndCollection(chainID, owner, collectionSlug, cursor, limit)
|
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) {
|
func (api *API) GetCollectiblesByOwnerWithCursor(ctx context.Context, chainID wcommon.ChainID, owner common.Address, cursor string, limit int) (*thirdparty.CollectibleDataContainer, error) {
|
||||||
log.Debug("call to FetchAllAssetsByOwner")
|
log.Debug("call to GetCollectiblesByOwnerWithCursor")
|
||||||
return api.s.collectiblesManager.FetchAllAssetsByOwner(chainID, owner, cursor, limit)
|
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) {
|
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 GetOpenseaAssetsByOwnerAndContractAddressWithCursor")
|
log.Debug("call to GetCollectiblesByOwnerAndContractAddressWithCursor")
|
||||||
return api.s.collectiblesManager.FetchAllAssetsByOwnerAndContractAddress(chainID, owner, contractAddresses, cursor, limit)
|
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) {
|
func (api *API) GetCollectiblesByUniqueID(ctx context.Context, uniqueIDs []thirdparty.CollectibleUniqueID) ([]thirdparty.CollectibleData, error) {
|
||||||
log.Debug("call to GetOpenseaAssetsByNFTUniqueID")
|
log.Debug("call to GetCollectiblesByUniqueID")
|
||||||
return api.s.collectiblesManager.FetchAssetsByNFTUniqueID(chainID, uniqueIDs, limit)
|
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")
|
log.Debug("call to GetCollectibleOwnersByContractAddress")
|
||||||
return api.s.collectiblesManager.FetchCollectibleOwnersByContractAddress(chainID, contractAddress)
|
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")
|
Collectibles API End
|
||||||
return api.s.collectiblesManager.FetchBalancesByOwnerAndContractAddress(chainID, ownerAddress, contractAddresses)
|
*/
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) AddEthereumChain(ctx context.Context, network params.Network) error {
|
func (api *API) AddEthereumChain(ctx context.Context, network params.Network) error {
|
||||||
log.Debug("call to AddEthereumChain")
|
log.Debug("call to AddEthereumChain")
|
||||||
|
|
|
@ -12,10 +12,10 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"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/contracts/collectibles"
|
||||||
"github.com/status-im/status-go/rpc"
|
"github.com/status-im/status-go/rpc"
|
||||||
"github.com/status-im/status-go/services/wallet/bigint"
|
"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"
|
||||||
"github.com/status-im/status-go/services/wallet/thirdparty/opensea"
|
"github.com/status-im/status-go/services/wallet/thirdparty/opensea"
|
||||||
)
|
)
|
||||||
|
@ -24,8 +24,6 @@ const requestTimeout = 5 * time.Second
|
||||||
|
|
||||||
const hystrixContractOwnershipClientName = "contractOwnershipClient"
|
const hystrixContractOwnershipClientName = "contractOwnershipClient"
|
||||||
|
|
||||||
const maxNFTDescriptionLength = 1024
|
|
||||||
|
|
||||||
// ERC721 does not support function "TokenURI" if call
|
// ERC721 does not support function "TokenURI" if call
|
||||||
// returns error starting with one of these strings
|
// returns error starting with one of these strings
|
||||||
var noTokenURIErrorPrefixes = []string{
|
var noTokenURIErrorPrefixes = []string{
|
||||||
|
@ -39,11 +37,11 @@ type Manager struct {
|
||||||
fallbackContractOwnershipProvider thirdparty.CollectibleContractOwnershipProvider
|
fallbackContractOwnershipProvider thirdparty.CollectibleContractOwnershipProvider
|
||||||
metadataProvider thirdparty.CollectibleMetadataProvider
|
metadataProvider thirdparty.CollectibleMetadataProvider
|
||||||
opensea *opensea.Client
|
opensea *opensea.Client
|
||||||
nftCache map[uint64]map[string]opensea.Asset
|
nftCache map[walletCommon.ChainID]map[string]thirdparty.CollectibleData
|
||||||
nftCacheLock sync.RWMutex
|
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{
|
hystrix.ConfigureCommand(hystrixContractOwnershipClientName, hystrix.CommandConfig{
|
||||||
Timeout: 10000,
|
Timeout: 10000,
|
||||||
MaxConcurrentRequests: 100,
|
MaxConcurrentRequests: 100,
|
||||||
|
@ -55,8 +53,7 @@ func NewManager(rpcClient *rpc.Client, mainContractOwnershipProvider thirdparty.
|
||||||
rpcClient: rpcClient,
|
rpcClient: rpcClient,
|
||||||
mainContractOwnershipProvider: mainContractOwnershipProvider,
|
mainContractOwnershipProvider: mainContractOwnershipProvider,
|
||||||
fallbackContractOwnershipProvider: fallbackContractOwnershipProvider,
|
fallbackContractOwnershipProvider: fallbackContractOwnershipProvider,
|
||||||
opensea: opensea.NewClient(openseaAPIKey, walletFeed),
|
opensea: opensea,
|
||||||
nftCache: make(map[uint64]map[string]opensea.Asset),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,17 +91,21 @@ func (o *Manager) SetMetadataProvider(metadataProvider thirdparty.CollectibleMet
|
||||||
o.metadataProvider = metadataProvider
|
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)
|
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)
|
assetContainer, err := o.opensea.FetchAllAssetsByOwnerAndCollection(chainID, owner, collectionSlug, cursor, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = o.processAssets(chainID, assetContainer.Assets)
|
err = o.processAssets(assetContainer.Collectibles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// 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)
|
ret := make(thirdparty.TokenBalancesPerContractAddress)
|
||||||
|
|
||||||
for _, contractAddress := range contractAddresses {
|
for _, contractAddress := range contractAddresses {
|
||||||
|
@ -138,10 +139,10 @@ func (o *Manager) FetchBalancesByOwnerAndContractAddress(chainID uint64, ownerAd
|
||||||
}
|
}
|
||||||
} else if err == nil {
|
} else if err == nil {
|
||||||
// OpenSea could provide
|
// OpenSea could provide
|
||||||
for _, asset := range assetsContainer.Assets {
|
for _, collectible := range assetsContainer.Collectibles {
|
||||||
contractAddress := common.HexToAddress(asset.Contract.Address)
|
contractAddress := collectible.ID.ContractAddress
|
||||||
balance := thirdparty.TokenBalance{
|
balance := thirdparty.TokenBalance{
|
||||||
TokenID: asset.TokenID,
|
TokenID: collectible.ID.TokenID,
|
||||||
Balance: &bigint.BigInt{Int: big.NewInt(1)},
|
Balance: &bigint.BigInt{Int: big.NewInt(1)},
|
||||||
}
|
}
|
||||||
ret[contractAddress] = append(ret[contractAddress], balance)
|
ret[contractAddress] = append(ret[contractAddress], balance)
|
||||||
|
@ -154,13 +155,13 @@ func (o *Manager) FetchBalancesByOwnerAndContractAddress(chainID uint64, ownerAd
|
||||||
return ret, nil
|
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)
|
assetContainer, err := o.opensea.FetchAllAssetsByOwnerAndContractAddress(chainID, owner, contractAddresses, cursor, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = o.processAssets(chainID, assetContainer.Assets)
|
err = o.processAssets(assetContainer.Collectibles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -168,13 +169,13 @@ func (o *Manager) FetchAllAssetsByOwnerAndContractAddress(chainID uint64, owner
|
||||||
return assetContainer, nil
|
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)
|
assetContainer, err := o.opensea.FetchAllAssetsByOwner(chainID, owner, cursor, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = o.processAssets(chainID, assetContainer.Assets)
|
err = o.processAssets(assetContainer.Collectibles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -182,31 +183,25 @@ func (o *Manager) FetchAllAssetsByOwner(chainID uint64, owner common.Address, cu
|
||||||
return assetContainer, nil
|
return assetContainer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Manager) FetchAssetsByNFTUniqueID(chainID uint64, uniqueIDs []thirdparty.CollectibleUniqueID, limit int) (*opensea.AssetContainer, error) {
|
func (o *Manager) FetchAssetsByCollectibleUniqueID(uniqueIDs []thirdparty.CollectibleUniqueID) ([]thirdparty.CollectibleData, error) {
|
||||||
assetContainer := new(opensea.AssetContainer)
|
idsToFetch := o.getIDsNotInCollectiblesDataCache(uniqueIDs)
|
||||||
|
|
||||||
idsToFetch := o.getIDsNotInCache(chainID, uniqueIDs)
|
|
||||||
if len(idsToFetch) > 0 {
|
if len(idsToFetch) > 0 {
|
||||||
fetchedAssetContainer, err := o.opensea.FetchAssetsByNFTUniqueID(chainID, idsToFetch, limit)
|
fetchedAssets, err := o.opensea.FetchAssetsByCollectibleUniqueID(idsToFetch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = o.processAssets(chainID, fetchedAssetContainer.Assets)
|
err = o.processAssets(fetchedAssets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
assetContainer.NextCursor = fetchedAssetContainer.NextCursor
|
|
||||||
assetContainer.PreviousCursor = fetchedAssetContainer.PreviousCursor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assetContainer.Assets = o.getCachedAssets(chainID, uniqueIDs)
|
return o.getCacheCollectiblesData(uniqueIDs), nil
|
||||||
|
|
||||||
return assetContainer, 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) {
|
mainFunc := func() (any, error) {
|
||||||
return o.mainContractOwnershipProvider.FetchCollectibleOwnersByContractAddress(chainID, contractAddress)
|
return o.mainContractOwnershipProvider.FetchCollectibleOwnersByContractAddress(chainID, contractAddress)
|
||||||
}
|
}
|
||||||
|
@ -224,15 +219,15 @@ func (o *Manager) FetchCollectibleOwnersByContractAddress(chainID uint64, contra
|
||||||
return owners.(*thirdparty.CollectibleContractOwnership), nil
|
return owners.(*thirdparty.CollectibleContractOwnership), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isMetadataEmpty(asset opensea.Asset) bool {
|
func isMetadataEmpty(asset thirdparty.CollectibleData) bool {
|
||||||
return asset.Name == "" &&
|
return asset.Name == "" &&
|
||||||
asset.Description == "" &&
|
asset.Description == "" &&
|
||||||
asset.ImageURL == "" &&
|
asset.ImageURL == "" &&
|
||||||
asset.TokenURI == ""
|
asset.TokenURI == ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Manager) fetchTokenURI(chainID uint64, id thirdparty.CollectibleUniqueID) (string, error) {
|
func (o *Manager) fetchTokenURI(id thirdparty.CollectibleUniqueID) (string, error) {
|
||||||
backend, err := o.rpcClient.EthClient(chainID)
|
backend, err := o.rpcClient.EthClient(uint64(id.ChainID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -262,25 +257,15 @@ func (o *Manager) fetchTokenURI(chainID uint64, id thirdparty.CollectibleUniqueI
|
||||||
return tokenURI, err
|
return tokenURI, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Manager) processAssets(chainID uint64, assets []opensea.Asset) error {
|
func (o *Manager) processAssets(assets []thirdparty.CollectibleData) error {
|
||||||
o.nftCacheLock.Lock()
|
|
||||||
defer o.nftCacheLock.Unlock()
|
|
||||||
|
|
||||||
if _, ok := o.nftCache[chainID]; !ok {
|
|
||||||
o.nftCache[chainID] = make(map[string]opensea.Asset)
|
|
||||||
}
|
|
||||||
|
|
||||||
for idx, asset := range assets {
|
for idx, asset := range assets {
|
||||||
id := thirdparty.CollectibleUniqueID{
|
id := asset.ID
|
||||||
ContractAddress: common.HexToAddress(asset.Contract.Address),
|
|
||||||
TokenID: asset.TokenID,
|
|
||||||
}
|
|
||||||
|
|
||||||
if isMetadataEmpty(asset) {
|
if isMetadataEmpty(asset) {
|
||||||
if o.metadataProvider == nil {
|
if o.metadataProvider == nil {
|
||||||
return fmt.Errorf("CollectibleMetadataProvider not available")
|
return fmt.Errorf("CollectibleMetadataProvider not available")
|
||||||
}
|
}
|
||||||
tokenURI, err := o.fetchTokenURI(chainID, id)
|
tokenURI, err := o.fetchTokenURI(id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -288,70 +273,82 @@ func (o *Manager) processAssets(chainID uint64, assets []opensea.Asset) error {
|
||||||
|
|
||||||
assets[idx].TokenURI = tokenURI
|
assets[idx].TokenURI = tokenURI
|
||||||
|
|
||||||
canProvide, err := o.metadataProvider.CanProvideCollectibleMetadata(chainID, id, tokenURI)
|
canProvide, err := o.metadataProvider.CanProvideCollectibleMetadata(id, tokenURI)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if canProvide {
|
if canProvide {
|
||||||
metadata, err := o.metadataProvider.FetchCollectibleMetadata(chainID, id, tokenURI)
|
metadata, err := o.metadataProvider.FetchCollectibleMetadata(id, tokenURI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if metadata != nil {
|
if metadata != nil {
|
||||||
assets[idx].Name = metadata.Name
|
assets[idx] = *metadata
|
||||||
assets[idx].Description = metadata.Description
|
|
||||||
assets[idx].Collection.ImageURL = metadata.CollectionImageURL
|
|
||||||
assets[idx].ImageURL = metadata.ImageURL
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The NFT description field could be arbitrarily large, causing memory management issues upstream.
|
o.setCacheCollectibleData(assets[idx])
|
||||||
// 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]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Manager) getIDsNotInCache(chainID uint64, uniqueIDs []thirdparty.CollectibleUniqueID) []thirdparty.CollectibleUniqueID {
|
func (o *Manager) isIDInCollectiblesDataCache(id thirdparty.CollectibleUniqueID) bool {
|
||||||
o.nftCacheLock.RLock()
|
o.nftCacheLock.RLock()
|
||||||
defer o.nftCacheLock.RUnlock()
|
defer o.nftCacheLock.RUnlock()
|
||||||
|
if _, ok := o.nftCache[id.ChainID]; ok {
|
||||||
idsToFetch := make([]thirdparty.CollectibleUniqueID, 0, len(uniqueIDs))
|
if _, ok := o.nftCache[id.ChainID][id.HashKey()]; ok {
|
||||||
if _, ok := o.nftCache[chainID]; !ok {
|
return true
|
||||||
idsToFetch = uniqueIDs
|
|
||||||
} else {
|
|
||||||
for _, id := range uniqueIDs {
|
|
||||||
if _, ok := o.nftCache[chainID][id.HashKey()]; !ok {
|
|
||||||
idsToFetch = append(idsToFetch, id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
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()
|
o.nftCacheLock.RLock()
|
||||||
defer o.nftCacheLock.RUnlock()
|
defer o.nftCacheLock.RUnlock()
|
||||||
|
|
||||||
assets := make([]opensea.Asset, 0, len(uniqueIDs))
|
assets := make([]thirdparty.CollectibleData, 0, len(uniqueIDs))
|
||||||
|
for _, id := range uniqueIDs {
|
||||||
if _, ok := o.nftCache[chainID]; ok {
|
if _, ok := o.nftCache[id.ChainID]; ok {
|
||||||
for _, id := range uniqueIDs {
|
if asset, ok := o.nftCache[id.ChainID][id.HashKey()]; ok {
|
||||||
|
|
||||||
if asset, ok := o.nftCache[chainID][id.HashKey()]; ok {
|
|
||||||
assets = append(assets, asset)
|
assets = append(assets, asset)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
emptyAsset := thirdparty.CollectibleData{
|
||||||
|
ID: id,
|
||||||
|
}
|
||||||
|
assets = append(assets, emptyAsset)
|
||||||
}
|
}
|
||||||
|
|
||||||
return assets
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package common
|
||||||
type ChainID uint64
|
type ChainID uint64
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
UnknownChainID uint64 = 0
|
||||||
EthereumMainnet uint64 = 1
|
EthereumMainnet uint64 = 1
|
||||||
EthereumGoerli uint64 = 5
|
EthereumGoerli uint64 = 5
|
||||||
EthereumSepolia uint64 = 11155111
|
EthereumSepolia uint64 = 11155111
|
||||||
|
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/status-im/status-go/services/wallet/thirdparty/coingecko"
|
"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/cryptocompare"
|
||||||
"github.com/status-im/status-go/services/wallet/thirdparty/infura"
|
"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/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"
|
||||||
|
@ -106,7 +107,8 @@ func NewService(
|
||||||
|
|
||||||
alchemyClient := alchemy.NewClient(config.WalletConfig.AlchemyAPIKeys)
|
alchemyClient := alchemy.NewClient(config.WalletConfig.AlchemyAPIKeys)
|
||||||
infuraClient := infura.NewClient(config.WalletConfig.InfuraAPIKey, config.WalletConfig.InfuraAPIKeySecret)
|
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{
|
return &Service{
|
||||||
db: db,
|
db: db,
|
||||||
accountsDB: accountsDB,
|
accountsDB: accountsDB,
|
||||||
|
|
|
@ -15,8 +15,8 @@ import (
|
||||||
"github.com/status-im/status-go/services/wallet/thirdparty"
|
"github.com/status-im/status-go/services/wallet/thirdparty"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getBaseURL(chainID uint64) (string, error) {
|
func getBaseURL(chainID walletCommon.ChainID) (string, error) {
|
||||||
switch chainID {
|
switch uint64(chainID) {
|
||||||
case walletCommon.EthereumMainnet:
|
case walletCommon.EthereumMainnet:
|
||||||
return "https://eth-mainnet.g.alchemy.com", nil
|
return "https://eth-mainnet.g.alchemy.com", nil
|
||||||
case walletCommon.EthereumGoerli:
|
case walletCommon.EthereumGoerli:
|
||||||
|
@ -43,7 +43,7 @@ func getAPIKeySubpath(apiKey string) string {
|
||||||
return apiKey
|
return apiKey
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNFTBaseURL(chainID uint64, apiKey string) (string, error) {
|
func getNFTBaseURL(chainID walletCommon.ChainID, apiKey string) (string, error) {
|
||||||
baseURL, err := getBaseURL(chainID)
|
baseURL, err := getBaseURL(chainID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -93,7 +93,7 @@ func (o *Client) doQuery(url string) (*http.Response, error) {
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Client) IsChainSupported(chainID uint64) bool {
|
func (o *Client) IsChainSupported(chainID walletCommon.ChainID) bool {
|
||||||
_, err := getBaseURL(chainID)
|
_, err := getBaseURL(chainID)
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
@ -125,13 +125,13 @@ func alchemyOwnershipToCommon(contractAddress common.Address, alchemyOwnership C
|
||||||
return &ownership, nil
|
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{
|
queryParams := url.Values{
|
||||||
"contractAddress": {contractAddress.String()},
|
"contractAddress": {contractAddress.String()},
|
||||||
"withTokenBalances": {"true"},
|
"withTokenBalances": {"true"},
|
||||||
}
|
}
|
||||||
|
|
||||||
url, err := getNFTBaseURL(chainID, o.apiKeys[chainID])
|
url, err := getNFTBaseURL(chainID, o.apiKeys[uint64(chainID)])
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -1,29 +1,115 @@
|
||||||
package thirdparty
|
package thirdparty
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/status-im/status-go/services/wallet/bigint"
|
"github.com/status-im/status-go/services/wallet/bigint"
|
||||||
|
w_common "github.com/status-im/status-go/services/wallet/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CollectibleUniqueID struct {
|
type CollectibleUniqueID struct {
|
||||||
ContractAddress common.Address `json:"contractAddress"`
|
ChainID w_common.ChainID `json:"chainID"`
|
||||||
TokenID *bigint.BigInt `json:"tokenID"`
|
ContractAddress common.Address `json:"contractAddress"`
|
||||||
|
TokenID *bigint.BigInt `json:"tokenID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *CollectibleUniqueID) HashKey() string {
|
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 {
|
func GroupCollectibleUIDsByChainID(uids []CollectibleUniqueID) map[w_common.ChainID][]CollectibleUniqueID {
|
||||||
Name string `json:"name"`
|
ret := make(map[w_common.ChainID][]CollectibleUniqueID)
|
||||||
Description string `json:"description"`
|
|
||||||
CollectionImageURL string `json:"collection_image"`
|
for _, uid := range uids {
|
||||||
ImageURL string `json:"image"`
|
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 {
|
type CollectibleMetadataProvider interface {
|
||||||
CanProvideCollectibleMetadata(chainID uint64, id CollectibleUniqueID, tokenURI string) (bool, error)
|
CanProvideCollectibleMetadata(id CollectibleUniqueID, tokenURI string) (bool, error)
|
||||||
FetchCollectibleMetadata(chainID uint64, id CollectibleUniqueID, tokenURI string) (*CollectibleMetadata, error)
|
FetchCollectibleMetadata(id CollectibleUniqueID, tokenURI string) (*CollectibleData, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type TokenBalance struct {
|
type TokenBalance struct {
|
||||||
|
@ -44,6 +130,6 @@ type CollectibleContractOwnership struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type CollectibleContractOwnershipProvider interface {
|
type CollectibleContractOwnershipProvider interface {
|
||||||
FetchCollectibleOwnersByContractAddress(chainID uint64, contractAddress common.Address) (*CollectibleContractOwnership, error)
|
FetchCollectibleOwnersByContractAddress(chainID w_common.ChainID, contractAddress common.Address) (*CollectibleContractOwnership, error)
|
||||||
IsChainSupported(chainID uint64) bool
|
IsChainSupported(chainID w_common.ChainID) bool
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,8 +63,8 @@ func (o *Client) doQuery(url string) (*http.Response, error) {
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Client) IsChainSupported(chainID uint64) bool {
|
func (o *Client) IsChainSupported(chainID walletCommon.ChainID) bool {
|
||||||
switch chainID {
|
switch uint64(chainID) {
|
||||||
case walletCommon.EthereumMainnet, walletCommon.EthereumGoerli, walletCommon.EthereumSepolia, walletCommon.ArbitrumMainnet:
|
case walletCommon.EthereumMainnet, walletCommon.EthereumGoerli, walletCommon.EthereumSepolia, walletCommon.ArbitrumMainnet:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@ func infuraOwnershipToCommon(contractAddress common.Address, ownersMap map[commo
|
||||||
return &ownership, nil
|
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 := ""
|
cursor := ""
|
||||||
ownersMap := make(map[common.Address][]CollectibleOwner)
|
ownersMap := make(map[common.Address][]CollectibleOwner)
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,9 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"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"
|
"github.com/status-im/status-go/services/wallet/bigint"
|
||||||
walletCommon "github.com/status-im/status-go/services/wallet/common"
|
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/connection"
|
||||||
|
@ -36,12 +39,17 @@ const GetRequestWaitTime = 300 * time.Millisecond
|
||||||
|
|
||||||
const ChainIDRequiringAPIKey = walletCommon.EthereumMainnet
|
const ChainIDRequiringAPIKey = walletCommon.EthereumMainnet
|
||||||
|
|
||||||
|
const FetchNoLimit = 0
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrChainIDNotSupported = errors.New("chainID not supported by opensea API")
|
ErrChainIDNotSupported = errors.New("chainID not supported by opensea API")
|
||||||
)
|
)
|
||||||
|
|
||||||
func getbaseURL(chainID uint64) (string, error) {
|
type urlGetter func(walletCommon.ChainID, string) (string, error)
|
||||||
switch chainID {
|
|
||||||
|
func getbaseURL(chainID walletCommon.ChainID) (string, error) {
|
||||||
|
// v1 Endpoints only support L1 chain
|
||||||
|
switch uint64(chainID) {
|
||||||
case walletCommon.EthereumMainnet:
|
case walletCommon.EthereumMainnet:
|
||||||
return "https://api.opensea.io/api/v1", nil
|
return "https://api.opensea.io/api/v1", nil
|
||||||
case walletCommon.EthereumGoerli:
|
case walletCommon.EthereumGoerli:
|
||||||
|
@ -51,6 +59,34 @@ func getbaseURL(chainID uint64) (string, error) {
|
||||||
return "", ErrChainIDNotSupported
|
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
|
type TraitValue string
|
||||||
|
|
||||||
func (st *TraitValue) UnmarshalJSON(b []byte) error {
|
func (st *TraitValue) UnmarshalJSON(b []byte) error {
|
||||||
|
@ -78,7 +114,8 @@ type AssetContainer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Contract struct {
|
type Contract struct {
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
|
ChainIdentifier string `json:"chain_identifier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Trait struct {
|
type Trait struct {
|
||||||
|
@ -143,6 +180,62 @@ type OwnedCollection struct {
|
||||||
OwnedAssetCount *bigint.BigInt `json:"owned_asset_count"`
|
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 {
|
type HTTPClient struct {
|
||||||
client *http.Client
|
client *http.Client
|
||||||
getRequestLock sync.RWMutex
|
getRequestLock sync.RWMutex
|
||||||
|
@ -242,6 +335,7 @@ type Client struct {
|
||||||
client *HTTPClient
|
client *HTTPClient
|
||||||
apiKey string
|
apiKey string
|
||||||
connectionStatus *connection.Status
|
connectionStatus *connection.Status
|
||||||
|
urlGetter urlGetter
|
||||||
}
|
}
|
||||||
|
|
||||||
// new opensea client.
|
// new opensea client.
|
||||||
|
@ -250,20 +344,21 @@ func NewClient(apiKey string, feed *event.Feed) *Client {
|
||||||
client: newHTTPClient(),
|
client: newHTTPClient(),
|
||||||
apiKey: apiKey,
|
apiKey: apiKey,
|
||||||
connectionStatus: connection.NewStatus(EventCollectibleStatusChanged, feed),
|
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
|
offset := 0
|
||||||
var collections []OwnedCollection
|
var collections []OwnedCollection
|
||||||
|
|
||||||
baseURL, err := getbaseURL(chainID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
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)
|
body, err := o.client.doGetRequest(url, o.apiKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
o.connectionStatus.SetIsConnected(false)
|
o.connectionStatus.SetIsConnected(false)
|
||||||
|
@ -291,7 +386,7 @@ func (o *Client) FetchAllCollectionsByOwner(chainID uint64, owner common.Address
|
||||||
return collections, nil
|
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{
|
queryParams := url.Values{
|
||||||
"owner": {owner.String()},
|
"owner": {owner.String()},
|
||||||
"collection": {collectionSlug},
|
"collection": {collectionSlug},
|
||||||
|
@ -304,7 +399,7 @@ func (o *Client) FetchAllAssetsByOwnerAndCollection(chainID uint64, owner common
|
||||||
return o.fetchAssets(chainID, queryParams, limit)
|
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{
|
queryParams := url.Values{
|
||||||
"owner": {owner.String()},
|
"owner": {owner.String()},
|
||||||
}
|
}
|
||||||
|
@ -320,7 +415,7 @@ func (o *Client) FetchAllAssetsByOwnerAndContractAddress(chainID uint64, owner c
|
||||||
return o.fetchAssets(chainID, queryParams, limit)
|
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{
|
queryParams := url.Values{
|
||||||
"owner": {owner.String()},
|
"owner": {owner.String()},
|
||||||
}
|
}
|
||||||
|
@ -332,18 +427,107 @@ func (o *Client) FetchAllAssetsByOwner(chainID uint64, owner common.Address, cur
|
||||||
return o.fetchAssets(chainID, queryParams, limit)
|
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{}
|
queryParams := url.Values{}
|
||||||
|
|
||||||
for _, uniqueID := range uniqueIDs {
|
ret := make([]thirdparty.CollectibleData, 0, len(uniqueIDs))
|
||||||
queryParams.Add("token_ids", uniqueID.TokenID.String())
|
|
||||||
queryParams.Add("asset_contract_addresses", uniqueID.ContractAddress.String())
|
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)
|
assets := new(AssetContainer)
|
||||||
|
|
||||||
if len(queryParams["cursor"]) > 0 {
|
if len(queryParams["cursor"]) > 0 {
|
||||||
|
|
|
@ -9,6 +9,9 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/status-im/status-go/services/wallet/bigint"
|
"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"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
@ -20,8 +23,27 @@ const (
|
||||||
ExpectedExpiredKeyError = "invalid json: Expired API key"
|
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) {
|
func TestFetchAllCollectionsByOwner(t *testing.T) {
|
||||||
expected := []OwnedCollection{{
|
expectedOS := []OwnedCollection{{
|
||||||
Collection: Collection{
|
Collection: Collection{
|
||||||
Name: "Rocky",
|
Name: "Rocky",
|
||||||
Slug: "rocky",
|
Slug: "rocky",
|
||||||
|
@ -29,7 +51,7 @@ func TestFetchAllCollectionsByOwner(t *testing.T) {
|
||||||
},
|
},
|
||||||
OwnedAssetCount: &bigint.BigInt{Int: big.NewInt(1)},
|
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) {
|
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)
|
||||||
|
@ -39,15 +61,9 @@ func TestFetchAllCollectionsByOwner(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
client := &HTTPClient{
|
opensea := initTestClient(srv)
|
||||||
client: srv.Client(),
|
res, err := opensea.FetchAllCollectionsByOwner(walletCommon.ChainID(1), common.Address{1})
|
||||||
}
|
assert.Equal(t, expectedOS, res)
|
||||||
opensea := &Client{
|
|
||||||
client: client,
|
|
||||||
url: srv.URL,
|
|
||||||
}
|
|
||||||
res, err := opensea.FetchAllCollectionsByOwner(common.Address{1})
|
|
||||||
assert.Equal(t, expected, res)
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,34 +77,56 @@ func TestFetchAllCollectionsByOwnerWithInValidJson(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
client := &HTTPClient{
|
opensea := initTestClient(srv)
|
||||||
client: srv.Client(),
|
res, err := opensea.FetchAllCollectionsByOwner(walletCommon.ChainID(1), common.Address{1})
|
||||||
}
|
|
||||||
opensea := &Client{
|
|
||||||
client: client,
|
|
||||||
url: srv.URL,
|
|
||||||
}
|
|
||||||
res, err := opensea.FetchAllCollectionsByOwner(common.Address{1})
|
|
||||||
assert.Nil(t, res)
|
assert.Nil(t, res)
|
||||||
assert.Equal(t, err, fmt.Errorf(ExpectedExpiredKeyError))
|
assert.Equal(t, err, fmt.Errorf(ExpectedExpiredKeyError))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetchAllAssetsByOwnerAndCollection(t *testing.T) {
|
func TestFetchAllAssetsByOwnerAndCollection(t *testing.T) {
|
||||||
expected := AssetContainer{
|
expectedOS := AssetContainer{
|
||||||
Assets: []Asset{{
|
Assets: []Asset{{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
|
TokenID: &bigint.BigInt{Int: big.NewInt(1)},
|
||||||
Name: "Rocky",
|
Name: "Rocky",
|
||||||
Description: "Rocky Balboa",
|
Description: "Rocky Balboa",
|
||||||
Permalink: "permalink",
|
Permalink: "permalink",
|
||||||
ImageThumbnailURL: "ImageThumbnailURL",
|
ImageThumbnailURL: "ImageThumbnailURL",
|
||||||
ImageURL: "ImageUrl",
|
ImageURL: "ImageUrl",
|
||||||
Contract: Contract{Address: "1"},
|
Contract: Contract{
|
||||||
Collection: Collection{Name: "Rocky"},
|
Address: "1",
|
||||||
|
ChainIdentifier: "ethereum",
|
||||||
|
},
|
||||||
|
Collection: Collection{
|
||||||
|
Name: "Rocky",
|
||||||
|
Traits: map[string]CollectionTrait{},
|
||||||
|
},
|
||||||
|
Traits: []Trait{},
|
||||||
}},
|
}},
|
||||||
NextCursor: "",
|
NextCursor: "",
|
||||||
PreviousCursor: "",
|
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) {
|
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)
|
||||||
|
@ -98,16 +136,10 @@ func TestFetchAllAssetsByOwnerAndCollection(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
client := &HTTPClient{
|
opensea := initTestClient(srv)
|
||||||
client: srv.Client(),
|
res, err := opensea.FetchAllAssetsByOwnerAndCollection(walletCommon.ChainID(1), common.Address{1}, "rocky", "", 200)
|
||||||
}
|
|
||||||
opensea := &Client{
|
|
||||||
client: client,
|
|
||||||
url: srv.URL,
|
|
||||||
}
|
|
||||||
res, err := opensea.FetchAllAssetsByOwnerAndCollection(common.Address{1}, "rocky", "", 200)
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, expected, *res)
|
assert.Equal(t, expectedCommon, *res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetchAllAssetsByOwnerAndCollectionInvalidJson(t *testing.T) {
|
func TestFetchAllAssetsByOwnerAndCollectionInvalidJson(t *testing.T) {
|
||||||
|
@ -120,14 +152,8 @@ func TestFetchAllAssetsByOwnerAndCollectionInvalidJson(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
client := &HTTPClient{
|
opensea := initTestClient(srv)
|
||||||
client: srv.Client(),
|
res, err := opensea.FetchAllAssetsByOwnerAndCollection(walletCommon.ChainID(1), common.Address{1}, "rocky", "", 200)
|
||||||
}
|
|
||||||
opensea := &Client{
|
|
||||||
client: client,
|
|
||||||
url: srv.URL,
|
|
||||||
}
|
|
||||||
res, err := opensea.FetchAllAssetsByOwnerAndCollection(common.Address{1}, "rocky", "", 200)
|
|
||||||
assert.Nil(t, res)
|
assert.Nil(t, res)
|
||||||
assert.Equal(t, fmt.Errorf(ExpectedExpiredKeyError), err)
|
assert.Equal(t, fmt.Errorf(ExpectedExpiredKeyError), err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue