add nft assets endpoint (#298)

This commit is contained in:
Asim Aslam
2021-12-08 14:04:19 +00:00
committed by GitHub
parent 22ce7074a0
commit cc0a59aaf7
9 changed files with 1829 additions and 108 deletions

103
nft/domain/opensea.go Normal file
View File

@@ -0,0 +1,103 @@
package domain
type AssetResponse struct {
Assets []*Asset `json:"assets"`
}
type Asset struct {
Id int32 `json:"id"`
TokenId string `json:"token_id"`
Sales int32 `json:"num_sales"`
ImageUrl string `json:"image_url"`
Name string `json:"name"`
Description string `json:"description"`
Permalink string `json:"permalink"`
Contract *Contract `json:"asset_contract"`
Collection *Collection `json:"collection"`
Creator *User `json:"creator"`
Owner *User `json:"owner"`
LastSale *Sale `json:"last_sale,omitempty"`
Presale bool `json:"is_presale"`
ListingDate string `json:"listing_date,omitempty"`
}
type Contract struct {
// name of contract
Name string `json:"name,omitempty"`
// ethereum address
Address string `json:"address,omitempty"`
// type of contract e.g "semi-fungible"
Type string `json:"asset_contract_type,omitempty"`
// timestamp of creation
CreatedAt string `json:"created_date,omitempty"`
// owner id
Owner int32 `json:"owner,omitempty"`
// aka "ERC1155"
Schema string `json:"schema_name,omitempty"`
// related symbol
Symbol string `json:"symbol,omitempty"`
// description of contract
Description string `json:"description,omitempty"`
// payout address
PayoutAddress string `json:"payout_address,omitempty"`
// seller fees
SellerFees string `json:"seller_fees_basis_points,omitempty"`
}
type Collection struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Slug string `json:"slug,omitempty"`
ImageUrl string `json:"image_url,omitempty"`
CreatedAt string `json:"created_date,omitempty"`
PayoutAddress string `json:"payout_address,omitempty"`
}
type User struct {
User *Username `json:"user"`
ProfileUrl string `json:"profile_img_url,omitempty"`
Address string `json:"address,omitempty"`
}
type Username struct {
Username string `json:"username",omitempty"`
}
type SaleAsset struct {
TokenId string `json:"token_id"`
Decimals int32 `json:"decimals"`
}
type Sale struct {
Asset *SaleAsset `json:"asset"`
EventType string `json:"event_type,omitempty"`
EventTimestamp string `json:"event_timestamp,omitempty"`
TotalPrice string `json:"total_price,omitempty"`
Quantity string `json:"quantity,omitempty"`
CreatedAt string `json:"created_date,omitempty"`
Transaction *Transaction `json:"transaction,omitempty"`
PaymentToken *Token `json:"payment_token,omitempty"`
}
type Transaction struct {
Id int32 `json:"id,omitempty"`
Timestamp string `json:"timestamp,omitempty"`
BlockHash string `json:"block_hash,omitempty"`
BlockNumber string `json:"block_number,omitempty"`
FromAccount *User `json:"from_account,omitempty"`
ToAccount *User `json:"to_account,omitempty"`
TransactionHash string `json:"transaction_hash,omitempty"`
TransactionIndex string `json:"transaction_index,omitempty"`
}
type Token struct {
Id int32 `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Symbol string `json:"symbol,omitempty"`
Address string `json:"address,omitempty"`
ImageUrl string `json:"image_url,omitempty"`
Decimals int32 `json:"decimals,omitempty"`
EthPrice string `json:"eth_price,omitempty"`
UsdPrice string `json:"usd_price,omitempty"`
}

View File

@@ -1,13 +1,105 @@
{
"vote": [
"assets": [
{
"title": "Vote for the API",
"title": "Get a list of assets",
"run_check": false,
"request": {
"message": "Launch it!"
"order_by": "sale_date",
"limit": 1
},
"response": {
"message": "Thanks for the vote!"
"assets": [
{
"id": 96959754,
"token_id": "24",
"name": "The Bitcoin Price Crash",
"description": "\"The Bitcoin Price Crash\" from CryptoCards collection",
"image_url": "https://lh3.googleusercontent.com/QKCsg3W-71eFbFSHl_4sVV89gxzZvgX8sls9r8PCyJIwYkEKVm1VJVcWdCEm5IxpqYgpcLDNm9JaiF13jKxWvOflVnYW5KIs8GhIdXg",
"sales": 7,
"permalink": "https://opensea.io/assets/0x3a7dc718eaf31f0a55988161f3d75d7ca785b034/24",
"contract": {
"name": "Cryptocards",
"address": "0x3a7dc718eaf31f0a55988161f3d75d7ca785b034",
"type": "semi-fungible",
"created_at": "2021-11-10T08:31:01.405462",
"owner": 63182848,
"schema": "ERC1155",
"symbol": "CC",
"description": "The Cryptocards collection was created at the dawn of what is considered cryptoart or NFTs today being minted on January 2018. \n\nIt is the only NFT collection chronicling the history of Bitcoin including over 50 cards with a total supply of 7.851.\n\nThe initial CryptoCards were issued as a ERC-20, but we developed an ERC1155 wrapper to make them available on OpenSea.\n\nBeware of other fake collections on OS. Stay safe and join the community: https://discord.gg/m9nUKEzcWJ",
"payout_address": "0xb7e0c211fb088e42aa9fd936b0a9c52985fbd273",
"seller_fees": ""
},
"collection": {
"name": "The CryptoCards Collection (2018)",
"description": "The Cryptocards collection was created at the dawn of what is considered cryptoart or NFTs today being minted on January 2018. \n\nIt is the only NFT collection chronicling the history of Bitcoin including over 50 cards with a total supply of 7.851.\n\nThe initial CryptoCards were issued as a ERC-20, but we developed an ERC1155 wrapper to make them available on OpenSea.\n\nBeware of other fake collections on OS. Stay safe and join the community: https://discord.gg/m9nUKEzcWJ",
"slug": "cryptocards-collection",
"image_url": "https://lh3.googleusercontent.com/ByPl14lqRv2VsFm_drrTcxM7qE4pDRH3A3beQ2lPtes2gJ7gl4qFH1-v-8Vn7I47yLDN4F_QGLT1-Qqi1IIsuM20AkXZUKZX9l800LI=s120",
"created_at": "2021-11-10T08:44:55.811633",
"payout_address": "0xb7e0c211fb088e42aa9fd936b0a9c52985fbd273"
},
"creator": {
"username": "",
"profile_url": "",
"address": ""
},
"owner": {
"username": "NullAddress",
"profile_url": "https://storage.googleapis.com/opensea-static/opensea-profile/1.png",
"address": "0x0000000000000000000000000000000000000000"
},
"presale": false,
"last_sale": {
"asset_token_id": "24",
"asset_decimals": 0,
"event_type": "successful",
"event_timestamp": "2021-12-08T13:52:33",
"total_price": "75000000000000000",
"quantity": "1",
"created_at": "2021-12-08T13:52:54.475811",
"transaction": {
"id": 218344593,
"timestamp": "2021-12-08T13:52:33",
"block_hash": "0xe168736f9f7f9bf610c04c0705165ef2ca470ddfc5fedb6d0337701f1d083d32",
"block_number": "13765159",
"from_account": {
"username": "austincountach",
"profile_url": "https://storage.googleapis.com/opensea-static/opensea-profile/31.png",
"address": "0x12ad5707401114453020a92a7384f267c9c559f2"
},
"to_account": {
"username": "OpenSea-Orders",
"profile_url": "https://storage.googleapis.com/opensea-static/opensea-profile/22.png",
"address": "0x7be8076f4ea4a4ad08075c2508e481d6c946d12b"
},
"transaction_hash": "0xd3bfa0ffe5d37af6854f485606ceb349634e957ff5778570ea396288fe8914db",
"transaction_index": "176"
},
"payment_token": {
"id": 1,
"name": "Ether",
"symbol": "ETH",
"address": "0x0000000000000000000000000000000000000000",
"image_url": "https://storage.opensea.io/files/6f8e2979d428180222796ff4a33ab929.svg",
"decimals": 18,
"eth_price": "1.000000000000000",
"usd_price": "4274.930000000000291000"
}
},
"listing_date": ""
}
]
}
}
],
"create": [
{
"title": "Create an NFT",
"run_check": false,
"request": {
"name": "Guybrush Threepwood",
"description": "The epic monkey island character"
},
"response": {}
}
}
]

View File

@@ -2,49 +2,14 @@ package handler
import (
"context"
"sync"
"time"
"github.com/micro/services/pkg/tenant"
"github.com/micro/micro/v3/service/store"
pb "github.com/micro/services/nft/proto"
)
type Nft struct{}
var (
mtx sync.RWMutex
voteKey = "votes/"
)
type Vote struct {
Id string `json:"id"`
Message string `json:"message"`
VotedAt time.Time `json:"voted_at"`
}
func (n *Nft) Vote(ctx context.Context, req *pb.VoteRequest, rsp *pb.VoteResponse) error {
mtx.Lock()
defer mtx.Unlock()
id, ok := tenant.FromContext(ctx)
if !ok {
id = "micro"
}
rec := store.NewRecord(voteKey + id, &Vote{
Id: id,
Message: req.Message,
VotedAt: time.Now(),
})
// we don't need to check the error
store.Write(rec)
rsp.Message = "Thanks for the vote!"
func (n *Nft) Assets(ctx context.Context, req *pb.AssetsRequest, rsp *pb.AssetsResponse) error {
return nil
}

227
nft/handler/opensea.go Normal file
View File

@@ -0,0 +1,227 @@
package handler
import (
"context"
"fmt"
"github.com/micro/services/pkg/api"
"github.com/micro/services/nft/domain"
"github.com/micro/micro/v3/service/config"
"github.com/micro/micro/v3/service/logger"
"github.com/micro/micro/v3/service/errors"
pb "github.com/micro/services/nft/proto"
)
// OpenSea handler
type OpenSea struct {
apiKey string
// embed nft api
*Nft
}
var (
openseaURL = "https://api.opensea.io/api/v1"
)
func New() *OpenSea {
v, err := config.Get("nft.key")
if err != nil {
logger.Fatal("nft.key config not found: %v", err)
}
key := v.String("")
if len(key) == 0 {
logger.Fatal("nft.key config not found")
}
// set the api key
api.SetKey("X-API-KEY", key)
return &OpenSea {
apiKey: key,
Nft: new(Nft),
}
}
func (o *OpenSea) Assets(ctx context.Context, req *pb.AssetsRequest, rsp *pb.AssetsResponse) error {
uri := openseaURL + "/assets"
params := "?"
limit := int32(20)
offset := int32(0)
order := "desc"
orderBy := ""
if req.Limit > 0 {
limit = req.Limit
}
if req.Offset > 0 {
offset = req.Offset
}
if req.Order == "asc" {
order = "asc"
}
switch req.OrderBy {
case "sale_date", "sale_count", "sale_price", "total_price":
orderBy = req.OrderBy
}
params += fmt.Sprintf("limit=%d&offset=%d&order_direction=%s",
limit, offset, order)
if len(orderBy) > 0 {
params += "&order_by=" + orderBy
}
if len(req.Collection) > 0 {
params += "&collection=" + req.Collection
}
var resp domain.AssetResponse
if err := api.Get(uri + params, &resp); err != nil {
return errors.InternalServerError("nft.assets", "failed to get assets: %v", err)
}
for _, asset := range resp.Assets {
if asset.Creator == nil {
asset.Creator = &domain.User{
User: &domain.Username{},
}
}
if asset.Creator.User == nil {
asset.Creator.User = &domain.Username{}
}
if asset.Owner == nil {
asset.Owner = &domain.User{
User: &domain.Username{},
}
}
if asset.Owner.User == nil {
asset.Owner.User = &domain.Username{}
}
if asset.Collection == nil {
asset.Collection = new(domain.Collection)
}
if asset.Contract == nil {
asset.Contract = new(domain.Contract)
}
lastSale := new(pb.Sale)
if asset.LastSale != nil {
if asset.LastSale.Transaction == nil {
asset.LastSale.Transaction = &domain.Transaction{
FromAccount: &domain.User{User: new(domain.Username)},
ToAccount: &domain.User{User: new(domain.Username)},
}
}
if asset.LastSale.Transaction.FromAccount == nil {
asset.LastSale.Transaction.FromAccount = &domain.User{User: new(domain.Username)}
}
if asset.LastSale.Transaction.FromAccount.User == nil {
asset.LastSale.Transaction.FromAccount.User = new(domain.Username)
}
if asset.LastSale.Transaction.ToAccount == nil {
asset.LastSale.Transaction.ToAccount = &domain.User{User: new(domain.Username)}
}
if asset.LastSale.Transaction.ToAccount.User == nil {
asset.LastSale.Transaction.ToAccount.User = new(domain.Username)
}
if asset.LastSale.PaymentToken == nil {
asset.LastSale.PaymentToken = new(domain.Token)
}
lastSale = &pb.Sale{
AssetTokenId: asset.LastSale.Asset.TokenId,
AssetDecimals: asset.LastSale.Asset.Decimals,
EventType: asset.LastSale.EventType,
EventTimestamp: asset.LastSale.EventTimestamp,
TotalPrice: asset.LastSale.TotalPrice,
Quantity: asset.LastSale.Quantity,
CreatedAt: asset.LastSale.CreatedAt,
Transaction: &pb.Transaction{
Id: asset.LastSale.Transaction.Id,
Timestamp: asset.LastSale.Transaction.Timestamp,
BlockHash: asset.LastSale.Transaction.BlockHash,
BlockNumber: asset.LastSale.Transaction.BlockNumber,
FromAccount: &pb.User{
Username: asset.LastSale.Transaction.FromAccount.User.Username,
ProfileUrl: asset.LastSale.Transaction.FromAccount.ProfileUrl,
Address: asset.LastSale.Transaction.FromAccount.Address,
},
ToAccount: &pb.User{
Username: asset.LastSale.Transaction.ToAccount.User.Username,
ProfileUrl: asset.LastSale.Transaction.ToAccount.ProfileUrl,
Address: asset.LastSale.Transaction.ToAccount.Address,
},
TransactionHash: asset.LastSale.Transaction.TransactionHash,
TransactionIndex: asset.LastSale.Transaction.TransactionIndex,
},
PaymentToken: &pb.Token{
Id: asset.LastSale.PaymentToken.Id,
Name: asset.LastSale.PaymentToken.Name,
Symbol: asset.LastSale.PaymentToken.Symbol,
Address: asset.LastSale.PaymentToken.Address,
ImageUrl: asset.LastSale.PaymentToken.ImageUrl,
Decimals: asset.LastSale.PaymentToken.Decimals,
EthPrice: asset.LastSale.PaymentToken.EthPrice,
UsdPrice: asset.LastSale.PaymentToken.UsdPrice,
},
}
}
rsp.Assets = append(rsp.Assets, &pb.Asset{
Name: asset.Name,
Description: asset.Description,
Id: asset.Id,
TokenId: asset.TokenId,
ImageUrl: asset.ImageUrl,
Sales: asset.Sales,
Permalink: asset.Permalink,
Contract: &pb.Contract{
Name: asset.Contract.Name,
Description: asset.Contract.Description,
Address: asset.Contract.Address,
Type: asset.Contract.Type,
CreatedAt: asset.Contract.CreatedAt,
Owner: asset.Contract.Owner,
Schema: asset.Contract.Schema,
Symbol: asset.Contract.Symbol,
PayoutAddress: asset.Contract.PayoutAddress,
SellerFees: asset.Contract.SellerFees,
},
Collection: &pb.Collection{
Name: asset.Collection.Name,
Description: asset.Collection.Description,
Slug: asset.Collection.Slug,
ImageUrl: asset.Collection.ImageUrl,
CreatedAt: asset.Collection.CreatedAt,
PayoutAddress: asset.Collection.PayoutAddress,
},
Owner: &pb.User{
Username: asset.Owner.User.Username,
ProfileUrl: asset.Owner.ProfileUrl,
Address: asset.Owner.Address,
},
Creator: &pb.User{
Username: asset.Creator.User.Username,
ProfileUrl: asset.Creator.ProfileUrl,
Address: asset.Creator.Address,
},
LastSale: lastSale,
Presale: asset.Presale,
ListingDate: asset.ListingDate,
})
}
return nil
}
func (o *OpenSea) Create(ctx context.Context, req *pb.CreateRequest, rsp *pb.CreateResponse) error {
return errors.BadRequest("nft.create", "coming soon")
}

View File

@@ -15,7 +15,7 @@ func main() {
)
// Register handler
pb.RegisterNftHandler(srv.Server(), new(handler.Nft))
pb.RegisterNftHandler(srv.Server(), handler.New())
// Run service
if err := srv.Run(); err != nil {

File diff suppressed because it is too large Load Diff

View File

@@ -42,7 +42,8 @@ func NewNftEndpoints() []*api.Endpoint {
// Client API for Nft service
type NftService interface {
Vote(ctx context.Context, in *VoteRequest, opts ...client.CallOption) (*VoteResponse, error)
Assets(ctx context.Context, in *AssetsRequest, opts ...client.CallOption) (*AssetsResponse, error)
Create(ctx context.Context, in *CreateRequest, opts ...client.CallOption) (*CreateResponse, error)
}
type nftService struct {
@@ -57,9 +58,19 @@ func NewNftService(name string, c client.Client) NftService {
}
}
func (c *nftService) Vote(ctx context.Context, in *VoteRequest, opts ...client.CallOption) (*VoteResponse, error) {
req := c.c.NewRequest(c.name, "Nft.Vote", in)
out := new(VoteResponse)
func (c *nftService) Assets(ctx context.Context, in *AssetsRequest, opts ...client.CallOption) (*AssetsResponse, error) {
req := c.c.NewRequest(c.name, "Nft.Assets", in)
out := new(AssetsResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *nftService) Create(ctx context.Context, in *CreateRequest, opts ...client.CallOption) (*CreateResponse, error) {
req := c.c.NewRequest(c.name, "Nft.Create", in)
out := new(CreateResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
@@ -70,12 +81,14 @@ func (c *nftService) Vote(ctx context.Context, in *VoteRequest, opts ...client.C
// Server API for Nft service
type NftHandler interface {
Vote(context.Context, *VoteRequest, *VoteResponse) error
Assets(context.Context, *AssetsRequest, *AssetsResponse) error
Create(context.Context, *CreateRequest, *CreateResponse) error
}
func RegisterNftHandler(s server.Server, hdlr NftHandler, opts ...server.HandlerOption) error {
type nft interface {
Vote(ctx context.Context, in *VoteRequest, out *VoteResponse) error
Assets(ctx context.Context, in *AssetsRequest, out *AssetsResponse) error
Create(ctx context.Context, in *CreateRequest, out *CreateResponse) error
}
type Nft struct {
nft
@@ -88,6 +101,10 @@ type nftHandler struct {
NftHandler
}
func (h *nftHandler) Vote(ctx context.Context, in *VoteRequest, out *VoteResponse) error {
return h.NftHandler.Vote(ctx, in, out)
func (h *nftHandler) Assets(ctx context.Context, in *AssetsRequest, out *AssetsResponse) error {
return h.NftHandler.Assets(ctx, in, out)
}
func (h *nftHandler) Create(ctx context.Context, in *CreateRequest, out *CreateResponse) error {
return h.NftHandler.Create(ctx, in, out)
}

View File

@@ -5,16 +5,144 @@ package nft;
option go_package = "./proto;nft";
service Nft {
rpc Vote(VoteRequest) returns (VoteResponse) {}
rpc Assets(AssetsRequest) returns (AssetsResponse) {}
rpc Create(CreateRequest) returns (CreateResponse) {}
}
// Vote to have the NFT api launched faster!
message VoteRequest {
// optional message
string message = 1;
// Create your own NFT (coming soon)
message CreateRequest {
// name of the NFT
string name = 1;
// description
string description = 2;
// image data
bytes image = 3;
// data if not image
bytes data = 4;
}
message VoteResponse {
// response message
string message = 2;
message CreateResponse {
Asset asset = 1;
}
message Asset {
// id of the asset
int32 id = 1;
// the token id
string token_id = 2;
// name of the asset
string name = 3;
// related description
string description = 4;
// the image url
string image_url = 5;
// number of sales
int32 sales = 6;
// the permalink
string permalink = 7;
// asset contract
Contract contract = 8;
// associated collection
Collection collection = 9;
// Creator of the NFT
User creator = 10;
// Owner of the NFT
User owner = 11;
// is it a presale
bool presale = 12;
// last time sold
Sale last_sale = 13;
// listing date
string listing_date = 14;
}
message Contract {
// name of contract
string name = 1;
// ethereum address
string address = 2;
// type of contract e.g "semi-fungible"
string type = 3;
// timestamp of creation
string created_at = 4;
// owner id
int32 owner = 5;
// aka "ERC1155"
string schema = 6;
// related symbol
string symbol = 7;
// description of contract
string description = 8;
// payout address
string payout_address = 9;
// seller fees
string seller_fees = 10;
}
message Collection {
string name = 1;
string description = 2;
string slug = 3;
string image_url = 4;
string created_at = 5;
string payout_address = 6;
}
message User {
string username = 1;
string profile_url = 2;
string address = 3;
}
message Sale {
string asset_token_id = 1;
int32 asset_decimals = 2;
string event_type = 3;
string event_timestamp = 4;
string total_price = 5;
string quantity = 6;
string created_at = 7;
Transaction transaction = 8;
Token payment_token = 9;
}
message Transaction {
int32 id = 1;
string timestamp = 2;
string block_hash = 3;
string block_number = 4;
User from_account = 5;
User to_account = 6;
string transaction_hash = 7;
string transaction_index = 8;
}
message Token {
int32 id = 1;
string name = 2;
string symbol = 3;
string address = 4;
string image_url = 5;
int32 decimals = 6;
string eth_price = 7;
string usd_price = 8;
}
// Return a list of NFT assets
message AssetsRequest {
// limit returned assets
int32 limit = 1;
// offset for pagination
int32 offset = 2;
// order "asc" or "desc"
string order = 3;
// order by "sale_date", "sale_count", "sale_price", "total_price"
string order_by = 4;
// limit to members of a collection by slug name (case sensitive)
string collection = 5;
}
message AssetsResponse {
// list of assets
repeated Asset assets = 1;
}

View File

@@ -1,6 +1,6 @@
{
"name": "nft",
"icon": "🪙",
"category": "coming soon",
"display_name": "NFTs (Coming Soon)"
"category": "crypto",
"display_name": "NFTs"
}