@net-protocol/bazaar 0.1.5 → 0.1.7

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.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { PublicClient } from 'viem';
2
2
  import { NetMessage } from '@net-protocol/core';
3
- import { G as GetListingsOptions, L as Listing, a as GetCollectionOffersOptions, C as CollectionOffer, b as GetErc20OffersOptions, E as Erc20Offer, c as GetErc20ListingsOptions, d as Erc20Listing, W as WriteTransactionConfig, S as SeaportOrderComponents, e as SeaportSubmission, f as SeaportOrderParameters, g as SeaportOrderStatusInfo, h as SeaportOrderStatus } from './types-CfGfQTJL.mjs';
4
- export { j as ConsiderationItem, l as CreateCollectionOfferParams, k as CreateListingParams, I as ItemType, i as OfferItem, O as OrderType } from './types-CfGfQTJL.mjs';
3
+ import { G as GetListingsOptions, L as Listing, a as GetCollectionOffersOptions, C as CollectionOffer, b as GetErc20OffersOptions, E as Erc20Offer, c as GetErc20ListingsOptions, d as Erc20Listing, e as GetSalesOptions, S as Sale, W as WriteTransactionConfig, f as SeaportOrderComponents, g as SeaportSubmission, h as SeaportOrderParameters, i as SeaportOrderStatusInfo, j as SeaportOrderStatus } from './types-5kMf461x.mjs';
4
+ export { l as ConsiderationItem, n as CreateCollectionOfferParams, m as CreateListingParams, I as ItemType, k as OfferItem, O as OrderType } from './types-5kMf461x.mjs';
5
5
  import { Seaport } from '@opensea/seaport-js';
6
6
 
7
7
  /**
@@ -97,6 +97,22 @@ declare class BazaarClient {
97
97
  * to avoid redundant RPC calls.
98
98
  */
99
99
  processErc20ListingsFromMessages(messages: NetMessage[], options: Pick<GetErc20ListingsOptions, "tokenAddress" | "excludeMaker">): Promise<Erc20Listing[]>;
100
+ /**
101
+ * Get recent sales for a collection
102
+ *
103
+ * Sales are stored differently from listings: Net messages contain order hashes,
104
+ * and the actual sale data is fetched from the bulk storage contract.
105
+ *
106
+ * Results are sorted by timestamp (most recent first)
107
+ */
108
+ getSales(options: GetSalesOptions): Promise<Sale[]>;
109
+ /**
110
+ * Process pre-fetched messages into sales.
111
+ *
112
+ * Each message's data field contains an order hash. The actual sale data
113
+ * is fetched from the bulk storage contract using these order hashes.
114
+ */
115
+ processSalesFromMessages(messages: NetMessage[], options: Pick<GetSalesOptions, "nftAddress">): Promise<Sale[]>;
100
116
  /**
101
117
  * Get the chain ID this client is configured for
102
118
  */
@@ -995,5 +1011,16 @@ declare function parseErc20ListingFromMessage(message: NetMessage, chainId: numb
995
1011
  * Sort ERC20 listings by price per token (lowest first)
996
1012
  */
997
1013
  declare function sortErc20ListingsByPricePerToken(listings: Erc20Listing[]): Erc20Listing[];
1014
+ /**
1015
+ * Parse a sale from bulk storage data (zone-stored sale details).
1016
+ *
1017
+ * The storage contract stores the full ZoneParameters struct for each sale,
1018
+ * keyed by order hash with operator = NET_SEAPORT_ZONE_ADDRESS.
1019
+ */
1020
+ declare function parseSaleFromStoredData(storedData: string, chainId: number): Sale | null;
1021
+ /**
1022
+ * Sort sales by timestamp (most recent first)
1023
+ */
1024
+ declare function sortSalesByTimestamp(sales: Sale[]): Sale[];
998
1025
 
999
- export { BAZAAR_COLLECTION_OFFERS_ABI, BAZAAR_SUBMISSION_ABI, BAZAAR_V2_ABI, BULK_SEAPORT_ORDER_STATUS_FETCHER_ABI, BULK_SEAPORT_ORDER_STATUS_FETCHER_ADDRESS, type BazaarChainConfig, BazaarClient, CollectionOffer, ERC20_BULK_BALANCE_CHECKER_ABI, ERC20_BULK_BALANCE_CHECKER_ADDRESS, ERC721_OWNER_OF_HELPER_ABI, ERC721_OWNER_OF_HELPER_ADDRESS, Erc20Listing, Erc20Offer, GetCollectionOffersOptions, GetErc20ListingsOptions, GetErc20OffersOptions, GetListingsOptions, Listing, NET_SEAPORT_COLLECTION_OFFER_ZONE_ADDRESS, NET_SEAPORT_PRIVATE_ORDER_ZONE_ADDRESS, NET_SEAPORT_ZONE_ADDRESS, SEAPORT_CANCEL_ABI, SeaportOrderComponents, SeaportOrderParameters, SeaportOrderStatus, SeaportOrderStatusInfo, SeaportSubmission, type WrappedNativeCurrency, WriteTransactionConfig, 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 };
1026
+ export { BAZAAR_COLLECTION_OFFERS_ABI, BAZAAR_SUBMISSION_ABI, BAZAAR_V2_ABI, BULK_SEAPORT_ORDER_STATUS_FETCHER_ABI, BULK_SEAPORT_ORDER_STATUS_FETCHER_ADDRESS, type BazaarChainConfig, BazaarClient, CollectionOffer, ERC20_BULK_BALANCE_CHECKER_ABI, ERC20_BULK_BALANCE_CHECKER_ADDRESS, ERC721_OWNER_OF_HELPER_ABI, ERC721_OWNER_OF_HELPER_ADDRESS, Erc20Listing, Erc20Offer, GetCollectionOffersOptions, GetErc20ListingsOptions, GetErc20OffersOptions, GetListingsOptions, GetSalesOptions, Listing, NET_SEAPORT_COLLECTION_OFFER_ZONE_ADDRESS, NET_SEAPORT_PRIVATE_ORDER_ZONE_ADDRESS, NET_SEAPORT_ZONE_ADDRESS, SEAPORT_CANCEL_ABI, Sale, SeaportOrderComponents, SeaportOrderParameters, SeaportOrderStatus, SeaportOrderStatusInfo, SeaportSubmission, type WrappedNativeCurrency, WriteTransactionConfig, 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 };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { PublicClient } from 'viem';
2
2
  import { NetMessage } from '@net-protocol/core';
3
- import { G as GetListingsOptions, L as Listing, a as GetCollectionOffersOptions, C as CollectionOffer, b as GetErc20OffersOptions, E as Erc20Offer, c as GetErc20ListingsOptions, d as Erc20Listing, W as WriteTransactionConfig, S as SeaportOrderComponents, e as SeaportSubmission, f as SeaportOrderParameters, g as SeaportOrderStatusInfo, h as SeaportOrderStatus } from './types-CfGfQTJL.js';
4
- export { j as ConsiderationItem, l as CreateCollectionOfferParams, k as CreateListingParams, I as ItemType, i as OfferItem, O as OrderType } from './types-CfGfQTJL.js';
3
+ import { G as GetListingsOptions, L as Listing, a as GetCollectionOffersOptions, C as CollectionOffer, b as GetErc20OffersOptions, E as Erc20Offer, c as GetErc20ListingsOptions, d as Erc20Listing, e as GetSalesOptions, S as Sale, W as WriteTransactionConfig, f as SeaportOrderComponents, g as SeaportSubmission, h as SeaportOrderParameters, i as SeaportOrderStatusInfo, j as SeaportOrderStatus } from './types-5kMf461x.js';
4
+ export { l as ConsiderationItem, n as CreateCollectionOfferParams, m as CreateListingParams, I as ItemType, k as OfferItem, O as OrderType } from './types-5kMf461x.js';
5
5
  import { Seaport } from '@opensea/seaport-js';
6
6
 
7
7
  /**
@@ -97,6 +97,22 @@ declare class BazaarClient {
97
97
  * to avoid redundant RPC calls.
98
98
  */
99
99
  processErc20ListingsFromMessages(messages: NetMessage[], options: Pick<GetErc20ListingsOptions, "tokenAddress" | "excludeMaker">): Promise<Erc20Listing[]>;
100
+ /**
101
+ * Get recent sales for a collection
102
+ *
103
+ * Sales are stored differently from listings: Net messages contain order hashes,
104
+ * and the actual sale data is fetched from the bulk storage contract.
105
+ *
106
+ * Results are sorted by timestamp (most recent first)
107
+ */
108
+ getSales(options: GetSalesOptions): Promise<Sale[]>;
109
+ /**
110
+ * Process pre-fetched messages into sales.
111
+ *
112
+ * Each message's data field contains an order hash. The actual sale data
113
+ * is fetched from the bulk storage contract using these order hashes.
114
+ */
115
+ processSalesFromMessages(messages: NetMessage[], options: Pick<GetSalesOptions, "nftAddress">): Promise<Sale[]>;
100
116
  /**
101
117
  * Get the chain ID this client is configured for
102
118
  */
@@ -995,5 +1011,16 @@ declare function parseErc20ListingFromMessage(message: NetMessage, chainId: numb
995
1011
  * Sort ERC20 listings by price per token (lowest first)
996
1012
  */
997
1013
  declare function sortErc20ListingsByPricePerToken(listings: Erc20Listing[]): Erc20Listing[];
1014
+ /**
1015
+ * Parse a sale from bulk storage data (zone-stored sale details).
1016
+ *
1017
+ * The storage contract stores the full ZoneParameters struct for each sale,
1018
+ * keyed by order hash with operator = NET_SEAPORT_ZONE_ADDRESS.
1019
+ */
1020
+ declare function parseSaleFromStoredData(storedData: string, chainId: number): Sale | null;
1021
+ /**
1022
+ * Sort sales by timestamp (most recent first)
1023
+ */
1024
+ declare function sortSalesByTimestamp(sales: Sale[]): Sale[];
998
1025
 
999
- export { BAZAAR_COLLECTION_OFFERS_ABI, BAZAAR_SUBMISSION_ABI, BAZAAR_V2_ABI, BULK_SEAPORT_ORDER_STATUS_FETCHER_ABI, BULK_SEAPORT_ORDER_STATUS_FETCHER_ADDRESS, type BazaarChainConfig, BazaarClient, CollectionOffer, ERC20_BULK_BALANCE_CHECKER_ABI, ERC20_BULK_BALANCE_CHECKER_ADDRESS, ERC721_OWNER_OF_HELPER_ABI, ERC721_OWNER_OF_HELPER_ADDRESS, Erc20Listing, Erc20Offer, GetCollectionOffersOptions, GetErc20ListingsOptions, GetErc20OffersOptions, GetListingsOptions, Listing, NET_SEAPORT_COLLECTION_OFFER_ZONE_ADDRESS, NET_SEAPORT_PRIVATE_ORDER_ZONE_ADDRESS, NET_SEAPORT_ZONE_ADDRESS, SEAPORT_CANCEL_ABI, SeaportOrderComponents, SeaportOrderParameters, SeaportOrderStatus, SeaportOrderStatusInfo, SeaportSubmission, type WrappedNativeCurrency, WriteTransactionConfig, 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 };
1026
+ export { BAZAAR_COLLECTION_OFFERS_ABI, BAZAAR_SUBMISSION_ABI, BAZAAR_V2_ABI, BULK_SEAPORT_ORDER_STATUS_FETCHER_ABI, BULK_SEAPORT_ORDER_STATUS_FETCHER_ADDRESS, type BazaarChainConfig, BazaarClient, CollectionOffer, ERC20_BULK_BALANCE_CHECKER_ABI, ERC20_BULK_BALANCE_CHECKER_ADDRESS, ERC721_OWNER_OF_HELPER_ABI, ERC721_OWNER_OF_HELPER_ADDRESS, Erc20Listing, Erc20Offer, GetCollectionOffersOptions, GetErc20ListingsOptions, GetErc20OffersOptions, GetListingsOptions, GetSalesOptions, Listing, NET_SEAPORT_COLLECTION_OFFER_ZONE_ADDRESS, NET_SEAPORT_PRIVATE_ORDER_ZONE_ADDRESS, NET_SEAPORT_ZONE_ADDRESS, SEAPORT_CANCEL_ABI, Sale, SeaportOrderComponents, SeaportOrderParameters, SeaportOrderStatus, SeaportOrderStatusInfo, SeaportSubmission, type WrappedNativeCurrency, WriteTransactionConfig, 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 };
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ var core = require('@net-protocol/core');
5
5
  var seaportJs = require('@opensea/seaport-js');
6
6
  var ethers = require('ethers');
7
7
  var actions = require('viem/actions');
8
+ var storage = require('@net-protocol/storage');
8
9
 
9
10
  // src/client/BazaarClient.ts
10
11
 
@@ -791,8 +792,6 @@ function isErc20ListingValid(orderStatus, expirationDate, tokenAmount, sellerTok
791
792
  }
792
793
  return true;
793
794
  }
794
-
795
- // src/utils/parsing.ts
796
795
  function parseListingFromMessage(message, chainId) {
797
796
  try {
798
797
  const submission = decodeSeaportSubmission(message.data);
@@ -1014,8 +1013,85 @@ function sortErc20ListingsByPricePerToken(listings) {
1014
1013
  return 0;
1015
1014
  });
1016
1015
  }
1017
-
1018
- // src/client/BazaarClient.ts
1016
+ var ZONE_STORED_SALE_ABI = [
1017
+ { type: "uint256" },
1018
+ // timestamp
1019
+ { type: "uint256" },
1020
+ // netTotalMessageLength
1021
+ { type: "uint256" },
1022
+ // netTotalMessageForAppTopicLength
1023
+ {
1024
+ name: "zoneParameters",
1025
+ type: "tuple",
1026
+ internalType: "struct ZoneParameters",
1027
+ components: [
1028
+ { name: "orderHash", type: "bytes32", internalType: "bytes32" },
1029
+ { name: "fulfiller", type: "address", internalType: "address" },
1030
+ { name: "offerer", type: "address", internalType: "address" },
1031
+ {
1032
+ name: "offer",
1033
+ type: "tuple[]",
1034
+ internalType: "struct SpentItem[]",
1035
+ components: [
1036
+ { name: "itemType", type: "uint8", internalType: "enum ItemType" },
1037
+ { name: "token", type: "address", internalType: "address" },
1038
+ { name: "identifier", type: "uint256", internalType: "uint256" },
1039
+ { name: "amount", type: "uint256", internalType: "uint256" }
1040
+ ]
1041
+ },
1042
+ {
1043
+ name: "consideration",
1044
+ type: "tuple[]",
1045
+ internalType: "struct ReceivedItem[]",
1046
+ components: [
1047
+ { name: "itemType", type: "uint8", internalType: "enum ItemType" },
1048
+ { name: "token", type: "address", internalType: "address" },
1049
+ { name: "identifier", type: "uint256", internalType: "uint256" },
1050
+ { name: "amount", type: "uint256", internalType: "uint256" },
1051
+ { name: "recipient", type: "address", internalType: "address payable" }
1052
+ ]
1053
+ },
1054
+ { name: "extraData", type: "bytes", internalType: "bytes" },
1055
+ { name: "orderHashes", type: "bytes32[]", internalType: "bytes32[]" },
1056
+ { name: "startTime", type: "uint256", internalType: "uint256" },
1057
+ { name: "endTime", type: "uint256", internalType: "uint256" },
1058
+ { name: "zoneHash", type: "bytes32", internalType: "bytes32" }
1059
+ ]
1060
+ }
1061
+ ];
1062
+ function parseSaleFromStoredData(storedData, chainId) {
1063
+ try {
1064
+ const cleanedData = "0x" + (storedData.startsWith("0x") ? storedData.slice(2) : storedData);
1065
+ const [timestamp, , , zoneParameters] = viem.decodeAbiParameters(
1066
+ ZONE_STORED_SALE_ABI,
1067
+ cleanedData
1068
+ );
1069
+ const offerItem = zoneParameters.offer[0];
1070
+ if (!offerItem) return null;
1071
+ const totalConsideration = zoneParameters.consideration.reduce(
1072
+ (acc, item) => acc + item.amount,
1073
+ BigInt(0)
1074
+ );
1075
+ return {
1076
+ seller: zoneParameters.offerer,
1077
+ buyer: zoneParameters.fulfiller,
1078
+ tokenAddress: offerItem.token,
1079
+ tokenId: offerItem.identifier.toString(),
1080
+ amount: offerItem.amount,
1081
+ itemType: offerItem.itemType,
1082
+ priceWei: totalConsideration,
1083
+ price: parseFloat(viem.formatEther(totalConsideration)),
1084
+ currency: getCurrencySymbol(chainId),
1085
+ timestamp: Number(timestamp),
1086
+ orderHash: zoneParameters.orderHash
1087
+ };
1088
+ } catch {
1089
+ return null;
1090
+ }
1091
+ }
1092
+ function sortSalesByTimestamp(sales) {
1093
+ return [...sales].sort((a, b) => b.timestamp - a.timestamp);
1094
+ }
1019
1095
  var CHAIN_RPC_URLS = {
1020
1096
  8453: ["https://base-mainnet.public.blastapi.io", "https://mainnet.base.org"],
1021
1097
  84532: ["https://sepolia.base.org"],
@@ -1078,7 +1154,7 @@ var BazaarClient = class {
1078
1154
  const bazaarAddress = getBazaarAddress(this.chainId);
1079
1155
  const filter = {
1080
1156
  appAddress: bazaarAddress,
1081
- topic: nftAddress.toLowerCase(),
1157
+ topic: nftAddress?.toLowerCase(),
1082
1158
  maker
1083
1159
  };
1084
1160
  let startIndex;
@@ -1151,18 +1227,57 @@ var BazaarClient = class {
1151
1227
  }
1152
1228
  const openListings = listings.filter((l) => l.orderStatus === 2 /* OPEN */);
1153
1229
  const expiredListings = includeExpired ? listings.filter((l) => l.orderStatus === 1 /* EXPIRED */) : [];
1154
- const tokenIds = openListings.map((l) => l.tokenId);
1155
- const owners = await bulkFetchNftOwners(this.client, nftAddress, tokenIds);
1230
+ let validOpenListings;
1156
1231
  const beforeOwnership = openListings.length;
1157
- const validOpenListings = openListings.filter((listing, index) => {
1158
- const owner = owners[index];
1159
- return isListingValid(
1160
- listing.orderStatus,
1161
- listing.expirationDate,
1162
- listing.maker,
1163
- owner
1232
+ if (nftAddress) {
1233
+ const tokenIds = openListings.map((l) => l.tokenId);
1234
+ const owners = await bulkFetchNftOwners(this.client, nftAddress, tokenIds);
1235
+ validOpenListings = openListings.filter((listing, index) => {
1236
+ const owner = owners[index];
1237
+ return isListingValid(
1238
+ listing.orderStatus,
1239
+ listing.expirationDate,
1240
+ listing.maker,
1241
+ owner
1242
+ );
1243
+ });
1244
+ } else {
1245
+ const groups = /* @__PURE__ */ new Map();
1246
+ for (const listing of openListings) {
1247
+ const key = listing.nftAddress.toLowerCase();
1248
+ const group = groups.get(key);
1249
+ if (group) {
1250
+ group.push(listing);
1251
+ } else {
1252
+ groups.set(key, [listing]);
1253
+ }
1254
+ }
1255
+ const groupEntries = Array.from(groups.entries());
1256
+ const ownerResults = await Promise.all(
1257
+ groupEntries.map(
1258
+ ([addr, groupListings]) => bulkFetchNftOwners(
1259
+ this.client,
1260
+ addr,
1261
+ groupListings.map((l) => l.tokenId)
1262
+ )
1263
+ )
1164
1264
  );
1165
- });
1265
+ validOpenListings = [];
1266
+ groupEntries.forEach(([, groupListings], groupIndex) => {
1267
+ const owners = ownerResults[groupIndex];
1268
+ for (let i = 0; i < groupListings.length; i++) {
1269
+ const listing = groupListings[i];
1270
+ if (isListingValid(
1271
+ listing.orderStatus,
1272
+ listing.expirationDate,
1273
+ listing.maker,
1274
+ owners[i]
1275
+ )) {
1276
+ validOpenListings.push(listing);
1277
+ }
1278
+ }
1279
+ });
1280
+ }
1166
1281
  console.log(tag, `after ownership filter: ${validOpenListings.length}/${beforeOwnership} (${beforeOwnership - validOpenListings.length} dropped)`);
1167
1282
  const dedupedOpen = sortListingsByPrice(getBestListingPerToken(validOpenListings));
1168
1283
  const activeTokenKeys = new Set(dedupedOpen.map((l) => `${l.nftAddress.toLowerCase()}-${l.tokenId}`));
@@ -1495,6 +1610,72 @@ var BazaarClient = class {
1495
1610
  console.log(tag, `after balance filter: ${listings.length}/${beforeBalance} (${beforeBalance - listings.length} dropped)`);
1496
1611
  return sortErc20ListingsByPricePerToken(listings);
1497
1612
  }
1613
+ /**
1614
+ * Get recent sales for a collection
1615
+ *
1616
+ * Sales are stored differently from listings: Net messages contain order hashes,
1617
+ * and the actual sale data is fetched from the bulk storage contract.
1618
+ *
1619
+ * Results are sorted by timestamp (most recent first)
1620
+ */
1621
+ async getSales(options) {
1622
+ const { nftAddress, maxMessages = 100 } = options;
1623
+ const filter = {
1624
+ appAddress: NET_SEAPORT_ZONE_ADDRESS,
1625
+ topic: nftAddress.toLowerCase()
1626
+ };
1627
+ const count = await this.netClient.getMessageCount({ filter });
1628
+ if (count === 0) {
1629
+ return [];
1630
+ }
1631
+ const startIndex = Math.max(0, count - maxMessages);
1632
+ const messages = await this.netClient.getMessages({
1633
+ filter,
1634
+ startIndex,
1635
+ endIndex: count
1636
+ });
1637
+ return this.processSalesFromMessages(messages, options);
1638
+ }
1639
+ /**
1640
+ * Process pre-fetched messages into sales.
1641
+ *
1642
+ * Each message's data field contains an order hash. The actual sale data
1643
+ * is fetched from the bulk storage contract using these order hashes.
1644
+ */
1645
+ async processSalesFromMessages(messages, options) {
1646
+ const tag = `[BazaarClient.processSales chain=${this.chainId}]`;
1647
+ if (messages.length === 0) {
1648
+ return [];
1649
+ }
1650
+ const orderHashes = messages.map((m) => m.data);
1651
+ console.log(tag, `fetching ${orderHashes.length} sale details from storage...`);
1652
+ const bulkKeys = orderHashes.map((hash) => ({
1653
+ key: hash,
1654
+ operator: NET_SEAPORT_ZONE_ADDRESS
1655
+ }));
1656
+ let storedResults;
1657
+ try {
1658
+ storedResults = await this.client.readContract({
1659
+ abi: storage.STORAGE_CONTRACT.abi,
1660
+ address: storage.STORAGE_CONTRACT.address,
1661
+ functionName: "bulkGet",
1662
+ args: [bulkKeys]
1663
+ });
1664
+ } catch (err) {
1665
+ console.error(tag, "bulk storage fetch failed:", err);
1666
+ return [];
1667
+ }
1668
+ const sales = [];
1669
+ for (const result of storedResults) {
1670
+ if (!result.value || result.value === "0x") continue;
1671
+ const sale = parseSaleFromStoredData(result.value, this.chainId);
1672
+ if (sale) {
1673
+ sales.push(sale);
1674
+ }
1675
+ }
1676
+ console.log(tag, `parsed ${sales.length}/${storedResults.length} sales`);
1677
+ return sales;
1678
+ }
1498
1679
  /**
1499
1680
  * Get the chain ID this client is configured for
1500
1681
  */
@@ -1663,9 +1844,11 @@ exports.parseCollectionOfferFromMessage = parseCollectionOfferFromMessage;
1663
1844
  exports.parseErc20ListingFromMessage = parseErc20ListingFromMessage;
1664
1845
  exports.parseErc20OfferFromMessage = parseErc20OfferFromMessage;
1665
1846
  exports.parseListingFromMessage = parseListingFromMessage;
1847
+ exports.parseSaleFromStoredData = parseSaleFromStoredData;
1666
1848
  exports.sortErc20ListingsByPricePerToken = sortErc20ListingsByPricePerToken;
1667
1849
  exports.sortErc20OffersByPricePerToken = sortErc20OffersByPricePerToken;
1668
1850
  exports.sortListingsByPrice = sortListingsByPrice;
1669
1851
  exports.sortOffersByPrice = sortOffersByPrice;
1852
+ exports.sortSalesByTimestamp = sortSalesByTimestamp;
1670
1853
  //# sourceMappingURL=index.js.map
1671
1854
  //# sourceMappingURL=index.js.map