Skip to content

Commit

Permalink
Add get all listings/offers, get best listing/offer (#1292)
Browse files Browse the repository at this point in the history
* add API endpoints:
 - get all listings and offers
 - get best listing and offer

* bump package.json version

* add typedocs

* docs updates
  • Loading branch information
ryanio authored Nov 23, 2023
1 parent 20e5008 commit d893417
Show file tree
Hide file tree
Showing 8 changed files with 302 additions and 32 deletions.
35 changes: 22 additions & 13 deletions developerDocs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,32 +125,41 @@ Note that auctions aren't supported with Ether directly due to limitations in Et

### Fetching Orders

To retrieve a list of offers and auctions on an asset, you can use an instance of the `OpenSeaAPI` exposed on the client. Parameters passed into API filter objects are camel-cased and serialized before being sent as [OpenSea API parameters](https://docs.opensea.io/v2.0/reference):
To retrieve a list of offers and auctions on an asset, you can use `getOrders`. Parameters passed into API filter objects are camel-cased and serialized before being sent as [API parameters](https://docs.opensea.io/v2.0/reference):

```typescript
// Get offers (bids), a.k.a. orders where `side == 0`
// Get offers (bids), a.k.a. orders where `side == "bid"`
const { orders, count } = await openseaSDK.api.getOrders({
assetContractAddress: tokenAddress,
tokenId,
side: "bid",
});

// Get page 2 of all auctions, a.k.a. orders where `side == 1`
const { orders, count } = await openseaSDK.api.getOrders(
{
assetContractAddress: tokenAddress,
tokenId,
side: "ask",
},
2,
);
// Get page 2 of all auctions, a.k.a. orders where `side == "ask"`
const { orders, count } = await openseaSDK.api.getOrders({
assetContractAddress: tokenAddress,
tokenId,
side: "ask",
});
```

Note that the listing price of an asset is equal to the `currentPrice` of the **lowest listing** on the asset. Users can lower their listing price without invalidating previous listing, so all get shipped down until they're canceled, or one is fulfilled.

To learn more about signatures, makers, takers, listingTime vs createdTime and other kinds of order terminology, please read the [**Terminology Section**](https://docs.opensea.io/reference#terminology) of the API Docs.
#### Fetching All Offers and Best Listings for a given collection

There are two endpoints that return all offers and listings for a given collection, `getAllOffers` and `getAllListings`.

The available API filters for the orders endpoint is documented in the `OrdersQueryOptions` interface. See the main [API Docs](https://docs.opensea.io/reference#reference-getting-started) for a playground, along with more up-to-date and detailed explanations.
```typescript
const { offers } = await openseaSDK.api.getAllOffers(collectionSlug);
```

#### Fetching Best Offers and Best Listings for a given NFT

There are two endpoints that return the best offer or listing, `getBestOffer` and `getBestListing`.

```typescript
const offer = await openseaSDK.api.getBestOffer(collectionSlug, tokenId);
```

### Buying Items

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "opensea-js",
"version": "6.1.13",
"version": "6.1.14",
"description": "JavaScript SDK for the OpenSea marketplace helps developers build new experiences using NFTs and our marketplace data!",
"license": "MIT",
"author": "OpenSea Developers",
Expand Down
97 changes: 92 additions & 5 deletions src/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ethers } from "ethers";
import {
BuildOfferResponse,
Offer,
GetCollectionResponse,
ListNFTsResponse,
GetNFTResponse,
Expand All @@ -10,6 +9,11 @@ import {
GetOrdersResponse,
GetPaymentTokensResponse,
GetBundlesResponse,
GetBestOfferResponse,
GetBestListingResponse,
GetOffersResponse,
GetListingsResponse,
CollectionOffer,
} from "./types";
import { API_BASE_MAINNET, API_BASE_TESTNET, API_V1_PATH } from "../constants";
import {
Expand Down Expand Up @@ -40,6 +44,10 @@ import {
getRefreshMetadataPath,
getCollectionOffersPath,
getListNFTsByAccountPath,
getBestOfferAPIPath,
getBestListingAPIPath,
getAllOffersAPIPath,
getAllListingsAPIPath,
} from "../orders/utils";
import {
Chain,
Expand Down Expand Up @@ -177,6 +185,82 @@ export class OpenSeaAPI {
};
}

/**
* Gets all offers for a given collection.
* @param collectionSlug The slug of the collection.
* @param limit The number of offers to return. Must be between 1 and 100. Default: 100
* @param next The cursor for the next page of results. This is returned from a previous request.
* @returns The {@link GetOffersResponse} returned by the API.
*/
public async getAllOffers(
collectionSlug: string,
limit?: number,
next?: string,
): Promise<GetOffersResponse> {
const response = await this.get<GetOffersResponse>(
getAllOffersAPIPath(collectionSlug),
serializeOrdersQueryOptions({
limit,
next,
}),
);
return response;
}

/**
* Gets all listings for a given collection.
* @param collectionSlug The slug of the collection.
* @param limit The number of listings to return. Must be between 1 and 100. Default: 100
* @param next The cursor for the next page of results. This is returned from a previous request.
* @returns The {@link GetListingsResponse} returned by the API.
*/
public async getAllListings(
collectionSlug: string,
limit?: number,
next?: string,
): Promise<GetListingsResponse> {
const response = await this.get<GetListingsResponse>(
getAllListingsAPIPath(collectionSlug),
serializeOrdersQueryOptions({
limit,
next,
}),
);
return response;
}

/**
* Gets the best offer for a given token.
* @param collectionSlug The slug of the collection.
* @param tokenId The token identifier.
* @returns The {@link GetBestOfferResponse} returned by the API.
*/
public async getBestOffer(
collectionSlug: string,
tokenId: string | number,
): Promise<GetBestOfferResponse> {
const response = await this.get<GetBestOfferResponse>(
getBestOfferAPIPath(collectionSlug, tokenId),
);
return response;
}

/**
* Gets the best listing for a given token.
* @param collectionSlug The slug of the collection.
* @param tokenId The token identifier.
* @returns The {@link GetBestListingResponse} returned by the API.
*/
public async getBestListing(
collectionSlug: string,
tokenId: string | number,
): Promise<GetBestListingResponse> {
const response = await this.get<GetBestListingResponse>(
getBestListingAPIPath(collectionSlug, tokenId),
);
return response;
}

/**
* Generate the data needed to fulfill a listing or an offer onchain.
* @param fulfillerAddress The wallet address which will be used to fulfill the order
Expand Down Expand Up @@ -302,10 +386,13 @@ export class OpenSeaAPI {
order: ProtocolData,
slug: string,
retries = 0,
): Promise<Offer | null> {
): Promise<CollectionOffer | null> {
const payload = getPostCollectionOfferPayload(slug, order);
try {
return await this.post<Offer>(getPostCollectionOfferPath(), payload);
return await this.post<CollectionOffer>(
getPostCollectionOfferPath(),
payload,
);
} catch (error) {
_throwOrContinue(error, retries);
await delay(1000);
Expand Down Expand Up @@ -586,7 +673,7 @@ export class OpenSeaAPI {

/**
* Fetch list of bundles from the API.
* @param query Query to use for getting bunldes. See {@link OpenSeaAssetBundleQuery}.
* @param query Query to use for getting bundles. See {@link OpenSeaAssetBundleQuery}.
* @param page Page number to fetch. Defaults to 1.
* @returns The {@link GetBundlesResponse} returned by the API.
*/
Expand Down Expand Up @@ -651,7 +738,7 @@ export class OpenSeaAPI {
}

/**
* Generic post methd for any API endpoint.
* Generic post method for any API endpoint.
* @param apiPath Path to URL endpoint under API
* @param body Data to send.
* @param opts ethers ConnectionInfo, similar to Fetch API.
Expand Down
83 changes: 76 additions & 7 deletions src/api/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { ConsiderationItem } from "@opensea/seaport-js/lib/types";
import { OrderV2, ProtocolData, QueryCursors } from "../orders/types";
import {
OrderType,
OrderV2,
ProtocolData,
QueryCursors,
} from "../orders/types";
import {
OpenSeaAsset,
OpenSeaAssetBundle,
Expand All @@ -12,7 +17,7 @@ import {
* @category API Response Types
*/
export type BuildOfferResponse = {
/** A portion of the parameters needed to sumbit a criteria offer, i.e. collection offer. */
/** A portion of the parameters needed to submit a criteria offer, i.e. collection offer. */
partialParameters: PartialParameters;
};

Expand Down Expand Up @@ -46,29 +51,65 @@ export type GetCollectionResponse = {
};

/**
* Collection Offer type.
* Base Order type shared between Listings and Offers.
* @category API Models
*/
export type Offer = {
export type Order = {
/** Offer Identifier */
order_hash: string;
/** Chain the offer exists on */
chain: string;
/** Defines which NFTs meet the criteria to fulfill the offer. */
criteria: Criteria;
/** The protocol data for the order. Only 'seaport' is currently supported. */
protocol_data: ProtocolData;
/** The contract address of the protocol. */
protocol_address: string;
};

/**
* Offer type.
* @category API Models
*/
export type Offer = Order;

/**
* Collection Offer type.
* @category API Models
*/
export type CollectionOffer = Offer & {
/** Defines which NFTs meet the criteria to fulfill the offer. */
criteria: Criteria;
};

/**
* Price response.
* @category API Models
*/
export type Price = {
current: {
currency: string;
decimals: number;
value: string;
};
};

/**
* Listing order type.
* @category API Models
*/
export type Listing = Order & {
/** The order type of the listing. */
type: OrderType;
/** The price of the listing. */
price: Price;
};

/**
* Response from OpenSea API for fetching a list of collection offers.
* @category API Response Types
*/
export type ListCollectionOffersResponse = {
/** List of {@link Offer} */
offers: Offer[];
offers: CollectionOffer[];
};

/**
Expand Down Expand Up @@ -115,6 +156,34 @@ export type GetOrdersResponse = QueryCursors & {
orders: OrderV2[];
};

/**
* Response from OpenSea API for fetching offers.
* @category API Response Types
*/
export type GetOffersResponse = QueryCursors & {
offers: Offer[];
};

/**
* Response from OpenSea API for fetching listings.
* @category API Response Types
*/
export type GetListingsResponse = QueryCursors & {
listings: Listing[];
};

/**
* Response from OpenSea API for fetching a best offer.
* @category API Response Types
*/
export type GetBestOfferResponse = Offer | CollectionOffer;

/**
* Response from OpenSea API for fetching a best listing.
* @category API Response Types
*/
export type GetBestListingResponse = Listing;

/**
* Response from OpenSea API for fetching payment tokens.
* @category API Response Types
Expand Down
5 changes: 3 additions & 2 deletions src/orders/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export type ProtocolData =
OrderProtocolToProtocolData[keyof OrderProtocolToProtocolData];

// Protocol agnostic order data
type OrderType = "basic" | "dutch" | "english" | "criteria";
export type OrderType = "basic" | "dutch" | "english" | "criteria";
export type OrderSide = "ask" | "bid";
type OrderFee = {
account: OpenSeaAccount;
Expand Down Expand Up @@ -98,8 +98,9 @@ export type OrderAPIOptions = {
};

export type OrdersQueryOptions = OrderAPIOptions & {
limit: number;
limit?: number;
cursor?: string;
next?: string;

paymentTokenAddress?: string;
maker?: string;
Expand Down
22 changes: 22 additions & 0 deletions src/orders/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,28 @@ export const getOrdersAPIPath = (
return `/v2/orders/${chain}/${protocol}/${sidePath}`;
};

export const getAllOffersAPIPath = (collectionSlug: string) => {
return `/v2/offers/collection/${collectionSlug}/all`;
};

export const getAllListingsAPIPath = (collectionSlug: string) => {
return `/v2/listings/collection/${collectionSlug}/all`;
};

export const getBestOfferAPIPath = (
collectionSlug: string,
tokenId: string | number,
) => {
return `/v2/offers/collection/${collectionSlug}/nfts/${tokenId}/best`;
};

export const getBestListingAPIPath = (
collectionSlug: string,
tokenId: string | number,
) => {
return `/v2/listings/collection/${collectionSlug}/nfts/${tokenId}/best`;
};

export const getCollectionPath = (slug: string) => {
return `/api/v1/collection/${slug}`;
};
Expand Down
Loading

0 comments on commit d893417

Please sign in to comment.