Skip to content

Commit f46f92f

Browse files
committed
Release v1.9.0
Origin-SHA: bbe72b6405aad02845d2b39d2706d893c4b165de
1 parent 6b194ed commit f46f92f

7 files changed

Lines changed: 205 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,39 @@
11
# @opensea/cli
22

3+
## 1.9.0
4+
5+
### Minor Changes
6+
7+
- 8fa9fb5: Expose the new `token/{chain}/{address}/holders` and `token/{chain}/{address}/liquidity-pools` endpoints across SDK, CLI, and skill.
8+
9+
## SDK (`@opensea/sdk`)
10+
11+
- `OpenSeaAPI.getTokenHolders(chain, address, args?)``TokenHoldersResponse` — paginated holders (`limit`, `cursor`, `sortBy: "QUANTITY"`, `sortDirection`) plus aggregate distribution health (`STRONG | HEALTHY | CONCERNING | BAD`).
12+
- `OpenSeaAPI.getTokenLiquidityPools(chain, address, args?)``TokenLiquidityPoolsResponse` — pools with pool type, USD reserves, bonding-curve progress, graduation flag.
13+
- New type exports: `TokenHoldersResponse`, `TokenHoldersArgs`, `TokenLiquidityPoolsResponse`, `TokenLiquidityPoolsArgs`.
14+
- New path helpers in `apiPaths.ts`: `getTokenHoldersPath`, `getTokenLiquidityPoolsPath`.
15+
16+
## CLI (`@opensea/cli`)
17+
18+
- `opensea tokens holders <chain> <address> [--limit] [--next] [--sort-by] [--sort-direction]`
19+
- `opensea tokens liquidity-pools <chain> <address> [--limit]`
20+
- SDK class additions: `OpenSeaCLI.tokens.holders(...)`, `OpenSeaCLI.tokens.liquidityPools(...)`.
21+
- New type re-exports: `TokenHoldersResponse`, `TokenLiquidityPoolsResponse`.
22+
23+
## Skill (`@opensea/skill`)
24+
25+
- `tokens/opensea-token-holders.sh <chain> <address> [limit] [cursor] [sort_by] [sort_direction]`
26+
- `tokens/opensea-token-liquidity-pools.sh <chain> <address> [limit]`
27+
- Documentation: added rows to `SKILL.md` (Investigation Scripts) and `references/rest-api.md` (Tokens).
28+
29+
Bumps consume `@opensea/api-types` 0.4.3 (released alongside, see the spec-sync PR for full schema details).
30+
31+
### Patch Changes
32+
33+
- Updated dependencies [96928f4]
34+
- Updated dependencies [90702a7]
35+
- @opensea/api-types@0.4.3
36+
337
## 1.8.0
438

539
### Minor Changes

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@opensea/cli",
3-
"version": "1.8.0",
3+
"version": "1.9.0",
44
"type": "module",
55
"description": "OpenSea CLI - Query the OpenSea API from the command line or programmatically",
66
"main": "dist/index.js",
@@ -23,7 +23,7 @@
2323
"prepublishOnly": "npm run build"
2424
},
2525
"dependencies": {
26-
"@opensea/api-types": "^0.4.2",
26+
"@opensea/api-types": "^0.4.3",
2727
"@opensea/wallet-adapters": "^0.3.0",
2828
"commander": "^14.0.3",
2929
"zod": "^4.3.6"

src/commands/tokens.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,5 +187,62 @@ export function tokensCommand(
187187
},
188188
)
189189

190+
cmd
191+
.command("holders")
192+
.description(
193+
"Get paginated holders for a token (with aggregate distribution health)",
194+
)
195+
.argument("<chain>", "Chain")
196+
.argument("<address>", "Token contract address")
197+
.option("--limit <limit>", "Number of results (max 100)", "20")
198+
.option("--next <cursor>", "Pagination cursor")
199+
.option("--sort-by <field>", "Sort field (QUANTITY)")
200+
.option("--sort-direction <direction>", "Sort direction (asc|desc)")
201+
.action(
202+
async (
203+
chain: string,
204+
address: string,
205+
options: {
206+
limit: string
207+
next?: string
208+
sortBy?: string
209+
sortDirection?: string
210+
},
211+
) => {
212+
const client = getClient()
213+
await outputGet(
214+
client,
215+
getFormat(),
216+
`/api/v2/chain/${chain as Chain}/token/${address}/holders`,
217+
{
218+
limit: parseIntOption(options.limit, "--limit"),
219+
cursor: options.next,
220+
sort_by: options.sortBy,
221+
sort_direction: options.sortDirection,
222+
},
223+
)
224+
},
225+
)
226+
227+
cmd
228+
.command("liquidity-pools")
229+
.description("Get liquidity pools for a token")
230+
.argument("<chain>", "Chain")
231+
.argument("<address>", "Token contract address")
232+
.option("--limit <limit>", "Number of results (max 50)", "20")
233+
.action(
234+
async (chain: string, address: string, options: { limit: string }) => {
235+
const client = getClient()
236+
await outputGet(
237+
client,
238+
getFormat(),
239+
`/api/v2/chain/${chain as Chain}/token/${address}/liquidity-pools`,
240+
{
241+
limit: parseIntOption(options.limit, "--limit"),
242+
},
243+
)
244+
},
245+
)
246+
190247
return cmd
191248
}

src/sdk.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ import type {
5656
TokenBalanceSortBy,
5757
TokenBatchResponse,
5858
TokenDetails,
59+
TokenHoldersResponse,
60+
TokenLiquidityPoolsResponse,
5961
TokenSwapActivityPaginatedResponse,
6062
TraitFilter,
6163
TransactionReceiptRequest,
@@ -883,6 +885,37 @@ class TokensAPI {
883885
cursor: options?.next,
884886
})
885887
}
888+
889+
async holders(
890+
chain: Chain,
891+
address: string,
892+
options?: {
893+
limit?: number
894+
next?: string
895+
sortBy?: "QUANTITY"
896+
sortDirection?: "asc" | "desc"
897+
},
898+
): Promise<TokenHoldersResponse> {
899+
return this.client.get(`/api/v2/chain/${chain}/token/${address}/holders`, {
900+
limit: options?.limit,
901+
cursor: options?.next,
902+
sort_by: options?.sortBy,
903+
sort_direction: options?.sortDirection,
904+
})
905+
}
906+
907+
async liquidityPools(
908+
chain: Chain,
909+
address: string,
910+
options?: { limit?: number },
911+
): Promise<TokenLiquidityPoolsResponse> {
912+
return this.client.get(
913+
`/api/v2/chain/${chain}/token/${address}/liquidity-pools`,
914+
{
915+
limit: options?.limit,
916+
},
917+
)
918+
}
886919
}
887920

888921
class SearchAPI {

src/types/api.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ export type PriceHistoryResponse = Schemas["PriceHistoryResponse"]
9292
export type OhlcvResponse = Schemas["OhlcvResponse"]
9393
export type TokenSwapActivityPaginatedResponse =
9494
Schemas["TokenSwapActivityPaginatedResponse"]
95+
export type TokenHoldersResponse = Schemas["TokenHoldersResponse"]
96+
export type TokenLiquidityPoolsResponse = Schemas["TokenLiquidityPoolsResponse"]
9597
export type OwnersPaginatedResponse = Schemas["OwnersPaginatedResponse"]
9698
export type NftAnalyticsResponse = Schemas["NftAnalyticsResponse"]
9799
export type PortfolioStatsResponse = Schemas["PortfolioStatsResponse"]

test/commands/tokens.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ describe("tokensCommand", () => {
2020
expect(subcommands).toContain("trending")
2121
expect(subcommands).toContain("top")
2222
expect(subcommands).toContain("get")
23+
expect(subcommands).toContain("holders")
24+
expect(subcommands).toContain("liquidity-pools")
2325
})
2426

2527
it("trending subcommand passes options", async () => {
@@ -73,4 +75,51 @@ describe("tokensCommand", () => {
7375
"/api/v2/chain/ethereum/token/0xabc",
7476
)
7577
})
78+
79+
it("holders subcommand passes pagination + sort options", async () => {
80+
ctx.mockClient.get.mockResolvedValue({ holders: [] })
81+
82+
const cmd = tokensCommand(ctx.getClient, ctx.getFormat)
83+
await cmd.parseAsync(
84+
[
85+
"holders",
86+
"ethereum",
87+
"0xabc",
88+
"--limit",
89+
"50",
90+
"--next",
91+
"page-2",
92+
"--sort-by",
93+
"QUANTITY",
94+
"--sort-direction",
95+
"desc",
96+
],
97+
{ from: "user" },
98+
)
99+
100+
expect(ctx.mockClient.get).toHaveBeenCalledWith(
101+
"/api/v2/chain/ethereum/token/0xabc/holders",
102+
expect.objectContaining({
103+
limit: 50,
104+
cursor: "page-2",
105+
sort_by: "QUANTITY",
106+
sort_direction: "desc",
107+
}),
108+
)
109+
})
110+
111+
it("liquidity-pools subcommand passes limit", async () => {
112+
ctx.mockClient.get.mockResolvedValue({ pools: [] })
113+
114+
const cmd = tokensCommand(ctx.getClient, ctx.getFormat)
115+
await cmd.parseAsync(
116+
["liquidity-pools", "ethereum", "0xabc", "--limit", "30"],
117+
{ from: "user" },
118+
)
119+
120+
expect(ctx.mockClient.get).toHaveBeenCalledWith(
121+
"/api/v2/chain/ethereum/token/0xabc/liquidity-pools",
122+
expect.objectContaining({ limit: 30 }),
123+
)
124+
})
76125
})

test/sdk.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,34 @@ describe("OpenSeaCLI", () => {
436436
await sdk.tokens.get("ethereum", "0xabc")
437437
expect(mockGet).toHaveBeenCalledWith("/api/v2/chain/ethereum/token/0xabc")
438438
})
439+
440+
it("holders calls correct endpoint with pagination + sort", async () => {
441+
mockGet.mockResolvedValue({ holders: [] })
442+
await sdk.tokens.holders("ethereum", "0xabc", {
443+
limit: 50,
444+
next: "page-2",
445+
sortBy: "QUANTITY",
446+
sortDirection: "desc",
447+
})
448+
expect(mockGet).toHaveBeenCalledWith(
449+
"/api/v2/chain/ethereum/token/0xabc/holders",
450+
{
451+
limit: 50,
452+
cursor: "page-2",
453+
sort_by: "QUANTITY",
454+
sort_direction: "desc",
455+
},
456+
)
457+
})
458+
459+
it("liquidityPools calls correct endpoint", async () => {
460+
mockGet.mockResolvedValue({ pools: [] })
461+
await sdk.tokens.liquidityPools("ethereum", "0xabc", { limit: 30 })
462+
expect(mockGet).toHaveBeenCalledWith(
463+
"/api/v2/chain/ethereum/token/0xabc/liquidity-pools",
464+
{ limit: 30 },
465+
)
466+
})
439467
})
440468

441469
describe("search", () => {

0 commit comments

Comments
 (0)