@net-protocol/cli 0.1.3 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @net-protocol/cli
2
2
 
3
- A command-line tool for interacting with Net Protocol. Supports storage uploads and more.
3
+ Command-line tool for Net Protocol send messages, store data onchain, and deploy tokens across Base and other EVM chains.
4
4
 
5
5
  ## Installation
6
6
 
@@ -354,6 +354,128 @@ netp token info \
354
354
  --json
355
355
  ```
356
356
 
357
+ #### Profile Command
358
+
359
+ Profile operations for managing your Net Protocol profile.
360
+
361
+ **Available Subcommands:**
362
+
363
+ - `profile get` - Get profile data for an address
364
+ - `profile set-picture` - Set your profile picture URL
365
+ - `profile set-x-username` - Set your X (Twitter) username
366
+
367
+ ##### Profile Get
368
+
369
+ Read profile data for any address.
370
+
371
+ ```bash
372
+ netp profile get \
373
+ --address <wallet-address> \
374
+ [--chain-id <8453|1|...>] \
375
+ [--rpc-url <custom-rpc>] \
376
+ [--json]
377
+ ```
378
+
379
+ **Profile Get Arguments:**
380
+
381
+ - `--address` (required): Wallet address to get profile for
382
+ - `--chain-id` (optional): Chain ID. Can also be set via `NET_CHAIN_ID` environment variable
383
+ - `--rpc-url` (optional): Custom RPC URL. Can also be set via `NET_RPC_URL` environment variable
384
+ - `--json` (optional): Output in JSON format
385
+
386
+ **Example:**
387
+
388
+ ```bash
389
+ # Human-readable output
390
+ netp profile get \
391
+ --address 0x1234567890abcdef1234567890abcdef12345678 \
392
+ --chain-id 8453
393
+
394
+ # JSON output
395
+ netp profile get \
396
+ --address 0x1234567890abcdef1234567890abcdef12345678 \
397
+ --chain-id 8453 \
398
+ --json
399
+ ```
400
+
401
+ ##### Profile Set Picture
402
+
403
+ Set your profile picture URL.
404
+
405
+ ```bash
406
+ netp profile set-picture \
407
+ --url <image-url> \
408
+ [--private-key <0x...>] \
409
+ [--chain-id <8453|1|...>] \
410
+ [--rpc-url <custom-rpc>] \
411
+ [--encode-only]
412
+ ```
413
+
414
+ **Profile Set Picture Arguments:**
415
+
416
+ - `--url` (required): Image URL for profile picture (HTTPS, IPFS, etc.)
417
+ - `--private-key` (optional): Private key. Can also be set via `NET_PRIVATE_KEY` environment variable
418
+ - `--chain-id` (optional): Chain ID. Can also be set via `NET_CHAIN_ID` environment variable
419
+ - `--rpc-url` (optional): Custom RPC URL. Can also be set via `NET_RPC_URL` environment variable
420
+ - `--encode-only` (optional): Output transaction data as JSON instead of executing
421
+
422
+ **Example:**
423
+
424
+ ```bash
425
+ # Set profile picture
426
+ netp profile set-picture \
427
+ --url "https://example.com/my-avatar.jpg" \
428
+ --chain-id 8453
429
+
430
+ # Encode-only (get transaction data without executing)
431
+ netp profile set-picture \
432
+ --url "https://example.com/my-avatar.jpg" \
433
+ --chain-id 8453 \
434
+ --encode-only
435
+ ```
436
+
437
+ ##### Profile Set X Username
438
+
439
+ Set your X (Twitter) username for your profile.
440
+
441
+ **Note:** The username is stored without the @ prefix. If you provide @ it will be stripped automatically.
442
+
443
+ ```bash
444
+ netp profile set-x-username \
445
+ --username <x-username> \
446
+ [--private-key <0x...>] \
447
+ [--chain-id <8453|1|...>] \
448
+ [--rpc-url <custom-rpc>] \
449
+ [--encode-only]
450
+ ```
451
+
452
+ **Profile Set X Username Arguments:**
453
+
454
+ - `--username` (required): Your X (Twitter) username (with or without @, stored without @)
455
+ - `--private-key` (optional): Private key. Can also be set via `NET_PRIVATE_KEY` environment variable
456
+ - `--chain-id` (optional): Chain ID. Can also be set via `NET_CHAIN_ID` environment variable
457
+ - `--rpc-url` (optional): Custom RPC URL. Can also be set via `NET_RPC_URL` environment variable
458
+ - `--encode-only` (optional): Output transaction data as JSON instead of executing
459
+
460
+ **Example:**
461
+
462
+ ```bash
463
+ # Set X username (@ is optional, will be stripped before storage)
464
+ netp profile set-x-username \
465
+ --username "myusername" \
466
+ --chain-id 8453
467
+
468
+ netp profile set-x-username \
469
+ --username "@myusername" \
470
+ --chain-id 8453
471
+
472
+ # Encode-only
473
+ netp profile set-x-username \
474
+ --username "myusername" \
475
+ --chain-id 8453 \
476
+ --encode-only
477
+ ```
478
+
357
479
  #### Info Command
358
480
 
359
481
  Show contract info and stats.
@@ -429,13 +551,21 @@ src/
429
551
  │ ├── index.ts # Main entry point, sets up commander program
430
552
  │ └── shared.ts # Shared option parsing and validation
431
553
  ├── commands/
432
- └── storage/ # Storage command module
433
- ├── index.ts # Storage command definition
434
- ├── core/ # Upload and preview logic
435
- ├── storage/ # Storage operations
436
- ├── transactions/ # Transaction handling
437
- ├── utils.ts # Storage-specific utilities
438
- └── types.ts # Storage-specific types
554
+ ├── storage/ # Storage command module
555
+ ├── index.ts # Storage command definition
556
+ ├── core/ # Upload and preview logic
557
+ ├── storage/ # Storage operations
558
+ ├── transactions/ # Transaction handling
559
+ ├── utils.ts # Storage-specific utilities
560
+ └── types.ts # Storage-specific types
561
+ │ ├── profile/ # Profile command module
562
+ │ │ ├── index.ts # Profile command definition
563
+ │ │ ├── get.ts # Profile get logic
564
+ │ │ ├── set-picture.ts # Set profile picture
565
+ │ │ ├── set-username.ts # Set X username
566
+ │ │ └── types.ts # Profile-specific types
567
+ │ ├── message/ # Message command module
568
+ │ └── token/ # Token command module
439
569
  └── shared/ # Shared utilities across commands
440
570
  └── types.ts # Common types (CommonOptions, etc.)
441
571
  ```
@@ -5,12 +5,14 @@ import { createRequire } from 'module';
5
5
  import chalk4 from 'chalk';
6
6
  import * as fs from 'fs';
7
7
  import { readFileSync } from 'fs';
8
- import { StorageClient, detectFileTypeFromBase64, base64ToDataUri, shouldSuggestXmlStorage, getStorageKeyBytes, encodeStorageKeyForUrl, STORAGE_CONTRACT, CHUNKED_STORAGE_CONTRACT } from '@net-protocol/storage';
9
- import { stringToHex, createWalletClient, http, hexToString, parseEther, encodeFunctionData, defineChain } from 'viem';
8
+ import { StorageClient, detectFileTypeFromBase64, base64ToDataUri, shouldSuggestXmlStorage, getStorageKeyBytes, encodeStorageKeyForUrl, STORAGE_CONTRACT as STORAGE_CONTRACT$1, CHUNKED_STORAGE_CONTRACT } from '@net-protocol/storage';
9
+ import { stringToHex, createWalletClient, http, hexToString, parseEther, encodeFunctionData, publicActions, defineChain } from 'viem';
10
10
  import { privateKeyToAccount } from 'viem/accounts';
11
11
  import { getNetContract, getChainName, getPublicClient, getChainRpcUrls, NetClient } from '@net-protocol/core';
12
12
  import { createRelayX402Client, createRelaySession, checkBackendWalletBalance, fundBackendWallet, batchTransactions, submitTransactionsViaRelay, waitForConfirmations, retryFailedTransactions as retryFailedTransactions$1 } from '@net-protocol/relay';
13
13
  import { isNetrSupportedChain, NetrClient } from '@net-protocol/netr';
14
+ import { PROFILE_PICTURE_STORAGE_KEY, PROFILE_METADATA_STORAGE_KEY, parseProfileMetadata, isValidUrl, getProfilePictureStorageArgs, STORAGE_CONTRACT, isValidXUsername, getXUsernameStorageArgs } from '@net-protocol/profiles';
15
+ import { base } from 'viem/chains';
14
16
 
15
17
  function getRequiredChainId(optionValue) {
16
18
  const chainId = optionValue || (process.env.NET_CHAIN_ID ? parseInt(process.env.NET_CHAIN_ID, 10) : void 0);
@@ -297,7 +299,7 @@ async function filterExistingTransactions(params) {
297
299
  async function filterXmlStorageTransactions(params) {
298
300
  const { storageClient, transactions, operatorAddress } = params;
299
301
  const metadataTx = transactions.find(
300
- (tx) => tx.to.toLowerCase() === STORAGE_CONTRACT.address.toLowerCase()
302
+ (tx) => tx.to.toLowerCase() === STORAGE_CONTRACT$1.address.toLowerCase()
301
303
  );
302
304
  const chunkTxs = transactions.filter(
303
305
  (tx) => tx.to.toLowerCase() === CHUNKED_STORAGE_CONTRACT.address.toLowerCase()
@@ -1468,8 +1470,8 @@ function registerStorageCommand(program2) {
1468
1470
  console.log(chalk4.blue(`\u{1F4C1} Reading file: ${options.file}`));
1469
1471
  console.log(chalk4.blue(`\u{1F517} Using relay API: ${options.apiUrl}`));
1470
1472
  const result = await uploadFileWithRelay(uploadRelayOptions);
1471
- const { privateKeyToAccount: privateKeyToAccount6 } = await import('viem/accounts');
1472
- const userAccount = privateKeyToAccount6(commonOptions.privateKey);
1473
+ const { privateKeyToAccount: privateKeyToAccount8 } = await import('viem/accounts');
1474
+ const userAccount = privateKeyToAccount8(commonOptions.privateKey);
1473
1475
  const storageUrl = generateStorageUrl(
1474
1476
  userAccount.address,
1475
1477
  commonOptions.chainId,
@@ -2216,6 +2218,289 @@ function registerTokenCommand(program2) {
2216
2218
  tokenCommand.addCommand(deployCommand);
2217
2219
  tokenCommand.addCommand(infoCommand);
2218
2220
  }
2221
+ async function executeProfileGet(options) {
2222
+ const readOnlyOptions = parseReadOnlyOptions({
2223
+ chainId: options.chainId,
2224
+ rpcUrl: options.rpcUrl
2225
+ });
2226
+ const client = new StorageClient({
2227
+ chainId: readOnlyOptions.chainId,
2228
+ overrides: options.rpcUrl ? { rpcUrls: [options.rpcUrl] } : void 0
2229
+ });
2230
+ try {
2231
+ let profilePicture;
2232
+ try {
2233
+ const pictureResult = await client.readStorageData({
2234
+ key: PROFILE_PICTURE_STORAGE_KEY,
2235
+ operator: options.address
2236
+ });
2237
+ if (pictureResult.data) {
2238
+ profilePicture = pictureResult.data;
2239
+ }
2240
+ } catch (error) {
2241
+ const errorMessage = error instanceof Error ? error.message : String(error);
2242
+ if (errorMessage !== "StoredDataNotFound") {
2243
+ throw error;
2244
+ }
2245
+ }
2246
+ let xUsername;
2247
+ try {
2248
+ const metadataResult = await client.readStorageData({
2249
+ key: PROFILE_METADATA_STORAGE_KEY,
2250
+ operator: options.address
2251
+ });
2252
+ if (metadataResult.data) {
2253
+ const metadata = parseProfileMetadata(metadataResult.data);
2254
+ xUsername = metadata?.x_username;
2255
+ }
2256
+ } catch (error) {
2257
+ const errorMessage = error instanceof Error ? error.message : String(error);
2258
+ if (errorMessage !== "StoredDataNotFound") {
2259
+ throw error;
2260
+ }
2261
+ }
2262
+ const hasProfile = profilePicture || xUsername;
2263
+ if (options.json) {
2264
+ const output = {
2265
+ address: options.address,
2266
+ chainId: readOnlyOptions.chainId,
2267
+ profilePicture: profilePicture || null,
2268
+ xUsername: xUsername || null,
2269
+ hasProfile
2270
+ };
2271
+ console.log(JSON.stringify(output, null, 2));
2272
+ return;
2273
+ }
2274
+ console.log(chalk4.white.bold("\nProfile:\n"));
2275
+ console.log(` ${chalk4.cyan("Address:")} ${options.address}`);
2276
+ console.log(` ${chalk4.cyan("Chain ID:")} ${readOnlyOptions.chainId}`);
2277
+ console.log(
2278
+ ` ${chalk4.cyan("Profile Picture:")} ${profilePicture || chalk4.gray("(not set)")}`
2279
+ );
2280
+ console.log(
2281
+ ` ${chalk4.cyan("X Username:")} ${xUsername ? `@${xUsername}` : chalk4.gray("(not set)")}`
2282
+ );
2283
+ if (!hasProfile) {
2284
+ console.log(chalk4.yellow("\n No profile data found for this address."));
2285
+ }
2286
+ console.log();
2287
+ } catch (error) {
2288
+ exitWithError(
2289
+ `Failed to read profile: ${error instanceof Error ? error.message : String(error)}`
2290
+ );
2291
+ }
2292
+ }
2293
+ async function executeProfileSetPicture(options) {
2294
+ if (!isValidUrl(options.url)) {
2295
+ exitWithError(
2296
+ `Invalid URL: "${options.url}". Please provide a valid URL (e.g., https://example.com/image.jpg)`
2297
+ );
2298
+ }
2299
+ const storageArgs = getProfilePictureStorageArgs(options.url);
2300
+ if (options.encodeOnly) {
2301
+ const readOnlyOptions = parseReadOnlyOptions({
2302
+ chainId: options.chainId,
2303
+ rpcUrl: options.rpcUrl
2304
+ });
2305
+ const encoded = encodeTransaction(
2306
+ {
2307
+ to: STORAGE_CONTRACT.address,
2308
+ abi: STORAGE_CONTRACT.abi,
2309
+ functionName: "put",
2310
+ args: [storageArgs.bytesKey, storageArgs.topic, storageArgs.bytesValue]
2311
+ },
2312
+ readOnlyOptions.chainId
2313
+ );
2314
+ console.log(JSON.stringify(encoded, null, 2));
2315
+ return;
2316
+ }
2317
+ const commonOptions = parseCommonOptions({
2318
+ privateKey: options.privateKey,
2319
+ chainId: options.chainId,
2320
+ rpcUrl: options.rpcUrl
2321
+ });
2322
+ try {
2323
+ const account = privateKeyToAccount(commonOptions.privateKey);
2324
+ const rpcUrls = getChainRpcUrls({
2325
+ chainId: commonOptions.chainId,
2326
+ rpcUrl: commonOptions.rpcUrl
2327
+ });
2328
+ const client = createWalletClient({
2329
+ account,
2330
+ chain: base,
2331
+ // TODO: Support other chains
2332
+ transport: http(rpcUrls[0])
2333
+ }).extend(publicActions);
2334
+ console.log(chalk4.blue(`\u{1F4F7} Setting profile picture...`));
2335
+ console.log(chalk4.gray(` URL: ${options.url}`));
2336
+ console.log(chalk4.gray(` Address: ${account.address}`));
2337
+ const hash = await client.writeContract({
2338
+ address: STORAGE_CONTRACT.address,
2339
+ abi: STORAGE_CONTRACT.abi,
2340
+ functionName: "put",
2341
+ args: [storageArgs.bytesKey, storageArgs.topic, storageArgs.bytesValue]
2342
+ });
2343
+ console.log(chalk4.blue(`\u23F3 Waiting for confirmation...`));
2344
+ const receipt = await client.waitForTransactionReceipt({ hash });
2345
+ if (receipt.status === "success") {
2346
+ console.log(
2347
+ chalk4.green(
2348
+ `
2349
+ \u2713 Profile picture updated successfully!
2350
+ Transaction: ${hash}
2351
+ URL: ${options.url}`
2352
+ )
2353
+ );
2354
+ } else {
2355
+ exitWithError(`Transaction failed: ${hash}`);
2356
+ }
2357
+ } catch (error) {
2358
+ exitWithError(
2359
+ `Failed to set profile picture: ${error instanceof Error ? error.message : String(error)}`
2360
+ );
2361
+ }
2362
+ }
2363
+ async function executeProfileSetUsername(options) {
2364
+ if (!isValidXUsername(options.username)) {
2365
+ exitWithError(
2366
+ `Invalid X username: "${options.username}". Usernames must be 1-15 characters, alphanumeric and underscores only.`
2367
+ );
2368
+ }
2369
+ const storageArgs = getXUsernameStorageArgs(options.username);
2370
+ const displayUsername = options.username.startsWith("@") ? options.username : `@${options.username}`;
2371
+ if (options.encodeOnly) {
2372
+ const readOnlyOptions = parseReadOnlyOptions({
2373
+ chainId: options.chainId,
2374
+ rpcUrl: options.rpcUrl
2375
+ });
2376
+ const encoded = encodeTransaction(
2377
+ {
2378
+ to: STORAGE_CONTRACT.address,
2379
+ abi: STORAGE_CONTRACT.abi,
2380
+ functionName: "put",
2381
+ args: [storageArgs.bytesKey, storageArgs.topic, storageArgs.bytesValue]
2382
+ },
2383
+ readOnlyOptions.chainId
2384
+ );
2385
+ console.log(JSON.stringify(encoded, null, 2));
2386
+ return;
2387
+ }
2388
+ const commonOptions = parseCommonOptions({
2389
+ privateKey: options.privateKey,
2390
+ chainId: options.chainId,
2391
+ rpcUrl: options.rpcUrl
2392
+ });
2393
+ try {
2394
+ const account = privateKeyToAccount(commonOptions.privateKey);
2395
+ const rpcUrls = getChainRpcUrls({
2396
+ chainId: commonOptions.chainId,
2397
+ rpcUrl: commonOptions.rpcUrl
2398
+ });
2399
+ const client = createWalletClient({
2400
+ account,
2401
+ chain: base,
2402
+ // TODO: Support other chains
2403
+ transport: http(rpcUrls[0])
2404
+ }).extend(publicActions);
2405
+ console.log(chalk4.blue(`\u{1F426} Setting X username...`));
2406
+ console.log(chalk4.gray(` Username: ${displayUsername}`));
2407
+ console.log(chalk4.gray(` Address: ${account.address}`));
2408
+ const hash = await client.writeContract({
2409
+ address: STORAGE_CONTRACT.address,
2410
+ abi: STORAGE_CONTRACT.abi,
2411
+ functionName: "put",
2412
+ args: [storageArgs.bytesKey, storageArgs.topic, storageArgs.bytesValue]
2413
+ });
2414
+ console.log(chalk4.blue(`\u23F3 Waiting for confirmation...`));
2415
+ const receipt = await client.waitForTransactionReceipt({ hash });
2416
+ if (receipt.status === "success") {
2417
+ console.log(
2418
+ chalk4.green(
2419
+ `
2420
+ \u2713 X username updated successfully!
2421
+ Transaction: ${hash}
2422
+ Username: ${displayUsername}`
2423
+ )
2424
+ );
2425
+ } else {
2426
+ exitWithError(`Transaction failed: ${hash}`);
2427
+ }
2428
+ } catch (error) {
2429
+ exitWithError(
2430
+ `Failed to set X username: ${error instanceof Error ? error.message : String(error)}`
2431
+ );
2432
+ }
2433
+ }
2434
+
2435
+ // src/commands/profile/index.ts
2436
+ function registerProfileCommand(program2) {
2437
+ const profileCommand = program2.command("profile").description("User profile operations");
2438
+ const getCommand = new Command("get").description("Get profile data for an address").requiredOption("--address <address>", "Wallet address to get profile for").option(
2439
+ "--chain-id <id>",
2440
+ "Chain ID. Can also be set via NET_CHAIN_ID env var",
2441
+ (value) => parseInt(value, 10)
2442
+ ).option(
2443
+ "--rpc-url <url>",
2444
+ "Custom RPC URL. Can also be set via NET_RPC_URL env var"
2445
+ ).option("--json", "Output in JSON format").action(async (options) => {
2446
+ await executeProfileGet({
2447
+ address: options.address,
2448
+ chainId: options.chainId,
2449
+ rpcUrl: options.rpcUrl,
2450
+ json: options.json
2451
+ });
2452
+ });
2453
+ const setPictureCommand = new Command("set-picture").description("Set your profile picture URL").requiredOption("--url <url>", "Image URL for profile picture").option(
2454
+ "--private-key <key>",
2455
+ "Private key (0x-prefixed hex, 66 characters). Can also be set via NET_PRIVATE_KEY env var"
2456
+ ).option(
2457
+ "--chain-id <id>",
2458
+ "Chain ID. Can also be set via NET_CHAIN_ID env var",
2459
+ (value) => parseInt(value, 10)
2460
+ ).option(
2461
+ "--rpc-url <url>",
2462
+ "Custom RPC URL. Can also be set via NET_RPC_URL env var"
2463
+ ).option(
2464
+ "--encode-only",
2465
+ "Output transaction data as JSON instead of executing"
2466
+ ).action(async (options) => {
2467
+ await executeProfileSetPicture({
2468
+ url: options.url,
2469
+ privateKey: options.privateKey,
2470
+ chainId: options.chainId,
2471
+ rpcUrl: options.rpcUrl,
2472
+ encodeOnly: options.encodeOnly
2473
+ });
2474
+ });
2475
+ const setUsernameCommand = new Command("set-x-username").description("Set your X (Twitter) username for your profile").requiredOption(
2476
+ "--username <username>",
2477
+ "Your X (Twitter) username (with or without @)"
2478
+ ).option(
2479
+ "--private-key <key>",
2480
+ "Private key (0x-prefixed hex, 66 characters). Can also be set via NET_PRIVATE_KEY env var"
2481
+ ).option(
2482
+ "--chain-id <id>",
2483
+ "Chain ID. Can also be set via NET_CHAIN_ID env var",
2484
+ (value) => parseInt(value, 10)
2485
+ ).option(
2486
+ "--rpc-url <url>",
2487
+ "Custom RPC URL. Can also be set via NET_RPC_URL env var"
2488
+ ).option(
2489
+ "--encode-only",
2490
+ "Output transaction data as JSON instead of executing"
2491
+ ).action(async (options) => {
2492
+ await executeProfileSetUsername({
2493
+ username: options.username,
2494
+ privateKey: options.privateKey,
2495
+ chainId: options.chainId,
2496
+ rpcUrl: options.rpcUrl,
2497
+ encodeOnly: options.encodeOnly
2498
+ });
2499
+ });
2500
+ profileCommand.addCommand(getCommand);
2501
+ profileCommand.addCommand(setPictureCommand);
2502
+ profileCommand.addCommand(setUsernameCommand);
2503
+ }
2219
2504
 
2220
2505
  // src/cli/index.ts
2221
2506
  var require2 = createRequire(import.meta.url);
@@ -2227,6 +2512,7 @@ registerMessageCommand(program);
2227
2512
  registerChainsCommand(program);
2228
2513
  registerInfoCommand(program);
2229
2514
  registerTokenCommand(program);
2515
+ registerProfileCommand(program);
2230
2516
  program.parse();
2231
2517
  //# sourceMappingURL=index.mjs.map
2232
2518
  //# sourceMappingURL=index.mjs.map