@bananapus/nana-sdk-core 1.2.0 → 1.4.0
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/cjs/utils/hook.d.ts +15 -0
- package/dist/cjs/utils/hook.d.ts.map +1 -1
- package/dist/cjs/utils/hook.js +20 -0
- package/dist/cjs/utils/hook.js.map +1 -1
- package/dist/cjs/utils/index.d.ts +1 -0
- package/dist/cjs/utils/index.d.ts.map +1 -1
- package/dist/cjs/utils/index.js +1 -0
- package/dist/cjs/utils/index.js.map +1 -1
- package/dist/cjs/utils/ipfs.d.ts +33 -1
- package/dist/cjs/utils/ipfs.d.ts.map +1 -1
- package/dist/cjs/utils/ipfs.js +212 -8
- package/dist/cjs/utils/ipfs.js.map +1 -1
- package/dist/cjs/utils/ipfs.test.js +34 -0
- package/dist/cjs/utils/ipfs.test.js.map +1 -1
- package/dist/cjs/v6/cashOut.d.ts +23 -0
- package/dist/cjs/v6/cashOut.d.ts.map +1 -1
- package/dist/cjs/v6/cashOut.js +35 -0
- package/dist/cjs/v6/cashOut.js.map +1 -1
- package/dist/cjs/v6/cashOut.test.js +20 -0
- package/dist/cjs/v6/cashOut.test.js.map +1 -1
- package/dist/cjs/v6/index.d.ts +3 -0
- package/dist/cjs/v6/index.d.ts.map +1 -1
- package/dist/cjs/v6/index.js +3 -0
- package/dist/cjs/v6/index.js.map +1 -1
- package/dist/cjs/v6/launch.d.ts +23 -2
- package/dist/cjs/v6/launch.d.ts.map +1 -1
- package/dist/cjs/v6/launch.js +40 -1
- package/dist/cjs/v6/launch.js.map +1 -1
- package/dist/cjs/v6/launch.test.js +25 -0
- package/dist/cjs/v6/launch.test.js.map +1 -1
- package/dist/cjs/v6/nft.d.ts +165 -0
- package/dist/cjs/v6/nft.d.ts.map +1 -0
- package/dist/cjs/v6/nft.js +356 -0
- package/dist/cjs/v6/nft.js.map +1 -0
- package/dist/cjs/v6/nft.test.d.ts +2 -0
- package/dist/cjs/v6/nft.test.d.ts.map +1 -0
- package/dist/cjs/v6/nft.test.js +279 -0
- package/dist/cjs/v6/nft.test.js.map +1 -0
- package/dist/cjs/v6/pay.d.ts +13 -5
- package/dist/cjs/v6/pay.d.ts.map +1 -1
- package/dist/cjs/v6/pay.js +15 -18
- package/dist/cjs/v6/pay.js.map +1 -1
- package/dist/cjs/v6/pay.test.js +7 -0
- package/dist/cjs/v6/pay.test.js.map +1 -1
- package/dist/cjs/v6/permissions.d.ts +21 -0
- package/dist/cjs/v6/permissions.d.ts.map +1 -1
- package/dist/cjs/v6/permissions.js +44 -1
- package/dist/cjs/v6/permissions.js.map +1 -1
- package/dist/cjs/v6/permissions.test.js +25 -0
- package/dist/cjs/v6/permissions.test.js.map +1 -1
- package/dist/cjs/v6/projectPayers.d.ts +178 -0
- package/dist/cjs/v6/projectPayers.d.ts.map +1 -0
- package/dist/cjs/v6/projectPayers.js +108 -0
- package/dist/cjs/v6/projectPayers.js.map +1 -0
- package/dist/cjs/v6/projectPayers.test.d.ts +2 -0
- package/dist/cjs/v6/projectPayers.test.d.ts.map +1 -0
- package/dist/cjs/v6/projectPayers.test.js +85 -0
- package/dist/cjs/v6/projectPayers.test.js.map +1 -0
- package/dist/cjs/v6/revnets.d.ts +3 -2
- package/dist/cjs/v6/revnets.d.ts.map +1 -1
- package/dist/cjs/v6/revnets.js +3 -2
- package/dist/cjs/v6/revnets.js.map +1 -1
- package/dist/cjs/v6/rulesets.d.ts +42 -0
- package/dist/cjs/v6/rulesets.d.ts.map +1 -1
- package/dist/cjs/v6/rulesets.js +87 -0
- package/dist/cjs/v6/rulesets.js.map +1 -1
- package/dist/cjs/v6/rulesets.test.js +54 -0
- package/dist/cjs/v6/rulesets.test.js.map +1 -1
- package/dist/cjs/v6/suckers.d.ts +740 -2
- package/dist/cjs/v6/suckers.d.ts.map +1 -1
- package/dist/cjs/v6/suckers.js +647 -8
- package/dist/cjs/v6/suckers.js.map +1 -1
- package/dist/cjs/v6/suckers.test.js +228 -0
- package/dist/cjs/v6/suckers.test.js.map +1 -1
- package/dist/cjs/v6/uniswapV4.d.ts +82 -0
- package/dist/cjs/v6/uniswapV4.d.ts.map +1 -0
- package/dist/cjs/v6/uniswapV4.js +336 -0
- package/dist/cjs/v6/uniswapV4.js.map +1 -0
- package/dist/cjs/v6/uniswapV4.test.d.ts +2 -0
- package/dist/cjs/v6/uniswapV4.test.d.ts.map +1 -0
- package/dist/cjs/v6/uniswapV4.test.js +128 -0
- package/dist/cjs/v6/uniswapV4.test.js.map +1 -0
- package/dist/esm/utils/hook.d.ts +15 -0
- package/dist/esm/utils/hook.d.ts.map +1 -1
- package/dist/esm/utils/hook.js +20 -1
- package/dist/esm/utils/hook.js.map +1 -1
- package/dist/esm/utils/index.d.ts +1 -0
- package/dist/esm/utils/index.d.ts.map +1 -1
- package/dist/esm/utils/index.js +1 -0
- package/dist/esm/utils/index.js.map +1 -1
- package/dist/esm/utils/ipfs.d.ts +33 -1
- package/dist/esm/utils/ipfs.d.ts.map +1 -1
- package/dist/esm/utils/ipfs.js +204 -8
- package/dist/esm/utils/ipfs.js.map +1 -1
- package/dist/esm/utils/ipfs.test.js +35 -1
- package/dist/esm/utils/ipfs.test.js.map +1 -1
- package/dist/esm/v6/cashOut.d.ts +23 -0
- package/dist/esm/v6/cashOut.d.ts.map +1 -1
- package/dist/esm/v6/cashOut.js +34 -0
- package/dist/esm/v6/cashOut.js.map +1 -1
- package/dist/esm/v6/cashOut.test.js +22 -2
- package/dist/esm/v6/cashOut.test.js.map +1 -1
- package/dist/esm/v6/index.d.ts +3 -0
- package/dist/esm/v6/index.d.ts.map +1 -1
- package/dist/esm/v6/index.js +3 -0
- package/dist/esm/v6/index.js.map +1 -1
- package/dist/esm/v6/launch.d.ts +23 -2
- package/dist/esm/v6/launch.d.ts.map +1 -1
- package/dist/esm/v6/launch.js +39 -2
- package/dist/esm/v6/launch.js.map +1 -1
- package/dist/esm/v6/launch.test.js +27 -2
- package/dist/esm/v6/launch.test.js.map +1 -1
- package/dist/esm/v6/nft.d.ts +165 -0
- package/dist/esm/v6/nft.d.ts.map +1 -0
- package/dist/esm/v6/nft.js +342 -0
- package/dist/esm/v6/nft.js.map +1 -0
- package/dist/esm/v6/nft.test.d.ts +2 -0
- package/dist/esm/v6/nft.test.d.ts.map +1 -0
- package/dist/esm/v6/nft.test.js +277 -0
- package/dist/esm/v6/nft.test.js.map +1 -0
- package/dist/esm/v6/pay.d.ts +13 -5
- package/dist/esm/v6/pay.d.ts.map +1 -1
- package/dist/esm/v6/pay.js +17 -20
- package/dist/esm/v6/pay.js.map +1 -1
- package/dist/esm/v6/pay.test.js +7 -0
- package/dist/esm/v6/pay.test.js.map +1 -1
- package/dist/esm/v6/permissions.d.ts +21 -0
- package/dist/esm/v6/permissions.d.ts.map +1 -1
- package/dist/esm/v6/permissions.js +40 -0
- package/dist/esm/v6/permissions.js.map +1 -1
- package/dist/esm/v6/permissions.test.js +26 -1
- package/dist/esm/v6/permissions.test.js.map +1 -1
- package/dist/esm/v6/projectPayers.d.ts +178 -0
- package/dist/esm/v6/projectPayers.d.ts.map +1 -0
- package/dist/esm/v6/projectPayers.js +101 -0
- package/dist/esm/v6/projectPayers.js.map +1 -0
- package/dist/esm/v6/projectPayers.test.d.ts +2 -0
- package/dist/esm/v6/projectPayers.test.d.ts.map +1 -0
- package/dist/esm/v6/projectPayers.test.js +83 -0
- package/dist/esm/v6/projectPayers.test.js.map +1 -0
- package/dist/esm/v6/revnets.d.ts +3 -2
- package/dist/esm/v6/revnets.d.ts.map +1 -1
- package/dist/esm/v6/revnets.js +3 -2
- package/dist/esm/v6/revnets.js.map +1 -1
- package/dist/esm/v6/rulesets.d.ts +42 -0
- package/dist/esm/v6/rulesets.d.ts.map +1 -1
- package/dist/esm/v6/rulesets.js +83 -0
- package/dist/esm/v6/rulesets.js.map +1 -1
- package/dist/esm/v6/rulesets.test.js +55 -1
- package/dist/esm/v6/rulesets.test.js.map +1 -1
- package/dist/esm/v6/suckers.d.ts +740 -2
- package/dist/esm/v6/suckers.d.ts.map +1 -1
- package/dist/esm/v6/suckers.js +630 -8
- package/dist/esm/v6/suckers.js.map +1 -1
- package/dist/esm/v6/suckers.test.js +230 -2
- package/dist/esm/v6/suckers.test.js.map +1 -1
- package/dist/esm/v6/uniswapV4.d.ts +82 -0
- package/dist/esm/v6/uniswapV4.d.ts.map +1 -0
- package/dist/esm/v6/uniswapV4.js +318 -0
- package/dist/esm/v6/uniswapV4.js.map +1 -0
- package/dist/esm/v6/uniswapV4.test.d.ts +2 -0
- package/dist/esm/v6/uniswapV4.test.d.ts.map +1 -0
- package/dist/esm/v6/uniswapV4.test.js +126 -0
- package/dist/esm/v6/uniswapV4.test.js.map +1 -0
- package/dist/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DISCOUNT_DENOMINATOR = exports.TIER_UNLIMITED_SUPPLY = void 0;
|
|
4
|
+
exports.parseTierMetadataJson = parseTierMetadataJson;
|
|
5
|
+
exports.pickTierMetadata = pickTierMetadata;
|
|
6
|
+
exports.isSafeTierMediaUri = isSafeTierMediaUri;
|
|
7
|
+
exports.buildTierMetadata = buildTierMetadata;
|
|
8
|
+
exports.tierMediaAssetUrl = tierMediaAssetUrl;
|
|
9
|
+
exports.tierMediaImageUrl = tierMediaImageUrl;
|
|
10
|
+
exports.tierDisplayMetadata = tierDisplayMetadata;
|
|
11
|
+
exports.buildTierCategoryPlan = buildTierCategoryPlan;
|
|
12
|
+
exports.effectiveTierPrice = effectiveTierPrice;
|
|
13
|
+
exports.get721MetadataIdTarget = get721MetadataIdTarget;
|
|
14
|
+
exports.getProject721Shop = getProject721Shop;
|
|
15
|
+
const viem_1 = require("viem");
|
|
16
|
+
const contracts_js_1 = require("../contracts.js");
|
|
17
|
+
const juicebox_js_1 = require("../generated/juicebox.js");
|
|
18
|
+
const ipfs_js_1 = require("../utils/ipfs.js");
|
|
19
|
+
const revnets_js_1 = require("./revnets.js");
|
|
20
|
+
const rulesets_js_1 = require("./rulesets.js");
|
|
21
|
+
/**
|
|
22
|
+
* App-level sentinel convention used by Juicebox 721 shops for effectively
|
|
23
|
+
* unlimited inventory.
|
|
24
|
+
*/
|
|
25
|
+
exports.TIER_UNLIMITED_SUPPLY = 999_999_999;
|
|
26
|
+
/**
|
|
27
|
+
* JB721TiersHookStore's `DISCOUNT_DENOMINATOR`: a tier's `discountPercent` is
|
|
28
|
+
* out of 200, so the shopper-facing "% off" is `discountPercent / 2`.
|
|
29
|
+
*/
|
|
30
|
+
exports.DISCOUNT_DENOMINATOR = 200n;
|
|
31
|
+
function nonEmptyString(value) {
|
|
32
|
+
return typeof value === "string" && value ? value : undefined;
|
|
33
|
+
}
|
|
34
|
+
function decodeBase64Utf8(value) {
|
|
35
|
+
const binary = atob(value);
|
|
36
|
+
return new TextDecoder().decode(Uint8Array.from(binary, (character) => character.charCodeAt(0)));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Parse a `data:application/json` tier URI. Invalid, non-object, and non-data
|
|
40
|
+
* values return `null` so callers can fall back to an IPFS metadata fetch.
|
|
41
|
+
*/
|
|
42
|
+
function parseTierMetadataJson(uri) {
|
|
43
|
+
if (!uri.startsWith("data:application/json"))
|
|
44
|
+
return null;
|
|
45
|
+
const comma = uri.indexOf(",");
|
|
46
|
+
if (comma === -1)
|
|
47
|
+
return null;
|
|
48
|
+
try {
|
|
49
|
+
const header = uri.slice(0, comma);
|
|
50
|
+
const payload = uri.slice(comma + 1);
|
|
51
|
+
const json = JSON.parse(header.split(";").includes("base64")
|
|
52
|
+
? decodeBase64Utf8(payload)
|
|
53
|
+
: decodeURIComponent(payload));
|
|
54
|
+
return json !== null && typeof json === "object" && !Array.isArray(json)
|
|
55
|
+
? json
|
|
56
|
+
: null;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Normalize legacy and current tier metadata field names into one display
|
|
64
|
+
* shape. Only non-empty strings are returned.
|
|
65
|
+
*/
|
|
66
|
+
function pickTierMetadata(json) {
|
|
67
|
+
return {
|
|
68
|
+
name: nonEmptyString(json.productName) ?? nonEmptyString(json.name),
|
|
69
|
+
description: nonEmptyString(json.productDescription) ??
|
|
70
|
+
nonEmptyString(json.description),
|
|
71
|
+
image: nonEmptyString(json.image) ?? nonEmptyString(json.imageUri),
|
|
72
|
+
animationUrl: nonEmptyString(json.animation_url) ?? nonEmptyString(json.animationUrl),
|
|
73
|
+
mediaType: nonEmptyString(json.mediaType) ??
|
|
74
|
+
nonEmptyString(json.animationType) ??
|
|
75
|
+
nonEmptyString(json.mimeType),
|
|
76
|
+
categoryName: nonEmptyString(json.categoryName),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/** Whether a tier media value uses an immutable or browser-loadable URI form. */
|
|
80
|
+
function isSafeTierMediaUri(value) {
|
|
81
|
+
const uri = value.trim();
|
|
82
|
+
if (!uri || /\s/.test(uri))
|
|
83
|
+
return false;
|
|
84
|
+
const ipfsPath = (0, ipfs_js_1.ipfsAssetPath)(uri);
|
|
85
|
+
if (ipfsPath && (0, ipfs_js_1.isIpfsCid)(ipfsPath.split("/")[0]))
|
|
86
|
+
return true;
|
|
87
|
+
if ((0, ipfs_js_1.isIpfsCid)(uri.split("/")[0]))
|
|
88
|
+
return true;
|
|
89
|
+
if (/^data:(image|audio|video)\/[a-z0-9.+-]+[;,]/i.test(uri))
|
|
90
|
+
return true;
|
|
91
|
+
try {
|
|
92
|
+
const url = new URL(uri);
|
|
93
|
+
return url.protocol === "https:" || url.protocol === "http:";
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Build the canonical JSON object pinned for one tier. */
|
|
100
|
+
function buildTierMetadata(input) {
|
|
101
|
+
const name = input.name.trim();
|
|
102
|
+
if (!name)
|
|
103
|
+
throw new Error("Tier name is required.");
|
|
104
|
+
const optional = (value) => value?.trim() || undefined;
|
|
105
|
+
const image = optional(input.image);
|
|
106
|
+
const animationUrl = optional(input.animationUrl);
|
|
107
|
+
const mediaType = optional(input.mediaType);
|
|
108
|
+
if (image && !isSafeTierMediaUri(image)) {
|
|
109
|
+
throw new Error("Tier images must use IPFS, HTTP, HTTPS, or an image data URI.");
|
|
110
|
+
}
|
|
111
|
+
if (animationUrl && !isSafeTierMediaUri(animationUrl)) {
|
|
112
|
+
throw new Error("Tier animations must use IPFS, HTTP, HTTPS, or a media data URI.");
|
|
113
|
+
}
|
|
114
|
+
if (mediaType &&
|
|
115
|
+
!/^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/i.test(mediaType)) {
|
|
116
|
+
throw new Error("Tier media type must be a valid MIME type.");
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
name,
|
|
120
|
+
description: optional(input.description),
|
|
121
|
+
image,
|
|
122
|
+
animation_url: animationUrl,
|
|
123
|
+
mediaType,
|
|
124
|
+
categoryName: optional(input.categoryName),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Route IPFS media through a caller-selected gateway base URL. Data URIs and
|
|
129
|
+
* ordinary HTTP URLs pass through unchanged.
|
|
130
|
+
*/
|
|
131
|
+
function tierMediaAssetUrl(value, ipfsGatewayBaseUrl = "https://ipfs.io/ipfs/") {
|
|
132
|
+
if (typeof value !== "string" || !value)
|
|
133
|
+
return undefined;
|
|
134
|
+
if (value.startsWith("data:"))
|
|
135
|
+
return value;
|
|
136
|
+
const trimmed = value.trim();
|
|
137
|
+
const path = (0, ipfs_js_1.ipfsAssetPath)(trimmed) ??
|
|
138
|
+
((0, ipfs_js_1.isIpfsCid)(trimmed.split("/")[0]) ? trimmed : null);
|
|
139
|
+
if (!path)
|
|
140
|
+
return value;
|
|
141
|
+
return `${ipfsGatewayBaseUrl.replace(/\/?$/, "/")}${path}`;
|
|
142
|
+
}
|
|
143
|
+
function svgDataUriMarkup(uri) {
|
|
144
|
+
const match = /^data:image\/svg\+xml([^,]*),([\s\S]*)$/.exec(uri);
|
|
145
|
+
if (!match)
|
|
146
|
+
return null;
|
|
147
|
+
try {
|
|
148
|
+
return match[1].split(";").includes("base64")
|
|
149
|
+
? decodeBase64Utf8(match[2])
|
|
150
|
+
: decodeURIComponent(match[2]);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Return a browser-renderable tier image URL. SVG data URIs that only wrap an
|
|
158
|
+
* external `<image href>` are unwrapped because browsers block that nested
|
|
159
|
+
* network request when the SVG itself is used as an `<img>` source.
|
|
160
|
+
*/
|
|
161
|
+
function tierMediaImageUrl(image, ipfsGatewayBaseUrl) {
|
|
162
|
+
if (typeof image !== "string" || !image)
|
|
163
|
+
return undefined;
|
|
164
|
+
const markup = svgDataUriMarkup(image);
|
|
165
|
+
if (markup) {
|
|
166
|
+
const href = /<image[^>]+href=["']([^"']+)["']/.exec(markup)?.[1];
|
|
167
|
+
if (href && !href.startsWith("data:")) {
|
|
168
|
+
return tierMediaAssetUrl(href, ipfsGatewayBaseUrl);
|
|
169
|
+
}
|
|
170
|
+
return image;
|
|
171
|
+
}
|
|
172
|
+
return tierMediaAssetUrl(image, ipfsGatewayBaseUrl);
|
|
173
|
+
}
|
|
174
|
+
/** Pick and resolve all tier metadata fields in one pass. */
|
|
175
|
+
function tierDisplayMetadata(json, ipfsGatewayBaseUrl) {
|
|
176
|
+
const metadata = pickTierMetadata(json);
|
|
177
|
+
return {
|
|
178
|
+
...metadata,
|
|
179
|
+
image: tierMediaImageUrl(metadata.image, ipfsGatewayBaseUrl),
|
|
180
|
+
animationUrl: tierMediaAssetUrl(metadata.animationUrl, ipfsGatewayBaseUrl),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/** Stable first-seen category ids, with blank categories assigned to 0. */
|
|
184
|
+
function buildTierCategoryPlan(tiers) {
|
|
185
|
+
const categoryIds = new Map();
|
|
186
|
+
const categoryByTierId = {};
|
|
187
|
+
const storeCategories = {};
|
|
188
|
+
for (const tier of tiers) {
|
|
189
|
+
const name = tier.categoryName?.trim() || "";
|
|
190
|
+
if (!name) {
|
|
191
|
+
categoryByTierId[tier.id] = 0;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
let category = categoryIds.get(name);
|
|
195
|
+
if (category === undefined) {
|
|
196
|
+
category = categoryIds.size + 1;
|
|
197
|
+
categoryIds.set(name, category);
|
|
198
|
+
storeCategories[String(category)] = name;
|
|
199
|
+
}
|
|
200
|
+
categoryByTierId[tier.id] = category;
|
|
201
|
+
}
|
|
202
|
+
return { categoryByTierId, storeCategories };
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* A tier's effective (discounted) price, matching the on-chain formula in
|
|
206
|
+
* JB721TiersHookStore: `price - mulDiv(price, discountPercent, 200)` with
|
|
207
|
+
* integer-floor division.
|
|
208
|
+
*
|
|
209
|
+
* @param price The tier's full (undiscounted) price.
|
|
210
|
+
* @param discountPercent The tier's discount, out of 200 (`DISCOUNT_DENOMINATOR`).
|
|
211
|
+
*/
|
|
212
|
+
function effectiveTierPrice(price, discountPercent) {
|
|
213
|
+
let discount = BigInt(discountPercent);
|
|
214
|
+
if (discount <= 0n)
|
|
215
|
+
return price;
|
|
216
|
+
if (discount > exports.DISCOUNT_DENOMINATOR)
|
|
217
|
+
discount = exports.DISCOUNT_DENOMINATOR;
|
|
218
|
+
return price - (price * discount) / exports.DISCOUNT_DENOMINATOR;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Read a JB721 hook's `METADATA_ID_TARGET` — the shared *implementation* address
|
|
222
|
+
* that pay/cash-out metadata ids must key off (a delegatecall clone reports the
|
|
223
|
+
* implementation, not itself). Falls back to `hook` if the read reverts (e.g. an
|
|
224
|
+
* older hook without the getter), which is only correct for a non-clone hook.
|
|
225
|
+
*
|
|
226
|
+
* @param client A viem public client on the given chain.
|
|
227
|
+
* @param args.hook The project's 721 tiers hook (clone) address.
|
|
228
|
+
*/
|
|
229
|
+
async function get721MetadataIdTarget(client, { hook }) {
|
|
230
|
+
return client
|
|
231
|
+
.readContract({
|
|
232
|
+
address: hook,
|
|
233
|
+
abi: juicebox_js_1.jb721TiersHookAbi,
|
|
234
|
+
functionName: "METADATA_ID_TARGET",
|
|
235
|
+
})
|
|
236
|
+
.catch(() => hook);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Resolve a project's 721 tiers hook, its store, metadata id target, pricing
|
|
240
|
+
* context, and tiers in one call.
|
|
241
|
+
*
|
|
242
|
+
* Hook resolution mirrors the app patterns: revnets read
|
|
243
|
+
* `REVOwner.tiered721HookOf`; custom projects read the current ruleset's pay
|
|
244
|
+
* data hook — either the omnichain deployer (the real hook lives in
|
|
245
|
+
* `JBOmnichainDeployer.tiered721HookOf(projectId, rulesetId)`) or the 721 hook
|
|
246
|
+
* itself, verified by probing `STORE()` (a revert means it is not a 721 hook, so
|
|
247
|
+
* the project has no shop).
|
|
248
|
+
*
|
|
249
|
+
* Returns `null` when the project authoritatively has no 721 shop. RPC failures
|
|
250
|
+
* on authoritative reads throw, so a transient error surfaces as an error rather
|
|
251
|
+
* than a false "no shop".
|
|
252
|
+
*
|
|
253
|
+
* @param client A viem public client on the given chain.
|
|
254
|
+
* @param args.projectId The project (or revnet) id.
|
|
255
|
+
* @param args.isRevnet Whether the project is a revnet (changes hook resolution).
|
|
256
|
+
* @param args.tierLimit Max tiers to read from `tiersOf`. Defaults to 100.
|
|
257
|
+
* @param args.categories Tier categories to filter by (empty = all). Defaults to `[]`.
|
|
258
|
+
*/
|
|
259
|
+
async function getProject721Shop(client, { chainId, projectId, isRevnet, tierLimit = 100, categories = [], }) {
|
|
260
|
+
// 1. Resolve the 721 hook.
|
|
261
|
+
let hook = null;
|
|
262
|
+
// A non-authoritative candidate is the ruleset data hook, which might be some
|
|
263
|
+
// other kind of hook; the STORE() probe below decides.
|
|
264
|
+
let authoritative = true;
|
|
265
|
+
if (isRevnet) {
|
|
266
|
+
const revnetHook = await (0, revnets_js_1.getRevnetTiered721Hook)(client, {
|
|
267
|
+
chainId,
|
|
268
|
+
revnetId: projectId,
|
|
269
|
+
});
|
|
270
|
+
hook = revnetHook !== viem_1.zeroAddress ? revnetHook : null;
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
const { ruleset, metadata } = await (0, rulesets_js_1.getCurrentRuleset)(client, {
|
|
274
|
+
chainId,
|
|
275
|
+
projectId,
|
|
276
|
+
});
|
|
277
|
+
const dataHook = metadata.dataHook;
|
|
278
|
+
if (metadata.useDataHookForPay && dataHook && dataHook !== viem_1.zeroAddress) {
|
|
279
|
+
const omni = juicebox_js_1.jbContractAddress["6"][contracts_js_1.JBOmnichainDeployerContracts.JBOmnichainDeployer]?.[chainId];
|
|
280
|
+
if (omni && dataHook.toLowerCase() === omni.toLowerCase()) {
|
|
281
|
+
// Omnichain project: the real 721 hook lives in the deployer's
|
|
282
|
+
// per-ruleset mapping.
|
|
283
|
+
const [omniHook] = await client.readContract({
|
|
284
|
+
address: omni,
|
|
285
|
+
abi: juicebox_js_1.jbOmnichainDeployerAbi,
|
|
286
|
+
functionName: "tiered721HookOf",
|
|
287
|
+
args: [projectId, BigInt(ruleset.id)],
|
|
288
|
+
});
|
|
289
|
+
hook = omniHook !== viem_1.zeroAddress ? omniHook : null;
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
// Single-chain custom project: the data hook may be the 721 hook itself.
|
|
293
|
+
hook = dataHook;
|
|
294
|
+
authoritative = false;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (!hook)
|
|
299
|
+
return null;
|
|
300
|
+
// 2. The hook's store. For a non-authoritative candidate a revert means "not a
|
|
301
|
+
// 721 hook" (no shop); for an authoritative one, let failures throw.
|
|
302
|
+
const readStore = () => client.readContract({
|
|
303
|
+
address: hook,
|
|
304
|
+
abi: juicebox_js_1.jb721TiersHookAbi,
|
|
305
|
+
functionName: "STORE",
|
|
306
|
+
});
|
|
307
|
+
const store = authoritative
|
|
308
|
+
? await readStore()
|
|
309
|
+
: await readStore().catch(() => null);
|
|
310
|
+
if (!store)
|
|
311
|
+
return null;
|
|
312
|
+
// 3. Metadata id target + pricing context. Pricing is meaningless without the
|
|
313
|
+
// hook's exact currency + decimals, so a failure here is an error, not a
|
|
314
|
+
// fallback.
|
|
315
|
+
const [metadataIdTarget, pricingRaw] = await Promise.all([
|
|
316
|
+
get721MetadataIdTarget(client, { chainId, hook }),
|
|
317
|
+
client.readContract({
|
|
318
|
+
address: hook,
|
|
319
|
+
abi: juicebox_js_1.jb721TiersHookAbi,
|
|
320
|
+
functionName: "pricingContext",
|
|
321
|
+
}),
|
|
322
|
+
]);
|
|
323
|
+
// 4. The tiers, with resolver URIs included so callers can name tiers without
|
|
324
|
+
// a per-tier resolver read.
|
|
325
|
+
const raw = await client.readContract({
|
|
326
|
+
address: store,
|
|
327
|
+
abi: juicebox_js_1.jb721TiersHookStoreAbi,
|
|
328
|
+
functionName: "tiersOf",
|
|
329
|
+
args: [hook, categories, true, 0n, BigInt(tierLimit)],
|
|
330
|
+
});
|
|
331
|
+
const tiers = raw
|
|
332
|
+
.filter((tier) => tier.initialSupply > 0)
|
|
333
|
+
.map((tier) => ({
|
|
334
|
+
id: tier.id,
|
|
335
|
+
price: tier.price,
|
|
336
|
+
remainingSupply: tier.remainingSupply,
|
|
337
|
+
initialSupply: tier.initialSupply,
|
|
338
|
+
votingUnits: tier.votingUnits,
|
|
339
|
+
reserveFrequency: tier.reserveFrequency,
|
|
340
|
+
category: tier.category,
|
|
341
|
+
discountPercent: tier.discountPercent,
|
|
342
|
+
encodedIpfsUri: tier.encodedIpfsUri,
|
|
343
|
+
resolvedUri: tier.resolvedUri ?? "",
|
|
344
|
+
}));
|
|
345
|
+
return {
|
|
346
|
+
hook,
|
|
347
|
+
store,
|
|
348
|
+
metadataIdTarget,
|
|
349
|
+
pricing: {
|
|
350
|
+
currency: Number(pricingRaw[0]),
|
|
351
|
+
decimals: Number(pricingRaw[1]),
|
|
352
|
+
},
|
|
353
|
+
tiers,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
//# sourceMappingURL=nft.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nft.js","sourceRoot":"","sources":["../../../src/v6/nft.ts"],"names":[],"mappings":";;;AAsEA,sDAqBC;AAMD,4CAeC;AAGD,gDAeC;AAGD,8CAmCC;AAMD,8CAYC;AAmBD,8CAcC;AAGD,kDAUC;AAGD,sDA0BC;AAUD,gDAQC;AAkDD,wDAWC;AAuBD,8CAoHC;AA/dD,+BAA+D;AAC/D,kDAA+D;AAC/D,0DAKkC;AAElC,8CAA4D;AAC5D,6CAAsD;AACtD,+CAAkD;AAElD;;;GAGG;AACU,QAAA,qBAAqB,GAAG,WAAW,CAAC;AAEjD;;;GAGG;AACU,QAAA,oBAAoB,GAAG,IAAI,CAAC;AAgCzC,SAAS,cAAc,CAAC,KAAc;IACpC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAChE,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAa;IACrC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3B,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAC7B,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAChE,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAgB,qBAAqB,CACnC,GAAW;IAEX,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,uBAAuB,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1D,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAE9B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QACnC,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CACrB,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAClC,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC;YAC3B,CAAC,CAAC,kBAAkB,CAAC,OAAO,CAAC,CACrB,CAAC;QACb,OAAO,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;YACtE,CAAC,CAAE,IAAgC;YACnC,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAgB,gBAAgB,CAAC,IAA6B;IAC5D,OAAO;QACL,IAAI,EAAE,cAAc,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;QACnE,WAAW,EACT,cAAc,CAAC,IAAI,CAAC,kBAAkB,CAAC;YACvC,cAAc,CAAC,IAAI,CAAC,WAAW,CAAC;QAClC,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC;QAClE,YAAY,EACV,cAAc,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,YAAY,CAAC;QACzE,SAAS,EACP,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC;YAC9B,cAAc,CAAC,IAAI,CAAC,aAAa,CAAC;YAClC,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC;QAC/B,YAAY,EAAE,cAAc,CAAC,IAAI,CAAC,YAAY,CAAC;KAChD,CAAC;AACJ,CAAC;AAED,iFAAiF;AACjF,SAAgB,kBAAkB,CAAC,KAAa;IAC9C,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IACzB,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAEzC,MAAM,QAAQ,GAAG,IAAA,uBAAa,EAAC,GAAG,CAAC,CAAC;IACpC,IAAI,QAAQ,IAAI,IAAA,mBAAS,EAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/D,IAAI,IAAA,mBAAS,EAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9C,IAAI,8CAA8C,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAE1E,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACzB,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,2DAA2D;AAC3D,SAAgB,iBAAiB,CAC/B,KAAwB;IAExB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IAC/B,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;IAErD,MAAM,QAAQ,GAAG,CAAC,KAAyB,EAAE,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,SAAS,CAAC;IAC3E,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC5C,IAAI,KAAK,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CACb,+DAA+D,CAChE,CAAC;IACJ,CAAC;IACD,IAAI,YAAY,IAAI,CAAC,kBAAkB,CAAC,YAAY,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CACb,kEAAkE,CACnE,CAAC;IACJ,CAAC;IACD,IACE,SAAS;QACT,CAAC,2CAA2C,CAAC,IAAI,CAAC,SAAS,CAAC,EAC5D,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IAED,OAAO;QACL,IAAI;QACJ,WAAW,EAAE,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC;QACxC,KAAK;QACL,aAAa,EAAE,YAAY;QAC3B,SAAS;QACT,YAAY,EAAE,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC;KAC3C,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAgB,iBAAiB,CAC/B,KAAc,EACd,kBAAkB,GAAG,uBAAuB;IAE5C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC1D,IAAI,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,MAAM,IAAI,GACR,IAAA,uBAAa,EAAC,OAAO,CAAC;QACtB,CAAC,IAAA,mBAAS,EAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACtD,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,OAAO,GAAG,kBAAkB,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC;AAC7D,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAW;IACnC,MAAM,KAAK,GAAG,yCAAyC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClE,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,CAAC;QACH,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAC3C,CAAC,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC5B,CAAC,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAgB,iBAAiB,CAC/B,KAAc,EACd,kBAA2B;IAE3B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC1D,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,IAAI,GAAG,kCAAkC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAClE,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACtC,OAAO,iBAAiB,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;QACrD,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,iBAAiB,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC;AACtD,CAAC;AAED,6DAA6D;AAC7D,SAAgB,mBAAmB,CACjC,IAA6B,EAC7B,kBAA2B;IAE3B,MAAM,QAAQ,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACxC,OAAO;QACL,GAAG,QAAQ;QACX,KAAK,EAAE,iBAAiB,CAAC,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;QAC5D,YAAY,EAAE,iBAAiB,CAAC,QAAQ,CAAC,YAAY,EAAE,kBAAkB,CAAC;KAC3E,CAAC;AACJ,CAAC;AAED,2EAA2E;AAC3E,SAAgB,qBAAqB,CACnC,KAA2D;IAK3D,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC9C,MAAM,gBAAgB,GAA2B,EAAE,CAAC;IACpD,MAAM,eAAe,GAA2B,EAAE,CAAC;IAEnD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QAC7C,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;YAC9B,SAAS;QACX,CAAC;QACD,IAAI,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,QAAQ,GAAG,WAAW,CAAC,IAAI,GAAG,CAAC,CAAC;YAChC,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YAChC,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC;QAC3C,CAAC;QACD,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC;IACvC,CAAC;IAED,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,CAAC;AAC/C,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,kBAAkB,CAChC,KAAa,EACb,eAAgC;IAEhC,IAAI,QAAQ,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC;IACvC,IAAI,QAAQ,IAAI,EAAE;QAAE,OAAO,KAAK,CAAC;IACjC,IAAI,QAAQ,GAAG,4BAAoB;QAAE,QAAQ,GAAG,4BAAoB,CAAC;IACrE,OAAO,KAAK,GAAG,CAAC,KAAK,GAAG,QAAQ,CAAC,GAAG,4BAAoB,CAAC;AAC3D,CAAC;AAyCD;;;;;;;;GAQG;AACI,KAAK,UAAU,sBAAsB,CAC1C,MAAoB,EACpB,EAAE,IAAI,EAAyC;IAE/C,OAAO,MAAM;SACV,YAAY,CAAC;QACZ,OAAO,EAAE,IAAI;QACb,GAAG,EAAE,+BAAiB;QACtB,YAAY,EAAE,oBAAoB;KACnC,CAAC;SACD,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;AACvB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACI,KAAK,UAAU,iBAAiB,CACrC,MAAoB,EACpB,EACE,OAAO,EACP,SAAS,EACT,QAAQ,EACR,SAAS,GAAG,GAAG,EACf,UAAU,GAAG,EAAE,GAOhB;IAED,2BAA2B;IAC3B,IAAI,IAAI,GAAmB,IAAI,CAAC;IAChC,8EAA8E;IAC9E,uDAAuD;IACvD,IAAI,aAAa,GAAG,IAAI,CAAC;IAEzB,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,UAAU,GAAG,MAAM,IAAA,mCAAsB,EAAC,MAAM,EAAE;YACtD,OAAO;YACP,QAAQ,EAAE,SAAS;SACpB,CAAC,CAAC;QACH,IAAI,GAAG,UAAU,KAAK,kBAAW,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;IACxD,CAAC;SAAM,CAAC;QACN,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,IAAA,+BAAiB,EAAC,MAAM,EAAE;YAC5D,OAAO;YACP,SAAS;SACV,CAAC,CAAC;QACH,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAmB,CAAC;QAC9C,IAAI,QAAQ,CAAC,iBAAiB,IAAI,QAAQ,IAAI,QAAQ,KAAK,kBAAW,EAAE,CAAC;YACvE,MAAM,IAAI,GAAG,+BAAiB,CAAC,GAAG,CAAC,CACjC,2CAA4B,CAAC,mBAAmB,CACjD,EAAE,CAAC,OAAO,CAAwB,CAAC;YACpC,IAAI,IAAI,IAAI,QAAQ,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;gBAC1D,+DAA+D;gBAC/D,uBAAuB;gBACvB,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC;oBAC3C,OAAO,EAAE,IAAI;oBACb,GAAG,EAAE,oCAAsB;oBAC3B,YAAY,EAAE,iBAAiB;oBAC/B,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;iBACtC,CAAC,CAAC;gBACH,IAAI,GAAG,QAAQ,KAAK,kBAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;YACpD,CAAC;iBAAM,CAAC;gBACN,yEAAyE;gBACzE,IAAI,GAAG,QAAQ,CAAC;gBAChB,aAAa,GAAG,KAAK,CAAC;YACxB,CAAC;QACH,CAAC;IACH,CAAC;IACD,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IAEvB,+EAA+E;IAC/E,qEAAqE;IACrE,MAAM,SAAS,GAAG,GAAG,EAAE,CACrB,MAAM,CAAC,YAAY,CAAC;QAClB,OAAO,EAAE,IAAK;QACd,GAAG,EAAE,+BAAiB;QACtB,YAAY,EAAE,OAAO;KACtB,CAAC,CAAC;IACL,MAAM,KAAK,GAAG,aAAa;QACzB,CAAC,CAAC,MAAM,SAAS,EAAE;QACnB,CAAC,CAAC,MAAM,SAAS,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,8EAA8E;IAC9E,yEAAyE;IACzE,YAAY;IACZ,MAAM,CAAC,gBAAgB,EAAE,UAAU,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACvD,sBAAsB,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACjD,MAAM,CAAC,YAAY,CAAC;YAClB,OAAO,EAAE,IAAI;YACb,GAAG,EAAE,+BAAiB;YACtB,YAAY,EAAE,gBAAgB;SAC/B,CAAC;KACH,CAAC,CAAC;IAEH,8EAA8E;IAC9E,4BAA4B;IAC5B,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC;QACpC,OAAO,EAAE,KAAK;QACd,GAAG,EAAE,oCAAsB;QAC3B,YAAY,EAAE,SAAS;QACvB,IAAI,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;KACtD,CAAC,CAAC;IAEH,MAAM,KAAK,GAAqB,GAAG;SAChC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC;SACxC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACd,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,eAAe,EAAE,IAAI,CAAC,eAAe;QACrC,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;QACvC,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,eAAe,EAAE,IAAI,CAAC,eAAe;QACrC,cAAc,EAAE,IAAI,CAAC,cAAc;QACnC,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,EAAE;KACpC,CAAC,CAAC,CAAC;IAEN,OAAO;QACL,IAAI;QACJ,KAAK;QACL,gBAAgB;QAChB,OAAO,EAAE;YACP,QAAQ,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;YAC/B,QAAQ,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;SAChC;QACD,KAAK;KACN,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nft.test.d.ts","sourceRoot":"","sources":["../../../src/v6/nft.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const viem_1 = require("viem");
|
|
4
|
+
const vitest_1 = require("vitest");
|
|
5
|
+
const contracts_js_1 = require("../contracts.js");
|
|
6
|
+
const juicebox_js_1 = require("../generated/juicebox.js");
|
|
7
|
+
const nft_js_1 = require("./nft.js");
|
|
8
|
+
const chainId = 11155111;
|
|
9
|
+
const hook = "0x1111111111111111111111111111111111111111";
|
|
10
|
+
const impl = "0x2222222222222222222222222222222222222222";
|
|
11
|
+
const store = "0x3333333333333333333333333333333333333333";
|
|
12
|
+
const CID = "QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR";
|
|
13
|
+
(0, vitest_1.describe)("tier metadata", () => {
|
|
14
|
+
(0, vitest_1.test)("parses encoded and base64 JSON data URIs", () => {
|
|
15
|
+
(0, vitest_1.expect)((0, nft_js_1.parseTierMetadataJson)(`data:application/json,${encodeURIComponent(JSON.stringify({ name: "Café" }))}`)).toEqual({ name: "Café" });
|
|
16
|
+
(0, vitest_1.expect)((0, nft_js_1.parseTierMetadataJson)("data:application/json;base64,eyJwcm9kdWN0TmFtZSI6IkJhbm55In0=")).toEqual({ productName: "Banny" });
|
|
17
|
+
(0, vitest_1.expect)((0, nft_js_1.parseTierMetadataJson)("https://example.com/tier.json")).toBeNull();
|
|
18
|
+
(0, vitest_1.expect)((0, nft_js_1.parseTierMetadataJson)("data:application/json,%7Bbad")).toBeNull();
|
|
19
|
+
});
|
|
20
|
+
(0, vitest_1.test)("normalizes legacy metadata aliases", () => {
|
|
21
|
+
(0, vitest_1.expect)((0, nft_js_1.pickTierMetadata)({
|
|
22
|
+
productName: "Legacy name",
|
|
23
|
+
name: "Current name",
|
|
24
|
+
productDescription: "Legacy description",
|
|
25
|
+
imageUri: `ipfs://${CID}/image.png`,
|
|
26
|
+
animation_url: "https://example.com/animation.mp4",
|
|
27
|
+
mimeType: "video/mp4",
|
|
28
|
+
categoryName: "Wearables",
|
|
29
|
+
})).toEqual({
|
|
30
|
+
name: "Legacy name",
|
|
31
|
+
description: "Legacy description",
|
|
32
|
+
image: `ipfs://${CID}/image.png`,
|
|
33
|
+
animationUrl: "https://example.com/animation.mp4",
|
|
34
|
+
mediaType: "video/mp4",
|
|
35
|
+
categoryName: "Wearables",
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
(0, vitest_1.test)("builds a canonical payload and validates media", () => {
|
|
39
|
+
(0, vitest_1.expect)((0, nft_js_1.buildTierMetadata)({
|
|
40
|
+
name: " Poster ",
|
|
41
|
+
description: " ",
|
|
42
|
+
image: `ipfs://${CID}/poster.png`,
|
|
43
|
+
animationUrl: "https://example.com/poster.mp4",
|
|
44
|
+
mediaType: "video/mp4",
|
|
45
|
+
})).toEqual({
|
|
46
|
+
name: "Poster",
|
|
47
|
+
description: undefined,
|
|
48
|
+
image: `ipfs://${CID}/poster.png`,
|
|
49
|
+
animation_url: "https://example.com/poster.mp4",
|
|
50
|
+
mediaType: "video/mp4",
|
|
51
|
+
categoryName: undefined,
|
|
52
|
+
});
|
|
53
|
+
(0, vitest_1.expect)((0, nft_js_1.isSafeTierMediaUri)(`${CID}/poster.png`)).toBe(true);
|
|
54
|
+
(0, vitest_1.expect)(() => (0, nft_js_1.buildTierMetadata)({ name: " " })).toThrow(/name/);
|
|
55
|
+
(0, vitest_1.expect)(() => (0, nft_js_1.buildTierMetadata)({ name: "Poster", image: "javascript:alert(1)" })).toThrow(/images/);
|
|
56
|
+
(0, vitest_1.expect)(() => (0, nft_js_1.buildTierMetadata)({ name: "Poster", mediaType: "not mime" })).toThrow(/MIME/);
|
|
57
|
+
});
|
|
58
|
+
(0, vitest_1.test)("routes IPFS media and unwraps external images in SVG data URIs", () => {
|
|
59
|
+
(0, vitest_1.expect)((0, nft_js_1.tierDisplayMetadata)({
|
|
60
|
+
image: `ipfs://${CID}/image.png`,
|
|
61
|
+
animation_url: `${CID}/animation.mp4`,
|
|
62
|
+
}, "/api/ipfs")).toMatchObject({
|
|
63
|
+
image: `/api/ipfs/${CID}/image.png`,
|
|
64
|
+
animationUrl: `/api/ipfs/${CID}/animation.mp4`,
|
|
65
|
+
});
|
|
66
|
+
const svg = `<svg><image xlink:href="ipfs://${CID}/wrapped.png" /></svg>`;
|
|
67
|
+
(0, vitest_1.expect)((0, nft_js_1.tierMediaImageUrl)(`data:image/svg+xml,${encodeURIComponent(svg)}`, "/api/ipfs")).toBe(`/api/ipfs/${CID}/wrapped.png`);
|
|
68
|
+
});
|
|
69
|
+
(0, vitest_1.test)("assigns stable first-seen category ids", () => {
|
|
70
|
+
(0, vitest_1.expect)((0, nft_js_1.buildTierCategoryPlan)([
|
|
71
|
+
{ id: "one", categoryName: " Posters " },
|
|
72
|
+
{ id: "two", categoryName: "" },
|
|
73
|
+
{ id: "three", categoryName: "Posters" },
|
|
74
|
+
{ id: "four", categoryName: "Music" },
|
|
75
|
+
])).toEqual({
|
|
76
|
+
categoryByTierId: { one: 1, two: 0, three: 1, four: 2 },
|
|
77
|
+
storeCategories: { "1": "Posters", "2": "Music" },
|
|
78
|
+
});
|
|
79
|
+
(0, vitest_1.expect)(nft_js_1.TIER_UNLIMITED_SUPPLY).toBe(999_999_999);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
(0, vitest_1.describe)("effectiveTierPrice", () => {
|
|
83
|
+
(0, vitest_1.test)("floors like on-chain mulDiv(price, discountPercent, 200)", () => {
|
|
84
|
+
// 100 - floor(100 * 25 / 200) = 100 - 12 = 88.
|
|
85
|
+
(0, vitest_1.expect)((0, nft_js_1.effectiveTierPrice)(100n, 25)).toEqual(88n);
|
|
86
|
+
});
|
|
87
|
+
(0, vitest_1.test)("no discount returns the full price", () => {
|
|
88
|
+
(0, vitest_1.expect)((0, nft_js_1.effectiveTierPrice)(1000n, 0)).toEqual(1000n);
|
|
89
|
+
});
|
|
90
|
+
(0, vitest_1.test)("full discount (denominator) is free", () => {
|
|
91
|
+
(0, vitest_1.expect)((0, nft_js_1.effectiveTierPrice)(1000n, Number(nft_js_1.DISCOUNT_DENOMINATOR))).toEqual(0n);
|
|
92
|
+
});
|
|
93
|
+
(0, vitest_1.test)("clamps discounts above the denominator to free", () => {
|
|
94
|
+
(0, vitest_1.expect)((0, nft_js_1.effectiveTierPrice)(1000n, 500)).toEqual(0n);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
(0, vitest_1.describe)("get721MetadataIdTarget", () => {
|
|
98
|
+
(0, vitest_1.test)("returns METADATA_ID_TARGET (the implementation, not the clone)", async () => {
|
|
99
|
+
const client = {
|
|
100
|
+
readContract: async () => impl,
|
|
101
|
+
};
|
|
102
|
+
(0, vitest_1.expect)(await (0, nft_js_1.get721MetadataIdTarget)(client, { chainId, hook })).toEqual(impl);
|
|
103
|
+
});
|
|
104
|
+
(0, vitest_1.test)("falls back to the hook address if the getter reverts", async () => {
|
|
105
|
+
const client = {
|
|
106
|
+
readContract: async () => {
|
|
107
|
+
throw new Error("no such function");
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
(0, vitest_1.expect)(await (0, nft_js_1.get721MetadataIdTarget)(client, { chainId, hook })).toEqual(hook);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
(0, vitest_1.describe)("getProject721Shop", () => {
|
|
114
|
+
(0, vitest_1.test)("returns null when a revnet has no 721 hook", async () => {
|
|
115
|
+
const client = {
|
|
116
|
+
// getRevnetTiered721Hook -> zero address.
|
|
117
|
+
readContract: async () => viem_1.zeroAddress,
|
|
118
|
+
};
|
|
119
|
+
(0, vitest_1.expect)(await (0, nft_js_1.getProject721Shop)(client, {
|
|
120
|
+
chainId,
|
|
121
|
+
projectId: 1n,
|
|
122
|
+
isRevnet: true,
|
|
123
|
+
})).toBeNull();
|
|
124
|
+
});
|
|
125
|
+
(0, vitest_1.test)("resolves a revnet shop: hook, store, target, pricing, tiers", async () => {
|
|
126
|
+
const client = {
|
|
127
|
+
readContract: async ({ address, functionName, }) => {
|
|
128
|
+
if (functionName === "tiered721HookOf")
|
|
129
|
+
return hook; // REVOwner
|
|
130
|
+
if (address === hook && functionName === "STORE")
|
|
131
|
+
return store;
|
|
132
|
+
if (functionName === "METADATA_ID_TARGET")
|
|
133
|
+
return impl;
|
|
134
|
+
if (functionName === "pricingContext")
|
|
135
|
+
return [61166n, 18n];
|
|
136
|
+
if (functionName === "tiersOf") {
|
|
137
|
+
return [
|
|
138
|
+
{
|
|
139
|
+
id: 1,
|
|
140
|
+
price: 1000n,
|
|
141
|
+
remainingSupply: 5,
|
|
142
|
+
initialSupply: 10,
|
|
143
|
+
votingUnits: 0n,
|
|
144
|
+
reserveFrequency: 0,
|
|
145
|
+
reserveBeneficiary: viem_1.zeroAddress,
|
|
146
|
+
encodedIpfsUri: `0x${"11".repeat(32)}`,
|
|
147
|
+
category: 0,
|
|
148
|
+
discountPercent: 0,
|
|
149
|
+
flags: {},
|
|
150
|
+
splitPercent: 0,
|
|
151
|
+
resolvedUri: "",
|
|
152
|
+
},
|
|
153
|
+
// Filtered out: initialSupply 0.
|
|
154
|
+
{
|
|
155
|
+
id: 2,
|
|
156
|
+
price: 0n,
|
|
157
|
+
remainingSupply: 0,
|
|
158
|
+
initialSupply: 0,
|
|
159
|
+
votingUnits: 0n,
|
|
160
|
+
reserveFrequency: 0,
|
|
161
|
+
reserveBeneficiary: viem_1.zeroAddress,
|
|
162
|
+
encodedIpfsUri: `0x${"00".repeat(32)}`,
|
|
163
|
+
category: 0,
|
|
164
|
+
discountPercent: 0,
|
|
165
|
+
flags: {},
|
|
166
|
+
splitPercent: 0,
|
|
167
|
+
resolvedUri: "",
|
|
168
|
+
},
|
|
169
|
+
];
|
|
170
|
+
}
|
|
171
|
+
throw new Error(`unexpected read ${functionName}`);
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
const shop = await (0, nft_js_1.getProject721Shop)(client, {
|
|
175
|
+
chainId,
|
|
176
|
+
projectId: 1n,
|
|
177
|
+
isRevnet: true,
|
|
178
|
+
});
|
|
179
|
+
(0, vitest_1.expect)(shop).not.toBeNull();
|
|
180
|
+
(0, vitest_1.expect)(shop.hook).toEqual(hook);
|
|
181
|
+
(0, vitest_1.expect)(shop.store).toEqual(store);
|
|
182
|
+
(0, vitest_1.expect)(shop.metadataIdTarget).toEqual(impl);
|
|
183
|
+
(0, vitest_1.expect)(shop.pricing).toEqual({ currency: 61166, decimals: 18 });
|
|
184
|
+
(0, vitest_1.expect)(shop.tiers).toHaveLength(1);
|
|
185
|
+
(0, vitest_1.expect)(shop.tiers[0]).toMatchObject({ id: 1, price: 1000n });
|
|
186
|
+
});
|
|
187
|
+
const tiersOfOne = () => [
|
|
188
|
+
{
|
|
189
|
+
id: 1,
|
|
190
|
+
price: 1000n,
|
|
191
|
+
remainingSupply: 5,
|
|
192
|
+
initialSupply: 10,
|
|
193
|
+
votingUnits: 0n,
|
|
194
|
+
reserveFrequency: 0,
|
|
195
|
+
reserveBeneficiary: viem_1.zeroAddress,
|
|
196
|
+
encodedIpfsUri: `0x${"11".repeat(32)}`,
|
|
197
|
+
category: 0,
|
|
198
|
+
discountPercent: 0,
|
|
199
|
+
flags: {},
|
|
200
|
+
splitPercent: 0,
|
|
201
|
+
resolvedUri: "",
|
|
202
|
+
},
|
|
203
|
+
];
|
|
204
|
+
// The custom-project omnichain deployer address on chain 11155111, read from
|
|
205
|
+
// the SDK's own address book so the branch matches real wiring.
|
|
206
|
+
const omniDeployer = juicebox_js_1.jbContractAddress["6"][contracts_js_1.JBOmnichainDeployerContracts.JBOmnichainDeployer]["11155111"];
|
|
207
|
+
(0, vitest_1.test)("custom project: resolves the real hook via the omnichain deployer", async () => {
|
|
208
|
+
const client = {
|
|
209
|
+
readContract: async ({ address, functionName, }) => {
|
|
210
|
+
// currentRulesetOf -> [ruleset, metadata] with the deployer as data hook.
|
|
211
|
+
if (functionName === "currentRulesetOf") {
|
|
212
|
+
return [
|
|
213
|
+
{ id: 7 },
|
|
214
|
+
{ useDataHookForPay: true, dataHook: omniDeployer },
|
|
215
|
+
];
|
|
216
|
+
}
|
|
217
|
+
// The deployer maps to the real clone hook.
|
|
218
|
+
if (address.toLowerCase() === omniDeployer.toLowerCase() &&
|
|
219
|
+
functionName === "tiered721HookOf") {
|
|
220
|
+
return [hook, false];
|
|
221
|
+
}
|
|
222
|
+
if (address === hook && functionName === "STORE")
|
|
223
|
+
return store;
|
|
224
|
+
if (functionName === "METADATA_ID_TARGET")
|
|
225
|
+
return impl;
|
|
226
|
+
if (functionName === "pricingContext")
|
|
227
|
+
return [1n, 18n];
|
|
228
|
+
if (functionName === "tiersOf")
|
|
229
|
+
return tiersOfOne();
|
|
230
|
+
throw new Error(`unexpected read ${functionName} @ ${address}`);
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
const shop = await (0, nft_js_1.getProject721Shop)(client, {
|
|
234
|
+
chainId: 11155111,
|
|
235
|
+
projectId: 9n,
|
|
236
|
+
isRevnet: false,
|
|
237
|
+
});
|
|
238
|
+
(0, vitest_1.expect)(shop.hook).toEqual(hook);
|
|
239
|
+
(0, vitest_1.expect)(shop.metadataIdTarget).toEqual(impl);
|
|
240
|
+
});
|
|
241
|
+
(0, vitest_1.test)("custom project: data hook that is not a 721 hook is no shop", async () => {
|
|
242
|
+
const notAHook = "0x4444444444444444444444444444444444444444";
|
|
243
|
+
const client = {
|
|
244
|
+
readContract: async ({ functionName }) => {
|
|
245
|
+
if (functionName === "currentRulesetOf") {
|
|
246
|
+
return [{ id: 7 }, { useDataHookForPay: true, dataHook: notAHook }];
|
|
247
|
+
}
|
|
248
|
+
// Non-authoritative candidate: STORE() reverts => not a 721 hook.
|
|
249
|
+
if (functionName === "STORE")
|
|
250
|
+
throw new Error("no STORE");
|
|
251
|
+
throw new Error(`unexpected read ${functionName}`);
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
(0, vitest_1.expect)(await (0, nft_js_1.getProject721Shop)(client, {
|
|
255
|
+
chainId: 11155111,
|
|
256
|
+
projectId: 9n,
|
|
257
|
+
isRevnet: false,
|
|
258
|
+
})).toBeNull();
|
|
259
|
+
});
|
|
260
|
+
(0, vitest_1.test)("custom project: no pay data hook is no shop", async () => {
|
|
261
|
+
const client = {
|
|
262
|
+
readContract: async ({ functionName }) => {
|
|
263
|
+
if (functionName === "currentRulesetOf") {
|
|
264
|
+
return [
|
|
265
|
+
{ id: 7 },
|
|
266
|
+
{ useDataHookForPay: false, dataHook: viem_1.zeroAddress },
|
|
267
|
+
];
|
|
268
|
+
}
|
|
269
|
+
throw new Error(`unexpected read ${functionName}`);
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
(0, vitest_1.expect)(await (0, nft_js_1.getProject721Shop)(client, {
|
|
273
|
+
chainId: 11155111,
|
|
274
|
+
projectId: 9n,
|
|
275
|
+
isRevnet: false,
|
|
276
|
+
})).toBeNull();
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
//# sourceMappingURL=nft.test.js.map
|