@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 +30 -3
- package/dist/index.d.ts +30 -3
- package/dist/index.js +198 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +197 -16
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +63 -5
- package/dist/react.d.ts +63 -5
- package/dist/react.js +306 -17
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +306 -18
- package/dist/react.mjs.map +1 -1
- package/dist/{types-CfGfQTJL.d.mts → types-5kMf461x.d.mts} +39 -3
- package/dist/{types-CfGfQTJL.d.ts → types-5kMf461x.d.ts} +39 -3
- package/package.json +2 -1
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
|
-
|
|
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"],
|
|
@@ -858,7 +932,7 @@ var BazaarClient = class {
|
|
|
858
932
|
const bazaarAddress = getBazaarAddress(this.chainId);
|
|
859
933
|
const filter = {
|
|
860
934
|
appAddress: bazaarAddress,
|
|
861
|
-
topic: nftAddress
|
|
935
|
+
topic: nftAddress?.toLowerCase(),
|
|
862
936
|
maker
|
|
863
937
|
};
|
|
864
938
|
let startIndex;
|
|
@@ -931,18 +1005,57 @@ var BazaarClient = class {
|
|
|
931
1005
|
}
|
|
932
1006
|
const openListings = listings.filter((l) => l.orderStatus === 2 /* OPEN */);
|
|
933
1007
|
const expiredListings = includeExpired ? listings.filter((l) => l.orderStatus === 1 /* EXPIRED */) : [];
|
|
934
|
-
|
|
935
|
-
const owners = await bulkFetchNftOwners(this.client, nftAddress, tokenIds);
|
|
1008
|
+
let validOpenListings;
|
|
936
1009
|
const beforeOwnership = openListings.length;
|
|
937
|
-
|
|
938
|
-
const
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1010
|
+
if (nftAddress) {
|
|
1011
|
+
const tokenIds = openListings.map((l) => l.tokenId);
|
|
1012
|
+
const owners = await bulkFetchNftOwners(this.client, nftAddress, tokenIds);
|
|
1013
|
+
validOpenListings = openListings.filter((listing, index) => {
|
|
1014
|
+
const owner = owners[index];
|
|
1015
|
+
return isListingValid(
|
|
1016
|
+
listing.orderStatus,
|
|
1017
|
+
listing.expirationDate,
|
|
1018
|
+
listing.maker,
|
|
1019
|
+
owner
|
|
1020
|
+
);
|
|
1021
|
+
});
|
|
1022
|
+
} else {
|
|
1023
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1024
|
+
for (const listing of openListings) {
|
|
1025
|
+
const key = listing.nftAddress.toLowerCase();
|
|
1026
|
+
const group = groups.get(key);
|
|
1027
|
+
if (group) {
|
|
1028
|
+
group.push(listing);
|
|
1029
|
+
} else {
|
|
1030
|
+
groups.set(key, [listing]);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
const groupEntries = Array.from(groups.entries());
|
|
1034
|
+
const ownerResults = await Promise.all(
|
|
1035
|
+
groupEntries.map(
|
|
1036
|
+
([addr, groupListings]) => bulkFetchNftOwners(
|
|
1037
|
+
this.client,
|
|
1038
|
+
addr,
|
|
1039
|
+
groupListings.map((l) => l.tokenId)
|
|
1040
|
+
)
|
|
1041
|
+
)
|
|
944
1042
|
);
|
|
945
|
-
|
|
1043
|
+
validOpenListings = [];
|
|
1044
|
+
groupEntries.forEach(([, groupListings], groupIndex) => {
|
|
1045
|
+
const owners = ownerResults[groupIndex];
|
|
1046
|
+
for (let i = 0; i < groupListings.length; i++) {
|
|
1047
|
+
const listing = groupListings[i];
|
|
1048
|
+
if (isListingValid(
|
|
1049
|
+
listing.orderStatus,
|
|
1050
|
+
listing.expirationDate,
|
|
1051
|
+
listing.maker,
|
|
1052
|
+
owners[i]
|
|
1053
|
+
)) {
|
|
1054
|
+
validOpenListings.push(listing);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
946
1059
|
console.log(tag, `after ownership filter: ${validOpenListings.length}/${beforeOwnership} (${beforeOwnership - validOpenListings.length} dropped)`);
|
|
947
1060
|
const dedupedOpen = sortListingsByPrice(getBestListingPerToken(validOpenListings));
|
|
948
1061
|
const activeTokenKeys = new Set(dedupedOpen.map((l) => `${l.nftAddress.toLowerCase()}-${l.tokenId}`));
|
|
@@ -1275,6 +1388,72 @@ var BazaarClient = class {
|
|
|
1275
1388
|
console.log(tag, `after balance filter: ${listings.length}/${beforeBalance} (${beforeBalance - listings.length} dropped)`);
|
|
1276
1389
|
return sortErc20ListingsByPricePerToken(listings);
|
|
1277
1390
|
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Get recent sales for a collection
|
|
1393
|
+
*
|
|
1394
|
+
* Sales are stored differently from listings: Net messages contain order hashes,
|
|
1395
|
+
* and the actual sale data is fetched from the bulk storage contract.
|
|
1396
|
+
*
|
|
1397
|
+
* Results are sorted by timestamp (most recent first)
|
|
1398
|
+
*/
|
|
1399
|
+
async getSales(options) {
|
|
1400
|
+
const { nftAddress, maxMessages = 100 } = options;
|
|
1401
|
+
const filter = {
|
|
1402
|
+
appAddress: NET_SEAPORT_ZONE_ADDRESS,
|
|
1403
|
+
topic: nftAddress.toLowerCase()
|
|
1404
|
+
};
|
|
1405
|
+
const count = await this.netClient.getMessageCount({ filter });
|
|
1406
|
+
if (count === 0) {
|
|
1407
|
+
return [];
|
|
1408
|
+
}
|
|
1409
|
+
const startIndex = Math.max(0, count - maxMessages);
|
|
1410
|
+
const messages = await this.netClient.getMessages({
|
|
1411
|
+
filter,
|
|
1412
|
+
startIndex,
|
|
1413
|
+
endIndex: count
|
|
1414
|
+
});
|
|
1415
|
+
return this.processSalesFromMessages(messages, options);
|
|
1416
|
+
}
|
|
1417
|
+
/**
|
|
1418
|
+
* Process pre-fetched messages into sales.
|
|
1419
|
+
*
|
|
1420
|
+
* Each message's data field contains an order hash. The actual sale data
|
|
1421
|
+
* is fetched from the bulk storage contract using these order hashes.
|
|
1422
|
+
*/
|
|
1423
|
+
async processSalesFromMessages(messages, options) {
|
|
1424
|
+
const tag = `[BazaarClient.processSales chain=${this.chainId}]`;
|
|
1425
|
+
if (messages.length === 0) {
|
|
1426
|
+
return [];
|
|
1427
|
+
}
|
|
1428
|
+
const orderHashes = messages.map((m) => m.data);
|
|
1429
|
+
console.log(tag, `fetching ${orderHashes.length} sale details from storage...`);
|
|
1430
|
+
const bulkKeys = orderHashes.map((hash) => ({
|
|
1431
|
+
key: hash,
|
|
1432
|
+
operator: NET_SEAPORT_ZONE_ADDRESS
|
|
1433
|
+
}));
|
|
1434
|
+
let storedResults;
|
|
1435
|
+
try {
|
|
1436
|
+
storedResults = await this.client.readContract({
|
|
1437
|
+
abi: STORAGE_CONTRACT.abi,
|
|
1438
|
+
address: STORAGE_CONTRACT.address,
|
|
1439
|
+
functionName: "bulkGet",
|
|
1440
|
+
args: [bulkKeys]
|
|
1441
|
+
});
|
|
1442
|
+
} catch (err) {
|
|
1443
|
+
console.error(tag, "bulk storage fetch failed:", err);
|
|
1444
|
+
return [];
|
|
1445
|
+
}
|
|
1446
|
+
const sales = [];
|
|
1447
|
+
for (const result of storedResults) {
|
|
1448
|
+
if (!result.value || result.value === "0x") continue;
|
|
1449
|
+
const sale = parseSaleFromStoredData(result.value, this.chainId);
|
|
1450
|
+
if (sale) {
|
|
1451
|
+
sales.push(sale);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
console.log(tag, `parsed ${sales.length}/${storedResults.length} sales`);
|
|
1455
|
+
return sales;
|
|
1456
|
+
}
|
|
1278
1457
|
/**
|
|
1279
1458
|
* Get the chain ID this client is configured for
|
|
1280
1459
|
*/
|
|
@@ -1421,7 +1600,7 @@ function useBazaarListings({
|
|
|
1421
1600
|
const filter = useMemo(
|
|
1422
1601
|
() => ({
|
|
1423
1602
|
appAddress: bazaarAddress,
|
|
1424
|
-
topic: nftAddress
|
|
1603
|
+
topic: nftAddress?.toLowerCase(),
|
|
1425
1604
|
maker
|
|
1426
1605
|
}),
|
|
1427
1606
|
[bazaarAddress, nftAddress, maker]
|
|
@@ -1445,7 +1624,7 @@ function useBazaarListings({
|
|
|
1445
1624
|
endIndex,
|
|
1446
1625
|
enabled: enabled && isSupported && (hasRangeOverride || totalCount > 0)
|
|
1447
1626
|
});
|
|
1448
|
-
const TAG = `[useBazaarListings chain=${chainId} nft=${nftAddress
|
|
1627
|
+
const TAG = `[useBazaarListings chain=${chainId} nft=${nftAddress?.slice(0, 10) ?? "all"}]`;
|
|
1449
1628
|
useEffect(() => {
|
|
1450
1629
|
console.log(TAG, {
|
|
1451
1630
|
enabled,
|
|
@@ -1861,7 +2040,116 @@ function useBazaarErc20Listings({
|
|
|
1861
2040
|
refetch
|
|
1862
2041
|
};
|
|
1863
2042
|
}
|
|
2043
|
+
function useBazaarSales({
|
|
2044
|
+
chainId,
|
|
2045
|
+
nftAddress,
|
|
2046
|
+
maxMessages = 100,
|
|
2047
|
+
enabled = true
|
|
2048
|
+
}) {
|
|
2049
|
+
const wagmiClient = usePublicClient({ chainId });
|
|
2050
|
+
const [sales, setSales] = useState([]);
|
|
2051
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
2052
|
+
const [processingError, setProcessingError] = useState();
|
|
2053
|
+
const [refetchTrigger, setRefetchTrigger] = useState(0);
|
|
2054
|
+
const isSupported = useMemo(
|
|
2055
|
+
() => isBazaarSupportedOnChain(chainId),
|
|
2056
|
+
[chainId]
|
|
2057
|
+
);
|
|
2058
|
+
const filter = useMemo(
|
|
2059
|
+
() => ({
|
|
2060
|
+
appAddress: NET_SEAPORT_ZONE_ADDRESS,
|
|
2061
|
+
topic: nftAddress.toLowerCase()
|
|
2062
|
+
}),
|
|
2063
|
+
[nftAddress]
|
|
2064
|
+
);
|
|
2065
|
+
const { count: totalCount, isLoading: isLoadingCount } = useNetMessageCount({
|
|
2066
|
+
chainId,
|
|
2067
|
+
filter,
|
|
2068
|
+
enabled: enabled && isSupported
|
|
2069
|
+
});
|
|
2070
|
+
const startIndex = useMemo(
|
|
2071
|
+
() => Math.max(0, totalCount - maxMessages),
|
|
2072
|
+
[totalCount, maxMessages]
|
|
2073
|
+
);
|
|
2074
|
+
const {
|
|
2075
|
+
messages,
|
|
2076
|
+
isLoading: isLoadingMessages,
|
|
2077
|
+
error: messagesError,
|
|
2078
|
+
refetch: refetchMessages
|
|
2079
|
+
} = useNetMessages({
|
|
2080
|
+
chainId,
|
|
2081
|
+
filter,
|
|
2082
|
+
startIndex,
|
|
2083
|
+
endIndex: totalCount,
|
|
2084
|
+
enabled: enabled && isSupported && totalCount > 0
|
|
2085
|
+
});
|
|
2086
|
+
const TAG = `[useBazaarSales chain=${chainId} nft=${nftAddress.slice(0, 10)}]`;
|
|
2087
|
+
useEffect(() => {
|
|
2088
|
+
console.log(TAG, {
|
|
2089
|
+
enabled,
|
|
2090
|
+
isSupported,
|
|
2091
|
+
totalCount,
|
|
2092
|
+
isLoadingCount,
|
|
2093
|
+
startIndex,
|
|
2094
|
+
endIndex: totalCount,
|
|
2095
|
+
messagesLength: messages?.length ?? 0,
|
|
2096
|
+
isLoadingMessages,
|
|
2097
|
+
messagesError: messagesError?.message
|
|
2098
|
+
});
|
|
2099
|
+
}, [enabled, isSupported, totalCount, isLoadingCount, startIndex, messages?.length, isLoadingMessages, messagesError]);
|
|
2100
|
+
useEffect(() => {
|
|
2101
|
+
if (!isSupported || !enabled) {
|
|
2102
|
+
setSales([]);
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
if (!messages || messages.length === 0) {
|
|
2106
|
+
setSales([]);
|
|
2107
|
+
return;
|
|
2108
|
+
}
|
|
2109
|
+
let cancelled = false;
|
|
2110
|
+
async function processSales() {
|
|
2111
|
+
setIsProcessing(true);
|
|
2112
|
+
setProcessingError(void 0);
|
|
2113
|
+
console.log(TAG, `processing ${messages.length} messages...`);
|
|
2114
|
+
try {
|
|
2115
|
+
const client = new BazaarClient({ chainId, publicClient: wagmiClient });
|
|
2116
|
+
const parsedSales = await client.processSalesFromMessages(
|
|
2117
|
+
messages,
|
|
2118
|
+
{ nftAddress }
|
|
2119
|
+
);
|
|
2120
|
+
console.log(TAG, `processed \u2192 ${parsedSales.length} sales`);
|
|
2121
|
+
if (!cancelled) {
|
|
2122
|
+
setSales(parsedSales);
|
|
2123
|
+
}
|
|
2124
|
+
} catch (err) {
|
|
2125
|
+
console.error(TAG, "processing error:", err);
|
|
2126
|
+
if (!cancelled) {
|
|
2127
|
+
setProcessingError(err instanceof Error ? err : new Error(String(err)));
|
|
2128
|
+
setSales([]);
|
|
2129
|
+
}
|
|
2130
|
+
} finally {
|
|
2131
|
+
if (!cancelled) {
|
|
2132
|
+
setIsProcessing(false);
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
processSales();
|
|
2137
|
+
return () => {
|
|
2138
|
+
cancelled = true;
|
|
2139
|
+
};
|
|
2140
|
+
}, [chainId, nftAddress, messages, isSupported, enabled, refetchTrigger]);
|
|
2141
|
+
const refetch = () => {
|
|
2142
|
+
refetchMessages();
|
|
2143
|
+
setRefetchTrigger((t) => t + 1);
|
|
2144
|
+
};
|
|
2145
|
+
return {
|
|
2146
|
+
sales,
|
|
2147
|
+
isLoading: isLoadingCount || isLoadingMessages || isProcessing,
|
|
2148
|
+
error: messagesError || processingError,
|
|
2149
|
+
refetch
|
|
2150
|
+
};
|
|
2151
|
+
}
|
|
1864
2152
|
|
|
1865
|
-
export { useBazaarCollectionOffers, useBazaarErc20Listings, useBazaarErc20Offers, useBazaarListings };
|
|
2153
|
+
export { useBazaarCollectionOffers, useBazaarErc20Listings, useBazaarErc20Offers, useBazaarListings, useBazaarSales };
|
|
1866
2154
|
//# sourceMappingURL=react.mjs.map
|
|
1867
2155
|
//# sourceMappingURL=react.mjs.map
|