@net-protocol/cli 0.1.10 → 0.1.12

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
@@ -841,3 +841,108 @@ The CLI handles various error scenarios:
841
841
  - Storage read errors
842
842
 
843
843
  If a transaction fails mid-upload, you can safely retry the command - it will only upload missing chunks.
844
+
845
+ #### Bazaar Command
846
+
847
+ NFT Bazaar operations — list, buy, sell, and trade NFTs via Seaport.
848
+
849
+ **Available Subcommands:**
850
+
851
+ - `bazaar list-listings` - List active NFT listings
852
+ - `bazaar list-offers` - List active collection offers
853
+ - `bazaar list-sales` - List recent sales
854
+ - `bazaar owned-nfts` - List NFTs owned by an address
855
+ - `bazaar create-listing` - Create an NFT listing
856
+ - `bazaar create-offer` - Create a collection offer
857
+ - `bazaar submit-listing` - Submit a signed listing
858
+ - `bazaar submit-offer` - Submit a signed offer
859
+ - `bazaar buy-listing` - Buy an NFT listing
860
+ - `bazaar accept-offer` - Accept a collection offer
861
+
862
+ ##### Read Commands
863
+
864
+ ```bash
865
+ # List active listings (--nft-address optional for cross-collection)
866
+ netp bazaar list-listings [--nft-address <address>] --chain-id 8453 [--json]
867
+
868
+ # List collection offers
869
+ netp bazaar list-offers --nft-address <address> --chain-id 8453 [--json]
870
+
871
+ # List recent sales
872
+ netp bazaar list-sales --nft-address <address> --chain-id 8453 [--json]
873
+
874
+ # Check NFTs owned by an address
875
+ netp bazaar owned-nfts --nft-address <address> --owner <address> --chain-id 8453 [--json]
876
+ ```
877
+
878
+ ##### Create Commands (Dual Mode)
879
+
880
+ Create commands support two modes: with `--private-key` for full flow (approve + sign + submit), or without for EIP-712 output (external signing).
881
+
882
+ ```bash
883
+ # Full flow with private key
884
+ netp bazaar create-listing \
885
+ --nft-address <address> --token-id <id> --price <eth> \
886
+ --chain-id 8453 --private-key 0x...
887
+
888
+ # Keyless: outputs EIP-712 data + approval txs for external signing
889
+ netp bazaar create-listing \
890
+ --nft-address <address> --token-id <id> --price <eth> \
891
+ --offerer <address> --chain-id 8453
892
+
893
+ # Same pattern for offers
894
+ netp bazaar create-offer \
895
+ --nft-address <address> --price <eth> \
896
+ --chain-id 8453 --private-key 0x...
897
+ ```
898
+
899
+ ##### Submit Commands
900
+
901
+ Follow-up to keyless create commands:
902
+
903
+ ```bash
904
+ netp bazaar submit-listing \
905
+ --order-data <path> --signature <sig> \
906
+ --chain-id 8453 [--private-key 0x... | --encode-only]
907
+
908
+ netp bazaar submit-offer \
909
+ --order-data <path> --signature <sig> \
910
+ --chain-id 8453 [--private-key 0x... | --encode-only]
911
+ ```
912
+
913
+ ##### Fulfillment Commands
914
+
915
+ ```bash
916
+ # Buy a listing
917
+ netp bazaar buy-listing \
918
+ --order-hash <hash> --nft-address <address> \
919
+ --chain-id 8453 [--private-key 0x... | --buyer <address> --encode-only]
920
+
921
+ # Accept an offer (sell your NFT)
922
+ netp bazaar accept-offer \
923
+ --order-hash <hash> --nft-address <address> --token-id <id> \
924
+ --chain-id 8453 [--private-key 0x... | --seller <address> --encode-only]
925
+ ```
926
+
927
+ ##### Encode-Only Mode
928
+
929
+ All write commands support `--encode-only` for agent integration:
930
+
931
+ ```bash
932
+ netp bazaar buy-listing \
933
+ --order-hash 0x... --nft-address 0x... \
934
+ --buyer 0xAgentWallet --chain-id 8453 --encode-only
935
+ ```
936
+
937
+ Output:
938
+ ```json
939
+ {
940
+ "approvals": [],
941
+ "fulfillment": {
942
+ "to": "0x...",
943
+ "data": "0x...",
944
+ "chainId": 8453,
945
+ "value": "10000000000000"
946
+ }
947
+ }
948
+ ```
@@ -12,7 +12,7 @@ import { privateKeyToAccount } from 'viem/accounts';
12
12
  import { getNetContract, getChainName, getPublicClient, getChainRpcUrls, NetClient, toBytes32 } 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 { isNetrSupportedChain, NetrClient } from '@net-protocol/netr';
15
- import { PROFILE_PICTURE_STORAGE_KEY, PROFILE_METADATA_STORAGE_KEY, parseProfileMetadata, PROFILE_CANVAS_STORAGE_KEY, isValidUrl, getProfilePictureStorageArgs, STORAGE_CONTRACT, isValidXUsername, getXUsernameStorageArgs, isValidBio, getProfileMetadataStorageArgs } from '@net-protocol/profiles';
15
+ import { PROFILE_PICTURE_STORAGE_KEY, PROFILE_METADATA_STORAGE_KEY, parseProfileMetadata, PROFILE_CANVAS_STORAGE_KEY, isValidUrl, getProfilePictureStorageArgs, STORAGE_CONTRACT, isValidXUsername, getProfileMetadataStorageArgs, isValidBio, isValidTokenAddress } from '@net-protocol/profiles';
16
16
  import { base } from 'viem/chains';
17
17
  import * as path from 'path';
18
18
  import { BazaarClient } from '@net-protocol/bazaar';
@@ -1478,8 +1478,8 @@ function registerStorageCommand(program2) {
1478
1478
  console.log(chalk4.blue(`\u{1F4C1} Reading file: ${options.file}`));
1479
1479
  console.log(chalk4.blue(`\u{1F517} Using relay API: ${options.apiUrl}`));
1480
1480
  const result = await uploadFileWithRelay(uploadRelayOptions);
1481
- const { privateKeyToAccount: privateKeyToAccount16 } = await import('viem/accounts');
1482
- const userAccount = privateKeyToAccount16(commonOptions.privateKey);
1481
+ const { privateKeyToAccount: privateKeyToAccount17 } = await import('viem/accounts');
1482
+ const userAccount = privateKeyToAccount17(commonOptions.privateKey);
1483
1483
  const storageUrl = generateStorageUrl(
1484
1484
  userAccount.address,
1485
1485
  commonOptions.chainId,
@@ -2261,6 +2261,7 @@ async function executeProfileGet(options) {
2261
2261
  }
2262
2262
  let xUsername;
2263
2263
  let bio;
2264
+ let tokenAddress;
2264
2265
  try {
2265
2266
  const metadataResult = await client.readStorageData({
2266
2267
  key: PROFILE_METADATA_STORAGE_KEY,
@@ -2270,6 +2271,7 @@ async function executeProfileGet(options) {
2270
2271
  const metadata = parseProfileMetadata(metadataResult.data);
2271
2272
  xUsername = metadata?.x_username;
2272
2273
  bio = metadata?.bio;
2274
+ tokenAddress = metadata?.token_address;
2273
2275
  }
2274
2276
  } catch (error) {
2275
2277
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -2294,7 +2296,7 @@ async function executeProfileGet(options) {
2294
2296
  throw error;
2295
2297
  }
2296
2298
  }
2297
- const hasProfile = profilePicture || xUsername || bio || canvasSize;
2299
+ const hasProfile = profilePicture || xUsername || bio || tokenAddress || canvasSize;
2298
2300
  if (options.json) {
2299
2301
  const output = {
2300
2302
  address: options.address,
@@ -2302,6 +2304,7 @@ async function executeProfileGet(options) {
2302
2304
  profilePicture: profilePicture || null,
2303
2305
  xUsername: xUsername || null,
2304
2306
  bio: bio || null,
2307
+ tokenAddress: tokenAddress || null,
2305
2308
  canvas: canvasSize ? { size: canvasSize, isDataUri: canvasIsDataUri } : null,
2306
2309
  hasProfile
2307
2310
  };
@@ -2320,6 +2323,9 @@ async function executeProfileGet(options) {
2320
2323
  console.log(
2321
2324
  ` ${chalk4.cyan("Bio:")} ${bio || chalk4.gray("(not set)")}`
2322
2325
  );
2326
+ console.log(
2327
+ ` ${chalk4.cyan("Token Address:")} ${tokenAddress || chalk4.gray("(not set)")}`
2328
+ );
2323
2329
  console.log(
2324
2330
  ` ${chalk4.cyan("Canvas:")} ${canvasSize ? `${canvasSize} bytes${canvasIsDataUri ? " (data URI)" : ""}` : chalk4.gray("(not set)")}`
2325
2331
  );
@@ -2407,19 +2413,47 @@ async function executeProfileSetPicture(options) {
2407
2413
  );
2408
2414
  }
2409
2415
  }
2416
+ async function readExistingMetadata(address, client) {
2417
+ try {
2418
+ const metadataResult = await client.readStorageData({
2419
+ key: PROFILE_METADATA_STORAGE_KEY,
2420
+ operator: address
2421
+ });
2422
+ if (metadataResult.data) {
2423
+ const metadata = parseProfileMetadata(metadataResult.data);
2424
+ return {
2425
+ x_username: metadata?.x_username,
2426
+ bio: metadata?.bio,
2427
+ display_name: metadata?.display_name,
2428
+ token_address: metadata?.token_address
2429
+ };
2430
+ }
2431
+ } catch (error) {
2432
+ const errorMessage = error instanceof Error ? error.message : String(error);
2433
+ if (errorMessage !== "StoredDataNotFound") {
2434
+ throw error;
2435
+ }
2436
+ }
2437
+ return {};
2438
+ }
2439
+
2440
+ // src/commands/profile/set-username.ts
2410
2441
  async function executeProfileSetUsername(options) {
2411
2442
  if (!isValidXUsername(options.username)) {
2412
2443
  exitWithError(
2413
2444
  `Invalid X username: "${options.username}". Usernames must be 1-15 characters, alphanumeric and underscores only.`
2414
2445
  );
2415
2446
  }
2416
- const storageArgs = getXUsernameStorageArgs(options.username);
2417
- const displayUsername = options.username.startsWith("@") ? options.username : `@${options.username}`;
2447
+ const usernameForStorage = options.username.startsWith("@") ? options.username.slice(1) : options.username;
2448
+ const displayUsername = `@${usernameForStorage}`;
2418
2449
  if (options.encodeOnly) {
2419
2450
  const readOnlyOptions = parseReadOnlyOptions({
2420
2451
  chainId: options.chainId,
2421
2452
  rpcUrl: options.rpcUrl
2422
2453
  });
2454
+ const storageArgs = getProfileMetadataStorageArgs({
2455
+ x_username: usernameForStorage
2456
+ });
2423
2457
  const encoded = encodeTransaction(
2424
2458
  {
2425
2459
  to: STORAGE_CONTRACT.address,
@@ -2453,22 +2487,36 @@ async function executeProfileSetUsername(options) {
2453
2487
  // TODO: Support other chains
2454
2488
  transport: http(rpcUrls[0])
2455
2489
  }).extend(publicActions);
2456
- console.log(chalk4.blue(`\u{1F426} Setting X username...`));
2490
+ console.log(chalk4.blue(`Setting X username...`));
2457
2491
  console.log(chalk4.gray(` Username: ${displayUsername}`));
2458
2492
  console.log(chalk4.gray(` Address: ${account.address}`));
2493
+ const storageClient = new StorageClient({
2494
+ chainId: commonOptions.chainId,
2495
+ overrides: commonOptions.rpcUrl ? { rpcUrls: [commonOptions.rpcUrl] } : void 0
2496
+ });
2497
+ const existing = await readExistingMetadata(
2498
+ account.address,
2499
+ storageClient
2500
+ );
2501
+ const storageArgs = getProfileMetadataStorageArgs({
2502
+ x_username: usernameForStorage,
2503
+ bio: existing.bio,
2504
+ display_name: existing.display_name,
2505
+ token_address: existing.token_address
2506
+ });
2459
2507
  const hash = await client.writeContract({
2460
2508
  address: STORAGE_CONTRACT.address,
2461
2509
  abi: STORAGE_CONTRACT.abi,
2462
2510
  functionName: "put",
2463
2511
  args: [storageArgs.bytesKey, storageArgs.topic, storageArgs.bytesValue]
2464
2512
  });
2465
- console.log(chalk4.blue(`\u23F3 Waiting for confirmation...`));
2513
+ console.log(chalk4.blue(`Waiting for confirmation...`));
2466
2514
  const receipt = await client.waitForTransactionReceipt({ hash });
2467
2515
  if (receipt.status === "success") {
2468
2516
  console.log(
2469
2517
  chalk4.green(
2470
2518
  `
2471
- \u2713 X username updated successfully!
2519
+ X username updated successfully!
2472
2520
  Transaction: ${hash}
2473
2521
  Username: ${displayUsername}`
2474
2522
  )
@@ -2488,12 +2536,12 @@ async function executeProfileSetBio(options) {
2488
2536
  `Invalid bio: "${options.bio}". Bio must be 1-280 characters and cannot contain control characters.`
2489
2537
  );
2490
2538
  }
2491
- const storageArgs = getProfileMetadataStorageArgs({ bio: options.bio });
2492
2539
  if (options.encodeOnly) {
2493
2540
  const readOnlyOptions = parseReadOnlyOptions({
2494
2541
  chainId: options.chainId,
2495
2542
  rpcUrl: options.rpcUrl
2496
2543
  });
2544
+ const storageArgs = getProfileMetadataStorageArgs({ bio: options.bio });
2497
2545
  const encoded = encodeTransaction(
2498
2546
  {
2499
2547
  to: STORAGE_CONTRACT.address,
@@ -2530,6 +2578,20 @@ async function executeProfileSetBio(options) {
2530
2578
  console.log(chalk4.blue(`Setting profile bio...`));
2531
2579
  console.log(chalk4.gray(` Bio: ${options.bio}`));
2532
2580
  console.log(chalk4.gray(` Address: ${account.address}`));
2581
+ const storageClient = new StorageClient({
2582
+ chainId: commonOptions.chainId,
2583
+ overrides: commonOptions.rpcUrl ? { rpcUrls: [commonOptions.rpcUrl] } : void 0
2584
+ });
2585
+ const existing = await readExistingMetadata(
2586
+ account.address,
2587
+ storageClient
2588
+ );
2589
+ const storageArgs = getProfileMetadataStorageArgs({
2590
+ bio: options.bio,
2591
+ x_username: existing.x_username,
2592
+ display_name: existing.display_name,
2593
+ token_address: existing.token_address
2594
+ });
2533
2595
  const hash = await client.writeContract({
2534
2596
  address: STORAGE_CONTRACT.address,
2535
2597
  abi: STORAGE_CONTRACT.abi,
@@ -2556,6 +2618,97 @@ Bio updated successfully!
2556
2618
  );
2557
2619
  }
2558
2620
  }
2621
+ async function executeProfileSetTokenAddress(options) {
2622
+ if (!isValidTokenAddress(options.tokenAddress)) {
2623
+ exitWithError(
2624
+ `Invalid token address: "${options.tokenAddress}". Must be a valid EVM address (0x-prefixed, 40 hex characters).`
2625
+ );
2626
+ }
2627
+ const normalizedAddress = options.tokenAddress.toLowerCase();
2628
+ if (options.encodeOnly) {
2629
+ const readOnlyOptions = parseReadOnlyOptions({
2630
+ chainId: options.chainId,
2631
+ rpcUrl: options.rpcUrl
2632
+ });
2633
+ const storageArgs = getProfileMetadataStorageArgs({
2634
+ token_address: normalizedAddress
2635
+ });
2636
+ const encoded = encodeTransaction(
2637
+ {
2638
+ to: STORAGE_CONTRACT.address,
2639
+ abi: STORAGE_CONTRACT.abi,
2640
+ functionName: "put",
2641
+ args: [storageArgs.bytesKey, storageArgs.topic, storageArgs.bytesValue]
2642
+ },
2643
+ readOnlyOptions.chainId
2644
+ );
2645
+ console.log(JSON.stringify(encoded, null, 2));
2646
+ return;
2647
+ }
2648
+ const commonOptions = parseCommonOptions(
2649
+ {
2650
+ privateKey: options.privateKey,
2651
+ chainId: options.chainId,
2652
+ rpcUrl: options.rpcUrl
2653
+ },
2654
+ true
2655
+ // supports --encode-only
2656
+ );
2657
+ try {
2658
+ const account = privateKeyToAccount(commonOptions.privateKey);
2659
+ const rpcUrls = getChainRpcUrls({
2660
+ chainId: commonOptions.chainId,
2661
+ rpcUrl: commonOptions.rpcUrl
2662
+ });
2663
+ const client = createWalletClient({
2664
+ account,
2665
+ chain: base,
2666
+ // TODO: Support other chains
2667
+ transport: http(rpcUrls[0])
2668
+ }).extend(publicActions);
2669
+ console.log(chalk4.blue(`Setting profile token address...`));
2670
+ console.log(chalk4.gray(` Token Address: ${normalizedAddress}`));
2671
+ console.log(chalk4.gray(` Address: ${account.address}`));
2672
+ const storageClient = new StorageClient({
2673
+ chainId: commonOptions.chainId,
2674
+ overrides: commonOptions.rpcUrl ? { rpcUrls: [commonOptions.rpcUrl] } : void 0
2675
+ });
2676
+ const existing = await readExistingMetadata(
2677
+ account.address,
2678
+ storageClient
2679
+ );
2680
+ const storageArgs = getProfileMetadataStorageArgs({
2681
+ token_address: normalizedAddress,
2682
+ x_username: existing.x_username,
2683
+ bio: existing.bio,
2684
+ display_name: existing.display_name
2685
+ });
2686
+ const hash = await client.writeContract({
2687
+ address: STORAGE_CONTRACT.address,
2688
+ abi: STORAGE_CONTRACT.abi,
2689
+ functionName: "put",
2690
+ args: [storageArgs.bytesKey, storageArgs.topic, storageArgs.bytesValue]
2691
+ });
2692
+ console.log(chalk4.blue(`Waiting for confirmation...`));
2693
+ const receipt = await client.waitForTransactionReceipt({ hash });
2694
+ if (receipt.status === "success") {
2695
+ console.log(
2696
+ chalk4.green(
2697
+ `
2698
+ Token address updated successfully!
2699
+ Transaction: ${hash}
2700
+ Token Address: ${normalizedAddress}`
2701
+ )
2702
+ );
2703
+ } else {
2704
+ exitWithError(`Transaction failed: ${hash}`);
2705
+ }
2706
+ } catch (error) {
2707
+ exitWithError(
2708
+ `Failed to set token address: ${error instanceof Error ? error.message : String(error)}`
2709
+ );
2710
+ }
2711
+ }
2559
2712
  var MAX_CANVAS_SIZE = 60 * 1024;
2560
2713
  var CANVAS_FILENAME = "profile-compressed.html";
2561
2714
  function isBinaryContent(buffer) {
@@ -2889,6 +3042,31 @@ function registerProfileCommand(program2) {
2889
3042
  encodeOnly: options.encodeOnly
2890
3043
  });
2891
3044
  });
3045
+ const setTokenAddressCommand = new Command("set-token-address").description("Set your profile token address (ERC-20 token that represents you)").requiredOption(
3046
+ "--token-address <address>",
3047
+ "ERC-20 token contract address (0x-prefixed)"
3048
+ ).option(
3049
+ "--private-key <key>",
3050
+ "Private key (0x-prefixed hex, 66 characters). Can also be set via NET_PRIVATE_KEY env var"
3051
+ ).option(
3052
+ "--chain-id <id>",
3053
+ "Chain ID. Can also be set via NET_CHAIN_ID env var",
3054
+ (value) => parseInt(value, 10)
3055
+ ).option(
3056
+ "--rpc-url <url>",
3057
+ "Custom RPC URL. Can also be set via NET_RPC_URL env var"
3058
+ ).option(
3059
+ "--encode-only",
3060
+ "Output transaction data as JSON instead of executing"
3061
+ ).action(async (options) => {
3062
+ await executeProfileSetTokenAddress({
3063
+ tokenAddress: options.tokenAddress,
3064
+ privateKey: options.privateKey,
3065
+ chainId: options.chainId,
3066
+ rpcUrl: options.rpcUrl,
3067
+ encodeOnly: options.encodeOnly
3068
+ });
3069
+ });
2892
3070
  const setCanvasCommand = new Command("set-canvas").description("Set your profile canvas (HTML content)").option("--file <path>", "Path to file containing canvas content").option("--content <html>", "HTML content for canvas (inline)").option(
2893
3071
  "--private-key <key>",
2894
3072
  "Private key (0x-prefixed hex, 66 characters). Can also be set via NET_PRIVATE_KEY env var"
@@ -2932,6 +3110,7 @@ function registerProfileCommand(program2) {
2932
3110
  profileCommand.addCommand(setPictureCommand);
2933
3111
  profileCommand.addCommand(setUsernameCommand);
2934
3112
  profileCommand.addCommand(setBioCommand);
3113
+ profileCommand.addCommand(setTokenAddressCommand);
2935
3114
  profileCommand.addCommand(setCanvasCommand);
2936
3115
  profileCommand.addCommand(getCanvasCommand);
2937
3116
  }
@@ -3862,7 +4041,7 @@ var privateKeyOption = [
3862
4041
  "Private key (0x-prefixed hex). Can also be set via NET_PRIVATE_KEY env var"
3863
4042
  ];
3864
4043
  function registerBazaarCommand(program2) {
3865
- const bazaarCommand = program2.command("bazaar").description("Bazaar NFT marketplace operations");
4044
+ const bazaarCommand = program2.command("bazaar").description("Bazaar NFT trading operations");
3866
4045
  const listListingsCommand = new Command("list-listings").description("List active NFT listings").option("--nft-address <address>", "NFT contract address (optional for cross-collection)").option(...chainIdOption).option(...rpcUrlOption).option("--json", "Output in JSON format").action(async (options) => {
3867
4046
  await executeListListings({
3868
4047
  nftAddress: options.nftAddress,