Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
swapsCommand,
tokensCommand,
} from "./commands/index.js"
import { parseIntOption } from "./parse.js"

const BANNER = `
____ _____
Expand All @@ -34,12 +35,16 @@ program
.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")

function getClient(): OpenSeaClient {
const opts = program.opts<{
apiKey?: string
chain: string
baseUrl?: string
timeout: string
verbose?: boolean
}>()

const apiKey = opts.apiKey ?? process.env.OPENSEA_API_KEY
Expand All @@ -54,6 +59,8 @@ function getClient(): OpenSeaClient {
apiKey,
chain: opts.chain,
baseUrl: opts.baseUrl,
timeout: parseIntOption(opts.timeout, "--timeout"),
verbose: opts.verbose,
})
}

Expand Down Expand Up @@ -91,7 +98,19 @@ async function main() {
)
process.exit(1)
}
throw error
const label =
error instanceof TypeError ? "Network Error" : (error as Error).name
console.error(
JSON.stringify(
{
error: label,
message: (error as Error).message,
},
null,
2,
),
)
process.exit(1)
}
}

Expand Down
65 changes: 58 additions & 7 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@ import type { OpenSeaClientConfig } from "./types/index.js"

const DEFAULT_BASE_URL = "https://api.opensea.io"
const DEFAULT_GRAPHQL_URL = "https://gql.opensea.io/graphql"
const DEFAULT_TIMEOUT_MS = 30_000

export class OpenSeaClient {
private apiKey: string
private baseUrl: string
private graphqlUrl: string
private defaultChain: string
private timeoutMs: number
private verbose: boolean

constructor(config: OpenSeaClientConfig) {
this.apiKey = config.apiKey
this.baseUrl = config.baseUrl ?? DEFAULT_BASE_URL
this.graphqlUrl = config.graphqlUrl ?? DEFAULT_GRAPHQL_URL
this.defaultChain = config.chain ?? "ethereum"
this.timeoutMs = config.timeout ?? DEFAULT_TIMEOUT_MS
this.verbose = config.verbose ?? false
}

async get<T>(path: string, params?: Record<string, unknown>): Promise<T> {
Expand All @@ -27,14 +32,23 @@ export class OpenSeaClient {
}
}

if (this.verbose) {
console.error(`[verbose] GET ${url.toString()}`)
}

const response = await fetch(url.toString(), {
method: "GET",
headers: {
Accept: "application/json",
"x-api-key": this.apiKey,
},
signal: AbortSignal.timeout(this.timeoutMs),
})

if (this.verbose) {
console.error(`[verbose] ${response.status} ${response.statusText}`)
}

if (!response.ok) {
const body = await response.text()
throw new OpenSeaAPIError(response.status, body, path)
Expand All @@ -43,20 +57,48 @@ export class OpenSeaClient {
return response.json() as Promise<T>
}

async post<T>(path: string): Promise<T> {
async post<T>(
path: string,
body?: Record<string, unknown>,
params?: Record<string, unknown>,
): Promise<T> {
const url = new URL(`${this.baseUrl}${path}`)

if (params) {
for (const [key, value] of Object.entries(params)) {
if (value !== undefined && value !== null) {
url.searchParams.set(key, String(value))
}
}
}

const headers: Record<string, string> = {
Accept: "application/json",
"x-api-key": this.apiKey,
}

if (body) {
headers["Content-Type"] = "application/json"
}

if (this.verbose) {
console.error(`[verbose] POST ${url.toString()}`)
}

const response = await fetch(url.toString(), {
method: "POST",
headers: {
Accept: "application/json",
"x-api-key": this.apiKey,
},
headers,
body: body ? JSON.stringify(body) : undefined,
signal: AbortSignal.timeout(this.timeoutMs),
})

if (this.verbose) {
console.error(`[verbose] ${response.status} ${response.statusText}`)
}

if (!response.ok) {
const body = await response.text()
throw new OpenSeaAPIError(response.status, body, path)
const text = await response.text()
throw new OpenSeaAPIError(response.status, text, path)
}

return response.json() as Promise<T>
Expand All @@ -66,6 +108,10 @@ export class OpenSeaClient {
query: string,
variables?: Record<string, unknown>,
): Promise<T> {
if (this.verbose) {
console.error(`[verbose] POST ${this.graphqlUrl}`)
}

const response = await fetch(this.graphqlUrl, {
method: "POST",
headers: {
Expand All @@ -74,8 +120,13 @@ export class OpenSeaClient {
"x-api-key": this.apiKey,
},
body: JSON.stringify({ query, variables }),
signal: AbortSignal.timeout(this.timeoutMs),
})

if (this.verbose) {
console.error(`[verbose] ${response.status} ${response.statusText}`)
}

if (!response.ok) {
const body = await response.text()
throw new OpenSeaAPIError(response.status, body, "graphql")
Expand Down
3 changes: 2 additions & 1 deletion src/commands/collections.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Command } from "commander"
import type { OpenSeaClient } from "../client.js"
import { formatOutput } from "../output.js"
import { parseIntOption } from "../parse.js"
import type {
Chain,
Collection,
Expand Down Expand Up @@ -57,7 +58,7 @@ export function collectionsCommand(
order_by: options.orderBy as CollectionOrderBy | undefined,
creator_username: options.creator,
include_hidden: options.includeHidden,
limit: Number.parseInt(options.limit, 10),
limit: parseIntOption(options.limit, "--limit"),
next: options.next,
})
console.log(formatOutput(result, getFormat()))
Expand Down
15 changes: 9 additions & 6 deletions src/commands/events.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Command } from "commander"
import type { OpenSeaClient } from "../client.js"
import { formatOutput } from "../output.js"
import { parseIntOption } from "../parse.js"
import type { AssetEvent } from "../types/index.js"

export function eventsCommand(
Expand Down Expand Up @@ -36,12 +37,14 @@ export function eventsCommand(
next?: string
}>("/api/v2/events", {
event_type: options.eventType,
after: options.after ? Number.parseInt(options.after, 10) : undefined,
after: options.after
? parseIntOption(options.after, "--after")
: undefined,
before: options.before
? Number.parseInt(options.before, 10)
? parseIntOption(options.before, "--before")
: undefined,
chain: options.chain,
limit: Number.parseInt(options.limit, 10),
limit: parseIntOption(options.limit, "--limit"),
next: options.next,
})
console.log(formatOutput(result, getFormat()))
Expand Down Expand Up @@ -73,7 +76,7 @@ export function eventsCommand(
}>(`/api/v2/events/accounts/${address}`, {
event_type: options.eventType,
chain: options.chain,
limit: Number.parseInt(options.limit, 10),
limit: parseIntOption(options.limit, "--limit"),
next: options.next,
})
console.log(formatOutput(result, getFormat()))
Expand Down Expand Up @@ -102,7 +105,7 @@ export function eventsCommand(
next?: string
}>(`/api/v2/events/collection/${slug}`, {
event_type: options.eventType,
limit: Number.parseInt(options.limit, 10),
limit: parseIntOption(options.limit, "--limit"),
next: options.next,
})
console.log(formatOutput(result, getFormat()))
Expand Down Expand Up @@ -137,7 +140,7 @@ export function eventsCommand(
`/api/v2/events/chain/${chain}/contract/${contract}/nfts/${tokenId}`,
{
event_type: options.eventType,
limit: Number.parseInt(options.limit, 10),
limit: parseIntOption(options.limit, "--limit"),
next: options.next,
},
)
Expand Down
5 changes: 3 additions & 2 deletions src/commands/listings.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Command } from "commander"
import type { OpenSeaClient } from "../client.js"
import { formatOutput } from "../output.js"
import { parseIntOption } from "../parse.js"
import type { Listing } from "../types/index.js"

export function listingsCommand(
Expand All @@ -22,7 +23,7 @@ export function listingsCommand(
listings: Listing[]
next?: string
}>(`/api/v2/listings/collection/${collection}/all`, {
limit: Number.parseInt(options.limit, 10),
limit: parseIntOption(options.limit, "--limit"),
next: options.next,
})
console.log(formatOutput(result, getFormat()))
Expand All @@ -42,7 +43,7 @@ export function listingsCommand(
listings: Listing[]
next?: string
}>(`/api/v2/listings/collection/${collection}/best`, {
limit: Number.parseInt(options.limit, 10),
limit: parseIntOption(options.limit, "--limit"),
next: options.next,
})
console.log(formatOutput(result, getFormat()))
Expand Down
7 changes: 4 additions & 3 deletions src/commands/nfts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Command } from "commander"
import type { OpenSeaClient } from "../client.js"
import { formatOutput } from "../output.js"
import { parseIntOption } from "../parse.js"
import type { Contract, NFT } from "../types/index.js"

export function nftsCommand(
Expand Down Expand Up @@ -34,7 +35,7 @@ export function nftsCommand(
const result = await client.get<{ nfts: NFT[]; next?: string }>(
`/api/v2/collection/${slug}/nfts`,
{
limit: Number.parseInt(options.limit, 10),
limit: parseIntOption(options.limit, "--limit"),
next: options.next,
},
)
Expand All @@ -58,7 +59,7 @@ export function nftsCommand(
const result = await client.get<{ nfts: NFT[]; next?: string }>(
`/api/v2/chain/${chain}/contract/${contract}/nfts`,
{
limit: Number.parseInt(options.limit, 10),
limit: parseIntOption(options.limit, "--limit"),
next: options.next,
},
)
Expand All @@ -83,7 +84,7 @@ export function nftsCommand(
const result = await client.get<{ nfts: NFT[]; next?: string }>(
`/api/v2/chain/${chain}/account/${address}/nfts`,
{
limit: Number.parseInt(options.limit, 10),
limit: parseIntOption(options.limit, "--limit"),
next: options.next,
},
)
Expand Down
7 changes: 4 additions & 3 deletions src/commands/offers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Command } from "commander"
import type { OpenSeaClient } from "../client.js"
import { formatOutput } from "../output.js"
import { parseIntOption } from "../parse.js"
import type { Offer } from "../types/index.js"

export function offersCommand(
Expand All @@ -22,7 +23,7 @@ export function offersCommand(
offers: Offer[]
next?: string
}>(`/api/v2/offers/collection/${collection}/all`, {
limit: Number.parseInt(options.limit, 10),
limit: parseIntOption(options.limit, "--limit"),
next: options.next,
})
console.log(formatOutput(result, getFormat()))
Expand All @@ -42,7 +43,7 @@ export function offersCommand(
offers: Offer[]
next?: string
}>(`/api/v2/offers/collection/${collection}`, {
limit: Number.parseInt(options.limit, 10),
limit: parseIntOption(options.limit, "--limit"),
next: options.next,
})
console.log(formatOutput(result, getFormat()))
Expand Down Expand Up @@ -87,7 +88,7 @@ export function offersCommand(
}>(`/api/v2/offers/collection/${collection}/traits`, {
type: options.type,
value: options.value,
limit: Number.parseInt(options.limit, 10),
limit: parseIntOption(options.limit, "--limit"),
next: options.next,
})
console.log(formatOutput(result, getFormat()))
Expand Down
9 changes: 5 additions & 4 deletions src/commands/search.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Command } from "commander"
import type { OpenSeaClient } from "../client.js"
import { formatOutput } from "../output.js"
import { parseIntOption } from "../parse.js"
import {
SEARCH_ACCOUNTS_QUERY,
SEARCH_COLLECTIONS_QUERY,
Expand Down Expand Up @@ -35,7 +36,7 @@ export function searchCommand(
collectionsByQuery: SearchCollectionResult[]
}>(SEARCH_COLLECTIONS_QUERY, {
query,
limit: Number.parseInt(options.limit, 10),
limit: parseIntOption(options.limit, "--limit"),
chains: options.chains?.split(","),
})
console.log(formatOutput(result.collectionsByQuery, getFormat()))
Expand All @@ -60,7 +61,7 @@ export function searchCommand(
}>(SEARCH_NFTS_QUERY, {
query,
collectionSlug: options.collection,
limit: Number.parseInt(options.limit, 10),
limit: parseIntOption(options.limit, "--limit"),
chains: options.chains?.split(","),
})
console.log(formatOutput(result.itemsByQuery, getFormat()))
Expand All @@ -80,7 +81,7 @@ export function searchCommand(
currenciesByQuery: SearchTokenResult[]
}>(SEARCH_TOKENS_QUERY, {
query,
limit: Number.parseInt(options.limit, 10),
limit: parseIntOption(options.limit, "--limit"),
chain: options.chain,
})
console.log(formatOutput(result.currenciesByQuery, getFormat()))
Expand All @@ -98,7 +99,7 @@ export function searchCommand(
accountsByQuery: SearchAccountResult[]
}>(SEARCH_ACCOUNTS_QUERY, {
query,
limit: Number.parseInt(options.limit, 10),
limit: parseIntOption(options.limit, "--limit"),
})
console.log(formatOutput(result.accountsByQuery, getFormat()))
})
Expand Down
Loading