Skip to content

Commit

Permalink
- add List Collections API (#1460)
Browse files Browse the repository at this point in the history
- add Cancel Order API
  • Loading branch information
ryanio authored May 8, 2024
1 parent 8e19262 commit b143166
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 2 deletions.
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": "7.1.7",
"version": "7.1.8",
"description": "TypeScript SDK for the OpenSea marketplace helps developers build new experiences using NFTs and our marketplace data",
"license": "MIT",
"author": "OpenSea Developers",
Expand Down
61 changes: 61 additions & 0 deletions src/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ethers } from "ethers";
import {
getCollectionPath,
getCollectionsPath,
getOrdersAPIPath,
getPostCollectionOfferPath,
getBuildOfferPath,
Expand All @@ -18,10 +19,12 @@ import {
getAccountPath,
getCollectionStatsPath,
getBestListingsAPIPath,
getCancelOrderPath,
} from "./apiPaths";
import {
BuildOfferResponse,
GetCollectionResponse,
GetCollectionsResponse,
ListNFTsResponse,
GetNFTResponse,
ListCollectionOffersResponse,
Expand All @@ -31,6 +34,9 @@ import {
GetOffersResponse,
GetListingsResponse,
CollectionOffer,
CollectionOrderByOption,
CancelOrderResponse,
GetCollectionsArgs,
} from "./types";
import { API_BASE_MAINNET, API_BASE_TESTNET } from "../constants";
import {
Expand Down Expand Up @@ -532,6 +538,40 @@ export class OpenSeaAPI {
return collectionFromJSON(response);
}

/**
* Fetch a list of OpenSea collections.
* @param orderBy The order to return the collections in. Default: CREATED_DATE
* @param chain The chain to filter the collections on. Default: all chains
* @param creatorUsername The creator's OpenSea username to filter the collections on.
* @param includeHidden If hidden collections should be returned. Default: false
* @param limit The limit of collections to return.
* @param next The cursor for the next page of results. This is returned from a previous request.
* @returns List of {@link OpenSeaCollection} returned by the API.
*/
public async getCollections(
orderBy: CollectionOrderByOption = CollectionOrderByOption.CREATED_DATE,
chain?: Chain,
creatorUsername?: string,
includeHidden: boolean = false,
limit?: number,
next?: string,
): Promise<GetCollectionsResponse> {
const path = getCollectionsPath();
const args: GetCollectionsArgs = {
order_by: orderBy,
chain,
creator_username: creatorUsername,
include_hidden: includeHidden,
limit,
next,
};
const response = await this.get<GetCollectionsResponse>(path, args);
response.collections = response.collections.map((collection) =>
collectionFromJSON(collection),
);
return response;
}

/**
* Fetch stats for an OpenSea collection.
* @param slug The slug (identifier) of the collection.
Expand Down Expand Up @@ -592,6 +632,27 @@ export class OpenSeaAPI {
return response;
}

/**
* Offchain cancel an order, offer or listing, by its order hash when protected by the SignedZone.
* Protocol and Chain are required to prevent hash collisions.
* Please note cancellation is only assured if a fulfillment signature was not vended prior to cancellation.
* @param protocolAddress The Seaport address for the order.
* @param orderHash The order hash, or external identifier, of the order.
* @param chain The chain where the order is located.
* @returns The response from the API.
*/
public async offchainCancelOrder(
protocolAddress: string,
orderHash: string,
chain: Chain = this.chain,
): Promise<CancelOrderResponse> {
const response = await this.post<CancelOrderResponse>(
getCancelOrderPath(chain, protocolAddress, orderHash),
{},
);
return response;
}

/**
* Generic fetch method for any API endpoint
* @param apiPath Path to URL endpoint under API
Expand Down
12 changes: 12 additions & 0 deletions src/api/apiPaths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ export const getCollectionPath = (slug: string) => {
return `/api/v2/collections/${slug}`;
};

export const getCollectionsPath = () => {
return "/api/v2/collections";
};

export const getCollectionStatsPath = (slug: string) => {
return `/api/v2/collections/${slug}/stats`;
};
Expand Down Expand Up @@ -91,3 +95,11 @@ export const getRefreshMetadataPath = (
) => {
return `/v2/chain/${chain}/contract/${address}/nfts/${identifier}/refresh`;
};

export const getCancelOrderPath = (
chain: Chain,
protocolAddress: string,
orderHash: string,
) => {
return `/v2/orders/chain/${chain}/protocol/${protocolAddress}/${orderHash}/cancel`;
};
39 changes: 39 additions & 0 deletions src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,19 @@ type ContractCriteria = {
address: string;
};

/**
* Query args for Get Collections
* @category API Query Args
*/
export interface GetCollectionsArgs {
order_by?: string;
limit?: number;
next?: string;
chain?: string;
creator_username?: string;
include_hidden?: boolean;
}

/**
* Response from OpenSea API for fetching a single collection.
* @category API Response Types
Expand All @@ -63,6 +76,24 @@ export type GetCollectionResponse = {
collection: OpenSeaCollection;
};

/**
* Response from OpenSea API for fetching a list of collections.
* @category API Response Types
*/
export type GetCollectionsResponse = QueryCursorsV2 & {
/** List of collections. See {@link OpenSeaCollection} */
collections: OpenSeaCollection[];
};

export enum CollectionOrderByOption {
CREATED_DATE = "created_date",
ONE_DAY_CHANGE = "one_day_change",
SEVEN_DAY_VOLUME = "seven_day_volume",
SEVEN_DAY_CHANGE = "seven_day_change",
NUM_OWNERS = "num_owners",
MARKET_CAP = "market_cap",
}

/**
* Base Order type shared between Listings and Offers.
* @category API Models
Expand Down Expand Up @@ -190,6 +221,14 @@ export type GetBestOfferResponse = Offer | CollectionOffer;
*/
export type GetBestListingResponse = Listing;

/**
* Response from OpenSea API for offchain canceling an order.
* @category API Response Types
*/
export type CancelOrderResponse = {
last_signature_issued_valid_until: string | null;
};

/**
* NFT type returned by OpenSea API.
* @category API Models
Expand Down
19 changes: 18 additions & 1 deletion src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -877,7 +877,7 @@ export class OpenSeaSDK {
}

/**
* Cancel an order on-chain, preventing it from ever being fulfilled.
* Cancel an order onchain, preventing it from ever being fulfilled.
* @param options
* @param options.order The order to cancel
* @param options.accountAddress The account address that will be cancelling the order.
Expand Down Expand Up @@ -916,6 +916,23 @@ export class OpenSeaSDK {
);
}

/**
* Offchain cancel an order, offer or listing, by its order hash when protected by the SignedZone.
* Protocol and Chain are required to prevent hash collisions.
* Please note cancellation is only assured if a fulfillment signature was not vended prior to cancellation.
* @param protocolAddress The Seaport address for the order.
* @param orderJash The order hash, or external identifier, of the order.
* @param chain The chain where the order is located.
* @returns The response from the API.
*/
public async offchainCancelOrder(
protocolAddress: string,
orderHash: string,
chain: Chain = this.chain,
) {
return this.api.offchainCancelOrder(protocolAddress, orderHash, chain);
}

/**
* Returns whether an order is fulfillable.
* An order may not be fulfillable if a target item's transfer function
Expand Down
23 changes: 23 additions & 0 deletions test/integration/getCollection.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { assert } from "chai";
import { suite, test } from "mocha";
import { sdk } from "./setup";
import { CollectionOrderByOption } from "../../src/api/types";
import { SafelistStatus } from "../../src/types";

suite("SDK: getCollection", () => {
Expand All @@ -18,6 +19,28 @@ suite("SDK: getCollection", () => {
);
});

test("Get Collections", async () => {
const response = await sdk.api.getCollections();
const { collections, next } = response;
assert(collections[0], "Collection should not be null");
assert(collections[0].name, "Collection name should exist");
assert(next, "Next cursor should be included");

const response2 = await sdk.api.getCollections(
CollectionOrderByOption.MARKET_CAP,
);
const { collections: collectionsByMarketCap, next: nextByMarketCap } =
response2;
assert(collectionsByMarketCap[0], "Collection should not be null");
assert(collectionsByMarketCap[0].name, "Collection name should exist");
assert(nextByMarketCap, "Next cursor should be included");

assert(
collectionsByMarketCap[0].name != collections[0].name,
"Collection order should differ",
);
});

test("Get Collection Stats", async () => {
const slug = "cool-cats-nft";
const stats = await sdk.api.getCollectionStats(slug);
Expand Down
10 changes: 10 additions & 0 deletions test/integration/postOrder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,16 @@ suite("SDK: order posting", () => {
};
const offerResponse = await sdk.createCollectionOffer(postOrderRequest);
expect(offerResponse).to.exist.and.to.have.property("protocol_data");

// Cancel the order
const { protocol_address, order_hash } = offerResponse!;
const cancelResponse = await sdk.offchainCancelOrder(
protocol_address,
order_hash,
);
expect(cancelResponse).to.exist.and.to.have.property(
"last_signature_issued_valid_until",
);
});

test("Post Collection Offer - Polygon", async () => {
Expand Down

0 comments on commit b143166

Please sign in to comment.