opensea-js
Version:
TypeScript SDK for the OpenSea marketplace helps developers build new experiences using NFTs and our marketplace data
237 lines (222 loc) • 6.22 kB
text/typescript
import {
getOrdersAPIPath,
getOrderByHashPath,
getCancelOrderPath,
} from "./apiPaths";
import {
GetOrdersResponse,
CancelOrderResponse,
GetOrderByHashResponse,
} from "./types";
import {
FulfillmentDataResponse,
OrderAPIOptions,
OrdersPostQueryResponse,
OrdersQueryOptions,
OrdersQueryResponse,
OrderV2,
ProtocolData,
} from "../orders/types";
import {
serializeOrdersQueryOptions,
deserializeOrder,
getFulfillmentDataPath,
getFulfillListingPayload,
getFulfillOfferPayload,
} from "../orders/utils";
import { Chain, OrderSide } from "../types";
import { Fetcher } from "./fetcher";
/**
* Order-related API operations
*/
export class OrdersAPI {
constructor(
private fetcher: Fetcher,
private chain: Chain,
) {}
/**
* Gets an order from API based on query options.
*/
async getOrder({
side,
protocol = "seaport",
orderDirection = "desc",
orderBy = "created_date",
...restOptions
}: Omit<OrdersQueryOptions, "limit">): Promise<OrderV2> {
// Validate eth_price orderBy requires additional parameters
if (orderBy === "eth_price") {
if (
!restOptions.assetContractAddress ||
!restOptions.tokenIds ||
restOptions.tokenIds.length === 0
) {
throw new Error(
'When using orderBy: "eth_price", you must provide both asset_contract_address and token_ids parameters',
);
}
}
const { orders } = await this.fetcher.get<OrdersQueryResponse>(
getOrdersAPIPath(this.chain, protocol, side),
serializeOrdersQueryOptions({
limit: 1,
orderBy,
orderDirection,
...restOptions,
}),
);
if (orders.length === 0) {
throw new Error("Not found: no matching order found");
}
return deserializeOrder(orders[0]);
}
/**
* Gets a single order by its order hash.
* Returns the raw API response which can be either an Offer or Listing.
*/
async getOrderByHash(
orderHash: string,
protocolAddress: string,
chain: Chain = this.chain,
): Promise<GetOrderByHashResponse> {
const response = await this.fetcher.get<{
order: GetOrderByHashResponse;
}>(getOrderByHashPath(chain, protocolAddress, orderHash));
return response.order;
}
/**
* Gets a list of orders from API based on query options.
*/
async getOrders({
side,
protocol = "seaport",
orderDirection = "desc",
orderBy = "created_date",
pageSize = 20,
...restOptions
}: Omit<OrdersQueryOptions, "limit"> & {
pageSize?: number;
}): Promise<GetOrdersResponse> {
// Validate eth_price orderBy requires additional parameters
if (orderBy === "eth_price") {
if (
!restOptions.assetContractAddress ||
!restOptions.tokenIds ||
restOptions.tokenIds.length === 0
) {
throw new Error(
'When using orderBy: "eth_price", you must provide both asset_contract_address and token_ids parameters',
);
}
}
const response = await this.fetcher.get<OrdersQueryResponse>(
getOrdersAPIPath(this.chain, protocol, side),
serializeOrdersQueryOptions({
limit: pageSize,
orderBy,
orderDirection,
...restOptions,
}),
);
return {
...response,
orders: response.orders.map(deserializeOrder),
};
}
/**
* Generate the data needed to fulfill a listing or an offer onchain.
*/
async generateFulfillmentData(
fulfillerAddress: string,
orderHash: string,
protocolAddress: string,
side: OrderSide,
assetContractAddress?: string,
tokenId?: string,
unitsToFill?: string,
recipientAddress?: string,
includeOptionalCreatorFees: boolean = false,
): Promise<FulfillmentDataResponse> {
let payload: object | null = null;
if (side === OrderSide.LISTING) {
payload = getFulfillListingPayload(
fulfillerAddress,
orderHash,
protocolAddress,
this.chain,
assetContractAddress,
tokenId,
unitsToFill,
recipientAddress,
includeOptionalCreatorFees,
);
} else {
payload = getFulfillOfferPayload(
fulfillerAddress,
orderHash,
protocolAddress,
this.chain,
assetContractAddress,
tokenId,
unitsToFill,
includeOptionalCreatorFees,
);
}
const response = await this.fetcher.post<FulfillmentDataResponse>(
getFulfillmentDataPath(side),
payload,
);
return response;
}
/**
* Post an order to OpenSea.
*/
async postOrder(
order: ProtocolData,
apiOptions: OrderAPIOptions,
): Promise<OrderV2> {
const { protocol = "seaport", side, protocolAddress } = apiOptions;
// Validate required fields
if (!side) {
throw new Error("apiOptions.side is required");
}
if (!protocolAddress) {
throw new Error("apiOptions.protocolAddress is required");
}
if (!order) {
throw new Error("order data is required");
}
// Validate protocol value
if (protocol !== "seaport") {
throw new Error("Currently only 'seaport' protocol is supported");
}
// Validate side value
if (side !== "ask" && side !== "bid") {
throw new Error("side must be either 'ask' or 'bid'");
}
// Validate protocolAddress format
if (!/^0x[a-fA-F0-9]{40}$/.test(protocolAddress)) {
throw new Error("Invalid protocol address format");
}
const response = await this.fetcher.post<OrdersPostQueryResponse>(
getOrdersAPIPath(this.chain, protocol, side),
{ ...order, protocol_address: protocolAddress },
);
return deserializeOrder(response.order);
}
/**
* Offchain cancel an order, offer or listing, by its order hash when protected by the SignedZone.
*/
async offchainCancelOrder(
protocolAddress: string,
orderHash: string,
chain: Chain = this.chain,
offererSignature?: string,
): Promise<CancelOrderResponse> {
const response = await this.fetcher.post<CancelOrderResponse>(
getCancelOrderPath(chain, protocolAddress, orderHash),
{ offererSignature },
);
return response;
}
}