feat: implement opensea v2 endpoints
This commit is contained in:
parent
3734f03645
commit
ad0e2c2450
|
@ -105,7 +105,9 @@ func NewService(
|
||||||
currency := currency.NewService(db, walletFeed, tokenManager, marketManager)
|
currency := currency.NewService(db, walletFeed, tokenManager, marketManager)
|
||||||
activity := activity.NewService(db, tokenManager, walletFeed, accountsDB)
|
activity := activity.NewService(db, tokenManager, walletFeed, accountsDB)
|
||||||
|
|
||||||
openseaClient := opensea.NewClient(config.WalletConfig.OpenseaAPIKey, walletFeed)
|
openseaHTTPClient := opensea.NewHTTPClient()
|
||||||
|
openseaClient := opensea.NewClient(config.WalletConfig.OpenseaAPIKey, openseaHTTPClient, walletFeed)
|
||||||
|
openseaV2Client := opensea.NewClientV2(config.WalletConfig.OpenseaAPIKey, openseaHTTPClient, walletFeed)
|
||||||
infuraClient := infura.NewClient(config.WalletConfig.InfuraAPIKey, config.WalletConfig.InfuraAPIKeySecret)
|
infuraClient := infura.NewClient(config.WalletConfig.InfuraAPIKey, config.WalletConfig.InfuraAPIKeySecret)
|
||||||
alchemyClient := alchemy.NewClient(config.WalletConfig.AlchemyAPIKeys)
|
alchemyClient := alchemy.NewClient(config.WalletConfig.AlchemyAPIKeys)
|
||||||
|
|
||||||
|
@ -117,12 +119,14 @@ func NewService(
|
||||||
|
|
||||||
accountOwnershipProviders := []thirdparty.CollectibleAccountOwnershipProvider{
|
accountOwnershipProviders := []thirdparty.CollectibleAccountOwnershipProvider{
|
||||||
openseaClient,
|
openseaClient,
|
||||||
|
openseaV2Client,
|
||||||
infuraClient,
|
infuraClient,
|
||||||
alchemyClient,
|
alchemyClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
collectibleDataProviders := []thirdparty.CollectibleDataProvider{
|
collectibleDataProviders := []thirdparty.CollectibleDataProvider{
|
||||||
openseaClient,
|
openseaClient,
|
||||||
|
openseaV2Client,
|
||||||
infuraClient,
|
infuraClient,
|
||||||
alchemyClient,
|
alchemyClient,
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
@ -18,26 +17,20 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
EventCollectibleStatusChanged walletevent.EventType = "wallet-collectible-opensea-v1-status-changed"
|
EventOpenseaV1StatusChanged walletevent.EventType = "wallet-collectible-opensea-v1-status-changed"
|
||||||
)
|
)
|
||||||
|
|
||||||
const AssetLimit = 200
|
const AssetLimit = 200
|
||||||
const CollectionLimit = 300
|
const CollectionLimit = 300
|
||||||
|
|
||||||
const RequestTimeout = 5 * time.Second
|
|
||||||
const GetRequestRetryMaxCount = 15
|
|
||||||
const GetRequestWaitTime = 300 * time.Millisecond
|
|
||||||
|
|
||||||
const ChainIDRequiringAPIKey = walletCommon.EthereumMainnet
|
const ChainIDRequiringAPIKey = walletCommon.EthereumMainnet
|
||||||
|
|
||||||
type urlGetter func(walletCommon.ChainID, string) (string, error)
|
|
||||||
|
|
||||||
func getBaseURL(chainID walletCommon.ChainID) (string, error) {
|
func getBaseURL(chainID walletCommon.ChainID) (string, error) {
|
||||||
// v1 Endpoints only support L1 chain
|
// v1 Endpoints only support L1 chain
|
||||||
switch uint64(chainID) {
|
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.EthereumSepolia:
|
||||||
return "https://testnets-api.opensea.io/api/v1", nil
|
return "https://testnets-api.opensea.io/api/v1", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,12 +62,12 @@ type Client struct {
|
||||||
urlGetter urlGetter
|
urlGetter urlGetter
|
||||||
}
|
}
|
||||||
|
|
||||||
// new opensea client.
|
// new opensea v1 client.
|
||||||
func NewClient(apiKey string, feed *event.Feed) *Client {
|
func NewClient(apiKey string, httpClient *HTTPClient, feed *event.Feed) *Client {
|
||||||
return &Client{
|
return &Client{
|
||||||
client: newHTTPClient(),
|
client: httpClient,
|
||||||
apiKey: apiKey,
|
apiKey: apiKey,
|
||||||
connectionStatus: connection.NewStatus(EventCollectibleStatusChanged, feed),
|
connectionStatus: connection.NewStatus(EventOpenseaV1StatusChanged, feed),
|
||||||
urlGetter: getURL,
|
urlGetter: getURL,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,220 @@
|
||||||
|
package opensea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
|
||||||
|
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/status-im/status-go/services/wallet/walletevent"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EventOpenseaV2StatusChanged walletevent.EventType = "wallet-collectible-opensea-v2-status-changed"
|
||||||
|
)
|
||||||
|
|
||||||
|
const assetLimitV2 = 50
|
||||||
|
|
||||||
|
func getV2BaseURL(chainID walletCommon.ChainID) (string, error) {
|
||||||
|
switch uint64(chainID) {
|
||||||
|
case walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet, walletCommon.OptimismMainnet:
|
||||||
|
return "https://api.opensea.io/api/v2", nil
|
||||||
|
case walletCommon.EthereumGoerli, walletCommon.EthereumSepolia, walletCommon.ArbitrumGoerli, walletCommon.OptimismGoerli:
|
||||||
|
return "https://testnets-api.opensea.io/api/v2", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", thirdparty.ErrChainIDNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ClientV2) ID() string {
|
||||||
|
return OpenseaV2ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ClientV2) IsChainSupported(chainID walletCommon.ChainID) bool {
|
||||||
|
_, err := getV2BaseURL(chainID)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getV2URL(chainID walletCommon.ChainID, path string) (string, error) {
|
||||||
|
baseURL, err := getV2BaseURL(chainID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s/%s", baseURL, path), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientV2 struct {
|
||||||
|
client *HTTPClient
|
||||||
|
apiKey string
|
||||||
|
connectionStatus *connection.Status
|
||||||
|
urlGetter urlGetter
|
||||||
|
}
|
||||||
|
|
||||||
|
// new opensea v2 client.
|
||||||
|
func NewClientV2(apiKey string, httpClient *HTTPClient, feed *event.Feed) *ClientV2 {
|
||||||
|
return &ClientV2{
|
||||||
|
client: httpClient,
|
||||||
|
apiKey: apiKey,
|
||||||
|
connectionStatus: connection.NewStatus(EventOpenseaV2StatusChanged, feed),
|
||||||
|
urlGetter: getV2URL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ClientV2) FetchAllAssetsByOwnerAndContractAddress(chainID walletCommon.ChainID, owner common.Address, contractAddresses []common.Address, cursor string, limit int) (*thirdparty.FullCollectibleDataContainer, error) {
|
||||||
|
// No dedicated endpoint to filter owned assets by contract address.
|
||||||
|
// Will probably be available at some point, for now do the filtering ourselves.
|
||||||
|
assets := new(thirdparty.FullCollectibleDataContainer)
|
||||||
|
|
||||||
|
// Build map for more efficient contract address check
|
||||||
|
contractHashMap := make(map[string]bool)
|
||||||
|
for _, contractAddress := range contractAddresses {
|
||||||
|
contractID := thirdparty.ContractID{
|
||||||
|
ChainID: chainID,
|
||||||
|
Address: contractAddress,
|
||||||
|
}
|
||||||
|
contractHashMap[contractID.HashKey()] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
assets.PreviousCursor = cursor
|
||||||
|
|
||||||
|
for {
|
||||||
|
assetsPage, err := o.FetchAllAssetsByOwner(chainID, owner, cursor, assetLimitV2)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, asset := range assetsPage.Items {
|
||||||
|
if contractHashMap[asset.CollectibleData.ID.ContractID.HashKey()] {
|
||||||
|
assets.Items = append(assets.Items, asset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assets.NextCursor = assetsPage.NextCursor
|
||||||
|
|
||||||
|
if assets.NextCursor == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if limit > thirdparty.FetchNoLimit && len(assets.Items) >= limit {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return assets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ClientV2) FetchAllAssetsByOwner(chainID walletCommon.ChainID, owner common.Address, cursor string, limit int) (*thirdparty.FullCollectibleDataContainer, error) {
|
||||||
|
pathParams := []string{
|
||||||
|
"chain", chainIDToChainString(chainID),
|
||||||
|
"account", owner.String(),
|
||||||
|
"nfts",
|
||||||
|
}
|
||||||
|
|
||||||
|
queryParams := url.Values{}
|
||||||
|
|
||||||
|
return o.fetchAssets(chainID, pathParams, queryParams, limit, cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ClientV2) FetchAssetsByCollectibleUniqueID(uniqueIDs []thirdparty.CollectibleUniqueID) ([]thirdparty.FullCollectibleData, error) {
|
||||||
|
return o.fetchDetailedAssets(uniqueIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ClientV2) fetchAssets(chainID walletCommon.ChainID, pathParams []string, queryParams url.Values, limit int, cursor string) (*thirdparty.FullCollectibleDataContainer, error) {
|
||||||
|
assets := new(thirdparty.FullCollectibleDataContainer)
|
||||||
|
|
||||||
|
tmpLimit := AssetLimit
|
||||||
|
if limit > thirdparty.FetchNoLimit && limit < tmpLimit {
|
||||||
|
tmpLimit = limit
|
||||||
|
}
|
||||||
|
queryParams["limit"] = []string{strconv.Itoa(tmpLimit)}
|
||||||
|
|
||||||
|
assets.PreviousCursor = cursor
|
||||||
|
if cursor != "" {
|
||||||
|
queryParams["next"] = []string{cursor}
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
path := fmt.Sprintf("%s?%s", strings.Join(pathParams, "/"), 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 := NFTContainer{}
|
||||||
|
err = json.Unmarshal(body, &container)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, asset := range container.NFTs {
|
||||||
|
assets.Items = append(assets.Items, asset.toCommon(chainID))
|
||||||
|
}
|
||||||
|
assets.NextCursor = container.NextCursor
|
||||||
|
|
||||||
|
if assets.NextCursor == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
queryParams["next"] = []string{assets.NextCursor}
|
||||||
|
|
||||||
|
if limit > thirdparty.FetchNoLimit && len(assets.Items) >= limit {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return assets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ClientV2) fetchDetailedAssets(uniqueIDs []thirdparty.CollectibleUniqueID) ([]thirdparty.FullCollectibleData, error) {
|
||||||
|
assets := make([]thirdparty.FullCollectibleData, 0, len(uniqueIDs))
|
||||||
|
|
||||||
|
for _, id := range uniqueIDs {
|
||||||
|
path := fmt.Sprintf("chain/%s/contract/%s/nfts/%s", chainIDToChainString(id.ContractID.ChainID), id.ContractID.Address.String(), id.TokenID.String())
|
||||||
|
url, err := o.urlGetter(id.ContractID.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))
|
||||||
|
}
|
||||||
|
|
||||||
|
nft := DetailedNFT{}
|
||||||
|
err = json.Unmarshal(body, &nft)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
assets = append(assets, nft.toCommon(id.ContractID.ChainID))
|
||||||
|
}
|
||||||
|
|
||||||
|
return assets, nil
|
||||||
|
}
|
|
@ -10,15 +10,19 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const requestTimeout = 5 * time.Second
|
||||||
|
const getRequestRetryMaxCount = 15
|
||||||
|
const getRequestWaitTime = 300 * time.Millisecond
|
||||||
|
|
||||||
type HTTPClient struct {
|
type HTTPClient struct {
|
||||||
client *http.Client
|
client *http.Client
|
||||||
getRequestLock sync.RWMutex
|
getRequestLock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHTTPClient() *HTTPClient {
|
func NewHTTPClient() *HTTPClient {
|
||||||
return &HTTPClient{
|
return &HTTPClient{
|
||||||
client: &http.Client{
|
client: &http.Client{
|
||||||
Timeout: RequestTimeout,
|
Timeout: requestTimeout,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,9 +66,9 @@ func (o *HTTPClient) doGetRequest(url string, apiKey string) ([]byte, error) {
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
return body, err
|
return body, err
|
||||||
case http.StatusTooManyRequests:
|
case http.StatusTooManyRequests:
|
||||||
if retryCount < GetRequestRetryMaxCount {
|
if retryCount < getRequestRetryMaxCount {
|
||||||
// sleep and retry
|
// sleep and retry
|
||||||
time.Sleep(GetRequestWaitTime)
|
time.Sleep(getRequestWaitTime)
|
||||||
retryCount++
|
retryCount++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -74,7 +78,7 @@ func (o *HTTPClient) doGetRequest(url string, apiKey string) ([]byte, error) {
|
||||||
if tmpAPIKey == "" && apiKey != "" {
|
if tmpAPIKey == "" && apiKey != "" {
|
||||||
tmpAPIKey = apiKey
|
tmpAPIKey = apiKey
|
||||||
// sleep and retry
|
// sleep and retry
|
||||||
time.Sleep(GetRequestWaitTime)
|
time.Sleep(getRequestWaitTime)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// break and error
|
// break and error
|
||||||
|
|
|
@ -17,24 +17,7 @@ import (
|
||||||
|
|
||||||
const OpenseaV1ID = "openseaV1"
|
const OpenseaV1ID = "openseaV1"
|
||||||
|
|
||||||
func chainStringToChainID(chainString string) walletCommon.ChainID {
|
type urlGetter func(walletCommon.ChainID, string) (string, error)
|
||||||
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
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
package opensea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
OpenseaV2ID = "openseaV2"
|
||||||
|
ethereumMainnetString = "ethereum"
|
||||||
|
arbitrumMainnetString = "arbitrum"
|
||||||
|
optimismMainnetString = "optimism"
|
||||||
|
ethereumGoerliString = "goerli"
|
||||||
|
arbitrumGoerliString = "arbitrum_goerli"
|
||||||
|
optimismGoerliString = "optimism_goerli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func chainStringToChainID(chainString string) walletCommon.ChainID {
|
||||||
|
chainID := walletCommon.UnknownChainID
|
||||||
|
switch chainString {
|
||||||
|
case ethereumMainnetString:
|
||||||
|
chainID = walletCommon.EthereumMainnet
|
||||||
|
case arbitrumMainnetString:
|
||||||
|
chainID = walletCommon.ArbitrumMainnet
|
||||||
|
case optimismMainnetString:
|
||||||
|
chainID = walletCommon.OptimismMainnet
|
||||||
|
case ethereumGoerliString:
|
||||||
|
chainID = walletCommon.EthereumGoerli
|
||||||
|
case arbitrumGoerliString:
|
||||||
|
chainID = walletCommon.ArbitrumGoerli
|
||||||
|
case optimismGoerliString:
|
||||||
|
chainID = walletCommon.OptimismGoerli
|
||||||
|
}
|
||||||
|
return walletCommon.ChainID(chainID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func chainIDToChainString(chainID walletCommon.ChainID) string {
|
||||||
|
chainString := ""
|
||||||
|
switch uint64(chainID) {
|
||||||
|
case walletCommon.EthereumMainnet:
|
||||||
|
chainString = ethereumMainnetString
|
||||||
|
case walletCommon.ArbitrumMainnet:
|
||||||
|
chainString = arbitrumMainnetString
|
||||||
|
case walletCommon.OptimismMainnet:
|
||||||
|
chainString = optimismMainnetString
|
||||||
|
case walletCommon.EthereumGoerli:
|
||||||
|
chainString = ethereumGoerliString
|
||||||
|
case walletCommon.ArbitrumGoerli:
|
||||||
|
chainString = arbitrumGoerliString
|
||||||
|
case walletCommon.OptimismGoerli:
|
||||||
|
chainString = optimismGoerliString
|
||||||
|
}
|
||||||
|
return chainString
|
||||||
|
}
|
||||||
|
|
||||||
|
type NFTContainer struct {
|
||||||
|
NFTs []NFT `json:"nfts"`
|
||||||
|
NextCursor string `json:"next"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NFT struct {
|
||||||
|
TokenID *bigint.BigInt `json:"identifier"`
|
||||||
|
Collection string `json:"collection"`
|
||||||
|
Contract common.Address `json:"contract"`
|
||||||
|
TokenStandard string `json:"token_standard"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
ImageURL string `json:"image_url"`
|
||||||
|
MetadataURL string `json:"metadata_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DetailedNFT struct {
|
||||||
|
NFT
|
||||||
|
Owners []OwnerV2 `json:"owners"`
|
||||||
|
Traits []TraitV2 `json:"traits"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OwnerV2 struct {
|
||||||
|
Address common.Address `json:"address"`
|
||||||
|
Quantity *bigint.BigInt `json:"quantity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TraitV2 struct {
|
||||||
|
TraitType string `json:"trait_type"`
|
||||||
|
DisplayType string `json:"display_type"`
|
||||||
|
MaxValue string `json:"max_value"`
|
||||||
|
TraitCount int `json:"trait_count"`
|
||||||
|
Order string `json:"order"`
|
||||||
|
Value TraitValue `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *NFT) id(chainID walletCommon.ChainID) thirdparty.CollectibleUniqueID {
|
||||||
|
return thirdparty.CollectibleUniqueID{
|
||||||
|
ContractID: thirdparty.ContractID{
|
||||||
|
ChainID: chainID,
|
||||||
|
Address: c.Contract,
|
||||||
|
},
|
||||||
|
TokenID: c.TokenID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func openseaV2ToCollectibleTraits(traits []TraitV2) []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 *NFT) toCollectiblesData(chainID walletCommon.ChainID) thirdparty.CollectibleData {
|
||||||
|
return thirdparty.CollectibleData{
|
||||||
|
ID: c.id(chainID),
|
||||||
|
Provider: OpenseaV2ID,
|
||||||
|
Name: c.Name,
|
||||||
|
Description: c.Description,
|
||||||
|
ImageURL: c.ImageURL,
|
||||||
|
AnimationURL: c.ImageURL,
|
||||||
|
Traits: make([]thirdparty.CollectibleTrait, 0),
|
||||||
|
TokenURI: c.MetadataURL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *NFT) toCommon(chainID walletCommon.ChainID) thirdparty.FullCollectibleData {
|
||||||
|
return thirdparty.FullCollectibleData{
|
||||||
|
CollectibleData: c.toCollectiblesData(chainID),
|
||||||
|
CollectionData: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DetailedNFT) toCommon(chainID walletCommon.ChainID) thirdparty.FullCollectibleData {
|
||||||
|
fullData := c.NFT.toCommon(chainID)
|
||||||
|
fullData.CollectibleData.Traits = openseaV2ToCollectibleTraits(c.Traits)
|
||||||
|
|
||||||
|
return thirdparty.FullCollectibleData{
|
||||||
|
CollectibleData: c.toCollectiblesData(chainID),
|
||||||
|
CollectionData: nil,
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue