Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add APIs: Cancel Order (offchain), List Collections #1460

Merged
merged 1 commit into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading