Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "opensea-js",
"version": "8.0.8",
"version": "8.0.9",
"description": "TypeScript SDK for the OpenSea marketplace helps developers build new experiences using NFTs and our marketplace data",
"license": "MIT",
"author": "OpenSea Developers",
Expand Down
66 changes: 48 additions & 18 deletions src/orders/privateListings.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ItemType } from "@opensea/seaport-js/lib/constants";
import {
ConsiderationInputItem,
CreateInputItem,
Expand All @@ -7,6 +8,28 @@ import {
} from "@opensea/seaport-js/lib/types";
import { isCurrencyItem } from "@opensea/seaport-js/lib/utils/item";
import { generateRandomSalt } from "@opensea/seaport-js/lib/utils/order";
import { ZeroAddress } from "ethers";

/**
* Compute the native currency (ETH) value required to fulfill a private listing.
* Sums all native currency consideration items not going to the taker.
* @param order The private listing order
* @param takerAddress The address of the private listing recipient
* @returns The total native currency value as a bigint
*/
export const computePrivateListingValue = (
order: OrderWithCounter,
takerAddress: string,
): bigint => {
return order.parameters.consideration
.filter(
(item) =>
item.recipient.toLowerCase() !== takerAddress.toLowerCase() &&
item.token.toLowerCase() === ZeroAddress.toLowerCase() &&
item.itemType === ItemType.NATIVE,
)
.reduce((sum, item) => sum + BigInt(item.startAmount), 0n);
};

export const getPrivateListingConsiderations = (
offer: CreateInputItem[],
Expand All @@ -28,15 +51,18 @@ export const constructPrivateListingCounterOrder = (
item.recipient.toLowerCase() !== privateSaleRecipient.toLowerCase(),
);

if (!paymentItems.every((item) => isCurrencyItem(item))) {
throw new Error(
"The consideration for the private listing did not contain only currency items",
);
}
if (
!paymentItems.every((item) => item.itemType === paymentItems[0].itemType)
) {
throw new Error("Not all currency items were the same for private order");
// Only validate payment items if there are any (zero-payment private listings are valid)
if (paymentItems.length > 0) {
if (!paymentItems.every((item) => isCurrencyItem(item))) {
throw new Error(
"The consideration for the private listing did not contain only currency items",
);
}
if (
!paymentItems.every((item) => item.itemType === paymentItems[0].itemType)
) {
throw new Error("Not all currency items were the same for private order");
}
}

const { aggregatedStartAmount, aggregatedEndAmount } = paymentItems.reduce(
Expand All @@ -54,15 +80,19 @@ export const constructPrivateListingCounterOrder = (
parameters: {
...order.parameters,
offerer: privateSaleRecipient,
offer: [
{
itemType: paymentItems[0].itemType,
token: paymentItems[0].token,
identifierOrCriteria: paymentItems[0].identifierOrCriteria,
startAmount: aggregatedStartAmount.toString(),
endAmount: aggregatedEndAmount.toString(),
},
],
// Empty offer for zero-payment private listings, single aggregated item otherwise
offer:
paymentItems.length > 0
? [
{
itemType: paymentItems[0].itemType,
token: paymentItems[0].token,
identifierOrCriteria: paymentItems[0].identifierOrCriteria,
startAmount: aggregatedStartAmount.toString(),
endAmount: aggregatedEndAmount.toString(),
},
]
: [],
// The consideration here is empty as the original private listing order supplies
// the taker address to receive the desired items.
consideration: [],
Expand Down
11 changes: 10 additions & 1 deletion src/sdk/fulfillment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { SDKContext } from "./context";
import { OrdersManager } from "./orders";
import { Listing, Offer, Order } from "../api/types";
import {
computePrivateListingValue,
constructPrivateListingCounterOrder,
getPrivateListingFulfillments,
} from "../orders/privateListings";
Expand Down Expand Up @@ -60,6 +61,14 @@ export class FulfillmentManager {
order.taker.address,
);
const fulfillments = getPrivateListingFulfillments(order.protocolData);

// Compute ETH value from original order's consideration items
// This handles both standard private listings and zero-payment listings (e.g., rewards)
const value = computePrivateListingValue(
order.protocolData,
order.taker.address,
);

const seaport = getSeaportInstance(
order.protocolAddress,
this.context.seaport,
Expand All @@ -70,7 +79,7 @@ export class FulfillmentManager {
fulfillments,
overrides: {
...overrides,
value: counterOrder.parameters.offer[0].startAmount,
value,
},
accountAddress,
domain,
Expand Down
Loading