@opensea/cli 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -6,16 +6,21 @@ import { Command as Command10 } from "commander";
6
6
  // src/client.ts
7
7
  var DEFAULT_BASE_URL = "https://api.opensea.io";
8
8
  var DEFAULT_GRAPHQL_URL = "https://gql.opensea.io/graphql";
9
+ var DEFAULT_TIMEOUT_MS = 3e4;
9
10
  var OpenSeaClient = class {
10
11
  apiKey;
11
12
  baseUrl;
12
13
  graphqlUrl;
13
14
  defaultChain;
15
+ timeoutMs;
16
+ verbose;
14
17
  constructor(config) {
15
18
  this.apiKey = config.apiKey;
16
19
  this.baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
17
20
  this.graphqlUrl = config.graphqlUrl ?? DEFAULT_GRAPHQL_URL;
18
21
  this.defaultChain = config.chain ?? "ethereum";
22
+ this.timeoutMs = config.timeout ?? DEFAULT_TIMEOUT_MS;
23
+ this.verbose = config.verbose ?? false;
19
24
  }
20
25
  async get(path, params) {
21
26
  const url = new URL(`${this.baseUrl}${path}`);
@@ -26,35 +31,64 @@ var OpenSeaClient = class {
26
31
  }
27
32
  }
28
33
  }
34
+ if (this.verbose) {
35
+ console.error(`[verbose] GET ${url.toString()}`);
36
+ }
29
37
  const response = await fetch(url.toString(), {
30
38
  method: "GET",
31
39
  headers: {
32
40
  Accept: "application/json",
33
41
  "x-api-key": this.apiKey
34
- }
42
+ },
43
+ signal: AbortSignal.timeout(this.timeoutMs)
35
44
  });
45
+ if (this.verbose) {
46
+ console.error(`[verbose] ${response.status} ${response.statusText}`);
47
+ }
36
48
  if (!response.ok) {
37
49
  const body = await response.text();
38
50
  throw new OpenSeaAPIError(response.status, body, path);
39
51
  }
40
52
  return response.json();
41
53
  }
42
- async post(path) {
54
+ async post(path, body, params) {
43
55
  const url = new URL(`${this.baseUrl}${path}`);
56
+ if (params) {
57
+ for (const [key, value] of Object.entries(params)) {
58
+ if (value !== void 0 && value !== null) {
59
+ url.searchParams.set(key, String(value));
60
+ }
61
+ }
62
+ }
63
+ const headers = {
64
+ Accept: "application/json",
65
+ "x-api-key": this.apiKey
66
+ };
67
+ if (body) {
68
+ headers["Content-Type"] = "application/json";
69
+ }
70
+ if (this.verbose) {
71
+ console.error(`[verbose] POST ${url.toString()}`);
72
+ }
44
73
  const response = await fetch(url.toString(), {
45
74
  method: "POST",
46
- headers: {
47
- Accept: "application/json",
48
- "x-api-key": this.apiKey
49
- }
75
+ headers,
76
+ body: body ? JSON.stringify(body) : void 0,
77
+ signal: AbortSignal.timeout(this.timeoutMs)
50
78
  });
79
+ if (this.verbose) {
80
+ console.error(`[verbose] ${response.status} ${response.statusText}`);
81
+ }
51
82
  if (!response.ok) {
52
- const body = await response.text();
53
- throw new OpenSeaAPIError(response.status, body, path);
83
+ const text = await response.text();
84
+ throw new OpenSeaAPIError(response.status, text, path);
54
85
  }
55
86
  return response.json();
56
87
  }
57
88
  async graphql(query, variables) {
89
+ if (this.verbose) {
90
+ console.error(`[verbose] POST ${this.graphqlUrl}`);
91
+ }
58
92
  const response = await fetch(this.graphqlUrl, {
59
93
  method: "POST",
60
94
  headers: {
@@ -62,8 +96,12 @@ var OpenSeaClient = class {
62
96
  Accept: "application/json",
63
97
  "x-api-key": this.apiKey
64
98
  },
65
- body: JSON.stringify({ query, variables })
99
+ body: JSON.stringify({ query, variables }),
100
+ signal: AbortSignal.timeout(this.timeoutMs)
66
101
  });
102
+ if (this.verbose) {
103
+ console.error(`[verbose] ${response.status} ${response.statusText}`);
104
+ }
67
105
  if (!response.ok) {
68
106
  const body = await response.text();
69
107
  throw new OpenSeaAPIError(response.status, body, "graphql");
@@ -130,6 +168,7 @@ function formatTable(data) {
130
168
  }
131
169
  if (data && typeof data === "object") {
132
170
  const entries = Object.entries(data);
171
+ if (entries.length === 0) return "(empty)";
133
172
  const maxKeyLength = Math.max(...entries.map(([k]) => k.length));
134
173
  return entries.map(([key, value]) => {
135
174
  const displayValue = typeof value === "object" && value !== null ? JSON.stringify(value) : String(value ?? "");
@@ -152,6 +191,24 @@ function accountsCommand(getClient2, getFormat2) {
152
191
 
153
192
  // src/commands/collections.ts
154
193
  import { Command as Command2 } from "commander";
194
+
195
+ // src/parse.ts
196
+ function parseIntOption(value, name) {
197
+ const parsed = Number.parseInt(value, 10);
198
+ if (Number.isNaN(parsed)) {
199
+ throw new Error(`Invalid value for ${name}: "${value}" is not an integer`);
200
+ }
201
+ return parsed;
202
+ }
203
+ function parseFloatOption(value, name) {
204
+ const parsed = Number.parseFloat(value);
205
+ if (Number.isNaN(parsed)) {
206
+ throw new Error(`Invalid value for ${name}: "${value}" is not a number`);
207
+ }
208
+ return parsed;
209
+ }
210
+
211
+ // src/commands/collections.ts
155
212
  function collectionsCommand(getClient2, getFormat2) {
156
213
  const cmd = new Command2("collections").description(
157
214
  "Manage and query NFT collections"
@@ -172,7 +229,7 @@ function collectionsCommand(getClient2, getFormat2) {
172
229
  order_by: options.orderBy,
173
230
  creator_username: options.creator,
174
231
  include_hidden: options.includeHidden,
175
- limit: Number.parseInt(options.limit, 10),
232
+ limit: parseIntOption(options.limit, "--limit"),
176
233
  next: options.next
177
234
  });
178
235
  console.log(formatOutput(result, getFormat2()));
@@ -207,10 +264,10 @@ function eventsCommand(getClient2, getFormat2) {
207
264
  const client = getClient2();
208
265
  const result = await client.get("/api/v2/events", {
209
266
  event_type: options.eventType,
210
- after: options.after ? Number.parseInt(options.after, 10) : void 0,
211
- before: options.before ? Number.parseInt(options.before, 10) : void 0,
267
+ after: options.after ? parseIntOption(options.after, "--after") : void 0,
268
+ before: options.before ? parseIntOption(options.before, "--before") : void 0,
212
269
  chain: options.chain,
213
- limit: Number.parseInt(options.limit, 10),
270
+ limit: parseIntOption(options.limit, "--limit"),
214
271
  next: options.next
215
272
  });
216
273
  console.log(formatOutput(result, getFormat2()));
@@ -222,7 +279,7 @@ function eventsCommand(getClient2, getFormat2) {
222
279
  const result = await client.get(`/api/v2/events/accounts/${address}`, {
223
280
  event_type: options.eventType,
224
281
  chain: options.chain,
225
- limit: Number.parseInt(options.limit, 10),
282
+ limit: parseIntOption(options.limit, "--limit"),
226
283
  next: options.next
227
284
  });
228
285
  console.log(formatOutput(result, getFormat2()));
@@ -233,7 +290,7 @@ function eventsCommand(getClient2, getFormat2) {
233
290
  const client = getClient2();
234
291
  const result = await client.get(`/api/v2/events/collection/${slug}`, {
235
292
  event_type: options.eventType,
236
- limit: Number.parseInt(options.limit, 10),
293
+ limit: parseIntOption(options.limit, "--limit"),
237
294
  next: options.next
238
295
  });
239
296
  console.log(formatOutput(result, getFormat2()));
@@ -246,7 +303,7 @@ function eventsCommand(getClient2, getFormat2) {
246
303
  `/api/v2/events/chain/${chain}/contract/${contract}/nfts/${tokenId}`,
247
304
  {
248
305
  event_type: options.eventType,
249
- limit: Number.parseInt(options.limit, 10),
306
+ limit: parseIntOption(options.limit, "--limit"),
250
307
  next: options.next
251
308
  }
252
309
  );
@@ -264,7 +321,7 @@ function listingsCommand(getClient2, getFormat2) {
264
321
  async (collection, options) => {
265
322
  const client = getClient2();
266
323
  const result = await client.get(`/api/v2/listings/collection/${collection}/all`, {
267
- limit: Number.parseInt(options.limit, 10),
324
+ limit: parseIntOption(options.limit, "--limit"),
268
325
  next: options.next
269
326
  });
270
327
  console.log(formatOutput(result, getFormat2()));
@@ -274,7 +331,7 @@ function listingsCommand(getClient2, getFormat2) {
274
331
  async (collection, options) => {
275
332
  const client = getClient2();
276
333
  const result = await client.get(`/api/v2/listings/collection/${collection}/best`, {
277
- limit: Number.parseInt(options.limit, 10),
334
+ limit: parseIntOption(options.limit, "--limit"),
278
335
  next: options.next
279
336
  });
280
337
  console.log(formatOutput(result, getFormat2()));
@@ -306,7 +363,7 @@ function nftsCommand(getClient2, getFormat2) {
306
363
  const result = await client.get(
307
364
  `/api/v2/collection/${slug}/nfts`,
308
365
  {
309
- limit: Number.parseInt(options.limit, 10),
366
+ limit: parseIntOption(options.limit, "--limit"),
310
367
  next: options.next
311
368
  }
312
369
  );
@@ -318,7 +375,7 @@ function nftsCommand(getClient2, getFormat2) {
318
375
  const result = await client.get(
319
376
  `/api/v2/chain/${chain}/contract/${contract}/nfts`,
320
377
  {
321
- limit: Number.parseInt(options.limit, 10),
378
+ limit: parseIntOption(options.limit, "--limit"),
322
379
  next: options.next
323
380
  }
324
381
  );
@@ -331,7 +388,7 @@ function nftsCommand(getClient2, getFormat2) {
331
388
  const result = await client.get(
332
389
  `/api/v2/chain/${chain}/account/${address}/nfts`,
333
390
  {
334
- limit: Number.parseInt(options.limit, 10),
391
+ limit: parseIntOption(options.limit, "--limit"),
335
392
  next: options.next
336
393
  }
337
394
  );
@@ -368,7 +425,7 @@ function offersCommand(getClient2, getFormat2) {
368
425
  async (collection, options) => {
369
426
  const client = getClient2();
370
427
  const result = await client.get(`/api/v2/offers/collection/${collection}/all`, {
371
- limit: Number.parseInt(options.limit, 10),
428
+ limit: parseIntOption(options.limit, "--limit"),
372
429
  next: options.next
373
430
  });
374
431
  console.log(formatOutput(result, getFormat2()));
@@ -378,7 +435,7 @@ function offersCommand(getClient2, getFormat2) {
378
435
  async (collection, options) => {
379
436
  const client = getClient2();
380
437
  const result = await client.get(`/api/v2/offers/collection/${collection}`, {
381
- limit: Number.parseInt(options.limit, 10),
438
+ limit: parseIntOption(options.limit, "--limit"),
382
439
  next: options.next
383
440
  });
384
441
  console.log(formatOutput(result, getFormat2()));
@@ -397,7 +454,7 @@ function offersCommand(getClient2, getFormat2) {
397
454
  const result = await client.get(`/api/v2/offers/collection/${collection}/traits`, {
398
455
  type: options.type,
399
456
  value: options.value,
400
- limit: Number.parseInt(options.limit, 10),
457
+ limit: parseIntOption(options.limit, "--limit"),
401
458
  next: options.next
402
459
  });
403
460
  console.log(formatOutput(result, getFormat2()));
@@ -512,7 +569,7 @@ function searchCommand(getClient2, getFormat2) {
512
569
  const client = getClient2();
513
570
  const result = await client.graphql(SEARCH_COLLECTIONS_QUERY, {
514
571
  query,
515
- limit: Number.parseInt(options.limit, 10),
572
+ limit: parseIntOption(options.limit, "--limit"),
516
573
  chains: options.chains?.split(",")
517
574
  });
518
575
  console.log(formatOutput(result.collectionsByQuery, getFormat2()));
@@ -524,7 +581,7 @@ function searchCommand(getClient2, getFormat2) {
524
581
  const result = await client.graphql(SEARCH_NFTS_QUERY, {
525
582
  query,
526
583
  collectionSlug: options.collection,
527
- limit: Number.parseInt(options.limit, 10),
584
+ limit: parseIntOption(options.limit, "--limit"),
528
585
  chains: options.chains?.split(",")
529
586
  });
530
587
  console.log(formatOutput(result.itemsByQuery, getFormat2()));
@@ -535,7 +592,7 @@ function searchCommand(getClient2, getFormat2) {
535
592
  const client = getClient2();
536
593
  const result = await client.graphql(SEARCH_TOKENS_QUERY, {
537
594
  query,
538
- limit: Number.parseInt(options.limit, 10),
595
+ limit: parseIntOption(options.limit, "--limit"),
539
596
  chain: options.chain
540
597
  });
541
598
  console.log(formatOutput(result.currenciesByQuery, getFormat2()));
@@ -545,7 +602,7 @@ function searchCommand(getClient2, getFormat2) {
545
602
  const client = getClient2();
546
603
  const result = await client.graphql(SEARCH_ACCOUNTS_QUERY, {
547
604
  query,
548
- limit: Number.parseInt(options.limit, 10)
605
+ limit: parseIntOption(options.limit, "--limit")
549
606
  });
550
607
  console.log(formatOutput(result.accountsByQuery, getFormat2()));
551
608
  });
@@ -584,7 +641,7 @@ function swapsCommand(getClient2, getFormat2) {
584
641
  to_address: options.toAddress,
585
642
  quantity: options.quantity,
586
643
  address: options.address,
587
- slippage: options.slippage ? Number.parseFloat(options.slippage) : void 0,
644
+ slippage: options.slippage ? parseFloatOption(options.slippage, "--slippage") : void 0,
588
645
  recipient: options.recipient
589
646
  }
590
647
  );
@@ -600,29 +657,31 @@ function tokensCommand(getClient2, getFormat2) {
600
657
  const cmd = new Command9("tokens").description(
601
658
  "Query trending tokens, top tokens, and token details"
602
659
  );
603
- cmd.command("trending").description("Get trending tokens based on OpenSea's trending score").option("--chains <chains>", "Comma-separated list of chains to filter by").option("--limit <limit>", "Number of results (max 100)", "20").option("--cursor <cursor>", "Pagination cursor").action(
660
+ cmd.command("trending").description("Get trending tokens based on OpenSea's trending score").option("--chains <chains>", "Comma-separated list of chains to filter by").option("--limit <limit>", "Number of results (max 100)", "20").option("--next <cursor>", "Pagination cursor").action(
604
661
  async (options) => {
605
662
  const client = getClient2();
606
663
  const result = await client.get(
607
664
  "/api/v2/tokens/trending",
608
665
  {
609
666
  chains: options.chains,
610
- limit: Number.parseInt(options.limit, 10),
611
- cursor: options.cursor
667
+ limit: parseIntOption(options.limit, "--limit"),
668
+ // Tokens API uses "cursor" instead of "next" as the query param
669
+ cursor: options.next
612
670
  }
613
671
  );
614
672
  console.log(formatOutput(result, getFormat2()));
615
673
  }
616
674
  );
617
- cmd.command("top").description("Get top tokens ranked by 24-hour trading volume").option("--chains <chains>", "Comma-separated list of chains to filter by").option("--limit <limit>", "Number of results (max 100)", "20").option("--cursor <cursor>", "Pagination cursor").action(
675
+ cmd.command("top").description("Get top tokens ranked by 24-hour trading volume").option("--chains <chains>", "Comma-separated list of chains to filter by").option("--limit <limit>", "Number of results (max 100)", "20").option("--next <cursor>", "Pagination cursor").action(
618
676
  async (options) => {
619
677
  const client = getClient2();
620
678
  const result = await client.get(
621
679
  "/api/v2/tokens/top",
622
680
  {
623
681
  chains: options.chains,
624
- limit: Number.parseInt(options.limit, 10),
625
- cursor: options.cursor
682
+ limit: parseIntOption(options.limit, "--limit"),
683
+ // Tokens API uses "cursor" instead of "next" as the query param
684
+ cursor: options.next
626
685
  }
627
686
  );
628
687
  console.log(formatOutput(result, getFormat2()));
@@ -650,7 +709,7 @@ var BANNER = `
650
709
  |_|
651
710
  `;
652
711
  var program = new Command10();
653
- program.name("opensea").description("OpenSea CLI - Query the OpenSea API from the command line").version(process.env.npm_package_version ?? "0.0.0").addHelpText("before", BANNER).option("--api-key <key>", "OpenSea API key (or set OPENSEA_API_KEY env var)").option("--chain <chain>", "Default chain", "ethereum").option("--format <format>", "Output format (json or table)", "json").option("--base-url <url>", "API base URL");
712
+ program.name("opensea").description("OpenSea CLI - Query the OpenSea API from the command line").version(process.env.npm_package_version ?? "0.0.0").addHelpText("before", BANNER).option("--api-key <key>", "OpenSea API key (or set OPENSEA_API_KEY env var)").option("--chain <chain>", "Default chain", "ethereum").option("--format <format>", "Output format (json or table)", "json").option("--base-url <url>", "API base URL").option("--timeout <ms>", "Request timeout in milliseconds", "30000").option("--verbose", "Log request and response info to stderr");
654
713
  function getClient() {
655
714
  const opts = program.opts();
656
715
  const apiKey = opts.apiKey ?? process.env.OPENSEA_API_KEY;
@@ -663,7 +722,9 @@ function getClient() {
663
722
  return new OpenSeaClient({
664
723
  apiKey,
665
724
  chain: opts.chain,
666
- baseUrl: opts.baseUrl
725
+ baseUrl: opts.baseUrl,
726
+ timeout: parseIntOption(opts.timeout, "--timeout"),
727
+ verbose: opts.verbose
667
728
  });
668
729
  }
669
730
  function getFormat() {
@@ -698,7 +759,18 @@ async function main() {
698
759
  );
699
760
  process.exit(1);
700
761
  }
701
- throw error;
762
+ const label = error instanceof TypeError ? "Network Error" : error.name;
763
+ console.error(
764
+ JSON.stringify(
765
+ {
766
+ error: label,
767
+ message: error.message
768
+ },
769
+ null,
770
+ 2
771
+ )
772
+ );
773
+ process.exit(1);
702
774
  }
703
775
  }
704
776
  main();