Skip to content

Commit

Permalink
add trait offers (#1433)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanio authored Apr 3, 2024
1 parent 5946d72 commit d5740e4
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 5 deletions.
4 changes: 4 additions & 0 deletions developerDocs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ The units for `startAmount` are Ether (ETH). If you want to specify another ERC-

See [Listening to Events](#listening-to-events) to respond to the setup transactions that occur the first time a user sells an item.

### Creating Collection and Trait Offers

Criteria offers, consisting of collection and trait offers, are supported with `openseaSDK.createCollectionOffer()`. For trait offers, include `traitType` as the trait name and `traitValue` as the required value for the offer.

#### Creating English Auctions

English Auctions are auctions that start at a small amount (we recommend even doing 0!) and increase with every bid. At expiration time, the item sells to the highest bidder.
Expand Down
30 changes: 28 additions & 2 deletions src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,19 +355,32 @@ export class OpenSeaAPI {
* @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.
*/
public async buildOffer(
offererAddress: string,
quantity: number,
collectionSlug: string,
offerProtectionEnabled = true,
traitType?: string,
traitValue?: string,
): Promise<BuildOfferResponse> {
if (traitType || traitValue) {
if (!traitType || !traitValue) {
throw new Error(
"Both traitType and traitValue must be defined if one is defined.",
);
}
}
const payload = getBuildCollectionOfferPayload(
offererAddress,
quantity,
collectionSlug,
offerProtectionEnabled,
traitType,
traitValue,
);
const response = await this.post<BuildOfferResponse>(
getBuildOfferPath(),
Expand All @@ -393,13 +406,22 @@ export class OpenSeaAPI {
* 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.
*/
public async postCollectionOffer(
order: ProtocolData,
slug: string,
traitType?: string,
traitValue?: string,
): Promise<CollectionOffer | null> {
const payload = getPostCollectionOfferPayload(slug, order);
const payload = getPostCollectionOfferPayload(
slug,
order,
traitType,
traitValue,
);
return await this.post<CollectionOffer>(
getPostCollectionOfferPath(),
payload,
Expand Down Expand Up @@ -656,7 +678,11 @@ export class OpenSeaAPI {
// If an errors array is returned, throw with the error messages.
const errors = response.bodyJson?.errors;
if (errors?.length > 0) {
throw new Error(`Server Error: ${errors.join(", ")}`);
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.
Expand Down
24 changes: 22 additions & 2 deletions src/orders/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,35 @@ export const DEFAULT_SEAPORT_CONTRACT_ADDRESS =
export const getPostCollectionOfferPayload = (
collectionSlug: string,
protocol_data: ProtocolData,
traitType?: string,
traitValue?: string,
) => {
return {
const payload = {
criteria: {
collection: { slug: collectionSlug },
},
protocol_data,
protocol_address: DEFAULT_SEAPORT_CONTRACT_ADDRESS,
};
if (traitType && traitValue) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(payload.criteria as any).trait = {
type: traitType,
value: traitValue,
};
}
return payload;
};

export const getBuildCollectionOfferPayload = (
offererAddress: string,
quantity: number,
collectionSlug: string,
offerProtectionEnabled: boolean,
traitType?: string,
traitValue?: string,
) => {
return {
const payload = {
offerer: offererAddress,
quantity,
criteria: {
Expand All @@ -41,6 +53,14 @@ export const getBuildCollectionOfferPayload = (
protocol_address: DEFAULT_SEAPORT_CONTRACT_ADDRESS,
offer_protection_enabled: offerProtectionEnabled,
};
if (traitType && traitValue) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(payload.criteria as any).trait = {
type: traitType,
value: traitValue,
};
}
return payload;
};

export const getFulfillmentDataPath = (side: OrderSide) => {
Expand Down
19 changes: 18 additions & 1 deletion src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,9 @@ export class OpenSeaSDK {
* @param options.expirationTime Expiration time for the order, in UTC seconds.
* @param options.paymentTokenAddress ERC20 address for the payment token in the order. If unspecified, defaults to WETH.
* @param options.excludeOptionalCreatorFees If true, optional creator fees will be excluded from the offer. Default: false.
* @param options.offerProtectionEnabled Build the offer on OpenSea's signed zone to provide offer protections from receiving an item which is disabled from trading.
* @param options.traitType If defined, the trait name to create the collection offer for.
* @param options.traitValue If defined, the trait value to create the collection offer for.
* @returns The {@link CollectionOffer} that was created.
*/
public async createCollectionOffer({
Expand All @@ -576,6 +579,9 @@ export class OpenSeaSDK {
expirationTime,
paymentTokenAddress = getWETHAddress(this.chain),
excludeOptionalCreatorFees = false,
offerProtectionEnabled = true,
traitType,
traitValue,
}: {
collectionSlug: string;
accountAddress: string;
Expand All @@ -586,6 +592,9 @@ export class OpenSeaSDK {
expirationTime?: number | string;
paymentTokenAddress: string;
excludeOptionalCreatorFees?: boolean;
offerProtectionEnabled?: boolean;
traitType?: string;
traitValue?: string;
}): Promise<CollectionOffer | null> {
await this._requireAccountIsAvailable(accountAddress);

Expand All @@ -594,6 +603,9 @@ export class OpenSeaSDK {
accountAddress,
quantity,
collectionSlug,
offerProtectionEnabled,
traitType,
traitValue,
);
const item = buildOfferResult.partialParameters.consideration[0];
const convertedConsiderationItem = {
Expand Down Expand Up @@ -647,7 +659,12 @@ export class OpenSeaSDK {
);
const order = await executeAllActions();

return this.api.postCollectionOffer(order, collectionSlug);
return this.api.postCollectionOffer(
order,
collectionSlug,
traitType,
traitValue,
);
}

/**
Expand Down
20 changes: 20 additions & 0 deletions test/integration/postOrder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,24 @@ suite("SDK: order posting", () => {
await sdkPolygon.createCollectionOffer(postOrderRequest);
expect(offerResponse).to.exist.and.to.have.property("protocol_data");
});

test("Post Trait Offer - Ethereum", async () => {
const collection = await sdk.api.getCollection("cool-cats-nft");
const paymentTokenAddress = getWETHAddress(sdk.chain);
const postOrderRequest = {
collectionSlug: collection.collection,
accountAddress: walletAddress,
amount: OFFER_AMOUNT,
quantity: 1,
paymentTokenAddress,
traitType: "face",
traitValue: "tvface bobross",
};
const offerResponse = await sdk.createCollectionOffer(postOrderRequest);
expect(offerResponse).to.exist.and.to.have.property("protocol_data");
expect(offerResponse?.criteria.trait).to.deep.equal({
type: "face",
value: "tvface bobross",
});
});
});

0 comments on commit d5740e4

Please sign in to comment.