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
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
accountsFeed: accountsFeed,
|
||||
opensea: newOpenseaClient(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,6 +38,7 @@ type Service struct {
|
|||
client *walletClient
|
||||
cryptoOnRampManager *CryptoOnRampManager
|
||||
started bool
|
||||
opensea *OpenseaClient
|
||||
|
||||
group *Group
|
||||
accountsFeed *event.Feed
|
||||
|
|
Loading…
Reference in New Issue