@net-protocol/bazaar 0.1.5 → 0.1.6

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
@@ -3,6 +3,7 @@ import { NetClient } from '@net-protocol/core';
3
3
  import { Seaport } from '@opensea/seaport-js';
4
4
  import { ethers } from 'ethers';
5
5
  import { readContract } from 'viem/actions';
6
+ import { STORAGE_CONTRACT } from '@net-protocol/storage';
6
7
 
7
8
  // src/client/BazaarClient.ts
8
9
 
@@ -789,8 +790,6 @@ function isErc20ListingValid(orderStatus, expirationDate, tokenAmount, sellerTok
789
790
  }
790
791
  return true;
791
792
  }
792
-
793
- // src/utils/parsing.ts
794
793
  function parseListingFromMessage(message, chainId) {
795
794
  try {
796
795
  const submission = decodeSeaportSubmission(message.data);
@@ -1012,8 +1011,85 @@ function sortErc20ListingsByPricePerToken(listings) {
1012
1011
  return 0;
1013
1012
  });
1014
1013
  }
1015
-
1016
- // src/client/BazaarClient.ts
1014
+ var ZONE_STORED_SALE_ABI = [
1015
+ { type: "uint256" },
1016
+ // timestamp
1017
+ { type: "uint256" },
1018
+ // netTotalMessageLength
1019
+ { type: "uint256" },
1020
+ // netTotalMessageForAppTopicLength
1021
+ {
1022
+ name: "zoneParameters",
1023
+ type: "tuple",
1024
+ internalType: "struct ZoneParameters",
1025
+ components: [
1026
+ { name: "orderHash", type: "bytes32", internalType: "bytes32" },
1027
+ { name: "fulfiller", type: "address", internalType: "address" },
1028
+ { name: "offerer", type: "address", internalType: "address" },
1029
+ {
1030
+ name: "offer",
1031
+ type: "tuple[]",
1032
+ internalType: "struct SpentItem[]",
1033
+ components: [
1034
+ { name: "itemType", type: "uint8", internalType: "enum ItemType" },
1035
+ { name: "token", type: "address", internalType: "address" },
1036
+ { name: "identifier", type: "uint256", internalType: "uint256" },
1037
+ { name: "amount", type: "uint256", internalType: "uint256" }
1038
+ ]
1039
+ },
1040
+ {
1041
+ name: "consideration",
1042
+ type: "tuple[]",
1043
+ internalType: "struct ReceivedItem[]",
1044
+ components: [
1045
+ { name: "itemType", type: "uint8", internalType: "enum ItemType" },
1046
+ { name: "token", type: "address", internalType: "address" },
1047
+ { name: "identifier", type: "uint256", internalType: "uint256" },
1048
+ { name: "amount", type: "uint256", internalType: "uint256" },
1049
+ { name: "recipient", type: "address", internalType: "address payable" }
1050
+ ]
1051
+ },
1052
+ { name: "extraData", type: "bytes", internalType: "bytes" },
1053
+ { name: "orderHashes", type: "bytes32[]", internalType: "bytes32[]" },
1054
+ { name: "startTime", type: "uint256", internalType: "uint256" },
1055
+ { name: "endTime", type: "uint256", internalType: "uint256" },
1056
+ { name: "zoneHash", type: "bytes32", internalType: "bytes32" }
1057
+ ]
1058
+ }
1059
+ ];
1060
+ function parseSaleFromStoredData(storedData, chainId) {
1061
+ try {
1062
+ const cleanedData = "0x" + (storedData.startsWith("0x") ? storedData.slice(2) : storedData);
1063
+ const [timestamp, , , zoneParameters] = decodeAbiParameters(
1064
+ ZONE_STORED_SALE_ABI,
1065
+ cleanedData
1066
+ );
1067
+ const offerItem = zoneParameters.offer[0];
1068
+ if (!offerItem) return null;
1069
+ const totalConsideration = zoneParameters.consideration.reduce(
1070
+ (acc, item) => acc + item.amount,
1071
+ BigInt(0)
1072
+ );
1073
+ return {
1074
+ seller: zoneParameters.offerer,
1075
+ buyer: zoneParameters.fulfiller,
1076
+ tokenAddress: offerItem.token,
1077
+ tokenId: offerItem.identifier.toString(),
1078
+ amount: offerItem.amount,
1079
+ itemType: offerItem.itemType,
1080
+ priceWei: totalConsideration,
1081
+ price: parseFloat(formatEther(totalConsideration)),
1082
+ currency: getCurrencySymbol(chainId),
1083
+ timestamp: Number(timestamp),
1084
+ orderHash: zoneParameters.orderHash
1085
+ };
1086
+ } catch {
1087
+ return null;
1088
+ }
1089
+ }
1090
+ function sortSalesByTimestamp(sales) {
1091
+ return [...sales].sort((a, b) => b.timestamp - a.timestamp);
1092
+ }
1017
1093
  var CHAIN_RPC_URLS = {
1018
1094
  8453: ["https://base-mainnet.public.blastapi.io", "https://mainnet.base.org"],
1019
1095
  84532: ["https://sepolia.base.org"],
@@ -1493,6 +1569,72 @@ var BazaarClient = class {
1493
1569
  console.log(tag, `after balance filter: ${listings.length}/${beforeBalance} (${beforeBalance - listings.length} dropped)`);
1494
1570
  return sortErc20ListingsByPricePerToken(listings);
1495
1571
  }
1572
+ /**
1573
+ * Get recent sales for a collection
1574
+ *
1575
+ * Sales are stored differently from listings: Net messages contain order hashes,
1576
+ * and the actual sale data is fetched from the bulk storage contract.
1577
+ *
1578
+ * Results are sorted by timestamp (most recent first)
1579
+ */
1580
+ async getSales(options) {
1581
+ const { nftAddress, maxMessages = 100 } = options;
1582
+ const filter = {
1583
+ appAddress: NET_SEAPORT_ZONE_ADDRESS,
1584
+ topic: nftAddress.toLowerCase()
1585
+ };
1586
+ const count = await this.netClient.getMessageCount({ filter });
1587
+ if (count === 0) {
1588
+ return [];
1589
+ }
1590
+ const startIndex = Math.max(0, count - maxMessages);
1591
+ const messages = await this.netClient.getMessages({
1592
+ filter,
1593
+ startIndex,
1594
+ endIndex: count
1595
+ });
1596
+ return this.processSalesFromMessages(messages, options);
1597
+ }
1598
+ /**
1599
+ * Process pre-fetched messages into sales.
1600
+ *
1601
+ * Each message's data field contains an order hash. The actual sale data
1602
+ * is fetched from the bulk storage contract using these order hashes.
1603
+ */
1604
+ async processSalesFromMessages(messages, options) {
1605
+ const tag = `[BazaarClient.processSales chain=${this.chainId}]`;
1606
+ if (messages.length === 0) {
1607
+ return [];
1608
+ }
1609
+ const orderHashes = messages.map((m) => m.data);
1610
+ console.log(tag, `fetching ${orderHashes.length} sale details from storage...`);
1611
+ const bulkKeys = orderHashes.map((hash) => ({
1612
+ key: hash,
1613
+ operator: NET_SEAPORT_ZONE_ADDRESS
1614
+ }));
1615
+ let storedResults;
1616
+ try {
1617
+ storedResults = await this.client.readContract({
1618
+ abi: STORAGE_CONTRACT.abi,
1619
+ address: STORAGE_CONTRACT.address,
1620
+ functionName: "bulkGet",
1621
+ args: [bulkKeys]
1622
+ });
1623
+ } catch (err) {
1624
+ console.error(tag, "bulk storage fetch failed:", err);
1625
+ return [];
1626
+ }
1627
+ const sales = [];
1628
+ for (const result of storedResults) {
1629
+ if (!result.value || result.value === "0x") continue;
1630
+ const sale = parseSaleFromStoredData(result.value, this.chainId);
1631
+ if (sale) {
1632
+ sales.push(sale);
1633
+ }
1634
+ }
1635
+ console.log(tag, `parsed ${sales.length}/${storedResults.length} sales`);
1636
+ return sales;
1637
+ }
1496
1638
  /**
1497
1639
  * Get the chain ID this client is configured for
1498
1640
  */
@@ -1608,6 +1750,6 @@ var BazaarClient = class {
1608
1750
  }
1609
1751
  };
1610
1752
 
1611
- 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 };
1753
+ 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, parseSaleFromStoredData, sortErc20ListingsByPricePerToken, sortErc20OffersByPricePerToken, sortListingsByPrice, sortOffersByPrice, sortSalesByTimestamp };
1612
1754
  //# sourceMappingURL=index.mjs.map
1613
1755
  //# sourceMappingURL=index.mjs.map