Skip to content

Commit ed2afd7

Browse files
committed
Release v11.0.0
Origin-SHA: 36691654b9a5b5cbfe770b3d23c8fc9f05179a62
1 parent 092a40c commit ed2afd7

54 files changed

Lines changed: 1570 additions & 1838 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ pnpm run check-types # TypeScript type checking (stricter tsconfig)
2121
|-----------|------|
2222
| `src/sdk.ts` | `OpenSeaSDK` — main ethers entry point |
2323
| `src/viem.ts` | `OpenSeaViemSDK` — viem entry point |
24+
| `src/types.ts` | Public types: `Chain` enum, `Amount`, order/event types |
25+
| `src/constants.ts` | Math and Ethereum constants (basis points, addresses, etc.) |
2426
| `src/sdk/base.ts` | `BaseOpenSeaSDK` — shared logic for both providers |
2527
| `src/sdk/fulfillment.ts` | Order fulfillment (buy, sell, match) via Seaport |
2628
| `src/sdk/orders.ts` | Create and manage listings and offers |

CHANGELOG.md

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

3+
## 11.0.0
4+
5+
### Major Changes
6+
7+
- e7deba3: Rebuild the SDK's type layer on `@opensea/api-types` with automatic case translation at the fetcher boundary. Consumer API stays camelCase; underneath, the fetcher snakeizes outgoing query params and POST bodies and camelizes responses, so the SDK no longer ships hand-rolled response shapes.
8+
9+
## What changed
10+
11+
### Types are sourced from `@opensea/api-types`
12+
13+
The Order family, NFT/Trait, Drop family, Collection, Account, Payment, Chain, Token, and event response shapes now derive directly from the generated OpenAPI types via a generic `Camelize<T>` mapper. When the API spec gains a field, the SDK type picks it up automatically — no per-endpoint converter to keep in sync. The old `utils/converters.ts` is gone.
14+
15+
### Case translation at the fetcher boundary
16+
17+
`utils/case.ts` ships two utilities:
18+
19+
- `camelizeKeysDeep<T>` / `Camelize<T>` — walks the API response and rewrites snake_case keys to camelCase.
20+
- `snakeizeKeysDeep<T>` / `Snakeize<T>` — the inverse, applied to query params and POST bodies on the way out.
21+
22+
Consumers always see camelCase; the API always sees snake_case. No converter drift, no field-name typos.
23+
24+
### Narrowing intersections preserved
25+
26+
Where the OpenAPI spec is too loose, the SDK still narrows:
27+
28+
- `Listing.type` is the `OrderType` enum (spec ships plain `string`).
29+
- `Listing.status` / `Offer.status` are the `OrderStatus` enum.
30+
- `Order` / `Offer` / `Listing` `.protocolData` is the seaport-js `OrderWithCounter` (the SDK passes it directly to Seaport).
31+
32+
### Shape changes consumers should know about
33+
34+
These come from aligning with what the API actually returns:
35+
36+
- `Order.protocolData` and `Order.protocolAddress` are **optional**. They're populated on every endpoint except the profile listings/offers endpoints, where the API intentionally returns null for performance. Code that reads them unconditionally needs a guard.
37+
- `Order` base type no longer carries `price` — only `Offer` and `Listing` do (matching the API).
38+
- `Offer` and `Listing` gain `remainingQuantity` (required), `orderCreatedAt`, and `asset?: OrderAsset` (the field added in ProjectOpenSea/os2-core#42022 for profile endpoints).
39+
- `NFT` is now `NftDetailed` — gains `displayImageUrl`, `displayAnimationUrl`, `originalImageUrl`, `originalAnimationUrl`, `animationUrl`, `isSuspicious`, `subscription`, `owner.quantityString`. Drops stale `rarity.{score,calculatedAt,maxRank,tokensScored,rankingFeatures}` that weren't actually in the spec.
40+
- `TokenBalance` gains optional `status`, `baseTokenLiquidityUsd`, `quoteTokenLiquidityUsd`.
41+
- `RarityStrategy` is now `Camelize<Rarity>` from api-types — `{ strategyId, strategyVersion, rank? }`. The previous extra fields (`calculatedAt`, `maxRank`, `tokensScored`) were spec-incomplete patches.
42+
- `GetCollectionResponse` is now an alias for `OpenSeaCollection` — the previous `{ collection: OpenSeaCollection }` wrapper never matched the actual API response.
43+
- Acronym casing follows generic snake→camel rules: `is_nsfw``isNsfw` (not `isNSFW`).
44+
- `PaymentToken.image` (was `imageUrl`) — the spec uses `image`; the previous converter renamed it. Code reading `paymentToken.imageUrl` should switch to `paymentToken.image`.
45+
46+
### Removed
47+
48+
- `utils/converters.ts` (`collectionFromJSON`, `accountFromJSON`, `paymentTokenFromJSON`, `feeFromJSON`, `rarityFromJSON`, `pricingCurrenciesFromJSON`) and the corresponding test file.
49+
50+
### Surfaces the new `Order.asset` field
51+
52+
Profile endpoints (`/account/{address}/listings`, `/offers`, `/offers_received`) now expose `asset: { identifier?: string; contract: string }`, so consumers no longer have to parse Seaport `protocolData.parameters.offer[0]` to identify the NFT.
53+
54+
### Patch Changes
55+
56+
- fb03c09: Source `EventPayment`, `EventAsset`, `GetNFTResponse`, `BuildOfferResponse`, and `CancelOrderResponse` from `@opensea/api-types` instead of hand-rolling them. Same shapes consumers see today (after camelize at the fetcher), now auto-tracking the OpenAPI spec.
57+
58+
- `EventPayment``Camelize<Payment>`
59+
- `EventAsset``Camelize<Nft>` (gains `original_image_url`, `original_animation_url`, and `traits` fields the API also returns)
60+
- `GetNFTResponse``Camelize<NftResponse>`
61+
- `BuildOfferResponse``Camelize<BuildOfferResponse>` (api-types ships this with camelCase keys natively)
62+
- `CancelOrderResponse``Camelize<CancelResponse>`
63+
64+
The narrow event types (`ListingEvent`, `OfferEvent`, `TraitOfferEvent`, `CollectionOfferEvent`, `OrderEvent`, `MintEvent`, `SaleEvent`, `TransferEvent`) and `AssetEvent` union keep their existing SDK definitions — they're refinements that narrow `eventType` to specific enum values, which the api-types `OrderEvent`/`SaleEvent`/`TransferEvent` schemas don't model.
65+
66+
- 68b07cb: Fix critical bugs introduced by the api-types migration where unconditional body snakeize corrupted Seaport-shaped POST payloads.
67+
68+
## What was broken
69+
70+
The OpenSea OpenAPI spec is **mixed-casing**: outer envelope keys are snake_case (`protocol_address`, `protocol_data`, `order_hash`) but inner Seaport struct keys are camelCase to mirror the on-chain struct (`parameters.startTime`, `parameters.endTime`, `parameters.orderType`, `parameters.zoneHash`, `parameters.conduitKey`, `parameters.totalOriginalConsiderationItems`, `parameters.offer[].itemType`, `parameters.offer[].identifierOrCriteria`, etc.). A few top-level request fields are also camelCase per spec: `CancelRequest.offererSignature`, `CriteriaObject.numericTraits`.
71+
72+
The blanket `snakeizeKeysDeep(body)` at the fetcher boundary recursively rewrote every inner key to snake_case, breaking:
73+
74+
- `postListing` / `postOffer` — Seaport `parameters` sent with snake_case keys the API rejected (or that no longer matched the EIP-712 signature digest).
75+
- `offchainCancelOrder``offererSignature` shipped as `offerer_signature`, silently dropping the cancel signature.
76+
- `buildOffer` / `postCollectionOffer``criteria.numericTraits` shipped as `numeric_traits`, broadening trait offers to the whole collection.
77+
78+
## Fix
79+
80+
Added `snakeizeBody?: boolean` (default `true`) to the public `Fetcher.post()` method. Internal callsites whose wire bodies contain camelCase keys now pass `snakeizeBody: false` and emit bodies in exact wire shape:
81+
82+
- `OrdersAPI.postListing`, `OrdersAPI.postOffer` — outer `protocol_address` snake_case; inner `parameters` preserved camelCase via spread of the Seaport `OrderWithCounter`.
83+
- `OrdersAPI.offchainCancelOrder` — body `{ offererSignature }` preserved.
84+
- `OffersAPI.buildOffer`, `OffersAPI.postCollectionOffer` — outer `protocol_address` / `protocol_data` / `offer_protection_enabled` snake_case; `criteria.numericTraits` preserved camelCase.
85+
86+
The default behavior (snakeize-all) is unchanged for any caller of `api.post()` that doesn't hit a mixed-casing endpoint.
87+
88+
## Other related fixes
89+
90+
- `OpenSeaAPI.requestInstantApiKey` (and the `OpenSeaSDK` passthrough) now camelizes its response — previously it called `fetch()` directly and returned snake_case despite the typed surface promising `{ apiKey, expiresAt, ... }`. JSDoc examples on both methods corrected.
91+
- `OpenSeaRateLimitError.responseBody` is now camelized to match the rest of the boundary contract.
92+
- `_fetch` error envelope is camelized before reading `.errors`, so nested snake_case keys no longer leak into thrown Error messages.
93+
- `camelToSnake` no longer emits a leading underscore for PascalCase / acronym keys (`URL``url`, `MyKey``my_key`). The corresponding `Snakeize<T>` type was updated to match the runtime.
94+
- `OpenSeaAccount.socialMediaAccounts` defends against the wire returning `null` (the previous hand-rolled converter did `?? []`; the new pipeline did not).
95+
- Dead-code OrderV2/Order casts dropped in `fulfillment.ts` — both branches read the same camelCase property after the migration.
96+
97+
## Tests
98+
99+
Added 11 unit tests covering `snakeizeKeysDeep` (flat + nested objects, array walking, multi-segment, primitives, null/undefined, Date passthrough, top-level `offererSignature`/`protocolAddress` rewrite, position-0 guard). The previous test file imported only `camelizeKeysDeep` — the entire outbound translator had zero unit coverage, which is how these bugs slipped through.
100+
101+
A new CI workflow (`.github/workflows/sdk-integration.yml`) runs the SDK integration suite nightly and on PRs labeled `run-integration`, so future fetcher-boundary regressions are caught against the live API.
102+
103+
- Updated dependencies [fb03c09]
104+
- @opensea/api-types@0.4.2
105+
3106
## 10.5.0
4107

5108
### Minor Changes

developerDocs/api-reference.md

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const { nft } = await openseaSDK.api.getNFT(
3939
);
4040

4141
console.log(nft.name);
42-
console.log(nft.image_url);
42+
console.log(nft.imageUrl);
4343
console.log(nft.traits);
4444
```
4545

@@ -349,7 +349,7 @@ const { listings, next } = await openseaSDK.api.getAllListings(
349349
listings.forEach((listing) => {
350350
console.log(`Price: ${listing.price.current.value}`);
351351
console.log(
352-
`Token ID: ${listing.protocol_data.parameters.offer[0].identifierOrCriteria}`,
352+
`Token ID: ${listing.protocolData.parameters.offer[0].identifierOrCriteria}`,
353353
);
354354
});
355355
```
@@ -378,7 +378,7 @@ Get the best (lowest price) active listing for a specific NFT.
378378
const listing = await openseaSDK.api.getBestListing("boredapeyachtclub", "1");
379379

380380
console.log(`Best price: ${listing.price.current.value}`);
381-
console.log(`Seller: ${listing.protocol_data.parameters.offerer}`);
381+
console.log(`Seller: ${listing.protocolData.parameters.offerer}`);
382382
```
383383

384384
**Parameters:**
@@ -539,7 +539,7 @@ Get the highest active offer for a specific NFT.
539539
const offer = await openseaSDK.api.getBestOffer("boredapeyachtclub", "1");
540540

541541
console.log(`Best offer: ${offer.price.value}`);
542-
console.log(`Offerer: ${offer.protocol_data.parameters.offerer}`);
542+
console.log(`Offerer: ${offer.protocolData.parameters.offerer}`);
543543
```
544544

545545
**Parameters:**
@@ -572,7 +572,7 @@ offers.forEach((offer) => {
572572
const price = offer.price.value;
573573
const decimals = offer.price.decimals;
574574
const priceInEth = parseFloat(price) / Math.pow(10, decimals);
575-
const offerer = offer.protocol_data.parameters.offerer;
575+
const offerer = offer.protocolData.parameters.offerer;
576576
console.log(`${priceInEth} ETH from ${offerer}`);
577577
});
578578
```
@@ -730,7 +730,7 @@ const order = await openseaSDK.api.getOrderByHash(
730730
Chain.Mainnet, // Optional: chain
731731
);
732732

733-
console.log(order.protocol_data.parameters);
733+
console.log(order.protocolData.parameters);
734734
```
735735

736736
**Parameters:**
@@ -844,7 +844,7 @@ const result = await openseaSDK.api.offchainCancelOrder(
844844
);
845845

846846
console.log(
847-
`Last signature valid until: ${result.last_signature_issued_valid_until}`,
847+
`Last signature valid until: ${result.lastSignatureIssuedValidUntil}`,
848848
);
849849
```
850850

@@ -930,15 +930,15 @@ Fetch all events with optional filtering.
930930

931931
```typescript
932932
const { asset_events, next } = await openseaSDK.api.getEvents({
933-
event_type: AssetEventType.SALE,
933+
eventType: AssetEventType.SALE,
934934
limit: 50,
935935
after: 1672531200, // Unix timestamp
936936
before: 1675209600, // Unix timestamp
937937
chain: "ethereum",
938938
});
939939

940940
asset_events.forEach((event) => {
941-
if (event.event_type === "sale") {
941+
if (event.eventType === "sale") {
942942
console.log(`Sale: ${event.payment.quantity} at ${event.event_timestamp}`);
943943
}
944944
});
@@ -948,7 +948,7 @@ asset_events.forEach((event) => {
948948

949949
| Parameter | Type | Required | Description |
950950
| ------------ | ------------------------ | -------- | ---------------------------- |
951-
| `event_type` | AssetEventType \| string | No | Filter by event type |
951+
| `eventType` | AssetEventType \| string | No | Filter by event type |
952952
| `after` | number | No | Events after Unix timestamp |
953953
| `before` | number | No | Events before Unix timestamp |
954954
| `limit` | number | No | Number of events to return |
@@ -980,7 +980,7 @@ Fetch events for a specific account.
980980
const { asset_events } = await openseaSDK.api.getEventsByAccount(
981981
"0xfBa662e1a8e91a350702cF3b87D0C2d2Fb4BA57F",
982982
{
983-
event_type: AssetEventType.SALE,
983+
eventType: AssetEventType.SALE,
984984
limit: 100,
985985
},
986986
);
@@ -1005,7 +1005,7 @@ Fetch events for a specific collection.
10051005
const { asset_events } = await openseaSDK.api.getEventsByCollection(
10061006
"boredapeyachtclub",
10071007
{
1008-
event_type: AssetEventType.SALE,
1008+
eventType: AssetEventType.SALE,
10091009
limit: 100,
10101010
after: Math.floor(Date.now() / 1000) - 86400, // Last 24 hours
10111011
},
@@ -1014,7 +1014,7 @@ const { asset_events } = await openseaSDK.api.getEventsByCollection(
10141014
// Calculate total volume in last 24 hours
10151015
let totalVolume = 0n;
10161016
asset_events.forEach((event) => {
1017-
if (event.event_type === "sale") {
1017+
if (event.eventType === "sale") {
10181018
totalVolume += BigInt(event.payment.quantity);
10191019
}
10201020
});
@@ -1041,13 +1041,13 @@ const { asset_events } = await openseaSDK.api.getEventsByNFT(
10411041
"0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D",
10421042
"1",
10431043
{
1044-
event_type: AssetEventType.SALE,
1044+
eventType: AssetEventType.SALE,
10451045
},
10461046
);
10471047

10481048
// Show sale history
10491049
asset_events.forEach((event) => {
1050-
if (event.event_type === "sale") {
1050+
if (event.eventType === "sale") {
10511051
const price = event.payment.quantity;
10521052
const date = new Date(event.event_timestamp * 1000);
10531053
console.log(`Sold for ${price} on ${date.toLocaleDateString()}`);
@@ -1074,7 +1074,7 @@ asset_events.forEach((event) => {
10741074

10751075
```typescript
10761076
{
1077-
event_type: "sale",
1077+
eventType: "sale",
10781078
event_timestamp: 1234567890,
10791079
chain: "ethereum",
10801080
transaction: "0x...",
@@ -1094,8 +1094,8 @@ asset_events.forEach((event) => {
10941094

10951095
```typescript
10961096
{
1097-
event_type: "order",
1098-
order_type: "listing" | "item_offer" | "collection_offer" | "trait_offer",
1097+
eventType: "order",
1098+
orderType: "listing" | "item_offer" | "collection_offer" | "trait_offer",
10991099
event_timestamp: 1234567890,
11001100
maker: "0x...",
11011101
taker: "0x...",
@@ -1110,7 +1110,7 @@ asset_events.forEach((event) => {
11101110

11111111
```typescript
11121112
{
1113-
event_type: "transfer",
1113+
eventType: "transfer",
11141114
event_timestamp: 1234567890,
11151115
transaction: "0x...",
11161116
from_address: "0x...",

developerDocs/getting-started.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ Fetch all events with optional filters:
250250
import { AssetEventType } from "@opensea/sdk";
251251

252252
const { asset_events, next } = await openseaSDK.api.getEvents({
253-
event_type: AssetEventType.SALE, // Optional: filter by event type
253+
eventType: AssetEventType.SALE, // Optional: filter by event type
254254
limit: 50, // Optional: limit results (default: 50)
255255
after: 1672531200, // Optional: filter events after timestamp
256256
before: 1675209600, // Optional: filter events before timestamp
@@ -277,7 +277,7 @@ Fetch events for a specific account address:
277277
const { asset_events } = await openseaSDK.api.getEventsByAccount(
278278
"0x...", // Account address
279279
{
280-
event_type: AssetEventType.SALE,
280+
eventType: AssetEventType.SALE,
281281
limit: 20,
282282
},
283283
);
@@ -308,7 +308,7 @@ const { asset_events } = await openseaSDK.api.getEventsByNFT(
308308
"0x...", // Contract address
309309
"1", // Token ID
310310
{
311-
event_type: AssetEventType.SALE,
311+
eventType: AssetEventType.SALE,
312312
},
313313
);
314314
```
@@ -317,7 +317,7 @@ const { asset_events } = await openseaSDK.api.getEventsByNFT(
317317

318318
Each event includes:
319319

320-
- `event_type`: Type of event (sale, transfer, order, etc.)
320+
- `eventType`: Type of event (sale, transfer, order, etc.)
321321
- `event_timestamp`: When the event occurred (Unix timestamp)
322322
- `chain`: Which blockchain the event occurred on
323323
- `quantity`: Number of items involved
@@ -331,7 +331,7 @@ For **sale events**, additional fields include:
331331

332332
For **order events** (listings/offers), additional fields include:
333333

334-
- `order_type`: "listing", "item_offer", "collection_offer", or "trait_offer"
334+
- `orderType`: "listing", "item_offer", "collection_offer", or "trait_offer"
335335
- `maker` and `taker`: Wallet addresses
336336
- `payment`: Offer/listing amount
337337
- `expiration_date`: When the order expires
@@ -353,7 +353,7 @@ const allEvents = [];
353353

354354
do {
355355
const response = await openseaSDK.api.getEvents({
356-
event_type: AssetEventType.SALE,
356+
eventType: AssetEventType.SALE,
357357
limit: 50,
358358
next: cursor,
359359
});

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.5.0",
3+
"version": "11.0.0",
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.4.0",
48+
"@opensea/api-types": "^0.4.2",
4949
"@opensea/seaport-js": "^4.1.1",
5050
"ethers": "^6.16.0"
5151
},

0 commit comments

Comments
 (0)