From bac7eb08ca233a4ab9177ac16fa4fe0c2b68f79e Mon Sep 17 00:00:00 2001 From: Dario Gabriel Lipicar Date: Mon, 6 Mar 2023 13:15:48 -0300 Subject: [PATCH] feat(Wallet): add API to fetch collectibles in a paginated way --- services/wallet/api.go | 23 +++++++- services/wallet/thirdparty/opensea/client.go | 52 +++++++++++++------ .../wallet/thirdparty/opensea/client_test.go | 30 ++++++----- 3 files changed, 74 insertions(+), 31 deletions(-) diff --git a/services/wallet/api.go b/services/wallet/api.go index 1f1bed1e8..d52c619b8 100644 --- a/services/wallet/api.go +++ b/services/wallet/api.go @@ -303,17 +303,36 @@ func (api *API) GetOpenseaCollectionsByOwner(ctx context.Context, chainID uint64 return client.FetchAllCollectionsByOwner(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) { + container, err := api.GetOpenseaAssetsByOwnerAndCollectionWithCursor(ctx, chainID, owner, collectionSlug, "", limit) + if err != nil { + return nil, err + } + 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") client, err := opensea.NewOpenseaClient(chainID, api.s.openseaAPIKey) if err != nil { return nil, err } - return client.FetchAllAssetsByOwnerAndCollection(owner, collectionSlug, limit) + return client.FetchAllAssetsByOwnerAndCollection(owner, collectionSlug, cursor, limit) } -func (api *API) GetOpenseaAssetsByNFTUniqueID(ctx context.Context, chainID uint64, uniqueIDs []opensea.NFTUniqueID, limit int) ([]opensea.Asset, 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") + client, err := opensea.NewOpenseaClient(chainID, api.s.openseaAPIKey) + if err != nil { + return nil, err + } + + return client.FetchAllAssetsByOwner(owner, cursor, limit) +} + +func (api *API) GetOpenseaAssetsByNFTUniqueID(ctx context.Context, chainID uint64, uniqueIDs []opensea.NFTUniqueID, limit int) (*opensea.AssetContainer, error) { log.Debug("call to GetOpenseaAssetsByNFTUniqueID") client, err := opensea.NewOpenseaClient(chainID, api.s.openseaAPIKey) diff --git a/services/wallet/thirdparty/opensea/client.go b/services/wallet/thirdparty/opensea/client.go index 1198241fa..22c0c7122 100644 --- a/services/wallet/thirdparty/opensea/client.go +++ b/services/wallet/thirdparty/opensea/client.go @@ -18,7 +18,7 @@ import ( "github.com/status-im/status-go/services/wallet/bigint" ) -const AssetLimit = 50 +const AssetLimit = 200 const CollectionLimit = 300 const RequestRetryMaxCount = 1 @@ -203,16 +203,32 @@ func (o *Client) FetchAllCollectionsByOwner(owner common.Address) ([]OwnedCollec return collections, nil } -func (o *Client) FetchAllAssetsByOwnerAndCollection(owner common.Address, collectionSlug string, limit int) ([]Asset, error) { +func (o *Client) FetchAllAssetsByOwnerAndCollection(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.fetchAssets(queryParams, limit) } -func (o *Client) FetchAssetsByNFTUniqueID(uniqueIDs []NFTUniqueID, limit int) ([]Asset, error) { +func (o *Client) FetchAllAssetsByOwner(owner common.Address, cursor string, limit int) (*AssetContainer, error) { + queryParams := url.Values{ + "owner": {owner.String()}, + } + + if len(cursor) > 0 { + queryParams["cursor"] = []string{cursor} + } + + return o.fetchAssets(queryParams, limit) +} + +func (o *Client) FetchAssetsByNFTUniqueID(uniqueIDs []NFTUniqueID, limit int) (*AssetContainer, error) { queryParams := url.Values{} for _, uniqueID := range uniqueIDs { @@ -223,10 +239,19 @@ func (o *Client) FetchAssetsByNFTUniqueID(uniqueIDs []NFTUniqueID, limit int) ([ return o.fetchAssets(queryParams, limit) } -func (o *Client) fetchAssets(queryParams url.Values, limit int) ([]Asset, error) { - var assets []Asset +func (o *Client) fetchAssets(queryParams url.Values, limit int) (*AssetContainer, error) { + assets := new(AssetContainer) - queryParams["limit"] = []string{strconv.Itoa(AssetLimit)} + if len(queryParams["cursor"]) > 0 { + assets.PreviousCursor = queryParams["cursor"][0] + } + + tmpLimit := limit + if AssetLimit < limit { + tmpLimit = AssetLimit + } + + queryParams["limit"] = []string{strconv.Itoa(tmpLimit)} for { url := o.url + "/assets?" + queryParams.Encode() @@ -248,22 +273,17 @@ func (o *Client) fetchAssets(queryParams url.Values, limit int) ([]Asset, error) asset.Traits[i].TraitType = strings.Replace(asset.Traits[i].TraitType, "_", " ", 1) asset.Traits[i].Value = TraitValue(strings.Title(string(asset.Traits[i].Value))) } - assets = append(assets, asset) + assets.Assets = append(assets.Assets, asset) } + assets.NextCursor = container.NextCursor - if len(container.Assets) < AssetLimit { + if len(assets.NextCursor) == 0 { break } - nextCursor := container.NextCursor + queryParams["cursor"] = []string{assets.NextCursor} - if len(nextCursor) == 0 { - break - } - - queryParams["cursor"] = []string{nextCursor} - - if len(assets) >= limit { + if len(assets.Assets) >= limit { break } } diff --git a/services/wallet/thirdparty/opensea/client_test.go b/services/wallet/thirdparty/opensea/client_test.go index 6844ce7c7..192f07835 100644 --- a/services/wallet/thirdparty/opensea/client_test.go +++ b/services/wallet/thirdparty/opensea/client_test.go @@ -43,17 +43,21 @@ func TestFetchAllCollectionsByOwner(t *testing.T) { } func TestFetchAllAssetsByOwnerAndCollection(t *testing.T) { - expected := []Asset{{ - ID: 1, - Name: "Rocky", - Description: "Rocky Balboa", - Permalink: "permalink", - ImageThumbnailURL: "ImageThumbnailURL", - ImageURL: "ImageUrl", - Contract: Contract{Address: "1"}, - Collection: Collection{Name: "Rocky"}, - }} - response, _ := json.Marshal(AssetContainer{Assets: expected}) + expected := AssetContainer{ + Assets: []Asset{{ + ID: 1, + Name: "Rocky", + Description: "Rocky Balboa", + Permalink: "permalink", + ImageThumbnailURL: "ImageThumbnailURL", + ImageURL: "ImageUrl", + Contract: Contract{Address: "1"}, + Collection: Collection{Name: "Rocky"}, + }}, + NextCursor: "", + PreviousCursor: "", + } + response, _ := json.Marshal(expected) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) _, err := w.Write(response) @@ -67,7 +71,7 @@ func TestFetchAllAssetsByOwnerAndCollection(t *testing.T) { client: srv.Client(), url: srv.URL, } - res, err := opensea.FetchAllAssetsByOwnerAndCollection(common.Address{1}, "rocky", 200) - assert.Equal(t, expected, res) + res, err := opensea.FetchAllAssetsByOwnerAndCollection(common.Address{1}, "rocky", "", 200) assert.Nil(t, err) + assert.Equal(t, expected, *res) }