@net-protocol/bazaar 0.1.0 → 0.1.1

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.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { L as Listing, C as CollectionOffer, E as Erc20Offer } from './types-CY-6M9Ta.mjs';
1
+ import { L as Listing, C as CollectionOffer, E as Erc20Offer, d as Erc20Listing } from './types--4_RIuQ5.mjs';
2
2
 
3
3
  /**
4
4
  * React hook for fetching NFT listings from Bazaar
@@ -175,4 +175,65 @@ interface UseBazaarErc20OffersResult {
175
175
  */
176
176
  declare function useBazaarErc20Offers({ chainId, tokenAddress, excludeMaker, maxMessages, enabled, }: UseBazaarErc20OffersOptions): UseBazaarErc20OffersResult;
177
177
 
178
- export { type UseBazaarCollectionOffersOptions, type UseBazaarCollectionOffersResult, type UseBazaarErc20OffersOptions, type UseBazaarErc20OffersResult, type UseBazaarListingsOptions, type UseBazaarListingsResult, useBazaarCollectionOffers, useBazaarErc20Offers, useBazaarListings };
178
+ /**
179
+ * React hook for fetching ERC20 listings from Bazaar
180
+ */
181
+
182
+ interface UseBazaarErc20ListingsOptions {
183
+ /** Chain ID to query */
184
+ chainId: number;
185
+ /** ERC20 token address */
186
+ tokenAddress: `0x${string}`;
187
+ /** Exclude listings from this address */
188
+ excludeMaker?: `0x${string}`;
189
+ /** Maximum number of messages to fetch (default: 200) */
190
+ maxMessages?: number;
191
+ /** Whether the query is enabled (default: true) */
192
+ enabled?: boolean;
193
+ }
194
+ interface UseBazaarErc20ListingsResult {
195
+ /** Valid ERC20 listings (sorted by price per token, lowest first) */
196
+ listings: Erc20Listing[];
197
+ /** Whether the data is loading */
198
+ isLoading: boolean;
199
+ /** Error if any */
200
+ error: Error | undefined;
201
+ /** Refetch function */
202
+ refetch: () => void;
203
+ }
204
+ /**
205
+ * React hook for fetching valid ERC20 listings from Bazaar
206
+ *
207
+ * ERC20 listings are available on all supported chains.
208
+ *
209
+ * Returns listings that are:
210
+ * - OPEN status (not filled, cancelled, or expired)
211
+ * - Not expired
212
+ * - Seller has sufficient ERC20 token balance
213
+ *
214
+ * Results are sorted by price per token (lowest first)
215
+ *
216
+ * @example
217
+ * ```tsx
218
+ * const { listings, isLoading, error } = useBazaarErc20Listings({
219
+ * chainId: 8453,
220
+ * tokenAddress: "0x...",
221
+ * });
222
+ *
223
+ * if (isLoading) return <div>Loading...</div>;
224
+ * if (error) return <div>Error: {error.message}</div>;
225
+ *
226
+ * const bestListing = listings[0];
227
+ * if (bestListing) {
228
+ * return (
229
+ * <div>
230
+ * Best listing: {bestListing.pricePerToken} {bestListing.currency} per token
231
+ * (total: {bestListing.price} {bestListing.currency} for {bestListing.tokenAmount.toString()} tokens)
232
+ * </div>
233
+ * );
234
+ * }
235
+ * ```
236
+ */
237
+ declare function useBazaarErc20Listings({ chainId, tokenAddress, excludeMaker, maxMessages, enabled, }: UseBazaarErc20ListingsOptions): UseBazaarErc20ListingsResult;
238
+
239
+ export { type UseBazaarCollectionOffersOptions, type UseBazaarCollectionOffersResult, type UseBazaarErc20ListingsOptions, type UseBazaarErc20ListingsResult, type UseBazaarErc20OffersOptions, type UseBazaarErc20OffersResult, type UseBazaarListingsOptions, type UseBazaarListingsResult, useBazaarCollectionOffers, useBazaarErc20Listings, useBazaarErc20Offers, useBazaarListings };
package/dist/react.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { L as Listing, C as CollectionOffer, E as Erc20Offer } from './types-CY-6M9Ta.js';
1
+ import { L as Listing, C as CollectionOffer, E as Erc20Offer, d as Erc20Listing } from './types--4_RIuQ5.js';
2
2
 
3
3
  /**
4
4
  * React hook for fetching NFT listings from Bazaar
@@ -175,4 +175,65 @@ interface UseBazaarErc20OffersResult {
175
175
  */
176
176
  declare function useBazaarErc20Offers({ chainId, tokenAddress, excludeMaker, maxMessages, enabled, }: UseBazaarErc20OffersOptions): UseBazaarErc20OffersResult;
177
177
 
178
- export { type UseBazaarCollectionOffersOptions, type UseBazaarCollectionOffersResult, type UseBazaarErc20OffersOptions, type UseBazaarErc20OffersResult, type UseBazaarListingsOptions, type UseBazaarListingsResult, useBazaarCollectionOffers, useBazaarErc20Offers, useBazaarListings };
178
+ /**
179
+ * React hook for fetching ERC20 listings from Bazaar
180
+ */
181
+
182
+ interface UseBazaarErc20ListingsOptions {
183
+ /** Chain ID to query */
184
+ chainId: number;
185
+ /** ERC20 token address */
186
+ tokenAddress: `0x${string}`;
187
+ /** Exclude listings from this address */
188
+ excludeMaker?: `0x${string}`;
189
+ /** Maximum number of messages to fetch (default: 200) */
190
+ maxMessages?: number;
191
+ /** Whether the query is enabled (default: true) */
192
+ enabled?: boolean;
193
+ }
194
+ interface UseBazaarErc20ListingsResult {
195
+ /** Valid ERC20 listings (sorted by price per token, lowest first) */
196
+ listings: Erc20Listing[];
197
+ /** Whether the data is loading */
198
+ isLoading: boolean;
199
+ /** Error if any */
200
+ error: Error | undefined;
201
+ /** Refetch function */
202
+ refetch: () => void;
203
+ }
204
+ /**
205
+ * React hook for fetching valid ERC20 listings from Bazaar
206
+ *
207
+ * ERC20 listings are available on all supported chains.
208
+ *
209
+ * Returns listings that are:
210
+ * - OPEN status (not filled, cancelled, or expired)
211
+ * - Not expired
212
+ * - Seller has sufficient ERC20 token balance
213
+ *
214
+ * Results are sorted by price per token (lowest first)
215
+ *
216
+ * @example
217
+ * ```tsx
218
+ * const { listings, isLoading, error } = useBazaarErc20Listings({
219
+ * chainId: 8453,
220
+ * tokenAddress: "0x...",
221
+ * });
222
+ *
223
+ * if (isLoading) return <div>Loading...</div>;
224
+ * if (error) return <div>Error: {error.message}</div>;
225
+ *
226
+ * const bestListing = listings[0];
227
+ * if (bestListing) {
228
+ * return (
229
+ * <div>
230
+ * Best listing: {bestListing.pricePerToken} {bestListing.currency} per token
231
+ * (total: {bestListing.price} {bestListing.currency} for {bestListing.tokenAmount.toString()} tokens)
232
+ * </div>
233
+ * );
234
+ * }
235
+ * ```
236
+ */
237
+ declare function useBazaarErc20Listings({ chainId, tokenAddress, excludeMaker, maxMessages, enabled, }: UseBazaarErc20ListingsOptions): UseBazaarErc20ListingsResult;
238
+
239
+ export { type UseBazaarCollectionOffersOptions, type UseBazaarCollectionOffersResult, type UseBazaarErc20ListingsOptions, type UseBazaarErc20ListingsResult, type UseBazaarErc20OffersOptions, type UseBazaarErc20OffersResult, type UseBazaarListingsOptions, type UseBazaarListingsResult, useBazaarCollectionOffers, useBazaarErc20Listings, useBazaarErc20Offers, useBazaarListings };
package/dist/react.js CHANGED
@@ -179,6 +179,7 @@ var DEFAULT_SEAPORT_ADDRESS = "0x0000000000000068F116a894984e2DB1123eB395";
179
179
  var DEFAULT_BAZAAR_ADDRESS = "0x00000000E3dA5fC031282A39759bDDA78ae7fAE5";
180
180
  var DEFAULT_COLLECTION_OFFERS_ADDRESS = "0x0000000D43423E0A12CecB307a74591999b32B32";
181
181
  var DEFAULT_FEE_COLLECTOR_ADDRESS = "0x32D16C15410248bef498D7aF50D10Db1a546b9E5";
182
+ var DEFAULT_ERC20_BAZAAR_ADDRESS = "0x00000000a2d173a4610c85c7471a25b6bc216a70";
182
183
  var DEFAULT_NFT_FEE_BPS = 500;
183
184
  var BULK_SEAPORT_ORDER_STATUS_FETCHER_ADDRESS = "0x0000009112ABCE652674b4fE3eD9C765B22d11A7";
184
185
  var ERC721_OWNER_OF_HELPER_ADDRESS = "0x000000aa4eFa2e5A4a6002C7F08B6e8Ec8cf1dDa";
@@ -190,6 +191,7 @@ var BAZAAR_CHAIN_CONFIGS = {
190
191
  bazaarAddress: "0x000000058f3ade587388daf827174d0e6fc97595",
191
192
  collectionOffersAddress: "0x0000000f9c45efcff0f78d8b54aa6a40092d66dc",
192
193
  erc20OffersAddress: "0x0000000e23a89aa06f317306aa1ae231d3503082",
194
+ erc20BazaarAddress: "0x00000006557e3629e2fc50bbad0c002b27cac492",
193
195
  seaportAddress: DEFAULT_SEAPORT_ADDRESS,
194
196
  feeCollectorAddress: "0x66547ff4f7206e291F7BC157b54C026Fc6660961",
195
197
  nftFeeBps: 0,
@@ -279,6 +281,7 @@ var BAZAAR_CHAIN_CONFIGS = {
279
281
  bazaarAddress: "0x000000058f3ade587388daf827174d0e6fc97595",
280
282
  collectionOffersAddress: "0x0000000f9c45efcff0f78d8b54aa6a40092d66dc",
281
283
  erc20OffersAddress: "0x0000000e23a89aa06f317306aa1ae231d3503082",
284
+ erc20BazaarAddress: "0x00000006557e3629e2fc50bbad0c002b27cac492",
282
285
  seaportAddress: DEFAULT_SEAPORT_ADDRESS,
283
286
  feeCollectorAddress: "0x66547ff4f7206e291F7BC157b54C026Fc6660961",
284
287
  nftFeeBps: 0,
@@ -346,6 +349,9 @@ function getHighEthAddress(chainId) {
346
349
  function getErc20OffersAddress(chainId) {
347
350
  return BAZAAR_CHAIN_CONFIGS[chainId]?.erc20OffersAddress;
348
351
  }
352
+ function getErc20BazaarAddress(chainId) {
353
+ return BAZAAR_CHAIN_CONFIGS[chainId]?.erc20BazaarAddress ?? DEFAULT_ERC20_BAZAAR_ADDRESS;
354
+ }
349
355
  function decodeSeaportSubmission(messageData) {
350
356
  const [decoded] = viem.decodeAbiParameters(BAZAAR_SUBMISSION_ABI, messageData);
351
357
  return {
@@ -543,6 +549,19 @@ function isErc20OfferValid(orderStatus, expirationDate, priceWei, buyerWethBalan
543
549
  }
544
550
  return true;
545
551
  }
552
+ function isErc20ListingValid(orderStatus, expirationDate, tokenAmount, sellerTokenBalance) {
553
+ if (orderStatus !== 2 /* OPEN */) {
554
+ return false;
555
+ }
556
+ const now = Math.floor(Date.now() / 1e3);
557
+ if (expirationDate <= now) {
558
+ return false;
559
+ }
560
+ if (sellerTokenBalance < tokenAmount) {
561
+ return false;
562
+ }
563
+ return true;
564
+ }
546
565
 
547
566
  // src/utils/parsing.ts
548
567
  function parseListingFromMessage(message, chainId) {
@@ -704,6 +723,58 @@ function sortErc20OffersByPricePerToken(offers) {
704
723
  return 0;
705
724
  });
706
725
  }
726
+ function parseErc20ListingFromMessage(message, chainId) {
727
+ try {
728
+ const submission = decodeSeaportSubmission(message.data);
729
+ const { parameters } = submission;
730
+ if (parameters.zone.toLowerCase() === NET_SEAPORT_COLLECTION_OFFER_ZONE_ADDRESS.toLowerCase()) {
731
+ return null;
732
+ }
733
+ const offerItem = parameters.offer[0];
734
+ if (!offerItem || offerItem.itemType !== 1 /* ERC20 */) {
735
+ return null;
736
+ }
737
+ const tokenAmount = offerItem.startAmount;
738
+ if (tokenAmount === BigInt(0)) {
739
+ return null;
740
+ }
741
+ const priceWei = getTotalConsiderationAmount(parameters);
742
+ if (priceWei === BigInt(0)) {
743
+ return null;
744
+ }
745
+ const pricePerTokenWei = priceWei / tokenAmount;
746
+ return {
747
+ maker: parameters.offerer,
748
+ tokenAddress: offerItem.token,
749
+ tokenAmount,
750
+ priceWei,
751
+ pricePerTokenWei,
752
+ price: formatPrice(priceWei),
753
+ pricePerToken: formatPrice(pricePerTokenWei),
754
+ currency: getCurrencySymbol(chainId),
755
+ expirationDate: Number(parameters.endTime),
756
+ orderHash: "0x",
757
+ // Will be computed later
758
+ orderStatus: 2 /* OPEN */,
759
+ // Will be validated later
760
+ messageData: message.data,
761
+ orderComponents: {
762
+ ...parameters,
763
+ counter: submission.counter
764
+ }
765
+ };
766
+ } catch {
767
+ return null;
768
+ }
769
+ }
770
+ function sortErc20ListingsByPricePerToken(listings) {
771
+ return [...listings].sort((a, b) => {
772
+ const diff = a.pricePerTokenWei - b.pricePerTokenWei;
773
+ if (diff < BigInt(0)) return -1;
774
+ if (diff > BigInt(0)) return 1;
775
+ return 0;
776
+ });
777
+ }
707
778
 
708
779
  // src/client/BazaarClient.ts
709
780
  var CHAIN_RPC_URLS = {
@@ -999,6 +1070,87 @@ var BazaarClient = class {
999
1070
  });
1000
1071
  return sortErc20OffersByPricePerToken(offers);
1001
1072
  }
1073
+ /**
1074
+ * Get valid ERC20 listings for a token
1075
+ *
1076
+ * Returns listings that are:
1077
+ * - OPEN status (not filled, cancelled, or expired)
1078
+ * - Not expired
1079
+ * - Seller has sufficient ERC20 token balance
1080
+ *
1081
+ * Results are sorted by price per token (lowest first). No deduplication —
1082
+ * all valid listings are returned (grouped by maker in the UI).
1083
+ */
1084
+ async getErc20Listings(options) {
1085
+ const { tokenAddress, excludeMaker, maxMessages = 200 } = options;
1086
+ const erc20BazaarAddress = getErc20BazaarAddress(this.chainId);
1087
+ const count = await this.netClient.getMessageCount({
1088
+ filter: {
1089
+ appAddress: erc20BazaarAddress,
1090
+ topic: tokenAddress.toLowerCase()
1091
+ }
1092
+ });
1093
+ if (count === 0) {
1094
+ return [];
1095
+ }
1096
+ const startIndex = Math.max(0, count - maxMessages);
1097
+ const messages = await this.netClient.getMessages({
1098
+ filter: {
1099
+ appAddress: erc20BazaarAddress,
1100
+ topic: tokenAddress.toLowerCase()
1101
+ },
1102
+ startIndex,
1103
+ endIndex: count
1104
+ });
1105
+ let listings = [];
1106
+ for (const message of messages) {
1107
+ const listing = parseErc20ListingFromMessage(message, this.chainId);
1108
+ if (!listing) continue;
1109
+ if (listing.tokenAddress.toLowerCase() !== tokenAddress.toLowerCase()) {
1110
+ continue;
1111
+ }
1112
+ if (excludeMaker && listing.maker.toLowerCase() === excludeMaker.toLowerCase()) {
1113
+ continue;
1114
+ }
1115
+ listings.push(listing);
1116
+ }
1117
+ if (listings.length === 0) {
1118
+ return [];
1119
+ }
1120
+ const seaport = createSeaportInstance(this.chainId, this.rpcUrl);
1121
+ for (const listing of listings) {
1122
+ const order = getSeaportOrderFromMessageData(listing.messageData);
1123
+ listing.orderHash = computeOrderHash(seaport, order.parameters, order.counter);
1124
+ }
1125
+ const orderHashes = listings.map((l) => l.orderHash);
1126
+ const statusInfos = await bulkFetchOrderStatuses(this.client, this.chainId, orderHashes);
1127
+ listings.forEach((listing, index) => {
1128
+ const statusInfo = statusInfos[index];
1129
+ listing.orderStatus = getOrderStatusFromInfo(listing.orderComponents, statusInfo);
1130
+ });
1131
+ listings = listings.filter(
1132
+ (l) => l.orderStatus === 2 /* OPEN */ && l.expirationDate > Math.floor(Date.now() / 1e3)
1133
+ );
1134
+ if (listings.length === 0) {
1135
+ return [];
1136
+ }
1137
+ const uniqueMakers = [...new Set(listings.map((l) => l.maker))];
1138
+ const balances = await bulkFetchErc20Balances(this.client, tokenAddress, uniqueMakers);
1139
+ const balanceMap = /* @__PURE__ */ new Map();
1140
+ uniqueMakers.forEach((maker, index) => {
1141
+ balanceMap.set(maker.toLowerCase(), balances[index]);
1142
+ });
1143
+ listings = listings.filter((listing) => {
1144
+ const balance = balanceMap.get(listing.maker.toLowerCase()) || BigInt(0);
1145
+ return isErc20ListingValid(
1146
+ listing.orderStatus,
1147
+ listing.expirationDate,
1148
+ listing.tokenAmount,
1149
+ balance
1150
+ );
1151
+ });
1152
+ return sortErc20ListingsByPricePerToken(listings);
1153
+ }
1002
1154
  /**
1003
1155
  * Get the chain ID this client is configured for
1004
1156
  */
@@ -1024,6 +1176,12 @@ var BazaarClient = class {
1024
1176
  getErc20OffersAddress() {
1025
1177
  return getErc20OffersAddress(this.chainId);
1026
1178
  }
1179
+ /**
1180
+ * Get the ERC20 bazaar (listings) contract address for this chain
1181
+ */
1182
+ getErc20BazaarAddress() {
1183
+ return getErc20BazaarAddress(this.chainId);
1184
+ }
1027
1185
  /**
1028
1186
  * Get the Seaport contract address for this chain
1029
1187
  */
@@ -1054,6 +1212,18 @@ var BazaarClient = class {
1054
1212
  }
1055
1213
  return this.prepareCancelOrder(offer.orderComponents);
1056
1214
  }
1215
+ /**
1216
+ * Prepare a transaction to cancel an ERC20 listing
1217
+ *
1218
+ * The listing must have been created by the caller.
1219
+ * Use the orderComponents from the Erc20Listing object returned by getErc20Listings().
1220
+ */
1221
+ prepareCancelErc20Listing(listing) {
1222
+ if (!listing.orderComponents) {
1223
+ throw new Error("Listing does not have order components");
1224
+ }
1225
+ return this.prepareCancelOrder(listing.orderComponents);
1226
+ }
1057
1227
  /**
1058
1228
  * Prepare a transaction to cancel a Seaport order
1059
1229
  *
@@ -1389,8 +1559,106 @@ function useBazaarErc20Offers({
1389
1559
  refetch
1390
1560
  };
1391
1561
  }
1562
+ function useBazaarErc20Listings({
1563
+ chainId,
1564
+ tokenAddress,
1565
+ excludeMaker,
1566
+ maxMessages = 200,
1567
+ enabled = true
1568
+ }) {
1569
+ const [listings, setListings] = react.useState([]);
1570
+ const [isProcessing, setIsProcessing] = react.useState(false);
1571
+ const [processingError, setProcessingError] = react.useState();
1572
+ const [refetchTrigger, setRefetchTrigger] = react.useState(0);
1573
+ const isSupported = react.useMemo(
1574
+ () => isBazaarSupportedOnChain(chainId),
1575
+ [chainId]
1576
+ );
1577
+ const erc20BazaarAddress = react.useMemo(
1578
+ () => isSupported ? getErc20BazaarAddress(chainId) : void 0,
1579
+ [chainId, isSupported]
1580
+ );
1581
+ const filter = react.useMemo(
1582
+ () => ({
1583
+ appAddress: erc20BazaarAddress,
1584
+ topic: tokenAddress.toLowerCase()
1585
+ }),
1586
+ [erc20BazaarAddress, tokenAddress]
1587
+ );
1588
+ const { count: totalCount, isLoading: isLoadingCount } = react$1.useNetMessageCount({
1589
+ chainId,
1590
+ filter,
1591
+ enabled: enabled && isSupported
1592
+ });
1593
+ const startIndex = react.useMemo(
1594
+ () => Math.max(0, totalCount - maxMessages),
1595
+ [totalCount, maxMessages]
1596
+ );
1597
+ const {
1598
+ messages,
1599
+ isLoading: isLoadingMessages,
1600
+ error: messagesError,
1601
+ refetch: refetchMessages
1602
+ } = react$1.useNetMessages({
1603
+ chainId,
1604
+ filter,
1605
+ startIndex,
1606
+ endIndex: totalCount,
1607
+ enabled: enabled && isSupported && totalCount > 0
1608
+ });
1609
+ react.useEffect(() => {
1610
+ if (!isSupported || !enabled) {
1611
+ setListings([]);
1612
+ return;
1613
+ }
1614
+ if (!messages || messages.length === 0) {
1615
+ setListings([]);
1616
+ return;
1617
+ }
1618
+ let cancelled = false;
1619
+ async function processListings() {
1620
+ setIsProcessing(true);
1621
+ setProcessingError(void 0);
1622
+ try {
1623
+ const client = new BazaarClient({ chainId });
1624
+ const validListings = await client.getErc20Listings({
1625
+ tokenAddress,
1626
+ excludeMaker,
1627
+ maxMessages
1628
+ });
1629
+ if (!cancelled) {
1630
+ setListings(validListings);
1631
+ }
1632
+ } catch (err) {
1633
+ if (!cancelled) {
1634
+ setProcessingError(err instanceof Error ? err : new Error(String(err)));
1635
+ setListings([]);
1636
+ }
1637
+ } finally {
1638
+ if (!cancelled) {
1639
+ setIsProcessing(false);
1640
+ }
1641
+ }
1642
+ }
1643
+ processListings();
1644
+ return () => {
1645
+ cancelled = true;
1646
+ };
1647
+ }, [chainId, tokenAddress, excludeMaker, maxMessages, messages, isSupported, enabled, refetchTrigger]);
1648
+ const refetch = () => {
1649
+ refetchMessages();
1650
+ setRefetchTrigger((t) => t + 1);
1651
+ };
1652
+ return {
1653
+ listings,
1654
+ isLoading: isLoadingCount || isLoadingMessages || isProcessing,
1655
+ error: messagesError || processingError,
1656
+ refetch
1657
+ };
1658
+ }
1392
1659
 
1393
1660
  exports.useBazaarCollectionOffers = useBazaarCollectionOffers;
1661
+ exports.useBazaarErc20Listings = useBazaarErc20Listings;
1394
1662
  exports.useBazaarErc20Offers = useBazaarErc20Offers;
1395
1663
  exports.useBazaarListings = useBazaarListings;
1396
1664
  //# sourceMappingURL=react.js.map