opensea-js
Version:
TypeScript SDK for the OpenSea marketplace helps developers build new experiences using NFTs and our marketplace data
535 lines • 24.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OpenSeaAPI = void 0;
const ethers_1 = require("ethers");
const apiPaths_1 = require("./apiPaths");
const types_1 = require("./types");
const constants_1 = require("../constants");
const utils_1 = require("../orders/utils");
const types_2 = require("../types");
const utils_2 = require("../utils/utils");
/**
* The API class for the OpenSea SDK.
* @category Main Classes
*/
class OpenSeaAPI {
/**
* Create an instance of the OpenSeaAPI
* @param config OpenSeaAPIConfig for setting up the API, including an optional API key, Chain name, and base URL
* @param logger Optional function for logging debug strings before and after requests are made. Defaults to no logging
*/
constructor(config, logger) {
/**
* Default size to use for fetching orders
*/
this.pageSize = 20;
this.apiKey = config.apiKey;
this.chain = config.chain ?? types_2.Chain.Mainnet;
if (config.apiBaseUrl) {
this.apiBaseUrl = config.apiBaseUrl;
}
else {
this.apiBaseUrl = constants_1.API_BASE_MAINNET;
}
// Debugging: default to nothing
this.logger = logger ?? ((arg) => arg);
}
/**
* Gets an order from API based on query options.
* @param options
* @param options.side The side of the order (listing or offer)
* @param options.protocol The protocol, typically seaport, to query orders for
* @param options.orderDirection The direction to sort the orders
* @param options.orderBy The field to sort the orders by
* @param options.limit The number of orders to retrieve
* @param options.maker Filter by the wallet address of the order maker
* @param options.taker Filter by wallet address of the order taker
* @param options.asset_contract_address Address of the NFT's contract
* @param options.token_ids String array of token IDs to filter by.
* @param options.listed_after Filter by orders listed after the Unix epoch timestamp in seconds
* @param options.listed_before Filter by orders listed before the Unix epoch timestamp in seconds
* @returns The first {@link OrderV2} returned by the API
*
* @throws An error if there are no matching orders.
*/
async getOrder({ side, protocol = "seaport", orderDirection = "desc", orderBy = "created_date", ...restOptions }) {
const { orders } = await this.get((0, apiPaths_1.getOrdersAPIPath)(this.chain, protocol, side), (0, utils_1.serializeOrdersQueryOptions)({
limit: 1,
orderBy,
orderDirection,
...restOptions,
}));
if (orders.length === 0) {
throw new Error("Not found: no matching order found");
}
return (0, utils_1.deserializeOrder)(orders[0]);
}
/**
* Gets a list of orders from API based on query options.
* @param options
* @param options.side The side of the order (buy or sell)
* @param options.protocol The protocol, typically seaport, to query orders for
* @param options.orderDirection The direction to sort the orders
* @param options.orderBy The field to sort the orders by
* @param options.limit The number of orders to retrieve
* @param options.maker Filter by the wallet address of the order maker
* @param options.taker Filter by wallet address of the order taker
* @param options.asset_contract_address Address of the NFT's contract
* @param options.token_ids String array of token IDs to filter by.
* @param options.listed_after Filter by orders listed after the Unix epoch timestamp in seconds
* @param options.listed_before Filter by orders listed before the Unix epoch timestamp in seconds
* @returns The {@link GetOrdersResponse} returned by the API.
*/
async getOrders({ side, protocol = "seaport", orderDirection = "desc", orderBy = "created_date", ...restOptions }) {
const response = await this.get((0, apiPaths_1.getOrdersAPIPath)(this.chain, protocol, side), (0, utils_1.serializeOrdersQueryOptions)({
limit: this.pageSize,
orderBy,
orderDirection,
...restOptions,
}));
return {
...response,
orders: response.orders.map(utils_1.deserializeOrder),
};
}
/**
* 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.
*/
async getAllOffers(collectionSlug, limit, next) {
const response = await this.get((0, apiPaths_1.getAllOffersAPIPath)(collectionSlug), {
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.
*/
async getAllListings(collectionSlug, limit, next) {
const response = await this.get((0, apiPaths_1.getAllListingsAPIPath)(collectionSlug), {
limit,
next,
});
return response;
}
/**
* Gets trait offers for a given collection.
* @param collectionSlug The slug of the collection.
* @param type The name of the trait (e.g. 'Background').
* @param value The value of the trait (e.g. 'Red').
* @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.
* @param floatValue The value of the trait for decimal-based numeric traits.
* @param intValue The value of the trait for integer-based numeric traits.
* @returns The {@link GetOffersResponse} returned by the API.
*/
async getTraitOffers(collectionSlug, type, value, limit, next, floatValue, intValue) {
const response = await this.get((0, apiPaths_1.getTraitOffersPath)(collectionSlug), {
type,
value,
limit,
next,
float_value: floatValue,
int_value: intValue,
});
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.
*/
async getBestOffer(collectionSlug, tokenId) {
const response = await this.get((0, apiPaths_1.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.
*/
async getBestListing(collectionSlug, tokenId) {
const response = await this.get((0, apiPaths_1.getBestListingAPIPath)(collectionSlug, tokenId));
return response;
}
/**
* Gets the best 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.
*/
async getBestListings(collectionSlug, limit, next) {
const response = await this.get((0, apiPaths_1.getBestListingsAPIPath)(collectionSlug), {
limit,
next,
});
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
* @param orderHash The hash of the order to fulfill
* @param protocolAddress The address of the seaport contract
* @side The side of the order (buy or sell)
* @returns The {@link FulfillmentDataResponse}
*/
async generateFulfillmentData(fulfillerAddress, orderHash, protocolAddress, side) {
let payload = null;
if (side === types_2.OrderSide.LISTING) {
payload = (0, utils_1.getFulfillListingPayload)(fulfillerAddress, orderHash, protocolAddress, this.chain);
}
else {
payload = (0, utils_1.getFulfillOfferPayload)(fulfillerAddress, orderHash, protocolAddress, this.chain);
}
const response = await this.post((0, utils_1.getFulfillmentDataPath)(side), payload);
return response;
}
/**
* Post an order to OpenSea.
* @param order The order to post
* @param apiOptions
* @param apiOptions.protocol The protocol, typically seaport, to post the order to.
* @param apiOptions.side The side of the order (buy or sell).
* @param apiOptions.protocolAddress The address of the seaport contract.
* @param options
* @returns The {@link OrderV2} posted to the API.
*/
async postOrder(order, apiOptions) {
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.post((0, apiPaths_1.getOrdersAPIPath)(this.chain, protocol, side), { ...order, protocol_address: protocolAddress });
return (0, utils_1.deserializeOrder)(response.order);
}
/**
* Build a OpenSea collection offer.
* @param offererAddress The wallet address which is creating the offer.
* @param quantity The number of NFTs requested in the offer.
* @param collectionSlug The slug (identifier) of the collection to build the offer for.
* @param offerProtectionEnabled Build the offer on OpenSea's signed zone to provide offer protections from receiving an item which is disabled from trading.
* @param traitType If defined, the trait name to create the collection offer for.
* @param traitValue If defined, the trait value to create the collection offer for.
* @returns The {@link BuildOfferResponse} returned by the API.
*/
async buildOffer(offererAddress, quantity, collectionSlug, offerProtectionEnabled = true, traitType, traitValue) {
if (traitType || traitValue) {
if (!traitType || !traitValue) {
throw new Error("Both traitType and traitValue must be defined if one is defined.");
}
}
const payload = (0, utils_1.getBuildCollectionOfferPayload)(offererAddress, quantity, collectionSlug, offerProtectionEnabled, traitType, traitValue);
const response = await this.post((0, apiPaths_1.getBuildOfferPath)(), payload);
return response;
}
/**
* Get a list collection offers for a given slug.
* @param slug The slug (identifier) of the collection to list offers for
* @returns The {@link ListCollectionOffersResponse} returned by the API.
*/
async getCollectionOffers(slug) {
return await this.get((0, apiPaths_1.getCollectionOffersPath)(slug));
}
/**
* Post a collection offer to OpenSea.
* @param order The collection offer to post.
* @param slug The slug (identifier) of the collection to post the offer for.
* @param traitType If defined, the trait name to create the collection offer for.
* @param traitValue If defined, the trait value to create the collection offer for.
* @returns The {@link Offer} returned to the API.
*/
async postCollectionOffer(order, slug, traitType, traitValue) {
const payload = (0, utils_1.getPostCollectionOfferPayload)(slug, order, traitType, traitValue);
return await this.post((0, apiPaths_1.getPostCollectionOfferPath)(), payload);
}
/**
* Fetch multiple NFTs for a collection.
* @param slug The slug (identifier) of the collection
* @param limit The number of NFTs to retrieve. Must be greater than 0 and less than 51.
* @param next Cursor to retrieve the next page of NFTs
* @returns The {@link ListNFTsResponse} returned by the API.
*/
async getNFTsByCollection(slug, limit = undefined, next = undefined) {
const response = await this.get((0, apiPaths_1.getListNFTsByCollectionPath)(slug), {
limit,
next,
});
return response;
}
/**
* Fetch multiple NFTs for a contract.
* @param address The NFT's contract address.
* @param limit The number of NFTs to retrieve. Must be greater than 0 and less than 51.
* @param next Cursor to retrieve the next page of NFTs.
* @param chain The NFT's chain.
* @returns The {@link ListNFTsResponse} returned by the API.
*/
async getNFTsByContract(address, limit = undefined, next = undefined, chain = this.chain) {
const response = await this.get((0, apiPaths_1.getListNFTsByContractPath)(chain, address), {
limit,
next,
});
return response;
}
/**
* Fetch NFTs owned by an account.
* @param address The address of the account
* @param limit The number of NFTs to retrieve. Must be greater than 0 and less than 51.
* @param next Cursor to retrieve the next page of NFTs
* @param chain The chain to query. Defaults to the chain set in the constructor.
* @returns The {@link ListNFTsResponse} returned by the API.
*/
async getNFTsByAccount(address, limit = undefined, next = undefined, chain = this.chain) {
const response = await this.get((0, apiPaths_1.getListNFTsByAccountPath)(chain, address), {
limit,
next,
});
return response;
}
/**
* Fetch metadata, traits, ownership information, and rarity for a single NFT.
* @param address The NFT's contract address.
* @param identifier the identifier of the NFT (i.e. Token ID)
* @param chain The NFT's chain.
* @returns The {@link GetNFTResponse} returned by the API.
*/
async getNFT(address, identifier, chain = this.chain) {
const response = await this.get((0, apiPaths_1.getNFTPath)(chain, address, identifier));
return response;
}
/**
* Fetch an OpenSea collection.
* @param slug The slug (identifier) of the collection.
* @returns The {@link OpenSeaCollection} returned by the API.
*/
async getCollection(slug) {
const path = (0, apiPaths_1.getCollectionPath)(slug);
const response = await this.get(path);
return (0, utils_2.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.
*/
async getCollections(orderBy = types_1.CollectionOrderByOption.CREATED_DATE, chain, creatorUsername, includeHidden = false, limit, next) {
const path = (0, apiPaths_1.getCollectionsPath)();
const args = {
order_by: orderBy,
chain,
creator_username: creatorUsername,
include_hidden: includeHidden,
limit,
next,
};
const response = await this.get(path, args);
response.collections = response.collections.map((collection) => (0, utils_2.collectionFromJSON)(collection));
return response;
}
/**
* Fetch stats for an OpenSea collection.
* @param slug The slug (identifier) of the collection.
* @returns The {@link OpenSeaCollection} returned by the API.
*/
async getCollectionStats(slug) {
const path = (0, apiPaths_1.getCollectionStatsPath)(slug);
const response = await this.get(path);
return response;
}
/**
* Fetch a payment token.
* @param query Query to use for getting tokens. See {@link OpenSeaPaymentTokenQuery}.
* @param next The cursor for the next page of results. This is returned from a previous request.
* @returns The {@link OpenSeaPaymentToken} returned by the API.
*/
async getPaymentToken(address, chain = this.chain) {
const json = await this.get((0, apiPaths_1.getPaymentTokenPath)(chain, address));
return (0, utils_2.paymentTokenFromJSON)(json);
}
/**
* Fetch account for an address.
* @param query Query to use for getting tokens. See {@link OpenSeaPaymentTokenQuery}.
* @param next The cursor for the next page of results. This is returned from a previous request.
* @returns The {@link GetAccountResponse} returned by the API.
*/
async getAccount(address) {
const json = await this.get((0, apiPaths_1.getAccountPath)(address));
return (0, utils_2.accountFromJSON)(json);
}
/**
* Force refresh the metadata for an NFT.
* @param address The address of the NFT's contract.
* @param identifier The identifier of the NFT.
* @param chain The chain where the NFT is located.
* @returns The response from the API.
*/
async refreshNFTMetadata(address, identifier, chain = this.chain) {
const response = await this.post((0, apiPaths_1.getRefreshMetadataPath)(chain, address, identifier), {});
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.
* @param offererSignature An EIP-712 signature from the offerer of the order.
* If this is not provided, the user associated with the API Key will be checked instead.
* The signature must be a EIP-712 signature consisting of the order's Seaport contract's
* name, version, address, and chain. The struct to sign is `OrderHash` containing a
* single bytes32 field.
* @returns The response from the API.
*/
async offchainCancelOrder(protocolAddress, orderHash, chain = this.chain, offererSignature) {
const response = await this.post((0, apiPaths_1.getCancelOrderPath)(chain, protocolAddress, orderHash), { offererSignature });
return response;
}
/**
* Generic fetch method for any API endpoint
* @param apiPath Path to URL endpoint under API
* @param query URL query params. Will be used to create a URLSearchParams object.
* @returns @typeParam T The response from the API.
*/
async get(apiPath, query = {}) {
const qs = this.objectToSearchParams(query);
const url = `${this.apiBaseUrl}${apiPath}?${qs}`;
return await this._fetch(url);
}
/**
* 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.
* @returns @typeParam T The response from the API.
*/
async post(apiPath, body, opts) {
const url = `${this.apiBaseUrl}${apiPath}`;
return await this._fetch(url, opts, body);
}
objectToSearchParams(params = {}) {
const urlSearchParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value && Array.isArray(value)) {
value.forEach((item) => item && urlSearchParams.append(key, item));
}
else if (value) {
urlSearchParams.append(key, value);
}
});
return urlSearchParams.toString();
}
/**
* Get from an API Endpoint, sending auth token in headers
* @param opts ethers ConnectionInfo, similar to Fetch API
* @param body Optional body to send. If set, will POST, otherwise GET
*/
async _fetch(url, headers, body) {
// Create the fetch request
const req = new ethers_1.ethers.FetchRequest(url);
// Set the headers
headers = {
"x-app-id": "opensea-js",
...(this.apiKey ? { "X-API-KEY": this.apiKey } : {}),
...headers,
};
for (const [key, value] of Object.entries(headers)) {
req.setHeader(key, value);
}
// Set the body if provided
if (body) {
req.body = body;
}
// Set the throttle params
req.setThrottleParams({ slotInterval: 1000 });
this.logger(`Sending request: ${url} ${JSON.stringify({
request: req,
headers: req.headers,
})}`);
const response = await req.send();
if (!response.ok()) {
// Handle rate limit errors (429 Too Many Requests and 599 custom rate limit)
if (response.statusCode === 599 || response.statusCode === 429) {
throw this._createRateLimitError(response);
}
// If an errors array is returned, throw with the error messages.
const errors = response.bodyJson?.errors;
if (errors?.length > 0) {
let errorMessage = errors.join(", ");
if (errorMessage === "[object Object]") {
errorMessage = JSON.stringify(errors);
}
throw new Error(`Server Error: ${errorMessage}`);
}
else {
// Otherwise, let ethers throw a SERVER_ERROR since it will include
// more context about the request and response.
response.assertOk();
}
}
return response.bodyJson;
}
/**
* Parses the retry-after header from the response with robust error handling.
* @param response The HTTP response object from the API
* @returns The retry-after value in seconds, or undefined if not present or invalid
*/
_parseRetryAfter(response) {
const retryAfterHeader = response.headers["retry-after"] || response.headers["Retry-After"];
if (retryAfterHeader) {
const parsed = parseInt(retryAfterHeader, 10);
return isNaN(parsed) || parsed <= 0 ? undefined : parsed;
}
return undefined;
}
/**
* Creates a rate limit error with retry-after information for backwards compatibility.
* @param response The HTTP response object from the API
* @returns An enhanced Error object with retryAfter and responseBody properties
*/
_createRateLimitError(response) {
const retryAfter = this._parseRetryAfter(response);
const error = new Error(`${response.statusCode} ${response.statusMessage}`);
// Add retry-after information to the error object for backwards compatibility
error.retryAfter = retryAfter;
error.responseBody = response.bodyJson;
return error;
}
}
exports.OpenSeaAPI = OpenSeaAPI;
//# sourceMappingURL=api.js.map