@net-protocol/bazaar 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.mjs CHANGED
@@ -1,15 +1,229 @@
1
1
  import { useState, useMemo, useEffect } from 'react';
2
2
  import { usePublicClient } from 'wagmi';
3
3
  import { useNetMessageCount, useNetMessages } from '@net-protocol/core/react';
4
- import { createPublicClient, http, defineChain, decodeAbiParameters, formatEther } from 'viem';
4
+ import { createPublicClient, http, defineChain, decodeAbiParameters, formatEther, keccak256, toBytes } from 'viem';
5
+ import { readContract } from 'viem/actions';
5
6
  import { NetClient } from '@net-protocol/core';
6
7
  import { Seaport } from '@opensea/seaport-js';
7
8
  import { ethers } from 'ethers';
8
- import { readContract } from 'viem/actions';
9
9
  import { STORAGE_CONTRACT } from '@net-protocol/storage';
10
10
 
11
11
  // src/hooks/useBazaarListings.ts
12
12
 
13
+ // src/abis/bazaarV2.ts
14
+ var BAZAAR_V2_ABI = [
15
+ {
16
+ type: "function",
17
+ name: "submit",
18
+ inputs: [
19
+ {
20
+ name: "submission",
21
+ type: "tuple",
22
+ internalType: "struct BazaarV1.Submission",
23
+ components: [
24
+ {
25
+ name: "parameters",
26
+ type: "tuple",
27
+ internalType: "struct OrderParameters",
28
+ components: [
29
+ { name: "offerer", type: "address", internalType: "address" },
30
+ { name: "zone", type: "address", internalType: "address" },
31
+ {
32
+ name: "offer",
33
+ type: "tuple[]",
34
+ internalType: "struct OfferItem[]",
35
+ components: [
36
+ { name: "itemType", type: "uint8", internalType: "enum ItemType" },
37
+ { name: "token", type: "address", internalType: "address" },
38
+ { name: "identifierOrCriteria", type: "uint256", internalType: "uint256" },
39
+ { name: "startAmount", type: "uint256", internalType: "uint256" },
40
+ { name: "endAmount", type: "uint256", internalType: "uint256" }
41
+ ]
42
+ },
43
+ {
44
+ name: "consideration",
45
+ type: "tuple[]",
46
+ internalType: "struct ConsiderationItem[]",
47
+ components: [
48
+ { name: "itemType", type: "uint8", internalType: "enum ItemType" },
49
+ { name: "token", type: "address", internalType: "address" },
50
+ { name: "identifierOrCriteria", type: "uint256", internalType: "uint256" },
51
+ { name: "startAmount", type: "uint256", internalType: "uint256" },
52
+ { name: "endAmount", type: "uint256", internalType: "uint256" },
53
+ { name: "recipient", type: "address", internalType: "address payable" }
54
+ ]
55
+ },
56
+ { name: "orderType", type: "uint8", internalType: "enum OrderType" },
57
+ { name: "startTime", type: "uint256", internalType: "uint256" },
58
+ { name: "endTime", type: "uint256", internalType: "uint256" },
59
+ { name: "zoneHash", type: "bytes32", internalType: "bytes32" },
60
+ { name: "salt", type: "uint256", internalType: "uint256" },
61
+ { name: "conduitKey", type: "bytes32", internalType: "bytes32" },
62
+ { name: "totalOriginalConsiderationItems", type: "uint256", internalType: "uint256" }
63
+ ]
64
+ },
65
+ { name: "counter", type: "uint256", internalType: "uint256" },
66
+ { name: "signature", type: "bytes", internalType: "bytes" }
67
+ ]
68
+ }
69
+ ],
70
+ outputs: [],
71
+ stateMutability: "nonpayable"
72
+ },
73
+ {
74
+ type: "event",
75
+ name: "Submitted",
76
+ inputs: [
77
+ { name: "tokenAddress", type: "address", indexed: true, internalType: "address" },
78
+ { name: "tokenId", type: "uint256", indexed: true, internalType: "uint256" }
79
+ ],
80
+ anonymous: false
81
+ },
82
+ { type: "error", name: "ConsiderationItemsMustContainTwoItems", inputs: [] },
83
+ { type: "error", name: "OfferItemsMustContainOneItem", inputs: [] }
84
+ ];
85
+ var BAZAAR_COLLECTION_OFFERS_ABI = [
86
+ {
87
+ type: "function",
88
+ name: "NET_APP_NAME",
89
+ inputs: [],
90
+ outputs: [{ name: "", type: "string", internalType: "string" }],
91
+ stateMutability: "view"
92
+ },
93
+ {
94
+ type: "function",
95
+ name: "submit",
96
+ inputs: [
97
+ {
98
+ name: "submission",
99
+ type: "tuple",
100
+ internalType: "struct BazaarV2CollectionOffers.Submission",
101
+ components: [
102
+ {
103
+ name: "parameters",
104
+ type: "tuple",
105
+ internalType: "struct OrderParameters",
106
+ components: [
107
+ { name: "offerer", type: "address", internalType: "address" },
108
+ { name: "zone", type: "address", internalType: "address" },
109
+ {
110
+ name: "offer",
111
+ type: "tuple[]",
112
+ internalType: "struct OfferItem[]",
113
+ components: [
114
+ { name: "itemType", type: "uint8", internalType: "enum ItemType" },
115
+ { name: "token", type: "address", internalType: "address" },
116
+ { name: "identifierOrCriteria", type: "uint256", internalType: "uint256" },
117
+ { name: "startAmount", type: "uint256", internalType: "uint256" },
118
+ { name: "endAmount", type: "uint256", internalType: "uint256" }
119
+ ]
120
+ },
121
+ {
122
+ name: "consideration",
123
+ type: "tuple[]",
124
+ internalType: "struct ConsiderationItem[]",
125
+ components: [
126
+ { name: "itemType", type: "uint8", internalType: "enum ItemType" },
127
+ { name: "token", type: "address", internalType: "address" },
128
+ { name: "identifierOrCriteria", type: "uint256", internalType: "uint256" },
129
+ { name: "startAmount", type: "uint256", internalType: "uint256" },
130
+ { name: "endAmount", type: "uint256", internalType: "uint256" },
131
+ { name: "recipient", type: "address", internalType: "address payable" }
132
+ ]
133
+ },
134
+ { name: "orderType", type: "uint8", internalType: "enum OrderType" },
135
+ { name: "startTime", type: "uint256", internalType: "uint256" },
136
+ { name: "endTime", type: "uint256", internalType: "uint256" },
137
+ { name: "zoneHash", type: "bytes32", internalType: "bytes32" },
138
+ { name: "salt", type: "uint256", internalType: "uint256" },
139
+ { name: "conduitKey", type: "bytes32", internalType: "bytes32" },
140
+ { name: "totalOriginalConsiderationItems", type: "uint256", internalType: "uint256" }
141
+ ]
142
+ },
143
+ { name: "counter", type: "uint256", internalType: "uint256" },
144
+ { name: "signature", type: "bytes", internalType: "bytes" }
145
+ ]
146
+ }
147
+ ],
148
+ outputs: [],
149
+ stateMutability: "nonpayable"
150
+ },
151
+ {
152
+ type: "event",
153
+ name: "Submitted",
154
+ inputs: [
155
+ { name: "tokenAddress", type: "address", indexed: true, internalType: "address" },
156
+ { name: "tokenId", type: "uint256", indexed: true, internalType: "uint256" }
157
+ ],
158
+ anonymous: false
159
+ },
160
+ { type: "error", name: "ConsiderationItemsMustContainTwoItems", inputs: [] },
161
+ { type: "error", name: "ConsiderationItemsMustIncludeFeeAddress", inputs: [] },
162
+ { type: "error", name: "ConsiderationItemsMustIncludeMsgSender", inputs: [] },
163
+ { type: "error", name: "InvalidFee", inputs: [] },
164
+ { type: "error", name: "OfferItemsMustContainOneItem", inputs: [] }
165
+ ];
166
+ var BAZAAR_ERC20_OFFERS_ABI = [
167
+ {
168
+ type: "function",
169
+ name: "submit",
170
+ inputs: [
171
+ {
172
+ name: "submission",
173
+ type: "tuple",
174
+ internalType: "struct BazaarV2CollectionOffers.Submission",
175
+ components: [
176
+ {
177
+ name: "parameters",
178
+ type: "tuple",
179
+ internalType: "struct OrderParameters",
180
+ components: [
181
+ { name: "offerer", type: "address", internalType: "address" },
182
+ { name: "zone", type: "address", internalType: "address" },
183
+ {
184
+ name: "offer",
185
+ type: "tuple[]",
186
+ internalType: "struct OfferItem[]",
187
+ components: [
188
+ { name: "itemType", type: "uint8", internalType: "enum ItemType" },
189
+ { name: "token", type: "address", internalType: "address" },
190
+ { name: "identifierOrCriteria", type: "uint256", internalType: "uint256" },
191
+ { name: "startAmount", type: "uint256", internalType: "uint256" },
192
+ { name: "endAmount", type: "uint256", internalType: "uint256" }
193
+ ]
194
+ },
195
+ {
196
+ name: "consideration",
197
+ type: "tuple[]",
198
+ internalType: "struct ConsiderationItem[]",
199
+ components: [
200
+ { name: "itemType", type: "uint8", internalType: "enum ItemType" },
201
+ { name: "token", type: "address", internalType: "address" },
202
+ { name: "identifierOrCriteria", type: "uint256", internalType: "uint256" },
203
+ { name: "startAmount", type: "uint256", internalType: "uint256" },
204
+ { name: "endAmount", type: "uint256", internalType: "uint256" },
205
+ { name: "recipient", type: "address", internalType: "address payable" }
206
+ ]
207
+ },
208
+ { name: "orderType", type: "uint8", internalType: "enum OrderType" },
209
+ { name: "startTime", type: "uint256", internalType: "uint256" },
210
+ { name: "endTime", type: "uint256", internalType: "uint256" },
211
+ { name: "zoneHash", type: "bytes32", internalType: "bytes32" },
212
+ { name: "salt", type: "uint256", internalType: "uint256" },
213
+ { name: "conduitKey", type: "bytes32", internalType: "bytes32" },
214
+ { name: "totalOriginalConsiderationItems", type: "uint256", internalType: "uint256" }
215
+ ]
216
+ },
217
+ { name: "counter", type: "uint256", internalType: "uint256" },
218
+ { name: "signature", type: "bytes", internalType: "bytes" }
219
+ ]
220
+ }
221
+ ],
222
+ outputs: [],
223
+ stateMutability: "nonpayable"
224
+ }
225
+ ];
226
+
13
227
  // src/abis/helpers.ts
14
228
  var BULK_SEAPORT_ORDER_STATUS_FETCHER_ABI = [
15
229
  { type: "constructor", inputs: [], stateMutability: "nonpayable" },
@@ -119,6 +333,161 @@ var SEAPORT_CANCEL_ABI = [
119
333
  type: "function"
120
334
  }
121
335
  ];
336
+ var ORDER_PARAMETERS_COMPONENTS = [
337
+ { internalType: "address", name: "offerer", type: "address" },
338
+ { internalType: "address", name: "zone", type: "address" },
339
+ {
340
+ components: [
341
+ { internalType: "enum ItemType", name: "itemType", type: "uint8" },
342
+ { internalType: "address", name: "token", type: "address" },
343
+ { internalType: "uint256", name: "identifierOrCriteria", type: "uint256" },
344
+ { internalType: "uint256", name: "startAmount", type: "uint256" },
345
+ { internalType: "uint256", name: "endAmount", type: "uint256" }
346
+ ],
347
+ internalType: "struct OfferItem[]",
348
+ name: "offer",
349
+ type: "tuple[]"
350
+ },
351
+ {
352
+ components: [
353
+ { internalType: "enum ItemType", name: "itemType", type: "uint8" },
354
+ { internalType: "address", name: "token", type: "address" },
355
+ { internalType: "uint256", name: "identifierOrCriteria", type: "uint256" },
356
+ { internalType: "uint256", name: "startAmount", type: "uint256" },
357
+ { internalType: "uint256", name: "endAmount", type: "uint256" },
358
+ { internalType: "address payable", name: "recipient", type: "address" }
359
+ ],
360
+ internalType: "struct ConsiderationItem[]",
361
+ name: "consideration",
362
+ type: "tuple[]"
363
+ },
364
+ { internalType: "enum OrderType", name: "orderType", type: "uint8" },
365
+ { internalType: "uint256", name: "startTime", type: "uint256" },
366
+ { internalType: "uint256", name: "endTime", type: "uint256" },
367
+ { internalType: "bytes32", name: "zoneHash", type: "bytes32" },
368
+ { internalType: "uint256", name: "salt", type: "uint256" },
369
+ { internalType: "bytes32", name: "conduitKey", type: "bytes32" },
370
+ { internalType: "uint256", name: "totalOriginalConsiderationItems", type: "uint256" }
371
+ ];
372
+ var SEAPORT_FULFILL_ORDER_ABI = [
373
+ {
374
+ inputs: [
375
+ {
376
+ components: [
377
+ {
378
+ components: ORDER_PARAMETERS_COMPONENTS,
379
+ internalType: "struct OrderParameters",
380
+ name: "parameters",
381
+ type: "tuple"
382
+ },
383
+ { internalType: "bytes", name: "signature", type: "bytes" }
384
+ ],
385
+ internalType: "struct Order",
386
+ name: "",
387
+ type: "tuple"
388
+ },
389
+ { internalType: "bytes32", name: "fulfillerConduitKey", type: "bytes32" }
390
+ ],
391
+ name: "fulfillOrder",
392
+ outputs: [{ internalType: "bool", name: "fulfilled", type: "bool" }],
393
+ stateMutability: "payable",
394
+ type: "function"
395
+ }
396
+ ];
397
+ var SEAPORT_FULFILL_ADVANCED_ORDER_ABI = [
398
+ {
399
+ inputs: [
400
+ {
401
+ components: [
402
+ {
403
+ components: ORDER_PARAMETERS_COMPONENTS,
404
+ internalType: "struct OrderParameters",
405
+ name: "parameters",
406
+ type: "tuple"
407
+ },
408
+ { internalType: "uint120", name: "numerator", type: "uint120" },
409
+ { internalType: "uint120", name: "denominator", type: "uint120" },
410
+ { internalType: "bytes", name: "signature", type: "bytes" },
411
+ { internalType: "bytes", name: "extraData", type: "bytes" }
412
+ ],
413
+ internalType: "struct AdvancedOrder",
414
+ name: "",
415
+ type: "tuple"
416
+ },
417
+ {
418
+ components: [
419
+ { internalType: "uint256", name: "orderIndex", type: "uint256" },
420
+ { internalType: "enum Side", name: "side", type: "uint8" },
421
+ { internalType: "uint256", name: "index", type: "uint256" },
422
+ { internalType: "uint256", name: "identifier", type: "uint256" },
423
+ { internalType: "bytes32[]", name: "criteriaProof", type: "bytes32[]" }
424
+ ],
425
+ internalType: "struct CriteriaResolver[]",
426
+ name: "",
427
+ type: "tuple[]"
428
+ },
429
+ { internalType: "bytes32", name: "fulfillerConduitKey", type: "bytes32" },
430
+ { internalType: "address", name: "recipient", type: "address" }
431
+ ],
432
+ name: "fulfillAdvancedOrder",
433
+ outputs: [{ internalType: "bool", name: "fulfilled", type: "bool" }],
434
+ stateMutability: "payable",
435
+ type: "function"
436
+ }
437
+ ];
438
+ var SEAPORT_GET_COUNTER_ABI = [
439
+ {
440
+ inputs: [{ internalType: "address", name: "offerer", type: "address" }],
441
+ name: "getCounter",
442
+ outputs: [{ internalType: "uint256", name: "counter", type: "uint256" }],
443
+ stateMutability: "view",
444
+ type: "function"
445
+ }
446
+ ];
447
+ var ERC721_APPROVAL_ABI = [
448
+ {
449
+ inputs: [
450
+ { internalType: "address", name: "owner", type: "address" },
451
+ { internalType: "address", name: "operator", type: "address" }
452
+ ],
453
+ name: "isApprovedForAll",
454
+ outputs: [{ internalType: "bool", name: "", type: "bool" }],
455
+ stateMutability: "view",
456
+ type: "function"
457
+ },
458
+ {
459
+ inputs: [
460
+ { internalType: "address", name: "operator", type: "address" },
461
+ { internalType: "bool", name: "approved", type: "bool" }
462
+ ],
463
+ name: "setApprovalForAll",
464
+ outputs: [],
465
+ stateMutability: "nonpayable",
466
+ type: "function"
467
+ }
468
+ ];
469
+ var ERC20_APPROVAL_ABI = [
470
+ {
471
+ inputs: [
472
+ { internalType: "address", name: "owner", type: "address" },
473
+ { internalType: "address", name: "spender", type: "address" }
474
+ ],
475
+ name: "allowance",
476
+ outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
477
+ stateMutability: "view",
478
+ type: "function"
479
+ },
480
+ {
481
+ inputs: [
482
+ { internalType: "address", name: "spender", type: "address" },
483
+ { internalType: "uint256", name: "amount", type: "uint256" }
484
+ ],
485
+ name: "approve",
486
+ outputs: [{ internalType: "bool", name: "", type: "bool" }],
487
+ stateMutability: "nonpayable",
488
+ type: "function"
489
+ }
490
+ ];
122
491
 
123
492
  // src/abis/index.ts
124
493
  var BAZAAR_SUBMISSION_ABI = [
@@ -339,6 +708,12 @@ function getCollectionOffersAddress(chainId) {
339
708
  function getSeaportAddress(chainId) {
340
709
  return BAZAAR_CHAIN_CONFIGS[chainId]?.seaportAddress ?? DEFAULT_SEAPORT_ADDRESS;
341
710
  }
711
+ function getFeeCollectorAddress(chainId) {
712
+ return BAZAAR_CHAIN_CONFIGS[chainId]?.feeCollectorAddress ?? DEFAULT_FEE_COLLECTOR_ADDRESS;
713
+ }
714
+ function getNftFeeBps(chainId) {
715
+ return BAZAAR_CHAIN_CONFIGS[chainId]?.nftFeeBps ?? DEFAULT_NFT_FEE_BPS;
716
+ }
342
717
  function getWrappedNativeCurrency(chainId) {
343
718
  return BAZAAR_CHAIN_CONFIGS[chainId]?.wrappedNativeCurrency;
344
719
  }
@@ -870,6 +1245,528 @@ function parseSaleFromStoredData(storedData, chainId) {
870
1245
  return null;
871
1246
  }
872
1247
  }
1248
+ var MAX_UINT256 = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
1249
+ async function checkErc721Approval(client, nftAddress, owner, spender) {
1250
+ const isApproved = await readContract(client, {
1251
+ address: nftAddress,
1252
+ abi: ERC721_APPROVAL_ABI,
1253
+ functionName: "isApprovedForAll",
1254
+ args: [owner, spender]
1255
+ });
1256
+ if (isApproved) {
1257
+ return null;
1258
+ }
1259
+ return {
1260
+ to: nftAddress,
1261
+ functionName: "setApprovalForAll",
1262
+ args: [spender, true],
1263
+ abi: ERC721_APPROVAL_ABI
1264
+ };
1265
+ }
1266
+ async function checkErc20Approval(client, tokenAddress, owner, spender, amount) {
1267
+ const allowance = await readContract(client, {
1268
+ address: tokenAddress,
1269
+ abi: ERC20_APPROVAL_ABI,
1270
+ functionName: "allowance",
1271
+ args: [owner, spender]
1272
+ });
1273
+ if (allowance >= amount) {
1274
+ return null;
1275
+ }
1276
+ return {
1277
+ to: tokenAddress,
1278
+ functionName: "approve",
1279
+ args: [spender, MAX_UINT256],
1280
+ abi: ERC20_APPROVAL_ABI
1281
+ };
1282
+ }
1283
+
1284
+ // src/utils/fulfillment.ts
1285
+ var ZERO_BYTES32 = "0x0000000000000000000000000000000000000000000000000000000000000000";
1286
+ function formatOrderParameters(params) {
1287
+ return {
1288
+ offerer: params.offerer,
1289
+ zone: params.zone,
1290
+ offer: params.offer.map((item) => ({
1291
+ itemType: item.itemType,
1292
+ token: item.token,
1293
+ identifierOrCriteria: item.identifierOrCriteria,
1294
+ startAmount: item.startAmount,
1295
+ endAmount: item.endAmount
1296
+ })),
1297
+ consideration: params.consideration.map((item) => ({
1298
+ itemType: item.itemType,
1299
+ token: item.token,
1300
+ identifierOrCriteria: item.identifierOrCriteria,
1301
+ startAmount: item.startAmount,
1302
+ endAmount: item.endAmount,
1303
+ recipient: item.recipient
1304
+ })),
1305
+ orderType: params.orderType,
1306
+ startTime: params.startTime,
1307
+ endTime: params.endTime,
1308
+ zoneHash: params.zoneHash,
1309
+ salt: params.salt,
1310
+ conduitKey: params.conduitKey,
1311
+ totalOriginalConsiderationItems: params.totalOriginalConsiderationItems
1312
+ };
1313
+ }
1314
+ function getNativeConsiderationValue(params) {
1315
+ return params.consideration.reduce((acc, item) => {
1316
+ if (item.itemType === 0 /* NATIVE */) {
1317
+ return acc + item.startAmount;
1318
+ }
1319
+ return acc;
1320
+ }, BigInt(0));
1321
+ }
1322
+ function buildFulfillListingTx(submission, recipient, seaportAddress) {
1323
+ const advancedOrder = {
1324
+ parameters: formatOrderParameters(submission.parameters),
1325
+ numerator: BigInt(1),
1326
+ denominator: BigInt(1),
1327
+ signature: submission.signature,
1328
+ extraData: "0x"
1329
+ };
1330
+ return {
1331
+ to: seaportAddress,
1332
+ functionName: "fulfillAdvancedOrder",
1333
+ args: [advancedOrder, [], ZERO_BYTES32, recipient],
1334
+ abi: SEAPORT_FULFILL_ADVANCED_ORDER_ABI,
1335
+ value: getNativeConsiderationValue(submission.parameters)
1336
+ };
1337
+ }
1338
+ function buildFulfillCollectionOfferTx(submission, tokenId, recipient, seaportAddress) {
1339
+ const advancedOrder = {
1340
+ parameters: formatOrderParameters(submission.parameters),
1341
+ numerator: BigInt(1),
1342
+ denominator: BigInt(1),
1343
+ signature: submission.signature,
1344
+ extraData: "0x"
1345
+ };
1346
+ const criteriaResolvers = [
1347
+ {
1348
+ orderIndex: BigInt(0),
1349
+ side: 1,
1350
+ // Consideration side
1351
+ index: BigInt(0),
1352
+ // First consideration item (the NFT)
1353
+ identifier: tokenId,
1354
+ criteriaProof: []
1355
+ }
1356
+ ];
1357
+ return {
1358
+ to: seaportAddress,
1359
+ functionName: "fulfillAdvancedOrder",
1360
+ args: [advancedOrder, criteriaResolvers, ZERO_BYTES32, recipient],
1361
+ abi: SEAPORT_FULFILL_ADVANCED_ORDER_ABI,
1362
+ value: BigInt(0)
1363
+ };
1364
+ }
1365
+ function buildFulfillErc20OfferTx(submission, seaportAddress) {
1366
+ const order = {
1367
+ parameters: formatOrderParameters(submission.parameters),
1368
+ signature: submission.signature
1369
+ };
1370
+ return {
1371
+ to: seaportAddress,
1372
+ functionName: "fulfillOrder",
1373
+ args: [order, ZERO_BYTES32],
1374
+ abi: SEAPORT_FULFILL_ORDER_ABI,
1375
+ value: BigInt(0)
1376
+ };
1377
+ }
1378
+ function buildFulfillErc20ListingTx(submission, recipient, seaportAddress) {
1379
+ const advancedOrder = {
1380
+ parameters: formatOrderParameters(submission.parameters),
1381
+ numerator: BigInt(1),
1382
+ denominator: BigInt(1),
1383
+ signature: submission.signature,
1384
+ extraData: "0x"
1385
+ };
1386
+ return {
1387
+ to: seaportAddress,
1388
+ functionName: "fulfillAdvancedOrder",
1389
+ args: [advancedOrder, [], ZERO_BYTES32, recipient],
1390
+ abi: SEAPORT_FULFILL_ADVANCED_ORDER_ABI,
1391
+ value: getNativeConsiderationValue(submission.parameters)
1392
+ };
1393
+ }
1394
+ var SEAPORT_EIP712_DOMAIN_NAME = "Seaport";
1395
+ var SEAPORT_EIP712_DOMAIN_VERSION = "1.6";
1396
+ var ZERO_BYTES322 = "0x0000000000000000000000000000000000000000000000000000000000000000";
1397
+ var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
1398
+ var SEAPORT_ORDER_EIP712_TYPES = {
1399
+ OrderComponents: [
1400
+ { name: "offerer", type: "address" },
1401
+ { name: "zone", type: "address" },
1402
+ { name: "offer", type: "OfferItem[]" },
1403
+ { name: "consideration", type: "ConsiderationItem[]" },
1404
+ { name: "orderType", type: "uint8" },
1405
+ { name: "startTime", type: "uint256" },
1406
+ { name: "endTime", type: "uint256" },
1407
+ { name: "zoneHash", type: "bytes32" },
1408
+ { name: "salt", type: "uint256" },
1409
+ { name: "conduitKey", type: "bytes32" },
1410
+ { name: "counter", type: "uint256" }
1411
+ ],
1412
+ OfferItem: [
1413
+ { name: "itemType", type: "uint8" },
1414
+ { name: "token", type: "address" },
1415
+ { name: "identifierOrCriteria", type: "uint256" },
1416
+ { name: "startAmount", type: "uint256" },
1417
+ { name: "endAmount", type: "uint256" }
1418
+ ],
1419
+ ConsiderationItem: [
1420
+ { name: "itemType", type: "uint8" },
1421
+ { name: "token", type: "address" },
1422
+ { name: "identifierOrCriteria", type: "uint256" },
1423
+ { name: "startAmount", type: "uint256" },
1424
+ { name: "endAmount", type: "uint256" },
1425
+ { name: "recipient", type: "address" }
1426
+ ]
1427
+ };
1428
+ function calculateFee(price, feeBps, useCeilingDivision) {
1429
+ if (feeBps === 0) return BigInt(0);
1430
+ const fee = price * BigInt(feeBps);
1431
+ if (useCeilingDivision) {
1432
+ return (fee + BigInt(9999)) / BigInt(1e4);
1433
+ }
1434
+ return fee / BigInt(1e4);
1435
+ }
1436
+ function generateSalt() {
1437
+ const bytes = new Uint8Array(32);
1438
+ if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.getRandomValues) {
1439
+ globalThis.crypto.getRandomValues(bytes);
1440
+ } else {
1441
+ for (let i = 0; i < 32; i++) {
1442
+ bytes[i] = Math.floor(Math.random() * 256);
1443
+ }
1444
+ }
1445
+ let hex = "0x";
1446
+ for (const b of bytes) {
1447
+ hex += b.toString(16).padStart(2, "0");
1448
+ }
1449
+ return BigInt(hex);
1450
+ }
1451
+ function getDefaultExpiration() {
1452
+ return Math.floor(Date.now() / 1e3) + 86400;
1453
+ }
1454
+ function buildListingOrderComponents(params, chainId, counter) {
1455
+ const feeBps = getNftFeeBps(chainId);
1456
+ const feeAmount = calculateFee(params.priceWei, feeBps, false);
1457
+ const sellerAmount = params.priceWei - feeAmount;
1458
+ const endTime = BigInt(params.expirationDate ?? getDefaultExpiration());
1459
+ const feeCollector = getFeeCollectorAddress(chainId);
1460
+ const consideration = [
1461
+ {
1462
+ itemType: 0 /* NATIVE */,
1463
+ token: ZERO_ADDRESS,
1464
+ identifierOrCriteria: BigInt(0),
1465
+ startAmount: sellerAmount,
1466
+ endAmount: sellerAmount,
1467
+ recipient: params.offerer
1468
+ }
1469
+ ];
1470
+ if (feeAmount > BigInt(0)) {
1471
+ consideration.push({
1472
+ itemType: 0 /* NATIVE */,
1473
+ token: ZERO_ADDRESS,
1474
+ identifierOrCriteria: BigInt(0),
1475
+ startAmount: feeAmount,
1476
+ endAmount: feeAmount,
1477
+ recipient: feeCollector
1478
+ });
1479
+ }
1480
+ let zone;
1481
+ let zoneHash;
1482
+ if (params.targetFulfiller) {
1483
+ zone = NET_SEAPORT_PRIVATE_ORDER_ZONE_ADDRESS;
1484
+ zoneHash = keccak256(toBytes(params.targetFulfiller));
1485
+ } else {
1486
+ zone = NET_SEAPORT_ZONE_ADDRESS;
1487
+ zoneHash = ZERO_BYTES322;
1488
+ }
1489
+ const orderParameters = {
1490
+ offerer: params.offerer,
1491
+ zone,
1492
+ offer: [
1493
+ {
1494
+ itemType: 2 /* ERC721 */,
1495
+ token: params.nftAddress,
1496
+ identifierOrCriteria: BigInt(params.tokenId),
1497
+ startAmount: BigInt(1),
1498
+ endAmount: BigInt(1)
1499
+ }
1500
+ ],
1501
+ consideration,
1502
+ orderType: 2 /* FULL_RESTRICTED */,
1503
+ startTime: BigInt(0),
1504
+ endTime,
1505
+ zoneHash,
1506
+ salt: generateSalt(),
1507
+ conduitKey: ZERO_BYTES322,
1508
+ totalOriginalConsiderationItems: BigInt(consideration.length)
1509
+ };
1510
+ return { orderParameters, counter };
1511
+ }
1512
+ function buildCollectionOfferOrderComponents(params, chainId, counter) {
1513
+ const weth = getWrappedNativeCurrency(chainId);
1514
+ if (!weth) {
1515
+ throw new Error(`No wrapped native currency configured for chain ${chainId}`);
1516
+ }
1517
+ const feeBps = getNftFeeBps(chainId);
1518
+ const feeAmount = calculateFee(params.priceWei, feeBps, false);
1519
+ const endTime = BigInt(params.expirationDate ?? getDefaultExpiration());
1520
+ const feeCollector = getFeeCollectorAddress(chainId);
1521
+ const consideration = [
1522
+ {
1523
+ itemType: 4 /* ERC721_WITH_CRITERIA */,
1524
+ token: params.nftAddress,
1525
+ identifierOrCriteria: BigInt(0),
1526
+ // Any token in the collection
1527
+ startAmount: BigInt(1),
1528
+ endAmount: BigInt(1),
1529
+ recipient: params.offerer
1530
+ }
1531
+ ];
1532
+ if (feeAmount > BigInt(0)) {
1533
+ consideration.push({
1534
+ itemType: 1 /* ERC20 */,
1535
+ token: weth.address,
1536
+ identifierOrCriteria: BigInt(0),
1537
+ startAmount: feeAmount,
1538
+ endAmount: feeAmount,
1539
+ recipient: feeCollector
1540
+ });
1541
+ }
1542
+ const orderParameters = {
1543
+ offerer: params.offerer,
1544
+ zone: NET_SEAPORT_COLLECTION_OFFER_ZONE_ADDRESS,
1545
+ offer: [
1546
+ {
1547
+ itemType: 1 /* ERC20 */,
1548
+ token: weth.address,
1549
+ identifierOrCriteria: BigInt(0),
1550
+ startAmount: params.priceWei,
1551
+ endAmount: params.priceWei
1552
+ }
1553
+ ],
1554
+ consideration,
1555
+ orderType: 2 /* FULL_RESTRICTED */,
1556
+ startTime: BigInt(0),
1557
+ endTime,
1558
+ zoneHash: ZERO_BYTES322,
1559
+ salt: generateSalt(),
1560
+ conduitKey: ZERO_BYTES322,
1561
+ totalOriginalConsiderationItems: BigInt(consideration.length)
1562
+ };
1563
+ return { orderParameters, counter };
1564
+ }
1565
+ function buildErc20OfferOrderComponents(params, chainId, counter) {
1566
+ const weth = getWrappedNativeCurrency(chainId);
1567
+ if (!weth) {
1568
+ throw new Error(`No wrapped native currency configured for chain ${chainId}`);
1569
+ }
1570
+ const feeBps = getNftFeeBps(chainId);
1571
+ const feeAmount = calculateFee(params.priceWei, feeBps, true);
1572
+ const endTime = BigInt(params.expirationDate ?? getDefaultExpiration());
1573
+ const feeCollector = getFeeCollectorAddress(chainId);
1574
+ const consideration = [
1575
+ {
1576
+ itemType: 1 /* ERC20 */,
1577
+ token: params.tokenAddress,
1578
+ identifierOrCriteria: BigInt(0),
1579
+ startAmount: params.tokenAmount,
1580
+ endAmount: params.tokenAmount,
1581
+ recipient: params.offerer
1582
+ }
1583
+ ];
1584
+ if (feeAmount > BigInt(0)) {
1585
+ consideration.push({
1586
+ itemType: 1 /* ERC20 */,
1587
+ token: weth.address,
1588
+ identifierOrCriteria: BigInt(0),
1589
+ startAmount: feeAmount,
1590
+ endAmount: feeAmount,
1591
+ recipient: feeCollector
1592
+ });
1593
+ }
1594
+ const orderParameters = {
1595
+ offerer: params.offerer,
1596
+ zone: NET_SEAPORT_COLLECTION_OFFER_ZONE_ADDRESS,
1597
+ offer: [
1598
+ {
1599
+ itemType: 1 /* ERC20 */,
1600
+ token: weth.address,
1601
+ identifierOrCriteria: BigInt(0),
1602
+ startAmount: params.priceWei,
1603
+ endAmount: params.priceWei
1604
+ }
1605
+ ],
1606
+ consideration,
1607
+ orderType: 2 /* FULL_RESTRICTED */,
1608
+ startTime: BigInt(0),
1609
+ endTime,
1610
+ zoneHash: ZERO_BYTES322,
1611
+ salt: generateSalt(),
1612
+ conduitKey: ZERO_BYTES322,
1613
+ totalOriginalConsiderationItems: BigInt(consideration.length)
1614
+ };
1615
+ return { orderParameters, counter };
1616
+ }
1617
+ function buildErc20ListingOrderComponents(params, chainId, counter) {
1618
+ const feeBps = getNftFeeBps(chainId);
1619
+ const feeAmount = calculateFee(params.priceWei, feeBps, true);
1620
+ const sellerAmount = params.priceWei - feeAmount;
1621
+ const endTime = BigInt(params.expirationDate ?? getDefaultExpiration());
1622
+ const feeCollector = getFeeCollectorAddress(chainId);
1623
+ const consideration = [
1624
+ {
1625
+ itemType: 0 /* NATIVE */,
1626
+ token: ZERO_ADDRESS,
1627
+ identifierOrCriteria: BigInt(0),
1628
+ startAmount: sellerAmount,
1629
+ endAmount: sellerAmount,
1630
+ recipient: params.offerer
1631
+ }
1632
+ ];
1633
+ if (feeAmount > BigInt(0)) {
1634
+ consideration.push({
1635
+ itemType: 0 /* NATIVE */,
1636
+ token: ZERO_ADDRESS,
1637
+ identifierOrCriteria: BigInt(0),
1638
+ startAmount: feeAmount,
1639
+ endAmount: feeAmount,
1640
+ recipient: feeCollector
1641
+ });
1642
+ }
1643
+ let zone;
1644
+ let zoneHash;
1645
+ if (params.targetFulfiller) {
1646
+ zone = NET_SEAPORT_PRIVATE_ORDER_ZONE_ADDRESS;
1647
+ zoneHash = keccak256(toBytes(params.targetFulfiller));
1648
+ } else {
1649
+ zone = NET_SEAPORT_ZONE_ADDRESS;
1650
+ zoneHash = ZERO_BYTES322;
1651
+ }
1652
+ const orderParameters = {
1653
+ offerer: params.offerer,
1654
+ zone,
1655
+ offer: [
1656
+ {
1657
+ itemType: 1 /* ERC20 */,
1658
+ token: params.tokenAddress,
1659
+ identifierOrCriteria: BigInt(0),
1660
+ startAmount: params.tokenAmount,
1661
+ endAmount: params.tokenAmount
1662
+ }
1663
+ ],
1664
+ consideration,
1665
+ orderType: 2 /* FULL_RESTRICTED */,
1666
+ startTime: BigInt(0),
1667
+ endTime,
1668
+ zoneHash,
1669
+ salt: generateSalt(),
1670
+ conduitKey: ZERO_BYTES322,
1671
+ totalOriginalConsiderationItems: BigInt(consideration.length)
1672
+ };
1673
+ return { orderParameters, counter };
1674
+ }
1675
+ function buildEIP712OrderData(orderParameters, counter, chainId, seaportAddress) {
1676
+ const message = {
1677
+ offerer: orderParameters.offerer,
1678
+ zone: orderParameters.zone,
1679
+ offer: orderParameters.offer.map((item) => ({
1680
+ itemType: item.itemType,
1681
+ token: item.token,
1682
+ identifierOrCriteria: item.identifierOrCriteria,
1683
+ startAmount: item.startAmount,
1684
+ endAmount: item.endAmount
1685
+ })),
1686
+ consideration: orderParameters.consideration.map((item) => ({
1687
+ itemType: item.itemType,
1688
+ token: item.token,
1689
+ identifierOrCriteria: item.identifierOrCriteria,
1690
+ startAmount: item.startAmount,
1691
+ endAmount: item.endAmount,
1692
+ recipient: item.recipient
1693
+ })),
1694
+ orderType: orderParameters.orderType,
1695
+ startTime: orderParameters.startTime,
1696
+ endTime: orderParameters.endTime,
1697
+ zoneHash: orderParameters.zoneHash,
1698
+ salt: orderParameters.salt,
1699
+ conduitKey: orderParameters.conduitKey,
1700
+ counter
1701
+ };
1702
+ return {
1703
+ domain: {
1704
+ name: SEAPORT_EIP712_DOMAIN_NAME,
1705
+ version: SEAPORT_EIP712_DOMAIN_VERSION,
1706
+ chainId,
1707
+ verifyingContract: seaportAddress
1708
+ },
1709
+ types: SEAPORT_ORDER_EIP712_TYPES,
1710
+ primaryType: "OrderComponents",
1711
+ message,
1712
+ orderParameters,
1713
+ counter
1714
+ };
1715
+ }
1716
+ function buildSubmitOrderTx(contractAddress, abi, orderParameters, counter, signature) {
1717
+ const submission = {
1718
+ parameters: {
1719
+ offerer: orderParameters.offerer,
1720
+ zone: orderParameters.zone,
1721
+ offer: orderParameters.offer.map((item) => ({
1722
+ itemType: item.itemType,
1723
+ token: item.token,
1724
+ identifierOrCriteria: item.identifierOrCriteria,
1725
+ startAmount: item.startAmount,
1726
+ endAmount: item.endAmount
1727
+ })),
1728
+ consideration: orderParameters.consideration.map((item) => ({
1729
+ itemType: item.itemType,
1730
+ token: item.token,
1731
+ identifierOrCriteria: item.identifierOrCriteria,
1732
+ startAmount: item.startAmount,
1733
+ endAmount: item.endAmount,
1734
+ recipient: item.recipient
1735
+ })),
1736
+ orderType: orderParameters.orderType,
1737
+ startTime: orderParameters.startTime,
1738
+ endTime: orderParameters.endTime,
1739
+ zoneHash: orderParameters.zoneHash,
1740
+ salt: orderParameters.salt,
1741
+ conduitKey: orderParameters.conduitKey,
1742
+ totalOriginalConsiderationItems: orderParameters.totalOriginalConsiderationItems
1743
+ },
1744
+ counter,
1745
+ signature
1746
+ };
1747
+ return {
1748
+ to: contractAddress,
1749
+ functionName: "submit",
1750
+ args: [submission],
1751
+ abi
1752
+ };
1753
+ }
1754
+ var ERC721_TOKEN_OWNER_RANGE_HELPER_ADDRESS = "0x00000000f4ec2016d6e856b0cb14d635949bfd3f";
1755
+ var ERC721_TOKEN_OWNER_RANGE_HELPER_ABI = [
1756
+ {
1757
+ inputs: [
1758
+ { name: "nftContract", type: "address" },
1759
+ { name: "user", type: "address" },
1760
+ { name: "startTokenId", type: "uint256" },
1761
+ { name: "endTokenId", type: "uint256" }
1762
+ ],
1763
+ name: "getOwnedTokensInRange",
1764
+ outputs: [{ name: "", type: "uint256[]" }],
1765
+ stateMutability: "view",
1766
+ type: "function"
1767
+ }
1768
+ ];
1769
+ var OWNED_TOKENS_BATCH_SIZE = 5000n;
873
1770
  var CHAIN_RPC_URLS = {
874
1771
  8453: ["https://base-mainnet.public.blastapi.io", "https://mainnet.base.org"],
875
1772
  84532: ["https://sepolia.base.org"],
@@ -1567,6 +2464,290 @@ var BazaarClient = class {
1567
2464
  abi: SEAPORT_CANCEL_ABI
1568
2465
  };
1569
2466
  }
2467
+ // ─── Fulfillment Methods ───────────────────────────────────────────
2468
+ /**
2469
+ * Prepare a fulfillment for an NFT listing (buy an NFT).
2470
+ *
2471
+ * Returns approval transactions (if the listing requires ERC20 payment) and
2472
+ * the Seaport `fulfillAdvancedOrder` transaction with the correct native currency value.
2473
+ */
2474
+ async prepareFulfillListing(listing, fulfillerAddress) {
2475
+ const submission = decodeSeaportSubmission(listing.messageData);
2476
+ const seaportAddress = getSeaportAddress(this.chainId);
2477
+ const fulfillment = buildFulfillListingTx(submission, fulfillerAddress, seaportAddress);
2478
+ return { approvals: [], fulfillment };
2479
+ }
2480
+ /**
2481
+ * Prepare a fulfillment for a collection offer (sell your NFT into an offer).
2482
+ *
2483
+ * Returns ERC721 approval transaction (if the NFT isn't approved for Seaport)
2484
+ * and the Seaport `fulfillAdvancedOrder` transaction.
2485
+ */
2486
+ async prepareFulfillCollectionOffer(offer, tokenId, fulfillerAddress) {
2487
+ const submission = decodeSeaportSubmission(offer.messageData);
2488
+ const seaportAddress = getSeaportAddress(this.chainId);
2489
+ const approvals = [];
2490
+ const nftApproval = await checkErc721Approval(
2491
+ this.client,
2492
+ offer.nftAddress,
2493
+ fulfillerAddress,
2494
+ seaportAddress
2495
+ );
2496
+ if (nftApproval) {
2497
+ approvals.push(nftApproval);
2498
+ }
2499
+ const fulfillment = buildFulfillCollectionOfferTx(
2500
+ submission,
2501
+ BigInt(tokenId),
2502
+ fulfillerAddress,
2503
+ seaportAddress
2504
+ );
2505
+ return { approvals, fulfillment };
2506
+ }
2507
+ /**
2508
+ * Prepare a fulfillment for an ERC20 offer (sell your ERC20 tokens into an offer).
2509
+ *
2510
+ * Returns ERC20 approval transaction (if the token isn't approved for Seaport)
2511
+ * and the Seaport `fulfillOrder` transaction.
2512
+ */
2513
+ async prepareFulfillErc20Offer(offer, fulfillerAddress) {
2514
+ const submission = decodeSeaportSubmission(offer.messageData);
2515
+ const seaportAddress = getSeaportAddress(this.chainId);
2516
+ const approvals = [];
2517
+ const tokenApproval = await checkErc20Approval(
2518
+ this.client,
2519
+ offer.tokenAddress,
2520
+ fulfillerAddress,
2521
+ seaportAddress,
2522
+ offer.tokenAmount
2523
+ );
2524
+ if (tokenApproval) {
2525
+ approvals.push(tokenApproval);
2526
+ }
2527
+ const fulfillment = buildFulfillErc20OfferTx(submission, seaportAddress);
2528
+ return { approvals, fulfillment };
2529
+ }
2530
+ /**
2531
+ * Prepare a fulfillment for an ERC20 listing (buy ERC20 tokens with native currency).
2532
+ *
2533
+ * Returns the Seaport `fulfillAdvancedOrder` transaction with the correct native currency value.
2534
+ * No approvals needed since the buyer pays in native currency.
2535
+ */
2536
+ async prepareFulfillErc20Listing(listing, fulfillerAddress) {
2537
+ const submission = decodeSeaportSubmission(listing.messageData);
2538
+ const seaportAddress = getSeaportAddress(this.chainId);
2539
+ const fulfillment = buildFulfillErc20ListingTx(submission, fulfillerAddress, seaportAddress);
2540
+ return { approvals: [], fulfillment };
2541
+ }
2542
+ // ─── Order Creation Methods (Step 1: Build EIP-712 data) ──────────
2543
+ /**
2544
+ * Fetch the Seaport counter for an address
2545
+ */
2546
+ async getSeaportCounter(offerer) {
2547
+ const seaportAddress = getSeaportAddress(this.chainId);
2548
+ const counter = await readContract(this.client, {
2549
+ address: seaportAddress,
2550
+ abi: SEAPORT_GET_COUNTER_ABI,
2551
+ functionName: "getCounter",
2552
+ args: [offerer]
2553
+ });
2554
+ return BigInt(counter);
2555
+ }
2556
+ /**
2557
+ * Prepare an NFT listing order for signing.
2558
+ *
2559
+ * Returns EIP-712 typed data for the caller to sign, plus any maker approvals needed
2560
+ * (ERC721 `setApprovalForAll` for Seaport).
2561
+ */
2562
+ async prepareCreateListing(params) {
2563
+ const seaportAddress = getSeaportAddress(this.chainId);
2564
+ const counter = await this.getSeaportCounter(params.offerer);
2565
+ const { orderParameters } = buildListingOrderComponents(params, this.chainId, counter);
2566
+ const eip712 = buildEIP712OrderData(orderParameters, counter, this.chainId, seaportAddress);
2567
+ const approvals = [];
2568
+ const nftApproval = await checkErc721Approval(
2569
+ this.client,
2570
+ params.nftAddress,
2571
+ params.offerer,
2572
+ seaportAddress
2573
+ );
2574
+ if (nftApproval) {
2575
+ approvals.push(nftApproval);
2576
+ }
2577
+ return { eip712, approvals };
2578
+ }
2579
+ /**
2580
+ * Prepare a collection offer order for signing.
2581
+ *
2582
+ * Returns EIP-712 typed data for the caller to sign, plus any maker approvals needed
2583
+ * (WETH `approve` for Seaport).
2584
+ */
2585
+ async prepareCreateCollectionOffer(params) {
2586
+ const seaportAddress = getSeaportAddress(this.chainId);
2587
+ const weth = getWrappedNativeCurrency(this.chainId);
2588
+ if (!weth) {
2589
+ throw new Error(`No wrapped native currency configured for chain ${this.chainId}`);
2590
+ }
2591
+ const counter = await this.getSeaportCounter(params.offerer);
2592
+ const { orderParameters } = buildCollectionOfferOrderComponents(params, this.chainId, counter);
2593
+ const eip712 = buildEIP712OrderData(orderParameters, counter, this.chainId, seaportAddress);
2594
+ const approvals = [];
2595
+ const wethApproval = await checkErc20Approval(
2596
+ this.client,
2597
+ weth.address,
2598
+ params.offerer,
2599
+ seaportAddress,
2600
+ params.priceWei
2601
+ );
2602
+ if (wethApproval) {
2603
+ approvals.push(wethApproval);
2604
+ }
2605
+ return { eip712, approvals };
2606
+ }
2607
+ /**
2608
+ * Prepare an ERC20 offer order for signing.
2609
+ *
2610
+ * Returns EIP-712 typed data for the caller to sign, plus any maker approvals needed
2611
+ * (WETH `approve` for Seaport).
2612
+ */
2613
+ async prepareCreateErc20Offer(params) {
2614
+ const seaportAddress = getSeaportAddress(this.chainId);
2615
+ const weth = getWrappedNativeCurrency(this.chainId);
2616
+ if (!weth) {
2617
+ throw new Error(`No wrapped native currency configured for chain ${this.chainId}`);
2618
+ }
2619
+ const counter = await this.getSeaportCounter(params.offerer);
2620
+ const { orderParameters } = buildErc20OfferOrderComponents(params, this.chainId, counter);
2621
+ const eip712 = buildEIP712OrderData(orderParameters, counter, this.chainId, seaportAddress);
2622
+ const approvals = [];
2623
+ const wethApproval = await checkErc20Approval(
2624
+ this.client,
2625
+ weth.address,
2626
+ params.offerer,
2627
+ seaportAddress,
2628
+ params.priceWei
2629
+ );
2630
+ if (wethApproval) {
2631
+ approvals.push(wethApproval);
2632
+ }
2633
+ return { eip712, approvals };
2634
+ }
2635
+ /**
2636
+ * Prepare an ERC20 listing order for signing.
2637
+ *
2638
+ * Returns EIP-712 typed data for the caller to sign, plus any maker approvals needed
2639
+ * (ERC20 `approve` for Seaport).
2640
+ */
2641
+ async prepareCreateErc20Listing(params) {
2642
+ const seaportAddress = getSeaportAddress(this.chainId);
2643
+ const counter = await this.getSeaportCounter(params.offerer);
2644
+ const { orderParameters } = buildErc20ListingOrderComponents(params, this.chainId, counter);
2645
+ const eip712 = buildEIP712OrderData(orderParameters, counter, this.chainId, seaportAddress);
2646
+ const approvals = [];
2647
+ const tokenApproval = await checkErc20Approval(
2648
+ this.client,
2649
+ params.tokenAddress,
2650
+ params.offerer,
2651
+ seaportAddress,
2652
+ params.tokenAmount
2653
+ );
2654
+ if (tokenApproval) {
2655
+ approvals.push(tokenApproval);
2656
+ }
2657
+ return { eip712, approvals };
2658
+ }
2659
+ // ─── Order Creation Methods (Step 2: Submit signed order) ─────────
2660
+ /**
2661
+ * Prepare a submit transaction for an NFT listing.
2662
+ *
2663
+ * Call this after the user has signed the EIP-712 data from prepareCreateListing().
2664
+ */
2665
+ prepareSubmitListing(orderParameters, counter, signature) {
2666
+ return buildSubmitOrderTx(
2667
+ getBazaarAddress(this.chainId),
2668
+ BAZAAR_V2_ABI,
2669
+ orderParameters,
2670
+ counter,
2671
+ signature
2672
+ );
2673
+ }
2674
+ /**
2675
+ * Prepare a submit transaction for a collection offer.
2676
+ *
2677
+ * Call this after the user has signed the EIP-712 data from prepareCreateCollectionOffer().
2678
+ */
2679
+ prepareSubmitCollectionOffer(orderParameters, counter, signature) {
2680
+ return buildSubmitOrderTx(
2681
+ getCollectionOffersAddress(this.chainId),
2682
+ BAZAAR_COLLECTION_OFFERS_ABI,
2683
+ orderParameters,
2684
+ counter,
2685
+ signature
2686
+ );
2687
+ }
2688
+ /**
2689
+ * Prepare a submit transaction for an ERC20 offer.
2690
+ *
2691
+ * Call this after the user has signed the EIP-712 data from prepareCreateErc20Offer().
2692
+ */
2693
+ prepareSubmitErc20Offer(orderParameters, counter, signature) {
2694
+ const erc20OffersAddress = getErc20OffersAddress(this.chainId);
2695
+ if (!erc20OffersAddress) {
2696
+ throw new Error(`ERC20 offers not available on chain ${this.chainId}`);
2697
+ }
2698
+ return buildSubmitOrderTx(
2699
+ erc20OffersAddress,
2700
+ BAZAAR_ERC20_OFFERS_ABI,
2701
+ orderParameters,
2702
+ counter,
2703
+ signature
2704
+ );
2705
+ }
2706
+ /**
2707
+ * Prepare a submit transaction for an ERC20 listing.
2708
+ *
2709
+ * Call this after the user has signed the EIP-712 data from prepareCreateErc20Listing().
2710
+ */
2711
+ prepareSubmitErc20Listing(orderParameters, counter, signature) {
2712
+ return buildSubmitOrderTx(
2713
+ getErc20BazaarAddress(this.chainId),
2714
+ BAZAAR_V2_ABI,
2715
+ orderParameters,
2716
+ counter,
2717
+ signature
2718
+ );
2719
+ }
2720
+ // ─── Owned Tokens Query ─────────────────────────────────────────────
2721
+ /**
2722
+ * Get token IDs owned by an address for an ERC721 collection.
2723
+ *
2724
+ * Uses the on-chain ERC721TokenOwnerRangeHelper contract, batching
2725
+ * large ranges into 5000-token chunks to avoid RPC limits.
2726
+ */
2727
+ async getOwnedTokens(params) {
2728
+ const { nftAddress, ownerAddress } = params;
2729
+ const startTokenId = params.startTokenId ?? 0n;
2730
+ const endTokenId = params.endTokenId ?? 10000n;
2731
+ const ownedTokens = [];
2732
+ let current = startTokenId;
2733
+ while (current < endTokenId) {
2734
+ const batchEnd = current + OWNED_TOKENS_BATCH_SIZE < endTokenId ? current + OWNED_TOKENS_BATCH_SIZE : endTokenId;
2735
+ try {
2736
+ const result = await readContract(this.client, {
2737
+ address: ERC721_TOKEN_OWNER_RANGE_HELPER_ADDRESS,
2738
+ abi: ERC721_TOKEN_OWNER_RANGE_HELPER_ABI,
2739
+ functionName: "getOwnedTokensInRange",
2740
+ args: [nftAddress, ownerAddress, current, batchEnd]
2741
+ });
2742
+ for (const tokenId of result) {
2743
+ ownedTokens.push(tokenId);
2744
+ }
2745
+ } catch {
2746
+ }
2747
+ current = batchEnd;
2748
+ }
2749
+ return ownedTokens;
2750
+ }
1570
2751
  };
1571
2752
 
1572
2753
  // src/hooks/useBazaarListings.ts