Skip to content

Commit 3b1a85a

Browse files
fix: exclude marketplace fees from private listings
- Modified getFees method to accept isPrivateListing parameter - Added marketplace fee filtering logic for private listings - Added integration tests for private listing fee behavior - Fixes issue #1710 where private listings were rejected by API The fix ensures that when buyerAddress is specified in createListing, marketplace fees (2.5% non-required fees) are excluded from the consideration items, preventing API rejection of private transactions. Co-Authored-By: Ryan Ghods <ralxzryan@gmail.com>
1 parent a156373 commit 3b1a85a

2 files changed

Lines changed: 91 additions & 0 deletions

File tree

src/sdk.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {
4444
EventData,
4545
EventType,
4646
Chain,
47+
Fee,
4748
OpenSeaAPIConfig,
4849
OpenSeaCollection,
4950
OrderSide,
@@ -260,18 +261,25 @@ export class OpenSeaSDK {
260261
startAmount,
261262
endAmount,
262263
excludeOptionalCreatorFees,
264+
isPrivateListing = false,
263265
}: {
264266
collection: OpenSeaCollection;
265267
seller?: string;
266268
paymentTokenAddress: string;
267269
startAmount: bigint;
268270
endAmount?: bigint;
269271
excludeOptionalCreatorFees?: boolean;
272+
isPrivateListing?: boolean;
270273
}): Promise<ConsiderationInputItem[]> {
271274
let collectionFees = collection.fees;
272275
if (excludeOptionalCreatorFees) {
273276
collectionFees = collectionFees.filter((fee) => fee.required);
274277
}
278+
if (isPrivateListing) {
279+
collectionFees = collectionFees.filter((fee) =>
280+
this.isNotMarketplaceFee(fee),
281+
);
282+
}
275283
const collectionFeesBasisPoints = totalBasisPointsForFees(collectionFees);
276284
const sellerBasisPoints = INVERSE_BASIS_POINT - collectionFeesBasisPoints;
277285

@@ -302,6 +310,14 @@ export class OpenSeaSDK {
302310
return considerationItems;
303311
}
304312

313+
private isNotMarketplaceFee(fee: Fee): boolean {
314+
const marketplaceFeePercentage = 2.5;
315+
const isMarketplaceFeePercentage = fee.fee === marketplaceFeePercentage;
316+
const isOptionalFee = !fee.required;
317+
318+
return !(isMarketplaceFeePercentage || isOptionalFee);
319+
}
320+
305321
private getNFTItems(
306322
nfts: NFT[],
307323
quantities: bigint[] = [],
@@ -385,6 +401,7 @@ export class OpenSeaSDK {
385401
paymentTokenAddress,
386402
startAmount: basePrice,
387403
excludeOptionalCreatorFees,
404+
isPrivateListing: false,
388405
});
389406

390407
if (collection.requiredZone) {
@@ -507,6 +524,7 @@ export class OpenSeaSDK {
507524
startAmount: basePrice,
508525
endAmount: endPrice,
509526
excludeOptionalCreatorFees,
527+
isPrivateListing: !!buyerAddress,
510528
});
511529

512530
if (buyerAddress) {
@@ -623,6 +641,7 @@ export class OpenSeaSDK {
623641
startAmount: basePrice,
624642
endAmount: basePrice,
625643
excludeOptionalCreatorFees,
644+
isPrivateListing: false,
626645
});
627646

628647
const considerationItems = [

test/sdk/privateListing.spec.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { expect } from "chai";
2+
import { suite, test } from "mocha";
3+
import {
4+
LISTING_AMOUNT,
5+
TOKEN_ADDRESS_MAINNET,
6+
TOKEN_ID_MAINNET,
7+
sdk,
8+
walletAddress,
9+
} from "../integration/setup";
10+
import { expectValidOrder } from "../utils/utils";
11+
12+
const ONE_HOUR = Math.floor(Date.now() / 1000) + 3600;
13+
const expirationTime = ONE_HOUR;
14+
15+
suite("SDK: Private Listings Integration", () => {
16+
test("Post Private Listing - Mainnet", async function () {
17+
if (!TOKEN_ADDRESS_MAINNET || !TOKEN_ID_MAINNET) {
18+
this.skip();
19+
}
20+
21+
const buyerAddress = "0x0000000000000000000000000000000000000001";
22+
23+
const privateListing = {
24+
accountAddress: walletAddress,
25+
startAmount: LISTING_AMOUNT,
26+
asset: {
27+
tokenAddress: TOKEN_ADDRESS_MAINNET as string,
28+
tokenId: TOKEN_ID_MAINNET as string,
29+
},
30+
buyerAddress,
31+
expirationTime,
32+
};
33+
34+
const order = await sdk.createListing(privateListing);
35+
expectValidOrder(order);
36+
37+
expect(order.protocolData.parameters.consideration).to.exist;
38+
39+
const marketplaceFeeRecipient =
40+
"0x0000a26b00c1F0DF003000390027140000fAa719";
41+
const hasMarketplaceFee = order.protocolData.parameters.consideration.some(
42+
(item: { recipient?: string }) =>
43+
item.recipient?.toLowerCase() === marketplaceFeeRecipient.toLowerCase(),
44+
);
45+
46+
expect(hasMarketplaceFee).to.be.false;
47+
});
48+
49+
test("Post Regular Listing - Mainnet (for comparison)", async function () {
50+
if (!TOKEN_ADDRESS_MAINNET || !TOKEN_ID_MAINNET) {
51+
this.skip();
52+
}
53+
54+
const regularListing = {
55+
accountAddress: walletAddress,
56+
startAmount: LISTING_AMOUNT,
57+
asset: {
58+
tokenAddress: TOKEN_ADDRESS_MAINNET as string,
59+
tokenId: TOKEN_ID_MAINNET as string,
60+
},
61+
expirationTime,
62+
};
63+
64+
const order = await sdk.createListing(regularListing);
65+
expectValidOrder(order);
66+
67+
expect(order.protocolData.parameters.consideration).to.exist;
68+
expect(
69+
order.protocolData.parameters.consideration.length,
70+
).to.be.greaterThan(0);
71+
});
72+
});

0 commit comments

Comments
 (0)