@net-protocol/profiles 0.1.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/README.md ADDED
@@ -0,0 +1,147 @@
1
+ # @net-protocol/profiles
2
+
3
+ Net Profiles SDK for reading and writing user profile data on the Net protocol.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @net-protocol/profiles
9
+ # or
10
+ yarn add @net-protocol/profiles
11
+ ```
12
+
13
+ ## Features
14
+
15
+ - **Read profile data**: Profile picture, X username, canvas content
16
+ - **Write profile data**: Utilities to prepare Storage.put() transactions
17
+ - **Efficient batch reads**: `useBasicUserProfileMetadata` batches multiple reads
18
+ - **Built on net-storage**: Uses the Net Storage SDK for underlying storage operations
19
+
20
+ ## Usage
21
+
22
+ ### Reading Profile Data (React)
23
+
24
+ ```tsx
25
+ import {
26
+ useProfilePicture,
27
+ useProfileXUsername,
28
+ useBasicUserProfileMetadata,
29
+ } from "@net-protocol/profiles/react";
30
+
31
+ function UserProfile({ address }: { address: string }) {
32
+ // Option 1: Individual hooks
33
+ const { profilePicture, isLoading: pictureLoading } = useProfilePicture({
34
+ chainId: 8453, // Base
35
+ userAddress: address,
36
+ });
37
+
38
+ const { xUsername, isLoading: usernameLoading } = useProfileXUsername({
39
+ chainId: 8453,
40
+ userAddress: address,
41
+ });
42
+
43
+ // Option 2: Batch read (more efficient)
44
+ const { profilePicture, xUsername, isLoading } = useBasicUserProfileMetadata({
45
+ chainId: 8453,
46
+ userAddress: address,
47
+ });
48
+
49
+ return (
50
+ <div>
51
+ {profilePicture && <img src={profilePicture} alt="Profile" />}
52
+ {xUsername && <a href={`https://x.com/${xUsername}`}>@{xUsername}</a>}
53
+ </div>
54
+ );
55
+ }
56
+ ```
57
+
58
+ ### Writing Profile Data
59
+
60
+ ```tsx
61
+ import { useWriteContract } from "wagmi";
62
+ import {
63
+ getProfilePictureStorageArgs,
64
+ getXUsernameStorageArgs,
65
+ STORAGE_CONTRACT,
66
+ } from "@net-protocol/profiles";
67
+
68
+ function UpdateProfile() {
69
+ const { writeContract } = useWriteContract();
70
+
71
+ const handleUpdatePicture = (imageUrl: string) => {
72
+ const args = getProfilePictureStorageArgs(imageUrl);
73
+ writeContract({
74
+ abi: STORAGE_CONTRACT.abi,
75
+ address: STORAGE_CONTRACT.address,
76
+ functionName: "put",
77
+ args: [args.bytesKey, args.topic, args.bytesValue],
78
+ });
79
+ };
80
+
81
+ const handleUpdateXUsername = (username: string) => {
82
+ const args = getXUsernameStorageArgs(username);
83
+ writeContract({
84
+ abi: STORAGE_CONTRACT.abi,
85
+ address: STORAGE_CONTRACT.address,
86
+ functionName: "put",
87
+ args: [args.bytesKey, args.topic, args.bytesValue],
88
+ });
89
+ };
90
+
91
+ return (
92
+ <form>
93
+ {/* Form fields */}
94
+ </form>
95
+ );
96
+ }
97
+ ```
98
+
99
+ ## Profile Fields
100
+
101
+ | Field | Description | Notes |
102
+ |-------|-------------|-------|
103
+ | Profile Picture | URL to your profile image | Any valid URL (HTTPS, IPFS, etc.) |
104
+ | X Username | Your X (Twitter) handle | Stored with @ prefix, displayed without |
105
+ | Canvas | Custom HTML profile page | For advanced customization |
106
+
107
+ ## Storage Keys
108
+
109
+ | Key | Description | Data Format |
110
+ |-----|-------------|-------------|
111
+ | `PROFILE_PICTURE_STORAGE_KEY` | Profile picture URL | Plain string (URL) |
112
+ | `PROFILE_METADATA_STORAGE_KEY` | Profile metadata JSON | `{ x_username: "@handle" }` |
113
+ | `PROFILE_CANVAS_STORAGE_KEY` | Custom HTML canvas | HTML string |
114
+
115
+ ## API Reference
116
+
117
+ ### Hooks (from `@net-protocol/profiles/react`)
118
+
119
+ - `useProfilePicture({ chainId, userAddress })` - Fetch profile picture URL
120
+ - `useProfileXUsername({ chainId, userAddress })` - Fetch X username
121
+ - `useProfileCanvas({ chainId, userAddress })` - Fetch canvas HTML
122
+ - `useBasicUserProfileMetadata({ chainId, userAddress })` - Batch fetch picture & username
123
+
124
+ ### Utilities (from `@net-protocol/profiles`)
125
+
126
+ - `getProfilePictureStorageArgs(imageUrl)` - Prepare picture update args
127
+ - `getXUsernameStorageArgs(username)` - Prepare X username update args
128
+ - `getProfileMetadataStorageArgs(metadata)` - Prepare metadata update args
129
+ - `getProfileCanvasStorageArgs(html)` - Prepare canvas update args
130
+ - `parseProfileMetadata(json)` - Parse metadata JSON
131
+ - `isValidUrl(url)` - Validate URL format
132
+ - `isValidXUsername(username)` - Validate X username format
133
+
134
+ ## Dependencies
135
+
136
+ - `@net-protocol/storage` - Storage SDK
137
+ - `@net-protocol/core` - Core utilities
138
+ - `viem` - Ethereum utilities
139
+
140
+ ### Peer Dependencies (for React hooks)
141
+
142
+ - `react` ^18.0.0
143
+ - `wagmi` ^2.15.0
144
+
145
+ ## License
146
+
147
+ MIT
@@ -0,0 +1,117 @@
1
+ import { P as ProfileStorageArgs, a as ProfileMetadata } from './types-DTmd-Ngx.mjs';
2
+ export { B as BasicUserProfileMetadata, b as UseProfileOptions, U as UserDisplayName, c as UserProfile } from './types-DTmd-Ngx.mjs';
3
+ export { STORAGE_CONTRACT } from '@net-protocol/storage';
4
+
5
+ /**
6
+ * Profile-related storage keys
7
+ *
8
+ * Using descriptive keys under 32 bytes to avoid hashing complexity and work seamlessly
9
+ * with existing storage infrastructure. The key includes app prefix and versioning for
10
+ * clarity and future-proofing.
11
+ *
12
+ * NOTE: if we change these keys, users will not be able to see their profile data
13
+ */
14
+ declare const PROFILE_CANVAS_STORAGE_KEY = "net-beta0.0.1-profile-canvas";
15
+ declare const PROFILE_PICTURE_STORAGE_KEY = "net-beta0.0.1-profile-picture";
16
+ declare const PROFILE_X_USERNAME_STORAGE_KEY = "net-beta0.0.1-profile-x-username";
17
+ declare const PROFILE_METADATA_STORAGE_KEY = "net-beta0.0.1-profile-metadata";
18
+ /**
19
+ * Topic strings used when writing to storage
20
+ * These are the second argument to Storage.put()
21
+ */
22
+ declare const PROFILE_PICTURE_TOPIC = "profile-picture";
23
+ declare const PROFILE_METADATA_TOPIC = "profile-metadata";
24
+ declare const PROFILE_CANVAS_TOPIC = "profile-canvas";
25
+
26
+ /**
27
+ * Convert a string value to hex for storage
28
+ */
29
+ declare function getValueArgForStorage(value: string): `0x${string}`;
30
+ /**
31
+ * Get storage args (key as bytes32, value as hex)
32
+ */
33
+ declare function getBytesArgsForStorage(key: string, value: string): {
34
+ bytesKey: `0x${string}`;
35
+ bytesValue: `0x${string}`;
36
+ };
37
+ /**
38
+ * Prepare transaction arguments for updating profile picture
39
+ *
40
+ * @param imageUrl - The URL of the profile picture
41
+ * @returns Arguments for Storage.put() - [bytesKey, topic, bytesValue]
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * const args = getProfilePictureStorageArgs("https://example.com/image.jpg");
46
+ * // Use with wagmi writeContract:
47
+ * writeContract({
48
+ * abi: STORAGE_CONTRACT.abi,
49
+ * address: STORAGE_CONTRACT.address,
50
+ * functionName: "put",
51
+ * args: [args.bytesKey, args.topic, args.bytesValue],
52
+ * });
53
+ * ```
54
+ */
55
+ declare function getProfilePictureStorageArgs(imageUrl: string): ProfileStorageArgs;
56
+ /**
57
+ * Prepare transaction arguments for updating profile metadata (X username, etc.)
58
+ *
59
+ * @param metadata - Profile metadata object to store
60
+ * @returns Arguments for Storage.put() - [bytesKey, topic, bytesValue]
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * const args = getProfileMetadataStorageArgs({ x_username: "@myusername" });
65
+ * writeContract({
66
+ * abi: STORAGE_CONTRACT.abi,
67
+ * address: STORAGE_CONTRACT.address,
68
+ * functionName: "put",
69
+ * args: [args.bytesKey, args.topic, args.bytesValue],
70
+ * });
71
+ * ```
72
+ */
73
+ declare function getProfileMetadataStorageArgs(metadata: ProfileMetadata): ProfileStorageArgs;
74
+ /**
75
+ * Prepare transaction arguments for updating X username
76
+ * This is a convenience wrapper around getProfileMetadataStorageArgs
77
+ *
78
+ * @param username - X/Twitter username (with or without @)
79
+ * @returns Arguments for Storage.put()
80
+ */
81
+ declare function getXUsernameStorageArgs(username: string): ProfileStorageArgs;
82
+ /**
83
+ * Prepare transaction arguments for updating profile canvas (HTML content)
84
+ *
85
+ * @param htmlContent - HTML content for the profile canvas
86
+ * @returns Arguments for Storage.put()
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * const args = getProfileCanvasStorageArgs("<div>My custom profile</div>");
91
+ * writeContract({
92
+ * abi: STORAGE_CONTRACT.abi,
93
+ * address: STORAGE_CONTRACT.address,
94
+ * functionName: "put",
95
+ * args: [args.bytesKey, args.topic, args.bytesValue],
96
+ * });
97
+ * ```
98
+ */
99
+ declare function getProfileCanvasStorageArgs(htmlContent: string): ProfileStorageArgs;
100
+ /**
101
+ * Parse profile metadata JSON and extract profile data
102
+ *
103
+ * @param jsonData - JSON string from storage
104
+ * @returns Parsed profile metadata or undefined if invalid
105
+ */
106
+ declare function parseProfileMetadata(jsonData: string): ProfileMetadata | undefined;
107
+ /**
108
+ * Validate that a string is a valid URL
109
+ */
110
+ declare function isValidUrl(url: string): boolean;
111
+ /**
112
+ * Validate X/Twitter username format
113
+ * Returns true if valid (alphanumeric and underscores, 1-15 chars)
114
+ */
115
+ declare function isValidXUsername(username: string): boolean;
116
+
117
+ 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, ProfileMetadata, ProfileStorageArgs, getBytesArgsForStorage, getProfileCanvasStorageArgs, getProfileMetadataStorageArgs, getProfilePictureStorageArgs, getValueArgForStorage, getXUsernameStorageArgs, isValidUrl, isValidXUsername, parseProfileMetadata };
@@ -0,0 +1,117 @@
1
+ import { P as ProfileStorageArgs, a as ProfileMetadata } from './types-DTmd-Ngx.js';
2
+ export { B as BasicUserProfileMetadata, b as UseProfileOptions, U as UserDisplayName, c as UserProfile } from './types-DTmd-Ngx.js';
3
+ export { STORAGE_CONTRACT } from '@net-protocol/storage';
4
+
5
+ /**
6
+ * Profile-related storage keys
7
+ *
8
+ * Using descriptive keys under 32 bytes to avoid hashing complexity and work seamlessly
9
+ * with existing storage infrastructure. The key includes app prefix and versioning for
10
+ * clarity and future-proofing.
11
+ *
12
+ * NOTE: if we change these keys, users will not be able to see their profile data
13
+ */
14
+ declare const PROFILE_CANVAS_STORAGE_KEY = "net-beta0.0.1-profile-canvas";
15
+ declare const PROFILE_PICTURE_STORAGE_KEY = "net-beta0.0.1-profile-picture";
16
+ declare const PROFILE_X_USERNAME_STORAGE_KEY = "net-beta0.0.1-profile-x-username";
17
+ declare const PROFILE_METADATA_STORAGE_KEY = "net-beta0.0.1-profile-metadata";
18
+ /**
19
+ * Topic strings used when writing to storage
20
+ * These are the second argument to Storage.put()
21
+ */
22
+ declare const PROFILE_PICTURE_TOPIC = "profile-picture";
23
+ declare const PROFILE_METADATA_TOPIC = "profile-metadata";
24
+ declare const PROFILE_CANVAS_TOPIC = "profile-canvas";
25
+
26
+ /**
27
+ * Convert a string value to hex for storage
28
+ */
29
+ declare function getValueArgForStorage(value: string): `0x${string}`;
30
+ /**
31
+ * Get storage args (key as bytes32, value as hex)
32
+ */
33
+ declare function getBytesArgsForStorage(key: string, value: string): {
34
+ bytesKey: `0x${string}`;
35
+ bytesValue: `0x${string}`;
36
+ };
37
+ /**
38
+ * Prepare transaction arguments for updating profile picture
39
+ *
40
+ * @param imageUrl - The URL of the profile picture
41
+ * @returns Arguments for Storage.put() - [bytesKey, topic, bytesValue]
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * const args = getProfilePictureStorageArgs("https://example.com/image.jpg");
46
+ * // Use with wagmi writeContract:
47
+ * writeContract({
48
+ * abi: STORAGE_CONTRACT.abi,
49
+ * address: STORAGE_CONTRACT.address,
50
+ * functionName: "put",
51
+ * args: [args.bytesKey, args.topic, args.bytesValue],
52
+ * });
53
+ * ```
54
+ */
55
+ declare function getProfilePictureStorageArgs(imageUrl: string): ProfileStorageArgs;
56
+ /**
57
+ * Prepare transaction arguments for updating profile metadata (X username, etc.)
58
+ *
59
+ * @param metadata - Profile metadata object to store
60
+ * @returns Arguments for Storage.put() - [bytesKey, topic, bytesValue]
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * const args = getProfileMetadataStorageArgs({ x_username: "@myusername" });
65
+ * writeContract({
66
+ * abi: STORAGE_CONTRACT.abi,
67
+ * address: STORAGE_CONTRACT.address,
68
+ * functionName: "put",
69
+ * args: [args.bytesKey, args.topic, args.bytesValue],
70
+ * });
71
+ * ```
72
+ */
73
+ declare function getProfileMetadataStorageArgs(metadata: ProfileMetadata): ProfileStorageArgs;
74
+ /**
75
+ * Prepare transaction arguments for updating X username
76
+ * This is a convenience wrapper around getProfileMetadataStorageArgs
77
+ *
78
+ * @param username - X/Twitter username (with or without @)
79
+ * @returns Arguments for Storage.put()
80
+ */
81
+ declare function getXUsernameStorageArgs(username: string): ProfileStorageArgs;
82
+ /**
83
+ * Prepare transaction arguments for updating profile canvas (HTML content)
84
+ *
85
+ * @param htmlContent - HTML content for the profile canvas
86
+ * @returns Arguments for Storage.put()
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * const args = getProfileCanvasStorageArgs("<div>My custom profile</div>");
91
+ * writeContract({
92
+ * abi: STORAGE_CONTRACT.abi,
93
+ * address: STORAGE_CONTRACT.address,
94
+ * functionName: "put",
95
+ * args: [args.bytesKey, args.topic, args.bytesValue],
96
+ * });
97
+ * ```
98
+ */
99
+ declare function getProfileCanvasStorageArgs(htmlContent: string): ProfileStorageArgs;
100
+ /**
101
+ * Parse profile metadata JSON and extract profile data
102
+ *
103
+ * @param jsonData - JSON string from storage
104
+ * @returns Parsed profile metadata or undefined if invalid
105
+ */
106
+ declare function parseProfileMetadata(jsonData: string): ProfileMetadata | undefined;
107
+ /**
108
+ * Validate that a string is a valid URL
109
+ */
110
+ declare function isValidUrl(url: string): boolean;
111
+ /**
112
+ * Validate X/Twitter username format
113
+ * Returns true if valid (alphanumeric and underscores, 1-15 chars)
114
+ */
115
+ declare function isValidXUsername(username: string): boolean;
116
+
117
+ 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, ProfileMetadata, ProfileStorageArgs, getBytesArgsForStorage, getProfileCanvasStorageArgs, getProfileMetadataStorageArgs, getProfilePictureStorageArgs, getValueArgForStorage, getXUsernameStorageArgs, isValidUrl, isValidXUsername, parseProfileMetadata };
package/dist/index.js ADDED
@@ -0,0 +1,109 @@
1
+ 'use strict';
2
+
3
+ var viem = require('viem');
4
+ var core = require('@net-protocol/core');
5
+ var storage = require('@net-protocol/storage');
6
+
7
+ // src/constants.ts
8
+ var PROFILE_CANVAS_STORAGE_KEY = "net-beta0.0.1-profile-canvas";
9
+ var PROFILE_PICTURE_STORAGE_KEY = "net-beta0.0.1-profile-picture";
10
+ var PROFILE_X_USERNAME_STORAGE_KEY = "net-beta0.0.1-profile-x-username";
11
+ var PROFILE_METADATA_STORAGE_KEY = "net-beta0.0.1-profile-metadata";
12
+ var PROFILE_PICTURE_TOPIC = "profile-picture";
13
+ var PROFILE_METADATA_TOPIC = "profile-metadata";
14
+ var PROFILE_CANVAS_TOPIC = "profile-canvas";
15
+ function getValueArgForStorage(value) {
16
+ return viem.stringToHex(value);
17
+ }
18
+ function getBytesArgsForStorage(key, value) {
19
+ const bytesKey = core.toBytes32(key);
20
+ const bytesValue = getValueArgForStorage(value);
21
+ return { bytesKey, bytesValue };
22
+ }
23
+ function getProfilePictureStorageArgs(imageUrl) {
24
+ const { bytesKey, bytesValue } = getBytesArgsForStorage(
25
+ PROFILE_PICTURE_STORAGE_KEY,
26
+ imageUrl
27
+ );
28
+ return {
29
+ bytesKey,
30
+ topic: PROFILE_PICTURE_TOPIC,
31
+ bytesValue
32
+ };
33
+ }
34
+ function getProfileMetadataStorageArgs(metadata) {
35
+ const jsonString = JSON.stringify(metadata);
36
+ const { bytesKey, bytesValue } = getBytesArgsForStorage(
37
+ PROFILE_METADATA_STORAGE_KEY,
38
+ jsonString
39
+ );
40
+ return {
41
+ bytesKey,
42
+ topic: PROFILE_METADATA_TOPIC,
43
+ bytesValue
44
+ };
45
+ }
46
+ function getXUsernameStorageArgs(username) {
47
+ const normalizedUsername = username.startsWith("@") ? username : `@${username}`;
48
+ return getProfileMetadataStorageArgs({ x_username: normalizedUsername });
49
+ }
50
+ function getProfileCanvasStorageArgs(htmlContent) {
51
+ const { bytesKey, bytesValue } = getBytesArgsForStorage(
52
+ PROFILE_CANVAS_STORAGE_KEY,
53
+ htmlContent
54
+ );
55
+ return {
56
+ bytesKey,
57
+ topic: PROFILE_CANVAS_TOPIC,
58
+ bytesValue
59
+ };
60
+ }
61
+ function parseProfileMetadata(jsonData) {
62
+ try {
63
+ const parsed = JSON.parse(jsonData);
64
+ const storedUsername = parsed?.x_username && typeof parsed.x_username === "string" && parsed.x_username.length > 0 ? parsed.x_username : void 0;
65
+ const usernameWithoutAt = storedUsername?.startsWith("@") ? storedUsername.slice(1) : storedUsername;
66
+ return {
67
+ x_username: usernameWithoutAt
68
+ };
69
+ } catch {
70
+ return void 0;
71
+ }
72
+ }
73
+ function isValidUrl(url) {
74
+ if (!url) return false;
75
+ try {
76
+ new URL(url);
77
+ return true;
78
+ } catch {
79
+ return false;
80
+ }
81
+ }
82
+ function isValidXUsername(username) {
83
+ if (!username) return false;
84
+ const cleanUsername = username.startsWith("@") ? username.slice(1) : username;
85
+ return /^[a-zA-Z0-9_]{1,15}$/.test(cleanUsername);
86
+ }
87
+
88
+ Object.defineProperty(exports, "STORAGE_CONTRACT", {
89
+ enumerable: true,
90
+ get: function () { return storage.STORAGE_CONTRACT; }
91
+ });
92
+ exports.PROFILE_CANVAS_STORAGE_KEY = PROFILE_CANVAS_STORAGE_KEY;
93
+ exports.PROFILE_CANVAS_TOPIC = PROFILE_CANVAS_TOPIC;
94
+ exports.PROFILE_METADATA_STORAGE_KEY = PROFILE_METADATA_STORAGE_KEY;
95
+ exports.PROFILE_METADATA_TOPIC = PROFILE_METADATA_TOPIC;
96
+ exports.PROFILE_PICTURE_STORAGE_KEY = PROFILE_PICTURE_STORAGE_KEY;
97
+ exports.PROFILE_PICTURE_TOPIC = PROFILE_PICTURE_TOPIC;
98
+ exports.PROFILE_X_USERNAME_STORAGE_KEY = PROFILE_X_USERNAME_STORAGE_KEY;
99
+ exports.getBytesArgsForStorage = getBytesArgsForStorage;
100
+ exports.getProfileCanvasStorageArgs = getProfileCanvasStorageArgs;
101
+ exports.getProfileMetadataStorageArgs = getProfileMetadataStorageArgs;
102
+ exports.getProfilePictureStorageArgs = getProfilePictureStorageArgs;
103
+ exports.getValueArgForStorage = getValueArgForStorage;
104
+ exports.getXUsernameStorageArgs = getXUsernameStorageArgs;
105
+ exports.isValidUrl = isValidUrl;
106
+ exports.isValidXUsername = isValidXUsername;
107
+ exports.parseProfileMetadata = parseProfileMetadata;
108
+ //# sourceMappingURL=index.js.map
109
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts","../src/utils.ts"],"names":["stringToHex","toBytes32"],"mappings":"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","file":"index.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 { 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 * @param username - X/Twitter username (with or without @)\n * @returns Arguments for Storage.put()\n */\nexport function getXUsernameStorageArgs(username: string): ProfileStorageArgs {\n // Ensure username has @ prefix for storage\n const normalizedUsername = username.startsWith(\"@\")\n ? username\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 @ from stored username for display\n const usernameWithoutAt = storedUsername?.startsWith(\"@\")\n ? storedUsername.slice(1)\n : storedUsername;\n\n return {\n x_username: usernameWithoutAt,\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"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,88 @@
1
+ import { stringToHex } from 'viem';
2
+ import { toBytes32 } from '@net-protocol/core';
3
+ export { STORAGE_CONTRACT } from '@net-protocol/storage';
4
+
5
+ // src/constants.ts
6
+ var PROFILE_CANVAS_STORAGE_KEY = "net-beta0.0.1-profile-canvas";
7
+ var PROFILE_PICTURE_STORAGE_KEY = "net-beta0.0.1-profile-picture";
8
+ var PROFILE_X_USERNAME_STORAGE_KEY = "net-beta0.0.1-profile-x-username";
9
+ var PROFILE_METADATA_STORAGE_KEY = "net-beta0.0.1-profile-metadata";
10
+ var PROFILE_PICTURE_TOPIC = "profile-picture";
11
+ var PROFILE_METADATA_TOPIC = "profile-metadata";
12
+ var PROFILE_CANVAS_TOPIC = "profile-canvas";
13
+ function getValueArgForStorage(value) {
14
+ return stringToHex(value);
15
+ }
16
+ function getBytesArgsForStorage(key, value) {
17
+ const bytesKey = toBytes32(key);
18
+ const bytesValue = getValueArgForStorage(value);
19
+ return { bytesKey, bytesValue };
20
+ }
21
+ function getProfilePictureStorageArgs(imageUrl) {
22
+ const { bytesKey, bytesValue } = getBytesArgsForStorage(
23
+ PROFILE_PICTURE_STORAGE_KEY,
24
+ imageUrl
25
+ );
26
+ return {
27
+ bytesKey,
28
+ topic: PROFILE_PICTURE_TOPIC,
29
+ bytesValue
30
+ };
31
+ }
32
+ function getProfileMetadataStorageArgs(metadata) {
33
+ const jsonString = JSON.stringify(metadata);
34
+ const { bytesKey, bytesValue } = getBytesArgsForStorage(
35
+ PROFILE_METADATA_STORAGE_KEY,
36
+ jsonString
37
+ );
38
+ return {
39
+ bytesKey,
40
+ topic: PROFILE_METADATA_TOPIC,
41
+ bytesValue
42
+ };
43
+ }
44
+ function getXUsernameStorageArgs(username) {
45
+ const normalizedUsername = username.startsWith("@") ? username : `@${username}`;
46
+ return getProfileMetadataStorageArgs({ x_username: normalizedUsername });
47
+ }
48
+ function getProfileCanvasStorageArgs(htmlContent) {
49
+ const { bytesKey, bytesValue } = getBytesArgsForStorage(
50
+ PROFILE_CANVAS_STORAGE_KEY,
51
+ htmlContent
52
+ );
53
+ return {
54
+ bytesKey,
55
+ topic: PROFILE_CANVAS_TOPIC,
56
+ bytesValue
57
+ };
58
+ }
59
+ function parseProfileMetadata(jsonData) {
60
+ try {
61
+ const parsed = JSON.parse(jsonData);
62
+ const storedUsername = parsed?.x_username && typeof parsed.x_username === "string" && parsed.x_username.length > 0 ? parsed.x_username : void 0;
63
+ const usernameWithoutAt = storedUsername?.startsWith("@") ? storedUsername.slice(1) : storedUsername;
64
+ return {
65
+ x_username: usernameWithoutAt
66
+ };
67
+ } catch {
68
+ return void 0;
69
+ }
70
+ }
71
+ function isValidUrl(url) {
72
+ if (!url) return false;
73
+ try {
74
+ new URL(url);
75
+ return true;
76
+ } catch {
77
+ return false;
78
+ }
79
+ }
80
+ function isValidXUsername(username) {
81
+ if (!username) return false;
82
+ const cleanUsername = username.startsWith("@") ? username.slice(1) : username;
83
+ return /^[a-zA-Z0-9_]{1,15}$/.test(cleanUsername);
84
+ }
85
+
86
+ 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, getBytesArgsForStorage, getProfileCanvasStorageArgs, getProfileMetadataStorageArgs, getProfilePictureStorageArgs, getValueArgForStorage, getXUsernameStorageArgs, isValidUrl, isValidXUsername, parseProfileMetadata };
87
+ //# sourceMappingURL=index.mjs.map
88
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts","../src/utils.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;ACN7B,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;AASO,SAAS,wBAAwB,QAAA,EAAsC;AAE5E,EAAA,MAAM,qBAAqB,QAAA,CAAS,UAAA,CAAW,GAAG,CAAA,GAC9C,QAAA,GACA,IAAI,QAAQ,CAAA,CAAA;AAChB,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;AAEJ,IAAA,OAAO;AAAA,MACL,UAAA,EAAY;AAAA,KACd;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","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 * @param username - X/Twitter username (with or without @)\n * @returns Arguments for Storage.put()\n */\nexport function getXUsernameStorageArgs(username: string): ProfileStorageArgs {\n // Ensure username has @ prefix for storage\n const normalizedUsername = username.startsWith(\"@\")\n ? username\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 @ from stored username for display\n const usernameWithoutAt = storedUsername?.startsWith(\"@\")\n ? storedUsername.slice(1)\n : storedUsername;\n\n return {\n x_username: usernameWithoutAt,\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"]}