diff --git a/developerDocs/api-reference.md b/developerDocs/api-reference.md index 266013457..b37009888 100644 --- a/developerDocs/api-reference.md +++ b/developerDocs/api-reference.md @@ -964,11 +964,13 @@ asset_events.forEach((event) => { **Event Types:** -- `AssetEventType.SALE` - NFT sales -- `AssetEventType.TRANSFER` - NFT transfers -- `AssetEventType.ORDER` - New listings and offers -- `AssetEventType.CANCEL` - Canceled orders -- `AssetEventType.REDEMPTION` - NFT redemptions +- `"sale"` - NFT sales +- `"transfer"` - NFT transfers +- `"mint"` - NFT mints +- `"listing"` - Item listings +- `"offer"` - Item offers +- `"trait_offer"` - Trait-based offers +- `"collection_offer"` - Collection offers **Returns:** `GetEventsResponse` containing: diff --git a/developerDocs/getting-started.md b/developerDocs/getting-started.md index d402ed018..053137687 100644 --- a/developerDocs/getting-started.md +++ b/developerDocs/getting-started.md @@ -250,11 +250,13 @@ const { asset_events, next } = await openseaSDK.api.getEvents({ **Event Types:** -- `AssetEventType.SALE` - Sales of NFTs -- `AssetEventType.TRANSFER` - Transfers of NFTs -- `AssetEventType.ORDER` - New listings and offers -- `AssetEventType.CANCEL` - Canceled orders -- `AssetEventType.REDEMPTION` - NFT redemptions +- `"sale"` - NFT sales +- `"transfer"` - NFT transfers +- `"mint"` - NFT mints +- `"listing"` - Item listings +- `"offer"` - Item offers +- `"trait_offer"` - Trait-based offers +- `"collection_offer"` - Collection offers ### Get Events by Account @@ -278,7 +280,6 @@ Fetch events for a specific collection: const { asset_events } = await openseaSDK.api.getEventsByCollection( "cool-cats-nft", // Collection slug { - event_type: AssetEventType.ORDER, limit: 100, }, ); diff --git a/package.json b/package.json index fb8ae12dc..839e0d9b2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opensea-js", - "version": "8.0.5", + "version": "8.0.6", "description": "TypeScript SDK for the OpenSea marketplace helps developers build new experiences using NFTs and our marketplace data", "license": "MIT", "author": "OpenSea Developers", diff --git a/src/api/api.ts b/src/api/api.ts index d3b05732e..75f55f724 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -29,7 +29,6 @@ import { GetCollectionsResponse, ListNFTsResponse, GetNFTResponse, - ListCollectionOffersResponse, GetOrdersResponse, GetBestOfferResponse, GetBestListingResponse, @@ -352,12 +351,16 @@ export class OpenSeaAPI { /** * 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. + * @param limit Optional limit for number of results. + * @param next Optional cursor for pagination. + * @returns The {@link GetOffersResponse} returned by the API. */ public async getCollectionOffers( slug: string, - ): Promise { - return this.offersAPI.getCollectionOffers(slug); + limit?: number, + next?: string, + ): Promise { + return this.offersAPI.getCollectionOffers(slug, limit, next); } /** diff --git a/src/api/offers.ts b/src/api/offers.ts index 9342bc15f..1527d55c1 100644 --- a/src/api/offers.ts +++ b/src/api/offers.ts @@ -9,7 +9,6 @@ import { } from "./apiPaths"; import { BuildOfferResponse, - ListCollectionOffersResponse, GetBestOfferResponse, GetOffersResponse, CollectionOffer, @@ -141,13 +140,19 @@ export class OffersAPI { } /** - * Get a list collection offers for a given slug. + * Get a list of collection offers for a given slug. */ async getCollectionOffers( slug: string, - ): Promise { - return await this.fetcher.get( + limit?: number, + next?: string, + ): Promise { + return await this.fetcher.get( getCollectionOffersPath(slug), + { + limit, + next, + }, ); } diff --git a/src/api/types.ts b/src/api/types.ts index 5731e2c8c..3f5c83f8f 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -338,18 +338,20 @@ export enum TraitDisplayType { * @category API Models */ export enum AssetEventType { - ORDER = "order", SALE = "sale", TRANSFER = "transfer", - CANCEL = "cancel", - REDEMPTION = "redemption", + MINT = "mint", + LISTING = "listing", + OFFER = "offer", + TRAIT_OFFER = "trait_offer", + COLLECTION_OFFER = "collection_offer", } /** - * Order type for events. + * Order type for order events. * @category API Models */ -export enum EventOrderType { +export enum OrderEventType { LISTING = "listing", ITEM_OFFER = "item_offer", COLLECTION_OFFER = "collection_offer", @@ -408,27 +410,23 @@ type BaseEvent = { }; /** - * Order event type. + * Listing event type. * @category API Models */ -export type OrderEvent = BaseEvent & { - event_type: AssetEventType.ORDER | "order"; +export type ListingEvent = BaseEvent & { + event_type: AssetEventType.LISTING | "listing"; /** Payment information */ payment: EventPayment; - /** Type of order */ - order_type: EventOrderType | string; - /** Start date of the order */ + /** Start date of the listing */ start_date: number | null; - /** Expiration date of the order */ + /** Expiration date of the listing */ expiration_date: number; - /** Asset involved in the order, null for collection/trait offers */ - asset: EventAsset | null; - /** Maker of the order */ + /** Asset involved in the listing */ + asset: EventAsset; + /** Maker of the listing */ maker: string; - /** Taker of the order */ + /** Taker of the listing */ taker: string; - /** Criteria for collection/trait offers */ - criteria: Record | null; /** Whether the listing is private */ is_private_listing: boolean; /** Order hash (optional) */ @@ -437,6 +435,92 @@ export type OrderEvent = BaseEvent & { protocol_address?: string; }; +/** + * Offer event type. + * @category API Models + */ +export type OfferEvent = BaseEvent & { + event_type: AssetEventType.OFFER | "offer"; + /** Payment information */ + payment: EventPayment; + /** Start date of the offer */ + start_date: number | null; + /** Expiration date of the offer */ + expiration_date: number; + /** Asset involved in the offer */ + asset: EventAsset; + /** Maker of the offer */ + maker: string; + /** Taker of the offer */ + taker: string; + /** Order hash (optional) */ + order_hash?: string; + /** Protocol address (optional) */ + protocol_address?: string; +}; + +/** + * Trait offer event type. + * @category API Models + */ +export type TraitOfferEvent = BaseEvent & { + event_type: AssetEventType.TRAIT_OFFER | "trait_offer"; + /** Payment information */ + payment: EventPayment; + /** Start date of the offer */ + start_date: number | null; + /** Expiration date of the offer */ + expiration_date: number; + /** Criteria for trait offers */ + criteria: Record; + /** Maker of the offer */ + maker: string; + /** Taker of the offer */ + taker: string; + /** Order hash (optional) */ + order_hash?: string; + /** Protocol address (optional) */ + protocol_address?: string; +}; + +/** + * Collection offer event type. + * @category API Models + */ +export type CollectionOfferEvent = BaseEvent & { + event_type: AssetEventType.COLLECTION_OFFER | "collection_offer"; + /** Payment information */ + payment: EventPayment; + /** Start date of the offer */ + start_date: number | null; + /** Expiration date of the offer */ + expiration_date: number; + /** Criteria for collection offers */ + criteria: Record; + /** Maker of the offer */ + maker: string; + /** Taker of the offer */ + taker: string; + /** Order hash (optional) */ + order_hash?: string; + /** Protocol address (optional) */ + protocol_address?: string; +}; + +/** + * Mint event type. + * @category API Models + */ +export type MintEvent = BaseEvent & { + event_type: AssetEventType.MINT | "mint"; + /** Transaction hash */ + transaction: string; + /** Address the NFT was minted to */ + to_address: string; + /** NFT that was minted */ + nft: EventAsset; +}; + /** * Sale event type. * @category API Models @@ -481,7 +565,14 @@ export type TransferEvent = BaseEvent & { * Generic event type that can be any event type. * @category API Models */ -export type AssetEvent = OrderEvent | SaleEvent | TransferEvent; +export type AssetEvent = + | ListingEvent + | OfferEvent + | TraitOfferEvent + | CollectionOfferEvent + | SaleEvent + | TransferEvent + | MintEvent; /** * Query args for Get Events endpoints. diff --git a/test/api/events.spec.ts b/test/api/events.spec.ts index d5eafcf6f..d8447d3a1 100644 --- a/test/api/events.spec.ts +++ b/test/api/events.spec.ts @@ -6,7 +6,8 @@ import { AssetEventType, EventAsset, GetEventsResponse, - OrderEvent, + ListingEvent, + OfferEvent, SaleEvent, TransferEvent, } from "../../src/api/types"; @@ -32,7 +33,7 @@ suite("API: EventsAPI", () => { const mockResponse: GetEventsResponse = { asset_events: [ { - event_type: AssetEventType.ORDER, + event_type: AssetEventType.LISTING, event_timestamp: 1234567890, chain: "ethereum", quantity: 1, @@ -42,15 +43,13 @@ suite("API: EventsAPI", () => { decimals: 18, symbol: "ETH", }, - order_type: "listing", start_date: null, expiration_date: 1234567990, - asset: null, + asset: {} as EventAsset, maker: "0x123", taker: "", - criteria: null, is_private_listing: false, - } as OrderEvent, + } as ListingEvent, ], next: "cursor-123", }; @@ -195,7 +194,7 @@ suite("API: EventsAPI", () => { test("handles multiple events in response", async () => { const mockResponse: GetEventsResponse = { asset_events: [ - { event_type: AssetEventType.ORDER } as OrderEvent, + { event_type: AssetEventType.LISTING } as ListingEvent, { event_type: AssetEventType.SALE } as SaleEvent, { event_type: AssetEventType.TRANSFER } as TransferEvent, ], @@ -393,7 +392,7 @@ suite("API: EventsAPI", () => { const mockResponse: GetEventsResponse = { asset_events: [ { - event_type: AssetEventType.ORDER, + event_type: AssetEventType.OFFER, event_timestamp: 1234567890, chain: "ethereum", quantity: 1, @@ -403,15 +402,12 @@ suite("API: EventsAPI", () => { decimals: 18, symbol: "ETH", }, - order_type: "item_offer", start_date: 1234567890, expiration_date: 1234567990, asset: {} as EventAsset, maker: "0x123", taker: "", - criteria: null, - is_private_listing: false, - } as OrderEvent, + } as OfferEvent, ], next: "cursor-123", }; @@ -497,11 +493,11 @@ suite("API: EventsAPI", () => { }); suite("Event Types", () => { - test("handles order events", async () => { + test("handles listing events", async () => { const mockResponse: GetEventsResponse = { asset_events: [ { - event_type: AssetEventType.ORDER, + event_type: AssetEventType.LISTING, event_timestamp: 1234567890, chain: "ethereum", quantity: 1, @@ -511,7 +507,6 @@ suite("API: EventsAPI", () => { decimals: 18, symbol: "ETH", }, - order_type: "listing", start_date: null, expiration_date: 1234567990, asset: { @@ -532,9 +527,8 @@ suite("API: EventsAPI", () => { }, maker: "0x123", taker: "", - criteria: null, is_private_listing: false, - } as OrderEvent, + } as ListingEvent, ], next: undefined, }; @@ -543,8 +537,10 @@ suite("API: EventsAPI", () => { const result = await eventsAPI.getEvents(); - expect(result.asset_events[0].event_type).to.equal(AssetEventType.ORDER); - expect((result.asset_events[0] as OrderEvent).maker).to.equal("0x123"); + expect(result.asset_events[0].event_type).to.equal( + AssetEventType.LISTING, + ); + expect((result.asset_events[0] as ListingEvent).maker).to.equal("0x123"); }); test("handles sale events", async () => { diff --git a/test/api/offers.spec.ts b/test/api/offers.spec.ts index 23a1058e3..cff98ac1a 100644 --- a/test/api/offers.spec.ts +++ b/test/api/offers.spec.ts @@ -5,7 +5,6 @@ import * as sinon from "sinon"; import { OffersAPI } from "../../src/api/offers"; import { BuildOfferResponse, - ListCollectionOffersResponse, GetBestOfferResponse, GetOffersResponse, CollectionOffer, @@ -614,14 +613,17 @@ suite("API: OffersAPI", () => { }); suite("getCollectionOffers", () => { - test("fetches collection offers for a slug", async () => { - const mockResponse: ListCollectionOffersResponse = { + test("fetches collection offers for a slug without parameters", async () => { + const mockResponse: GetOffersResponse = { offers: [ { + order_hash: "0x123", + chain: Chain.Mainnet, protocol_data: {} as unknown as ProtocolData, protocol_address: "0xabc", - } as unknown as CollectionOffer, + } as unknown as Offer, ], + next: "cursor-123", }; mockGet.resolves(mockResponse); @@ -632,28 +634,61 @@ suite("API: OffersAPI", () => { expect(mockGet.firstCall.args[0]).to.equal( "/api/v2/offers/collection/test-collection", ); - expect(result).to.deep.equal(mockResponse); - expect(result?.offers).to.have.length(1); + expect(mockGet.firstCall.args[1]).to.deep.equal({ + limit: undefined, + next: undefined, + }); + expect(result.offers).to.have.length(1); + expect(result.next).to.equal("cursor-123"); }); - test("handles empty offers list", async () => { - const mockResponse: ListCollectionOffersResponse = { + test("fetches collection offers with limit parameter", async () => { + const mockResponse: GetOffersResponse = { offers: [], + next: undefined, }; mockGet.resolves(mockResponse); - const result = await offersAPI.getCollectionOffers("test-collection"); + await offersAPI.getCollectionOffers("test-collection", 50); - expect(result?.offers).to.be.an("array").that.is.empty; + expect(mockGet.firstCall.args[1]).to.deep.equal({ + limit: 50, + next: undefined, + }); }); - test("returns null when appropriate", async () => { - mockGet.resolves(null); + test("fetches collection offers with pagination cursor", async () => { + const mockResponse: GetOffersResponse = { + offers: [], + next: "cursor-next", + }; + + mockGet.resolves(mockResponse); + + await offersAPI.getCollectionOffers( + "test-collection", + undefined, + "cursor-prev", + ); + + expect(mockGet.firstCall.args[1]).to.deep.equal({ + limit: undefined, + next: "cursor-prev", + }); + }); + + test("handles empty offers list", async () => { + const mockResponse: GetOffersResponse = { + offers: [], + next: undefined, + }; + + mockGet.resolves(mockResponse); const result = await offersAPI.getCollectionOffers("test-collection"); - expect(result).to.be.null; + expect(result.offers).to.be.an("array").that.is.empty; }); test("throws error on API failure", async () => { diff --git a/test/integration/getCollectionOffers.spec.ts b/test/integration/getCollectionOffers.spec.ts index 88d8e7ad1..d356f7657 100644 --- a/test/integration/getCollectionOffers.spec.ts +++ b/test/integration/getCollectionOffers.spec.ts @@ -16,8 +16,9 @@ suite("SDK: getCollectionOffers", () => { assert(response.offers.length > 0, "Collection offers should not be empty"); const offer = response.offers[0]; assert(offer.order_hash, "Order hash should not be null"); + assert(offer.criteria, "Criteria should not be undefined"); const tokens = offer.criteria.encoded_token_ids; - assert(tokens, "Criteria should not be null"); + assert(tokens, "Encoded token ids should not be null"); const encodedTokenIds = offer.criteria.encoded_token_ids; assert(encodedTokenIds, "Encoded tokens should not be null");