feat: fetch NFT metadata from Communities
This commit is contained in:
parent
fae7e8dba5
commit
a1e7eed141
|
@ -473,10 +473,17 @@ func (b *StatusNode) appmetricsService() common.StatusService {
|
||||||
|
|
||||||
func (b *StatusNode) walletService(accountsDB *accounts.Database, accountsFeed *event.Feed, openseaAPIKey string) common.StatusService {
|
func (b *StatusNode) walletService(accountsDB *accounts.Database, accountsFeed *event.Feed, openseaAPIKey string) common.StatusService {
|
||||||
if b.walletSrvc == nil {
|
if b.walletSrvc == nil {
|
||||||
|
var extService *ext.Service
|
||||||
|
if b.WakuV2ExtService() != nil {
|
||||||
|
extService = b.WakuV2ExtService().Service
|
||||||
|
} else if b.WakuExtService() != nil {
|
||||||
|
extService = b.WakuExtService().Service
|
||||||
|
}
|
||||||
b.walletSrvc = wallet.NewService(
|
b.walletSrvc = wallet.NewService(
|
||||||
b.appDB, accountsDB, b.rpcClient, accountsFeed, openseaAPIKey, b.gethAccountManager, b.transactor, b.config,
|
b.appDB, accountsDB, b.rpcClient, accountsFeed, openseaAPIKey, b.gethAccountManager, b.transactor, b.config,
|
||||||
b.ensService(),
|
b.ensService(),
|
||||||
b.stickersService(accountsDB),
|
b.stickersService(accountsDB),
|
||||||
|
extService,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return b.walletSrvc
|
return b.walletSrvc
|
||||||
|
|
|
@ -6,9 +6,11 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/syndtr/goleveldb/leveldb"
|
"github.com/syndtr/goleveldb/leveldb"
|
||||||
|
@ -24,6 +26,7 @@ import (
|
||||||
gethrpc "github.com/ethereum/go-ethereum/rpc"
|
gethrpc "github.com/ethereum/go-ethereum/rpc"
|
||||||
|
|
||||||
"github.com/status-im/status-go/account"
|
"github.com/status-im/status-go/account"
|
||||||
|
"github.com/status-im/status-go/api/multiformat"
|
||||||
"github.com/status-im/status-go/connection"
|
"github.com/status-im/status-go/connection"
|
||||||
"github.com/status-im/status-go/db"
|
"github.com/status-im/status-go/db"
|
||||||
coretypes "github.com/status-im/status-go/eth-node/core/types"
|
coretypes "github.com/status-im/status-go/eth-node/core/types"
|
||||||
|
@ -34,6 +37,7 @@ import (
|
||||||
"github.com/status-im/status-go/params"
|
"github.com/status-im/status-go/params"
|
||||||
"github.com/status-im/status-go/protocol"
|
"github.com/status-im/status-go/protocol"
|
||||||
"github.com/status-im/status-go/protocol/anonmetrics"
|
"github.com/status-im/status-go/protocol/anonmetrics"
|
||||||
|
"github.com/status-im/status-go/protocol/common"
|
||||||
"github.com/status-im/status-go/protocol/pushnotificationclient"
|
"github.com/status-im/status-go/protocol/pushnotificationclient"
|
||||||
"github.com/status-im/status-go/protocol/pushnotificationserver"
|
"github.com/status-im/status-go/protocol/pushnotificationserver"
|
||||||
"github.com/status-im/status-go/protocol/transport"
|
"github.com/status-im/status-go/protocol/transport"
|
||||||
|
@ -43,6 +47,7 @@ import (
|
||||||
"github.com/status-im/status-go/services/ext/mailservers"
|
"github.com/status-im/status-go/services/ext/mailservers"
|
||||||
localnotifications "github.com/status-im/status-go/services/local-notifications"
|
localnotifications "github.com/status-im/status-go/services/local-notifications"
|
||||||
mailserversDB "github.com/status-im/status-go/services/mailservers"
|
mailserversDB "github.com/status-im/status-go/services/mailservers"
|
||||||
|
"github.com/status-im/status-go/services/wallet/thirdparty"
|
||||||
"github.com/status-im/status-go/services/wallet/transfer"
|
"github.com/status-im/status-go/services/wallet/transfer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -56,6 +61,7 @@ type EnvelopeEventsHandler interface {
|
||||||
|
|
||||||
// Service is a service that provides some additional API to whisper-based protocols like Whisper or Waku.
|
// Service is a service that provides some additional API to whisper-based protocols like Whisper or Waku.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
|
thirdparty.NFTMetadataProvider
|
||||||
messenger *protocol.Messenger
|
messenger *protocol.Messenger
|
||||||
identity *ecdsa.PrivateKey
|
identity *ecdsa.PrivateKey
|
||||||
cancelMessenger chan struct{}
|
cancelMessenger chan struct{}
|
||||||
|
@ -507,3 +513,68 @@ func (s *Service) ConnectionChanged(state connection.State) {
|
||||||
func (s *Service) Messenger() *protocol.Messenger {
|
func (s *Service) Messenger() *protocol.Messenger {
|
||||||
return s.messenger
|
return s.messenger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tokenURIToCommunityID(tokenURI string) string {
|
||||||
|
tmpStr := strings.Split(tokenURI, "/")
|
||||||
|
|
||||||
|
// Community NFTs have a tokenURI of the form "compressedCommunityID/tokenID"
|
||||||
|
if len(tmpStr) != 2 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
compressedCommunityID := tmpStr[0]
|
||||||
|
|
||||||
|
hexCommunityID, err := multiformat.DeserializeCompressedKey(compressedCommunityID)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKey, err := common.HexToPubkey(hexCommunityID)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
communityID := types.EncodeHex(crypto.CompressPubkey(pubKey))
|
||||||
|
|
||||||
|
return communityID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) CanProvideNFTMetadata(chainID uint64, id thirdparty.NFTUniqueID, tokenURI string) (bool, error) {
|
||||||
|
ret := tokenURI != "" && tokenURIToCommunityID(tokenURI) != ""
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) FetchNFTMetadata(chainID uint64, id thirdparty.NFTUniqueID, tokenURI string) (*thirdparty.NFTMetadata, error) {
|
||||||
|
if s.messenger == nil {
|
||||||
|
return nil, fmt.Errorf("messenger not ready")
|
||||||
|
}
|
||||||
|
|
||||||
|
communityID := tokenURIToCommunityID(tokenURI)
|
||||||
|
|
||||||
|
if communityID == "" {
|
||||||
|
return nil, fmt.Errorf("invalid tokenURI")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to fetch metadata from Messenger communities
|
||||||
|
community, err := s.messenger.RequestCommunityInfoFromMailserver(communityID, true)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if community != nil {
|
||||||
|
tokensMetadata := community.CommunityTokensMetadata()
|
||||||
|
|
||||||
|
for _, tokenMetadata := range tokensMetadata {
|
||||||
|
contractAddresses := tokenMetadata.GetContractAddresses()
|
||||||
|
if contractAddresses[chainID] == id.ContractAddress.Hex() {
|
||||||
|
return &thirdparty.NFTMetadata{
|
||||||
|
Name: tokenMetadata.GetName(),
|
||||||
|
Description: tokenMetadata.GetDescription(),
|
||||||
|
ImageURL: tokenMetadata.GetImage(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
|
@ -295,12 +295,7 @@ func (api *API) GetCryptoOnRamps(ctx context.Context) ([]CryptoOnRamp, error) {
|
||||||
|
|
||||||
func (api *API) GetOpenseaCollectionsByOwner(ctx context.Context, chainID uint64, owner common.Address) ([]opensea.OwnedCollection, error) {
|
func (api *API) GetOpenseaCollectionsByOwner(ctx context.Context, chainID uint64, owner common.Address) ([]opensea.OwnedCollection, error) {
|
||||||
log.Debug("call to get opensea collections")
|
log.Debug("call to get opensea collections")
|
||||||
client, err := opensea.NewOpenseaClient(chainID, api.s.openseaAPIKey)
|
return api.s.collectiblesManager.FetchAllCollectionsByOwner(chainID, owner)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.FetchAllCollectionsByOwner(owner)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kept for compatibility with mobile app
|
// Kept for compatibility with mobile app
|
||||||
|
@ -314,43 +309,22 @@ func (api *API) GetOpenseaAssetsByOwnerAndCollection(ctx context.Context, chainI
|
||||||
|
|
||||||
func (api *API) GetOpenseaAssetsByOwnerAndCollectionWithCursor(ctx context.Context, chainID uint64, owner common.Address, collectionSlug string, cursor string, limit int) (*opensea.AssetContainer, error) {
|
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")
|
log.Debug("call to get opensea assets")
|
||||||
client, err := opensea.NewOpenseaClient(chainID, api.s.openseaAPIKey)
|
return api.s.collectiblesManager.FetchAllAssetsByOwnerAndCollection(chainID, owner, collectionSlug, cursor, limit)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.FetchAllAssetsByOwnerAndCollection(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) GetOpenseaAssetsByOwnerWithCursor(ctx context.Context, chainID uint64, owner common.Address, cursor string, limit int) (*opensea.AssetContainer, error) {
|
||||||
log.Debug("call to FetchAllAssetsByOwner")
|
log.Debug("call to FetchAllAssetsByOwner")
|
||||||
client, err := opensea.NewOpenseaClient(chainID, api.s.openseaAPIKey)
|
return api.s.collectiblesManager.FetchAllAssetsByOwner(chainID, owner, cursor, limit)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.FetchAllAssetsByOwner(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) GetOpenseaAssetsByOwnerAndContractAddressWithCursor(ctx context.Context, chainID uint64, owner common.Address, contractAddresses []common.Address, cursor string, limit int) (*opensea.AssetContainer, error) {
|
||||||
log.Debug("call to GetOpenseaAssetsByOwnerAndContractAddressWithCursor")
|
log.Debug("call to GetOpenseaAssetsByOwnerAndContractAddressWithCursor")
|
||||||
client, err := opensea.NewOpenseaClient(chainID, api.s.openseaAPIKey)
|
return api.s.collectiblesManager.FetchAllAssetsByOwnerAndContractAddress(chainID, owner, contractAddresses, cursor, limit)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.FetchAllAssetsByOwnerAndContractAddress(owner, contractAddresses, cursor, limit)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) GetOpenseaAssetsByNFTUniqueID(ctx context.Context, chainID uint64, uniqueIDs []opensea.NFTUniqueID, limit int) (*opensea.AssetContainer, error) {
|
func (api *API) GetOpenseaAssetsByNFTUniqueID(ctx context.Context, chainID uint64, uniqueIDs []thirdparty.NFTUniqueID, limit int) (*opensea.AssetContainer, error) {
|
||||||
log.Debug("call to GetOpenseaAssetsByNFTUniqueID")
|
log.Debug("call to GetOpenseaAssetsByNFTUniqueID")
|
||||||
|
return api.s.collectiblesManager.FetchAssetsByNFTUniqueID(chainID, uniqueIDs, limit)
|
||||||
client, err := opensea.NewOpenseaClient(chainID, api.s.openseaAPIKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.FetchAssetsByNFTUniqueID(uniqueIDs, limit)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) AddEthereumChain(ctx context.Context, network params.Network) error {
|
func (api *API) AddEthereumChain(ctx context.Context, network params.Network) error {
|
||||||
|
|
|
@ -0,0 +1,223 @@
|
||||||
|
package collectibles
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/status-im/status-go/contracts/collectibles"
|
||||||
|
"github.com/status-im/status-go/rpc"
|
||||||
|
"github.com/status-im/status-go/services/wallet/thirdparty"
|
||||||
|
"github.com/status-im/status-go/services/wallet/thirdparty/opensea"
|
||||||
|
)
|
||||||
|
|
||||||
|
const requestTimeout = 5 * time.Second
|
||||||
|
|
||||||
|
func erc721MetadataInterfaceID() [4]byte {
|
||||||
|
return [...]byte{0x5b, 0x5e, 0x13, 0x9f} // 0x5b5e139f
|
||||||
|
}
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
rpcClient *rpc.Client
|
||||||
|
metadataProvider thirdparty.NFTMetadataProvider
|
||||||
|
openseaAPIKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager(rpcClient *rpc.Client, metadataProvider thirdparty.NFTMetadataProvider, openseaAPIKey string) *Manager {
|
||||||
|
return &Manager{
|
||||||
|
rpcClient: rpcClient,
|
||||||
|
metadataProvider: metadataProvider,
|
||||||
|
openseaAPIKey: openseaAPIKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Manager) FetchAllCollectionsByOwner(chainID uint64, owner common.Address) ([]opensea.OwnedCollection, error) {
|
||||||
|
client, err := opensea.NewOpenseaClient(chainID, o.openseaAPIKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return client.FetchAllCollectionsByOwner(owner)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Manager) FetchAllAssetsByOwnerAndCollection(chainID uint64, owner common.Address, collectionSlug string, cursor string, limit int) (*opensea.AssetContainer, error) {
|
||||||
|
client, err := opensea.NewOpenseaClient(chainID, o.openseaAPIKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
assetContainer, err := client.FetchAllAssetsByOwnerAndCollection(owner, collectionSlug, cursor, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = o.processAssets(chainID, assetContainer.Assets)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return assetContainer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Manager) FetchAllAssetsByOwnerAndContractAddress(chainID uint64, owner common.Address, contractAddresses []common.Address, cursor string, limit int) (*opensea.AssetContainer, error) {
|
||||||
|
client, err := opensea.NewOpenseaClient(chainID, o.openseaAPIKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
assetContainer, err := client.FetchAllAssetsByOwnerAndContractAddress(owner, contractAddresses, cursor, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = o.processAssets(chainID, assetContainer.Assets)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return assetContainer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Manager) FetchAllAssetsByOwner(chainID uint64, owner common.Address, cursor string, limit int) (*opensea.AssetContainer, error) {
|
||||||
|
client, err := opensea.NewOpenseaClient(chainID, o.openseaAPIKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
assetContainer, err := client.FetchAllAssetsByOwner(owner, cursor, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = o.processAssets(chainID, assetContainer.Assets)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return assetContainer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Manager) FetchAssetsByNFTUniqueID(chainID uint64, uniqueIDs []thirdparty.NFTUniqueID, limit int) (*opensea.AssetContainer, error) {
|
||||||
|
client, err := opensea.NewOpenseaClient(chainID, o.openseaAPIKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
assetContainer, err := client.FetchAssetsByNFTUniqueID(uniqueIDs, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = o.processAssets(chainID, assetContainer.Assets)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return assetContainer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isMetadataEmpty(asset opensea.Asset) bool {
|
||||||
|
return asset.Name == "" &&
|
||||||
|
asset.Description == "" &&
|
||||||
|
asset.ImageURL == "" &&
|
||||||
|
asset.TokenURI == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Manager) supportsERC721Metadata(chainID uint64, contractAddress common.Address) (bool, error) {
|
||||||
|
backend, err := o.rpcClient.EthClient(chainID)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
caller, err := collectibles.NewCollectiblesCaller(contractAddress, backend)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutContext, timeoutCancel := context.WithTimeout(context.Background(), requestTimeout)
|
||||||
|
defer timeoutCancel()
|
||||||
|
|
||||||
|
supports, err := caller.SupportsInterface(&bind.CallOpts{
|
||||||
|
Context: timeoutContext,
|
||||||
|
}, erc721MetadataInterfaceID())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if strings.HasPrefix(err.Error(), vm.ErrExecutionReverted.Error()) {
|
||||||
|
// Contract doesn't support "SupportsInterface"
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return supports, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Manager) fetchTokenURI(chainID uint64, id thirdparty.NFTUniqueID) (string, error) {
|
||||||
|
backend, err := o.rpcClient.EthClient(chainID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
caller, err := collectibles.NewCollectiblesCaller(id.ContractAddress, backend)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutContext, timeoutCancel := context.WithTimeout(context.Background(), requestTimeout)
|
||||||
|
defer timeoutCancel()
|
||||||
|
|
||||||
|
return caller.TokenURI(&bind.CallOpts{
|
||||||
|
Context: timeoutContext,
|
||||||
|
}, id.TokenID.Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Manager) processAssets(chainID uint64, assets []opensea.Asset) error {
|
||||||
|
for idx, asset := range assets {
|
||||||
|
if isMetadataEmpty(asset) {
|
||||||
|
id := thirdparty.NFTUniqueID{
|
||||||
|
ContractAddress: common.HexToAddress(asset.Contract.Address),
|
||||||
|
TokenID: asset.TokenID,
|
||||||
|
}
|
||||||
|
|
||||||
|
supportsERC721Metadata, err := o.supportsERC721Metadata(chainID, id.ContractAddress)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if supportsERC721Metadata {
|
||||||
|
tokenURI, err := o.fetchTokenURI(chainID, id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
assets[idx].TokenURI = tokenURI
|
||||||
|
|
||||||
|
canProvide, err := o.metadataProvider.CanProvideNFTMetadata(chainID, id, tokenURI)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if canProvide {
|
||||||
|
metadata, err := o.metadataProvider.FetchNFTMetadata(chainID, 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.ImageURL
|
||||||
|
assets[idx].ImageURL = metadata.ImageURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -16,9 +16,11 @@ import (
|
||||||
"github.com/status-im/status-go/rpc"
|
"github.com/status-im/status-go/rpc"
|
||||||
"github.com/status-im/status-go/services/ens"
|
"github.com/status-im/status-go/services/ens"
|
||||||
"github.com/status-im/status-go/services/stickers"
|
"github.com/status-im/status-go/services/stickers"
|
||||||
|
"github.com/status-im/status-go/services/wallet/collectibles"
|
||||||
"github.com/status-im/status-go/services/wallet/currency"
|
"github.com/status-im/status-go/services/wallet/currency"
|
||||||
"github.com/status-im/status-go/services/wallet/history"
|
"github.com/status-im/status-go/services/wallet/history"
|
||||||
"github.com/status-im/status-go/services/wallet/market"
|
"github.com/status-im/status-go/services/wallet/market"
|
||||||
|
"github.com/status-im/status-go/services/wallet/thirdparty"
|
||||||
"github.com/status-im/status-go/services/wallet/thirdparty/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/opensea"
|
"github.com/status-im/status-go/services/wallet/thirdparty/opensea"
|
||||||
|
@ -51,6 +53,7 @@ func NewService(
|
||||||
config *params.NodeConfig,
|
config *params.NodeConfig,
|
||||||
ens *ens.Service,
|
ens *ens.Service,
|
||||||
stickers *stickers.Service,
|
stickers *stickers.Service,
|
||||||
|
nftMetadataProvider thirdparty.NFTMetadataProvider,
|
||||||
) *Service {
|
) *Service {
|
||||||
cryptoOnRampManager := NewCryptoOnRampManager(&CryptoOnRampOptions{
|
cryptoOnRampManager := NewCryptoOnRampManager(&CryptoOnRampOptions{
|
||||||
dataSourceType: DataSourceStatic,
|
dataSourceType: DataSourceStatic,
|
||||||
|
@ -69,6 +72,7 @@ func NewService(
|
||||||
reader := NewReader(rpcClient, tokenManager, marketManager, accountsDB, walletFeed)
|
reader := NewReader(rpcClient, tokenManager, marketManager, accountsDB, walletFeed)
|
||||||
history := history.NewService(db, walletFeed, rpcClient, tokenManager, marketManager)
|
history := history.NewService(db, walletFeed, rpcClient, tokenManager, marketManager)
|
||||||
currency := currency.NewService(db, walletFeed, tokenManager, marketManager)
|
currency := currency.NewService(db, walletFeed, tokenManager, marketManager)
|
||||||
|
collectiblesManager := collectibles.NewManager(rpcClient, nftMetadataProvider, openseaAPIKey)
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
db: db,
|
||||||
accountsDB: accountsDB,
|
accountsDB: accountsDB,
|
||||||
|
@ -78,7 +82,7 @@ func NewService(
|
||||||
transactionManager: transactionManager,
|
transactionManager: transactionManager,
|
||||||
transferController: transferController,
|
transferController: transferController,
|
||||||
cryptoOnRampManager: cryptoOnRampManager,
|
cryptoOnRampManager: cryptoOnRampManager,
|
||||||
openseaAPIKey: openseaAPIKey,
|
collectiblesManager: collectiblesManager,
|
||||||
feesManager: &FeeManager{rpcClient},
|
feesManager: &FeeManager{rpcClient},
|
||||||
gethManager: gethManager,
|
gethManager: gethManager,
|
||||||
marketManager: marketManager,
|
marketManager: marketManager,
|
||||||
|
@ -106,7 +110,7 @@ type Service struct {
|
||||||
feesManager *FeeManager
|
feesManager *FeeManager
|
||||||
marketManager *market.Manager
|
marketManager *market.Manager
|
||||||
started bool
|
started bool
|
||||||
openseaAPIKey string
|
collectiblesManager *collectibles.Manager
|
||||||
gethManager *account.GethManager
|
gethManager *account.GethManager
|
||||||
transactor *transactions.Transactor
|
transactor *transactions.Transactor
|
||||||
ens *ens.Service
|
ens *ens.Service
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
|
||||||
"github.com/status-im/status-go/services/wallet/bigint"
|
"github.com/status-im/status-go/services/wallet/bigint"
|
||||||
|
"github.com/status-im/status-go/services/wallet/thirdparty"
|
||||||
)
|
)
|
||||||
|
|
||||||
const AssetLimit = 200
|
const AssetLimit = 200
|
||||||
|
@ -36,11 +37,6 @@ const ChainIDRequiringAPIKey = 1
|
||||||
|
|
||||||
type TraitValue string
|
type TraitValue string
|
||||||
|
|
||||||
type NFTUniqueID struct {
|
|
||||||
ContractAddress common.Address `json:"contractAddress"`
|
|
||||||
TokenID bigint.BigInt `json:"tokenID"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *TraitValue) UnmarshalJSON(b []byte) error {
|
func (st *TraitValue) UnmarshalJSON(b []byte) error {
|
||||||
var item interface{}
|
var item interface{}
|
||||||
if err := json.Unmarshal(b, &item); err != nil {
|
if err := json.Unmarshal(b, &item); err != nil {
|
||||||
|
@ -109,6 +105,7 @@ type Asset struct {
|
||||||
LastSale LastSale `json:"last_sale"`
|
LastSale LastSale `json:"last_sale"`
|
||||||
SellOrders []SellOrder `json:"sell_orders"`
|
SellOrders []SellOrder `json:"sell_orders"`
|
||||||
BackgroundColor string `json:"background_color"`
|
BackgroundColor string `json:"background_color"`
|
||||||
|
TokenURI string `json:"token_metadata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CollectionTrait struct {
|
type CollectionTrait struct {
|
||||||
|
@ -249,7 +246,7 @@ func (o *Client) FetchAllAssetsByOwner(owner common.Address, cursor string, limi
|
||||||
return o.fetchAssets(queryParams, limit)
|
return o.fetchAssets(queryParams, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Client) FetchAssetsByNFTUniqueID(uniqueIDs []NFTUniqueID, limit int) (*AssetContainer, error) {
|
func (o *Client) FetchAssetsByNFTUniqueID(uniqueIDs []thirdparty.NFTUniqueID, limit int) (*AssetContainer, error) {
|
||||||
queryParams := url.Values{}
|
queryParams := url.Values{}
|
||||||
|
|
||||||
for _, uniqueID := range uniqueIDs {
|
for _, uniqueID := range uniqueIDs {
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
package thirdparty
|
package thirdparty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/status-im/status-go/services/wallet/bigint"
|
||||||
|
)
|
||||||
|
|
||||||
type HistoricalPrice struct {
|
type HistoricalPrice struct {
|
||||||
Timestamp int64 `json:"time"`
|
Timestamp int64 `json:"time"`
|
||||||
Value float64 `json:"close"`
|
Value float64 `json:"close"`
|
||||||
|
@ -35,3 +40,19 @@ type MarketDataProvider interface {
|
||||||
FetchTokenMarketValues(symbols []string, currency string) (map[string]TokenMarketValues, error)
|
FetchTokenMarketValues(symbols []string, currency string) (map[string]TokenMarketValues, error)
|
||||||
FetchTokenDetails(symbols []string) (map[string]TokenDetails, error)
|
FetchTokenDetails(symbols []string) (map[string]TokenDetails, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NFTUniqueID struct {
|
||||||
|
ContractAddress common.Address `json:"contractAddress"`
|
||||||
|
TokenID *bigint.BigInt `json:"tokenID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NFTMetadata struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
ImageURL string `json:"image"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NFTMetadataProvider interface {
|
||||||
|
CanProvideNFTMetadata(chainID uint64, id NFTUniqueID, tokenURI string) (bool, error)
|
||||||
|
FetchNFTMetadata(chainID uint64, id NFTUniqueID, tokenURI string) (*NFTMetadata, error)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue