Skip to content

Commit 51b98df

Browse files
feat: add trending tokens and swaps REST API endpoint support
Co-Authored-By: unknown <>
1 parent 6613da4 commit 51b98df

6 files changed

Lines changed: 279 additions & 0 deletions

File tree

src/cli.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
listingsCommand,
88
nftsCommand,
99
offersCommand,
10+
swapsCommand,
11+
tokensCommand,
1012
} from "./commands/index.js"
1113

1214
const BANNER = `
@@ -65,6 +67,8 @@ program.addCommand(listingsCommand(getClient, getFormat))
6567
program.addCommand(offersCommand(getClient, getFormat))
6668
program.addCommand(eventsCommand(getClient, getFormat))
6769
program.addCommand(accountsCommand(getClient, getFormat))
70+
program.addCommand(tokensCommand(getClient, getFormat))
71+
program.addCommand(swapsCommand(getClient, getFormat))
6872

6973
program.hook("postAction", () => {})
7074

src/commands/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ export { eventsCommand } from "./events.js"
44
export { listingsCommand } from "./listings.js"
55
export { nftsCommand } from "./nfts.js"
66
export { offersCommand } from "./offers.js"
7+
export { swapsCommand } from "./swaps.js"
8+
export { tokensCommand } from "./tokens.js"

src/commands/swaps.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { Command } from "commander"
2+
import type { OpenSeaClient } from "../client.js"
3+
import { formatOutput } from "../output.js"
4+
import type { SwapQuoteResponse } from "../types/index.js"
5+
6+
export function swapsCommand(
7+
getClient: () => OpenSeaClient,
8+
getFormat: () => "json" | "table",
9+
): Command {
10+
const cmd = new Command("swaps").description(
11+
"Get swap quotes for token trading",
12+
)
13+
14+
cmd
15+
.command("quote")
16+
.description(
17+
"Get a quote for swapping tokens, including price details and executable transaction data",
18+
)
19+
.requiredOption("--from-chain <chain>", "Chain of the token to swap from")
20+
.requiredOption(
21+
"--from-address <address>",
22+
"Contract address of the token to swap from",
23+
)
24+
.requiredOption("--to-chain <chain>", "Chain of the token to swap to")
25+
.requiredOption(
26+
"--to-address <address>",
27+
"Contract address of the token to swap to",
28+
)
29+
.requiredOption("--quantity <quantity>", "Amount to swap (in token units)")
30+
.requiredOption("--address <address>", "Wallet address executing the swap")
31+
.option(
32+
"--slippage <slippage>",
33+
"Slippage tolerance (0.0 to 0.5, default: 0.01)",
34+
)
35+
.option(
36+
"--recipient <recipient>",
37+
"Recipient address (defaults to sender address)",
38+
)
39+
.action(
40+
async (options: {
41+
fromChain: string
42+
fromAddress: string
43+
toChain: string
44+
toAddress: string
45+
quantity: string
46+
address: string
47+
slippage?: string
48+
recipient?: string
49+
}) => {
50+
const client = getClient()
51+
const result = await client.get<SwapQuoteResponse>(
52+
"/api/v2/swap/quote",
53+
{
54+
from_chain: options.fromChain,
55+
from_address: options.fromAddress,
56+
to_chain: options.toChain,
57+
to_address: options.toAddress,
58+
quantity: options.quantity,
59+
address: options.address,
60+
slippage: options.slippage
61+
? Number.parseFloat(options.slippage)
62+
: undefined,
63+
recipient: options.recipient,
64+
},
65+
)
66+
console.log(formatOutput(result, getFormat()))
67+
},
68+
)
69+
70+
return cmd
71+
}

src/commands/tokens.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { Command } from "commander"
2+
import type { OpenSeaClient } from "../client.js"
3+
import { formatOutput } from "../output.js"
4+
import type { Chain, Token, TokenDetails } from "../types/index.js"
5+
6+
export function tokensCommand(
7+
getClient: () => OpenSeaClient,
8+
getFormat: () => "json" | "table",
9+
): Command {
10+
const cmd = new Command("tokens").description(
11+
"Query trending tokens, top tokens, and token details",
12+
)
13+
14+
cmd
15+
.command("trending")
16+
.description("Get trending tokens based on OpenSea's trending score")
17+
.option("--chains <chains>", "Comma-separated list of chains to filter by")
18+
.option("--limit <limit>", "Number of results (max 100)", "20")
19+
.option("--cursor <cursor>", "Pagination cursor")
20+
.action(
21+
async (options: { chains?: string; limit: string; cursor?: string }) => {
22+
const client = getClient()
23+
const result = await client.get<{ tokens: Token[]; next?: string }>(
24+
"/api/v2/tokens/trending",
25+
{
26+
chains: options.chains,
27+
limit: Number.parseInt(options.limit, 10),
28+
cursor: options.cursor,
29+
},
30+
)
31+
console.log(formatOutput(result, getFormat()))
32+
},
33+
)
34+
35+
cmd
36+
.command("top")
37+
.description("Get top tokens ranked by 24-hour trading volume")
38+
.option("--chains <chains>", "Comma-separated list of chains to filter by")
39+
.option("--limit <limit>", "Number of results (max 100)", "20")
40+
.option("--cursor <cursor>", "Pagination cursor")
41+
.action(
42+
async (options: { chains?: string; limit: string; cursor?: string }) => {
43+
const client = getClient()
44+
const result = await client.get<{ tokens: Token[]; next?: string }>(
45+
"/api/v2/tokens/top",
46+
{
47+
chains: options.chains,
48+
limit: Number.parseInt(options.limit, 10),
49+
cursor: options.cursor,
50+
},
51+
)
52+
console.log(formatOutput(result, getFormat()))
53+
},
54+
)
55+
56+
cmd
57+
.command("get")
58+
.description("Get detailed information about a specific token")
59+
.argument("<chain>", "Blockchain chain")
60+
.argument("<address>", "Token contract address")
61+
.action(async (chain: string, address: string) => {
62+
const client = getClient()
63+
const result = await client.get<TokenDetails>(
64+
`/api/v2/chain/${chain as Chain}/token/${address}`,
65+
)
66+
console.log(formatOutput(result, getFormat()))
67+
})
68+
69+
return cmd
70+
}

src/sdk.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import type {
1313
NFT,
1414
Offer,
1515
OpenSeaClientConfig,
16+
SwapQuoteResponse,
17+
Token,
18+
TokenDetails,
1619
} from "./types/index.js"
1720

1821
export class OpenSeaCLI {
@@ -24,6 +27,8 @@ export class OpenSeaCLI {
2427
readonly offers: OffersAPI
2528
readonly events: EventsAPI
2629
readonly accounts: AccountsAPI
30+
readonly tokens: TokensAPI
31+
readonly swaps: SwapsAPI
2732

2833
constructor(config: OpenSeaClientConfig) {
2934
this.client = new OpenSeaClient(config)
@@ -33,6 +38,8 @@ export class OpenSeaCLI {
3338
this.offers = new OffersAPI(this.client)
3439
this.events = new EventsAPI(this.client)
3540
this.accounts = new AccountsAPI(this.client)
41+
this.tokens = new TokensAPI(this.client)
42+
this.swaps = new SwapsAPI(this.client)
3643
}
3744
}
3845

@@ -291,3 +298,61 @@ class AccountsAPI {
291298
return this.client.get(`/api/v2/accounts/${address}`)
292299
}
293300
}
301+
302+
class TokensAPI {
303+
constructor(private client: OpenSeaClient) {}
304+
305+
async trending(options?: {
306+
limit?: number
307+
chains?: string[]
308+
cursor?: string
309+
}): Promise<{ tokens: Token[]; next?: string }> {
310+
return this.client.get("/api/v2/tokens/trending", {
311+
limit: options?.limit,
312+
chains: options?.chains?.join(","),
313+
cursor: options?.cursor,
314+
})
315+
}
316+
317+
async top(options?: {
318+
limit?: number
319+
chains?: string[]
320+
cursor?: string
321+
}): Promise<{ tokens: Token[]; next?: string }> {
322+
return this.client.get("/api/v2/tokens/top", {
323+
limit: options?.limit,
324+
chains: options?.chains?.join(","),
325+
cursor: options?.cursor,
326+
})
327+
}
328+
329+
async get(chain: Chain, address: string): Promise<TokenDetails> {
330+
return this.client.get(`/api/v2/chain/${chain}/token/${address}`)
331+
}
332+
}
333+
334+
class SwapsAPI {
335+
constructor(private client: OpenSeaClient) {}
336+
337+
async quote(options: {
338+
fromChain: string
339+
fromAddress: string
340+
toChain: string
341+
toAddress: string
342+
quantity: string
343+
address: string
344+
slippage?: number
345+
recipient?: string
346+
}): Promise<SwapQuoteResponse> {
347+
return this.client.get("/api/v2/swap/quote", {
348+
from_chain: options.fromChain,
349+
from_address: options.fromAddress,
350+
to_chain: options.toChain,
351+
to_address: options.toAddress,
352+
quantity: options.quantity,
353+
address: options.address,
354+
slippage: options.slippage,
355+
recipient: options.recipient,
356+
})
357+
}
358+
}

src/types/api.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,3 +254,70 @@ export interface PaginatedResponse<T> {
254254
next?: string
255255
results: T[]
256256
}
257+
258+
export interface Token {
259+
address: string
260+
chain: string
261+
name: string
262+
symbol: string
263+
image_url?: string
264+
usd_price: string
265+
decimals: number
266+
market_cap_usd?: number
267+
volume_24h?: number
268+
price_change_24h?: number
269+
opensea_url: string
270+
}
271+
272+
export interface TokenDetails {
273+
address: string
274+
chain: string
275+
name: string
276+
symbol: string
277+
image_url?: string
278+
description?: string
279+
usd_price: string
280+
decimals: number
281+
stats?: TokenStats
282+
socials?: TokenSocials
283+
opensea_url: string
284+
}
285+
286+
export interface TokenStats {
287+
market_cap_usd?: number
288+
fdv_usd?: number
289+
circulating_supply?: number
290+
max_supply?: number
291+
total_supply?: number
292+
volume_24h?: number
293+
price_change_1h?: number
294+
price_change_24h?: number
295+
price_change_7d?: number
296+
price_change_30d?: number
297+
}
298+
299+
export interface TokenSocials {
300+
website?: string
301+
twitter_handle?: string
302+
telegram_identifier?: string
303+
}
304+
305+
export interface SwapQuote {
306+
total_price_usd: number
307+
total_cost_usd: number
308+
slippage_tolerance: number
309+
estimated_duration_ms: number
310+
marketplace_fee_bps: number
311+
}
312+
313+
export interface SwapTransaction {
314+
chain: string
315+
to?: string
316+
data: string
317+
value?: string
318+
}
319+
320+
export interface SwapQuoteResponse {
321+
quote: SwapQuote
322+
transactions: SwapTransaction[]
323+
}

0 commit comments

Comments
 (0)