Skip to content

Commit a93dac7

Browse files
committed
Release v10.2.1
1 parent d6aeeb8 commit a93dac7

21 files changed

Lines changed: 333 additions & 68 deletions

AGENTS.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# sdk — Agent Conventions
2+
3+
TypeScript SDK for buying, selling, and managing NFTs and tokens on OpenSea. Supports ethers and viem providers.
4+
5+
## Quick Reference
6+
7+
```bash
8+
cd packages/sdk
9+
pnpm install
10+
pnpm run build # Build with tsc
11+
pnpm run test # Run unit tests with Vitest
12+
pnpm run test:integration # Run integration tests (requires API key)
13+
pnpm run lint # Lint with Biome
14+
pnpm run format # Format with Biome
15+
pnpm run check-types # TypeScript type checking (stricter tsconfig)
16+
```
17+
18+
## Architecture
19+
20+
| Directory | Role |
21+
|-----------|------|
22+
| `src/sdk.ts` | `OpenSeaSDK` — main ethers entry point |
23+
| `src/viem.ts` | `OpenSeaViemSDK` — viem entry point |
24+
| `src/sdk/base.ts` | `BaseOpenSeaSDK` — shared logic for both providers |
25+
| `src/sdk/fulfillment.ts` | Order fulfillment (buy, sell, match) via Seaport |
26+
| `src/sdk/orders.ts` | Create and manage listings and offers |
27+
| `src/sdk/cancellation.ts` | Order cancellation logic |
28+
| `src/sdk/assets.ts` | Asset transfers and approvals |
29+
| `src/sdk/tokens.ts` | ERC20 wrap/unwrap (WETH) |
30+
| `src/sdk/context.ts` | Shared per-call context (provider, chain, API client) threaded through the SDK |
31+
| `src/api/` | `OpenSeaAPI` client — all REST API calls |
32+
| `src/provider/` | Provider abstraction: ethers adapter, viem adapter, Seaport bridge |
33+
| `src/abi/` | Contract ABIs (ERC20, ERC721, ERC1155, Multicall3, TransferHelper) |
34+
| `src/utils/` | Chain helpers, fee calculations, rate limiting, address utilities |
35+
| `src/orders/` | Order types and Seaport parameter construction |
36+
| `test/` | Unit and integration tests |
37+
38+
## Review Checklist
39+
40+
When reviewing changes to this package, verify:
41+
42+
1. **Chain enum sync**: The `Chain` enum in `src/types.ts` has a compile-time check (`_AssertAPIChainsCovered`) ensuring every `ChainIdentifier` from `@opensea/api-types` maps to a `Chain` value. When adding a chain, also update `scripts/chain-data.json` at the **monorepo root**, run `pnpm sync-chains` from the monorepo root, and update `getListingPaymentToken` / `getOfferPaymentToken` / `getNativeWrapTokenAddress`.
43+
44+
2. **Dual provider support**: Both `OpenSeaSDK` (ethers) and `OpenSeaViemSDK` (viem) must work. Changes to `BaseOpenSeaSDK` affect both. If adding provider-specific logic, ensure both adapters in `src/provider/` are updated.
45+
46+
3. **`@opensea/api-types` dependency**: The SDK imports types from the workspace `@opensea/api-types` package. If the OpenAPI spec changes, rebuild api-types first (`pnpm --filter @opensea/api-types run build`) before testing the SDK.
47+
48+
4. **Seaport integration**: Order creation and fulfillment flows use `@opensea/seaport-js`. Changes to order parameters, consideration items, or fulfillment logic must be tested against the Seaport contract behavior.
49+
50+
5. **No secret leakage**: API keys are passed via `OpenSeaAPIConfig.apiKey`. Never log or expose them. Integration tests read keys from environment variables.
51+
52+
## Conventions
53+
54+
- CommonJS (`"type": "commonjs"`). The SDK is consumed by both CJS and ESM projects.
55+
- Biome for linting and formatting (config at monorepo root).
56+
- `viem` is an optional peer dependency — the main entry point uses ethers; `@opensea/sdk/viem` is the viem entry point.
57+
- Amounts use the `Amount` type (`string | number | bigint`). Prefer `string` for decimal values to avoid floating-point precision issues.
58+
- The `Chain` enum uses API slug strings (e.g. `"ethereum"`, `"base"`), not numeric chain IDs.
59+
60+
## Testing
61+
62+
- **Unit tests** (`test/`): Mock API responses, test order construction and parameter validation.
63+
- **Integration tests** (`test/integration/`): Hit the real API. Require `OPENSEA_API_KEY` env var. Run separately with `pnpm run test:integration`.
64+
- Always run `pnpm run check-types` — it uses a stricter tsconfig than the build.

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# @opensea/sdk
22

3+
## 10.2.1
4+
5+
### Patch Changes
6+
7+
- 4a76bc1: Add server-side trait filtering on three collection-scoped read methods. `getNFTsByCollection`, `getBestListings`, and `getEventsByCollection` now accept an optional `traits` argument (a `TraitFilter[]`); multiple entries are AND-combined server-side. The SDK JSON-encodes the array for the request — callers pass a structured `[{ traitType, value }]`. New exports: `TraitFilter`, `GetEventsByCollectionArgs`, `encodeTraitsParam`. Requires `@opensea/api-types@^0.2.2`.
8+
39
## 10.2.0
410

511
### Minor Changes

biome.json

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
"**/*.ts",
66
"**/*.js",
77
"**/*.json",
8-
"!**/node_modules/**",
9-
"!**/dist/**",
10-
"!**/lib/**",
11-
"!**/coverage/**",
12-
"!**/.nyc_output/**",
13-
"!packages/sdk/src/typechain/**",
14-
"!**/typechain/**",
8+
"!**/node_modules",
9+
"!**/dist",
10+
"!**/lib",
11+
"!**/coverage",
12+
"!**/.nyc_output",
13+
"!packages/sdk/src/typechain",
14+
"!**/typechain",
1515
"!packages/api-types/src/generated.ts",
1616
"!**/pnpm-lock.yaml",
1717
"!**/package-lock.json"
@@ -41,9 +41,10 @@
4141
"noExplicitAny": "off"
4242
},
4343
"correctness": {
44-
"noUnusedImports": "warn"
44+
"noUnusedImports": "error"
4545
},
4646
"style": {
47+
"noNonNullAssertion": "error",
4748
"noUnusedTemplateLiteral": "off"
4849
}
4950
}
@@ -53,8 +54,8 @@
5354
"includes": ["**/*.test.ts", "**/*.spec.ts", "**/test/**"],
5455
"linter": {
5556
"rules": {
56-
"correctness": {
57-
"noUnusedImports": "off"
57+
"style": {
58+
"noNonNullAssertion": "off"
5859
}
5960
}
6061
}

developerDocs/api-reference.md

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,12 @@ console.log(`Fetched ${nfts.length} NFTs`);
7373

7474
**Parameters:**
7575

76-
| Parameter | Type | Required | Description |
77-
| --------- | ------ | -------- | --------------------------------------- |
78-
| `slug` | string | Yes | Collection slug (identifier) |
79-
| `limit` | number | No | Number of NFTs to retrieve (1-50) |
80-
| `next` | string | No | Pagination cursor from previous request |
76+
| Parameter | Type | Required | Description |
77+
| --------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
78+
| `slug` | string | Yes | Collection slug (identifier) |
79+
| `limit` | number | No | Number of NFTs to retrieve (1-50) |
80+
| `next` | string | No | Pagination cursor from previous request |
81+
| `traits` | TraitFilter[] | No | Trait filters, e.g. `[{ traitType: "Background", value: "Red" }]`. Multiple entries are AND-combined. The SDK JSON-encodes the array for the request. |
8182

8283
**Returns:** `ListNFTsResponse` containing:
8384

@@ -405,12 +406,13 @@ const { listings, next } = await openseaSDK.api.getBestListings(
405406

406407
**Parameters:**
407408

408-
| Parameter | Type | Required | Description |
409-
| ------------------------ | ------- | -------- | ----------------------------------------- |
410-
| `collectionSlug` | string | Yes | Collection slug |
411-
| `limit` | number | No | Number of listings (1-100) |
412-
| `next` | string | No | Pagination cursor |
413-
| `includePrivateListings` | boolean | No | Include private listings (default: false) |
409+
| Parameter | Type | Required | Description |
410+
| ------------------------ | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
411+
| `collectionSlug` | string | Yes | Collection slug |
412+
| `limit` | number | No | Number of listings (1-100) |
413+
| `next` | string | No | Pagination cursor |
414+
| `includePrivateListings` | boolean | No | Include private listings (default: false) |
415+
| `traits` | TraitFilter[] | No | Trait filters, e.g. `[{ traitType: "Background", value: "Red" }]`. Multiple entries are AND-combined. The SDK JSON-encodes the array for the request. |
414416

415417
**Returns:** `GetListingsResponse` with best listings.
416418

@@ -1020,10 +1022,10 @@ asset_events.forEach((event) => {
10201022

10211023
**Parameters:**
10221024

1023-
| Parameter | Type | Required | Description |
1024-
| ---------------- | ------------- | -------- | ----------------------- |
1025-
| `collectionSlug` | string | Yes | Collection slug |
1026-
| `args` | GetEventsArgs | No | Event filtering options |
1025+
| Parameter | Type | Required | Description |
1026+
| ---------------- | ------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1027+
| `collectionSlug` | string | Yes | Collection slug |
1028+
| `args` | GetEventsArgs | No | Event filtering options. `args.traits` is a `TraitFilter[]` (e.g. `[{ traitType: "Background", value: "Red" }]`) to scope events to NFTs matching every trait — the SDK JSON-encodes it for the request. |
10271029

10281030
**Returns:** `GetEventsResponse` with collection events.
10291031

developerDocs/getting-started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const { nft } = await openseaSDK.api.getNFT(tokenAddress, tokenId);
3737

3838
**Additional NFT Methods:**
3939

40-
- `getNFTsByCollection(collectionSlug, limit?, next?)` - Get all NFTs in a collection
40+
- `getNFTsByCollection(collectionSlug, limit?, next?, traits?)` - Get all NFTs in a collection (`traits` is an array of `{ traitType, value }` for server-side filtering — multiple entries are AND-combined)
4141
- `getNFTsByContract(contractAddress, limit?, next?, chain?)` - Get all NFTs for a contract
4242
- `getNFTsByAccount(accountAddress, limit?, next?, chain?)` - Get all NFTs owned by an account
4343
- `getContract(contractAddress, chain?)` - Get contract information

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@opensea/sdk",
3-
"version": "10.2.0",
3+
"version": "10.2.1",
44
"description": "TypeScript SDK for the OpenSea marketplace helps developers build new experiences using NFTs, tokens, and our marketplace data",
55
"license": "MIT",
66
"author": "OpenSea Developers",
@@ -45,7 +45,7 @@
4545
"types": "lib/index.d.ts",
4646
"dependencies": {
4747
"@noble/hashes": "^2.0.1",
48-
"@opensea/api-types": "^0.2.1",
48+
"@opensea/api-types": "^0.2.2",
4949
"@opensea/seaport-js": "^4.1.1",
5050
"ethers": "^6.16.0"
5151
},

src/api/api.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import {
4949
type GetDropsArgs,
5050
type GetDropsResponse,
5151
type GetEventsArgs,
52+
type GetEventsByCollectionArgs,
5253
type GetEventsResponse,
5354
type GetListingsResponse,
5455
type GetNFTMetadataResponse,
@@ -75,6 +76,7 @@ import {
7576
type ResolveAccountResponse,
7677
type SearchArgs,
7778
type SearchResponse,
79+
type TraitFilter,
7880
type ValidateMetadataResponse,
7981
} from "./types"
8082

@@ -298,19 +300,22 @@ export class OpenSeaAPI {
298300
* @param limit The number of listings to return. Must be between 1 and 100. Default: 100
299301
* @param next The cursor for the next page of results. This is returned from a previous request.
300302
* @param includePrivateListings Whether to include private listings (default: false)
303+
* @param traits Optional {@link TraitFilter} array. Returns 400 if a single trait matches more than 1000 items.
301304
* @returns The {@link GetListingsResponse} returned by the API.
302305
*/
303306
public async getBestListings(
304307
collectionSlug: string,
305308
limit?: number,
306309
next?: string,
307310
includePrivateListings?: boolean,
311+
traits?: TraitFilter[],
308312
): Promise<GetListingsResponse> {
309313
return this.listingsAPI.getBestListings(
310314
collectionSlug,
311315
limit,
312316
next,
313317
includePrivateListings,
318+
traits,
314319
)
315320
}
316321

@@ -473,14 +478,16 @@ export class OpenSeaAPI {
473478
* @param slug The slug (identifier) of the collection
474479
* @param limit The number of NFTs to retrieve. Must be greater than 0 and less than 51.
475480
* @param next Cursor to retrieve the next page of NFTs
481+
* @param traits Optional {@link TraitFilter} array. Returns 400 if a single trait matches more than 1000 items.
476482
* @returns The {@link ListNFTsResponse} returned by the API.
477483
*/
478484
public async getNFTsByCollection(
479485
slug: string,
480486
limit: number | undefined = undefined,
481487
next: string | undefined = undefined,
488+
traits: TraitFilter[] | undefined = undefined,
482489
): Promise<ListNFTsResponse> {
483-
return this.nftsAPI.getNFTsByCollection(slug, limit, next)
490+
return this.nftsAPI.getNFTsByCollection(slug, limit, next, traits)
484491
}
485492

486493
/**
@@ -668,14 +675,15 @@ export class OpenSeaAPI {
668675
}
669676

670677
/**
671-
* Gets a list of events for a specific collection.
678+
* Gets a list of events for a specific collection. Pass `args.traits` to
679+
* filter server-side by item traits (multiple entries are AND-combined).
672680
* @param collectionSlug The slug (identifier) of the collection.
673-
* @param args Query parameters for filtering events.
681+
* @param args Query parameters; see {@link GetEventsByCollectionArgs}.
674682
* @returns The {@link GetEventsResponse} returned by the API.
675683
*/
676684
public async getEventsByCollection(
677685
collectionSlug: string,
678-
args?: GetEventsArgs,
686+
args?: GetEventsByCollectionArgs,
679687
): Promise<GetEventsResponse> {
680688
return this.eventsAPI.getEventsByCollection(collectionSlug, args)
681689
}
@@ -1093,16 +1101,17 @@ export class OpenSeaAPI {
10931101
let userAbortHandler: (() => void) | undefined
10941102
let signal = options?.signal
10951103
if (options?.timeout !== undefined) {
1096-
controller = new AbortController()
1097-
timeoutId = setTimeout(() => controller!.abort(), options.timeout)
1104+
const ctrl = new AbortController()
1105+
controller = ctrl
1106+
timeoutId = setTimeout(() => ctrl.abort(), options.timeout)
10981107
// If user provided their own signal, abort ours if theirs fires
10991108
if (options.signal) {
11001109
if (options.signal.aborted) {
11011110
clearTimeout(timeoutId)
11021111
throw new Error("Request aborted")
11031112
}
11041113
userAbortHandler = () => {
1105-
controller!.abort()
1114+
ctrl.abort()
11061115
clearTimeout(timeoutId)
11071116
}
11081117
options.signal.addEventListener("abort", userAbortHandler)

src/api/events.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ import {
66
getEventsByNFTAPIPath,
77
} from "./apiPaths"
88
import type { Fetcher } from "./fetcher"
9-
import type { GetEventsArgs, GetEventsResponse } from "./types"
9+
import {
10+
encodeTraitsParam,
11+
type GetEventsArgs,
12+
type GetEventsByCollectionArgs,
13+
type GetEventsResponse,
14+
} from "./types"
1015

1116
/**
1217
* Events-related API operations
@@ -40,15 +45,16 @@ export class EventsAPI {
4045
}
4146

4247
/**
43-
* Gets a list of events for a specific collection.
48+
* Gets a list of events for a specific collection. Pass `args.traits` to
49+
* filter server-side; the SDK JSON-encodes the trait array for the request.
4450
*/
4551
async getEventsByCollection(
4652
collectionSlug: string,
47-
args?: GetEventsArgs,
53+
args?: GetEventsByCollectionArgs,
4854
): Promise<GetEventsResponse> {
4955
const response = await this.fetcher.get<GetEventsResponse>(
5056
getEventsByCollectionAPIPath(collectionSlug),
51-
args,
57+
encodeArgs(args),
5258
)
5359
return response
5460
}
@@ -69,3 +75,13 @@ export class EventsAPI {
6975
return response
7076
}
7177
}
78+
79+
// Returns `undefined` (not `{}`) when no args were passed so the fetcher
80+
// receives the same shape as before `traits` existed — preserves the
81+
// pre-feature query string for callers that don't set any args.
82+
function encodeArgs(args?: GetEventsByCollectionArgs) {
83+
if (!args) return args
84+
const { traits, ...rest } = args
85+
const encoded = encodeTraitsParam(traits)
86+
return encoded === undefined ? rest : { ...rest, traits: encoded }
87+
}

src/api/listings.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import {
77
getOrdersAPIPath,
88
} from "./apiPaths"
99
import type { Fetcher } from "./fetcher"
10-
import type { GetBestListingResponse, GetListingsResponse } from "./types"
10+
import {
11+
encodeTraitsParam,
12+
type GetBestListingResponse,
13+
type GetListingsResponse,
14+
type TraitFilter,
15+
} from "./types"
1116

1217
/**
1318
* Listing-related API operations
@@ -65,17 +70,15 @@ export class ListingsAPI {
6570
}
6671

6772
/**
68-
* Gets the best listings for a given collection.
69-
* @param collectionSlug The collection slug
70-
* @param limit The number of listings to return
71-
* @param next The cursor for pagination
72-
* @param includePrivateListings Whether to include private listings (default: false)
73+
* Gets the best listings for a given collection. Pass `traits` to filter
74+
* server-side by item traits (multiple entries are AND-combined).
7375
*/
7476
async getBestListings(
7577
collectionSlug: string,
7678
limit?: number,
7779
next?: string,
7880
includePrivateListings?: boolean,
81+
traits?: TraitFilter[],
7982
): Promise<GetListingsResponse> {
8083
const response = await this.fetcher.get<GetListingsResponse>(
8184
getBestListingsAPIPath(collectionSlug),
@@ -85,6 +88,7 @@ export class ListingsAPI {
8588
...(includePrivateListings !== undefined && {
8689
include_private_listings: includePrivateListings,
8790
}),
91+
traits: encodeTraitsParam(traits),
8892
},
8993
)
9094
return response

0 commit comments

Comments
 (0)