@net-protocol/profiles 0.1.4 → 0.1.5
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/README.md +6 -2
- package/dist/index.d.mts +80 -3
- package/dist/index.d.ts +80 -3
- package/dist/index.js +224 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +217 -1
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +28 -3
- package/dist/react.d.ts +28 -3
- package/dist/react.js +22 -0
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +22 -1
- package/dist/react.mjs.map +1 -1
- package/dist/{types-DSRRz8Ug.d.mts → types-DDKLfc0r.d.mts} +1 -0
- package/dist/{types-DSRRz8Ug.d.ts → types-DDKLfc0r.d.ts} +1 -0
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -10,6 +10,8 @@ var PROFILE_METADATA_STORAGE_KEY = "net-beta0.0.1-profile-metadata";
|
|
|
10
10
|
var PROFILE_PICTURE_TOPIC = "profile-picture";
|
|
11
11
|
var PROFILE_METADATA_TOPIC = "profile-metadata";
|
|
12
12
|
var PROFILE_CANVAS_TOPIC = "profile-canvas";
|
|
13
|
+
var PROFILE_CSS_STORAGE_KEY = "net-beta0.0.1-profile-css";
|
|
14
|
+
var PROFILE_CSS_TOPIC = "profile-css";
|
|
13
15
|
function getValueArgForStorage(value) {
|
|
14
16
|
return stringToHex(value);
|
|
15
17
|
}
|
|
@@ -115,7 +117,221 @@ function isValidTokenAddress(address) {
|
|
|
115
117
|
if (!address) return false;
|
|
116
118
|
return /^0x[a-fA-F0-9]{40}$/.test(address);
|
|
117
119
|
}
|
|
120
|
+
var MAX_CSS_SIZE = 10 * 1024;
|
|
121
|
+
function getProfileCSSStorageArgs(cssContent) {
|
|
122
|
+
const { bytesKey, bytesValue } = getBytesArgsForStorage(
|
|
123
|
+
PROFILE_CSS_STORAGE_KEY,
|
|
124
|
+
cssContent
|
|
125
|
+
);
|
|
126
|
+
return {
|
|
127
|
+
bytesKey,
|
|
128
|
+
topic: PROFILE_CSS_TOPIC,
|
|
129
|
+
bytesValue
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function isValidCSS(css) {
|
|
133
|
+
if (!css || css.trim().length === 0) return false;
|
|
134
|
+
if (Buffer.byteLength(css, "utf-8") > MAX_CSS_SIZE) return false;
|
|
135
|
+
const lowerCSS = css.toLowerCase();
|
|
136
|
+
if (lowerCSS.includes("expression(")) return false;
|
|
137
|
+
if (lowerCSS.includes("behavior:")) return false;
|
|
138
|
+
if (lowerCSS.includes("javascript:")) return false;
|
|
139
|
+
if (/<script/i.test(css)) return false;
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/theme-selectors.ts
|
|
144
|
+
var THEME_SELECTORS = [
|
|
145
|
+
// --- CSS Variables (stable) ---
|
|
146
|
+
{
|
|
147
|
+
selector: "--background",
|
|
148
|
+
description: "Page background color (HSL values, e.g. '210 40% 2%')",
|
|
149
|
+
category: "variable"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
selector: "--foreground",
|
|
153
|
+
description: "Default text color",
|
|
154
|
+
category: "variable"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
selector: "--primary",
|
|
158
|
+
description: "Primary accent color (buttons, links, headings)",
|
|
159
|
+
category: "variable"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
selector: "--primary-foreground",
|
|
163
|
+
description: "Text on primary-colored elements",
|
|
164
|
+
category: "variable"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
selector: "--secondary",
|
|
168
|
+
description: "Secondary accent color",
|
|
169
|
+
category: "variable"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
selector: "--secondary-foreground",
|
|
173
|
+
description: "Text on secondary-colored elements",
|
|
174
|
+
category: "variable"
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
selector: "--muted",
|
|
178
|
+
description: "Muted/subdued background",
|
|
179
|
+
category: "variable"
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
selector: "--muted-foreground",
|
|
183
|
+
description: "Text on muted backgrounds",
|
|
184
|
+
category: "variable"
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
selector: "--accent",
|
|
188
|
+
description: "Accent color for highlights",
|
|
189
|
+
category: "variable"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
selector: "--accent-foreground",
|
|
193
|
+
description: "Text on accent-colored elements",
|
|
194
|
+
category: "variable"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
selector: "--card",
|
|
198
|
+
description: "Card/panel background color",
|
|
199
|
+
category: "variable"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
selector: "--card-foreground",
|
|
203
|
+
description: "Text inside cards",
|
|
204
|
+
category: "variable"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
selector: "--border",
|
|
208
|
+
description: "Border color",
|
|
209
|
+
category: "variable"
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
selector: "--ring",
|
|
213
|
+
description: "Focus ring color",
|
|
214
|
+
category: "variable"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
selector: "--radius",
|
|
218
|
+
description: "Border radius (e.g. '0.5rem')",
|
|
219
|
+
category: "variable"
|
|
220
|
+
},
|
|
221
|
+
// --- Layout selectors (may change with page restructuring) ---
|
|
222
|
+
{
|
|
223
|
+
selector: ".profile-themed",
|
|
224
|
+
description: "Root wrapper for all themed profile content",
|
|
225
|
+
category: "layout"
|
|
226
|
+
},
|
|
227
|
+
// --- Component selectors (may change with page restructuring) ---
|
|
228
|
+
{
|
|
229
|
+
selector: ".profile-themed .profile-header",
|
|
230
|
+
description: "Profile header area (name, picture, bio)",
|
|
231
|
+
category: "component"
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
selector: ".profile-themed .profile-tabs",
|
|
235
|
+
description: "Tab navigation bar",
|
|
236
|
+
category: "component"
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
selector: ".profile-themed .profile-content",
|
|
240
|
+
description: "Main content area below tabs",
|
|
241
|
+
category: "component"
|
|
242
|
+
}
|
|
243
|
+
];
|
|
244
|
+
var DEMO_THEMES = {
|
|
245
|
+
hotPink: {
|
|
246
|
+
name: "Hot Pink Scene",
|
|
247
|
+
css: `.profile-themed {
|
|
248
|
+
--background: 320 80% 4%;
|
|
249
|
+
--foreground: 320 20% 95%;
|
|
250
|
+
--primary: 330 100% 60%;
|
|
251
|
+
--primary-foreground: 0 0% 100%;
|
|
252
|
+
--secondary: 280 60% 20%;
|
|
253
|
+
--secondary-foreground: 320 20% 95%;
|
|
254
|
+
--muted: 320 40% 12%;
|
|
255
|
+
--muted-foreground: 320 20% 70%;
|
|
256
|
+
--accent: 330 100% 60%;
|
|
257
|
+
--accent-foreground: 0 0% 100%;
|
|
258
|
+
--card: 320 60% 6%;
|
|
259
|
+
--card-foreground: 320 20% 95%;
|
|
260
|
+
--border: 330 60% 25%;
|
|
261
|
+
--ring: 330 100% 60%;
|
|
262
|
+
}`
|
|
263
|
+
},
|
|
264
|
+
midnightGrunge: {
|
|
265
|
+
name: "Midnight Grunge",
|
|
266
|
+
css: `.profile-themed {
|
|
267
|
+
--background: 220 30% 3%;
|
|
268
|
+
--foreground: 220 10% 80%;
|
|
269
|
+
--primary: 45 90% 55%;
|
|
270
|
+
--primary-foreground: 220 30% 5%;
|
|
271
|
+
--secondary: 220 20% 12%;
|
|
272
|
+
--secondary-foreground: 220 10% 80%;
|
|
273
|
+
--muted: 220 20% 8%;
|
|
274
|
+
--muted-foreground: 220 10% 50%;
|
|
275
|
+
--accent: 45 90% 55%;
|
|
276
|
+
--accent-foreground: 220 30% 5%;
|
|
277
|
+
--card: 220 25% 5%;
|
|
278
|
+
--card-foreground: 220 10% 80%;
|
|
279
|
+
--border: 220 15% 15%;
|
|
280
|
+
--ring: 45 90% 55%;
|
|
281
|
+
}`
|
|
282
|
+
},
|
|
283
|
+
ocean: {
|
|
284
|
+
name: "Deep Ocean",
|
|
285
|
+
css: `.profile-themed {
|
|
286
|
+
--background: 200 60% 3%;
|
|
287
|
+
--foreground: 190 20% 90%;
|
|
288
|
+
--primary: 190 80% 50%;
|
|
289
|
+
--primary-foreground: 200 60% 5%;
|
|
290
|
+
--secondary: 210 40% 15%;
|
|
291
|
+
--secondary-foreground: 190 20% 90%;
|
|
292
|
+
--muted: 200 40% 8%;
|
|
293
|
+
--muted-foreground: 190 20% 55%;
|
|
294
|
+
--accent: 170 70% 45%;
|
|
295
|
+
--accent-foreground: 200 60% 5%;
|
|
296
|
+
--card: 200 50% 5%;
|
|
297
|
+
--card-foreground: 190 20% 90%;
|
|
298
|
+
--border: 200 30% 18%;
|
|
299
|
+
--ring: 190 80% 50%;
|
|
300
|
+
}`
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
function buildCSSPrompt() {
|
|
304
|
+
const variableLines = THEME_SELECTORS.filter(
|
|
305
|
+
(s) => s.category === "variable"
|
|
306
|
+
).map((s) => ` ${s.selector}: ${s.description}`).join("\n");
|
|
307
|
+
const layoutLines = THEME_SELECTORS.filter((s) => s.category === "layout").map((s) => ` ${s.selector} \u2014 ${s.description}`).join("\n");
|
|
308
|
+
const componentLines = THEME_SELECTORS.filter(
|
|
309
|
+
(s) => s.category === "component"
|
|
310
|
+
).map((s) => ` ${s.selector} \u2014 ${s.description}`).join("\n");
|
|
311
|
+
return `You are a CSS theme generator for a user profile page.
|
|
312
|
+
All styles MUST be scoped under the .profile-themed wrapper class.
|
|
313
|
+
|
|
314
|
+
## CSS Variables (stable \u2014 preferred for theming)
|
|
315
|
+
These use HSL values WITHOUT the hsl() wrapper (e.g. "210 40% 98%"):
|
|
316
|
+
${variableLines}
|
|
317
|
+
|
|
318
|
+
## Layout Selectors (may change across site updates)
|
|
319
|
+
${layoutLines}
|
|
320
|
+
|
|
321
|
+
## Component Selectors (may change across site updates)
|
|
322
|
+
${componentLines}
|
|
323
|
+
|
|
324
|
+
## Rules
|
|
325
|
+
1. All selectors MUST start with .profile-themed
|
|
326
|
+
2. CSS variables go inside .profile-themed { ... } as custom properties
|
|
327
|
+
3. Use only valid CSS \u2014 no JavaScript, no expressions, no imports
|
|
328
|
+
4. Keep the output under 10KB
|
|
329
|
+
5. Prefer CSS variables over direct selector styling for durability
|
|
330
|
+
6. HSL values are bare numbers: "210 40% 98%" not "hsl(210, 40%, 98%)"
|
|
331
|
+
|
|
332
|
+
Given a user description, output ONLY the CSS (no explanation, no markdown fences).`;
|
|
333
|
+
}
|
|
118
334
|
|
|
119
|
-
export { PROFILE_CANVAS_STORAGE_KEY, PROFILE_CANVAS_TOPIC, PROFILE_METADATA_STORAGE_KEY, PROFILE_METADATA_TOPIC, PROFILE_PICTURE_STORAGE_KEY, PROFILE_PICTURE_TOPIC, PROFILE_X_USERNAME_STORAGE_KEY, getBioStorageArgs, getBytesArgsForStorage, getDisplayNameStorageArgs, getProfileCanvasStorageArgs, getProfileMetadataStorageArgs, getProfilePictureStorageArgs, getTokenAddressStorageArgs, getValueArgForStorage, getXUsernameStorageArgs, isValidBio, isValidDisplayName, isValidTokenAddress, isValidUrl, isValidXUsername, parseProfileMetadata };
|
|
335
|
+
export { DEMO_THEMES, MAX_CSS_SIZE, PROFILE_CANVAS_STORAGE_KEY, PROFILE_CANVAS_TOPIC, PROFILE_CSS_STORAGE_KEY, PROFILE_CSS_TOPIC, PROFILE_METADATA_STORAGE_KEY, PROFILE_METADATA_TOPIC, PROFILE_PICTURE_STORAGE_KEY, PROFILE_PICTURE_TOPIC, PROFILE_X_USERNAME_STORAGE_KEY, THEME_SELECTORS, buildCSSPrompt, getBioStorageArgs, getBytesArgsForStorage, getDisplayNameStorageArgs, getProfileCSSStorageArgs, getProfileCanvasStorageArgs, getProfileMetadataStorageArgs, getProfilePictureStorageArgs, getTokenAddressStorageArgs, getValueArgForStorage, getXUsernameStorageArgs, isValidBio, isValidCSS, isValidDisplayName, isValidTokenAddress, isValidUrl, isValidXUsername, parseProfileMetadata };
|
|
120
336
|
//# sourceMappingURL=index.mjs.map
|
|
121
337
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/constants.ts","../src/utils.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Profile-related storage keys\n *\n * Using descriptive keys under 32 bytes to avoid hashing complexity and work seamlessly\n * with existing storage infrastructure. The key includes app prefix and versioning for\n * clarity and future-proofing.\n *\n * NOTE: if we change these keys, users will not be able to see their profile data\n */\nexport const PROFILE_CANVAS_STORAGE_KEY = \"net-beta0.0.1-profile-canvas\";\nexport const PROFILE_PICTURE_STORAGE_KEY = \"net-beta0.0.1-profile-picture\";\nexport const PROFILE_X_USERNAME_STORAGE_KEY =\n \"net-beta0.0.1-profile-x-username\";\nexport const PROFILE_METADATA_STORAGE_KEY = \"net-beta0.0.1-profile-metadata\";\n\n/**\n * Topic strings used when writing to storage\n * These are the second argument to Storage.put()\n */\nexport const PROFILE_PICTURE_TOPIC = \"profile-picture\";\nexport const PROFILE_METADATA_TOPIC = \"profile-metadata\";\nexport const PROFILE_CANVAS_TOPIC = \"profile-canvas\";\n","import { stringToHex } from \"viem\";\nimport { toBytes32 } from \"@net-protocol/core\";\nimport {\n PROFILE_PICTURE_STORAGE_KEY,\n PROFILE_METADATA_STORAGE_KEY,\n PROFILE_CANVAS_STORAGE_KEY,\n PROFILE_PICTURE_TOPIC,\n PROFILE_METADATA_TOPIC,\n PROFILE_CANVAS_TOPIC,\n} from \"./constants\";\nimport type { ProfileMetadata, ProfileStorageArgs } from \"./types\";\n\n/**\n * Convert a string value to hex for storage\n */\nexport function getValueArgForStorage(value: string): `0x${string}` {\n return stringToHex(value);\n}\n\n/**\n * Get storage args (key as bytes32, value as hex)\n */\nexport function getBytesArgsForStorage(\n key: string,\n value: string\n): { bytesKey: `0x${string}`; bytesValue: `0x${string}` } {\n const bytesKey = toBytes32(key) as `0x${string}`;\n const bytesValue = getValueArgForStorage(value);\n return { bytesKey, bytesValue };\n}\n\n/**\n * Prepare transaction arguments for updating profile picture\n *\n * @param imageUrl - The URL of the profile picture\n * @returns Arguments for Storage.put() - [bytesKey, topic, bytesValue]\n *\n * @example\n * ```ts\n * const args = getProfilePictureStorageArgs(\"https://example.com/image.jpg\");\n * // Use with wagmi writeContract:\n * writeContract({\n * abi: STORAGE_CONTRACT.abi,\n * address: STORAGE_CONTRACT.address,\n * functionName: \"put\",\n * args: [args.bytesKey, args.topic, args.bytesValue],\n * });\n * ```\n */\nexport function getProfilePictureStorageArgs(\n imageUrl: string\n): ProfileStorageArgs {\n const { bytesKey, bytesValue } = getBytesArgsForStorage(\n PROFILE_PICTURE_STORAGE_KEY,\n imageUrl\n );\n return {\n bytesKey,\n topic: PROFILE_PICTURE_TOPIC,\n bytesValue,\n };\n}\n\n/**\n * Prepare transaction arguments for updating profile metadata (X username, etc.)\n *\n * @param metadata - Profile metadata object to store\n * @returns Arguments for Storage.put() - [bytesKey, topic, bytesValue]\n *\n * @example\n * ```ts\n * const args = getProfileMetadataStorageArgs({ x_username: \"myusername\" });\n * writeContract({\n * abi: STORAGE_CONTRACT.abi,\n * address: STORAGE_CONTRACT.address,\n * functionName: \"put\",\n * args: [args.bytesKey, args.topic, args.bytesValue],\n * });\n * ```\n */\nexport function getProfileMetadataStorageArgs(\n metadata: ProfileMetadata\n): ProfileStorageArgs {\n const jsonString = JSON.stringify(metadata);\n const { bytesKey, bytesValue } = getBytesArgsForStorage(\n PROFILE_METADATA_STORAGE_KEY,\n jsonString\n );\n return {\n bytesKey,\n topic: PROFILE_METADATA_TOPIC,\n bytesValue,\n };\n}\n\n/**\n * Prepare transaction arguments for updating X username\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * Note: Username is stored WITHOUT the @ prefix. The @ is stripped if provided.\n *\n * @param username - X/Twitter username (with or without @)\n * @returns Arguments for Storage.put()\n */\nexport function getXUsernameStorageArgs(username: string): ProfileStorageArgs {\n // Strip @ prefix if present - store username without @\n const normalizedUsername = username.startsWith(\"@\")\n ? username.slice(1)\n : username;\n return getProfileMetadataStorageArgs({ x_username: normalizedUsername });\n}\n\n/**\n * Prepare transaction arguments for updating profile canvas (HTML content)\n *\n * @param htmlContent - HTML content for the profile canvas\n * @returns Arguments for Storage.put()\n *\n * @example\n * ```ts\n * const args = getProfileCanvasStorageArgs(\"<div>My custom profile</div>\");\n * writeContract({\n * abi: STORAGE_CONTRACT.abi,\n * address: STORAGE_CONTRACT.address,\n * functionName: \"put\",\n * args: [args.bytesKey, args.topic, args.bytesValue],\n * });\n * ```\n */\nexport function getProfileCanvasStorageArgs(\n htmlContent: string\n): ProfileStorageArgs {\n const { bytesKey, bytesValue } = getBytesArgsForStorage(\n PROFILE_CANVAS_STORAGE_KEY,\n htmlContent\n );\n return {\n bytesKey,\n topic: PROFILE_CANVAS_TOPIC,\n bytesValue,\n };\n}\n\n/**\n * Parse profile metadata JSON and extract profile data\n *\n * @param jsonData - JSON string from storage\n * @returns Parsed profile metadata or undefined if invalid\n */\nexport function parseProfileMetadata(\n jsonData: string\n): ProfileMetadata | undefined {\n try {\n const parsed = JSON.parse(jsonData);\n const storedUsername =\n parsed?.x_username &&\n typeof parsed.x_username === \"string\" &&\n parsed.x_username.length > 0\n ? parsed.x_username\n : undefined;\n\n // Strip @ if present for backwards compatibility with older stored data\n const usernameWithoutAt = storedUsername?.startsWith(\"@\")\n ? storedUsername.slice(1)\n : storedUsername;\n\n // Extract bio if present\n const bio =\n parsed?.bio && typeof parsed.bio === \"string\" && parsed.bio.length > 0\n ? parsed.bio\n : undefined;\n\n // Extract display name if present\n const display_name =\n parsed?.display_name &&\n typeof parsed.display_name === \"string\" &&\n parsed.display_name.length > 0\n ? parsed.display_name\n : undefined;\n\n // Extract token address if present (stored as lowercase)\n const token_address =\n parsed?.token_address &&\n typeof parsed.token_address === \"string\" &&\n parsed.token_address.length > 0\n ? parsed.token_address.toLowerCase()\n : undefined;\n\n return {\n x_username: usernameWithoutAt,\n bio,\n display_name,\n token_address,\n };\n } catch {\n return undefined;\n }\n}\n\n/**\n * Validate that a string is a valid URL\n */\nexport function isValidUrl(url: string): boolean {\n if (!url) return false;\n try {\n new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Validate X/Twitter username format\n * Returns true if valid (alphanumeric and underscores, 1-15 chars)\n */\nexport function isValidXUsername(username: string): boolean {\n if (!username) return false;\n // Remove @ prefix if present\n const cleanUsername = username.startsWith(\"@\") ? username.slice(1) : username;\n // X usernames: 1-15 chars, alphanumeric and underscores only\n return /^[a-zA-Z0-9_]{1,15}$/.test(cleanUsername);\n}\n\n/**\n * Validate bio format\n * Returns true if valid (max 280 chars, no control characters except newlines)\n */\nexport function isValidBio(bio: string): boolean {\n if (!bio) return false;\n if (bio.length > 280) return false;\n // Allow printable characters, spaces, and newlines. Disallow other control chars.\n // eslint-disable-next-line no-control-regex\n const hasControlChars = /[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]/.test(bio);\n return !hasControlChars;\n}\n\n/**\n * Validate display name format\n * Returns true if valid (1-25 characters, no control characters except spaces)\n */\nexport function isValidDisplayName(displayName: string): boolean {\n if (!displayName) return false;\n if (displayName.length > 25) return false;\n // Disallow control characters\n // eslint-disable-next-line no-control-regex\n const hasControlChars = /[\\x00-\\x1F\\x7F]/.test(displayName);\n return !hasControlChars;\n}\n\n/**\n * Prepare transaction arguments for updating bio\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * @param bio - The bio text\n * @returns Arguments for Storage.put()\n */\nexport function getBioStorageArgs(bio: string): ProfileStorageArgs {\n return getProfileMetadataStorageArgs({ bio });\n}\n\n/**\n * Prepare transaction arguments for updating display name\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * @param displayName - The display name (max 25 characters)\n * @returns Arguments for Storage.put()\n */\nexport function getDisplayNameStorageArgs(\n displayName: string\n): ProfileStorageArgs {\n return getProfileMetadataStorageArgs({ display_name: displayName });\n}\n\n/**\n * Prepare transaction arguments for updating token address\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * @param tokenAddress - The ERC-20 token contract address (stored as lowercase)\n * @returns Arguments for Storage.put()\n */\nexport function getTokenAddressStorageArgs(\n tokenAddress: string\n): ProfileStorageArgs {\n return getProfileMetadataStorageArgs({\n token_address: tokenAddress.toLowerCase(),\n });\n}\n\n/**\n * Validate that a string is a valid EVM token address\n * Returns true if valid (0x-prefixed, 40 hex characters)\n */\nexport function isValidTokenAddress(address: string): boolean {\n if (!address) return false;\n return /^0x[a-fA-F0-9]{40}$/.test(address);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/utils.ts","../src/theme-selectors.ts"],"names":[],"mappings":";;;;;AASO,IAAM,0BAAA,GAA6B;AACnC,IAAM,2BAAA,GAA8B;AACpC,IAAM,8BAAA,GACX;AACK,IAAM,4BAAA,GAA+B;AAMrC,IAAM,qBAAA,GAAwB;AAC9B,IAAM,sBAAA,GAAyB;AAC/B,IAAM,oBAAA,GAAuB;AAC7B,IAAM,uBAAA,GAA0B;AAChC,IAAM,iBAAA,GAAoB;ACN1B,SAAS,sBAAsB,KAAA,EAA8B;AAClE,EAAA,OAAO,YAAY,KAAK,CAAA;AAC1B;AAKO,SAAS,sBAAA,CACd,KACA,KAAA,EACwD;AACxD,EAAA,MAAM,QAAA,GAAW,UAAU,GAAG,CAAA;AAC9B,EAAA,MAAM,UAAA,GAAa,sBAAsB,KAAK,CAAA;AAC9C,EAAA,OAAO,EAAE,UAAU,UAAA,EAAW;AAChC;AAoBO,SAAS,6BACd,QAAA,EACoB;AACpB,EAAA,MAAM,EAAE,QAAA,EAAU,UAAA,EAAW,GAAI,sBAAA;AAAA,IAC/B,2BAAA;AAAA,IACA;AAAA,GACF;AACA,EAAA,OAAO;AAAA,IACL,QAAA;AAAA,IACA,KAAA,EAAO,qBAAA;AAAA,IACP;AAAA,GACF;AACF;AAmBO,SAAS,8BACd,QAAA,EACoB;AACpB,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,SAAA,CAAU,QAAQ,CAAA;AAC1C,EAAA,MAAM,EAAE,QAAA,EAAU,UAAA,EAAW,GAAI,sBAAA;AAAA,IAC/B,4BAAA;AAAA,IACA;AAAA,GACF;AACA,EAAA,OAAO;AAAA,IACL,QAAA;AAAA,IACA,KAAA,EAAO,sBAAA;AAAA,IACP;AAAA,GACF;AACF;AAWO,SAAS,wBAAwB,QAAA,EAAsC;AAE5E,EAAA,MAAM,kBAAA,GAAqB,SAAS,UAAA,CAAW,GAAG,IAC9C,QAAA,CAAS,KAAA,CAAM,CAAC,CAAA,GAChB,QAAA;AACJ,EAAA,OAAO,6BAAA,CAA8B,EAAE,UAAA,EAAY,kBAAA,EAAoB,CAAA;AACzE;AAmBO,SAAS,4BACd,WAAA,EACoB;AACpB,EAAA,MAAM,EAAE,QAAA,EAAU,UAAA,EAAW,GAAI,sBAAA;AAAA,IAC/B,0BAAA;AAAA,IACA;AAAA,GACF;AACA,EAAA,OAAO;AAAA,IACL,QAAA;AAAA,IACA,KAAA,EAAO,oBAAA;AAAA,IACP;AAAA,GACF;AACF;AAQO,SAAS,qBACd,QAAA,EAC6B;AAC7B,EAAA,IAAI;AACF,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,QAAQ,CAAA;AAClC,IAAA,MAAM,cAAA,GACJ,MAAA,EAAQ,UAAA,IACR,OAAO,MAAA,CAAO,UAAA,KAAe,QAAA,IAC7B,MAAA,CAAO,UAAA,CAAW,MAAA,GAAS,CAAA,GACvB,MAAA,CAAO,UAAA,GACP,KAAA,CAAA;AAGN,IAAA,MAAM,iBAAA,GAAoB,gBAAgB,UAAA,CAAW,GAAG,IACpD,cAAA,CAAe,KAAA,CAAM,CAAC,CAAA,GACtB,cAAA;AAGJ,IAAA,MAAM,GAAA,GACJ,MAAA,EAAQ,GAAA,IAAO,OAAO,MAAA,CAAO,GAAA,KAAQ,QAAA,IAAY,MAAA,CAAO,GAAA,CAAI,MAAA,GAAS,CAAA,GACjE,MAAA,CAAO,GAAA,GACP,KAAA,CAAA;AAGN,IAAA,MAAM,YAAA,GACJ,MAAA,EAAQ,YAAA,IACR,OAAO,MAAA,CAAO,YAAA,KAAiB,QAAA,IAC/B,MAAA,CAAO,YAAA,CAAa,MAAA,GAAS,CAAA,GACzB,MAAA,CAAO,YAAA,GACP,KAAA,CAAA;AAGN,IAAA,MAAM,aAAA,GACJ,MAAA,EAAQ,aAAA,IACR,OAAO,OAAO,aAAA,KAAkB,QAAA,IAChC,MAAA,CAAO,aAAA,CAAc,MAAA,GAAS,CAAA,GAC1B,MAAA,CAAO,aAAA,CAAc,aAAY,GACjC,KAAA,CAAA;AAEN,IAAA,OAAO;AAAA,MACL,UAAA,EAAY,iBAAA;AAAA,MACZ,GAAA;AAAA,MACA,YAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AAKO,SAAS,WAAW,GAAA,EAAsB;AAC/C,EAAA,IAAI,CAAC,KAAK,OAAO,KAAA;AACjB,EAAA,IAAI;AACF,IAAA,IAAI,IAAI,GAAG,CAAA;AACX,IAAA,OAAO,IAAA;AAAA,EACT,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AAMO,SAAS,iBAAiB,QAAA,EAA2B;AAC1D,EAAA,IAAI,CAAC,UAAU,OAAO,KAAA;AAEtB,EAAA,MAAM,aAAA,GAAgB,SAAS,UAAA,CAAW,GAAG,IAAI,QAAA,CAAS,KAAA,CAAM,CAAC,CAAA,GAAI,QAAA;AAErE,EAAA,OAAO,sBAAA,CAAuB,KAAK,aAAa,CAAA;AAClD;AAMO,SAAS,WAAW,GAAA,EAAsB;AAC/C,EAAA,IAAI,CAAC,KAAK,OAAO,KAAA;AACjB,EAAA,IAAI,GAAA,CAAI,MAAA,GAAS,GAAA,EAAK,OAAO,KAAA;AAG7B,EAAA,MAAM,eAAA,GAAkB,kCAAA,CAAmC,IAAA,CAAK,GAAG,CAAA;AACnE,EAAA,OAAO,CAAC,eAAA;AACV;AAMO,SAAS,mBAAmB,WAAA,EAA8B;AAC/D,EAAA,IAAI,CAAC,aAAa,OAAO,KAAA;AACzB,EAAA,IAAI,WAAA,CAAY,MAAA,GAAS,EAAA,EAAI,OAAO,KAAA;AAGpC,EAAA,MAAM,eAAA,GAAkB,iBAAA,CAAkB,IAAA,CAAK,WAAW,CAAA;AAC1D,EAAA,OAAO,CAAC,eAAA;AACV;AASO,SAAS,kBAAkB,GAAA,EAAiC;AACjE,EAAA,OAAO,6BAAA,CAA8B,EAAE,GAAA,EAAK,CAAA;AAC9C;AASO,SAAS,0BACd,WAAA,EACoB;AACpB,EAAA,OAAO,6BAAA,CAA8B,EAAE,YAAA,EAAc,WAAA,EAAa,CAAA;AACpE;AASO,SAAS,2BACd,YAAA,EACoB;AACpB,EAAA,OAAO,6BAAA,CAA8B;AAAA,IACnC,aAAA,EAAe,aAAa,WAAA;AAAY,GACzC,CAAA;AACH;AAMO,SAAS,oBAAoB,OAAA,EAA0B;AAC5D,EAAA,IAAI,CAAC,SAAS,OAAO,KAAA;AACrB,EAAA,OAAO,qBAAA,CAAsB,KAAK,OAAO,CAAA;AAC3C;AAKO,IAAM,eAAe,EAAA,GAAK;AAmB1B,SAAS,yBACd,UAAA,EACoB;AACpB,EAAA,MAAM,EAAE,QAAA,EAAU,UAAA,EAAW,GAAI,sBAAA;AAAA,IAC/B,uBAAA;AAAA,IACA;AAAA,GACF;AACA,EAAA,OAAO;AAAA,IACL,QAAA;AAAA,IACA,KAAA,EAAO,iBAAA;AAAA,IACP;AAAA,GACF;AACF;AAMO,SAAS,WAAW,GAAA,EAAsB;AAC/C,EAAA,IAAI,CAAC,GAAA,IAAO,GAAA,CAAI,MAAK,CAAE,MAAA,KAAW,GAAG,OAAO,KAAA;AAC5C,EAAA,IAAI,OAAO,UAAA,CAAW,GAAA,EAAK,OAAO,CAAA,GAAI,cAAc,OAAO,KAAA;AAE3D,EAAA,MAAM,QAAA,GAAW,IAAI,WAAA,EAAY;AACjC,EAAA,IAAI,QAAA,CAAS,QAAA,CAAS,aAAa,CAAA,EAAG,OAAO,KAAA;AAC7C,EAAA,IAAI,QAAA,CAAS,QAAA,CAAS,WAAW,CAAA,EAAG,OAAO,KAAA;AAC3C,EAAA,IAAI,QAAA,CAAS,QAAA,CAAS,aAAa,CAAA,EAAG,OAAO,KAAA;AAC7C,EAAA,IAAI,UAAA,CAAW,IAAA,CAAK,GAAG,CAAA,EAAG,OAAO,KAAA;AACjC,EAAA,OAAO,IAAA;AACT;;;AC7TO,IAAM,eAAA,GAAmC;AAAA;AAAA,EAE9C;AAAA,IACE,QAAA,EAAU,cAAA;AAAA,IACV,WAAA,EAAa,uDAAA;AAAA,IACb,QAAA,EAAU;AAAA,GACZ;AAAA,EACA;AAAA,IACE,QAAA,EAAU,cAAA;AAAA,IACV,WAAA,EAAa,oBAAA;AAAA,IACb,QAAA,EAAU;AAAA,GACZ;AAAA,EACA;AAAA,IACE,QAAA,EAAU,WAAA;AAAA,IACV,WAAA,EAAa,iDAAA;AAAA,IACb,QAAA,EAAU;AAAA,GACZ;AAAA,EACA;AAAA,IACE,QAAA,EAAU,sBAAA;AAAA,IACV,WAAA,EAAa,kCAAA;AAAA,IACb,QAAA,EAAU;AAAA,GACZ;AAAA,EACA;AAAA,IACE,QAAA,EAAU,aAAA;AAAA,IACV,WAAA,EAAa,wBAAA;AAAA,IACb,QAAA,EAAU;AAAA,GACZ;AAAA,EACA;AAAA,IACE,QAAA,EAAU,wBAAA;AAAA,IACV,WAAA,EAAa,oCAAA;AAAA,IACb,QAAA,EAAU;AAAA,GACZ;AAAA,EACA;AAAA,IACE,QAAA,EAAU,SAAA;AAAA,IACV,WAAA,EAAa,0BAAA;AAAA,IACb,QAAA,EAAU;AAAA,GACZ;AAAA,EACA;AAAA,IACE,QAAA,EAAU,oBAAA;AAAA,IACV,WAAA,EAAa,2BAAA;AAAA,IACb,QAAA,EAAU;AAAA,GACZ;AAAA,EACA;AAAA,IACE,QAAA,EAAU,UAAA;AAAA,IACV,WAAA,EAAa,6BAAA;AAAA,IACb,QAAA,EAAU;AAAA,GACZ;AAAA,EACA;AAAA,IACE,QAAA,EAAU,qBAAA;AAAA,IACV,WAAA,EAAa,iCAAA;AAAA,IACb,QAAA,EAAU;AAAA,GACZ;AAAA,EACA;AAAA,IACE,QAAA,EAAU,QAAA;AAAA,IACV,WAAA,EAAa,6BAAA;AAAA,IACb,QAAA,EAAU;AAAA,GACZ;AAAA,EACA;AAAA,IACE,QAAA,EAAU,mBAAA;AAAA,IACV,WAAA,EAAa,mBAAA;AAAA,IACb,QAAA,EAAU;AAAA,GACZ;AAAA,EACA;AAAA,IACE,QAAA,EAAU,UAAA;AAAA,IACV,WAAA,EAAa,cAAA;AAAA,IACb,QAAA,EAAU;AAAA,GACZ;AAAA,EACA;AAAA,IACE,QAAA,EAAU,QAAA;AAAA,IACV,WAAA,EAAa,kBAAA;AAAA,IACb,QAAA,EAAU;AAAA,GACZ;AAAA,EACA;AAAA,IACE,QAAA,EAAU,UAAA;AAAA,IACV,WAAA,EAAa,+BAAA;AAAA,IACb,QAAA,EAAU;AAAA,GACZ;AAAA;AAAA,EAGA;AAAA,IACE,QAAA,EAAU,iBAAA;AAAA,IACV,WAAA,EAAa,6CAAA;AAAA,IACb,QAAA,EAAU;AAAA,GACZ;AAAA;AAAA,EAGA;AAAA,IACE,QAAA,EAAU,iCAAA;AAAA,IACV,WAAA,EAAa,0CAAA;AAAA,IACb,QAAA,EAAU;AAAA,GACZ;AAAA,EACA;AAAA,IACE,QAAA,EAAU,+BAAA;AAAA,IACV,WAAA,EAAa,oBAAA;AAAA,IACb,QAAA,EAAU;AAAA,GACZ;AAAA,EACA;AAAA,IACE,QAAA,EAAU,kCAAA;AAAA,IACV,WAAA,EAAa,8BAAA;AAAA,IACb,QAAA,EAAU;AAAA;AAEd;AAMO,IAAM,WAAA,GAA6D;AAAA,EACxE,OAAA,EAAS;AAAA,IACP,IAAA,EAAM,gBAAA;AAAA,IACN,GAAA,EAAK,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AAAA,GAgBP;AAAA,EACA,cAAA,EAAgB;AAAA,IACd,IAAA,EAAM,iBAAA;AAAA,IACN,GAAA,EAAK,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AAAA,GAgBP;AAAA,EACA,KAAA,EAAO;AAAA,IACL,IAAA,EAAM,YAAA;AAAA,IACN,GAAA,EAAK,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AAAA;AAiBT;AAQO,SAAS,cAAA,GAAyB;AACvC,EAAA,MAAM,gBAAgB,eAAA,CAAgB,MAAA;AAAA,IACpC,CAAC,CAAA,KAAM,CAAA,CAAE,QAAA,KAAa;AAAA,GACxB,CACG,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,EAAA,EAAK,CAAA,CAAE,QAAQ,CAAA,EAAA,EAAK,CAAA,CAAE,WAAW,CAAA,CAAE,CAAA,CAC9C,KAAK,IAAI,CAAA;AAEZ,EAAA,MAAM,WAAA,GAAc,gBAAgB,MAAA,CAAO,CAAC,MAAM,CAAA,CAAE,QAAA,KAAa,QAAQ,CAAA,CACtE,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,EAAA,EAAK,EAAE,QAAQ,CAAA,QAAA,EAAM,EAAE,WAAW,CAAA,CAAE,CAAA,CAC/C,IAAA,CAAK,IAAI,CAAA;AAEZ,EAAA,MAAM,iBAAiB,eAAA,CAAgB,MAAA;AAAA,IACrC,CAAC,CAAA,KAAM,CAAA,CAAE,QAAA,KAAa;AAAA,GACxB,CACG,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,EAAA,EAAK,CAAA,CAAE,QAAQ,CAAA,QAAA,EAAM,CAAA,CAAE,WAAW,CAAA,CAAE,CAAA,CAC/C,KAAK,IAAI,CAAA;AAEZ,EAAA,OAAO,CAAA;AAAA;;AAAA;AAAA;AAAA,EAKP,aAAa;;AAAA;AAAA,EAGb,WAAW;;AAAA;AAAA,EAGX,cAAc;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA,mFAAA,CAAA;AAWhB","file":"index.mjs","sourcesContent":["/**\n * Profile-related storage keys\n *\n * Using descriptive keys under 32 bytes to avoid hashing complexity and work seamlessly\n * with existing storage infrastructure. The key includes app prefix and versioning for\n * clarity and future-proofing.\n *\n * NOTE: if we change these keys, users will not be able to see their profile data\n */\nexport const PROFILE_CANVAS_STORAGE_KEY = \"net-beta0.0.1-profile-canvas\";\nexport const PROFILE_PICTURE_STORAGE_KEY = \"net-beta0.0.1-profile-picture\";\nexport const PROFILE_X_USERNAME_STORAGE_KEY =\n \"net-beta0.0.1-profile-x-username\";\nexport const PROFILE_METADATA_STORAGE_KEY = \"net-beta0.0.1-profile-metadata\";\n\n/**\n * Topic strings used when writing to storage\n * These are the second argument to Storage.put()\n */\nexport const PROFILE_PICTURE_TOPIC = \"profile-picture\";\nexport const PROFILE_METADATA_TOPIC = \"profile-metadata\";\nexport const PROFILE_CANVAS_TOPIC = \"profile-canvas\";\nexport const PROFILE_CSS_STORAGE_KEY = \"net-beta0.0.1-profile-css\";\nexport const PROFILE_CSS_TOPIC = \"profile-css\";\n","import { stringToHex } from \"viem\";\nimport { toBytes32 } from \"@net-protocol/core\";\nimport {\n PROFILE_PICTURE_STORAGE_KEY,\n PROFILE_METADATA_STORAGE_KEY,\n PROFILE_CANVAS_STORAGE_KEY,\n PROFILE_CSS_STORAGE_KEY,\n PROFILE_PICTURE_TOPIC,\n PROFILE_METADATA_TOPIC,\n PROFILE_CANVAS_TOPIC,\n PROFILE_CSS_TOPIC,\n} from \"./constants\";\nimport type { ProfileMetadata, ProfileStorageArgs } from \"./types\";\n\n/**\n * Convert a string value to hex for storage\n */\nexport function getValueArgForStorage(value: string): `0x${string}` {\n return stringToHex(value);\n}\n\n/**\n * Get storage args (key as bytes32, value as hex)\n */\nexport function getBytesArgsForStorage(\n key: string,\n value: string\n): { bytesKey: `0x${string}`; bytesValue: `0x${string}` } {\n const bytesKey = toBytes32(key) as `0x${string}`;\n const bytesValue = getValueArgForStorage(value);\n return { bytesKey, bytesValue };\n}\n\n/**\n * Prepare transaction arguments for updating profile picture\n *\n * @param imageUrl - The URL of the profile picture\n * @returns Arguments for Storage.put() - [bytesKey, topic, bytesValue]\n *\n * @example\n * ```ts\n * const args = getProfilePictureStorageArgs(\"https://example.com/image.jpg\");\n * // Use with wagmi writeContract:\n * writeContract({\n * abi: STORAGE_CONTRACT.abi,\n * address: STORAGE_CONTRACT.address,\n * functionName: \"put\",\n * args: [args.bytesKey, args.topic, args.bytesValue],\n * });\n * ```\n */\nexport function getProfilePictureStorageArgs(\n imageUrl: string\n): ProfileStorageArgs {\n const { bytesKey, bytesValue } = getBytesArgsForStorage(\n PROFILE_PICTURE_STORAGE_KEY,\n imageUrl\n );\n return {\n bytesKey,\n topic: PROFILE_PICTURE_TOPIC,\n bytesValue,\n };\n}\n\n/**\n * Prepare transaction arguments for updating profile metadata (X username, etc.)\n *\n * @param metadata - Profile metadata object to store\n * @returns Arguments for Storage.put() - [bytesKey, topic, bytesValue]\n *\n * @example\n * ```ts\n * const args = getProfileMetadataStorageArgs({ x_username: \"myusername\" });\n * writeContract({\n * abi: STORAGE_CONTRACT.abi,\n * address: STORAGE_CONTRACT.address,\n * functionName: \"put\",\n * args: [args.bytesKey, args.topic, args.bytesValue],\n * });\n * ```\n */\nexport function getProfileMetadataStorageArgs(\n metadata: ProfileMetadata\n): ProfileStorageArgs {\n const jsonString = JSON.stringify(metadata);\n const { bytesKey, bytesValue } = getBytesArgsForStorage(\n PROFILE_METADATA_STORAGE_KEY,\n jsonString\n );\n return {\n bytesKey,\n topic: PROFILE_METADATA_TOPIC,\n bytesValue,\n };\n}\n\n/**\n * Prepare transaction arguments for updating X username\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * Note: Username is stored WITHOUT the @ prefix. The @ is stripped if provided.\n *\n * @param username - X/Twitter username (with or without @)\n * @returns Arguments for Storage.put()\n */\nexport function getXUsernameStorageArgs(username: string): ProfileStorageArgs {\n // Strip @ prefix if present - store username without @\n const normalizedUsername = username.startsWith(\"@\")\n ? username.slice(1)\n : username;\n return getProfileMetadataStorageArgs({ x_username: normalizedUsername });\n}\n\n/**\n * Prepare transaction arguments for updating profile canvas (HTML content)\n *\n * @param htmlContent - HTML content for the profile canvas\n * @returns Arguments for Storage.put()\n *\n * @example\n * ```ts\n * const args = getProfileCanvasStorageArgs(\"<div>My custom profile</div>\");\n * writeContract({\n * abi: STORAGE_CONTRACT.abi,\n * address: STORAGE_CONTRACT.address,\n * functionName: \"put\",\n * args: [args.bytesKey, args.topic, args.bytesValue],\n * });\n * ```\n */\nexport function getProfileCanvasStorageArgs(\n htmlContent: string\n): ProfileStorageArgs {\n const { bytesKey, bytesValue } = getBytesArgsForStorage(\n PROFILE_CANVAS_STORAGE_KEY,\n htmlContent\n );\n return {\n bytesKey,\n topic: PROFILE_CANVAS_TOPIC,\n bytesValue,\n };\n}\n\n/**\n * Parse profile metadata JSON and extract profile data\n *\n * @param jsonData - JSON string from storage\n * @returns Parsed profile metadata or undefined if invalid\n */\nexport function parseProfileMetadata(\n jsonData: string\n): ProfileMetadata | undefined {\n try {\n const parsed = JSON.parse(jsonData);\n const storedUsername =\n parsed?.x_username &&\n typeof parsed.x_username === \"string\" &&\n parsed.x_username.length > 0\n ? parsed.x_username\n : undefined;\n\n // Strip @ if present for backwards compatibility with older stored data\n const usernameWithoutAt = storedUsername?.startsWith(\"@\")\n ? storedUsername.slice(1)\n : storedUsername;\n\n // Extract bio if present\n const bio =\n parsed?.bio && typeof parsed.bio === \"string\" && parsed.bio.length > 0\n ? parsed.bio\n : undefined;\n\n // Extract display name if present\n const display_name =\n parsed?.display_name &&\n typeof parsed.display_name === \"string\" &&\n parsed.display_name.length > 0\n ? parsed.display_name\n : undefined;\n\n // Extract token address if present (stored as lowercase)\n const token_address =\n parsed?.token_address &&\n typeof parsed.token_address === \"string\" &&\n parsed.token_address.length > 0\n ? parsed.token_address.toLowerCase()\n : undefined;\n\n return {\n x_username: usernameWithoutAt,\n bio,\n display_name,\n token_address,\n };\n } catch {\n return undefined;\n }\n}\n\n/**\n * Validate that a string is a valid URL\n */\nexport function isValidUrl(url: string): boolean {\n if (!url) return false;\n try {\n new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Validate X/Twitter username format\n * Returns true if valid (alphanumeric and underscores, 1-15 chars)\n */\nexport function isValidXUsername(username: string): boolean {\n if (!username) return false;\n // Remove @ prefix if present\n const cleanUsername = username.startsWith(\"@\") ? username.slice(1) : username;\n // X usernames: 1-15 chars, alphanumeric and underscores only\n return /^[a-zA-Z0-9_]{1,15}$/.test(cleanUsername);\n}\n\n/**\n * Validate bio format\n * Returns true if valid (max 280 chars, no control characters except newlines)\n */\nexport function isValidBio(bio: string): boolean {\n if (!bio) return false;\n if (bio.length > 280) return false;\n // Allow printable characters, spaces, and newlines. Disallow other control chars.\n // eslint-disable-next-line no-control-regex\n const hasControlChars = /[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]/.test(bio);\n return !hasControlChars;\n}\n\n/**\n * Validate display name format\n * Returns true if valid (1-25 characters, no control characters except spaces)\n */\nexport function isValidDisplayName(displayName: string): boolean {\n if (!displayName) return false;\n if (displayName.length > 25) return false;\n // Disallow control characters\n // eslint-disable-next-line no-control-regex\n const hasControlChars = /[\\x00-\\x1F\\x7F]/.test(displayName);\n return !hasControlChars;\n}\n\n/**\n * Prepare transaction arguments for updating bio\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * @param bio - The bio text\n * @returns Arguments for Storage.put()\n */\nexport function getBioStorageArgs(bio: string): ProfileStorageArgs {\n return getProfileMetadataStorageArgs({ bio });\n}\n\n/**\n * Prepare transaction arguments for updating display name\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * @param displayName - The display name (max 25 characters)\n * @returns Arguments for Storage.put()\n */\nexport function getDisplayNameStorageArgs(\n displayName: string\n): ProfileStorageArgs {\n return getProfileMetadataStorageArgs({ display_name: displayName });\n}\n\n/**\n * Prepare transaction arguments for updating token address\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * @param tokenAddress - The ERC-20 token contract address (stored as lowercase)\n * @returns Arguments for Storage.put()\n */\nexport function getTokenAddressStorageArgs(\n tokenAddress: string\n): ProfileStorageArgs {\n return getProfileMetadataStorageArgs({\n token_address: tokenAddress.toLowerCase(),\n });\n}\n\n/**\n * Validate that a string is a valid EVM token address\n * Returns true if valid (0x-prefixed, 40 hex characters)\n */\nexport function isValidTokenAddress(address: string): boolean {\n if (!address) return false;\n return /^0x[a-fA-F0-9]{40}$/.test(address);\n}\n\n/**\n * Maximum CSS size in bytes (10KB — CSS should be small)\n */\nexport const MAX_CSS_SIZE = 10 * 1024;\n\n/**\n * Prepare transaction arguments for updating profile custom CSS\n *\n * @param cssContent - CSS string to store\n * @returns Arguments for Storage.put() - [bytesKey, topic, bytesValue]\n *\n * @example\n * ```ts\n * const args = getProfileCSSStorageArgs(\".profile-themed { --primary: 210 40% 98%; }\");\n * writeContract({\n * abi: STORAGE_CONTRACT.abi,\n * address: STORAGE_CONTRACT.address,\n * functionName: \"put\",\n * args: [args.bytesKey, args.topic, args.bytesValue],\n * });\n * ```\n */\nexport function getProfileCSSStorageArgs(\n cssContent: string\n): ProfileStorageArgs {\n const { bytesKey, bytesValue } = getBytesArgsForStorage(\n PROFILE_CSS_STORAGE_KEY,\n cssContent\n );\n return {\n bytesKey,\n topic: PROFILE_CSS_TOPIC,\n bytesValue,\n };\n}\n\n/**\n * Validate CSS content\n * Returns true if valid (non-empty, within size limit, no script injection)\n */\nexport function isValidCSS(css: string): boolean {\n if (!css || css.trim().length === 0) return false;\n if (Buffer.byteLength(css, \"utf-8\") > MAX_CSS_SIZE) return false;\n // Block script injection via CSS expressions/behavior/url(javascript:)\n const lowerCSS = css.toLowerCase();\n if (lowerCSS.includes(\"expression(\")) return false;\n if (lowerCSS.includes(\"behavior:\")) return false;\n if (lowerCSS.includes(\"javascript:\")) return false;\n if (/<script/i.test(css)) return false;\n return true;\n}\n","/**\n * Single source of truth for profile CSS theming.\n *\n * This file defines:\n * 1. THEME_SELECTORS — the stable set of CSS selectors/variables available for theming\n * 2. DEMO_THEMES — example CSS themes users can apply\n * 3. buildCSSPrompt() — generates an AI prompt describing what can be themed\n *\n * When the profile page structure changes, update THEME_SELECTORS here and\n * everything downstream (AI prompt, docs, validation) stays in sync.\n */\n\n/**\n * A themeable selector or CSS variable group\n */\nexport interface ThemeSelector {\n /** CSS selector or variable name */\n selector: string;\n /** Human-readable description of what it targets */\n description: string;\n /** Category for grouping in documentation */\n category: \"variable\" | \"layout\" | \"component\";\n}\n\n/**\n * All themeable selectors available for profile CSS.\n *\n * CSS variables (category: \"variable\") are the most stable —\n * they survive page restructuring because they're part of the\n * shadcn/Tailwind design system.\n *\n * Layout and component selectors may break when the page structure changes.\n */\nexport const THEME_SELECTORS: ThemeSelector[] = [\n // --- CSS Variables (stable) ---\n {\n selector: \"--background\",\n description: \"Page background color (HSL values, e.g. '210 40% 2%')\",\n category: \"variable\",\n },\n {\n selector: \"--foreground\",\n description: \"Default text color\",\n category: \"variable\",\n },\n {\n selector: \"--primary\",\n description: \"Primary accent color (buttons, links, headings)\",\n category: \"variable\",\n },\n {\n selector: \"--primary-foreground\",\n description: \"Text on primary-colored elements\",\n category: \"variable\",\n },\n {\n selector: \"--secondary\",\n description: \"Secondary accent color\",\n category: \"variable\",\n },\n {\n selector: \"--secondary-foreground\",\n description: \"Text on secondary-colored elements\",\n category: \"variable\",\n },\n {\n selector: \"--muted\",\n description: \"Muted/subdued background\",\n category: \"variable\",\n },\n {\n selector: \"--muted-foreground\",\n description: \"Text on muted backgrounds\",\n category: \"variable\",\n },\n {\n selector: \"--accent\",\n description: \"Accent color for highlights\",\n category: \"variable\",\n },\n {\n selector: \"--accent-foreground\",\n description: \"Text on accent-colored elements\",\n category: \"variable\",\n },\n {\n selector: \"--card\",\n description: \"Card/panel background color\",\n category: \"variable\",\n },\n {\n selector: \"--card-foreground\",\n description: \"Text inside cards\",\n category: \"variable\",\n },\n {\n selector: \"--border\",\n description: \"Border color\",\n category: \"variable\",\n },\n {\n selector: \"--ring\",\n description: \"Focus ring color\",\n category: \"variable\",\n },\n {\n selector: \"--radius\",\n description: \"Border radius (e.g. '0.5rem')\",\n category: \"variable\",\n },\n\n // --- Layout selectors (may change with page restructuring) ---\n {\n selector: \".profile-themed\",\n description: \"Root wrapper for all themed profile content\",\n category: \"layout\",\n },\n\n // --- Component selectors (may change with page restructuring) ---\n {\n selector: \".profile-themed .profile-header\",\n description: \"Profile header area (name, picture, bio)\",\n category: \"component\",\n },\n {\n selector: \".profile-themed .profile-tabs\",\n description: \"Tab navigation bar\",\n category: \"component\",\n },\n {\n selector: \".profile-themed .profile-content\",\n description: \"Main content area below tabs\",\n category: \"component\",\n },\n];\n\n/**\n * Demo themes that users can choose from as starting points.\n * Each is a complete CSS string ready to store on-chain.\n */\nexport const DEMO_THEMES: Record<string, { name: string; css: string }> = {\n hotPink: {\n name: \"Hot Pink Scene\",\n css: `.profile-themed {\n --background: 320 80% 4%;\n --foreground: 320 20% 95%;\n --primary: 330 100% 60%;\n --primary-foreground: 0 0% 100%;\n --secondary: 280 60% 20%;\n --secondary-foreground: 320 20% 95%;\n --muted: 320 40% 12%;\n --muted-foreground: 320 20% 70%;\n --accent: 330 100% 60%;\n --accent-foreground: 0 0% 100%;\n --card: 320 60% 6%;\n --card-foreground: 320 20% 95%;\n --border: 330 60% 25%;\n --ring: 330 100% 60%;\n}`,\n },\n midnightGrunge: {\n name: \"Midnight Grunge\",\n css: `.profile-themed {\n --background: 220 30% 3%;\n --foreground: 220 10% 80%;\n --primary: 45 90% 55%;\n --primary-foreground: 220 30% 5%;\n --secondary: 220 20% 12%;\n --secondary-foreground: 220 10% 80%;\n --muted: 220 20% 8%;\n --muted-foreground: 220 10% 50%;\n --accent: 45 90% 55%;\n --accent-foreground: 220 30% 5%;\n --card: 220 25% 5%;\n --card-foreground: 220 10% 80%;\n --border: 220 15% 15%;\n --ring: 45 90% 55%;\n}`,\n },\n ocean: {\n name: \"Deep Ocean\",\n css: `.profile-themed {\n --background: 200 60% 3%;\n --foreground: 190 20% 90%;\n --primary: 190 80% 50%;\n --primary-foreground: 200 60% 5%;\n --secondary: 210 40% 15%;\n --secondary-foreground: 190 20% 90%;\n --muted: 200 40% 8%;\n --muted-foreground: 190 20% 55%;\n --accent: 170 70% 45%;\n --accent-foreground: 200 60% 5%;\n --card: 200 50% 5%;\n --card-foreground: 190 20% 90%;\n --border: 200 30% 18%;\n --ring: 190 80% 50%;\n}`,\n },\n};\n\n/**\n * Build an AI prompt that describes the available theming surface.\n * Feed this to an LLM alongside a user's description to generate CSS.\n *\n * @returns A prompt string listing all available selectors and usage rules\n */\nexport function buildCSSPrompt(): string {\n const variableLines = THEME_SELECTORS.filter(\n (s) => s.category === \"variable\"\n )\n .map((s) => ` ${s.selector}: ${s.description}`)\n .join(\"\\n\");\n\n const layoutLines = THEME_SELECTORS.filter((s) => s.category === \"layout\")\n .map((s) => ` ${s.selector} — ${s.description}`)\n .join(\"\\n\");\n\n const componentLines = THEME_SELECTORS.filter(\n (s) => s.category === \"component\"\n )\n .map((s) => ` ${s.selector} — ${s.description}`)\n .join(\"\\n\");\n\n return `You are a CSS theme generator for a user profile page.\nAll styles MUST be scoped under the .profile-themed wrapper class.\n\n## CSS Variables (stable — preferred for theming)\nThese use HSL values WITHOUT the hsl() wrapper (e.g. \"210 40% 98%\"):\n${variableLines}\n\n## Layout Selectors (may change across site updates)\n${layoutLines}\n\n## Component Selectors (may change across site updates)\n${componentLines}\n\n## Rules\n1. All selectors MUST start with .profile-themed\n2. CSS variables go inside .profile-themed { ... } as custom properties\n3. Use only valid CSS — no JavaScript, no expressions, no imports\n4. Keep the output under 10KB\n5. Prefer CSS variables over direct selector styling for durability\n6. HSL values are bare numbers: \"210 40% 98%\" not \"hsl(210, 40%, 98%)\"\n\nGiven a user description, output ONLY the CSS (no explanation, no markdown fences).`;\n}\n"]}
|
package/dist/react.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as _net_protocol_storage from '@net-protocol/storage';
|
|
2
|
-
import { b as UseProfileOptions, B as BasicUserProfileMetadata } from './types-
|
|
3
|
-
export { a as ProfileMetadata, c as UserProfile } from './types-
|
|
2
|
+
import { b as UseProfileOptions, B as BasicUserProfileMetadata } from './types-DDKLfc0r.mjs';
|
|
3
|
+
export { a as ProfileMetadata, c as UserProfile } from './types-DDKLfc0r.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Hook to fetch profile picture URL from storage
|
|
@@ -81,6 +81,31 @@ declare function useProfileCanvas({ chainId, userAddress, enabled, }: UseProfile
|
|
|
81
81
|
rawData: _net_protocol_storage.StorageData | undefined;
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Hook to fetch profile custom CSS from storage
|
|
86
|
+
*
|
|
87
|
+
* @param options - Chain ID and user address to fetch profile for
|
|
88
|
+
* @returns Custom CSS string and loading state
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```tsx
|
|
92
|
+
* const { css, isLoading } = useProfileCSS({
|
|
93
|
+
* chainId: 8453,
|
|
94
|
+
* userAddress: "0x...",
|
|
95
|
+
* });
|
|
96
|
+
*
|
|
97
|
+
* if (css) {
|
|
98
|
+
* return <style>{`.profile-themed { ${css} }`}</style>;
|
|
99
|
+
* }
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
declare function useProfileCSS({ chainId, userAddress, enabled, }: UseProfileOptions): {
|
|
103
|
+
css: string | undefined;
|
|
104
|
+
isLoading: boolean;
|
|
105
|
+
error: Error | undefined;
|
|
106
|
+
rawData: _net_protocol_storage.StorageData | undefined;
|
|
107
|
+
};
|
|
108
|
+
|
|
84
109
|
/**
|
|
85
110
|
* Hook to fetch basic user profile metadata (profile picture and X username) in a single call
|
|
86
111
|
*
|
|
@@ -107,4 +132,4 @@ declare function useProfileCanvas({ chainId, userAddress, enabled, }: UseProfile
|
|
|
107
132
|
*/
|
|
108
133
|
declare function useBasicUserProfileMetadata({ chainId, userAddress, enabled, }: UseProfileOptions): BasicUserProfileMetadata;
|
|
109
134
|
|
|
110
|
-
export { BasicUserProfileMetadata, UseProfileOptions, useBasicUserProfileMetadata, useProfileCanvas, useProfilePicture, useProfileXUsername };
|
|
135
|
+
export { BasicUserProfileMetadata, UseProfileOptions, useBasicUserProfileMetadata, useProfileCSS, useProfileCanvas, useProfilePicture, useProfileXUsername };
|
package/dist/react.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as _net_protocol_storage from '@net-protocol/storage';
|
|
2
|
-
import { b as UseProfileOptions, B as BasicUserProfileMetadata } from './types-
|
|
3
|
-
export { a as ProfileMetadata, c as UserProfile } from './types-
|
|
2
|
+
import { b as UseProfileOptions, B as BasicUserProfileMetadata } from './types-DDKLfc0r.js';
|
|
3
|
+
export { a as ProfileMetadata, c as UserProfile } from './types-DDKLfc0r.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Hook to fetch profile picture URL from storage
|
|
@@ -81,6 +81,31 @@ declare function useProfileCanvas({ chainId, userAddress, enabled, }: UseProfile
|
|
|
81
81
|
rawData: _net_protocol_storage.StorageData | undefined;
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Hook to fetch profile custom CSS from storage
|
|
86
|
+
*
|
|
87
|
+
* @param options - Chain ID and user address to fetch profile for
|
|
88
|
+
* @returns Custom CSS string and loading state
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```tsx
|
|
92
|
+
* const { css, isLoading } = useProfileCSS({
|
|
93
|
+
* chainId: 8453,
|
|
94
|
+
* userAddress: "0x...",
|
|
95
|
+
* });
|
|
96
|
+
*
|
|
97
|
+
* if (css) {
|
|
98
|
+
* return <style>{`.profile-themed { ${css} }`}</style>;
|
|
99
|
+
* }
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
declare function useProfileCSS({ chainId, userAddress, enabled, }: UseProfileOptions): {
|
|
103
|
+
css: string | undefined;
|
|
104
|
+
isLoading: boolean;
|
|
105
|
+
error: Error | undefined;
|
|
106
|
+
rawData: _net_protocol_storage.StorageData | undefined;
|
|
107
|
+
};
|
|
108
|
+
|
|
84
109
|
/**
|
|
85
110
|
* Hook to fetch basic user profile metadata (profile picture and X username) in a single call
|
|
86
111
|
*
|
|
@@ -107,4 +132,4 @@ declare function useProfileCanvas({ chainId, userAddress, enabled, }: UseProfile
|
|
|
107
132
|
*/
|
|
108
133
|
declare function useBasicUserProfileMetadata({ chainId, userAddress, enabled, }: UseProfileOptions): BasicUserProfileMetadata;
|
|
109
134
|
|
|
110
|
-
export { BasicUserProfileMetadata, UseProfileOptions, useBasicUserProfileMetadata, useProfileCanvas, useProfilePicture, useProfileXUsername };
|
|
135
|
+
export { BasicUserProfileMetadata, UseProfileOptions, useBasicUserProfileMetadata, useProfileCSS, useProfileCanvas, useProfilePicture, useProfileXUsername };
|
package/dist/react.js
CHANGED
|
@@ -12,6 +12,7 @@ var wagmi = require('wagmi');
|
|
|
12
12
|
var PROFILE_CANVAS_STORAGE_KEY = "net-beta0.0.1-profile-canvas";
|
|
13
13
|
var PROFILE_PICTURE_STORAGE_KEY = "net-beta0.0.1-profile-picture";
|
|
14
14
|
var PROFILE_METADATA_STORAGE_KEY = "net-beta0.0.1-profile-metadata";
|
|
15
|
+
var PROFILE_CSS_STORAGE_KEY = "net-beta0.0.1-profile-css";
|
|
15
16
|
function useProfilePicture({
|
|
16
17
|
chainId,
|
|
17
18
|
userAddress,
|
|
@@ -102,6 +103,26 @@ function useProfileCanvas({
|
|
|
102
103
|
rawData: data
|
|
103
104
|
};
|
|
104
105
|
}
|
|
106
|
+
function useProfileCSS({
|
|
107
|
+
chainId,
|
|
108
|
+
userAddress,
|
|
109
|
+
enabled = true
|
|
110
|
+
}) {
|
|
111
|
+
const { data, isLoading, error } = react.useStorage({
|
|
112
|
+
chainId,
|
|
113
|
+
key: storage.getStorageKeyBytes(PROFILE_CSS_STORAGE_KEY),
|
|
114
|
+
operatorAddress: userAddress,
|
|
115
|
+
enabled
|
|
116
|
+
});
|
|
117
|
+
const css = data?.value ? viem.hexToString(data.value) : void 0;
|
|
118
|
+
return {
|
|
119
|
+
css: css || void 0,
|
|
120
|
+
isLoading,
|
|
121
|
+
error,
|
|
122
|
+
// Also expose raw data for advanced use cases
|
|
123
|
+
rawData: data
|
|
124
|
+
};
|
|
125
|
+
}
|
|
105
126
|
function useBasicUserProfileMetadata({
|
|
106
127
|
chainId,
|
|
107
128
|
userAddress,
|
|
@@ -154,6 +175,7 @@ function useBasicUserProfileMetadata({
|
|
|
154
175
|
}
|
|
155
176
|
|
|
156
177
|
exports.useBasicUserProfileMetadata = useBasicUserProfileMetadata;
|
|
178
|
+
exports.useProfileCSS = useProfileCSS;
|
|
157
179
|
exports.useProfileCanvas = useProfileCanvas;
|
|
158
180
|
exports.useProfilePicture = useProfilePicture;
|
|
159
181
|
exports.useProfileXUsername = useProfileXUsername;
|
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/constants.ts","../src/hooks/useProfilePicture.ts","../src/utils.ts","../src/hooks/useProfileXUsername.ts","../src/hooks/useProfileCanvas.ts","../src/hooks/useBasicUserProfileMetadata.ts"],"names":["useStorage","getStorageKeyBytes","hexToString","useReadContracts","STORAGE_CONTRACT"],"mappings":"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","file":"react.js","sourcesContent":["/**\n * Profile-related storage keys\n *\n * Using descriptive keys under 32 bytes to avoid hashing complexity and work seamlessly\n * with existing storage infrastructure. The key includes app prefix and versioning for\n * clarity and future-proofing.\n *\n * NOTE: if we change these keys, users will not be able to see their profile data\n */\nexport const PROFILE_CANVAS_STORAGE_KEY = \"net-beta0.0.1-profile-canvas\";\nexport const PROFILE_PICTURE_STORAGE_KEY = \"net-beta0.0.1-profile-picture\";\nexport const PROFILE_X_USERNAME_STORAGE_KEY =\n \"net-beta0.0.1-profile-x-username\";\nexport const PROFILE_METADATA_STORAGE_KEY = \"net-beta0.0.1-profile-metadata\";\n\n/**\n * Topic strings used when writing to storage\n * These are the second argument to Storage.put()\n */\nexport const PROFILE_PICTURE_TOPIC = \"profile-picture\";\nexport const PROFILE_METADATA_TOPIC = \"profile-metadata\";\nexport const PROFILE_CANVAS_TOPIC = \"profile-canvas\";\n","import { useStorage } from \"@net-protocol/storage/react\";\nimport { getStorageKeyBytes } from \"@net-protocol/storage\";\nimport { PROFILE_PICTURE_STORAGE_KEY } from \"../constants\";\nimport { hexToString } from \"viem\";\nimport type { UseProfileOptions } from \"../types\";\n\n/**\n * Hook to fetch profile picture URL from storage\n *\n * @param options - Chain ID and user address to fetch profile for\n * @returns Profile picture URL and loading state\n *\n * @example\n * ```tsx\n * const { profilePicture, isLoading } = useProfilePicture({\n * chainId: 8453,\n * userAddress: \"0x...\",\n * });\n *\n * if (profilePicture) {\n * return <img src={profilePicture} alt=\"Profile\" />;\n * }\n * ```\n */\nexport function useProfilePicture({\n chainId,\n userAddress,\n enabled = true,\n}: UseProfileOptions) {\n const { data, isLoading, error } = useStorage({\n chainId,\n key: getStorageKeyBytes(PROFILE_PICTURE_STORAGE_KEY),\n operatorAddress: userAddress,\n enabled,\n });\n\n // Convert hex value to string URL\n const profilePicture = data?.value ? hexToString(data.value as any) : undefined;\n\n return {\n profilePicture: profilePicture || undefined,\n isLoading,\n error,\n // Also expose raw data for advanced use cases\n rawData: data,\n };\n}\n","import { stringToHex } from \"viem\";\nimport { toBytes32 } from \"@net-protocol/core\";\nimport {\n PROFILE_PICTURE_STORAGE_KEY,\n PROFILE_METADATA_STORAGE_KEY,\n PROFILE_CANVAS_STORAGE_KEY,\n PROFILE_PICTURE_TOPIC,\n PROFILE_METADATA_TOPIC,\n PROFILE_CANVAS_TOPIC,\n} from \"./constants\";\nimport type { ProfileMetadata, ProfileStorageArgs } from \"./types\";\n\n/**\n * Convert a string value to hex for storage\n */\nexport function getValueArgForStorage(value: string): `0x${string}` {\n return stringToHex(value);\n}\n\n/**\n * Get storage args (key as bytes32, value as hex)\n */\nexport function getBytesArgsForStorage(\n key: string,\n value: string\n): { bytesKey: `0x${string}`; bytesValue: `0x${string}` } {\n const bytesKey = toBytes32(key) as `0x${string}`;\n const bytesValue = getValueArgForStorage(value);\n return { bytesKey, bytesValue };\n}\n\n/**\n * Prepare transaction arguments for updating profile picture\n *\n * @param imageUrl - The URL of the profile picture\n * @returns Arguments for Storage.put() - [bytesKey, topic, bytesValue]\n *\n * @example\n * ```ts\n * const args = getProfilePictureStorageArgs(\"https://example.com/image.jpg\");\n * // Use with wagmi writeContract:\n * writeContract({\n * abi: STORAGE_CONTRACT.abi,\n * address: STORAGE_CONTRACT.address,\n * functionName: \"put\",\n * args: [args.bytesKey, args.topic, args.bytesValue],\n * });\n * ```\n */\nexport function getProfilePictureStorageArgs(\n imageUrl: string\n): ProfileStorageArgs {\n const { bytesKey, bytesValue } = getBytesArgsForStorage(\n PROFILE_PICTURE_STORAGE_KEY,\n imageUrl\n );\n return {\n bytesKey,\n topic: PROFILE_PICTURE_TOPIC,\n bytesValue,\n };\n}\n\n/**\n * Prepare transaction arguments for updating profile metadata (X username, etc.)\n *\n * @param metadata - Profile metadata object to store\n * @returns Arguments for Storage.put() - [bytesKey, topic, bytesValue]\n *\n * @example\n * ```ts\n * const args = getProfileMetadataStorageArgs({ x_username: \"myusername\" });\n * writeContract({\n * abi: STORAGE_CONTRACT.abi,\n * address: STORAGE_CONTRACT.address,\n * functionName: \"put\",\n * args: [args.bytesKey, args.topic, args.bytesValue],\n * });\n * ```\n */\nexport function getProfileMetadataStorageArgs(\n metadata: ProfileMetadata\n): ProfileStorageArgs {\n const jsonString = JSON.stringify(metadata);\n const { bytesKey, bytesValue } = getBytesArgsForStorage(\n PROFILE_METADATA_STORAGE_KEY,\n jsonString\n );\n return {\n bytesKey,\n topic: PROFILE_METADATA_TOPIC,\n bytesValue,\n };\n}\n\n/**\n * Prepare transaction arguments for updating X username\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * Note: Username is stored WITHOUT the @ prefix. The @ is stripped if provided.\n *\n * @param username - X/Twitter username (with or without @)\n * @returns Arguments for Storage.put()\n */\nexport function getXUsernameStorageArgs(username: string): ProfileStorageArgs {\n // Strip @ prefix if present - store username without @\n const normalizedUsername = username.startsWith(\"@\")\n ? username.slice(1)\n : username;\n return getProfileMetadataStorageArgs({ x_username: normalizedUsername });\n}\n\n/**\n * Prepare transaction arguments for updating profile canvas (HTML content)\n *\n * @param htmlContent - HTML content for the profile canvas\n * @returns Arguments for Storage.put()\n *\n * @example\n * ```ts\n * const args = getProfileCanvasStorageArgs(\"<div>My custom profile</div>\");\n * writeContract({\n * abi: STORAGE_CONTRACT.abi,\n * address: STORAGE_CONTRACT.address,\n * functionName: \"put\",\n * args: [args.bytesKey, args.topic, args.bytesValue],\n * });\n * ```\n */\nexport function getProfileCanvasStorageArgs(\n htmlContent: string\n): ProfileStorageArgs {\n const { bytesKey, bytesValue } = getBytesArgsForStorage(\n PROFILE_CANVAS_STORAGE_KEY,\n htmlContent\n );\n return {\n bytesKey,\n topic: PROFILE_CANVAS_TOPIC,\n bytesValue,\n };\n}\n\n/**\n * Parse profile metadata JSON and extract profile data\n *\n * @param jsonData - JSON string from storage\n * @returns Parsed profile metadata or undefined if invalid\n */\nexport function parseProfileMetadata(\n jsonData: string\n): ProfileMetadata | undefined {\n try {\n const parsed = JSON.parse(jsonData);\n const storedUsername =\n parsed?.x_username &&\n typeof parsed.x_username === \"string\" &&\n parsed.x_username.length > 0\n ? parsed.x_username\n : undefined;\n\n // Strip @ if present for backwards compatibility with older stored data\n const usernameWithoutAt = storedUsername?.startsWith(\"@\")\n ? storedUsername.slice(1)\n : storedUsername;\n\n // Extract bio if present\n const bio =\n parsed?.bio && typeof parsed.bio === \"string\" && parsed.bio.length > 0\n ? parsed.bio\n : undefined;\n\n // Extract display name if present\n const display_name =\n parsed?.display_name &&\n typeof parsed.display_name === \"string\" &&\n parsed.display_name.length > 0\n ? parsed.display_name\n : undefined;\n\n // Extract token address if present (stored as lowercase)\n const token_address =\n parsed?.token_address &&\n typeof parsed.token_address === \"string\" &&\n parsed.token_address.length > 0\n ? parsed.token_address.toLowerCase()\n : undefined;\n\n return {\n x_username: usernameWithoutAt,\n bio,\n display_name,\n token_address,\n };\n } catch {\n return undefined;\n }\n}\n\n/**\n * Validate that a string is a valid URL\n */\nexport function isValidUrl(url: string): boolean {\n if (!url) return false;\n try {\n new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Validate X/Twitter username format\n * Returns true if valid (alphanumeric and underscores, 1-15 chars)\n */\nexport function isValidXUsername(username: string): boolean {\n if (!username) return false;\n // Remove @ prefix if present\n const cleanUsername = username.startsWith(\"@\") ? username.slice(1) : username;\n // X usernames: 1-15 chars, alphanumeric and underscores only\n return /^[a-zA-Z0-9_]{1,15}$/.test(cleanUsername);\n}\n\n/**\n * Validate bio format\n * Returns true if valid (max 280 chars, no control characters except newlines)\n */\nexport function isValidBio(bio: string): boolean {\n if (!bio) return false;\n if (bio.length > 280) return false;\n // Allow printable characters, spaces, and newlines. Disallow other control chars.\n // eslint-disable-next-line no-control-regex\n const hasControlChars = /[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]/.test(bio);\n return !hasControlChars;\n}\n\n/**\n * Validate display name format\n * Returns true if valid (1-25 characters, no control characters except spaces)\n */\nexport function isValidDisplayName(displayName: string): boolean {\n if (!displayName) return false;\n if (displayName.length > 25) return false;\n // Disallow control characters\n // eslint-disable-next-line no-control-regex\n const hasControlChars = /[\\x00-\\x1F\\x7F]/.test(displayName);\n return !hasControlChars;\n}\n\n/**\n * Prepare transaction arguments for updating bio\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * @param bio - The bio text\n * @returns Arguments for Storage.put()\n */\nexport function getBioStorageArgs(bio: string): ProfileStorageArgs {\n return getProfileMetadataStorageArgs({ bio });\n}\n\n/**\n * Prepare transaction arguments for updating display name\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * @param displayName - The display name (max 25 characters)\n * @returns Arguments for Storage.put()\n */\nexport function getDisplayNameStorageArgs(\n displayName: string\n): ProfileStorageArgs {\n return getProfileMetadataStorageArgs({ display_name: displayName });\n}\n\n/**\n * Prepare transaction arguments for updating token address\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * @param tokenAddress - The ERC-20 token contract address (stored as lowercase)\n * @returns Arguments for Storage.put()\n */\nexport function getTokenAddressStorageArgs(\n tokenAddress: string\n): ProfileStorageArgs {\n return getProfileMetadataStorageArgs({\n token_address: tokenAddress.toLowerCase(),\n });\n}\n\n/**\n * Validate that a string is a valid EVM token address\n * Returns true if valid (0x-prefixed, 40 hex characters)\n */\nexport function isValidTokenAddress(address: string): boolean {\n if (!address) return false;\n return /^0x[a-fA-F0-9]{40}$/.test(address);\n}\n","import { useStorage } from \"@net-protocol/storage/react\";\nimport { getStorageKeyBytes } from \"@net-protocol/storage\";\nimport { PROFILE_METADATA_STORAGE_KEY } from \"../constants\";\nimport { hexToString } from \"viem\";\nimport { parseProfileMetadata } from \"../utils\";\nimport type { UseProfileOptions } from \"../types\";\n\n/**\n * Hook to fetch X/Twitter username from profile metadata storage\n *\n * @param options - Chain ID and user address to fetch profile for\n * @returns X username (without @) and loading state\n *\n * @example\n * ```tsx\n * const { xUsername, isLoading } = useProfileXUsername({\n * chainId: 8453,\n * userAddress: \"0x...\",\n * });\n *\n * if (xUsername) {\n * return <a href={`https://x.com/${xUsername}`}>@{xUsername}</a>;\n * }\n * ```\n */\nexport function useProfileXUsername({\n chainId,\n userAddress,\n enabled = true,\n}: UseProfileOptions) {\n const { data, isLoading, error } = useStorage({\n chainId,\n key: getStorageKeyBytes(PROFILE_METADATA_STORAGE_KEY),\n operatorAddress: userAddress,\n enabled,\n });\n\n // Parse JSON metadata to extract X username\n let xUsername: string | undefined;\n if (data?.value) {\n try {\n const jsonString = hexToString(data.value as any);\n const metadata = parseProfileMetadata(jsonString);\n xUsername = metadata?.x_username;\n } catch {\n // Invalid JSON or hex data\n }\n }\n\n return {\n xUsername,\n isLoading,\n error,\n // Also expose raw data for advanced use cases\n rawData: data,\n };\n}\n","import { useStorage } from \"@net-protocol/storage/react\";\nimport { getStorageKeyBytes } from \"@net-protocol/storage\";\nimport { PROFILE_CANVAS_STORAGE_KEY } from \"../constants\";\nimport { hexToString } from \"viem\";\nimport type { UseProfileOptions } from \"../types\";\n\n/**\n * Hook to fetch profile canvas HTML content from storage\n *\n * Note: For large canvas content (>20KB), you may need to use XML storage.\n * This hook fetches from regular storage. For large content, consider using\n * useXmlStorage from @net-protocol/storage/react directly.\n *\n * @param options - Chain ID and user address to fetch profile for\n * @returns Canvas HTML content and loading state\n *\n * @example\n * ```tsx\n * const { canvas, isLoading } = useProfileCanvas({\n * chainId: 8453,\n * userAddress: \"0x...\",\n * });\n *\n * if (canvas) {\n * return <iframe srcDoc={canvas} />;\n * }\n * ```\n */\nexport function useProfileCanvas({\n chainId,\n userAddress,\n enabled = true,\n}: UseProfileOptions) {\n const { data, isLoading, error } = useStorage({\n chainId,\n key: getStorageKeyBytes(PROFILE_CANVAS_STORAGE_KEY),\n operatorAddress: userAddress,\n enabled,\n // Use router for automatic chunked storage detection\n useRouter: true,\n });\n\n // Convert hex value to string HTML\n const canvas = data?.value ? hexToString(data.value as any) : undefined;\n\n return {\n canvas: canvas || undefined,\n isLoading,\n error,\n // Also expose raw data for advanced use cases\n rawData: data,\n };\n}\n","import { useReadContracts } from \"wagmi\";\nimport { STORAGE_CONTRACT, getStorageKeyBytes } from \"@net-protocol/storage\";\nimport {\n PROFILE_PICTURE_STORAGE_KEY,\n PROFILE_METADATA_STORAGE_KEY,\n} from \"../constants\";\nimport { hexToString } from \"viem\";\nimport { parseProfileMetadata } from \"../utils\";\nimport type { BasicUserProfileMetadata, UseProfileOptions } from \"../types\";\n\n/**\n * Hook to fetch basic user profile metadata (profile picture and X username) in a single call\n *\n * This hook is optimized for efficiency - it batches multiple storage reads into a single\n * contract call using wagmi's useReadContracts.\n *\n * @param options - Chain ID and user address to fetch profile for\n * @returns Profile picture URL, X username, and loading state\n *\n * @example\n * ```tsx\n * const { profilePicture, xUsername, isLoading } = useBasicUserProfileMetadata({\n * chainId: 8453,\n * userAddress: \"0x...\",\n * });\n *\n * return (\n * <div>\n * {profilePicture && <img src={profilePicture} alt=\"Profile\" />}\n * {xUsername && <span>@{xUsername}</span>}\n * </div>\n * );\n * ```\n */\nexport function useBasicUserProfileMetadata({\n chainId,\n userAddress,\n enabled = true,\n}: UseProfileOptions): BasicUserProfileMetadata {\n const profilePictureKey = getStorageKeyBytes(PROFILE_PICTURE_STORAGE_KEY);\n const profileMetadataKey = getStorageKeyBytes(PROFILE_METADATA_STORAGE_KEY);\n\n const {\n data: results,\n isLoading,\n error,\n } = useReadContracts({\n contracts: [\n {\n address: STORAGE_CONTRACT.address as `0x${string}`,\n abi: STORAGE_CONTRACT.abi,\n functionName: \"get\",\n args: [profilePictureKey, userAddress as `0x${string}`],\n chainId,\n },\n {\n address: STORAGE_CONTRACT.address as `0x${string}`,\n abi: STORAGE_CONTRACT.abi,\n functionName: \"get\",\n args: [profileMetadataKey, userAddress as `0x${string}`],\n chainId,\n },\n ],\n query: {\n enabled: enabled && !!userAddress && !!chainId,\n },\n });\n\n // Parse the results - decode hex data\n // Contract returns tuple [string text, bytes data]\n const profilePictureHex = (results as any)?.[0]?.result?.[1] || undefined;\n const profileMetadataJsonHex = (results as any)?.[1]?.result?.[1] || undefined;\n\n const profilePicture = profilePictureHex\n ? hexToString(profilePictureHex as `0x${string}`)\n : undefined;\n const profileMetadataJson = profileMetadataJsonHex\n ? hexToString(profileMetadataJsonHex as `0x${string}`)\n : undefined;\n\n // Parse X username, bio, and display name from JSON metadata\n const profileMetadata = profileMetadataJson\n ? parseProfileMetadata(profileMetadataJson)\n : undefined;\n const xUsername = profileMetadata?.x_username;\n const bio = profileMetadata?.bio;\n const displayName = profileMetadata?.display_name;\n const tokenAddress = profileMetadata?.token_address;\n\n return {\n profilePicture: profilePicture || undefined,\n xUsername: xUsername || undefined,\n bio: bio || undefined,\n displayName: displayName || undefined,\n tokenAddress: tokenAddress || undefined,\n isLoading,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/hooks/useProfilePicture.ts","../src/utils.ts","../src/hooks/useProfileXUsername.ts","../src/hooks/useProfileCanvas.ts","../src/hooks/useProfileCSS.ts","../src/hooks/useBasicUserProfileMetadata.ts"],"names":["useStorage","getStorageKeyBytes","hexToString","useReadContracts","STORAGE_CONTRACT"],"mappings":"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","file":"react.js","sourcesContent":["/**\n * Profile-related storage keys\n *\n * Using descriptive keys under 32 bytes to avoid hashing complexity and work seamlessly\n * with existing storage infrastructure. The key includes app prefix and versioning for\n * clarity and future-proofing.\n *\n * NOTE: if we change these keys, users will not be able to see their profile data\n */\nexport const PROFILE_CANVAS_STORAGE_KEY = \"net-beta0.0.1-profile-canvas\";\nexport const PROFILE_PICTURE_STORAGE_KEY = \"net-beta0.0.1-profile-picture\";\nexport const PROFILE_X_USERNAME_STORAGE_KEY =\n \"net-beta0.0.1-profile-x-username\";\nexport const PROFILE_METADATA_STORAGE_KEY = \"net-beta0.0.1-profile-metadata\";\n\n/**\n * Topic strings used when writing to storage\n * These are the second argument to Storage.put()\n */\nexport const PROFILE_PICTURE_TOPIC = \"profile-picture\";\nexport const PROFILE_METADATA_TOPIC = \"profile-metadata\";\nexport const PROFILE_CANVAS_TOPIC = \"profile-canvas\";\nexport const PROFILE_CSS_STORAGE_KEY = \"net-beta0.0.1-profile-css\";\nexport const PROFILE_CSS_TOPIC = \"profile-css\";\n","import { useStorage } from \"@net-protocol/storage/react\";\nimport { getStorageKeyBytes } from \"@net-protocol/storage\";\nimport { PROFILE_PICTURE_STORAGE_KEY } from \"../constants\";\nimport { hexToString } from \"viem\";\nimport type { UseProfileOptions } from \"../types\";\n\n/**\n * Hook to fetch profile picture URL from storage\n *\n * @param options - Chain ID and user address to fetch profile for\n * @returns Profile picture URL and loading state\n *\n * @example\n * ```tsx\n * const { profilePicture, isLoading } = useProfilePicture({\n * chainId: 8453,\n * userAddress: \"0x...\",\n * });\n *\n * if (profilePicture) {\n * return <img src={profilePicture} alt=\"Profile\" />;\n * }\n * ```\n */\nexport function useProfilePicture({\n chainId,\n userAddress,\n enabled = true,\n}: UseProfileOptions) {\n const { data, isLoading, error } = useStorage({\n chainId,\n key: getStorageKeyBytes(PROFILE_PICTURE_STORAGE_KEY),\n operatorAddress: userAddress,\n enabled,\n });\n\n // Convert hex value to string URL\n const profilePicture = data?.value ? hexToString(data.value as any) : undefined;\n\n return {\n profilePicture: profilePicture || undefined,\n isLoading,\n error,\n // Also expose raw data for advanced use cases\n rawData: data,\n };\n}\n","import { stringToHex } from \"viem\";\nimport { toBytes32 } from \"@net-protocol/core\";\nimport {\n PROFILE_PICTURE_STORAGE_KEY,\n PROFILE_METADATA_STORAGE_KEY,\n PROFILE_CANVAS_STORAGE_KEY,\n PROFILE_CSS_STORAGE_KEY,\n PROFILE_PICTURE_TOPIC,\n PROFILE_METADATA_TOPIC,\n PROFILE_CANVAS_TOPIC,\n PROFILE_CSS_TOPIC,\n} from \"./constants\";\nimport type { ProfileMetadata, ProfileStorageArgs } from \"./types\";\n\n/**\n * Convert a string value to hex for storage\n */\nexport function getValueArgForStorage(value: string): `0x${string}` {\n return stringToHex(value);\n}\n\n/**\n * Get storage args (key as bytes32, value as hex)\n */\nexport function getBytesArgsForStorage(\n key: string,\n value: string\n): { bytesKey: `0x${string}`; bytesValue: `0x${string}` } {\n const bytesKey = toBytes32(key) as `0x${string}`;\n const bytesValue = getValueArgForStorage(value);\n return { bytesKey, bytesValue };\n}\n\n/**\n * Prepare transaction arguments for updating profile picture\n *\n * @param imageUrl - The URL of the profile picture\n * @returns Arguments for Storage.put() - [bytesKey, topic, bytesValue]\n *\n * @example\n * ```ts\n * const args = getProfilePictureStorageArgs(\"https://example.com/image.jpg\");\n * // Use with wagmi writeContract:\n * writeContract({\n * abi: STORAGE_CONTRACT.abi,\n * address: STORAGE_CONTRACT.address,\n * functionName: \"put\",\n * args: [args.bytesKey, args.topic, args.bytesValue],\n * });\n * ```\n */\nexport function getProfilePictureStorageArgs(\n imageUrl: string\n): ProfileStorageArgs {\n const { bytesKey, bytesValue } = getBytesArgsForStorage(\n PROFILE_PICTURE_STORAGE_KEY,\n imageUrl\n );\n return {\n bytesKey,\n topic: PROFILE_PICTURE_TOPIC,\n bytesValue,\n };\n}\n\n/**\n * Prepare transaction arguments for updating profile metadata (X username, etc.)\n *\n * @param metadata - Profile metadata object to store\n * @returns Arguments for Storage.put() - [bytesKey, topic, bytesValue]\n *\n * @example\n * ```ts\n * const args = getProfileMetadataStorageArgs({ x_username: \"myusername\" });\n * writeContract({\n * abi: STORAGE_CONTRACT.abi,\n * address: STORAGE_CONTRACT.address,\n * functionName: \"put\",\n * args: [args.bytesKey, args.topic, args.bytesValue],\n * });\n * ```\n */\nexport function getProfileMetadataStorageArgs(\n metadata: ProfileMetadata\n): ProfileStorageArgs {\n const jsonString = JSON.stringify(metadata);\n const { bytesKey, bytesValue } = getBytesArgsForStorage(\n PROFILE_METADATA_STORAGE_KEY,\n jsonString\n );\n return {\n bytesKey,\n topic: PROFILE_METADATA_TOPIC,\n bytesValue,\n };\n}\n\n/**\n * Prepare transaction arguments for updating X username\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * Note: Username is stored WITHOUT the @ prefix. The @ is stripped if provided.\n *\n * @param username - X/Twitter username (with or without @)\n * @returns Arguments for Storage.put()\n */\nexport function getXUsernameStorageArgs(username: string): ProfileStorageArgs {\n // Strip @ prefix if present - store username without @\n const normalizedUsername = username.startsWith(\"@\")\n ? username.slice(1)\n : username;\n return getProfileMetadataStorageArgs({ x_username: normalizedUsername });\n}\n\n/**\n * Prepare transaction arguments for updating profile canvas (HTML content)\n *\n * @param htmlContent - HTML content for the profile canvas\n * @returns Arguments for Storage.put()\n *\n * @example\n * ```ts\n * const args = getProfileCanvasStorageArgs(\"<div>My custom profile</div>\");\n * writeContract({\n * abi: STORAGE_CONTRACT.abi,\n * address: STORAGE_CONTRACT.address,\n * functionName: \"put\",\n * args: [args.bytesKey, args.topic, args.bytesValue],\n * });\n * ```\n */\nexport function getProfileCanvasStorageArgs(\n htmlContent: string\n): ProfileStorageArgs {\n const { bytesKey, bytesValue } = getBytesArgsForStorage(\n PROFILE_CANVAS_STORAGE_KEY,\n htmlContent\n );\n return {\n bytesKey,\n topic: PROFILE_CANVAS_TOPIC,\n bytesValue,\n };\n}\n\n/**\n * Parse profile metadata JSON and extract profile data\n *\n * @param jsonData - JSON string from storage\n * @returns Parsed profile metadata or undefined if invalid\n */\nexport function parseProfileMetadata(\n jsonData: string\n): ProfileMetadata | undefined {\n try {\n const parsed = JSON.parse(jsonData);\n const storedUsername =\n parsed?.x_username &&\n typeof parsed.x_username === \"string\" &&\n parsed.x_username.length > 0\n ? parsed.x_username\n : undefined;\n\n // Strip @ if present for backwards compatibility with older stored data\n const usernameWithoutAt = storedUsername?.startsWith(\"@\")\n ? storedUsername.slice(1)\n : storedUsername;\n\n // Extract bio if present\n const bio =\n parsed?.bio && typeof parsed.bio === \"string\" && parsed.bio.length > 0\n ? parsed.bio\n : undefined;\n\n // Extract display name if present\n const display_name =\n parsed?.display_name &&\n typeof parsed.display_name === \"string\" &&\n parsed.display_name.length > 0\n ? parsed.display_name\n : undefined;\n\n // Extract token address if present (stored as lowercase)\n const token_address =\n parsed?.token_address &&\n typeof parsed.token_address === \"string\" &&\n parsed.token_address.length > 0\n ? parsed.token_address.toLowerCase()\n : undefined;\n\n return {\n x_username: usernameWithoutAt,\n bio,\n display_name,\n token_address,\n };\n } catch {\n return undefined;\n }\n}\n\n/**\n * Validate that a string is a valid URL\n */\nexport function isValidUrl(url: string): boolean {\n if (!url) return false;\n try {\n new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Validate X/Twitter username format\n * Returns true if valid (alphanumeric and underscores, 1-15 chars)\n */\nexport function isValidXUsername(username: string): boolean {\n if (!username) return false;\n // Remove @ prefix if present\n const cleanUsername = username.startsWith(\"@\") ? username.slice(1) : username;\n // X usernames: 1-15 chars, alphanumeric and underscores only\n return /^[a-zA-Z0-9_]{1,15}$/.test(cleanUsername);\n}\n\n/**\n * Validate bio format\n * Returns true if valid (max 280 chars, no control characters except newlines)\n */\nexport function isValidBio(bio: string): boolean {\n if (!bio) return false;\n if (bio.length > 280) return false;\n // Allow printable characters, spaces, and newlines. Disallow other control chars.\n // eslint-disable-next-line no-control-regex\n const hasControlChars = /[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]/.test(bio);\n return !hasControlChars;\n}\n\n/**\n * Validate display name format\n * Returns true if valid (1-25 characters, no control characters except spaces)\n */\nexport function isValidDisplayName(displayName: string): boolean {\n if (!displayName) return false;\n if (displayName.length > 25) return false;\n // Disallow control characters\n // eslint-disable-next-line no-control-regex\n const hasControlChars = /[\\x00-\\x1F\\x7F]/.test(displayName);\n return !hasControlChars;\n}\n\n/**\n * Prepare transaction arguments for updating bio\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * @param bio - The bio text\n * @returns Arguments for Storage.put()\n */\nexport function getBioStorageArgs(bio: string): ProfileStorageArgs {\n return getProfileMetadataStorageArgs({ bio });\n}\n\n/**\n * Prepare transaction arguments for updating display name\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * @param displayName - The display name (max 25 characters)\n * @returns Arguments for Storage.put()\n */\nexport function getDisplayNameStorageArgs(\n displayName: string\n): ProfileStorageArgs {\n return getProfileMetadataStorageArgs({ display_name: displayName });\n}\n\n/**\n * Prepare transaction arguments for updating token address\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * @param tokenAddress - The ERC-20 token contract address (stored as lowercase)\n * @returns Arguments for Storage.put()\n */\nexport function getTokenAddressStorageArgs(\n tokenAddress: string\n): ProfileStorageArgs {\n return getProfileMetadataStorageArgs({\n token_address: tokenAddress.toLowerCase(),\n });\n}\n\n/**\n * Validate that a string is a valid EVM token address\n * Returns true if valid (0x-prefixed, 40 hex characters)\n */\nexport function isValidTokenAddress(address: string): boolean {\n if (!address) return false;\n return /^0x[a-fA-F0-9]{40}$/.test(address);\n}\n\n/**\n * Maximum CSS size in bytes (10KB — CSS should be small)\n */\nexport const MAX_CSS_SIZE = 10 * 1024;\n\n/**\n * Prepare transaction arguments for updating profile custom CSS\n *\n * @param cssContent - CSS string to store\n * @returns Arguments for Storage.put() - [bytesKey, topic, bytesValue]\n *\n * @example\n * ```ts\n * const args = getProfileCSSStorageArgs(\".profile-themed { --primary: 210 40% 98%; }\");\n * writeContract({\n * abi: STORAGE_CONTRACT.abi,\n * address: STORAGE_CONTRACT.address,\n * functionName: \"put\",\n * args: [args.bytesKey, args.topic, args.bytesValue],\n * });\n * ```\n */\nexport function getProfileCSSStorageArgs(\n cssContent: string\n): ProfileStorageArgs {\n const { bytesKey, bytesValue } = getBytesArgsForStorage(\n PROFILE_CSS_STORAGE_KEY,\n cssContent\n );\n return {\n bytesKey,\n topic: PROFILE_CSS_TOPIC,\n bytesValue,\n };\n}\n\n/**\n * Validate CSS content\n * Returns true if valid (non-empty, within size limit, no script injection)\n */\nexport function isValidCSS(css: string): boolean {\n if (!css || css.trim().length === 0) return false;\n if (Buffer.byteLength(css, \"utf-8\") > MAX_CSS_SIZE) return false;\n // Block script injection via CSS expressions/behavior/url(javascript:)\n const lowerCSS = css.toLowerCase();\n if (lowerCSS.includes(\"expression(\")) return false;\n if (lowerCSS.includes(\"behavior:\")) return false;\n if (lowerCSS.includes(\"javascript:\")) return false;\n if (/<script/i.test(css)) return false;\n return true;\n}\n","import { useStorage } from \"@net-protocol/storage/react\";\nimport { getStorageKeyBytes } from \"@net-protocol/storage\";\nimport { PROFILE_METADATA_STORAGE_KEY } from \"../constants\";\nimport { hexToString } from \"viem\";\nimport { parseProfileMetadata } from \"../utils\";\nimport type { UseProfileOptions } from \"../types\";\n\n/**\n * Hook to fetch X/Twitter username from profile metadata storage\n *\n * @param options - Chain ID and user address to fetch profile for\n * @returns X username (without @) and loading state\n *\n * @example\n * ```tsx\n * const { xUsername, isLoading } = useProfileXUsername({\n * chainId: 8453,\n * userAddress: \"0x...\",\n * });\n *\n * if (xUsername) {\n * return <a href={`https://x.com/${xUsername}`}>@{xUsername}</a>;\n * }\n * ```\n */\nexport function useProfileXUsername({\n chainId,\n userAddress,\n enabled = true,\n}: UseProfileOptions) {\n const { data, isLoading, error } = useStorage({\n chainId,\n key: getStorageKeyBytes(PROFILE_METADATA_STORAGE_KEY),\n operatorAddress: userAddress,\n enabled,\n });\n\n // Parse JSON metadata to extract X username\n let xUsername: string | undefined;\n if (data?.value) {\n try {\n const jsonString = hexToString(data.value as any);\n const metadata = parseProfileMetadata(jsonString);\n xUsername = metadata?.x_username;\n } catch {\n // Invalid JSON or hex data\n }\n }\n\n return {\n xUsername,\n isLoading,\n error,\n // Also expose raw data for advanced use cases\n rawData: data,\n };\n}\n","import { useStorage } from \"@net-protocol/storage/react\";\nimport { getStorageKeyBytes } from \"@net-protocol/storage\";\nimport { PROFILE_CANVAS_STORAGE_KEY } from \"../constants\";\nimport { hexToString } from \"viem\";\nimport type { UseProfileOptions } from \"../types\";\n\n/**\n * Hook to fetch profile canvas HTML content from storage\n *\n * Note: For large canvas content (>20KB), you may need to use XML storage.\n * This hook fetches from regular storage. For large content, consider using\n * useXmlStorage from @net-protocol/storage/react directly.\n *\n * @param options - Chain ID and user address to fetch profile for\n * @returns Canvas HTML content and loading state\n *\n * @example\n * ```tsx\n * const { canvas, isLoading } = useProfileCanvas({\n * chainId: 8453,\n * userAddress: \"0x...\",\n * });\n *\n * if (canvas) {\n * return <iframe srcDoc={canvas} />;\n * }\n * ```\n */\nexport function useProfileCanvas({\n chainId,\n userAddress,\n enabled = true,\n}: UseProfileOptions) {\n const { data, isLoading, error } = useStorage({\n chainId,\n key: getStorageKeyBytes(PROFILE_CANVAS_STORAGE_KEY),\n operatorAddress: userAddress,\n enabled,\n // Use router for automatic chunked storage detection\n useRouter: true,\n });\n\n // Convert hex value to string HTML\n const canvas = data?.value ? hexToString(data.value as any) : undefined;\n\n return {\n canvas: canvas || undefined,\n isLoading,\n error,\n // Also expose raw data for advanced use cases\n rawData: data,\n };\n}\n","import { useStorage } from \"@net-protocol/storage/react\";\nimport { getStorageKeyBytes } from \"@net-protocol/storage\";\nimport { PROFILE_CSS_STORAGE_KEY } from \"../constants\";\nimport { hexToString } from \"viem\";\nimport type { UseProfileOptions } from \"../types\";\n\n/**\n * Hook to fetch profile custom CSS from storage\n *\n * @param options - Chain ID and user address to fetch profile for\n * @returns Custom CSS string and loading state\n *\n * @example\n * ```tsx\n * const { css, isLoading } = useProfileCSS({\n * chainId: 8453,\n * userAddress: \"0x...\",\n * });\n *\n * if (css) {\n * return <style>{`.profile-themed { ${css} }`}</style>;\n * }\n * ```\n */\nexport function useProfileCSS({\n chainId,\n userAddress,\n enabled = true,\n}: UseProfileOptions) {\n const { data, isLoading, error } = useStorage({\n chainId,\n key: getStorageKeyBytes(PROFILE_CSS_STORAGE_KEY),\n operatorAddress: userAddress,\n enabled,\n });\n\n // Convert hex value to string CSS\n const css = data?.value ? hexToString(data.value as any) : undefined;\n\n return {\n css: css || undefined,\n isLoading,\n error,\n // Also expose raw data for advanced use cases\n rawData: data,\n };\n}\n","import { useReadContracts } from \"wagmi\";\nimport { STORAGE_CONTRACT, getStorageKeyBytes } from \"@net-protocol/storage\";\nimport {\n PROFILE_PICTURE_STORAGE_KEY,\n PROFILE_METADATA_STORAGE_KEY,\n} from \"../constants\";\nimport { hexToString } from \"viem\";\nimport { parseProfileMetadata } from \"../utils\";\nimport type { BasicUserProfileMetadata, UseProfileOptions } from \"../types\";\n\n/**\n * Hook to fetch basic user profile metadata (profile picture and X username) in a single call\n *\n * This hook is optimized for efficiency - it batches multiple storage reads into a single\n * contract call using wagmi's useReadContracts.\n *\n * @param options - Chain ID and user address to fetch profile for\n * @returns Profile picture URL, X username, and loading state\n *\n * @example\n * ```tsx\n * const { profilePicture, xUsername, isLoading } = useBasicUserProfileMetadata({\n * chainId: 8453,\n * userAddress: \"0x...\",\n * });\n *\n * return (\n * <div>\n * {profilePicture && <img src={profilePicture} alt=\"Profile\" />}\n * {xUsername && <span>@{xUsername}</span>}\n * </div>\n * );\n * ```\n */\nexport function useBasicUserProfileMetadata({\n chainId,\n userAddress,\n enabled = true,\n}: UseProfileOptions): BasicUserProfileMetadata {\n const profilePictureKey = getStorageKeyBytes(PROFILE_PICTURE_STORAGE_KEY);\n const profileMetadataKey = getStorageKeyBytes(PROFILE_METADATA_STORAGE_KEY);\n\n const {\n data: results,\n isLoading,\n error,\n } = useReadContracts({\n contracts: [\n {\n address: STORAGE_CONTRACT.address as `0x${string}`,\n abi: STORAGE_CONTRACT.abi,\n functionName: \"get\",\n args: [profilePictureKey, userAddress as `0x${string}`],\n chainId,\n },\n {\n address: STORAGE_CONTRACT.address as `0x${string}`,\n abi: STORAGE_CONTRACT.abi,\n functionName: \"get\",\n args: [profileMetadataKey, userAddress as `0x${string}`],\n chainId,\n },\n ],\n query: {\n enabled: enabled && !!userAddress && !!chainId,\n },\n });\n\n // Parse the results - decode hex data\n // Contract returns tuple [string text, bytes data]\n const profilePictureHex = (results as any)?.[0]?.result?.[1] || undefined;\n const profileMetadataJsonHex = (results as any)?.[1]?.result?.[1] || undefined;\n\n const profilePicture = profilePictureHex\n ? hexToString(profilePictureHex as `0x${string}`)\n : undefined;\n const profileMetadataJson = profileMetadataJsonHex\n ? hexToString(profileMetadataJsonHex as `0x${string}`)\n : undefined;\n\n // Parse X username, bio, and display name from JSON metadata\n const profileMetadata = profileMetadataJson\n ? parseProfileMetadata(profileMetadataJson)\n : undefined;\n const xUsername = profileMetadata?.x_username;\n const bio = profileMetadata?.bio;\n const displayName = profileMetadata?.display_name;\n const tokenAddress = profileMetadata?.token_address;\n\n return {\n profilePicture: profilePicture || undefined,\n xUsername: xUsername || undefined,\n bio: bio || undefined,\n displayName: displayName || undefined,\n tokenAddress: tokenAddress || undefined,\n isLoading,\n };\n}\n"]}
|
package/dist/react.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import { useReadContracts } from 'wagmi';
|
|
|
10
10
|
var PROFILE_CANVAS_STORAGE_KEY = "net-beta0.0.1-profile-canvas";
|
|
11
11
|
var PROFILE_PICTURE_STORAGE_KEY = "net-beta0.0.1-profile-picture";
|
|
12
12
|
var PROFILE_METADATA_STORAGE_KEY = "net-beta0.0.1-profile-metadata";
|
|
13
|
+
var PROFILE_CSS_STORAGE_KEY = "net-beta0.0.1-profile-css";
|
|
13
14
|
function useProfilePicture({
|
|
14
15
|
chainId,
|
|
15
16
|
userAddress,
|
|
@@ -100,6 +101,26 @@ function useProfileCanvas({
|
|
|
100
101
|
rawData: data
|
|
101
102
|
};
|
|
102
103
|
}
|
|
104
|
+
function useProfileCSS({
|
|
105
|
+
chainId,
|
|
106
|
+
userAddress,
|
|
107
|
+
enabled = true
|
|
108
|
+
}) {
|
|
109
|
+
const { data, isLoading, error } = useStorage({
|
|
110
|
+
chainId,
|
|
111
|
+
key: getStorageKeyBytes(PROFILE_CSS_STORAGE_KEY),
|
|
112
|
+
operatorAddress: userAddress,
|
|
113
|
+
enabled
|
|
114
|
+
});
|
|
115
|
+
const css = data?.value ? hexToString(data.value) : void 0;
|
|
116
|
+
return {
|
|
117
|
+
css: css || void 0,
|
|
118
|
+
isLoading,
|
|
119
|
+
error,
|
|
120
|
+
// Also expose raw data for advanced use cases
|
|
121
|
+
rawData: data
|
|
122
|
+
};
|
|
123
|
+
}
|
|
103
124
|
function useBasicUserProfileMetadata({
|
|
104
125
|
chainId,
|
|
105
126
|
userAddress,
|
|
@@ -151,6 +172,6 @@ function useBasicUserProfileMetadata({
|
|
|
151
172
|
};
|
|
152
173
|
}
|
|
153
174
|
|
|
154
|
-
export { useBasicUserProfileMetadata, useProfileCanvas, useProfilePicture, useProfileXUsername };
|
|
175
|
+
export { useBasicUserProfileMetadata, useProfileCSS, useProfileCanvas, useProfilePicture, useProfileXUsername };
|
|
155
176
|
//# sourceMappingURL=react.mjs.map
|
|
156
177
|
//# sourceMappingURL=react.mjs.map
|