feat: removed soon-to-be-deprecated openseaV1 client
This commit is contained in:
parent
bc33f6d54c
commit
544e2ea76f
|
@ -23,7 +23,6 @@ import (
|
||||||
"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/thirdparty"
|
"github.com/status-im/status-go/services/wallet/thirdparty"
|
||||||
"github.com/status-im/status-go/services/wallet/thirdparty/opensea"
|
|
||||||
"github.com/status-im/status-go/services/wallet/token"
|
"github.com/status-im/status-go/services/wallet/token"
|
||||||
"github.com/status-im/status-go/services/wallet/transfer"
|
"github.com/status-im/status-go/services/wallet/transfer"
|
||||||
"github.com/status-im/status-go/services/wallet/walletevent"
|
"github.com/status-im/status-go/services/wallet/walletevent"
|
||||||
|
@ -361,34 +360,6 @@ func (api *API) GetCollectiblesDetailsAsync(requestID int32, uniqueIDs []thirdpa
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// @deprecated
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @deprecated
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @deprecated
|
|
||||||
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
|
|
||||||
}
|
|
||||||
return container.Assets, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// @deprecated
|
|
||||||
func (api *API) GetCollectiblesByOwnerAndCollectionWithCursor(ctx context.Context, chainID wcommon.ChainID, owner common.Address, collectionSlug string, cursor string, limit int) (*thirdparty.FullCollectibleDataContainer, error) {
|
|
||||||
log.Debug("call to GetCollectiblesByOwnerAndCollectionWithCursor")
|
|
||||||
return api.s.collectiblesManager.FetchAllAssetsByOwnerAndCollection(chainID, owner, collectionSlug, cursor, limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @deprecated
|
// @deprecated
|
||||||
func (api *API) GetCollectiblesByOwnerWithCursor(ctx context.Context, chainID wcommon.ChainID, owner common.Address, cursor string, limit int) (*thirdparty.FullCollectibleDataContainer, error) {
|
func (api *API) GetCollectiblesByOwnerWithCursor(ctx context.Context, chainID wcommon.ChainID, owner common.Address, cursor string, limit int) (*thirdparty.FullCollectibleDataContainer, error) {
|
||||||
log.Debug("call to GetCollectiblesByOwnerWithCursor")
|
log.Debug("call to GetCollectiblesByOwnerWithCursor")
|
||||||
|
|
|
@ -22,7 +22,6 @@ import (
|
||||||
walletCommon "github.com/status-im/status-go/services/wallet/common"
|
walletCommon "github.com/status-im/status-go/services/wallet/common"
|
||||||
"github.com/status-im/status-go/services/wallet/connection"
|
"github.com/status-im/status-go/services/wallet/connection"
|
||||||
"github.com/status-im/status-go/services/wallet/thirdparty"
|
"github.com/status-im/status-go/services/wallet/thirdparty"
|
||||||
"github.com/status-im/status-go/services/wallet/thirdparty/opensea"
|
|
||||||
"github.com/status-im/status-go/services/wallet/walletevent"
|
"github.com/status-im/status-go/services/wallet/walletevent"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -58,7 +57,6 @@ type Manager struct {
|
||||||
metadataProvider thirdparty.CollectibleMetadataProvider
|
metadataProvider thirdparty.CollectibleMetadataProvider
|
||||||
communityInfoProvider thirdparty.CollectibleCommunityInfoProvider
|
communityInfoProvider thirdparty.CollectibleCommunityInfoProvider
|
||||||
|
|
||||||
opensea *opensea.Client
|
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
|
|
||||||
collectiblesDataDB *CollectibleDataDB
|
collectiblesDataDB *CollectibleDataDB
|
||||||
|
@ -75,7 +73,6 @@ func NewManager(
|
||||||
accountOwnershipProviders []thirdparty.CollectibleAccountOwnershipProvider,
|
accountOwnershipProviders []thirdparty.CollectibleAccountOwnershipProvider,
|
||||||
collectibleDataProviders []thirdparty.CollectibleDataProvider,
|
collectibleDataProviders []thirdparty.CollectibleDataProvider,
|
||||||
collectionDataProviders []thirdparty.CollectionDataProvider,
|
collectionDataProviders []thirdparty.CollectionDataProvider,
|
||||||
opensea *opensea.Client,
|
|
||||||
feed *event.Feed) *Manager {
|
feed *event.Feed) *Manager {
|
||||||
hystrix.ConfigureCommand(hystrixContractOwnershipClientName, hystrix.CommandConfig{
|
hystrix.ConfigureCommand(hystrixContractOwnershipClientName, hystrix.CommandConfig{
|
||||||
Timeout: 10000,
|
Timeout: 10000,
|
||||||
|
@ -132,7 +129,6 @@ func NewManager(
|
||||||
collectibleDataProviders: collectibleDataProviders,
|
collectibleDataProviders: collectibleDataProviders,
|
||||||
collectionDataProviders: collectionDataProviders,
|
collectionDataProviders: collectionDataProviders,
|
||||||
collectibleProviders: collectibleProviders,
|
collectibleProviders: collectibleProviders,
|
||||||
opensea: opensea,
|
|
||||||
httpClient: &http.Client{
|
httpClient: &http.Client{
|
||||||
Timeout: requestTimeout,
|
Timeout: requestTimeout,
|
||||||
},
|
},
|
||||||
|
@ -208,28 +204,6 @@ func (o *Manager) SetCommunityInfoProvider(communityInfoProvider thirdparty.Coll
|
||||||
o.communityInfoProvider = communityInfoProvider
|
o.communityInfoProvider = communityInfoProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Manager) FetchAllCollectionsByOwner(chainID walletCommon.ChainID, owner common.Address) ([]opensea.OwnedCollection, error) {
|
|
||||||
return o.opensea.FetchAllCollectionsByOwner(chainID, owner)
|
|
||||||
}
|
|
||||||
|
|
||||||
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.FullCollectibleDataContainer, error) {
|
|
||||||
assetContainer, err := o.opensea.FetchAllAssetsByOwnerAndCollection(chainID, owner, collectionSlug, cursor, limit)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = o.processFullCollectibleData(assetContainer.Items)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return assetContainer, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Need to combine different providers to support all needed ChainIDs
|
// Need to combine different providers to support all needed ChainIDs
|
||||||
func (o *Manager) FetchBalancesByOwnerAndContractAddress(chainID walletCommon.ChainID, ownerAddress common.Address, contractAddresses []common.Address) (thirdparty.TokenBalancesPerContractAddress, error) {
|
func (o *Manager) FetchBalancesByOwnerAndContractAddress(chainID walletCommon.ChainID, ownerAddress common.Address, contractAddresses []common.Address) (thirdparty.TokenBalancesPerContractAddress, error) {
|
||||||
ret := make(thirdparty.TokenBalancesPerContractAddress)
|
ret := make(thirdparty.TokenBalancesPerContractAddress)
|
||||||
|
|
|
@ -107,7 +107,6 @@ func NewService(
|
||||||
blockChainState := NewBlockChainState(rpcClient, accountsDB)
|
blockChainState := NewBlockChainState(rpcClient, accountsDB)
|
||||||
|
|
||||||
openseaHTTPClient := opensea.NewHTTPClient()
|
openseaHTTPClient := opensea.NewHTTPClient()
|
||||||
openseaClient := opensea.NewClient(config.WalletConfig.OpenseaAPIKey, openseaHTTPClient)
|
|
||||||
openseaV2Client := opensea.NewClientV2(config.WalletConfig.OpenseaAPIKey, openseaHTTPClient)
|
openseaV2Client := opensea.NewClientV2(config.WalletConfig.OpenseaAPIKey, openseaHTTPClient)
|
||||||
alchemyClient := alchemy.NewClient(config.WalletConfig.AlchemyAPIKeys)
|
alchemyClient := alchemy.NewClient(config.WalletConfig.AlchemyAPIKeys)
|
||||||
|
|
||||||
|
@ -117,23 +116,20 @@ func NewService(
|
||||||
}
|
}
|
||||||
|
|
||||||
accountOwnershipProviders := []thirdparty.CollectibleAccountOwnershipProvider{
|
accountOwnershipProviders := []thirdparty.CollectibleAccountOwnershipProvider{
|
||||||
openseaClient,
|
|
||||||
openseaV2Client,
|
openseaV2Client,
|
||||||
alchemyClient,
|
alchemyClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
collectibleDataProviders := []thirdparty.CollectibleDataProvider{
|
collectibleDataProviders := []thirdparty.CollectibleDataProvider{
|
||||||
openseaClient,
|
|
||||||
openseaV2Client,
|
openseaV2Client,
|
||||||
alchemyClient,
|
alchemyClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
collectionDataProviders := []thirdparty.CollectionDataProvider{
|
collectionDataProviders := []thirdparty.CollectionDataProvider{
|
||||||
openseaClient,
|
|
||||||
alchemyClient,
|
alchemyClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
collectiblesManager := collectibles.NewManager(db, rpcClient, contractOwnershipProviders, accountOwnershipProviders, collectibleDataProviders, collectionDataProviders, openseaClient, feed)
|
collectiblesManager := collectibles.NewManager(db, rpcClient, contractOwnershipProviders, accountOwnershipProviders, collectibleDataProviders, collectionDataProviders, feed)
|
||||||
collectibles := collectibles.NewService(db, feed, accountsDB, accountFeed, rpcClient.NetworkManager, collectiblesManager)
|
collectibles := collectibles.NewService(db, feed, accountsDB, accountFeed, rpcClient.NetworkManager, collectiblesManager)
|
||||||
|
|
||||||
activity := activity.NewService(db, tokenManager, collectiblesManager, feed)
|
activity := activity.NewService(db, tokenManager, collectiblesManager, feed)
|
||||||
|
|
|
@ -1,347 +0,0 @@
|
||||||
package opensea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
const AssetLimit = 200
|
|
||||||
const CollectionLimit = 300
|
|
||||||
|
|
||||||
const ChainIDRequiringAPIKey = walletCommon.EthereumMainnet
|
|
||||||
|
|
||||||
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.EthereumSepolia:
|
|
||||||
return "https://testnets-api.opensea.io/api/v1", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", thirdparty.ErrChainIDNotSupported
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Client) ID() string {
|
|
||||||
return OpenseaV1ID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Client) IsChainSupported(chainID walletCommon.ChainID) bool {
|
|
||||||
_, err := getBaseURL(chainID)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Client) IsConnected() bool {
|
|
||||||
return o.connectionStatus.IsConnected()
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
client *HTTPClient
|
|
||||||
apiKey string
|
|
||||||
connectionStatus *connection.Status
|
|
||||||
urlGetter urlGetter
|
|
||||||
}
|
|
||||||
|
|
||||||
// new opensea v1 client.
|
|
||||||
func NewClient(apiKey string, httpClient *HTTPClient) *Client {
|
|
||||||
if apiKey == "" {
|
|
||||||
log.Warn("OpenseaV1 API key not available")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Client{
|
|
||||||
client: httpClient,
|
|
||||||
apiKey: apiKey,
|
|
||||||
connectionStatus: connection.NewStatus(),
|
|
||||||
urlGetter: getURL,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Client) FetchAllCollectionsByOwner(chainID walletCommon.ChainID, owner common.Address) ([]OwnedCollection, error) {
|
|
||||||
offset := 0
|
|
||||||
var collections []OwnedCollection
|
|
||||||
|
|
||||||
for {
|
|
||||||
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)
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
var tmp []OwnedCollection
|
|
||||||
err = json.Unmarshal(body, &tmp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
collections = append(collections, tmp...)
|
|
||||||
|
|
||||||
if len(tmp) < CollectionLimit {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return collections, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Client) FetchCollectionsDataByContractID(ids []thirdparty.ContractID) ([]thirdparty.CollectionData, error) {
|
|
||||||
ret := make([]thirdparty.CollectionData, 0, len(ids))
|
|
||||||
|
|
||||||
for _, id := range ids {
|
|
||||||
path := fmt.Sprintf("asset_contract/%s", id.Address.String())
|
|
||||||
url, err := o.urlGetter(id.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))
|
|
||||||
}
|
|
||||||
|
|
||||||
var tmp AssetContract
|
|
||||||
err = json.Unmarshal(body, &tmp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = append(ret, tmp.Collection.toCollectionData(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Client) FetchAllAssetsByOwnerAndCollection(chainID walletCommon.ChainID, owner common.Address, collectionSlug string, cursor string, limit int) (*thirdparty.FullCollectibleDataContainer, error) {
|
|
||||||
queryParams := url.Values{
|
|
||||||
"owner": {owner.String()},
|
|
||||||
"collection": {collectionSlug},
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cursor) > 0 {
|
|
||||||
queryParams["cursor"] = []string{cursor}
|
|
||||||
}
|
|
||||||
|
|
||||||
return o.fetchAssets(chainID, queryParams, limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Client) FetchAllAssetsByOwnerAndContractAddress(chainID walletCommon.ChainID, owner common.Address, contractAddresses []common.Address, cursor string, limit int) (*thirdparty.FullCollectibleDataContainer, error) {
|
|
||||||
queryParams := url.Values{
|
|
||||||
"owner": {owner.String()},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, contractAddress := range contractAddresses {
|
|
||||||
queryParams.Add("asset_contract_addresses", contractAddress.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cursor) > 0 {
|
|
||||||
queryParams["cursor"] = []string{cursor}
|
|
||||||
}
|
|
||||||
|
|
||||||
return o.fetchAssets(chainID, queryParams, limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Client) FetchAllAssetsByOwner(chainID walletCommon.ChainID, owner common.Address, cursor string, limit int) (*thirdparty.FullCollectibleDataContainer, error) {
|
|
||||||
queryParams := url.Values{
|
|
||||||
"owner": {owner.String()},
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cursor) > 0 {
|
|
||||||
queryParams["cursor"] = []string{cursor}
|
|
||||||
}
|
|
||||||
|
|
||||||
return o.fetchAssets(chainID, queryParams, limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Client) FetchAssetsByCollectibleUniqueID(uniqueIDs []thirdparty.CollectibleUniqueID) ([]thirdparty.FullCollectibleData, error) {
|
|
||||||
queryParams := url.Values{}
|
|
||||||
|
|
||||||
ret := make([]thirdparty.FullCollectibleData, 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.ContractID.Address.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := o.fetchAssets(chainID, queryParams, thirdparty.FetchNoLimit)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = append(ret, data.Items...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Client) fetchAssets(chainID walletCommon.ChainID, queryParams url.Values, limit int) (*thirdparty.FullCollectibleDataContainer, error) {
|
|
||||||
assets := new(thirdparty.FullCollectibleDataContainer)
|
|
||||||
|
|
||||||
if len(queryParams["cursor"]) > 0 {
|
|
||||||
assets.PreviousCursor = queryParams["cursor"][0]
|
|
||||||
}
|
|
||||||
assets.Provider = o.ID()
|
|
||||||
|
|
||||||
tmpLimit := AssetLimit
|
|
||||||
if limit > thirdparty.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 {
|
|
||||||
assets.Items = append(assets.Items, asset.toCommon())
|
|
||||||
}
|
|
||||||
assets.NextCursor = container.NextCursor
|
|
||||||
|
|
||||||
if len(assets.NextCursor) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
queryParams["cursor"] = []string{assets.NextCursor}
|
|
||||||
|
|
||||||
if limit > thirdparty.FetchNoLimit && len(assets.Items) >= 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 {
|
|
||||||
assets.PreviousCursor = queryParams["cursor"][0]
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpLimit := AssetLimit
|
|
||||||
if limit > 0 && limit < tmpLimit {
|
|
||||||
tmpLimit = limit
|
|
||||||
}
|
|
||||||
|
|
||||||
baseURL, err := getBaseURL(chainID)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
queryParams["limit"] = []string{strconv.Itoa(tmpLimit)}
|
|
||||||
for {
|
|
||||||
url := baseURL + "/assets?" + queryParams.Encode()
|
|
||||||
|
|
||||||
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 {
|
|
||||||
for i := range asset.Traits {
|
|
||||||
asset.Traits[i].TraitType = strings.Replace(asset.Traits[i].TraitType, "_", " ", 1)
|
|
||||||
asset.Traits[i].Value = TraitValue(strings.Title(string(asset.Traits[i].Value)))
|
|
||||||
}
|
|
||||||
assets.Assets = append(assets.Assets, asset)
|
|
||||||
}
|
|
||||||
assets.NextCursor = container.NextCursor
|
|
||||||
|
|
||||||
if len(assets.NextCursor) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
queryParams["cursor"] = []string{assets.NextCursor}
|
|
||||||
|
|
||||||
if limit > 0 && len(assets.Assets) >= limit {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return assets, nil
|
|
||||||
}
|
|
|
@ -1,171 +0,0 @@
|
||||||
package opensea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"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"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ExpiredKeyError = "Expired API key"
|
|
||||||
ExpectedExpiredKeyError = "invalid json: Expired API key"
|
|
||||||
)
|
|
||||||
|
|
||||||
func initTestClient(srv *httptest.Server) *Client {
|
|
||||||
urlGetter := func(chainID walletCommon.ChainID, path string) (string, error) {
|
|
||||||
return srv.URL, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
status := connection.NewStatus()
|
|
||||||
|
|
||||||
client := &HTTPClient{
|
|
||||||
client: srv.Client(),
|
|
||||||
}
|
|
||||||
opensea := &Client{
|
|
||||||
client: client,
|
|
||||||
connectionStatus: status,
|
|
||||||
urlGetter: urlGetter,
|
|
||||||
}
|
|
||||||
|
|
||||||
return opensea
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetchAllCollectionsByOwner(t *testing.T) {
|
|
||||||
expectedOS := []OwnedCollection{{
|
|
||||||
Collection: Collection{
|
|
||||||
Name: "Rocky",
|
|
||||||
Slug: "rocky",
|
|
||||||
ImageURL: "ImageUrl",
|
|
||||||
},
|
|
||||||
OwnedAssetCount: &bigint.BigInt{Int: big.NewInt(1)},
|
|
||||||
}}
|
|
||||||
response, _ := json.Marshal(expectedOS)
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(200)
|
|
||||||
_, err := w.Write(response)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
defer srv.Close()
|
|
||||||
|
|
||||||
opensea := initTestClient(srv)
|
|
||||||
res, err := opensea.FetchAllCollectionsByOwner(walletCommon.ChainID(1), common.Address{1})
|
|
||||||
assert.Equal(t, expectedOS, res)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetchAllCollectionsByOwnerWithInValidJson(t *testing.T) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(200)
|
|
||||||
_, err := w.Write([]byte(ExpiredKeyError))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
defer srv.Close()
|
|
||||||
|
|
||||||
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) {
|
|
||||||
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",
|
|
||||||
ChainIdentifier: "ethereum",
|
|
||||||
},
|
|
||||||
Collection: Collection{
|
|
||||||
Name: "Rocky",
|
|
||||||
Traits: map[string]CollectionTrait{},
|
|
||||||
},
|
|
||||||
Traits: []Trait{},
|
|
||||||
}},
|
|
||||||
NextCursor: "",
|
|
||||||
PreviousCursor: "",
|
|
||||||
}
|
|
||||||
expectedCommon := thirdparty.FullCollectibleDataContainer{
|
|
||||||
Items: []thirdparty.FullCollectibleData{
|
|
||||||
thirdparty.FullCollectibleData{
|
|
||||||
CollectibleData: thirdparty.CollectibleData{
|
|
||||||
ID: thirdparty.CollectibleUniqueID{
|
|
||||||
ContractID: thirdparty.ContractID{
|
|
||||||
ChainID: 1,
|
|
||||||
Address: common.HexToAddress("0x1"),
|
|
||||||
},
|
|
||||||
TokenID: &bigint.BigInt{Int: big.NewInt(1)},
|
|
||||||
},
|
|
||||||
Provider: "openseaV1",
|
|
||||||
Name: "Rocky",
|
|
||||||
Description: "Rocky Balboa",
|
|
||||||
Permalink: "permalink",
|
|
||||||
ImageURL: "ImageUrl",
|
|
||||||
Traits: []thirdparty.CollectibleTrait{},
|
|
||||||
},
|
|
||||||
CollectionData: &thirdparty.CollectionData{
|
|
||||||
ID: thirdparty.ContractID{
|
|
||||||
ChainID: 1,
|
|
||||||
Address: common.HexToAddress("0x1"),
|
|
||||||
},
|
|
||||||
Provider: "openseaV1",
|
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
defer srv.Close()
|
|
||||||
|
|
||||||
opensea := initTestClient(srv)
|
|
||||||
res, err := opensea.FetchAllAssetsByOwnerAndCollection(walletCommon.ChainID(1), common.Address{1}, "rocky", "", 200)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, expectedCommon, *res)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetchAllAssetsByOwnerAndCollectionInvalidJson(t *testing.T) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(200)
|
|
||||||
_, err := w.Write([]byte(ExpiredKeyError))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
defer srv.Close()
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
|
@ -135,7 +135,7 @@ func (o *ClientV2) FetchAssetsByCollectibleUniqueID(uniqueIDs []thirdparty.Colle
|
||||||
func (o *ClientV2) fetchAssets(chainID walletCommon.ChainID, pathParams []string, queryParams url.Values, limit int, cursor string) (*thirdparty.FullCollectibleDataContainer, error) {
|
func (o *ClientV2) fetchAssets(chainID walletCommon.ChainID, pathParams []string, queryParams url.Values, limit int, cursor string) (*thirdparty.FullCollectibleDataContainer, error) {
|
||||||
assets := new(thirdparty.FullCollectibleDataContainer)
|
assets := new(thirdparty.FullCollectibleDataContainer)
|
||||||
|
|
||||||
tmpLimit := AssetLimit
|
tmpLimit := assetLimitV2
|
||||||
if limit > thirdparty.FetchNoLimit && limit < tmpLimit {
|
if limit > thirdparty.FetchNoLimit && limit < tmpLimit {
|
||||||
tmpLimit = limit
|
tmpLimit = limit
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,183 +0,0 @@
|
||||||
package opensea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"strconv"
|
|
||||||
"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 OpenseaV1ID = "openseaV1"
|
|
||||||
|
|
||||||
type urlGetter func(walletCommon.ChainID, string) (string, error)
|
|
||||||
|
|
||||||
type TraitValue string
|
|
||||||
|
|
||||||
func (st *TraitValue) UnmarshalJSON(b []byte) error {
|
|
||||||
var item interface{}
|
|
||||||
if err := json.Unmarshal(b, &item); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v := item.(type) {
|
|
||||||
case float64:
|
|
||||||
*st = TraitValue(strconv.FormatFloat(v, 'f', 2, 64))
|
|
||||||
case int:
|
|
||||||
*st = TraitValue(strconv.Itoa(v))
|
|
||||||
case string:
|
|
||||||
*st = TraitValue(v)
|
|
||||||
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type AssetContainer struct {
|
|
||||||
Assets []Asset `json:"assets"`
|
|
||||||
NextCursor string `json:"next"`
|
|
||||||
PreviousCursor string `json:"previous"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Contract struct {
|
|
||||||
Address string `json:"address"`
|
|
||||||
ChainIdentifier string `json:"chain_identifier"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Trait struct {
|
|
||||||
TraitType string `json:"trait_type"`
|
|
||||||
Value TraitValue `json:"value"`
|
|
||||||
DisplayType string `json:"display_type"`
|
|
||||||
MaxValue string `json:"max_value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PaymentToken struct {
|
|
||||||
ID int `json:"id"`
|
|
||||||
Symbol string `json:"symbol"`
|
|
||||||
Address string `json:"address"`
|
|
||||||
ImageURL string `json:"image_url"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Decimals int `json:"decimals"`
|
|
||||||
EthPrice string `json:"eth_price"`
|
|
||||||
UsdPrice string `json:"usd_price"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LastSale struct {
|
|
||||||
PaymentToken PaymentToken `json:"payment_token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SellOrder struct {
|
|
||||||
CurrentPrice string `json:"current_price"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Asset struct {
|
|
||||||
ID int `json:"id"`
|
|
||||||
TokenID *bigint.BigInt `json:"token_id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Permalink string `json:"permalink"`
|
|
||||||
ImageThumbnailURL string `json:"image_thumbnail_url"`
|
|
||||||
ImageURL string `json:"image_url"`
|
|
||||||
AnimationURL string `json:"animation_url"`
|
|
||||||
Contract Contract `json:"asset_contract"`
|
|
||||||
Collection Collection `json:"collection"`
|
|
||||||
Traits []Trait `json:"traits"`
|
|
||||||
LastSale LastSale `json:"last_sale"`
|
|
||||||
SellOrders []SellOrder `json:"sell_orders"`
|
|
||||||
BackgroundColor string `json:"background_color"`
|
|
||||||
TokenURI string `json:"token_metadata"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CollectionTrait struct {
|
|
||||||
Min float64 `json:"min"`
|
|
||||||
Max float64 `json:"max"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Collection struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Slug string `json:"slug"`
|
|
||||||
ImageURL string `json:"image_url"`
|
|
||||||
Traits map[string]CollectionTrait `json:"traits"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type OwnedCollection struct {
|
|
||||||
Collection
|
|
||||||
OwnedAssetCount *bigint.BigInt `json:"owned_asset_count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AssetContract struct {
|
|
||||||
Collection Collection `json:"collection"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Asset) id() thirdparty.CollectibleUniqueID {
|
|
||||||
return thirdparty.CollectibleUniqueID{
|
|
||||||
ContractID: thirdparty.ContractID{
|
|
||||||
ChainID: chainStringToChainID(c.Contract.ChainIdentifier),
|
|
||||||
Address: 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) toCollectionData(id thirdparty.ContractID) thirdparty.CollectionData {
|
|
||||||
ret := thirdparty.CollectionData{
|
|
||||||
ID: id,
|
|
||||||
Provider: OpenseaV1ID,
|
|
||||||
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) toCollectiblesData() thirdparty.CollectibleData {
|
|
||||||
return thirdparty.CollectibleData{
|
|
||||||
ID: c.id(),
|
|
||||||
Provider: OpenseaV1ID,
|
|
||||||
Name: c.Name,
|
|
||||||
Description: c.Description,
|
|
||||||
Permalink: c.Permalink,
|
|
||||||
ImageURL: c.ImageURL,
|
|
||||||
AnimationURL: c.AnimationURL,
|
|
||||||
Traits: openseaToCollectibleTraits(c.Traits),
|
|
||||||
BackgroundColor: c.BackgroundColor,
|
|
||||||
TokenURI: c.TokenURI,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Asset) toCommon() thirdparty.FullCollectibleData {
|
|
||||||
collection := c.Collection.toCollectionData(c.id().ContractID)
|
|
||||||
return thirdparty.FullCollectibleData{
|
|
||||||
CollectibleData: c.toCollectiblesData(),
|
|
||||||
CollectionData: &collection,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,8 @@
|
||||||
package opensea
|
package opensea
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
@ -23,24 +25,7 @@ const (
|
||||||
optimismGoerliString = "optimism_goerli"
|
optimismGoerliString = "optimism_goerli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func chainStringToChainID(chainString string) walletCommon.ChainID {
|
type urlGetter func(walletCommon.ChainID, string) (string, error)
|
||||||
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 {
|
func chainIDToChainString(chainID walletCommon.ChainID) string {
|
||||||
chainString := ""
|
chainString := ""
|
||||||
|
@ -99,6 +84,26 @@ type OwnerV2 struct {
|
||||||
Quantity *bigint.BigInt `json:"quantity"`
|
Quantity *bigint.BigInt `json:"quantity"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TraitValue string
|
||||||
|
|
||||||
|
func (st *TraitValue) UnmarshalJSON(b []byte) error {
|
||||||
|
var item interface{}
|
||||||
|
if err := json.Unmarshal(b, &item); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := item.(type) {
|
||||||
|
case float64:
|
||||||
|
*st = TraitValue(strconv.FormatFloat(v, 'f', 2, 64))
|
||||||
|
case int:
|
||||||
|
*st = TraitValue(strconv.Itoa(v))
|
||||||
|
case string:
|
||||||
|
*st = TraitValue(v)
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type TraitV2 struct {
|
type TraitV2 struct {
|
||||||
TraitType string `json:"trait_type"`
|
TraitType string `json:"trait_type"`
|
||||||
DisplayType string `json:"display_type"`
|
DisplayType string `json:"display_type"`
|
||||||
|
|
Loading…
Reference in New Issue