@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.mjs CHANGED
@@ -177,6 +177,7 @@ var DEFAULT_SEAPORT_ADDRESS = "0x0000000000000068F116a894984e2DB1123eB395";
177
177
  var DEFAULT_BAZAAR_ADDRESS = "0x00000000E3dA5fC031282A39759bDDA78ae7fAE5";
178
178
  var DEFAULT_COLLECTION_OFFERS_ADDRESS = "0x0000000D43423E0A12CecB307a74591999b32B32";
179
179
  var DEFAULT_FEE_COLLECTOR_ADDRESS = "0x32D16C15410248bef498D7aF50D10Db1a546b9E5";
180
+ var DEFAULT_ERC20_BAZAAR_ADDRESS = "0x00000000a2d173a4610c85c7471a25b6bc216a70";
180
181
  var DEFAULT_NFT_FEE_BPS = 500;
181
182
  var BULK_SEAPORT_ORDER_STATUS_FETCHER_ADDRESS = "0x0000009112ABCE652674b4fE3eD9C765B22d11A7";
182
183
  var ERC721_OWNER_OF_HELPER_ADDRESS = "0x000000aa4eFa2e5A4a6002C7F08B6e8Ec8cf1dDa";
@@ -188,6 +189,7 @@ var BAZAAR_CHAIN_CONFIGS = {
188
189
  bazaarAddress: "0x000000058f3ade587388daf827174d0e6fc97595",
189
190
  collectionOffersAddress: "0x0000000f9c45efcff0f78d8b54aa6a40092d66dc",
190
191
  erc20OffersAddress: "0x0000000e23a89aa06f317306aa1ae231d3503082",
192
+ erc20BazaarAddress: "0x00000006557e3629e2fc50bbad0c002b27cac492",
191
193
  seaportAddress: DEFAULT_SEAPORT_ADDRESS,
192
194
  feeCollectorAddress: "0x66547ff4f7206e291F7BC157b54C026Fc6660961",
193
195
  nftFeeBps: 0,
@@ -277,6 +279,7 @@ var BAZAAR_CHAIN_CONFIGS = {
277
279
  bazaarAddress: "0x000000058f3ade587388daf827174d0e6fc97595",
278
280
  collectionOffersAddress: "0x0000000f9c45efcff0f78d8b54aa6a40092d66dc",
279
281
  erc20OffersAddress: "0x0000000e23a89aa06f317306aa1ae231d3503082",
282
+ erc20BazaarAddress: "0x00000006557e3629e2fc50bbad0c002b27cac492",
280
283
  seaportAddress: DEFAULT_SEAPORT_ADDRESS,
281
284
  feeCollectorAddress: "0x66547ff4f7206e291F7BC157b54C026Fc6660961",
282
285
  nftFeeBps: 0,
@@ -344,6 +347,9 @@ function getHighEthAddress(chainId) {
344
347
  function getErc20OffersAddress(chainId) {
345
348
  return BAZAAR_CHAIN_CONFIGS[chainId]?.erc20OffersAddress;
346
349
  }
350
+ function getErc20BazaarAddress(chainId) {
351
+ return BAZAAR_CHAIN_CONFIGS[chainId]?.erc20BazaarAddress ?? DEFAULT_ERC20_BAZAAR_ADDRESS;
352
+ }
347
353
  function decodeSeaportSubmission(messageData) {
348
354
  const [decoded] = decodeAbiParameters(BAZAAR_SUBMISSION_ABI, messageData);
349
355
  return {
@@ -541,6 +547,19 @@ function isErc20OfferValid(orderStatus, expirationDate, priceWei, buyerWethBalan
541
547
  }
542
548
  return true;
543
549
  }
550
+ function isErc20ListingValid(orderStatus, expirationDate, tokenAmount, sellerTokenBalance) {
551
+ if (orderStatus !== 2 /* OPEN */) {
552
+ return false;
553
+ }
554
+ const now = Math.floor(Date.now() / 1e3);
555
+ if (expirationDate <= now) {
556
+ return false;
557
+ }
558
+ if (sellerTokenBalance < tokenAmount) {
559
+ return false;
560
+ }
561
+ return true;
562
+ }
544
563
 
545
564
  // src/utils/parsing.ts
546
565
  function parseListingFromMessage(message, chainId) {
@@ -702,6 +721,58 @@ function sortErc20OffersByPricePerToken(offers) {
702
721
  return 0;
703
722
  });
704
723
  }
724
+ function parseErc20ListingFromMessage(message, chainId) {
725
+ try {
726
+ const submission = decodeSeaportSubmission(message.data);
727
+ const { parameters } = submission;
728
+ if (parameters.zone.toLowerCase() === NET_SEAPORT_COLLECTION_OFFER_ZONE_ADDRESS.toLowerCase()) {
729
+ return null;
730
+ }
731
+ const offerItem = parameters.offer[0];
732
+ if (!offerItem || offerItem.itemType !== 1 /* ERC20 */) {
733
+ return null;
734
+ }
735
+ const tokenAmount = offerItem.startAmount;
736
+ if (tokenAmount === BigInt(0)) {
737
+ return null;
738
+ }
739
+ const priceWei = getTotalConsiderationAmount(parameters);
740
+ if (priceWei === BigInt(0)) {
741
+ return null;
742
+ }
743
+ const pricePerTokenWei = priceWei / tokenAmount;
744
+ return {
745
+ maker: parameters.offerer,
746
+ tokenAddress: offerItem.token,
747
+ tokenAmount,
748
+ priceWei,
749
+ pricePerTokenWei,
750
+ price: formatPrice(priceWei),
751
+ pricePerToken: formatPrice(pricePerTokenWei),
752
+ currency: getCurrencySymbol(chainId),
753
+ expirationDate: Number(parameters.endTime),
754
+ orderHash: "0x",
755
+ // Will be computed later
756
+ orderStatus: 2 /* OPEN */,
757
+ // Will be validated later
758
+ messageData: message.data,
759
+ orderComponents: {
760
+ ...parameters,
761
+ counter: submission.counter
762
+ }
763
+ };
764
+ } catch {
765
+ return null;
766
+ }
767
+ }
768
+ function sortErc20ListingsByPricePerToken(listings) {
769
+ return [...listings].sort((a, b) => {
770
+ const diff = a.pricePerTokenWei - b.pricePerTokenWei;
771
+ if (diff < BigInt(0)) return -1;
772
+ if (diff > BigInt(0)) return 1;
773
+ return 0;
774
+ });
775
+ }
705
776
 
706
777
  // src/client/BazaarClient.ts
707
778
  var CHAIN_RPC_URLS = {
@@ -997,6 +1068,87 @@ var BazaarClient = class {
997
1068
  });
998
1069
  return sortErc20OffersByPricePerToken(offers);
999
1070
  }
1071
+ /**
1072
+ * Get valid ERC20 listings for a token
1073
+ *
1074
+ * Returns listings that are:
1075
+ * - OPEN status (not filled, cancelled, or expired)
1076
+ * - Not expired
1077
+ * - Seller has sufficient ERC20 token balance
1078
+ *
1079
+ * Results are sorted by price per token (lowest first). No deduplication —
1080
+ * all valid listings are returned (grouped by maker in the UI).
1081
+ */
1082
+ async getErc20Listings(options) {
1083
+ const { tokenAddress, excludeMaker, maxMessages = 200 } = options;
1084
+ const erc20BazaarAddress = getErc20BazaarAddress(this.chainId);
1085
+ const count = await this.netClient.getMessageCount({
1086
+ filter: {
1087
+ appAddress: erc20BazaarAddress,
1088
+ topic: tokenAddress.toLowerCase()
1089
+ }
1090
+ });
1091
+ if (count === 0) {
1092
+ return [];
1093
+ }
1094
+ const startIndex = Math.max(0, count - maxMessages);
1095
+ const messages = await this.netClient.getMessages({
1096
+ filter: {
1097
+ appAddress: erc20BazaarAddress,
1098
+ topic: tokenAddress.toLowerCase()
1099
+ },
1100
+ startIndex,
1101
+ endIndex: count
1102
+ });
1103
+ let listings = [];
1104
+ for (const message of messages) {
1105
+ const listing = parseErc20ListingFromMessage(message, this.chainId);
1106
+ if (!listing) continue;
1107
+ if (listing.tokenAddress.toLowerCase() !== tokenAddress.toLowerCase()) {
1108
+ continue;
1109
+ }
1110
+ if (excludeMaker && listing.maker.toLowerCase() === excludeMaker.toLowerCase()) {
1111
+ continue;
1112
+ }
1113
+ listings.push(listing);
1114
+ }
1115
+ if (listings.length === 0) {
1116
+ return [];
1117
+ }
1118
+ const seaport = createSeaportInstance(this.chainId, this.rpcUrl);
1119
+ for (const listing of listings) {
1120
+ const order = getSeaportOrderFromMessageData(listing.messageData);
1121
+ listing.orderHash = computeOrderHash(seaport, order.parameters, order.counter);
1122
+ }
1123
+ const orderHashes = listings.map((l) => l.orderHash);
1124
+ const statusInfos = await bulkFetchOrderStatuses(this.client, this.chainId, orderHashes);
1125
+ listings.forEach((listing, index) => {
1126
+ const statusInfo = statusInfos[index];
1127
+ listing.orderStatus = getOrderStatusFromInfo(listing.orderComponents, statusInfo);
1128
+ });
1129
+ listings = listings.filter(
1130
+ (l) => l.orderStatus === 2 /* OPEN */ && l.expirationDate > Math.floor(Date.now() / 1e3)
1131
+ );
1132
+ if (listings.length === 0) {
1133
+ return [];
1134
+ }
1135
+ const uniqueMakers = [...new Set(listings.map((l) => l.maker))];
1136
+ const balances = await bulkFetchErc20Balances(this.client, tokenAddress, uniqueMakers);
1137
+ const balanceMap = /* @__PURE__ */ new Map();
1138
+ uniqueMakers.forEach((maker, index) => {
1139
+ balanceMap.set(maker.toLowerCase(), balances[index]);
1140
+ });
1141
+ listings = listings.filter((listing) => {
1142
+ const balance = balanceMap.get(listing.maker.toLowerCase()) || BigInt(0);
1143
+ return isErc20ListingValid(
1144
+ listing.orderStatus,
1145
+ listing.expirationDate,
1146
+ listing.tokenAmount,
1147
+ balance
1148
+ );
1149
+ });
1150
+ return sortErc20ListingsByPricePerToken(listings);
1151
+ }
1000
1152
  /**
1001
1153
  * Get the chain ID this client is configured for
1002
1154
  */
@@ -1022,6 +1174,12 @@ var BazaarClient = class {
1022
1174
  getErc20OffersAddress() {
1023
1175
  return getErc20OffersAddress(this.chainId);
1024
1176
  }
1177
+ /**
1178
+ * Get the ERC20 bazaar (listings) contract address for this chain
1179
+ */
1180
+ getErc20BazaarAddress() {
1181
+ return getErc20BazaarAddress(this.chainId);
1182
+ }
1025
1183
  /**
1026
1184
  * Get the Seaport contract address for this chain
1027
1185
  */
@@ -1052,6 +1210,18 @@ var BazaarClient = class {
1052
1210
  }
1053
1211
  return this.prepareCancelOrder(offer.orderComponents);
1054
1212
  }
1213
+ /**
1214
+ * Prepare a transaction to cancel an ERC20 listing
1215
+ *
1216
+ * The listing must have been created by the caller.
1217
+ * Use the orderComponents from the Erc20Listing object returned by getErc20Listings().
1218
+ */
1219
+ prepareCancelErc20Listing(listing) {
1220
+ if (!listing.orderComponents) {
1221
+ throw new Error("Listing does not have order components");
1222
+ }
1223
+ return this.prepareCancelOrder(listing.orderComponents);
1224
+ }
1055
1225
  /**
1056
1226
  * Prepare a transaction to cancel a Seaport order
1057
1227
  *
@@ -1387,7 +1557,104 @@ function useBazaarErc20Offers({
1387
1557
  refetch
1388
1558
  };
1389
1559
  }
1560
+ function useBazaarErc20Listings({
1561
+ chainId,
1562
+ tokenAddress,
1563
+ excludeMaker,
1564
+ maxMessages = 200,
1565
+ enabled = true
1566
+ }) {
1567
+ const [listings, setListings] = useState([]);
1568
+ const [isProcessing, setIsProcessing] = useState(false);
1569
+ const [processingError, setProcessingError] = useState();
1570
+ const [refetchTrigger, setRefetchTrigger] = useState(0);
1571
+ const isSupported = useMemo(
1572
+ () => isBazaarSupportedOnChain(chainId),
1573
+ [chainId]
1574
+ );
1575
+ const erc20BazaarAddress = useMemo(
1576
+ () => isSupported ? getErc20BazaarAddress(chainId) : void 0,
1577
+ [chainId, isSupported]
1578
+ );
1579
+ const filter = useMemo(
1580
+ () => ({
1581
+ appAddress: erc20BazaarAddress,
1582
+ topic: tokenAddress.toLowerCase()
1583
+ }),
1584
+ [erc20BazaarAddress, tokenAddress]
1585
+ );
1586
+ const { count: totalCount, isLoading: isLoadingCount } = useNetMessageCount({
1587
+ chainId,
1588
+ filter,
1589
+ enabled: enabled && isSupported
1590
+ });
1591
+ const startIndex = useMemo(
1592
+ () => Math.max(0, totalCount - maxMessages),
1593
+ [totalCount, maxMessages]
1594
+ );
1595
+ const {
1596
+ messages,
1597
+ isLoading: isLoadingMessages,
1598
+ error: messagesError,
1599
+ refetch: refetchMessages
1600
+ } = useNetMessages({
1601
+ chainId,
1602
+ filter,
1603
+ startIndex,
1604
+ endIndex: totalCount,
1605
+ enabled: enabled && isSupported && totalCount > 0
1606
+ });
1607
+ useEffect(() => {
1608
+ if (!isSupported || !enabled) {
1609
+ setListings([]);
1610
+ return;
1611
+ }
1612
+ if (!messages || messages.length === 0) {
1613
+ setListings([]);
1614
+ return;
1615
+ }
1616
+ let cancelled = false;
1617
+ async function processListings() {
1618
+ setIsProcessing(true);
1619
+ setProcessingError(void 0);
1620
+ try {
1621
+ const client = new BazaarClient({ chainId });
1622
+ const validListings = await client.getErc20Listings({
1623
+ tokenAddress,
1624
+ excludeMaker,
1625
+ maxMessages
1626
+ });
1627
+ if (!cancelled) {
1628
+ setListings(validListings);
1629
+ }
1630
+ } catch (err) {
1631
+ if (!cancelled) {
1632
+ setProcessingError(err instanceof Error ? err : new Error(String(err)));
1633
+ setListings([]);
1634
+ }
1635
+ } finally {
1636
+ if (!cancelled) {
1637
+ setIsProcessing(false);
1638
+ }
1639
+ }
1640
+ }
1641
+ processListings();
1642
+ return () => {
1643
+ cancelled = true;
1644
+ };
1645
+ }, [chainId, tokenAddress, excludeMaker, maxMessages, messages, isSupported, enabled, refetchTrigger]);
1646
+ const refetch = () => {
1647
+ refetchMessages();
1648
+ setRefetchTrigger((t) => t + 1);
1649
+ };
1650
+ return {
1651
+ listings,
1652
+ isLoading: isLoadingCount || isLoadingMessages || isProcessing,
1653
+ error: messagesError || processingError,
1654
+ refetch
1655
+ };
1656
+ }
1390
1657
 
1391
- export { useBazaarCollectionOffers, useBazaarErc20Offers, useBazaarListings };
1658
+ export { useBazaarCollectionOffers, useBazaarErc20Listings, useBazaarErc20Offers, useBazaarListings };
1392
1659
  //# sourceMappingURL=react.mjs.map
1393
1660
  //# sourceMappingURL=react.mjs.map