Skip to content

Commit 1d929b1

Browse files
committed
Release v10.3.0
1 parent a93dac7 commit 1d929b1

8 files changed

Lines changed: 279 additions & 2 deletions

File tree

CHANGELOG.md

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

3+
## 10.3.0
4+
5+
### Minor Changes
6+
7+
- fc44d9f: feat: add cross-chain fulfillment support
8+
9+
Add support for the new `POST /api/v2/listings/cross_chain_fulfillment_data` endpoint across SDK, CLI, and skill packages.
10+
11+
**SDK**: New `getCrossChainFulfillmentData()` method on both the API client and the base SDK class. Accepts listings, fulfiller, payment token (chain + address), and optional recipient. Returns ordered transactions to sign and submit.
12+
13+
**CLI**: New `listings cross-chain-fulfill` subcommand with `--hashes`, `--listing-chain`, `--protocol-address`, `--fulfiller`, `--payment-chain`, `--payment-token`, and optional `--recipient` flags. Supports sweeping multiple listings via comma-separated hashes.
14+
15+
**Skill**: New `opensea-cross-chain-fulfill.sh` script and updated SKILL.md with cross-chain buying workflow documentation.
16+
317
## 10.2.1
418

519
### Patch Changes

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@opensea/sdk",
3-
"version": "10.2.1",
3+
"version": "10.3.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",

src/api/api.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import {
3535
type CancelOrderResponse,
3636
type CollectionOffer,
3737
CollectionOrderByOption,
38+
type CrossChainFulfillmentDataRequest,
39+
type CrossChainFulfillmentDataResponse,
3840
type DropMintRequest,
3941
type DropMintResponse,
4042
type GetAccountTokensArgs,
@@ -319,6 +321,19 @@ export class OpenSeaAPI {
319321
)
320322
}
321323

324+
/**
325+
* Get cross-chain fulfillment data for one or more listings.
326+
* Supports same-chain, cross-token, and cross-chain purchases (up to 50 listings).
327+
* All listings must be EVM (Seaport orders). Payment can be from any chain (EVM or SVM).
328+
* @param request The cross-chain fulfillment request containing listings, fulfiller, payment, and optional recipient
329+
* @returns The {@link CrossChainFulfillmentDataResponse} with ordered transactions to sign and submit
330+
*/
331+
public async getCrossChainFulfillmentData(
332+
request: CrossChainFulfillmentDataRequest,
333+
): Promise<CrossChainFulfillmentDataResponse> {
334+
return this.listingsAPI.getCrossChainFulfillmentData(request)
335+
}
336+
322337
/**
323338
* Generate the data needed to fulfill a listing or an offer onchain.
324339
* @param fulfillerAddress The wallet address which will be used to fulfill the order

src/api/apiPaths.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,10 @@ export const getTokenGroupPath = (slug: string) => {
231231
return `${API_V2_PREFIX}/token-groups/${slug}`
232232
}
233233

234+
export const getCrossChainFulfillmentDataPath = () => {
235+
return `${API_V2_PREFIX}/listings/cross_chain_fulfillment_data`
236+
}
237+
234238
export const getInstantApiKeyPath = () => {
235239
return `${API_V2_PREFIX}/auth/keys`
236240
}

src/api/listings.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import {
44
getAllListingsAPIPath,
55
getBestListingAPIPath,
66
getBestListingsAPIPath,
7+
getCrossChainFulfillmentDataPath,
78
getOrdersAPIPath,
89
} from "./apiPaths"
910
import type { Fetcher } from "./fetcher"
1011
import {
12+
type CrossChainFulfillmentDataRequest,
13+
type CrossChainFulfillmentDataResponse,
1114
encodeTraitsParam,
1215
type GetBestListingResponse,
1316
type GetListingsResponse,
@@ -127,4 +130,18 @@ export class ListingsAPI {
127130
)
128131
return response
129132
}
133+
134+
/**
135+
* Get cross-chain fulfillment data for one or more listings.
136+
* Supports same-chain, cross-token, and cross-chain purchases.
137+
* @param request The cross-chain fulfillment request
138+
*/
139+
async getCrossChainFulfillmentData(
140+
request: CrossChainFulfillmentDataRequest,
141+
): Promise<CrossChainFulfillmentDataResponse> {
142+
return this.fetcher.post<CrossChainFulfillmentDataResponse>(
143+
getCrossChainFulfillmentDataPath(),
144+
request,
145+
)
146+
}
130147
}

src/api/types.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,3 +1296,62 @@ export type ValidateMetadataResponse = {
12961296
* @category API Response Types
12971297
*/
12981298
export type GetNFTMetadataResponse = AssetMetadataResponse
1299+
1300+
// ─── Cross-chain fulfillment types ──────────────────────────────────
1301+
1302+
/**
1303+
* A single listing to fulfill in a cross-chain fulfillment request.
1304+
* @category API Models
1305+
*/
1306+
export type CrossChainListing = {
1307+
/** Order hash (66 chars, starts with 0x) */
1308+
hash: string
1309+
/** Chain slug where the listing lives (must be EVM) */
1310+
chain: string
1311+
/** Seaport contract address */
1312+
protocol_address: string
1313+
}
1314+
1315+
/**
1316+
* Request body for the cross-chain fulfillment endpoint.
1317+
* @category API Query Args
1318+
*/
1319+
export type CrossChainFulfillmentDataRequest = {
1320+
/** Array of listings to fulfill (up to 50) */
1321+
listings: CrossChainListing[]
1322+
/** The buyer's wallet */
1323+
fulfiller: { address: string }
1324+
/** Payment token details */
1325+
payment: {
1326+
/** Chain slug of the payment token (EVM or SVM) */
1327+
chain: string
1328+
/** Payment token contract address (0x0...0 for native, Base58 for Solana) */
1329+
token_address: string
1330+
}
1331+
/** Optional: different recipient address for the NFTs */
1332+
recipient?: string
1333+
}
1334+
1335+
/**
1336+
* A single transaction returned by the cross-chain fulfillment endpoint.
1337+
* @category API Response Types
1338+
*/
1339+
export type CrossChainTransaction = {
1340+
/** Chain slug for the transaction */
1341+
chain: string
1342+
/** Contract address to call */
1343+
to: string
1344+
/** Encoded calldata */
1345+
data: string
1346+
/** Wei amount as string */
1347+
value: string
1348+
}
1349+
1350+
/**
1351+
* Response from the cross-chain fulfillment endpoint.
1352+
* @category API Response Types
1353+
*/
1354+
export type CrossChainFulfillmentDataResponse = {
1355+
/** Ordered list of transactions to sign and submit */
1356+
transactions: CrossChainTransaction[]
1357+
}

src/sdk/base.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,46 @@ export class BaseOpenSeaSDK {
359359
return this._fulfillmentManager.fulfillPrivateOrder(options)
360360
}
361361

362+
/**
363+
* Get cross-chain fulfillment data for one or more listings.
364+
* Supports same-chain, cross-token, and cross-chain purchases (up to 50 listings).
365+
* All listings must be EVM (Seaport orders). Payment can be from any chain (EVM or SVM).
366+
* Returns an ordered list of transactions to sign and submit.
367+
* @param options
368+
* @param options.listings Array of listings to fulfill (order hash, chain, protocol address)
369+
* @param options.fulfillerAddress The buyer's wallet address
370+
* @param options.paymentChain Chain slug of the payment token (EVM or SVM)
371+
* @param options.paymentTokenAddress Payment token contract address (0x0...0 for native)
372+
* @param options.recipientAddress Optional different recipient for the NFTs
373+
*/
374+
public async getCrossChainFulfillmentData(options: {
375+
listings: Array<{
376+
hash: string
377+
chain: string
378+
protocolAddress: string
379+
}>
380+
fulfillerAddress: string
381+
paymentChain: string
382+
paymentTokenAddress: string
383+
recipientAddress?: string
384+
}) {
385+
return this.api.getCrossChainFulfillmentData({
386+
listings: options.listings.map(l => ({
387+
hash: l.hash,
388+
chain: l.chain,
389+
protocol_address: l.protocolAddress,
390+
})),
391+
fulfiller: { address: options.fulfillerAddress },
392+
payment: {
393+
chain: options.paymentChain,
394+
token_address: options.paymentTokenAddress,
395+
},
396+
...(options.recipientAddress
397+
? { recipient: options.recipientAddress }
398+
: {}),
399+
})
400+
}
401+
362402
/** Returns whether an order is fulfillable. */
363403
public async isOrderFulfillable(options: {
364404
order: OrderV2

test/api/listings.spec.ts

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { describe, expect, test, vi } from "vitest"
22
import { ListingsAPI } from "../../src/api/listings"
33
import type {
4+
CrossChainFulfillmentDataResponse,
45
GetBestListingResponse,
56
GetListingsResponse,
67
Listing,
@@ -11,11 +12,17 @@ import { createMockFetcher } from "../fixtures/fetcher"
1112

1213
describe("API: ListingsAPI", () => {
1314
let mockGet: ReturnType<typeof vi.fn>
15+
let mockPost: ReturnType<typeof vi.fn>
1416
let listingsAPI: ListingsAPI
1517

1618
beforeEach(() => {
17-
const { fetcher, mockGet: getMock } = createMockFetcher()
19+
const {
20+
fetcher,
21+
mockGet: getMock,
22+
mockPost: postMock,
23+
} = createMockFetcher()
1824
mockGet = getMock
25+
mockPost = postMock
1926
listingsAPI = new ListingsAPI(fetcher, Chain.Mainnet)
2027
})
2128

@@ -720,4 +727,125 @@ describe("API: ListingsAPI", () => {
720727
expect(result.listings[0].remaining_quantity).toBe(3)
721728
})
722729
})
730+
731+
describe("getCrossChainFulfillmentData", () => {
732+
test("posts to the correct endpoint with the full request body", async () => {
733+
const mockResponse: CrossChainFulfillmentDataResponse = {
734+
transactions: [
735+
{ chain: "ethereum", to: "0xabc", data: "0x123", value: "0" },
736+
],
737+
}
738+
739+
mockPost.mockResolvedValue(mockResponse)
740+
741+
const result = await listingsAPI.getCrossChainFulfillmentData({
742+
listings: [
743+
{
744+
hash: "0xorderhash",
745+
chain: "ethereum",
746+
protocol_address: "0xseaport",
747+
},
748+
],
749+
fulfiller: { address: "0xbuyer" },
750+
payment: {
751+
chain: "base",
752+
token_address: "0x0000000000000000000000000000000000000000",
753+
},
754+
})
755+
756+
expect(mockPost).toHaveBeenCalledWith(
757+
"/api/v2/listings/cross_chain_fulfillment_data",
758+
{
759+
listings: [
760+
{
761+
hash: "0xorderhash",
762+
chain: "ethereum",
763+
protocol_address: "0xseaport",
764+
},
765+
],
766+
fulfiller: { address: "0xbuyer" },
767+
payment: {
768+
chain: "base",
769+
token_address: "0x0000000000000000000000000000000000000000",
770+
},
771+
},
772+
)
773+
expect(result.transactions).toHaveLength(1)
774+
expect(result.transactions[0].chain).toBe("ethereum")
775+
})
776+
777+
test("includes optional recipient in request", async () => {
778+
mockPost.mockResolvedValue({ transactions: [] })
779+
780+
await listingsAPI.getCrossChainFulfillmentData({
781+
listings: [
782+
{
783+
hash: "0xhash",
784+
chain: "ethereum",
785+
protocol_address: "0xseaport",
786+
},
787+
],
788+
fulfiller: { address: "0xbuyer" },
789+
payment: {
790+
chain: "base",
791+
token_address: "0x0000000000000000000000000000000000000000",
792+
},
793+
recipient: "0xrecipient",
794+
})
795+
796+
expect(mockPost).toHaveBeenCalledWith(
797+
"/api/v2/listings/cross_chain_fulfillment_data",
798+
expect.objectContaining({
799+
recipient: "0xrecipient",
800+
}),
801+
)
802+
})
803+
804+
test("supports multiple listings for sweep", async () => {
805+
mockPost.mockResolvedValue({
806+
transactions: [
807+
{ chain: "ethereum", to: "0xabc", data: "0x111", value: "0" },
808+
{ chain: "ethereum", to: "0xabc", data: "0x222", value: "0" },
809+
],
810+
})
811+
812+
const result = await listingsAPI.getCrossChainFulfillmentData({
813+
listings: [
814+
{
815+
hash: "0xhash1",
816+
chain: "ethereum",
817+
protocol_address: "0xseaport",
818+
},
819+
{
820+
hash: "0xhash2",
821+
chain: "ethereum",
822+
protocol_address: "0xseaport",
823+
},
824+
],
825+
fulfiller: { address: "0xbuyer" },
826+
payment: {
827+
chain: "base",
828+
token_address: "0x0000000000000000000000000000000000000000",
829+
},
830+
})
831+
832+
expect(result.transactions).toHaveLength(2)
833+
expect(mockPost.mock.calls[0][1].listings).toHaveLength(2)
834+
})
835+
836+
test("throws on API error", async () => {
837+
mockPost.mockRejectedValue(new Error("Bad Request"))
838+
839+
await expect(
840+
listingsAPI.getCrossChainFulfillmentData({
841+
listings: [],
842+
fulfiller: { address: "0xbuyer" },
843+
payment: {
844+
chain: "base",
845+
token_address: "0x0000000000000000000000000000000000000000",
846+
},
847+
}),
848+
).rejects.toThrow("Bad Request")
849+
})
850+
})
723851
})

0 commit comments

Comments
 (0)