@net-protocol/cli 0.2.0 → 0.2.2

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
@@ -365,6 +365,59 @@ netp token info \
365
365
  --json
366
366
  ```
367
367
 
368
+ #### Upvote Command
369
+
370
+ Upvote-related operations for Net Protocol. Run `netp upvote --help` for the
371
+ full list of subcommands (`token`, `info`, `user`, `user-info`, `rankings`).
372
+
373
+ ##### Upvote Rankings
374
+
375
+ List the top tokens ranked by upvote activity — the same leaderboard that
376
+ powers the website's `/token/<chain>/trending` page. All reads are live from
377
+ chain; no off-chain index. Callers should cache via HTTP headers.
378
+
379
+ ```bash
380
+ netp upvote rankings \
381
+ [--sort <trending|recent|top>] \
382
+ [--limit <n>] \
383
+ [--scan-window <n>] \
384
+ [--min-upvotes <n>] \
385
+ [--min-market-cap <n>] \
386
+ [--recency-hours <n>] \
387
+ [--chain-id <8453>] \
388
+ [--rpc-url <custom-rpc>] \
389
+ [--json]
390
+ ```
391
+
392
+ **Arguments:**
393
+
394
+ - `--sort` (optional): Ranking strategy. `trending` (time-decayed score, recent upvotes weighted higher), `recent` (latest upvote timestamp), `top` (aggregate upvote count). Default: `trending`.
395
+ - `--limit` (optional): Number of tokens to return, 1-100. Default: 50.
396
+ - `--scan-window` (optional): Messages to scan per contract (legacy + 3 strategies). Default: 150.
397
+ - `--min-upvotes` (optional): Two-tier filter floor — tokens with at least this many aggregate upvotes get top slots. Default: 500.
398
+ - `--min-market-cap` (optional): FDV floor in USDC for the top slots. Default: 40000.
399
+ - `--recency-hours` (optional): Drop below-floor tokens with no upvote within N hours. Default: 48.
400
+ - `--chain-id` (optional): Chain ID. Base (8453) is currently the only supported chain.
401
+ - `--rpc-url` (optional): Custom RPC URL.
402
+ - `--json` (optional): Output JSON.
403
+
404
+ **Example:**
405
+
406
+ ```bash
407
+ netp upvote rankings --sort top --limit 5 --chain-id 8453
408
+ ```
409
+
410
+ Output:
411
+
412
+ ```
413
+ Top 5 tokens by top on chain 8453:
414
+ # 1 ALPHA 2238158 upvotes FDV 402.75K $0.000004 0x3d01fe5a...
415
+ # 2 BNKR 133462 upvotes FDV 53.03M $0.000530 0x22af33fe...
416
+ # 3 DICKBUTT 33364 upvotes FDV 388.94K $0.000004 0x2d57c47b...
417
+ # 4 AEGON 33335 upvotes FDV 285.30K $0.000285 0x78a5d1de...
418
+ # 5 DRB 31460 upvotes FDV 4.23M $0.000042 0x3ec2156d...
419
+ ```
420
+
368
421
  #### Profile Command
369
422
 
370
423
  Profile operations for managing your Net Protocol profile.
@@ -797,7 +850,7 @@ src/
797
850
  │ ├── feed/ # Feed command module
798
851
  │ ├── message/ # Message command module
799
852
  │ ├── token/ # Token command module
800
- │ ├── bazaar/ # NFT Bazaar command module
853
+ │ ├── bazaar/ # Bazaar command module (NFT + ERC-20)
801
854
  │ ├── agent/ # Onchain agent command module
802
855
  │ ├── relay/ # Relay fund/balance command module
803
856
  │ ├── upvote/ # Token/user upvoting command module
@@ -1077,10 +1130,11 @@ netp relay balance --chain-id 8453 --json
1077
1130
 
1078
1131
  #### Bazaar Command
1079
1132
 
1080
- NFT Bazaar operations — list, buy, sell, and trade NFTs via Seaport.
1133
+ Bazaar operations — list, buy, sell, and trade NFTs and ERC-20 tokens via Seaport. NFT bazaar is supported on Base (8453) and Ethereum (1); ERC-20 bazaar is supported on Base (8453) and HyperEVM (999).
1081
1134
 
1082
1135
  **Available Subcommands:**
1083
1136
 
1137
+ NFT:
1084
1138
  - `bazaar list-listings` - List active NFT listings
1085
1139
  - `bazaar list-offers` - List active collection offers
1086
1140
  - `bazaar list-sales` - List recent sales
@@ -1091,6 +1145,20 @@ NFT Bazaar operations — list, buy, sell, and trade NFTs via Seaport.
1091
1145
  - `bazaar submit-offer` - Submit a signed offer
1092
1146
  - `bazaar buy-listing` - Buy an NFT listing
1093
1147
  - `bazaar accept-offer` - Accept a collection offer
1148
+ - `bazaar cancel-listing` - Cancel an NFT listing you created
1149
+ - `bazaar cancel-offer` - Cancel a collection offer you created
1150
+
1151
+ ERC-20:
1152
+ - `bazaar list-erc20-listings` - List active ERC-20 token listings
1153
+ - `bazaar list-erc20-offers` - List active ERC-20 token offers
1154
+ - `bazaar create-erc20-listing` - Create an ERC-20 token listing
1155
+ - `bazaar create-erc20-offer` - Create an ERC-20 token offer
1156
+ - `bazaar submit-erc20-listing` - Submit a signed ERC-20 listing
1157
+ - `bazaar submit-erc20-offer` - Submit a signed ERC-20 offer
1158
+ - `bazaar buy-erc20-listing` - Buy an ERC-20 listing
1159
+ - `bazaar accept-erc20-offer` - Accept an ERC-20 offer
1160
+ - `bazaar cancel-erc20-listing` - Cancel an ERC-20 listing you created
1161
+ - `bazaar cancel-erc20-offer` - Cancel an ERC-20 offer you created
1094
1162
 
1095
1163
  ##### Read Commands
1096
1164
 
@@ -1106,6 +1174,12 @@ netp bazaar list-sales --nft-address <address> --chain-id 8453 [--json]
1106
1174
 
1107
1175
  # Check NFTs owned by an address
1108
1176
  netp bazaar owned-nfts --nft-address <address> --owner <address> --chain-id 8453 [--json]
1177
+
1178
+ # List active ERC-20 listings (sorted by price-per-token ascending)
1179
+ netp bazaar list-erc20-listings --token-address <address> --chain-id 8453 [--json]
1180
+
1181
+ # List active ERC-20 offers (sorted by price-per-token descending, balance-validated)
1182
+ netp bazaar list-erc20-offers --token-address <address> --chain-id 8453 [--json]
1109
1183
  ```
1110
1184
 
1111
1185
  ##### Create Commands (Dual Mode)
@@ -1127,8 +1201,20 @@ netp bazaar create-listing \
1127
1201
  netp bazaar create-offer \
1128
1202
  --nft-address <address> --price <eth> \
1129
1203
  --chain-id 8453 --private-key 0x...
1204
+
1205
+ # ERC-20 listing (sell tokens for the chain's native currency) — same dual mode
1206
+ netp bazaar create-erc20-listing \
1207
+ --token-address <address> --token-amount <raw-units> --price <native-amount> \
1208
+ --chain-id 8453 --private-key 0x...
1209
+
1210
+ # ERC-20 offer (bid wrapped native currency for tokens) — same dual mode
1211
+ netp bazaar create-erc20-offer \
1212
+ --token-address <address> --token-amount <raw-units> --price <wrapped-native-amount> \
1213
+ --chain-id 8453 --private-key 0x...
1130
1214
  ```
1131
1215
 
1216
+ `--token-amount` is in **raw token units** (bigint string, e.g. `1000000000000000000` for 1.0 of an 18-decimal token). `--price` is the **total** amount of the chain's native currency for ERC-20 listings (ETH on Base, HYPE on HyperEVM), or the **total** wrapped-native amount for ERC-20 offers (WETH on Base, wrapped HYPE on HyperEVM).
1217
+
1132
1218
  ##### Submit Commands
1133
1219
 
1134
1220
  Follow-up to keyless create commands:
@@ -1141,6 +1227,15 @@ netp bazaar submit-listing \
1141
1227
  netp bazaar submit-offer \
1142
1228
  --order-data <path> --signature <sig> \
1143
1229
  --chain-id 8453 [--private-key 0x... | --encode-only]
1230
+
1231
+ # ERC-20 variants
1232
+ netp bazaar submit-erc20-listing \
1233
+ --order-data <path> --signature <sig> \
1234
+ --chain-id 8453 [--private-key 0x... | --encode-only]
1235
+
1236
+ netp bazaar submit-erc20-offer \
1237
+ --order-data <path> --signature <sig> \
1238
+ --chain-id 8453 [--private-key 0x... | --encode-only]
1144
1239
  ```
1145
1240
 
1146
1241
  ##### Fulfillment Commands
@@ -1151,10 +1246,21 @@ netp bazaar buy-listing \
1151
1246
  --order-hash <hash> --nft-address <address> \
1152
1247
  --chain-id 8453 [--private-key 0x... | --buyer <address> --encode-only]
1153
1248
 
1154
- # Accept an offer (sell your NFT)
1249
+ # Accept an offer (sell your NFT). Fulfillment value is 0 — buyer pays in WETH.
1155
1250
  netp bazaar accept-offer \
1156
1251
  --order-hash <hash> --nft-address <address> --token-id <id> \
1157
1252
  --chain-id 8453 [--private-key 0x... | --seller <address> --encode-only]
1253
+
1254
+ # Buy an ERC-20 listing (pays in the chain's native currency — ETH on Base, HYPE on HyperEVM)
1255
+ netp bazaar buy-erc20-listing \
1256
+ --order-hash <hash> --token-address <address> \
1257
+ --chain-id 8453 [--private-key 0x... | --buyer <address> --encode-only]
1258
+
1259
+ # Accept an ERC-20 offer (sell tokens for wrapped native currency — WETH or wrapped HYPE;
1260
+ # no --token-id, amount comes from the offer; fulfillment value is 0)
1261
+ netp bazaar accept-erc20-offer \
1262
+ --order-hash <hash> --token-address <address> \
1263
+ --chain-id 8453 [--private-key 0x... | --seller <address> --encode-only]
1158
1264
  ```
1159
1265
 
1160
1266
  ##### Encode-Only Mode
@@ -1179,3 +1285,7 @@ Output:
1179
1285
  }
1180
1286
  }
1181
1287
  ```
1288
+
1289
+ The `fulfillment.value` is:
1290
+ - The listing price in wei (in the chain's native currency) for NFT or ERC-20 **listing buys**.
1291
+ - **Zero** for **offer accepts** on both sides (NFT collection offers and ERC-20 offers pay in the wrapped native currency, which the offerer pre-approved).
@@ -22,7 +22,7 @@ import { BazaarClient } from '@net-protocol/bazaar';
22
22
  import * as os from 'os';
23
23
  import { homedir } from 'os';
24
24
  import * as readline from 'readline';
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
+ 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, getTokenRankings } from '@net-protocol/score';
26
26
  import { RELAY_ACCESS_KEY, generateAgentChatTopic, AgentClient, isAgentChatTopic, parseAgentAddressFromTopic, buildSessionTypedData, NET_API_URL, exchangeSessionSignature, buildConversationAuthTypedData, NET_TESTNET_API_URL } from '@net-protocol/agents';
27
27
 
28
28
  var DEFAULT_CHAIN_ID = 8453;
@@ -8280,6 +8280,155 @@ function registerGetUserUpvotesCommand(parent, commandName = "user-info") {
8280
8280
  await executeGetUserUpvotes(options);
8281
8281
  });
8282
8282
  }
8283
+ var VALID_SORTS = ["trending", "recent", "top"];
8284
+ function formatNumber(n, digits = 2) {
8285
+ if (n == null || !Number.isFinite(n)) return "-";
8286
+ if (n >= 1e9) return `${(n / 1e9).toFixed(digits)}B`;
8287
+ if (n >= 1e6) return `${(n / 1e6).toFixed(digits)}M`;
8288
+ if (n >= 1e3) return `${(n / 1e3).toFixed(digits)}K`;
8289
+ return n.toFixed(digits);
8290
+ }
8291
+ function formatPrice(price) {
8292
+ if (price == null || !Number.isFinite(price)) return "-";
8293
+ if (price < 1e-6) return price.toExponential(2);
8294
+ if (price < 1) return `$${price.toFixed(6)}`;
8295
+ return `$${price.toFixed(4)}`;
8296
+ }
8297
+ async function executeRankings(options) {
8298
+ const sort = (options.sort ?? "trending").toLowerCase();
8299
+ if (!VALID_SORTS.includes(sort)) {
8300
+ exitWithError(
8301
+ `Invalid --sort "${options.sort}". Must be one of: ${VALID_SORTS.join(", ")}`
8302
+ );
8303
+ return;
8304
+ }
8305
+ const limit = options.limit ?? 10;
8306
+ if (!Number.isFinite(limit) || limit < 1 || limit > 100) {
8307
+ exitWithError("Invalid --limit. Must be an integer between 1 and 100.");
8308
+ return;
8309
+ }
8310
+ const optionalPositiveInt = (value, name, { allowZero = false } = {}) => {
8311
+ if (value === void 0) return void 0;
8312
+ if (!Number.isFinite(value) || (allowZero ? value < 0 : value < 1)) {
8313
+ exitWithError(
8314
+ `Invalid ${name}. Must be a ${allowZero ? "non-negative" : "positive"} integer.`
8315
+ );
8316
+ }
8317
+ return value;
8318
+ };
8319
+ const scanWindow = optionalPositiveInt(options.scanWindow, "--scan-window");
8320
+ const minUpvotes = optionalPositiveInt(options.minUpvotes, "--min-upvotes", {
8321
+ allowZero: true
8322
+ });
8323
+ const minMarketCap = optionalPositiveInt(
8324
+ options.minMarketCap,
8325
+ "--min-market-cap",
8326
+ { allowZero: true }
8327
+ );
8328
+ const recencyHours = optionalPositiveInt(
8329
+ options.recencyHours,
8330
+ "--recency-hours",
8331
+ { allowZero: true }
8332
+ );
8333
+ if (options.chainId !== void 0 && !Number.isFinite(options.chainId)) {
8334
+ exitWithError("Invalid --chain-id. Must be an integer.");
8335
+ return;
8336
+ }
8337
+ const readOnlyOptions = parseReadOnlyOptionsWithDefault({
8338
+ chainId: options.chainId,
8339
+ rpcUrl: options.rpcUrl
8340
+ });
8341
+ try {
8342
+ const tokens = await getTokenRankings({
8343
+ chainId: readOnlyOptions.chainId,
8344
+ sort,
8345
+ maxTokens: limit,
8346
+ messageScanWindow: scanWindow,
8347
+ thresholds: minUpvotes != null || minMarketCap != null || recencyHours != null ? {
8348
+ minUpvotes,
8349
+ minMarketCap,
8350
+ recencyHours
8351
+ } : void 0,
8352
+ rpcUrl: readOnlyOptions.rpcUrl
8353
+ });
8354
+ if (options.json) {
8355
+ console.log(
8356
+ JSON.stringify(
8357
+ {
8358
+ chainId: readOnlyOptions.chainId,
8359
+ sort,
8360
+ count: tokens.length,
8361
+ tokens: tokens.map((t) => ({
8362
+ ...t,
8363
+ url: tokenUrl(readOnlyOptions.chainId, t.address)
8364
+ }))
8365
+ },
8366
+ null,
8367
+ 2
8368
+ )
8369
+ );
8370
+ return;
8371
+ }
8372
+ if (tokens.length === 0) {
8373
+ console.log(chalk4.yellow("No tokens found."));
8374
+ return;
8375
+ }
8376
+ console.log(
8377
+ chalk4.white(
8378
+ `Top ${tokens.length} tokens by ${sort} on chain ${readOnlyOptions.chainId}:`
8379
+ )
8380
+ );
8381
+ console.log();
8382
+ tokens.forEach((t, i) => {
8383
+ const rank = chalk4.dim(`#${(i + 1).toString().padStart(2, " ")}`);
8384
+ const symbol = chalk4.cyan((t.symbol || "?").padEnd(10, " "));
8385
+ const upvotes = chalk4.white(`${t.upvotes} upvotes`.padEnd(18, " "));
8386
+ const fdv = chalk4.dim(`FDV ${formatNumber(t.fdv)}`.padEnd(14, " "));
8387
+ const price = chalk4.dim(`${formatPrice(t.priceInUsdc)}`.padEnd(14, " "));
8388
+ console.log(`${rank} ${symbol} ${upvotes} ${fdv} ${price} ${t.address}`);
8389
+ });
8390
+ } catch (error) {
8391
+ exitWithError(
8392
+ `Failed to fetch token rankings: ${error instanceof Error ? error.message : String(error)}`
8393
+ );
8394
+ }
8395
+ }
8396
+ function registerRankingsCommand(parent, commandName = "rankings") {
8397
+ parent.command(commandName).description(
8398
+ "List tokens ranked by upvote activity (trending / recent / top)"
8399
+ ).option(
8400
+ "--sort <sort>",
8401
+ `Ranking strategy: ${VALID_SORTS.join(" | ")} (default: trending)`,
8402
+ "trending"
8403
+ ).option(
8404
+ "--limit <n>",
8405
+ "Number of tokens to return (1-100, default: 50)",
8406
+ (v) => parseInt(v, 10),
8407
+ 50
8408
+ ).option(
8409
+ "--scan-window <n>",
8410
+ "Messages to scan per contract (default: 150)",
8411
+ (v) => parseInt(v, 10)
8412
+ ).option(
8413
+ "--min-upvotes <n>",
8414
+ "Floor for two-tier filter (default: 500)",
8415
+ (v) => parseInt(v, 10)
8416
+ ).option(
8417
+ "--min-market-cap <n>",
8418
+ "FDV floor in USDC (default: 40000)",
8419
+ (v) => parseInt(v, 10)
8420
+ ).option(
8421
+ "--recency-hours <n>",
8422
+ "Drop below-floor tokens with no upvote within N hours (default: 48)",
8423
+ (v) => parseInt(v, 10)
8424
+ ).option(
8425
+ "--chain-id <id>",
8426
+ "Chain ID (default: 8453 for Base)",
8427
+ (v) => parseInt(v, 10)
8428
+ ).option("--rpc-url <url>", "Custom RPC URL").option("--json", "Output in JSON format").action(async (options) => {
8429
+ await executeRankings(options);
8430
+ });
8431
+ }
8283
8432
 
8284
8433
  // src/commands/upvote/index.ts
8285
8434
  function registerUpvoteCommand(program2) {
@@ -8288,6 +8437,7 @@ function registerUpvoteCommand(program2) {
8288
8437
  registerGetUpvotesCommand(upvoteCommand);
8289
8438
  registerUpvoteUserCommand(upvoteCommand);
8290
8439
  registerGetUserUpvotesCommand(upvoteCommand);
8440
+ registerRankingsCommand(upvoteCommand);
8291
8441
  }
8292
8442
  var VALID_RUN_MODES = ["auto", "feeds", "chats"];
8293
8443
  function addAuthOptions(cmd) {
@@ -8741,7 +8891,7 @@ async function executeDm(agentAddress, message, options) {
8741
8891
  const usingSessionToken = !!(options.sessionToken || process.env.NET_SESSION_TOKEN);
8742
8892
  if (usingSessionToken && (!options.topic || !options.topicSignature)) {
8743
8893
  exitWithError(
8744
- "When using --session-token, you must also provide --topic and --topic-signature.\n Obtain the signature with:\n netp agent dm-auth-encode --agent-address <addr> \u2192 produces { typedData, topic }\n [sign .typedData with your external signer, e.g. Bankr /agent/sign]\n Then:\n netp agent dm <addr> <message> --session-token <token> --operator <addr> \\\n --topic <topic> --topic-signature <sig>"
8894
+ "When using --session-token, you must also provide --topic and --topic-signature.\n Obtain the signature with:\n netp agent dm-auth-encode --agent-address <addr> \u2192 produces { typedData, topic }\n [sign .typedData with your external signer]\n Then:\n netp agent dm <addr> <message> --session-token <token> --operator <addr> \\\n --topic <topic> --topic-signature <sig>"
8745
8895
  );
8746
8896
  }
8747
8897
  const auth = await resolveAuth(options);