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

API Validation Enhancement api.ts #1598

Closed
wants to merge 19 commits into from
Closed
3 changes: 1 addition & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ module.exports = {
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-empty-interface": "off",
"@typescript-eslint/no-var-requires": "off",
"import/no-unused-modules": "off",
"import/order": [
"error",
{
Expand All @@ -51,9 +52,7 @@ module.exports = {
},
},
],
"import/no-unused-modules": [1, { unusedExports: true }],
"no-control-regex": "off",

"object-shorthand": ["error", "always"],
},
settings: {
Expand Down
99 changes: 67 additions & 32 deletions src/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,33 @@
import { ethers } from "ethers";
import { API_BASE_MAINNET, API_BASE_TESTNET } from "../constants";
import {
ProtocolData,
OrdersQueryOptions,
OrdersQueryResponse,
OrderV2,
FulfillmentDataResponse,
OrderAPIOptions,
OrdersPostQueryResponse,
} from "../orders/types";
import {
getFulfillListingPayload,
getFulfillOfferPayload,
getFulfillmentDataPath,
getBuildCollectionOfferPayload,
getPostCollectionOfferPayload,
serializeOrdersQueryOptions,
deserializeOrder,
} from "../orders/utils";
import {
Chain,
OpenSeaAPIConfig,
OpenSeaCollection,
OpenSeaCollectionStats,
OpenSeaPaymentToken,
OpenSeaAccount,
OrderSide,
OrderProtocol,
} from "../types";
import {
getCollectionPath,
getCollectionsPath,
Expand Down Expand Up @@ -38,34 +67,6 @@ import {
CancelOrderResponse,
GetCollectionsArgs,
} from "./types";
import { API_BASE_MAINNET, API_BASE_TESTNET } from "../constants";
import {
FulfillmentDataResponse,
OrderAPIOptions,
OrdersPostQueryResponse,
OrdersQueryOptions,
OrdersQueryResponse,
OrderV2,
ProtocolData,
} from "../orders/types";
import {
serializeOrdersQueryOptions,
deserializeOrder,
getFulfillmentDataPath,
getFulfillListingPayload,
getFulfillOfferPayload,
getBuildCollectionOfferPayload,
getPostCollectionOfferPayload,
} from "../orders/utils";
import {
Chain,
OpenSeaAPIConfig,
OpenSeaAccount,
OpenSeaCollection,
OpenSeaCollectionStats,
OpenSeaPaymentToken,
OrderSide,
} from "../types";
import {
paymentTokenFromJSON,
collectionFromJSON,
Expand Down Expand Up @@ -135,7 +136,7 @@ export class OpenSeaAPI {
*/
public async getOrder({
side,
protocol = "seaport",
protocol = OrderProtocol.SEAPORT,
orderDirection = "desc",
orderBy = "created_date",
...restOptions
Expand Down Expand Up @@ -173,7 +174,7 @@ export class OpenSeaAPI {
*/
public async getOrders({
side,
protocol = "seaport",
protocol = OrderProtocol.SEAPORT,
orderDirection = "desc",
orderBy = "created_date",
...restOptions
Expand Down Expand Up @@ -342,8 +343,42 @@ export class OpenSeaAPI {
order: ProtocolData,
apiOptions: OrderAPIOptions,
): Promise<OrderV2> {
// TODO: Validate apiOptions. Avoid API calls that will definitely fail
const { protocol = "seaport", side, protocolAddress } = apiOptions;
// Input validation
if (!order || !apiOptions) {
throw new Error("Order and API options are required");
}

// Protocol validation
if (apiOptions.protocol && apiOptions.protocol !== OrderProtocol.SEAPORT) {
throw new Error(
`Invalid protocol specified. Must be ${OrderProtocol.SEAPORT}`,
);
}

// Side validation
if (
!apiOptions.side ||
(apiOptions.side !== OrderSide.LISTING &&
apiOptions.side !== OrderSide.OFFER)
) {
throw new Error(
`Invalid order side specified. Must be either ${OrderSide.LISTING} or ${OrderSide.OFFER}`,
);
}

// Protocol address validation
if (
!apiOptions.protocolAddress ||
!ethers.isAddress(apiOptions.protocolAddress)
) {
throw new Error("Invalid protocol address provided");
}

const {
protocol = OrderProtocol.SEAPORT,
side,
protocolAddress,
} = apiOptions;
const response = await this.post<OrdersPostQueryResponse>(
getOrdersAPIPath(this.chain, protocol, side),
{ ...order, protocol_address: protocolAddress },
Expand Down
3 changes: 1 addition & 2 deletions src/api/apiPaths.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { OrderProtocol } from "../orders/types";
import { Chain, OrderSide } from "../types";
import { Chain, OrderSide, OrderProtocol } from "../types";

export const getOrdersAPIPath = (
chain: Chain,
Expand Down
4 changes: 2 additions & 2 deletions src/api/types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { ConsiderationItem } from "@opensea/seaport-js/lib/types";
import {
import type {
OrderType,
OrderV2,
ProtocolData,
QueryCursors,
} from "../orders/types";
import { OpenSeaCollection } from "../types";
import type { OpenSeaCollection } from "../types";

/**
* Response from OpenSea API for building an offer.
Expand Down
1 change: 0 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export const MAX_EXPIRATION_MONTHS = 1;
export const API_BASE_MAINNET = "https://api.opensea.io";
export const API_BASE_TESTNET = "https://testnets-api.opensea.io";

// eslint-disable-next-line import/no-unused-modules
export const SIGNED_ZONE = "0x000056f7000000ece9003ca63978907a00ffd100";

export const ENGLISH_AUCTION_ZONE_MAINNETS =
Expand Down
11 changes: 5 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { OpenSeaSDK } from "./sdk";

/**
* @example
* // Example Setup
* ```ts
* import { ethers } from 'ethers'
* import { OpenSeaSDK, Chain } from 'opensea-js'
Expand All @@ -12,11 +11,11 @@ import { OpenSeaSDK } from "./sdk";
* })
* ```
*/
export {
// Main SDK export
OpenSeaSDK,
};

// Export main SDK
export { OpenSeaSDK };

// Export types
export * from "./types";
export * from "./api/types";
export * from "./orders/types";
export type { OrderType, ProtocolData } from "./orders/types";
55 changes: 37 additions & 18 deletions src/orders/types.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
import { BasicOrderParametersStruct } from "@opensea/seaport-js/lib/typechain-types/seaport/contracts/Seaport";
import { AdvancedOrder, OrderWithCounter } from "@opensea/seaport-js/lib/types";
import { OpenSeaAccount, OrderSide } from "../types";
import {
AdvancedOrder,
OrderWithCounter,
OrderParameters,
Order,
} from "@opensea/seaport-js/lib/types";
import { BigNumberish } from "ethers";
import { OpenSeaAccount, OrderSide, OrderProtocol } from "../types";

// Protocol data
type OrderProtocolToProtocolData = {
seaport: OrderWithCounter;
};
export type OrderProtocol = keyof OrderProtocolToProtocolData;
export type ProtocolData =
OrderProtocolToProtocolData[keyof OrderProtocolToProtocolData];
type _OrderProtocolToProtocolData = Record<
OrderProtocol,
OrderWithCounter | AdvancedOrder | BasicOrderParametersStruct
>;

export enum OrderType {
BASIC = "basic",
ENGLISH = "english",
CRITERIA = "criteria",
}

type OrderFee = {
account: OpenSeaAccount;
basisPoints: string;
};
export type ProtocolData =
| OrderWithCounter
| (Order & {
numerator: bigint;
denominator: bigint;
extraData: string;
parameters: OrderParameters & { counter: BigNumberish };
});

/**
* The latest OpenSea Order schema.
Expand Down Expand Up @@ -65,6 +67,23 @@ export type OrderV2 = {
remainingQuantity: number;
};

/**
* Represents the type of order in the OpenSea marketplace
*/
export enum OrderType {
/** Basic order type for simple transactions */
BASIC = "basic",
/** English auction order type */
ENGLISH = "english",
/** Criteria-based order type for collection offers */
CRITERIA = "criteria",
}

type OrderFee = {
account: OpenSeaAccount;
basisPoints: string;
};

export type FulfillmentDataResponse = {
protocol: string;
fulfillment_data: FulfillmentData;
Expand Down
5 changes: 3 additions & 2 deletions src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
TokenStandard,
AssetWithTokenStandard,
AssetWithTokenId,
OrderProtocol,
} from "./types";
import {
getMaxOrderExpirationTimestamp,
Expand Down Expand Up @@ -428,7 +429,7 @@ export class OpenSeaSDK {
const order = await executeAllActions();

return this.api.postOrder(order, {
protocol: "seaport",
protocol: OrderProtocol.SEAPORT,
protocolAddress: DEFAULT_SEAPORT_CONTRACT_ADDRESS,
side: OrderSide.OFFER,
});
Expand Down Expand Up @@ -552,7 +553,7 @@ export class OpenSeaSDK {
const order = await executeAllActions();

return this.api.postOrder(order, {
protocol: "seaport",
protocol: OrderProtocol.SEAPORT,
protocolAddress: DEFAULT_SEAPORT_CONTRACT_ADDRESS,
side: OrderSide.LISTING,
});
Expand Down
7 changes: 7 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,3 +375,10 @@ export interface SocialMediaAccount {
platform: string;
username: string;
}

/**
* Order protocol
*/
export enum OrderProtocol {
SEAPORT = "seaport",
}
11 changes: 7 additions & 4 deletions test/api/api.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { assert } from "chai";
import { assert, expect } from "chai";
import { suite, test } from "mocha";
import { Chain } from "../../src";
import { getWETHAddress } from "../../src/utils";
Expand All @@ -21,7 +21,9 @@ suite("API", () => {
const logPromise = new Promise<void>((resolve, reject) => {
mainAPI.logger = (log) => {
try {
assert.include(log, `"x-api-key":"${MAINNET_API_KEY}"`);
if (MAINNET_API_KEY) {
assert.include(log, `"x-api-key":"${MAINNET_API_KEY}"`);
}
resolve();
} catch (e) {
reject(e);
Expand All @@ -37,11 +39,12 @@ suite("API", () => {
});

test("API handles errors", async () => {
// 404 Not found for random token id
try {
await mainAPI.getNFT(BAYC_CONTRACT_ADDRESS, "404040");
expect.fail("Should have thrown an error");
} catch (error) {
assert.include((error as Error).message, "not found");
expect(error).to.be.an.instanceOf(Error);
expect((error as Error).message).to.include("not found");
}
});
});
Loading
Loading