@net-protocol/bazaar 0.1.2 → 0.1.3

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/index.mjs CHANGED
@@ -357,8 +357,8 @@ var DEFAULT_FEE_COLLECTOR_ADDRESS = "0x32D16C15410248bef498D7aF50D10Db1a546b9E5"
357
357
  var DEFAULT_ERC20_BAZAAR_ADDRESS = "0x00000000a2d173a4610c85c7471a25b6bc216a70";
358
358
  var DEFAULT_NFT_FEE_BPS = 500;
359
359
  var BULK_SEAPORT_ORDER_STATUS_FETCHER_ADDRESS = "0x0000009112ABCE652674b4fE3eD9C765B22d11A7";
360
- var ERC721_OWNER_OF_HELPER_ADDRESS = "0x000000aa4eFa2e5A4a6002C7F08B6e8Ec8cf1dDa";
361
- var ERC20_BULK_BALANCE_CHECKER_ADDRESS = "0x000000b50a9f2923f2db931391824f6d1278f712";
360
+ var ERC721_OWNER_OF_HELPER_ADDRESS = "0x00000012E3eb0700925947fAF9cd1440319b4F37";
361
+ var ERC20_BULK_BALANCE_CHECKER_ADDRESS = "0x000000B50A9f2923F2DB931391824F6D1278f712";
362
362
  var NET_SEAPORT_ZONE_ADDRESS = "0x000000007F8c58fbf215bF91Bda7421A806cf3ae";
363
363
  var NET_SEAPORT_COLLECTION_OFFER_ZONE_ADDRESS = "0x000000B799ec6D7aCC1B578f62bFc324c25DFC5A";
364
364
  var NET_SEAPORT_PRIVATE_ORDER_ZONE_ADDRESS = "0x000000bC63761cbb05305632212e2f3AE2BE7a9B";
@@ -649,18 +649,26 @@ async function bulkFetchOrderStatuses(client, chainId, orderHashes) {
649
649
  return [];
650
650
  }
651
651
  const seaportAddress = getSeaportAddress(chainId);
652
- const results = await readContract(client, {
653
- address: BULK_SEAPORT_ORDER_STATUS_FETCHER_ADDRESS,
654
- abi: BULK_SEAPORT_ORDER_STATUS_FETCHER_ABI,
655
- functionName: "getOrderStatuses",
656
- args: [seaportAddress, orderHashes]
657
- });
658
- return results.map((r) => ({
659
- isValidated: r.isValidated,
660
- isCancelled: r.isCancelled,
661
- totalFilled: BigInt(r.totalFilled),
662
- totalSize: BigInt(r.totalSize)
663
- }));
652
+ try {
653
+ console.log(`[bulkFetchOrderStatuses] fetching ${orderHashes.length} statuses via ${BULK_SEAPORT_ORDER_STATUS_FETCHER_ADDRESS}`);
654
+ const results = await readContract(client, {
655
+ address: BULK_SEAPORT_ORDER_STATUS_FETCHER_ADDRESS,
656
+ abi: BULK_SEAPORT_ORDER_STATUS_FETCHER_ABI,
657
+ functionName: "getOrderStatuses",
658
+ args: [seaportAddress, orderHashes]
659
+ });
660
+ const statuses = results.map((r) => ({
661
+ isValidated: r.isValidated,
662
+ isCancelled: r.isCancelled,
663
+ totalFilled: BigInt(r.totalFilled),
664
+ totalSize: BigInt(r.totalSize)
665
+ }));
666
+ console.log(`[bulkFetchOrderStatuses] success: ${statuses.length} statuses`);
667
+ return statuses;
668
+ } catch (err) {
669
+ console.error(`[bulkFetchOrderStatuses] FAILED for ${orderHashes.length} hashes:`, err);
670
+ throw err;
671
+ }
664
672
  }
665
673
  async function createOrderStatusMap(client, chainId, orderHashes) {
666
674
  const statuses = await bulkFetchOrderStatuses(client, chainId, orderHashes);
@@ -675,16 +683,21 @@ async function bulkFetchNftOwners(client, nftAddress, tokenIds) {
675
683
  return [];
676
684
  }
677
685
  try {
686
+ console.log(`[bulkFetchNftOwners] fetching ${tokenIds.length} owners for ${nftAddress.slice(0, 10)} via ${ERC721_OWNER_OF_HELPER_ADDRESS}`);
678
687
  const owners = await readContract(client, {
679
688
  address: ERC721_OWNER_OF_HELPER_ADDRESS,
680
689
  abi: ERC721_OWNER_OF_HELPER_ABI,
681
690
  functionName: "getTokenOwners",
682
691
  args: [nftAddress, tokenIds.map((id) => BigInt(id))]
683
692
  });
684
- return owners.map(
693
+ const result = owners.map(
685
694
  (owner) => owner === "0x0000000000000000000000000000000000000000" ? null : owner
686
695
  );
687
- } catch {
696
+ const validCount = result.filter((o) => o !== null).length;
697
+ console.log(`[bulkFetchNftOwners] success: ${validCount}/${tokenIds.length} have owners`);
698
+ return result;
699
+ } catch (err) {
700
+ console.error(`[bulkFetchNftOwners] FAILED for ${tokenIds.length} tokens \u2014 returning all null:`, err);
688
701
  return tokenIds.map(() => null);
689
702
  }
690
703
  }
@@ -701,14 +714,18 @@ async function bulkFetchErc20Balances(client, tokenAddress, addresses) {
701
714
  return [];
702
715
  }
703
716
  try {
717
+ console.log(`[bulkFetchErc20Balances] fetching ${addresses.length} balances for ${tokenAddress.slice(0, 10)}`);
704
718
  const balances = await readContract(client, {
705
719
  address: ERC20_BULK_BALANCE_CHECKER_ADDRESS,
706
720
  abi: ERC20_BULK_BALANCE_CHECKER_ABI,
707
721
  functionName: "getBalances",
708
722
  args: [tokenAddress, addresses]
709
723
  });
710
- return balances.map((b) => BigInt(b));
711
- } catch {
724
+ const result = balances.map((b) => BigInt(b));
725
+ console.log(`[bulkFetchErc20Balances] success: ${result.length} balances`);
726
+ return result;
727
+ } catch (err) {
728
+ console.error(`[bulkFetchErc20Balances] FAILED for ${addresses.length} addresses \u2014 returning all zero:`, err);
712
729
  return addresses.map(() => BigInt(0));
713
730
  }
714
731
  }
@@ -1015,26 +1032,30 @@ var BazaarClient = class {
1015
1032
  }
1016
1033
  this.chainId = params.chainId;
1017
1034
  this.rpcUrl = params.rpcUrl || CHAIN_RPC_URLS[params.chainId]?.[0] || "";
1018
- if (!this.rpcUrl) {
1019
- throw new Error(`No RPC URL available for chain ${params.chainId}`);
1035
+ if (params.publicClient) {
1036
+ this.client = params.publicClient;
1037
+ } else {
1038
+ if (!this.rpcUrl) {
1039
+ throw new Error(`No RPC URL available for chain ${params.chainId}`);
1040
+ }
1041
+ const config = getBazaarChainConfig(params.chainId);
1042
+ this.client = createPublicClient({
1043
+ chain: defineChain({
1044
+ id: params.chainId,
1045
+ name: `Chain ${params.chainId}`,
1046
+ nativeCurrency: {
1047
+ name: config.wrappedNativeCurrency.name.replace("Wrapped ", ""),
1048
+ symbol: config.currencySymbol.toUpperCase(),
1049
+ decimals: 18
1050
+ },
1051
+ rpcUrls: {
1052
+ default: { http: [this.rpcUrl] }
1053
+ }
1054
+ }),
1055
+ transport: http(this.rpcUrl),
1056
+ batch: { multicall: true }
1057
+ });
1020
1058
  }
1021
- const config = getBazaarChainConfig(params.chainId);
1022
- this.client = createPublicClient({
1023
- chain: defineChain({
1024
- id: params.chainId,
1025
- name: `Chain ${params.chainId}`,
1026
- nativeCurrency: {
1027
- name: config.wrappedNativeCurrency.name.replace("Wrapped ", ""),
1028
- symbol: config.currencySymbol.toUpperCase(),
1029
- decimals: 18
1030
- },
1031
- rpcUrls: {
1032
- default: { http: [this.rpcUrl] }
1033
- }
1034
- }),
1035
- transport: http(this.rpcUrl),
1036
- batch: { multicall: true }
1037
- });
1038
1059
  this.netClient = new NetClient({
1039
1060
  chainId: params.chainId,
1040
1061
  overrides: params.rpcUrl ? { rpcUrls: [params.rpcUrl] } : void 0
@@ -1076,6 +1097,17 @@ var BazaarClient = class {
1076
1097
  startIndex,
1077
1098
  endIndex
1078
1099
  });
1100
+ return this.processListingsFromMessages(messages, options);
1101
+ }
1102
+ /**
1103
+ * Process pre-fetched messages into valid NFT listings.
1104
+ *
1105
+ * Use this when messages have already been fetched (e.g. via useNetMessages)
1106
+ * to avoid redundant RPC calls.
1107
+ */
1108
+ async processListingsFromMessages(messages, options) {
1109
+ const { nftAddress, excludeMaker, includeExpired = false } = options;
1110
+ const tag = `[BazaarClient.processListings chain=${this.chainId}]`;
1079
1111
  let listings = [];
1080
1112
  for (const message of messages) {
1081
1113
  const listing = parseListingFromMessage(message, this.chainId);
@@ -1085,6 +1117,7 @@ var BazaarClient = class {
1085
1117
  }
1086
1118
  listings.push(listing);
1087
1119
  }
1120
+ console.log(tag, `parsed ${listings.length}/${messages.length} messages into listings`);
1088
1121
  if (listings.length === 0) {
1089
1122
  return [];
1090
1123
  }
@@ -1099,15 +1132,27 @@ var BazaarClient = class {
1099
1132
  const statusInfo = statusInfos[index];
1100
1133
  listing.orderStatus = getOrderStatusFromInfo(listing.orderComponents, statusInfo);
1101
1134
  });
1135
+ const statusCounts = {};
1136
+ const now = Math.floor(Date.now() / 1e3);
1137
+ let expiredCount = 0;
1138
+ for (const l of listings) {
1139
+ statusCounts[l.orderStatus] = (statusCounts[l.orderStatus] || 0) + 1;
1140
+ if (l.expirationDate <= now) expiredCount++;
1141
+ }
1142
+ console.log(tag, `order statuses:`, statusCounts, `expired: ${expiredCount}`);
1102
1143
  listings = listings.filter(
1103
- (l) => l.orderStatus === 2 /* OPEN */ && l.expirationDate > Math.floor(Date.now() / 1e3)
1144
+ (l) => l.orderStatus === 2 /* OPEN */ && l.expirationDate > now || includeExpired && l.orderStatus === 1 /* EXPIRED */
1104
1145
  );
1146
+ console.log(tag, `after status filter: ${listings.length} (OPEN${includeExpired ? " + EXPIRED" : ""})`);
1105
1147
  if (listings.length === 0) {
1106
1148
  return [];
1107
1149
  }
1108
- const tokenIds = listings.map((l) => l.tokenId);
1150
+ const openListings = listings.filter((l) => l.orderStatus === 2 /* OPEN */);
1151
+ const expiredListings = includeExpired ? listings.filter((l) => l.orderStatus === 1 /* EXPIRED */) : [];
1152
+ const tokenIds = openListings.map((l) => l.tokenId);
1109
1153
  const owners = await bulkFetchNftOwners(this.client, nftAddress, tokenIds);
1110
- listings = listings.filter((listing, index) => {
1154
+ const beforeOwnership = openListings.length;
1155
+ const validOpenListings = openListings.filter((listing, index) => {
1111
1156
  const owner = owners[index];
1112
1157
  return isListingValid(
1113
1158
  listing.orderStatus,
@@ -1116,7 +1161,16 @@ var BazaarClient = class {
1116
1161
  owner
1117
1162
  );
1118
1163
  });
1119
- return sortListingsByPrice(getBestListingPerToken(listings));
1164
+ console.log(tag, `after ownership filter: ${validOpenListings.length}/${beforeOwnership} (${beforeOwnership - validOpenListings.length} dropped)`);
1165
+ const dedupedOpen = sortListingsByPrice(getBestListingPerToken(validOpenListings));
1166
+ const activeTokenKeys = new Set(dedupedOpen.map((l) => `${l.nftAddress.toLowerCase()}-${l.tokenId}`));
1167
+ const uniqueExpired = getBestListingPerToken(
1168
+ expiredListings.filter((l) => !activeTokenKeys.has(`${l.nftAddress.toLowerCase()}-${l.tokenId}`))
1169
+ );
1170
+ const sortedExpired = sortListingsByPrice(uniqueExpired);
1171
+ const result = [...dedupedOpen, ...sortedExpired];
1172
+ console.log(tag, `final: ${result.length} listings (${dedupedOpen.length} open, ${sortedExpired.length} expired)`);
1173
+ return result;
1120
1174
  }
1121
1175
  /**
1122
1176
  * Get valid collection offers for a collection
@@ -1131,10 +1185,6 @@ var BazaarClient = class {
1131
1185
  async getCollectionOffers(options) {
1132
1186
  const { nftAddress, excludeMaker, maxMessages = 100 } = options;
1133
1187
  const collectionOffersAddress = getCollectionOffersAddress(this.chainId);
1134
- const weth = getWrappedNativeCurrency(this.chainId);
1135
- if (!weth) {
1136
- return [];
1137
- }
1138
1188
  const count = await this.netClient.getMessageCount({
1139
1189
  filter: {
1140
1190
  appAddress: collectionOffersAddress,
@@ -1153,6 +1203,21 @@ var BazaarClient = class {
1153
1203
  startIndex,
1154
1204
  endIndex: count
1155
1205
  });
1206
+ return this.processCollectionOffersFromMessages(messages, options);
1207
+ }
1208
+ /**
1209
+ * Process pre-fetched messages into valid collection offers.
1210
+ *
1211
+ * Use this when messages have already been fetched (e.g. via useNetMessages)
1212
+ * to avoid redundant RPC calls.
1213
+ */
1214
+ async processCollectionOffersFromMessages(messages, options) {
1215
+ const { nftAddress, excludeMaker } = options;
1216
+ const tag = `[BazaarClient.processCollectionOffers chain=${this.chainId}]`;
1217
+ const weth = getWrappedNativeCurrency(this.chainId);
1218
+ if (!weth) {
1219
+ return [];
1220
+ }
1156
1221
  let offers = [];
1157
1222
  for (const message of messages) {
1158
1223
  const offer = parseCollectionOfferFromMessage(message, this.chainId);
@@ -1167,6 +1232,7 @@ var BazaarClient = class {
1167
1232
  }
1168
1233
  offers.push(offer);
1169
1234
  }
1235
+ console.log(tag, `parsed ${offers.length}/${messages.length} messages into offers`);
1170
1236
  if (offers.length === 0) {
1171
1237
  return [];
1172
1238
  }
@@ -1181,9 +1247,11 @@ var BazaarClient = class {
1181
1247
  const statusInfo = statusInfos[index];
1182
1248
  offer.orderStatus = getOrderStatusFromInfo(offer.orderComponents, statusInfo);
1183
1249
  });
1250
+ const now = Math.floor(Date.now() / 1e3);
1184
1251
  offers = offers.filter(
1185
- (o) => o.orderStatus === 2 /* OPEN */ && o.expirationDate > Math.floor(Date.now() / 1e3)
1252
+ (o) => o.orderStatus === 2 /* OPEN */ && o.expirationDate > now
1186
1253
  );
1254
+ console.log(tag, `after status filter: ${offers.length} OPEN & not expired`);
1187
1255
  if (offers.length === 0) {
1188
1256
  return [];
1189
1257
  }
@@ -1193,6 +1261,7 @@ var BazaarClient = class {
1193
1261
  uniqueMakers.forEach((maker, index) => {
1194
1262
  balanceMap.set(maker.toLowerCase(), balances[index]);
1195
1263
  });
1264
+ const beforeBalance = offers.length;
1196
1265
  offers = offers.filter((offer) => {
1197
1266
  const balance = balanceMap.get(offer.maker.toLowerCase()) || BigInt(0);
1198
1267
  return isCollectionOfferValid(
@@ -1202,6 +1271,7 @@ var BazaarClient = class {
1202
1271
  balance
1203
1272
  );
1204
1273
  });
1274
+ console.log(tag, `after balance filter: ${offers.length}/${beforeBalance} (${beforeBalance - offers.length} dropped)`);
1205
1275
  return sortOffersByPrice(offers);
1206
1276
  }
1207
1277
  /**
@@ -1219,8 +1289,7 @@ var BazaarClient = class {
1219
1289
  async getErc20Offers(options) {
1220
1290
  const { tokenAddress, excludeMaker, maxMessages = 200 } = options;
1221
1291
  const erc20OffersAddress = getErc20OffersAddress(this.chainId);
1222
- const weth = getWrappedNativeCurrency(this.chainId);
1223
- if (!erc20OffersAddress || !weth) {
1292
+ if (!erc20OffersAddress) {
1224
1293
  return [];
1225
1294
  }
1226
1295
  const count = await this.netClient.getMessageCount({
@@ -1241,6 +1310,21 @@ var BazaarClient = class {
1241
1310
  startIndex,
1242
1311
  endIndex: count
1243
1312
  });
1313
+ return this.processErc20OffersFromMessages(messages, options);
1314
+ }
1315
+ /**
1316
+ * Process pre-fetched messages into valid ERC20 offers.
1317
+ *
1318
+ * Use this when messages have already been fetched (e.g. via useNetMessages)
1319
+ * to avoid redundant RPC calls.
1320
+ */
1321
+ async processErc20OffersFromMessages(messages, options) {
1322
+ const { tokenAddress, excludeMaker } = options;
1323
+ const tag = `[BazaarClient.processErc20Offers chain=${this.chainId}]`;
1324
+ const weth = getWrappedNativeCurrency(this.chainId);
1325
+ if (!weth) {
1326
+ return [];
1327
+ }
1244
1328
  let offers = [];
1245
1329
  for (const message of messages) {
1246
1330
  const offer = parseErc20OfferFromMessage(message, this.chainId);
@@ -1258,6 +1342,7 @@ var BazaarClient = class {
1258
1342
  }
1259
1343
  offers.push(offer);
1260
1344
  }
1345
+ console.log(tag, `parsed ${offers.length}/${messages.length} messages into offers`);
1261
1346
  if (offers.length === 0) {
1262
1347
  return [];
1263
1348
  }
@@ -1272,9 +1357,11 @@ var BazaarClient = class {
1272
1357
  const statusInfo = statusInfos[index];
1273
1358
  offer.orderStatus = getOrderStatusFromInfo(offer.orderComponents, statusInfo);
1274
1359
  });
1360
+ const now = Math.floor(Date.now() / 1e3);
1275
1361
  offers = offers.filter(
1276
- (o) => o.orderStatus === 2 /* OPEN */ && o.expirationDate > Math.floor(Date.now() / 1e3)
1362
+ (o) => o.orderStatus === 2 /* OPEN */ && o.expirationDate > now
1277
1363
  );
1364
+ console.log(tag, `after status filter: ${offers.length} OPEN & not expired`);
1278
1365
  if (offers.length === 0) {
1279
1366
  return [];
1280
1367
  }
@@ -1284,6 +1371,7 @@ var BazaarClient = class {
1284
1371
  uniqueMakers.forEach((maker, index) => {
1285
1372
  balanceMap.set(maker.toLowerCase(), balances[index]);
1286
1373
  });
1374
+ const beforeBalance = offers.length;
1287
1375
  offers = offers.filter((offer) => {
1288
1376
  const balance = balanceMap.get(offer.maker.toLowerCase()) || BigInt(0);
1289
1377
  return isErc20OfferValid(
@@ -1293,6 +1381,7 @@ var BazaarClient = class {
1293
1381
  balance
1294
1382
  );
1295
1383
  });
1384
+ console.log(tag, `after balance filter: ${offers.length}/${beforeBalance} (${beforeBalance - offers.length} dropped)`);
1296
1385
  return sortErc20OffersByPricePerToken(offers);
1297
1386
  }
1298
1387
  /**
@@ -1332,6 +1421,17 @@ var BazaarClient = class {
1332
1421
  startIndex,
1333
1422
  endIndex
1334
1423
  });
1424
+ return this.processErc20ListingsFromMessages(messages, options);
1425
+ }
1426
+ /**
1427
+ * Process pre-fetched messages into valid ERC20 listings.
1428
+ *
1429
+ * Use this when messages have already been fetched (e.g. via useNetMessages)
1430
+ * to avoid redundant RPC calls.
1431
+ */
1432
+ async processErc20ListingsFromMessages(messages, options) {
1433
+ const { tokenAddress, excludeMaker } = options;
1434
+ const tag = `[BazaarClient.processErc20Listings chain=${this.chainId}]`;
1335
1435
  let listings = [];
1336
1436
  for (const message of messages) {
1337
1437
  const listing = parseErc20ListingFromMessage(message, this.chainId);
@@ -1344,6 +1444,7 @@ var BazaarClient = class {
1344
1444
  }
1345
1445
  listings.push(listing);
1346
1446
  }
1447
+ console.log(tag, `parsed ${listings.length}/${messages.length} messages into listings`);
1347
1448
  if (listings.length === 0) {
1348
1449
  return [];
1349
1450
  }
@@ -1358,18 +1459,28 @@ var BazaarClient = class {
1358
1459
  const statusInfo = statusInfos[index];
1359
1460
  listing.orderStatus = getOrderStatusFromInfo(listing.orderComponents, statusInfo);
1360
1461
  });
1462
+ const statusCounts = {};
1463
+ const now = Math.floor(Date.now() / 1e3);
1464
+ let expiredCount = 0;
1465
+ for (const l of listings) {
1466
+ statusCounts[l.orderStatus] = (statusCounts[l.orderStatus] || 0) + 1;
1467
+ if (l.expirationDate <= now) expiredCount++;
1468
+ }
1469
+ console.log(tag, `order statuses:`, statusCounts, `expired: ${expiredCount}`);
1361
1470
  listings = listings.filter(
1362
- (l) => l.orderStatus === 2 /* OPEN */ && l.expirationDate > Math.floor(Date.now() / 1e3)
1471
+ (l) => l.orderStatus === 2 /* OPEN */ && l.expirationDate > now
1363
1472
  );
1473
+ console.log(tag, `after status filter: ${listings.length} OPEN & not expired`);
1364
1474
  if (listings.length === 0) {
1365
1475
  return [];
1366
1476
  }
1367
1477
  const uniqueMakers = [...new Set(listings.map((l) => l.maker))];
1368
1478
  const balances = await bulkFetchErc20Balances(this.client, tokenAddress, uniqueMakers);
1369
1479
  const balanceMap = /* @__PURE__ */ new Map();
1370
- uniqueMakers.forEach((maker2, index) => {
1371
- balanceMap.set(maker2.toLowerCase(), balances[index]);
1480
+ uniqueMakers.forEach((maker, index) => {
1481
+ balanceMap.set(maker.toLowerCase(), balances[index]);
1372
1482
  });
1483
+ const beforeBalance = listings.length;
1373
1484
  listings = listings.filter((listing) => {
1374
1485
  const balance = balanceMap.get(listing.maker.toLowerCase()) || BigInt(0);
1375
1486
  return isErc20ListingValid(
@@ -1379,6 +1490,7 @@ var BazaarClient = class {
1379
1490
  balance
1380
1491
  );
1381
1492
  });
1493
+ console.log(tag, `after balance filter: ${listings.length}/${beforeBalance} (${beforeBalance - listings.length} dropped)`);
1382
1494
  return sortErc20ListingsByPricePerToken(listings);
1383
1495
  }
1384
1496
  /**