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 {
|
||||
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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package common
|
|||
type ChainID uint64
|
||||
|
||||
const (
|
||||
UnknownChainID uint64 = 0
|
||||
EthereumMainnet uint64 = 1
|
||||
EthereumGoerli uint64 = 5
|
||||
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/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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue