@net-protocol/bazaar 0.1.4 → 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/react.mjs CHANGED
@@ -6,6 +6,7 @@ import { NetClient } from '@net-protocol/core';
6
6
  import { Seaport } from '@opensea/seaport-js';
7
7
  import { ethers } from 'ethers';
8
8
  import { readContract } from 'viem/actions';
9
+ import { STORAGE_CONTRACT } from '@net-protocol/storage';
9
10
 
10
11
  // src/hooks/useBazaarListings.ts
11
12
 
@@ -183,6 +184,7 @@ var DEFAULT_NFT_FEE_BPS = 500;
183
184
  var BULK_SEAPORT_ORDER_STATUS_FETCHER_ADDRESS = "0x0000009112ABCE652674b4fE3eD9C765B22d11A7";
184
185
  var ERC721_OWNER_OF_HELPER_ADDRESS = "0x00000012E3eb0700925947fAF9cd1440319b4F37";
185
186
  var ERC20_BULK_BALANCE_CHECKER_ADDRESS = "0x000000B50A9f2923F2DB931391824F6D1278f712";
187
+ var NET_SEAPORT_ZONE_ADDRESS = "0x000000007F8c58fbf215bF91Bda7421A806cf3ae";
186
188
  var NET_SEAPORT_COLLECTION_OFFER_ZONE_ADDRESS = "0x000000B799ec6D7aCC1B578f62bFc324c25DFC5A";
187
189
  var NET_SEAPORT_PRIVATE_ORDER_ZONE_ADDRESS = "0x000000bC63761cbb05305632212e2f3AE2BE7a9B";
188
190
  var BAZAAR_CHAIN_CONFIGS = {
@@ -579,8 +581,6 @@ function isErc20ListingValid(orderStatus, expirationDate, tokenAmount, sellerTok
579
581
  }
580
582
  return true;
581
583
  }
582
-
583
- // src/utils/parsing.ts
584
584
  function parseListingFromMessage(message, chainId) {
585
585
  try {
586
586
  const submission = decodeSeaportSubmission(message.data);
@@ -794,8 +794,82 @@ function sortErc20ListingsByPricePerToken(listings) {
794
794
  return 0;
795
795
  });
796
796
  }
797
-
798
- // src/client/BazaarClient.ts
797
+ var ZONE_STORED_SALE_ABI = [
798
+ { type: "uint256" },
799
+ // timestamp
800
+ { type: "uint256" },
801
+ // netTotalMessageLength
802
+ { type: "uint256" },
803
+ // netTotalMessageForAppTopicLength
804
+ {
805
+ name: "zoneParameters",
806
+ type: "tuple",
807
+ internalType: "struct ZoneParameters",
808
+ components: [
809
+ { name: "orderHash", type: "bytes32", internalType: "bytes32" },
810
+ { name: "fulfiller", type: "address", internalType: "address" },
811
+ { name: "offerer", type: "address", internalType: "address" },
812
+ {
813
+ name: "offer",
814
+ type: "tuple[]",
815
+ internalType: "struct SpentItem[]",
816
+ components: [
817
+ { name: "itemType", type: "uint8", internalType: "enum ItemType" },
818
+ { name: "token", type: "address", internalType: "address" },
819
+ { name: "identifier", type: "uint256", internalType: "uint256" },
820
+ { name: "amount", type: "uint256", internalType: "uint256" }
821
+ ]
822
+ },
823
+ {
824
+ name: "consideration",
825
+ type: "tuple[]",
826
+ internalType: "struct ReceivedItem[]",
827
+ components: [
828
+ { name: "itemType", type: "uint8", internalType: "enum ItemType" },
829
+ { name: "token", type: "address", internalType: "address" },
830
+ { name: "identifier", type: "uint256", internalType: "uint256" },
831
+ { name: "amount", type: "uint256", internalType: "uint256" },
832
+ { name: "recipient", type: "address", internalType: "address payable" }
833
+ ]
834
+ },
835
+ { name: "extraData", type: "bytes", internalType: "bytes" },
836
+ { name: "orderHashes", type: "bytes32[]", internalType: "bytes32[]" },
837
+ { name: "startTime", type: "uint256", internalType: "uint256" },
838
+ { name: "endTime", type: "uint256", internalType: "uint256" },
839
+ { name: "zoneHash", type: "bytes32", internalType: "bytes32" }
840
+ ]
841
+ }
842
+ ];
843
+ function parseSaleFromStoredData(storedData, chainId) {
844
+ try {
845
+ const cleanedData = "0x" + (storedData.startsWith("0x") ? storedData.slice(2) : storedData);
846
+ const [timestamp, , , zoneParameters] = decodeAbiParameters(
847
+ ZONE_STORED_SALE_ABI,
848
+ cleanedData
849
+ );
850
+ const offerItem = zoneParameters.offer[0];
851
+ if (!offerItem) return null;
852
+ const totalConsideration = zoneParameters.consideration.reduce(
853
+ (acc, item) => acc + item.amount,
854
+ BigInt(0)
855
+ );
856
+ return {
857
+ seller: zoneParameters.offerer,
858
+ buyer: zoneParameters.fulfiller,
859
+ tokenAddress: offerItem.token,
860
+ tokenId: offerItem.identifier.toString(),
861
+ amount: offerItem.amount,
862
+ itemType: offerItem.itemType,
863
+ priceWei: totalConsideration,
864
+ price: parseFloat(formatEther(totalConsideration)),
865
+ currency: getCurrencySymbol(chainId),
866
+ timestamp: Number(timestamp),
867
+ orderHash: zoneParameters.orderHash
868
+ };
869
+ } catch {
870
+ return null;
871
+ }
872
+ }
799
873
  var CHAIN_RPC_URLS = {
800
874
  8453: ["https://base-mainnet.public.blastapi.io", "https://mainnet.base.org"],
801
875
  84532: ["https://sepolia.base.org"],
@@ -970,7 +1044,7 @@ var BazaarClient = class {
970
1044
  const count = await this.netClient.getMessageCount({
971
1045
  filter: {
972
1046
  appAddress: collectionOffersAddress,
973
- topic: nftAddress.toLowerCase()
1047
+ topic: nftAddress?.toLowerCase()
974
1048
  }
975
1049
  });
976
1050
  if (count === 0) {
@@ -980,7 +1054,7 @@ var BazaarClient = class {
980
1054
  const messages = await this.netClient.getMessages({
981
1055
  filter: {
982
1056
  appAddress: collectionOffersAddress,
983
- topic: nftAddress.toLowerCase()
1057
+ topic: nftAddress?.toLowerCase()
984
1058
  },
985
1059
  startIndex,
986
1060
  endIndex: count
@@ -1275,6 +1349,72 @@ var BazaarClient = class {
1275
1349
  console.log(tag, `after balance filter: ${listings.length}/${beforeBalance} (${beforeBalance - listings.length} dropped)`);
1276
1350
  return sortErc20ListingsByPricePerToken(listings);
1277
1351
  }
1352
+ /**
1353
+ * Get recent sales for a collection
1354
+ *
1355
+ * Sales are stored differently from listings: Net messages contain order hashes,
1356
+ * and the actual sale data is fetched from the bulk storage contract.
1357
+ *
1358
+ * Results are sorted by timestamp (most recent first)
1359
+ */
1360
+ async getSales(options) {
1361
+ const { nftAddress, maxMessages = 100 } = options;
1362
+ const filter = {
1363
+ appAddress: NET_SEAPORT_ZONE_ADDRESS,
1364
+ topic: nftAddress.toLowerCase()
1365
+ };
1366
+ const count = await this.netClient.getMessageCount({ filter });
1367
+ if (count === 0) {
1368
+ return [];
1369
+ }
1370
+ const startIndex = Math.max(0, count - maxMessages);
1371
+ const messages = await this.netClient.getMessages({
1372
+ filter,
1373
+ startIndex,
1374
+ endIndex: count
1375
+ });
1376
+ return this.processSalesFromMessages(messages, options);
1377
+ }
1378
+ /**
1379
+ * Process pre-fetched messages into sales.
1380
+ *
1381
+ * Each message's data field contains an order hash. The actual sale data
1382
+ * is fetched from the bulk storage contract using these order hashes.
1383
+ */
1384
+ async processSalesFromMessages(messages, options) {
1385
+ const tag = `[BazaarClient.processSales chain=${this.chainId}]`;
1386
+ if (messages.length === 0) {
1387
+ return [];
1388
+ }
1389
+ const orderHashes = messages.map((m) => m.data);
1390
+ console.log(tag, `fetching ${orderHashes.length} sale details from storage...`);
1391
+ const bulkKeys = orderHashes.map((hash) => ({
1392
+ key: hash,
1393
+ operator: NET_SEAPORT_ZONE_ADDRESS
1394
+ }));
1395
+ let storedResults;
1396
+ try {
1397
+ storedResults = await this.client.readContract({
1398
+ abi: STORAGE_CONTRACT.abi,
1399
+ address: STORAGE_CONTRACT.address,
1400
+ functionName: "bulkGet",
1401
+ args: [bulkKeys]
1402
+ });
1403
+ } catch (err) {
1404
+ console.error(tag, "bulk storage fetch failed:", err);
1405
+ return [];
1406
+ }
1407
+ const sales = [];
1408
+ for (const result of storedResults) {
1409
+ if (!result.value || result.value === "0x") continue;
1410
+ const sale = parseSaleFromStoredData(result.value, this.chainId);
1411
+ if (sale) {
1412
+ sales.push(sale);
1413
+ }
1414
+ }
1415
+ console.log(tag, `parsed ${sales.length}/${storedResults.length} sales`);
1416
+ return sales;
1417
+ }
1278
1418
  /**
1279
1419
  * Get the chain ID this client is configured for
1280
1420
  */
@@ -1535,7 +1675,7 @@ function useBazaarCollectionOffers({
1535
1675
  const filter = useMemo(
1536
1676
  () => ({
1537
1677
  appAddress: collectionOffersAddress,
1538
- topic: nftAddress.toLowerCase()
1678
+ topic: nftAddress?.toLowerCase()
1539
1679
  }),
1540
1680
  [collectionOffersAddress, nftAddress]
1541
1681
  );
@@ -1560,7 +1700,7 @@ function useBazaarCollectionOffers({
1560
1700
  endIndex: totalCount,
1561
1701
  enabled: enabled && isSupported && totalCount > 0
1562
1702
  });
1563
- const TAG = `[useBazaarCollectionOffers chain=${chainId} nft=${nftAddress.slice(0, 10)}]`;
1703
+ const TAG = `[useBazaarCollectionOffers chain=${chainId}${nftAddress ? ` nft=${nftAddress.slice(0, 10)}` : ""}]`;
1564
1704
  useEffect(() => {
1565
1705
  console.log(TAG, {
1566
1706
  enabled,
@@ -1861,7 +2001,116 @@ function useBazaarErc20Listings({
1861
2001
  refetch
1862
2002
  };
1863
2003
  }
2004
+ function useBazaarSales({
2005
+ chainId,
2006
+ nftAddress,
2007
+ maxMessages = 100,
2008
+ enabled = true
2009
+ }) {
2010
+ const wagmiClient = usePublicClient({ chainId });
2011
+ const [sales, setSales] = useState([]);
2012
+ const [isProcessing, setIsProcessing] = useState(false);
2013
+ const [processingError, setProcessingError] = useState();
2014
+ const [refetchTrigger, setRefetchTrigger] = useState(0);
2015
+ const isSupported = useMemo(
2016
+ () => isBazaarSupportedOnChain(chainId),
2017
+ [chainId]
2018
+ );
2019
+ const filter = useMemo(
2020
+ () => ({
2021
+ appAddress: NET_SEAPORT_ZONE_ADDRESS,
2022
+ topic: nftAddress.toLowerCase()
2023
+ }),
2024
+ [nftAddress]
2025
+ );
2026
+ const { count: totalCount, isLoading: isLoadingCount } = useNetMessageCount({
2027
+ chainId,
2028
+ filter,
2029
+ enabled: enabled && isSupported
2030
+ });
2031
+ const startIndex = useMemo(
2032
+ () => Math.max(0, totalCount - maxMessages),
2033
+ [totalCount, maxMessages]
2034
+ );
2035
+ const {
2036
+ messages,
2037
+ isLoading: isLoadingMessages,
2038
+ error: messagesError,
2039
+ refetch: refetchMessages
2040
+ } = useNetMessages({
2041
+ chainId,
2042
+ filter,
2043
+ startIndex,
2044
+ endIndex: totalCount,
2045
+ enabled: enabled && isSupported && totalCount > 0
2046
+ });
2047
+ const TAG = `[useBazaarSales chain=${chainId} nft=${nftAddress.slice(0, 10)}]`;
2048
+ useEffect(() => {
2049
+ console.log(TAG, {
2050
+ enabled,
2051
+ isSupported,
2052
+ totalCount,
2053
+ isLoadingCount,
2054
+ startIndex,
2055
+ endIndex: totalCount,
2056
+ messagesLength: messages?.length ?? 0,
2057
+ isLoadingMessages,
2058
+ messagesError: messagesError?.message
2059
+ });
2060
+ }, [enabled, isSupported, totalCount, isLoadingCount, startIndex, messages?.length, isLoadingMessages, messagesError]);
2061
+ useEffect(() => {
2062
+ if (!isSupported || !enabled) {
2063
+ setSales([]);
2064
+ return;
2065
+ }
2066
+ if (!messages || messages.length === 0) {
2067
+ setSales([]);
2068
+ return;
2069
+ }
2070
+ let cancelled = false;
2071
+ async function processSales() {
2072
+ setIsProcessing(true);
2073
+ setProcessingError(void 0);
2074
+ console.log(TAG, `processing ${messages.length} messages...`);
2075
+ try {
2076
+ const client = new BazaarClient({ chainId, publicClient: wagmiClient });
2077
+ const parsedSales = await client.processSalesFromMessages(
2078
+ messages,
2079
+ { nftAddress }
2080
+ );
2081
+ console.log(TAG, `processed \u2192 ${parsedSales.length} sales`);
2082
+ if (!cancelled) {
2083
+ setSales(parsedSales);
2084
+ }
2085
+ } catch (err) {
2086
+ console.error(TAG, "processing error:", err);
2087
+ if (!cancelled) {
2088
+ setProcessingError(err instanceof Error ? err : new Error(String(err)));
2089
+ setSales([]);
2090
+ }
2091
+ } finally {
2092
+ if (!cancelled) {
2093
+ setIsProcessing(false);
2094
+ }
2095
+ }
2096
+ }
2097
+ processSales();
2098
+ return () => {
2099
+ cancelled = true;
2100
+ };
2101
+ }, [chainId, nftAddress, messages, isSupported, enabled, refetchTrigger]);
2102
+ const refetch = () => {
2103
+ refetchMessages();
2104
+ setRefetchTrigger((t) => t + 1);
2105
+ };
2106
+ return {
2107
+ sales,
2108
+ isLoading: isLoadingCount || isLoadingMessages || isProcessing,
2109
+ error: messagesError || processingError,
2110
+ refetch
2111
+ };
2112
+ }
1864
2113
 
1865
- export { useBazaarCollectionOffers, useBazaarErc20Listings, useBazaarErc20Offers, useBazaarListings };
2114
+ export { useBazaarCollectionOffers, useBazaarErc20Listings, useBazaarErc20Offers, useBazaarListings, useBazaarSales };
1866
2115
  //# sourceMappingURL=react.mjs.map
1867
2116
  //# sourceMappingURL=react.mjs.map