feat: fetch assets from opensea (#2320)
This commit is contained in:
parent
dbd34ae7eb
commit
8f9c644dae
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue