feat: fetch assets from opensea (#2320)

This commit is contained in:
Anthony Laibe 2021-08-20 21:53:24 +02:00 committed by GitHub
parent dbd34ae7eb
commit 8f9c644dae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 209 additions and 0 deletions

View File

@ -269,3 +269,13 @@ func (api *API) GetCachedBalances(ctx context.Context, addresses []common.Addres
return blocksToViews(result), nil return blocksToViews(result), nil
} }
func (api *API) GetOpenseaCollectionsByOwner(ctx context.Context, owner common.Address) ([]OpenseaCollection, error) {
log.Debug("call to get opensea collections")
return api.s.opensea.fetchAllCollectionsByOwner(owner)
}
func (api *API) GetOpenseaAssetsByOwnerAndCollection(ctx context.Context, owner common.Address, collectionSlug string, limit int) ([]OpenseaAsset, error) {
log.Debug("call to get opensea assets")
return api.s.opensea.fetchAllAssetsByOwnerAndCollection(owner, collectionSlug, limit)
}

134
services/wallet/opensea.go Normal file
View File

@ -0,0 +1,134 @@
package wallet
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
const AssetLimit = 50
const CollectionLimit = 300
type OpenseaAssetContainer struct {
Assets []OpenseaAsset `json:"assets"`
}
type OpenseaAssetCollection struct {
Name string `json:"name"`
}
type OpenseaContract struct {
Address string `json:"address"`
}
type OpenseaAsset struct {
ID int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Permalink string `json:"permalink"`
ImageThumbnailURL string `json:"image_thumbnail_url"`
ImageURL string `json:"image_url"`
Contract OpenseaContract `json:"asset_contract"`
Collection OpenseaAssetCollection `json:"collection"`
}
type OpenseaCollection struct {
Name string `json:"name"`
Slug string `json:"slug"`
ImageURL string `json:"image_url"`
OwnedAssetCount int `json:"owned_asset_count"`
}
type OpenseaClient struct {
client *http.Client
url string
}
// new opensea client.
func newOpenseaClient() *OpenseaClient {
client := &http.Client{
Timeout: time.Second * 5,
}
return &OpenseaClient{client: client, url: "https://api.opensea.io/api/v1"}
}
func (o *OpenseaClient) fetchAllCollectionsByOwner(owner common.Address) ([]OpenseaCollection, error) {
offset := 0
var collections []OpenseaCollection
for {
url := fmt.Sprintf("%s/collections?asset_owner=%s&offset=%d&limit=%d", o.url, owner, offset, CollectionLimit)
body, err := o.doOpenseaRequest(url)
if err != nil {
return nil, err
}
var tmp []OpenseaCollection
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 *OpenseaClient) fetchAllAssetsByOwnerAndCollection(owner common.Address, collectionSlug string, limit int) ([]OpenseaAsset, error) {
offset := 0
var assets []OpenseaAsset
for {
url := fmt.Sprintf("%s/assets?owner=%s&collection=%s&offset=%d&limit=%d", o.url, owner, collectionSlug, offset, AssetLimit)
body, err := o.doOpenseaRequest(url)
if err != nil {
return nil, err
}
container := OpenseaAssetContainer{}
err = json.Unmarshal(body, &container)
if err != nil {
return nil, err
}
assets = append(assets, container.Assets...)
if len(container.Assets) < AssetLimit {
break
}
if len(assets) >= limit {
break
}
}
return assets, nil
}
func (o *OpenseaClient) doOpenseaRequest(url string) ([]byte, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
resp, err := o.client.Do(req)
if err != nil {
return nil, err
}
defer func() {
if err := resp.Body.Close(); err != nil {
log.Error("failed to close opensea request body", "err", err)
}
}()
body, err := ioutil.ReadAll(resp.Body)
return body, err
}

View File

@ -0,0 +1,63 @@
package wallet
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/ethereum/go-ethereum/common"
)
func TestFetchAllCollectionsByOwner(t *testing.T) {
expected := []OpenseaCollection{OpenseaCollection{Name: "Rocky", Slug: "rocky", ImageURL: "ImageUrl", OwnedAssetCount: 1}}
response, _ := json.Marshal(expected)
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 := &OpenseaClient{
client: srv.Client(),
url: srv.URL,
}
res, err := opensea.fetchAllCollectionsByOwner(common.Address{1})
assert.Equal(t, expected, res)
assert.Nil(t, err)
}
func TestFetchAllAssetsByOwnerAndCollection(t *testing.T) {
expected := []OpenseaAsset{OpenseaAsset{
ID: 1,
Name: "Rocky",
Description: "Rocky Balboa",
Permalink: "permalink",
ImageThumbnailURL: "ImageThumbnailURL",
ImageURL: "ImageUrl",
Contract: OpenseaContract{Address: "1"},
Collection: OpenseaAssetCollection{Name: "Rocky"},
}}
response, _ := json.Marshal(OpenseaAssetContainer{Assets: expected})
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 := &OpenseaClient{
client: srv.Client(),
url: srv.URL,
}
res, err := opensea.fetchAllAssetsByOwnerAndCollection(common.Address{1}, "rocky", 200)
assert.Equal(t, expected, res)
assert.Nil(t, err)
}

View File

@ -25,6 +25,7 @@ func NewService(db *Database, accountsFeed *event.Feed) *Service {
publisher: feed, publisher: feed,
}, },
accountsFeed: accountsFeed, accountsFeed: accountsFeed,
opensea: newOpenseaClient(),
} }
} }
@ -37,6 +38,7 @@ type Service struct {
client *walletClient client *walletClient
cryptoOnRampManager *CryptoOnRampManager cryptoOnRampManager *CryptoOnRampManager
started bool started bool
opensea *OpenseaClient
group *Group group *Group
accountsFeed *event.Feed accountsFeed *event.Feed