Skip to content

Commit

Permalink
Add 4 new endpoints and support multichain (#1055)
Browse files Browse the repository at this point in the history
The API has added 4 new multichain endpoints which are being implemented here:

* List NFTS by Collection
* List NFTS by Contract
* Get an NFT
* Refresh Metadata for an NFT
  • Loading branch information
thenerdassassin authored Jul 6, 2023
1 parent 876845a commit 3ab45e7
Show file tree
Hide file tree
Showing 16 changed files with 388 additions and 182 deletions.
154 changes: 138 additions & 16 deletions src/api.ts → src/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import { ethers } from "ethers";
import { API_BASE_MAINNET, API_BASE_TESTNET, API_PATH } from "./constants";
import {
BuildOfferResponse,
FulfillmentDataResponse,
PostOfferResponse,
GetCollectionResponse,
ListNFTsResponse,
GetNFTResponse,
} from "./types";
import { API_BASE_MAINNET, API_BASE_TESTNET, API_PATH } from "../constants";
import {
FulfillmentDataResponse,
OrderAPIOptions,
OrderSide,
OrdersPostQueryResponse,
OrdersQueryOptions,
OrdersQueryResponse,
OrderV2,
PostOfferResponse,
ProtocolData,
QueryCursors,
} from "./orders/types";
} from "../orders/types";
import {
serializeOrdersQueryOptions,
getOrdersAPIPath,
Expand All @@ -26,7 +30,11 @@ import {
getCollectionPath,
getPostCollectionOfferPath,
getPostCollectionOfferPayload,
} from "./orders/utils";
getListNFTsByCollectionPath,
getListNFTsByContractPath,
getNFTPath,
getRefreshMetadataPath,
} from "../orders/utils";
import {
Chain,
OpenSeaAPIConfig,
Expand All @@ -37,14 +45,15 @@ import {
OpenSeaCollection,
OpenSeaFungibleToken,
OpenSeaFungibleTokenQuery,
} from "./types";
} from "../types";
import {
assetBundleFromJSON,
assetFromJSON,
delay,
tokenFromJSON,
collectionFromJSON,
} from "./utils/utils";
isTestChain,
} from "../utils/utils";

export class OpenSeaAPI {
/**
Expand Down Expand Up @@ -73,15 +82,9 @@ export class OpenSeaAPI {
this.apiKey = config.apiKey;
this.chain = config.chain ?? Chain.Mainnet;

switch (config.chain) {
case Chain.Goerli:
this.apiBaseUrl = config.apiBaseUrl ?? API_BASE_TESTNET;
break;
case Chain.Mainnet:
default:
this.apiBaseUrl = config.apiBaseUrl ?? API_BASE_MAINNET;
break;
}
this.apiBaseUrl = isTestChain(this.chain)
? API_BASE_TESTNET
: API_BASE_MAINNET;

// Debugging: default to nothing
this.logger = logger ?? ((arg: string) => arg);
Expand Down Expand Up @@ -269,6 +272,97 @@ export class OpenSeaAPI {
return assetFromJSON(json);
}

/**
* Fetch multiple NFTs for a collection from the API
* @param slug The collection you would like to list NFTs for
* @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 retries Number of times to retry if the service is unavailable for any reason
*/
public async getNFTsByCollection(
slug: string,
limit: number | undefined = undefined,
next: string | undefined = undefined,
retries = 1
): Promise<ListNFTsResponse> {
let response;
try {
response = await this.get<ListNFTsResponse>(
getListNFTsByCollectionPath(slug),
{
limit,
next,
}
);
} catch (error) {
_throwOrContinue(error, retries);
await delay(1000);
return this.getNFTsByCollection(slug, limit, next, retries - 1);
}

return response;
}

/**
* Fetch multiple NFTs for a contract from the API
* @param chain chain the contract is deployed to
* @param address address of the smart contract
* @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 retries Number of times to retry if the service is unavailable for any reason
*/
public async getNFTsByContract(
chain: Chain,
address: string,
limit: number | undefined = undefined,
next: string | undefined = undefined,
retries = 1
): Promise<ListNFTsResponse> {
let response;
try {
response = await this.get<ListNFTsResponse>(
getListNFTsByContractPath(chain, address),
{
limit,
next,
}
);
} catch (error) {
_throwOrContinue(error, retries);
await delay(1000);
return this.getNFTsByContract(chain, address, limit, next, retries - 1);
}

return response;
}

/**
* Fetch metadata, traits, ownership information, and rarity for an NFT from the API
* @param chain chain the contract is deployed to
* @param address address of the smart contract
* @param identifierthe identifier of the NFT (i.e. token_id)
* @param retries Number of times to retry if the service is unavailable for any reason
*/
public async getNFT(
chain: Chain,
address: string,
identifier: string,
retries = 1
): Promise<GetNFTResponse> {
let response;
try {
response = await this.get<GetNFTResponse>(
getNFTPath(chain, address, identifier)
);
} catch (error) {
_throwOrContinue(error, retries);
await delay(1000);
return this.getNFT(chain, address, identifier, retries - 1);
}

return response;
}

/**
* Fetch list of assets from the API, returning the page of assets and the count of total assets
* @param query Query to use for getting orders. A subset of parameters on the `OpenSeaAssetJSON` type is supported
Expand Down Expand Up @@ -375,6 +469,34 @@ export class OpenSeaAPI {
};
}

/**
* Used to force refresh the metadata for an NFT from the API
* @param chain chain the contract is deployed to
* @param address address of the smart contract
* @param identifierthe identifier of the NFT (i.e. token_id)
* @param retries Number of times to retry if the service is unavailable for any reason
*/
public async refreshNFTMetadata(
chain: Chain,
address: string,
identifier: string,
retries = 1
): Promise<unknown> {
let response;
try {
response = await this.post(
getRefreshMetadataPath(chain, address, identifier),
{}
);
} catch (error) {
_throwOrContinue(error, retries);
await delay(1000);
return this.refreshNFTMetadata(chain, address, identifier, retries - 1);
}

return response;
}

/**
* Get JSON data from API, sending auth token in headers
* @param apiPath Path to URL endpoint under API
Expand Down
1 change: 1 addition & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./api";
61 changes: 61 additions & 0 deletions src/api/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { ConsiderationItem } from "@opensea/seaport-js/lib/types";
import { ProtocolData } from "src/orders/types";

export type BuildOfferResponse = {
partialParameters: PartialParameters;
};

type PartialParameters = {
consideration: ConsiderationItem[];
zone: string;
zoneHash: string;
};

type Criteria = {
collection: CollectionCriteria;
contract?: ContractCriteria;
};

type CollectionCriteria = {
slug: string;
};

type ContractCriteria = {
address: string;
};

export type GetCollectionResponse = {
collection: object;
};

export type PostOfferResponse = {
order_hash: string;
chain: string;
criteria: Criteria;
protocol_data: ProtocolData;
protocol_address: string;
};

export type ListNFTsResponse = {
nfts: NFT[];
next: string;
};

export type GetNFTResponse = {
nft: NFT;
};

type NFT = {
identifier: string;
collection: string;
contract: string;
token_standard: string;
name: string;
description: string;
image_url: string;
metadata_url: string;
created_at: string;
updated_at: string;
is_disabled: boolean;
is_nsfw: boolean;
};
15 changes: 2 additions & 13 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Chain } from "./types";
import { ethers } from "ethers";

export const INVERSE_BASIS_POINT = 10_000; // 100 basis points per 1%
export const MAX_EXPIRATION_MONTHS = 1;
Expand All @@ -8,18 +8,7 @@ export const API_BASE_MAINNET = "https://api.opensea.io";
export const API_BASE_TESTNET = "https://testnets-api.opensea.io";
export const API_PATH = `/api/v${API_VERSION}`;

export const MERKLE_VALIDATOR_MAINNET =
"0xbaf2127b49fc93cbca6269fade0f7f31df4c88a7";

export const WETH_ADDRESS_BY_NETWORK = {
[Chain.Mainnet]: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
[Chain.Goerli]: "0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6",
} as const;

export const DEFAULT_ZONE_BY_NETWORK = {
[Chain.Mainnet]: "0x0000000000000000000000000000000000000000",
[Chain.Goerli]: "0x0000000000000000000000000000000000000000",
} as const;
export const DEFAULT_ZONE = ethers.constants.AddressZero;

export const SHARED_STOREFRONT_ADDRESS_MAINNET =
"0x495f947276749ce646f68ac8c248420045cb7b5e";
Expand Down
40 changes: 1 addition & 39 deletions src/orders/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import {
ConsiderationItem,
OrderWithCounter,
} from "@opensea/seaport-js/lib/types";
import { OrderWithCounter } from "@opensea/seaport-js/lib/types";
import { BigNumber } from "ethers";
import { OpenSeaAccount, OpenSeaAssetBundle } from "../types";

Expand Down Expand Up @@ -62,41 +59,6 @@ type Transaction = {
input_data: object;
};

export type BuildOfferResponse = {
partialParameters: PartialParameters;
};

export type GetCollectionResponse = {
collection: object;
};

export type PostOfferResponse = {
order_hash: string;
chain: string;
criteria: Criteria;
protocol_data: ProtocolData;
protocol_address: string;
};

type Criteria = {
collection: CollectionCriteria;
contract?: ContractCriteria;
};

type CollectionCriteria = {
slug: string;
};

type ContractCriteria = {
address: string;
};

type PartialParameters = {
consideration: ConsiderationItem[];
zone: string;
zoneHash: string;
};

// API query types
type OpenOrderOrderingOption = "created_date" | "eth_price";
type OrderByDirection = "asc" | "desc";
Expand Down
Loading

0 comments on commit 3ab45e7

Please sign in to comment.