Skip to content

Commit 451356f

Browse files
committed
Release v1.0.0
1 parent 10c90b6 commit 451356f

29 files changed

Lines changed: 2639 additions & 18 deletions

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# @opensea/cli
2+
3+
## 1.0.0
4+
5+
### Minor Changes
6+
7+
- b3a5e84: Add drops endpoints, trending/top collections, and account resolve
8+
9+
- api-types: Sync OpenAPI spec with 6 new endpoints and 8 new schemas (drops, trending/top collections, account resolve)
10+
- SDK: New DropsAPI class, extended CollectionsAPI and AccountsAPI with new methods
11+
- CLI: New `drops` command, `collections trending/top` subcommands, `accounts resolve` subcommand
12+
13+
### Patch Changes
14+
15+
- f82c035: Replace hardcoded chain ID maps with codegen from OpenSea REST API
16+
17+
- SDK: Fix Blast chain ID from 238 (testnet) to 81457 (mainnet)
18+
- CLI: Add chains previously only in SDK (b3, flow, ronin, etc.)
19+
- CLI: Remove `bsc`, `sepolia`, `base_sepolia`, `monad_testnet` from `CHAIN_IDS` — these are not in the OpenSea API
20+
- Add `pnpm sync-chains` codegen script (fetches GET /api/v2/chains as source of truth)
21+
22+
- Updated dependencies [b3a5e84]
23+
- @opensea/api-types@0.2.0

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@opensea/cli",
3-
"version": "0.4.2",
3+
"version": "1.0.0",
44
"type": "module",
55
"description": "OpenSea CLI - Query the OpenSea API from the command line or programmatically",
66
"main": "dist/index.js",
@@ -28,8 +28,7 @@
2828
"zod": "^4.3.6"
2929
},
3030
"devDependencies": {
31-
"@biomejs/biome": "^2.2.4",
32-
"@types/node": "^20.0.0",
31+
"@types/node": "^22.0.0",
3332
"@vitest/coverage-v8": "^3.2.4",
3433
"tsup": "^8.3.0",
3534
"typescript": "^6.0.2",

src/cli.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
accountsCommand,
55
chainsCommand,
66
collectionsCommand,
7+
dropsCommand,
78
eventsCommand,
89
healthCommand,
910
listingsCommand,
@@ -113,6 +114,7 @@ program.hook("preAction", () => {
113114

114115
program.addCommand(chainsCommand(getClient, getFormat))
115116
program.addCommand(collectionsCommand(getClient, getFormat))
117+
program.addCommand(dropsCommand(getClient, getFormat))
116118
program.addCommand(nftsCommand(getClient, getFormat))
117119
program.addCommand(listingsCommand(getClient, getFormat))
118120
program.addCommand(offersCommand(getClient, getFormat))

src/commands/accounts.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { formatOutput } from "../output.js"
55
import { parseIntOption } from "../parse.js"
66
import type {
77
Account,
8+
AccountResolveResponse,
89
TokenBalancePaginatedResponse,
910
TokenBalanceSortBy,
1011
} from "../types/index.js"
@@ -66,5 +67,22 @@ export function accountsCommand(
6667
},
6768
)
6869

70+
cmd
71+
.command("resolve")
72+
.description(
73+
"Resolve an ENS name, OpenSea username, or wallet address to canonical account info",
74+
)
75+
.argument(
76+
"<identifier>",
77+
"ENS name (e.g. vitalik.eth), OpenSea username, or wallet address",
78+
)
79+
.action(async (identifier: string) => {
80+
const client = getClient()
81+
const result = await client.get<AccountResolveResponse>(
82+
`/api/v2/accounts/resolve/${identifier}`,
83+
)
84+
console.log(formatOutput(result, getFormat()))
85+
})
86+
6987
return cmd
7088
}

src/commands/collections.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77
Chain,
88
Collection,
99
CollectionOrderBy,
10+
CollectionPaginatedResponse,
1011
CollectionStats,
1112
GetTraitsResponse,
1213
} from "../types/index.js"
@@ -90,5 +91,81 @@ export function collectionsCommand(
9091
console.log(formatOutput(result, getFormat()))
9192
})
9293

94+
cmd
95+
.command("trending")
96+
.description("Get trending collections by sales activity")
97+
.option(
98+
"--timeframe <timeframe>",
99+
"Time window (one_minute, five_minutes, fifteen_minutes, one_hour, one_day, seven_days, thirty_days, one_year, all_time)",
100+
"one_day",
101+
)
102+
.option("--chains <chains>", "Comma-separated list of chains to filter by")
103+
.option(
104+
"--category <category>",
105+
"Category (art, gaming, memberships, music, pfps, photography, domain-names, virtual-worlds, sports-collectibles)",
106+
)
107+
.option("--limit <limit>", "Number of results (max 100)", "20")
108+
.option("--next <cursor>", "Pagination cursor")
109+
.action(
110+
async (options: {
111+
timeframe: string
112+
chains?: string
113+
category?: string
114+
limit: string
115+
next?: string
116+
}) => {
117+
const client = getClient()
118+
const result = await client.get<CollectionPaginatedResponse>(
119+
"/api/v2/collections/trending",
120+
{
121+
timeframe: options.timeframe,
122+
chains: options.chains,
123+
category: options.category,
124+
limit: parseIntOption(options.limit, "--limit"),
125+
cursor: options.next,
126+
},
127+
)
128+
console.log(formatOutput(result, getFormat()))
129+
},
130+
)
131+
132+
cmd
133+
.command("top")
134+
.description("Get top collections ranked by volume, sales, or floor price")
135+
.option(
136+
"--sort-by <field>",
137+
"Sort by (one_day_volume, seven_days_volume, thirty_days_volume, floor_price, one_day_sales, seven_days_sales, thirty_days_sales, total_volume, total_sales)",
138+
"one_day_volume",
139+
)
140+
.option("--chains <chains>", "Comma-separated list of chains to filter by")
141+
.option(
142+
"--category <category>",
143+
"Category (art, gaming, memberships, music, pfps, photography, domain-names, virtual-worlds, sports-collectibles)",
144+
)
145+
.option("--limit <limit>", "Number of results (max 100)", "50")
146+
.option("--next <cursor>", "Pagination cursor")
147+
.action(
148+
async (options: {
149+
sortBy: string
150+
chains?: string
151+
category?: string
152+
limit: string
153+
next?: string
154+
}) => {
155+
const client = getClient()
156+
const result = await client.get<CollectionPaginatedResponse>(
157+
"/api/v2/collections/top",
158+
{
159+
sort_by: options.sortBy,
160+
chains: options.chains,
161+
category: options.category,
162+
limit: parseIntOption(options.limit, "--limit"),
163+
cursor: options.next,
164+
},
165+
)
166+
console.log(formatOutput(result, getFormat()))
167+
},
168+
)
169+
93170
return cmd
94171
}

src/commands/drops.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { Command } from "commander"
2+
import type { OpenSeaClient } from "../client.js"
3+
import type { OutputFormat } from "../output.js"
4+
import { formatOutput } from "../output.js"
5+
import { parseIntOption } from "../parse.js"
6+
import type {
7+
DropDetailedResponse,
8+
DropMintResponse,
9+
DropPaginatedResponse,
10+
} from "../types/index.js"
11+
12+
export function dropsCommand(
13+
getClient: () => OpenSeaClient,
14+
getFormat: () => OutputFormat,
15+
): Command {
16+
const cmd = new Command("drops").description("Query and mint NFT drops")
17+
18+
cmd
19+
.command("list")
20+
.description("List drops (featured, upcoming, or recently minted)")
21+
.option(
22+
"--type <type>",
23+
"Drop type: featured, upcoming, or recently_minted",
24+
"featured",
25+
)
26+
.option("--chains <chains>", "Comma-separated list of chains to filter by")
27+
.option("--limit <limit>", "Number of results (max 100)", "20")
28+
.option("--next <cursor>", "Pagination cursor")
29+
.action(
30+
async (options: {
31+
type: string
32+
chains?: string
33+
limit: string
34+
next?: string
35+
}) => {
36+
const client = getClient()
37+
const result = await client.get<DropPaginatedResponse>(
38+
"/api/v2/drops",
39+
{
40+
type: options.type,
41+
chains: options.chains,
42+
limit: parseIntOption(options.limit, "--limit"),
43+
cursor: options.next,
44+
},
45+
)
46+
console.log(formatOutput(result, getFormat()))
47+
},
48+
)
49+
50+
cmd
51+
.command("get")
52+
.description("Get detailed drop info by collection slug")
53+
.argument("<slug>", "Collection slug")
54+
.action(async (slug: string) => {
55+
const client = getClient()
56+
const result = await client.get<DropDetailedResponse>(
57+
`/api/v2/drops/${slug}`,
58+
)
59+
console.log(formatOutput(result, getFormat()))
60+
})
61+
62+
cmd
63+
.command("mint")
64+
.description("Build a mint transaction for a drop")
65+
.argument("<slug>", "Collection slug")
66+
.requiredOption("--minter <address>", "Wallet address to receive tokens")
67+
.option("--quantity <n>", "Number of tokens to mint", "1")
68+
.action(
69+
async (
70+
slug: string,
71+
options: {
72+
minter: string
73+
quantity: string
74+
},
75+
) => {
76+
const client = getClient()
77+
const result = await client.post<DropMintResponse>(
78+
`/api/v2/drops/${slug}/mint`,
79+
{
80+
minter: options.minter,
81+
quantity: parseIntOption(options.quantity, "--quantity"),
82+
},
83+
)
84+
console.log(formatOutput(result, getFormat()))
85+
},
86+
)
87+
88+
return cmd
89+
}

src/commands/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export { accountsCommand } from "./accounts.js"
22
export { chainsCommand } from "./chains.js"
33
export { collectionsCommand } from "./collections.js"
4+
export { dropsCommand } from "./drops.js"
45
export { eventsCommand } from "./events.js"
56
export { healthCommand } from "./health.js"
67
export { listingsCommand } from "./listings.js"

src/commands/swaps.ts

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ import type { OpenSeaClient } from "../client.js"
33
import type { OutputFormat } from "../output.js"
44
import { formatOutput } from "../output.js"
55
import { parseFloatOption } from "../parse.js"
6+
import { SwapsAPI } from "../sdk.js"
67
import type { SwapQuoteResponse } from "../types/index.js"
8+
import type { WalletProvider } from "../wallet/index.js"
9+
import { createWalletFromEnv, WALLET_PROVIDERS } from "../wallet/index.js"
710

811
export function swapsCommand(
912
getClient: () => OpenSeaClient,
1013
getFormat: () => OutputFormat,
1114
): Command {
1215
const cmd = new Command("swaps").description(
13-
"Get swap quotes for token trading",
16+
"Get swap quotes and execute token swaps",
1417
)
1518

1619
cmd
@@ -69,5 +72,98 @@ export function swapsCommand(
6972
},
7073
)
7174

75+
cmd
76+
.command("execute")
77+
.description(
78+
"Get a swap quote and execute it onchain using a managed wallet. " +
79+
"Supports Privy (default), Turnkey, and Fireblocks providers.",
80+
)
81+
.requiredOption("--from-chain <chain>", "Chain of the token to swap from")
82+
.requiredOption(
83+
"--from-address <address>",
84+
"Contract address of the token to swap from",
85+
)
86+
.requiredOption("--to-chain <chain>", "Chain of the token to swap to")
87+
.requiredOption(
88+
"--to-address <address>",
89+
"Contract address of the token to swap to",
90+
)
91+
.requiredOption("--quantity <quantity>", "Amount to swap (in token units)")
92+
.option(
93+
"--slippage <slippage>",
94+
"Slippage tolerance (0.0 to 0.5, default: 0.01)",
95+
)
96+
.option(
97+
"--recipient <recipient>",
98+
"Recipient address (defaults to wallet address)",
99+
)
100+
.option(
101+
"--wallet-provider <provider>",
102+
`Wallet provider to use (${WALLET_PROVIDERS.join(", ")})`,
103+
)
104+
.option("--dry-run", "Print quote and transaction details without signing")
105+
.action(
106+
async (options: {
107+
fromChain: string
108+
fromAddress: string
109+
toChain: string
110+
toAddress: string
111+
quantity: string
112+
slippage?: string
113+
recipient?: string
114+
walletProvider?: string
115+
dryRun?: boolean
116+
}) => {
117+
const wallet = createWalletFromEnv(
118+
options.walletProvider as WalletProvider | undefined,
119+
)
120+
const address = await wallet.getAddress()
121+
console.error(`Using ${wallet.name} wallet: ${address}`)
122+
123+
const swaps = new SwapsAPI(getClient())
124+
const format = getFormat()
125+
const slippage = options.slippage
126+
? parseFloatOption(options.slippage, "--slippage")
127+
: undefined
128+
129+
if (options.dryRun) {
130+
const quote = await swaps.quote({
131+
...options,
132+
address,
133+
slippage,
134+
})
135+
console.log(formatOutput(quote, format))
136+
return
137+
}
138+
139+
const results = await swaps.execute(
140+
{
141+
...options,
142+
address,
143+
slippage,
144+
},
145+
wallet,
146+
{
147+
onQuote: () =>
148+
console.error(
149+
`Quote: ${options.quantity} on ${options.fromChain}${options.toChain}`,
150+
),
151+
onSending: tx =>
152+
console.error(
153+
`Sending transaction to ${tx.to} on chain ${tx.chain} (${tx.chainId})...`,
154+
),
155+
onSkipped: tx =>
156+
console.error(
157+
`Skipping transaction on ${tx.chain}: ${tx.reason}`,
158+
),
159+
},
160+
)
161+
162+
for (const result of results) {
163+
console.log(formatOutput({ hash: result.hash }, format))
164+
}
165+
},
166+
)
167+
72168
return cmd
73169
}

0 commit comments

Comments
 (0)