@net-protocol/cli 0.1.34 → 0.1.36

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.
@@ -0,0 +1,18 @@
1
+ import { Command } from 'commander';
2
+
3
+ /**
4
+ * Register the chat read subcommand
5
+ */
6
+ declare function registerChatReadCommand(parent: Command): void;
7
+
8
+ /**
9
+ * Register the chat send subcommand
10
+ */
11
+ declare function registerChatSendCommand(parent: Command): void;
12
+
13
+ /**
14
+ * Register the chat command group with the commander program
15
+ */
16
+ declare function registerChatCommand(program: Command): void;
17
+
18
+ export { registerChatCommand, registerChatReadCommand, registerChatSendCommand };
@@ -0,0 +1,293 @@
1
+ import chalk3 from 'chalk';
2
+ import '@net-protocol/feeds';
3
+ import { ChatClient } from '@net-protocol/chats';
4
+ import { getBaseDataSuffix, getChainRpcUrls } from '@net-protocol/core';
5
+ import '@net-protocol/storage';
6
+ import { encodeFunctionData, concat, createWalletClient, http } from 'viem';
7
+ import { privateKeyToAccount } from 'viem/accounts';
8
+
9
+ // src/commands/chat/read.ts
10
+ var DEFAULT_CHAIN_ID = 8453;
11
+ function getChainIdWithDefault(optionValue) {
12
+ if (optionValue) {
13
+ return optionValue;
14
+ }
15
+ const envChainId = process.env.BOTCHAN_CHAIN_ID || process.env.NET_CHAIN_ID;
16
+ if (envChainId) {
17
+ return parseInt(envChainId, 10);
18
+ }
19
+ return DEFAULT_CHAIN_ID;
20
+ }
21
+ function getRpcUrlWithBotchanFallback(optionValue) {
22
+ return optionValue || process.env.BOTCHAN_RPC_URL || process.env.NET_RPC_URL;
23
+ }
24
+ function parseReadOnlyOptionsWithDefault(options) {
25
+ return {
26
+ chainId: getChainIdWithDefault(options.chainId),
27
+ rpcUrl: getRpcUrlWithBotchanFallback(options.rpcUrl)
28
+ };
29
+ }
30
+ function parseCommonOptionsWithDefault(options, supportsEncodeOnly = false) {
31
+ const privateKey = options.privateKey || process.env.BOTCHAN_PRIVATE_KEY || process.env.NET_PRIVATE_KEY || process.env.PRIVATE_KEY;
32
+ if (!privateKey) {
33
+ const encodeOnlyHint = supportsEncodeOnly ? ", or use --encode-only to output transaction data without submitting" : "";
34
+ console.error(
35
+ chalk3.red(
36
+ `Error: Private key is required. Provide via --private-key flag or NET_PRIVATE_KEY/BOTCHAN_PRIVATE_KEY environment variable${encodeOnlyHint}`
37
+ )
38
+ );
39
+ process.exit(1);
40
+ }
41
+ if (!privateKey.startsWith("0x") || privateKey.length !== 66) {
42
+ console.error(
43
+ chalk3.red(
44
+ "Error: Invalid private key format (must be 0x-prefixed, 66 characters)"
45
+ )
46
+ );
47
+ process.exit(1);
48
+ }
49
+ if (options.privateKey) {
50
+ console.warn(
51
+ chalk3.yellow(
52
+ "Warning: Private key provided via command line. Consider using NET_PRIVATE_KEY environment variable instead."
53
+ )
54
+ );
55
+ }
56
+ return {
57
+ privateKey,
58
+ chainId: getChainIdWithDefault(options.chainId),
59
+ rpcUrl: getRpcUrlWithBotchanFallback(options.rpcUrl)
60
+ };
61
+ }
62
+ function createChatClient(options) {
63
+ return new ChatClient({
64
+ chainId: options.chainId,
65
+ overrides: options.rpcUrl ? { rpcUrls: [options.rpcUrl] } : void 0
66
+ });
67
+ }
68
+ function exitWithError(message) {
69
+ console.error(chalk3.red(`Error: ${message}`));
70
+ process.exit(1);
71
+ }
72
+
73
+ // src/commands/chat/types.ts
74
+ function normalizeChatName(chat) {
75
+ return chat.toLowerCase();
76
+ }
77
+
78
+ // src/commands/chat/read.ts
79
+ function formatMessage(msg, index) {
80
+ const time = new Date(Number(msg.timestamp) * 1e3).toLocaleString();
81
+ const sender = msg.sender.slice(0, 6) + "..." + msg.sender.slice(-4);
82
+ return ` ${chalk3.gray(`[${index + 1}]`)} ${chalk3.cyan(sender)} ${chalk3.gray(time)}
83
+ ${msg.text}`;
84
+ }
85
+ function messageToJson(msg, index) {
86
+ return {
87
+ index,
88
+ sender: msg.sender,
89
+ text: msg.text,
90
+ timestamp: Number(msg.timestamp),
91
+ data: msg.data
92
+ };
93
+ }
94
+ function printJson(data) {
95
+ console.log(JSON.stringify(data, null, 2));
96
+ }
97
+ async function executeChatRead(chat, options) {
98
+ const normalizedChat = normalizeChatName(chat);
99
+ const readOnlyOptions = parseReadOnlyOptionsWithDefault({
100
+ chainId: options.chainId,
101
+ rpcUrl: options.rpcUrl
102
+ });
103
+ const client = createChatClient(readOnlyOptions);
104
+ const limit = options.limit ?? 20;
105
+ try {
106
+ const count = await client.getChatMessageCount(normalizedChat);
107
+ if (count === 0) {
108
+ if (options.json) {
109
+ printJson([]);
110
+ } else {
111
+ console.log(chalk3.yellow(`No messages found in chat "${normalizedChat}"`));
112
+ }
113
+ return;
114
+ }
115
+ const fetchLimit = options.sender ? Math.max(limit * 5, 100) : limit;
116
+ let messages = await client.getChatMessages({
117
+ topic: normalizedChat,
118
+ maxMessages: fetchLimit
119
+ });
120
+ if (options.sender) {
121
+ const senderLower = options.sender.toLowerCase();
122
+ messages = messages.filter(
123
+ (msg) => msg.sender.toLowerCase() === senderLower
124
+ );
125
+ messages = messages.slice(0, limit);
126
+ }
127
+ if (options.json) {
128
+ printJson(
129
+ messages.map((msg, i) => messageToJson(msg, i))
130
+ );
131
+ } else {
132
+ if (messages.length === 0) {
133
+ const senderNote2 = options.sender ? ` by ${options.sender}` : "";
134
+ console.log(chalk3.yellow(`No messages found in chat "${normalizedChat}"${senderNote2}`));
135
+ return;
136
+ }
137
+ const senderNote = options.sender ? ` by ${options.sender}` : "";
138
+ console.log(
139
+ chalk3.white(`Found ${messages.length} message(s) in chat "${normalizedChat}"${senderNote}:
140
+ `)
141
+ );
142
+ messages.forEach((msg, i) => {
143
+ console.log(formatMessage(msg, i));
144
+ if (i < messages.length - 1) {
145
+ console.log();
146
+ }
147
+ });
148
+ }
149
+ } catch (error) {
150
+ exitWithError(
151
+ `Failed to read chat: ${error instanceof Error ? error.message : String(error)}`
152
+ );
153
+ }
154
+ }
155
+ function registerChatReadCommand(parent) {
156
+ parent.command("read <chat>").description("Read messages from a group chat").option(
157
+ "--limit <n>",
158
+ "Maximum number of messages to display",
159
+ (value) => parseInt(value, 10)
160
+ ).option(
161
+ "--chain-id <id>",
162
+ "Chain ID (default: 8453 for Base)",
163
+ (value) => parseInt(value, 10)
164
+ ).option("--rpc-url <url>", "Custom RPC URL").option("--sender <address>", "Filter messages by sender address").option("--json", "Output in JSON format").action(async (chat, options) => {
165
+ await executeChatRead(chat, options);
166
+ });
167
+ }
168
+ function createWallet(privateKey, chainId, rpcUrl) {
169
+ const account = privateKeyToAccount(privateKey);
170
+ const rpcUrls = getChainRpcUrls({
171
+ chainId,
172
+ rpcUrl
173
+ });
174
+ return createWalletClient({
175
+ account,
176
+ transport: http(rpcUrls[0]),
177
+ dataSuffix: getBaseDataSuffix(chainId)
178
+ });
179
+ }
180
+ async function executeTransaction(walletClient, txConfig) {
181
+ const hash = await walletClient.writeContract({
182
+ address: txConfig.to,
183
+ abi: txConfig.abi,
184
+ functionName: txConfig.functionName,
185
+ args: txConfig.args,
186
+ value: txConfig.value,
187
+ chain: null
188
+ });
189
+ return hash;
190
+ }
191
+ function encodeTransaction(config, chainId) {
192
+ const calldata = encodeFunctionData({
193
+ abi: config.abi,
194
+ functionName: config.functionName,
195
+ args: config.args
196
+ });
197
+ const suffix = getBaseDataSuffix(chainId);
198
+ const data = suffix ? concat([calldata, suffix]) : calldata;
199
+ return {
200
+ to: config.to,
201
+ data,
202
+ chainId,
203
+ value: config.value?.toString() ?? "0"
204
+ };
205
+ }
206
+
207
+ // src/commands/chat/send.ts
208
+ var MAX_MESSAGE_LENGTH = 4e3;
209
+ async function executeChatSend(chat, message, options) {
210
+ const normalizedChat = normalizeChatName(chat);
211
+ if (message.length === 0) {
212
+ exitWithError("Message cannot be empty");
213
+ }
214
+ if (message.length > MAX_MESSAGE_LENGTH) {
215
+ exitWithError(
216
+ `Message too long (${message.length} chars). Maximum is ${MAX_MESSAGE_LENGTH} characters.`
217
+ );
218
+ }
219
+ if (options.encodeOnly) {
220
+ const readOnlyOptions = parseReadOnlyOptionsWithDefault({
221
+ chainId: options.chainId,
222
+ rpcUrl: options.rpcUrl
223
+ });
224
+ const client2 = createChatClient(readOnlyOptions);
225
+ const txConfig2 = client2.prepareSendChatMessage({
226
+ topic: normalizedChat,
227
+ text: message,
228
+ data: options.data
229
+ });
230
+ const encoded = encodeTransaction(txConfig2, readOnlyOptions.chainId);
231
+ console.log(JSON.stringify(encoded, null, 2));
232
+ return;
233
+ }
234
+ const commonOptions = parseCommonOptionsWithDefault(
235
+ {
236
+ privateKey: options.privateKey,
237
+ chainId: options.chainId,
238
+ rpcUrl: options.rpcUrl
239
+ },
240
+ true
241
+ // supports --encode-only
242
+ );
243
+ const client = createChatClient(commonOptions);
244
+ const txConfig = client.prepareSendChatMessage({
245
+ topic: normalizedChat,
246
+ text: message,
247
+ data: options.data
248
+ });
249
+ const walletClient = createWallet(
250
+ commonOptions.privateKey,
251
+ commonOptions.chainId,
252
+ commonOptions.rpcUrl
253
+ );
254
+ console.log(chalk3.blue(`Sending message to chat "${normalizedChat}"...`));
255
+ try {
256
+ const hash = await executeTransaction(walletClient, txConfig);
257
+ console.log(
258
+ chalk3.green(
259
+ `Message sent successfully!
260
+ Transaction: ${hash}
261
+ Chat: ${normalizedChat}
262
+ Text: ${message}`
263
+ )
264
+ );
265
+ } catch (error) {
266
+ exitWithError(
267
+ `Failed to send message: ${error instanceof Error ? error.message : String(error)}`
268
+ );
269
+ }
270
+ }
271
+ function registerChatSendCommand(parent) {
272
+ parent.command("send <chat> <message>").description("Send a message to a group chat").option(
273
+ "--chain-id <id>",
274
+ "Chain ID (default: 8453 for Base)",
275
+ (value) => parseInt(value, 10)
276
+ ).option("--rpc-url <url>", "Custom RPC URL").option("--private-key <key>", "Private key (0x-prefixed)").option(
277
+ "--encode-only",
278
+ "Output transaction data as JSON instead of executing"
279
+ ).option("--data <data>", "Optional data to attach to the message").action(async (chat, message, options) => {
280
+ await executeChatSend(chat, message, options);
281
+ });
282
+ }
283
+
284
+ // src/commands/chat/index.ts
285
+ function registerChatCommand(program) {
286
+ const chatCommand = program.command("chat").description("Group chat operations (read/send messages)");
287
+ registerChatReadCommand(chatCommand);
288
+ registerChatSendCommand(chatCommand);
289
+ }
290
+
291
+ export { registerChatCommand, registerChatReadCommand, registerChatSendCommand };
292
+ //# sourceMappingURL=index.mjs.map
293
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/cli/shared.ts","../../src/shared/client.ts","../../src/shared/output.ts","../../src/commands/chat/types.ts","../../src/commands/chat/read.ts","../../src/shared/wallet.ts","../../src/shared/encode.ts","../../src/commands/chat/send.ts","../../src/commands/chat/index.ts"],"names":["chalk","senderNote","getBaseDataSuffix","client","txConfig"],"mappings":"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","file":"index.mjs","sourcesContent":["import chalk from \"chalk\";\nimport type { CommonOptions, ReadOnlyOptions } from \"../shared/types\";\n\n/**\n * Default chain ID (Base mainnet) - used by feed commands\n */\nexport const DEFAULT_CHAIN_ID = 8453;\n\n/**\n * Get chain ID from option or environment variable, exit if not found\n */\nfunction getRequiredChainId(optionValue?: number): number {\n const chainId =\n optionValue ||\n (process.env.NET_CHAIN_ID\n ? parseInt(process.env.NET_CHAIN_ID, 10)\n : undefined);\n\n if (!chainId) {\n console.error(\n chalk.red(\n \"Error: Chain ID is required. Provide via --chain-id flag or NET_CHAIN_ID environment variable\"\n )\n );\n process.exit(1);\n }\n\n return chainId;\n}\n\n/**\n * Get chain ID from option or environment variable, defaulting to Base (8453)\n * Also checks BOTCHAN_* env vars for backward compat\n */\nfunction getChainIdWithDefault(optionValue?: number): number {\n if (optionValue) {\n return optionValue;\n }\n\n const envChainId =\n process.env.BOTCHAN_CHAIN_ID || process.env.NET_CHAIN_ID;\n\n if (envChainId) {\n return parseInt(envChainId, 10);\n }\n\n return DEFAULT_CHAIN_ID;\n}\n\n/**\n * Get RPC URL from option or environment variable\n */\nfunction getRpcUrl(optionValue?: string): string | undefined {\n return optionValue || process.env.NET_RPC_URL;\n}\n\n/**\n * Get RPC URL from option or environment variable, also checking BOTCHAN_* env vars.\n * Used only by feed commands for backward compat.\n */\nfunction getRpcUrlWithBotchanFallback(optionValue?: string): string | undefined {\n return optionValue || process.env.BOTCHAN_RPC_URL || process.env.NET_RPC_URL;\n}\n\n/**\n * Parse and validate common options shared across all commands.\n * Extracts private key, chain ID, and RPC URL from command options or environment variables.\n * @param options - Command options\n * @param supportsEncodeOnly - If true, mention --encode-only in error messages as an alternative\n */\nexport function parseCommonOptions(\n options: {\n privateKey?: string;\n chainId?: number;\n rpcUrl?: string;\n },\n supportsEncodeOnly = false\n): CommonOptions {\n const privateKey =\n options.privateKey ||\n process.env.NET_PRIVATE_KEY ||\n process.env.PRIVATE_KEY;\n\n if (!privateKey) {\n const encodeOnlyHint = supportsEncodeOnly\n ? \", or use --encode-only to output transaction data without submitting\"\n : \"\";\n console.error(\n chalk.red(\n `Error: Private key is required. Provide via --private-key flag or NET_PRIVATE_KEY/PRIVATE_KEY environment variable${encodeOnlyHint}`\n )\n );\n process.exit(1);\n }\n\n if (!privateKey.startsWith(\"0x\") || privateKey.length !== 66) {\n console.error(\n chalk.red(\n \"Error: Invalid private key format (must be 0x-prefixed, 66 characters)\"\n )\n );\n process.exit(1);\n }\n\n if (options.privateKey) {\n console.warn(\n chalk.yellow(\n \"Warning: Private key provided via command line. Consider using NET_PRIVATE_KEY environment variable instead.\"\n )\n );\n }\n\n return {\n privateKey: privateKey as `0x${string}`,\n chainId: getRequiredChainId(options.chainId),\n rpcUrl: getRpcUrl(options.rpcUrl),\n };\n}\n\n/**\n * Parse and validate read-only options for commands that don't need a private key.\n * Extracts chain ID and RPC URL from command options or environment variables.\n */\nexport function parseReadOnlyOptions(options: {\n chainId?: number;\n rpcUrl?: string;\n}): ReadOnlyOptions {\n return {\n chainId: getRequiredChainId(options.chainId),\n rpcUrl: getRpcUrl(options.rpcUrl),\n };\n}\n\n/**\n * Parse read-only options with a default chain ID (8453/Base).\n * Used by feed commands where chain ID is optional.\n * Also checks BOTCHAN_* env vars for backward compat.\n */\nexport function parseReadOnlyOptionsWithDefault(options: {\n chainId?: number;\n rpcUrl?: string;\n}): ReadOnlyOptions {\n return {\n chainId: getChainIdWithDefault(options.chainId),\n rpcUrl: getRpcUrlWithBotchanFallback(options.rpcUrl),\n };\n}\n\n/**\n * Parse common options with a default chain ID (8453/Base).\n * Used by feed write commands where chain ID is optional.\n * Also checks BOTCHAN_* env vars for backward compat.\n */\nexport function parseCommonOptionsWithDefault(\n options: {\n privateKey?: string;\n chainId?: number;\n rpcUrl?: string;\n },\n supportsEncodeOnly = false\n): CommonOptions {\n const privateKey =\n options.privateKey ||\n process.env.BOTCHAN_PRIVATE_KEY ||\n process.env.NET_PRIVATE_KEY ||\n process.env.PRIVATE_KEY;\n\n if (!privateKey) {\n const encodeOnlyHint = supportsEncodeOnly\n ? \", or use --encode-only to output transaction data without submitting\"\n : \"\";\n console.error(\n chalk.red(\n `Error: Private key is required. Provide via --private-key flag or NET_PRIVATE_KEY/BOTCHAN_PRIVATE_KEY environment variable${encodeOnlyHint}`\n )\n );\n process.exit(1);\n }\n\n if (!privateKey.startsWith(\"0x\") || privateKey.length !== 66) {\n console.error(\n chalk.red(\n \"Error: Invalid private key format (must be 0x-prefixed, 66 characters)\"\n )\n );\n process.exit(1);\n }\n\n if (options.privateKey) {\n console.warn(\n chalk.yellow(\n \"Warning: Private key provided via command line. Consider using NET_PRIVATE_KEY environment variable instead.\"\n )\n );\n }\n\n return {\n privateKey: privateKey as `0x${string}`,\n chainId: getChainIdWithDefault(options.chainId),\n rpcUrl: getRpcUrlWithBotchanFallback(options.rpcUrl),\n };\n}\n","import { FeedClient, FeedRegistryClient, AgentRegistryClient } from \"@net-protocol/feeds\";\nimport { ChatClient } from \"@net-protocol/chats\";\nimport { NetClient } from \"@net-protocol/core\";\nimport { StorageClient } from \"@net-protocol/storage\";\nimport type { ReadOnlyOptions } from \"./types\";\n\n/**\n * Create a FeedClient from read-only options\n */\nexport function createFeedClient(options: ReadOnlyOptions): FeedClient {\n return new FeedClient({\n chainId: options.chainId,\n overrides: options.rpcUrl ? { rpcUrls: [options.rpcUrl] } : undefined,\n });\n}\n\n/**\n * Create a FeedRegistryClient from read-only options\n */\nexport function createFeedRegistryClient(\n options: ReadOnlyOptions\n): FeedRegistryClient {\n return new FeedRegistryClient({\n chainId: options.chainId,\n overrides: options.rpcUrl ? { rpcUrls: [options.rpcUrl] } : undefined,\n });\n}\n\n/**\n * Create a ChatClient from read-only options\n */\nexport function createChatClient(options: ReadOnlyOptions): ChatClient {\n return new ChatClient({\n chainId: options.chainId,\n overrides: options.rpcUrl ? { rpcUrls: [options.rpcUrl] } : undefined,\n });\n}\n\n/**\n * Create a NetClient from read-only options\n */\nexport function createNetClient(options: ReadOnlyOptions): NetClient {\n return new NetClient({\n chainId: options.chainId,\n overrides: options.rpcUrl ? { rpcUrls: [options.rpcUrl] } : undefined,\n });\n}\n\n/**\n * Create an AgentRegistryClient from read-only options\n */\nexport function createAgentRegistryClient(\n options: ReadOnlyOptions\n): AgentRegistryClient {\n return new AgentRegistryClient({\n chainId: options.chainId,\n overrides: options.rpcUrl ? { rpcUrls: [options.rpcUrl] } : undefined,\n });\n}\n\n/**\n * Create a StorageClient from read-only options\n */\nexport function createStorageClient(options: ReadOnlyOptions): StorageClient {\n return new StorageClient({\n chainId: options.chainId,\n overrides: options.rpcUrl ? { rpcUrls: [options.rpcUrl] } : undefined,\n });\n}\n","import chalk from \"chalk\";\nimport type { NetMessage } from \"@net-protocol/core\";\n\n/**\n * Format a message for human-readable output\n */\nexport function formatMessage(\n message: NetMessage,\n index: number\n): string {\n const timestamp = new Date(Number(message.timestamp) * 1000).toISOString();\n const lines = [\n chalk.cyan(`[${index}]`) + ` ${chalk.gray(timestamp)}`,\n ` ${chalk.white(\"Sender:\")} ${message.sender}`,\n ` ${chalk.white(\"App:\")} ${message.app}`,\n ];\n\n if (message.topic) {\n lines.push(` ${chalk.white(\"Topic:\")} ${message.topic}`);\n }\n\n lines.push(` ${chalk.white(\"Text:\")} ${message.text}`);\n\n if (message.data && message.data !== \"0x\") {\n lines.push(` ${chalk.white(\"Data:\")} ${message.data}`);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Format a message for JSON output\n */\nexport function messageToJson(\n message: NetMessage,\n index: number\n): Record<string, unknown> {\n return {\n index,\n sender: message.sender,\n app: message.app,\n timestamp: Number(message.timestamp),\n text: message.text,\n topic: message.topic,\n data: message.data,\n };\n}\n\n/**\n * Print messages in human-readable or JSON format\n */\nexport function printMessages(\n messages: NetMessage[],\n startIndex: number,\n json: boolean\n): void {\n if (json) {\n const output = messages.map((msg, i) => messageToJson(msg, startIndex + i));\n console.log(JSON.stringify(output, null, 2));\n } else {\n if (messages.length === 0) {\n console.log(chalk.yellow(\"No messages found\"));\n return;\n }\n\n messages.forEach((msg, i) => {\n console.log(formatMessage(msg, startIndex + i));\n if (i < messages.length - 1) {\n console.log(); // Empty line between messages\n }\n });\n }\n}\n\n/**\n * Print a count result\n */\nexport function printCount(\n count: number,\n label: string,\n json: boolean\n): void {\n if (json) {\n console.log(JSON.stringify({ count }, null, 2));\n } else {\n console.log(`${chalk.white(label)} ${chalk.cyan(count)}`);\n }\n}\n\n/**\n * Print an error message and exit\n */\nexport function exitWithError(message: string): never {\n console.error(chalk.red(`Error: ${message}`));\n process.exit(1);\n}\n","/**\n * Normalize a chat name to lowercase for consistency.\n */\nexport function normalizeChatName(chat: string): string {\n return chat.toLowerCase();\n}\n","import chalk from \"chalk\";\nimport { Command } from \"commander\";\nimport type { NetMessage } from \"@net-protocol/chats\";\nimport { parseReadOnlyOptionsWithDefault } from \"../../cli/shared\";\nimport { createChatClient } from \"../../shared/client\";\nimport { exitWithError } from \"../../shared/output\";\nimport { normalizeChatName } from \"./types\";\n\ninterface ReadOptions {\n limit?: number;\n chainId?: number;\n rpcUrl?: string;\n json?: boolean;\n sender?: string;\n}\n\nfunction formatMessage(msg: NetMessage, index: number): string {\n const time = new Date(Number(msg.timestamp) * 1000).toLocaleString();\n const sender = msg.sender.slice(0, 6) + \"...\" + msg.sender.slice(-4);\n return ` ${chalk.gray(`[${index + 1}]`)} ${chalk.cyan(sender)} ${chalk.gray(time)}\\n ${msg.text}`;\n}\n\nfunction messageToJson(msg: NetMessage, index: number) {\n return {\n index,\n sender: msg.sender,\n text: msg.text,\n timestamp: Number(msg.timestamp),\n data: msg.data,\n };\n}\n\nfunction printJson(data: unknown): void {\n console.log(JSON.stringify(data, null, 2));\n}\n\n/**\n * Execute the chat read command\n */\nasync function executeChatRead(chat: string, options: ReadOptions): Promise<void> {\n const normalizedChat = normalizeChatName(chat);\n const readOnlyOptions = parseReadOnlyOptionsWithDefault({\n chainId: options.chainId,\n rpcUrl: options.rpcUrl,\n });\n\n const client = createChatClient(readOnlyOptions);\n const limit = options.limit ?? 20;\n\n try {\n const count = await client.getChatMessageCount(normalizedChat);\n\n if (count === 0) {\n if (options.json) {\n printJson([]);\n } else {\n console.log(chalk.yellow(`No messages found in chat \"${normalizedChat}\"`));\n }\n return;\n }\n\n const fetchLimit = options.sender ? Math.max(limit * 5, 100) : limit;\n\n let messages = await client.getChatMessages({\n topic: normalizedChat,\n maxMessages: fetchLimit,\n });\n\n // Filter by sender if specified\n if (options.sender) {\n const senderLower = options.sender.toLowerCase();\n messages = messages.filter(\n (msg: NetMessage) => msg.sender.toLowerCase() === senderLower\n );\n messages = messages.slice(0, limit);\n }\n\n if (options.json) {\n printJson(\n messages.map((msg: NetMessage, i: number) => messageToJson(msg, i))\n );\n } else {\n if (messages.length === 0) {\n const senderNote = options.sender ? ` by ${options.sender}` : \"\";\n console.log(chalk.yellow(`No messages found in chat \"${normalizedChat}\"${senderNote}`));\n return;\n }\n\n const senderNote = options.sender ? ` by ${options.sender}` : \"\";\n console.log(\n chalk.white(`Found ${messages.length} message(s) in chat \"${normalizedChat}\"${senderNote}:\\n`)\n );\n messages.forEach((msg: NetMessage, i: number) => {\n console.log(formatMessage(msg, i));\n if (i < messages.length - 1) {\n console.log(); // Empty line between messages\n }\n });\n }\n } catch (error) {\n exitWithError(\n `Failed to read chat: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n}\n\n/**\n * Register the chat read subcommand\n */\nexport function registerChatReadCommand(parent: Command): void {\n parent\n .command(\"read <chat>\")\n .description(\"Read messages from a group chat\")\n .option(\n \"--limit <n>\",\n \"Maximum number of messages to display\",\n (value) => parseInt(value, 10)\n )\n .option(\n \"--chain-id <id>\",\n \"Chain ID (default: 8453 for Base)\",\n (value) => parseInt(value, 10)\n )\n .option(\"--rpc-url <url>\", \"Custom RPC URL\")\n .option(\"--sender <address>\", \"Filter messages by sender address\")\n .option(\"--json\", \"Output in JSON format\")\n .action(async (chat, options) => {\n await executeChatRead(chat, options);\n });\n}\n","import { createWalletClient, http } from \"viem\";\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport { getChainRpcUrls, getBaseDataSuffix } from \"@net-protocol/core\";\nimport type { WriteTransactionConfig } from \"@net-protocol/core\";\n\n/**\n * Create a wallet client from a private key\n */\nexport function createWallet(\n privateKey: `0x${string}`,\n chainId: number,\n rpcUrl?: string\n) {\n const account = privateKeyToAccount(privateKey);\n const rpcUrls = getChainRpcUrls({\n chainId,\n rpcUrl: rpcUrl,\n });\n\n return createWalletClient({\n account,\n transport: http(rpcUrls[0]),\n dataSuffix: getBaseDataSuffix(chainId),\n });\n}\n\n/**\n * Execute a transaction using a wallet client\n */\nexport async function executeTransaction(\n walletClient: ReturnType<typeof createWallet>,\n txConfig: WriteTransactionConfig\n): Promise<`0x${string}`> {\n const hash = await walletClient.writeContract({\n address: txConfig.to,\n abi: txConfig.abi,\n functionName: txConfig.functionName,\n args: txConfig.args,\n value: txConfig.value,\n chain: null,\n } as Parameters<typeof walletClient.writeContract>[0]);\n\n return hash;\n}\n","import { encodeFunctionData, concat } from \"viem\";\nimport { getBaseDataSuffix } from \"@net-protocol/core\";\nimport type { WriteTransactionConfig } from \"@net-protocol/core\";\nimport type { EncodedTransaction } from \"./types\";\n\nexport type { EncodedTransaction };\n\n/**\n * Encode a write transaction config into transaction data\n * Used for --encode-only mode where we output transaction data instead of executing\n */\nexport function encodeTransaction(\n config: WriteTransactionConfig,\n chainId: number\n): EncodedTransaction {\n const calldata = encodeFunctionData({\n abi: config.abi,\n functionName: config.functionName,\n args: config.args,\n });\n\n const suffix = getBaseDataSuffix(chainId);\n const data = suffix ? concat([calldata, suffix]) : calldata;\n\n return {\n to: config.to,\n data,\n chainId,\n value: config.value?.toString() ?? \"0\",\n };\n}\n","import chalk from \"chalk\";\nimport { Command } from \"commander\";\nimport { parseReadOnlyOptionsWithDefault, parseCommonOptionsWithDefault } from \"../../cli/shared\";\nimport { createChatClient } from \"../../shared/client\";\nimport { createWallet, executeTransaction } from \"../../shared/wallet\";\nimport { encodeTransaction } from \"../../shared/encode\";\nimport { exitWithError } from \"../../shared/output\";\nimport { normalizeChatName } from \"./types\";\n\ninterface SendOptions {\n chainId?: number;\n rpcUrl?: string;\n privateKey?: string;\n encodeOnly?: boolean;\n data?: string;\n}\n\nconst MAX_MESSAGE_LENGTH = 4000;\n\n/**\n * Execute the chat send command\n */\nasync function executeChatSend(\n chat: string,\n message: string,\n options: SendOptions\n): Promise<void> {\n const normalizedChat = normalizeChatName(chat);\n\n if (message.length === 0) {\n exitWithError(\"Message cannot be empty\");\n }\n\n if (message.length > MAX_MESSAGE_LENGTH) {\n exitWithError(\n `Message too long (${message.length} chars). Maximum is ${MAX_MESSAGE_LENGTH} characters.`\n );\n }\n\n // For encode-only mode, we don't need a private key\n if (options.encodeOnly) {\n const readOnlyOptions = parseReadOnlyOptionsWithDefault({\n chainId: options.chainId,\n rpcUrl: options.rpcUrl,\n });\n\n const client = createChatClient(readOnlyOptions);\n const txConfig = client.prepareSendChatMessage({\n topic: normalizedChat,\n text: message,\n data: options.data,\n });\n const encoded = encodeTransaction(txConfig, readOnlyOptions.chainId);\n\n console.log(JSON.stringify(encoded, null, 2));\n return;\n }\n\n // For actual execution, we need a private key\n const commonOptions = parseCommonOptionsWithDefault(\n {\n privateKey: options.privateKey,\n chainId: options.chainId,\n rpcUrl: options.rpcUrl,\n },\n true // supports --encode-only\n );\n\n const client = createChatClient(commonOptions);\n const txConfig = client.prepareSendChatMessage({\n topic: normalizedChat,\n text: message,\n data: options.data,\n });\n\n const walletClient = createWallet(\n commonOptions.privateKey,\n commonOptions.chainId,\n commonOptions.rpcUrl\n );\n\n console.log(chalk.blue(`Sending message to chat \"${normalizedChat}\"...`));\n\n try {\n const hash = await executeTransaction(walletClient, txConfig);\n\n console.log(\n chalk.green(\n `Message sent successfully!\\n Transaction: ${hash}\\n Chat: ${normalizedChat}\\n Text: ${message}`\n )\n );\n } catch (error) {\n exitWithError(\n `Failed to send message: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n}\n\n/**\n * Register the chat send subcommand\n */\nexport function registerChatSendCommand(parent: Command): void {\n parent\n .command(\"send <chat> <message>\")\n .description(\"Send a message to a group chat\")\n .option(\n \"--chain-id <id>\",\n \"Chain ID (default: 8453 for Base)\",\n (value) => parseInt(value, 10)\n )\n .option(\"--rpc-url <url>\", \"Custom RPC URL\")\n .option(\"--private-key <key>\", \"Private key (0x-prefixed)\")\n .option(\n \"--encode-only\",\n \"Output transaction data as JSON instead of executing\"\n )\n .option(\"--data <data>\", \"Optional data to attach to the message\")\n .action(async (chat, message, options) => {\n await executeChatSend(chat, message, options);\n });\n}\n","import { Command } from \"commander\";\nimport { registerChatReadCommand } from \"./read\";\nimport { registerChatSendCommand } from \"./send\";\n\n/**\n * Register the chat command group with the commander program\n */\nexport function registerChatCommand(program: Command): void {\n const chatCommand = program\n .command(\"chat\")\n .description(\"Group chat operations (read/send messages)\");\n\n registerChatReadCommand(chatCommand);\n registerChatSendCommand(chatCommand);\n}\n\n// Re-export individual command registrations for botchan wrapper\nexport { registerChatReadCommand } from \"./read\";\nexport { registerChatSendCommand } from \"./send\";\n"]}
@@ -7,11 +7,12 @@ import chalk4 from 'chalk';
7
7
  import * as fs6 from 'fs';
8
8
  import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
9
9
  import { OPTIMAL_CHUNK_SIZE, StorageClient, detectFileTypeFromBase64, base64ToDataUri, shouldSuggestXmlStorage, getStorageKeyBytes, encodeStorageKeyForUrl, chunkDataForStorage, CHUNKED_STORAGE_CONTRACT, STORAGE_CONTRACT as STORAGE_CONTRACT$1 } from '@net-protocol/storage';
10
- import { stringToHex, createWalletClient, http, hexToString, parseEther, encodeFunctionData, publicActions, concat, defineChain, createPublicClient } from 'viem';
10
+ import { stringToHex, createWalletClient, http, hexToString, parseEther, encodeFunctionData, publicActions, concat, defineChain, createPublicClient, formatEther } from 'viem';
11
11
  import { privateKeyToAccount } from 'viem/accounts';
12
12
  import { getNetContract, getChainName, getPublicClient, getChainRpcUrls, getBaseDataSuffix, NetClient, toBytes32, NULL_ADDRESS } from '@net-protocol/core';
13
13
  import { createRelayX402Client, createRelaySession, checkBackendWalletBalance, fundBackendWallet, batchTransactions, submitTransactionsViaRelay, waitForConfirmations, retryFailedTransactions as retryFailedTransactions$1 } from '@net-protocol/relay';
14
14
  import { FeedRegistryClient, FeedClient, AgentRegistryClient } from '@net-protocol/feeds';
15
+ import '@net-protocol/chats';
15
16
  import { isNetrSupportedChain, NetrClient } from '@net-protocol/netr';
16
17
  import { PROFILE_PICTURE_STORAGE_KEY, PROFILE_METADATA_STORAGE_KEY, parseProfileMetadata, PROFILE_CANVAS_STORAGE_KEY, PROFILE_CSS_STORAGE_KEY, isValidUrl, getProfilePictureStorageArgs, STORAGE_CONTRACT, isValidXUsername, getProfileMetadataStorageArgs, isValidBio, isValidDisplayName, isValidTokenAddress, DEMO_THEMES, MAX_CSS_SIZE, isValidCSS, getProfileCSSStorageArgs, buildCSSPrompt } from '@net-protocol/profiles';
17
18
  import { base } from 'viem/chains';
@@ -21,7 +22,7 @@ import { BazaarClient } from '@net-protocol/bazaar';
21
22
  import * as os from 'os';
22
23
  import { homedir } from 'os';
23
24
  import * as readline from 'readline';
24
- import { discoverTokenPool, PURE_ALPHA_STRATEGY, UNIV234_POOLS_STRATEGY, encodePoolKey, DYNAMIC_SPLIT_STRATEGY, getTokenScoreKey, UPVOTE_PRICE_ETH, UPVOTE_APP, ScoreClient, ALL_STRATEGY_ADDRESSES } from '@net-protocol/score';
25
+ import { discoverTokenPool, PURE_ALPHA_STRATEGY, UNIV234_POOLS_STRATEGY, encodePoolKey, DYNAMIC_SPLIT_STRATEGY, getTokenScoreKey, UPVOTE_PRICE_ETH, UPVOTE_APP, ScoreClient, ALL_STRATEGY_ADDRESSES, NULL_ADDRESS as NULL_ADDRESS$1, UserUpvoteClient, calculateUpvoteCost, USER_UPVOTE_CONTRACT } from '@net-protocol/score';
25
26
 
26
27
  var DEFAULT_CHAIN_ID = 8453;
27
28
  function getRequiredChainId(optionValue) {
@@ -7155,12 +7156,174 @@ function registerGetUpvotesCommand(parent, commandName = "info") {
7155
7156
  await executeGetUpvotes(options);
7156
7157
  });
7157
7158
  }
7159
+ async function executeUpvoteUser(options) {
7160
+ const count = parseInt(options.count, 10);
7161
+ if (isNaN(count) || count <= 0) {
7162
+ exitWithError("Count must be a positive integer");
7163
+ return;
7164
+ }
7165
+ const userAddress = options.address;
7166
+ if (!userAddress.startsWith("0x") || userAddress.length !== 42) {
7167
+ exitWithError(
7168
+ "Invalid address format (must be 0x-prefixed, 42 characters)"
7169
+ );
7170
+ return;
7171
+ }
7172
+ const token = options.token ?? NULL_ADDRESS$1;
7173
+ const feeTier = options.feeTier ? parseInt(options.feeTier, 10) : 0;
7174
+ const readOnlyOptions = parseReadOnlyOptionsWithDefault({
7175
+ chainId: options.chainId,
7176
+ rpcUrl: options.rpcUrl
7177
+ });
7178
+ const client = new UserUpvoteClient({
7179
+ chainId: readOnlyOptions.chainId,
7180
+ overrides: readOnlyOptions.rpcUrl ? { rpcUrls: [readOnlyOptions.rpcUrl] } : void 0
7181
+ });
7182
+ let upvotePrice;
7183
+ try {
7184
+ upvotePrice = await client.getUpvotePrice();
7185
+ } catch (error) {
7186
+ exitWithError(
7187
+ `Failed to fetch upvote price: ${error instanceof Error ? error.message : String(error)}`
7188
+ );
7189
+ return;
7190
+ }
7191
+ const totalCost = calculateUpvoteCost(count, upvotePrice);
7192
+ if (options.encodeOnly) {
7193
+ const txConfig = {
7194
+ to: USER_UPVOTE_CONTRACT.address,
7195
+ abi: USER_UPVOTE_CONTRACT.abi,
7196
+ functionName: "upvoteUser",
7197
+ args: [userAddress, token, BigInt(count), BigInt(feeTier)],
7198
+ value: totalCost
7199
+ };
7200
+ const encoded = encodeTransaction(txConfig, readOnlyOptions.chainId);
7201
+ console.log(JSON.stringify(encoded, null, 2));
7202
+ return;
7203
+ }
7204
+ const commonOptions = parseCommonOptionsWithDefault(
7205
+ {
7206
+ privateKey: options.privateKey,
7207
+ chainId: options.chainId,
7208
+ rpcUrl: options.rpcUrl
7209
+ },
7210
+ true
7211
+ );
7212
+ const walletClient = createWallet(
7213
+ commonOptions.privateKey,
7214
+ commonOptions.chainId,
7215
+ commonOptions.rpcUrl
7216
+ );
7217
+ console.log(
7218
+ chalk4.blue(`Submitting ${count} profile upvote(s) for ${userAddress}...`)
7219
+ );
7220
+ try {
7221
+ const hash = await client.upvoteUser({
7222
+ walletClient,
7223
+ userToUpvote: userAddress,
7224
+ token,
7225
+ numUpvotes: count,
7226
+ feeTier,
7227
+ value: totalCost
7228
+ });
7229
+ console.log(chalk4.green("Profile upvote submitted successfully!"));
7230
+ console.log(chalk4.white(` Transaction: ${hash}`));
7231
+ console.log(chalk4.white(` User: ${userAddress}`));
7232
+ console.log(chalk4.white(` Count: ${count}`));
7233
+ console.log(chalk4.white(` Value: ${formatEther(totalCost)} ETH`));
7234
+ if (token !== NULL_ADDRESS$1) {
7235
+ console.log(chalk4.white(` Token: ${token}`));
7236
+ }
7237
+ } catch (error) {
7238
+ exitWithError(
7239
+ `Failed to submit profile upvote: ${error instanceof Error ? error.message : String(error)}`
7240
+ );
7241
+ }
7242
+ }
7243
+ function registerUpvoteUserCommand(parent, commandName = "user") {
7244
+ parent.command(commandName).description("Upvote a user's profile on Net Protocol").requiredOption("--address <address>", "User address to upvote").requiredOption("--count <n>", "Number of upvotes").option("--token <address>", "Token address (default: null address)").option("--fee-tier <tier>", "Fee tier (default: 0)").option(
7245
+ "--chain-id <id>",
7246
+ "Chain ID (default: 8453 for Base)",
7247
+ (value) => parseInt(value, 10)
7248
+ ).option("--rpc-url <url>", "Custom RPC URL").option("--private-key <key>", "Private key (0x-prefixed)").option(
7249
+ "--encode-only",
7250
+ "Output transaction data as JSON instead of executing"
7251
+ ).action(async (options) => {
7252
+ await executeUpvoteUser(options);
7253
+ });
7254
+ }
7255
+ async function executeGetUserUpvotes(options) {
7256
+ const userAddress = options.address;
7257
+ if (!userAddress.startsWith("0x") || userAddress.length !== 42) {
7258
+ exitWithError(
7259
+ "Invalid address format (must be 0x-prefixed, 42 characters)"
7260
+ );
7261
+ return;
7262
+ }
7263
+ const readOnlyOptions = parseReadOnlyOptionsWithDefault({
7264
+ chainId: options.chainId,
7265
+ rpcUrl: options.rpcUrl
7266
+ });
7267
+ const client = new UserUpvoteClient({
7268
+ chainId: readOnlyOptions.chainId,
7269
+ overrides: readOnlyOptions.rpcUrl ? { rpcUrls: [readOnlyOptions.rpcUrl] } : void 0
7270
+ });
7271
+ try {
7272
+ const [given, received, upvotePrice] = await Promise.all([
7273
+ client.getUserUpvotesGiven({
7274
+ user: userAddress
7275
+ }),
7276
+ client.getUserUpvotesReceived({
7277
+ user: userAddress
7278
+ }),
7279
+ client.getUpvotePrice()
7280
+ ]);
7281
+ if (options.json) {
7282
+ console.log(
7283
+ JSON.stringify(
7284
+ {
7285
+ address: userAddress,
7286
+ chainId: readOnlyOptions.chainId,
7287
+ upvotesGiven: Number(given),
7288
+ upvotesReceived: Number(received),
7289
+ upvotePriceWei: upvotePrice.toString(),
7290
+ upvotePriceEth: formatEther(upvotePrice)
7291
+ },
7292
+ null,
7293
+ 2
7294
+ )
7295
+ );
7296
+ } else {
7297
+ console.log(chalk4.white(`Profile upvotes for ${userAddress}:`));
7298
+ console.log(chalk4.cyan(` Upvotes Given: ${given}`));
7299
+ console.log(chalk4.cyan(` Upvotes Received: ${received}`));
7300
+ console.log(
7301
+ chalk4.white(` Upvote Price: ${formatEther(upvotePrice)} ETH`)
7302
+ );
7303
+ }
7304
+ } catch (error) {
7305
+ exitWithError(
7306
+ `Failed to fetch user upvotes: ${error instanceof Error ? error.message : String(error)}`
7307
+ );
7308
+ }
7309
+ }
7310
+ function registerGetUserUpvotesCommand(parent, commandName = "user-info") {
7311
+ parent.command(commandName).description("Get profile upvote stats for a user").requiredOption("--address <address>", "User address to look up").option(
7312
+ "--chain-id <id>",
7313
+ "Chain ID (default: 8453 for Base)",
7314
+ (value) => parseInt(value, 10)
7315
+ ).option("--rpc-url <url>", "Custom RPC URL").option("--json", "Output in JSON format").action(async (options) => {
7316
+ await executeGetUserUpvotes(options);
7317
+ });
7318
+ }
7158
7319
 
7159
7320
  // src/commands/upvote/index.ts
7160
7321
  function registerUpvoteCommand(program2) {
7161
- const upvoteCommand = program2.command("upvote").description("Upvote tokens on Net Protocol");
7322
+ const upvoteCommand = program2.command("upvote").description("Upvote tokens and users on Net Protocol");
7162
7323
  registerUpvoteTokenCommand(upvoteCommand);
7163
7324
  registerGetUpvotesCommand(upvoteCommand);
7325
+ registerUpvoteUserCommand(upvoteCommand);
7326
+ registerGetUserUpvotesCommand(upvoteCommand);
7164
7327
  }
7165
7328
  var CACHE_DIR = join(homedir(), ".netp");
7166
7329
  var CACHE_FILE = join(CACHE_DIR, "update-check.json");