@net-protocol/bazaar 0.1.0 → 0.1.2

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
@@ -354,18 +354,21 @@ var DEFAULT_SEAPORT_ADDRESS = "0x0000000000000068F116a894984e2DB1123eB395";
354
354
  var DEFAULT_BAZAAR_ADDRESS = "0x00000000E3dA5fC031282A39759bDDA78ae7fAE5";
355
355
  var DEFAULT_COLLECTION_OFFERS_ADDRESS = "0x0000000D43423E0A12CecB307a74591999b32B32";
356
356
  var DEFAULT_FEE_COLLECTOR_ADDRESS = "0x32D16C15410248bef498D7aF50D10Db1a546b9E5";
357
+ var DEFAULT_ERC20_BAZAAR_ADDRESS = "0x00000000a2d173a4610c85c7471a25b6bc216a70";
357
358
  var DEFAULT_NFT_FEE_BPS = 500;
358
359
  var BULK_SEAPORT_ORDER_STATUS_FETCHER_ADDRESS = "0x0000009112ABCE652674b4fE3eD9C765B22d11A7";
359
360
  var ERC721_OWNER_OF_HELPER_ADDRESS = "0x000000aa4eFa2e5A4a6002C7F08B6e8Ec8cf1dDa";
360
361
  var ERC20_BULK_BALANCE_CHECKER_ADDRESS = "0x000000b50a9f2923f2db931391824f6d1278f712";
361
362
  var NET_SEAPORT_ZONE_ADDRESS = "0x000000007F8c58fbf215bF91Bda7421A806cf3ae";
362
363
  var NET_SEAPORT_COLLECTION_OFFER_ZONE_ADDRESS = "0x000000B799ec6D7aCC1B578f62bFc324c25DFC5A";
364
+ var NET_SEAPORT_PRIVATE_ORDER_ZONE_ADDRESS = "0x000000bC63761cbb05305632212e2f3AE2BE7a9B";
363
365
  var BAZAAR_CHAIN_CONFIGS = {
364
366
  // Base Mainnet
365
367
  8453: {
366
368
  bazaarAddress: "0x000000058f3ade587388daf827174d0e6fc97595",
367
369
  collectionOffersAddress: "0x0000000f9c45efcff0f78d8b54aa6a40092d66dc",
368
370
  erc20OffersAddress: "0x0000000e23a89aa06f317306aa1ae231d3503082",
371
+ erc20BazaarAddress: "0x00000006557e3629e2fc50bbad0c002b27cac492",
369
372
  seaportAddress: DEFAULT_SEAPORT_ADDRESS,
370
373
  feeCollectorAddress: "0x66547ff4f7206e291F7BC157b54C026Fc6660961",
371
374
  nftFeeBps: 0,
@@ -455,6 +458,7 @@ var BAZAAR_CHAIN_CONFIGS = {
455
458
  bazaarAddress: "0x000000058f3ade587388daf827174d0e6fc97595",
456
459
  collectionOffersAddress: "0x0000000f9c45efcff0f78d8b54aa6a40092d66dc",
457
460
  erc20OffersAddress: "0x0000000e23a89aa06f317306aa1ae231d3503082",
461
+ erc20BazaarAddress: "0x00000006557e3629e2fc50bbad0c002b27cac492",
458
462
  seaportAddress: DEFAULT_SEAPORT_ADDRESS,
459
463
  feeCollectorAddress: "0x66547ff4f7206e291F7BC157b54C026Fc6660961",
460
464
  nftFeeBps: 0,
@@ -531,6 +535,9 @@ function getHighEthAddress(chainId) {
531
535
  function getErc20OffersAddress(chainId) {
532
536
  return BAZAAR_CHAIN_CONFIGS[chainId]?.erc20OffersAddress;
533
537
  }
538
+ function getErc20BazaarAddress(chainId) {
539
+ return BAZAAR_CHAIN_CONFIGS[chainId]?.erc20BazaarAddress ?? DEFAULT_ERC20_BAZAAR_ADDRESS;
540
+ }
534
541
  function decodeSeaportSubmission(messageData) {
535
542
  const [decoded] = decodeAbiParameters(BAZAAR_SUBMISSION_ABI, messageData);
536
543
  return {
@@ -752,6 +759,19 @@ function isErc20OfferValid(orderStatus, expirationDate, priceWei, buyerWethBalan
752
759
  }
753
760
  return true;
754
761
  }
762
+ function isErc20ListingValid(orderStatus, expirationDate, tokenAmount, sellerTokenBalance) {
763
+ if (orderStatus !== 2 /* OPEN */) {
764
+ return false;
765
+ }
766
+ const now = Math.floor(Date.now() / 1e3);
767
+ if (expirationDate <= now) {
768
+ return false;
769
+ }
770
+ if (sellerTokenBalance < tokenAmount) {
771
+ return false;
772
+ }
773
+ return true;
774
+ }
755
775
 
756
776
  // src/utils/parsing.ts
757
777
  function parseListingFromMessage(message, chainId) {
@@ -767,6 +787,7 @@ function parseListingFromMessage(message, chainId) {
767
787
  }
768
788
  const priceWei = getTotalConsiderationAmount(parameters);
769
789
  const tokenId = offerItem.identifierOrCriteria.toString();
790
+ const targetFulfiller = parameters.zone.toLowerCase() === NET_SEAPORT_PRIVATE_ORDER_ZONE_ADDRESS.toLowerCase() && parameters.zoneHash !== "0x0000000000000000000000000000000000000000000000000000000000000000" ? parameters.zoneHash : void 0;
770
791
  return {
771
792
  maker: parameters.offerer,
772
793
  nftAddress: offerItem.token,
@@ -783,7 +804,8 @@ function parseListingFromMessage(message, chainId) {
783
804
  orderComponents: {
784
805
  ...parameters,
785
806
  counter: submission.counter
786
- }
807
+ },
808
+ targetFulfiller
787
809
  };
788
810
  } catch {
789
811
  return null;
@@ -921,6 +943,58 @@ function sortErc20OffersByPricePerToken(offers) {
921
943
  return 0;
922
944
  });
923
945
  }
946
+ function parseErc20ListingFromMessage(message, chainId) {
947
+ try {
948
+ const submission = decodeSeaportSubmission(message.data);
949
+ const { parameters } = submission;
950
+ if (parameters.zone.toLowerCase() === NET_SEAPORT_COLLECTION_OFFER_ZONE_ADDRESS.toLowerCase()) {
951
+ return null;
952
+ }
953
+ const offerItem = parameters.offer[0];
954
+ if (!offerItem || offerItem.itemType !== 1 /* ERC20 */) {
955
+ return null;
956
+ }
957
+ const tokenAmount = offerItem.startAmount;
958
+ if (tokenAmount === BigInt(0)) {
959
+ return null;
960
+ }
961
+ const priceWei = getTotalConsiderationAmount(parameters);
962
+ if (priceWei === BigInt(0)) {
963
+ return null;
964
+ }
965
+ const pricePerTokenWei = priceWei / tokenAmount;
966
+ return {
967
+ maker: parameters.offerer,
968
+ tokenAddress: offerItem.token,
969
+ tokenAmount,
970
+ priceWei,
971
+ pricePerTokenWei,
972
+ price: formatPrice(priceWei),
973
+ pricePerToken: formatPrice(pricePerTokenWei),
974
+ currency: getCurrencySymbol(chainId),
975
+ expirationDate: Number(parameters.endTime),
976
+ orderHash: "0x",
977
+ // Will be computed later
978
+ orderStatus: 2 /* OPEN */,
979
+ // Will be validated later
980
+ messageData: message.data,
981
+ orderComponents: {
982
+ ...parameters,
983
+ counter: submission.counter
984
+ }
985
+ };
986
+ } catch {
987
+ return null;
988
+ }
989
+ }
990
+ function sortErc20ListingsByPricePerToken(listings) {
991
+ return [...listings].sort((a, b) => {
992
+ const diff = a.pricePerTokenWei - b.pricePerTokenWei;
993
+ if (diff < BigInt(0)) return -1;
994
+ if (diff > BigInt(0)) return 1;
995
+ return 0;
996
+ });
997
+ }
924
998
 
925
999
  // src/client/BazaarClient.ts
926
1000
  var CHAIN_RPC_URLS = {
@@ -977,25 +1051,30 @@ var BazaarClient = class {
977
1051
  * Results are deduplicated (one per token) and sorted by price (lowest first)
978
1052
  */
979
1053
  async getListings(options) {
980
- const { nftAddress, excludeMaker, maxMessages = 200 } = options;
1054
+ const { nftAddress, excludeMaker, maker, maxMessages = 200 } = options;
981
1055
  const bazaarAddress = getBazaarAddress(this.chainId);
982
- const count = await this.netClient.getMessageCount({
983
- filter: {
984
- appAddress: bazaarAddress,
985
- topic: nftAddress.toLowerCase()
1056
+ const filter = {
1057
+ appAddress: bazaarAddress,
1058
+ topic: nftAddress.toLowerCase(),
1059
+ maker
1060
+ };
1061
+ let startIndex;
1062
+ let endIndex;
1063
+ if (options.startIndex != null && options.endIndex != null) {
1064
+ startIndex = options.startIndex;
1065
+ endIndex = options.endIndex;
1066
+ } else {
1067
+ const count = await this.netClient.getMessageCount({ filter });
1068
+ if (count === 0) {
1069
+ return [];
986
1070
  }
987
- });
988
- if (count === 0) {
989
- return [];
1071
+ startIndex = Math.max(0, count - maxMessages);
1072
+ endIndex = count;
990
1073
  }
991
- const startIndex = Math.max(0, count - maxMessages);
992
1074
  const messages = await this.netClient.getMessages({
993
- filter: {
994
- appAddress: bazaarAddress,
995
- topic: nftAddress.toLowerCase()
996
- },
1075
+ filter,
997
1076
  startIndex,
998
- endIndex: count
1077
+ endIndex
999
1078
  });
1000
1079
  let listings = [];
1001
1080
  for (const message of messages) {
@@ -1216,6 +1295,92 @@ var BazaarClient = class {
1216
1295
  });
1217
1296
  return sortErc20OffersByPricePerToken(offers);
1218
1297
  }
1298
+ /**
1299
+ * Get valid ERC20 listings for a token
1300
+ *
1301
+ * Returns listings that are:
1302
+ * - OPEN status (not filled, cancelled, or expired)
1303
+ * - Not expired
1304
+ * - Seller has sufficient ERC20 token balance
1305
+ *
1306
+ * Results are sorted by price per token (lowest first). No deduplication —
1307
+ * all valid listings are returned (grouped by maker in the UI).
1308
+ */
1309
+ async getErc20Listings(options) {
1310
+ const { tokenAddress, excludeMaker, maker, maxMessages = 200 } = options;
1311
+ const erc20BazaarAddress = getErc20BazaarAddress(this.chainId);
1312
+ const filter = {
1313
+ appAddress: erc20BazaarAddress,
1314
+ topic: tokenAddress.toLowerCase(),
1315
+ maker
1316
+ };
1317
+ let startIndex;
1318
+ let endIndex;
1319
+ if (options.startIndex != null && options.endIndex != null) {
1320
+ startIndex = options.startIndex;
1321
+ endIndex = options.endIndex;
1322
+ } else {
1323
+ const count = await this.netClient.getMessageCount({ filter });
1324
+ if (count === 0) {
1325
+ return [];
1326
+ }
1327
+ startIndex = Math.max(0, count - maxMessages);
1328
+ endIndex = count;
1329
+ }
1330
+ const messages = await this.netClient.getMessages({
1331
+ filter,
1332
+ startIndex,
1333
+ endIndex
1334
+ });
1335
+ let listings = [];
1336
+ for (const message of messages) {
1337
+ const listing = parseErc20ListingFromMessage(message, this.chainId);
1338
+ if (!listing) continue;
1339
+ if (listing.tokenAddress.toLowerCase() !== tokenAddress.toLowerCase()) {
1340
+ continue;
1341
+ }
1342
+ if (excludeMaker && listing.maker.toLowerCase() === excludeMaker.toLowerCase()) {
1343
+ continue;
1344
+ }
1345
+ listings.push(listing);
1346
+ }
1347
+ if (listings.length === 0) {
1348
+ return [];
1349
+ }
1350
+ const seaport = createSeaportInstance(this.chainId, this.rpcUrl);
1351
+ for (const listing of listings) {
1352
+ const order = getSeaportOrderFromMessageData(listing.messageData);
1353
+ listing.orderHash = computeOrderHash(seaport, order.parameters, order.counter);
1354
+ }
1355
+ const orderHashes = listings.map((l) => l.orderHash);
1356
+ const statusInfos = await bulkFetchOrderStatuses(this.client, this.chainId, orderHashes);
1357
+ listings.forEach((listing, index) => {
1358
+ const statusInfo = statusInfos[index];
1359
+ listing.orderStatus = getOrderStatusFromInfo(listing.orderComponents, statusInfo);
1360
+ });
1361
+ listings = listings.filter(
1362
+ (l) => l.orderStatus === 2 /* OPEN */ && l.expirationDate > Math.floor(Date.now() / 1e3)
1363
+ );
1364
+ if (listings.length === 0) {
1365
+ return [];
1366
+ }
1367
+ const uniqueMakers = [...new Set(listings.map((l) => l.maker))];
1368
+ const balances = await bulkFetchErc20Balances(this.client, tokenAddress, uniqueMakers);
1369
+ const balanceMap = /* @__PURE__ */ new Map();
1370
+ uniqueMakers.forEach((maker2, index) => {
1371
+ balanceMap.set(maker2.toLowerCase(), balances[index]);
1372
+ });
1373
+ listings = listings.filter((listing) => {
1374
+ const balance = balanceMap.get(listing.maker.toLowerCase()) || BigInt(0);
1375
+ return isErc20ListingValid(
1376
+ listing.orderStatus,
1377
+ listing.expirationDate,
1378
+ listing.tokenAmount,
1379
+ balance
1380
+ );
1381
+ });
1382
+ return sortErc20ListingsByPricePerToken(listings);
1383
+ }
1219
1384
  /**
1220
1385
  * Get the chain ID this client is configured for
1221
1386
  */
@@ -1241,6 +1406,12 @@ var BazaarClient = class {
1241
1406
  getErc20OffersAddress() {
1242
1407
  return getErc20OffersAddress(this.chainId);
1243
1408
  }
1409
+ /**
1410
+ * Get the ERC20 bazaar (listings) contract address for this chain
1411
+ */
1412
+ getErc20BazaarAddress() {
1413
+ return getErc20BazaarAddress(this.chainId);
1414
+ }
1244
1415
  /**
1245
1416
  * Get the Seaport contract address for this chain
1246
1417
  */
@@ -1271,6 +1442,18 @@ var BazaarClient = class {
1271
1442
  }
1272
1443
  return this.prepareCancelOrder(offer.orderComponents);
1273
1444
  }
1445
+ /**
1446
+ * Prepare a transaction to cancel an ERC20 listing
1447
+ *
1448
+ * The listing must have been created by the caller.
1449
+ * Use the orderComponents from the Erc20Listing object returned by getErc20Listings().
1450
+ */
1451
+ prepareCancelErc20Listing(listing) {
1452
+ if (!listing.orderComponents) {
1453
+ throw new Error("Listing does not have order components");
1454
+ }
1455
+ return this.prepareCancelOrder(listing.orderComponents);
1456
+ }
1274
1457
  /**
1275
1458
  * Prepare a transaction to cancel a Seaport order
1276
1459
  *
@@ -1313,6 +1496,6 @@ var BazaarClient = class {
1313
1496
  }
1314
1497
  };
1315
1498
 
1316
- export { BAZAAR_COLLECTION_OFFERS_ABI, BAZAAR_SUBMISSION_ABI, BAZAAR_V2_ABI, BULK_SEAPORT_ORDER_STATUS_FETCHER_ABI, BULK_SEAPORT_ORDER_STATUS_FETCHER_ADDRESS, BazaarClient, ERC20_BULK_BALANCE_CHECKER_ABI, ERC20_BULK_BALANCE_CHECKER_ADDRESS, ERC721_OWNER_OF_HELPER_ABI, ERC721_OWNER_OF_HELPER_ADDRESS, ItemType, NET_SEAPORT_COLLECTION_OFFER_ZONE_ADDRESS, NET_SEAPORT_ZONE_ADDRESS, OrderType, SEAPORT_CANCEL_ABI, SeaportOrderStatus, bulkFetchErc20Balances, bulkFetchNftOwners, bulkFetchOrderStatuses, computeOrderHash, createBalanceMap, createOrderStatusMap, createOwnershipMap, createSeaportInstance, decodeSeaportSubmission, formatPrice, getBazaarAddress, getBazaarChainConfig, getBazaarSupportedChainIds, getBestCollectionOffer, getBestListingPerToken, getCollectionOffersAddress, getCurrencySymbol, getErc20OffersAddress, getFeeCollectorAddress, getHighEthAddress, getNftFeeBps, getOrderStatusFromInfo, getSeaportAddress, getSeaportOrderFromMessageData, getTotalConsiderationAmount, getWrappedNativeCurrency, isBazaarSupportedOnChain, isCollectionOfferValid, isErc20OfferValid, isListingValid, parseCollectionOfferFromMessage, parseErc20OfferFromMessage, parseListingFromMessage, sortErc20OffersByPricePerToken, sortListingsByPrice, sortOffersByPrice };
1499
+ export { BAZAAR_COLLECTION_OFFERS_ABI, BAZAAR_SUBMISSION_ABI, BAZAAR_V2_ABI, BULK_SEAPORT_ORDER_STATUS_FETCHER_ABI, BULK_SEAPORT_ORDER_STATUS_FETCHER_ADDRESS, BazaarClient, ERC20_BULK_BALANCE_CHECKER_ABI, ERC20_BULK_BALANCE_CHECKER_ADDRESS, ERC721_OWNER_OF_HELPER_ABI, ERC721_OWNER_OF_HELPER_ADDRESS, ItemType, NET_SEAPORT_COLLECTION_OFFER_ZONE_ADDRESS, NET_SEAPORT_PRIVATE_ORDER_ZONE_ADDRESS, NET_SEAPORT_ZONE_ADDRESS, OrderType, SEAPORT_CANCEL_ABI, SeaportOrderStatus, bulkFetchErc20Balances, bulkFetchNftOwners, bulkFetchOrderStatuses, computeOrderHash, createBalanceMap, createOrderStatusMap, createOwnershipMap, createSeaportInstance, decodeSeaportSubmission, formatPrice, getBazaarAddress, getBazaarChainConfig, getBazaarSupportedChainIds, getBestCollectionOffer, getBestListingPerToken, getCollectionOffersAddress, getCurrencySymbol, getErc20BazaarAddress, getErc20OffersAddress, getFeeCollectorAddress, getHighEthAddress, getNftFeeBps, getOrderStatusFromInfo, getSeaportAddress, getSeaportOrderFromMessageData, getTotalConsiderationAmount, getWrappedNativeCurrency, isBazaarSupportedOnChain, isCollectionOfferValid, isErc20ListingValid, isErc20OfferValid, isListingValid, parseCollectionOfferFromMessage, parseErc20ListingFromMessage, parseErc20OfferFromMessage, parseListingFromMessage, sortErc20ListingsByPricePerToken, sortErc20OffersByPricePerToken, sortListingsByPrice, sortOffersByPrice };
1317
1500
  //# sourceMappingURL=index.mjs.map
1318
1501
  //# sourceMappingURL=index.mjs.map